rman 1.0.12 → 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/README.md +90 -70
- package/cli.js +226 -14
- package/commands/build.command.js +1 -0
- package/commands/changed.command.js +2 -2
- package/commands/changelog.command.js +13 -15
- package/commands/config.command.js +61 -0
- package/commands/diff.command.js +9 -4
- package/commands/exec.command.js +2 -8
- package/commands/github-release.command.js +1 -0
- package/commands/info.command.d.ts +9 -0
- package/commands/info.command.js +12 -2
- package/commands/run.command.js +5 -8
- package/commands/test.command.js +1 -0
- package/commands/version.command.js +53 -14
- package/constants.js +1 -1
- package/core/config.d.ts +265 -17
- package/core/config.js +651 -76
- package/core/custom-command.d.ts +133 -0
- package/core/custom-command.js +99 -0
- package/core/extends-config.d.ts +27 -0
- package/core/extends-config.js +89 -0
- package/core/manifest.d.ts +222 -0
- package/core/manifest.js +150 -0
- package/core/merge-config.d.ts +70 -0
- package/core/merge-config.js +193 -0
- package/core/package.d.ts +73 -7
- package/core/package.js +86 -24
- package/core/plugin.d.ts +112 -0
- package/core/plugin.js +189 -0
- package/core/repository.d.ts +91 -1
- package/core/repository.js +277 -132
- package/core/resolve-target.d.ts +12 -0
- package/core/resolve-target.js +33 -0
- package/core/run-step.d.ts +75 -0
- package/core/run-step.js +1 -0
- package/core/version-scheme.d.ts +134 -0
- package/core/version-scheme.js +148 -0
- package/core/workspace.d.ts +68 -0
- package/core/workspace.js +83 -0
- package/index.d.ts +55 -8
- package/index.js +42 -7
- package/interfaces/rman-config.interface.d.ts +222 -46
- package/package.json +16 -7
- package/services/change-hash.service.d.ts +88 -0
- package/services/change-hash.service.js +112 -0
- package/services/changelog.service.d.ts +8 -13
- package/services/changelog.service.js +12 -11
- package/services/conventional-commits.service.d.ts +73 -0
- package/services/conventional-commits.service.js +116 -0
- package/services/docker-publish.service.js +1 -1
- package/services/exec.service.js +1 -1
- package/services/github-release.service.d.ts +2 -2
- package/services/github-release.service.js +10 -5
- package/services/list.service.js +5 -2
- package/services/run.service.d.ts +112 -6
- package/services/run.service.js +265 -89
- package/services/system-info.d.ts +22 -7
- package/services/system-info.js +8 -23
- package/services/version-plan.service.d.ts +244 -0
- package/services/version-plan.service.js +414 -0
- package/services/version.service.d.ts +102 -82
- package/services/version.service.js +226 -434
- package/services.d.ts +5 -3
- package/services.js +5 -3
- package/utils/bin-path.d.ts +59 -0
- package/utils/bin-path.js +82 -0
- package/utils/child-tracker.d.ts +16 -0
- package/utils/child-tracker.js +30 -0
- package/utils/exec.d.ts +13 -2
- package/utils/exec.js +17 -17
- package/utils/git.d.ts +9 -3
- package/utils/git.js +10 -2
- package/utils/package-filter.d.ts +33 -2
- package/utils/package-filter.js +47 -7
- package/utils/printable-config.d.ts +15 -0
- package/utils/printable-config.js +42 -0
- package/utils/release-version.js +3 -3
- package/utils/run-bin.d.ts +46 -0
- package/utils/run-bin.js +63 -0
- package/utils/version-stamp.d.ts +14 -6
- package/utils/version-stamp.js +25 -13
- package/commands/ci.command.js +0 -30
- package/commands/clean.command.d.ts +0 -3
- package/commands/clean.command.js +0 -36
- package/commands/publish.command.d.ts +0 -3
- package/commands/publish.command.js +0 -225
- package/rmanrc.schema.json +0 -392
- package/services/ci.service.d.ts +0 -40
- package/services/ci.service.js +0 -204
- package/services/clean.service.d.ts +0 -42
- package/services/clean.service.js +0 -226
- package/services/publish.service.d.ts +0 -79
- package/services/publish.service.js +0 -273
- package/utils/change-hash.d.ts +0 -68
- package/utils/change-hash.js +0 -98
- package/utils/conventional-commits.d.ts +0 -52
- package/utils/conventional-commits.js +0 -90
- package/utils/npm-run-path.d.ts +0 -67
- package/utils/npm-run-path.js +0 -63
- package/utils/workspace-range.d.ts +0 -17
- package/utils/workspace-range.js +0 -28
- /package/commands/{ci.command.d.ts → config.command.d.ts} +0 -0
package/services/run.service.js
CHANGED
|
@@ -1,49 +1,142 @@
|
|
|
1
1
|
import os from 'node:os';
|
|
2
|
-
import
|
|
2
|
+
import { inspect } from 'node:util';
|
|
3
3
|
import colors from 'ansi-colors';
|
|
4
4
|
import { tokenize } from 'fast-tokenizer';
|
|
5
5
|
import { Task } from 'power-tasks';
|
|
6
6
|
import { exec } from '../utils/exec.js';
|
|
7
|
-
import { LOG_LEVELS, resolveRootLogLevel } from '../utils/logger.js';
|
|
7
|
+
import { LOG_LEVELS, Logger, resolveRootLogLevel } from '../utils/logger.js';
|
|
8
8
|
import { filterPackages } from '../utils/package-filter.js';
|
|
9
9
|
import { ProgressPanel } from '../utils/progress-panel.js';
|
|
10
|
+
import { runBin } from '../utils/run-bin.js';
|
|
10
11
|
export var RunService;
|
|
11
12
|
(function (RunService) {
|
|
12
13
|
/**
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
14
|
+
* Registers a source. Called by `loadPlugins` for each plugin's `runSteps`, in `plugins`
|
|
15
|
+
* declaration order - never as an import side effect, so what is registered is exactly what the
|
|
16
|
+
* repository's `.rmanrc` asked for.
|
|
17
|
+
*/
|
|
18
|
+
function addStepSource(source) {
|
|
19
|
+
/** Idempotent per source: a plugin both declares `runSteps` (registered by `loadPlugins`) and
|
|
20
|
+
* may call its own `augmentRun()` for programmatic callers, so the same function arrives
|
|
21
|
+
* twice. Pushing twice is harmless - the first match wins - but it makes the registry lie
|
|
22
|
+
* about what is in it. */
|
|
23
|
+
if (stepSources.includes(source))
|
|
24
|
+
return;
|
|
25
|
+
stepSources.push(source);
|
|
26
|
+
}
|
|
27
|
+
RunService.addStepSource = addStepSource;
|
|
28
|
+
/** For tests, which would otherwise leak a source into every later case in the process. */
|
|
29
|
+
function clearStepSources() {
|
|
30
|
+
stepSources.length = 0;
|
|
31
|
+
}
|
|
32
|
+
RunService.clearStepSources = clearStepSources;
|
|
33
|
+
/**
|
|
34
|
+
* What the *package itself* declares for the lifecycle `script`, from the contributed sources
|
|
35
|
+
* alone - no `.rmanrc` involved. `undefined` when it declares nothing.
|
|
36
|
+
*
|
|
37
|
+
* Exported because `run` is not the only lifecycle rman wraps: `version` runs hooks around the
|
|
38
|
+
* version write, and npm spells those `preversion`/`version`/`postversion` in `package.json` -
|
|
39
|
+
* which is the same `pre<script>`/`<script>`/`post<script>` shape a step source already answers.
|
|
40
|
+
* So `VersionService` asks here for `'version'` instead of reading `manifest.raw.scripts` itself,
|
|
41
|
+
* and npm's version lifecycle keeps working with no second seam and no extra line in any plugin.
|
|
42
|
+
* A plugin for another ecosystem gets its own lifecycle hooks the moment it contributes steps.
|
|
43
|
+
*/
|
|
44
|
+
function contributedSlots(pkg, script) {
|
|
45
|
+
return firstContributed(pkg, script);
|
|
46
|
+
}
|
|
47
|
+
RunService.contributedSlots = contributedSlots;
|
|
48
|
+
/**
|
|
49
|
+
* Runs one slot of a lifecycle belonging to some operation other than `run` itself - `version`'s
|
|
50
|
+
* hooks around the version write are the only one so far.
|
|
51
|
+
*
|
|
52
|
+
* **Here rather than in `VersionService`, because the rule it applies is this service's**: the
|
|
53
|
+
* package's own declaration for `script` wins over the caller's `fallback`, slot by slot, exactly
|
|
54
|
+
* as `getScriptSteps` decides it for `run`. Kept in two places that rule would drift, and one of
|
|
55
|
+
* the copies would sit in the file that writes versions - which now runs no command of its own at
|
|
56
|
+
* all.
|
|
57
|
+
*
|
|
58
|
+
* `fallback` is the caller's own configured step(s), **already evaluated**: `version`'s three
|
|
59
|
+
* paths are in `DEFERRED_PATHS` precisely because only the caller can bind
|
|
60
|
+
* `${{ pkg.targetVersion }}`, so interpolating here would either be too early or need a scope this
|
|
61
|
+
* service has no business holding.
|
|
33
62
|
*
|
|
34
|
-
* A
|
|
35
|
-
*
|
|
36
|
-
*
|
|
37
|
-
* test: mocha # same as test: { exec: mocha }
|
|
63
|
+
* **A list, not one joined string.** `VersionService` used to `join(' && ')` an array into a
|
|
64
|
+
* single shell line, which a function step cannot be part of - and which quietly changed the
|
|
65
|
+
* semantics of the shell case too, since `cd x && y` in one process is not the same as two.
|
|
38
66
|
*/
|
|
67
|
+
async function runLifecycleSlot(pkg, script, slot, fallback) {
|
|
68
|
+
const own = contributedSlots(pkg, script)?.[slot] ?? [];
|
|
69
|
+
const values = own.length ? own : (fallback ?? []);
|
|
70
|
+
for (const value of values) {
|
|
71
|
+
if (typeof value === 'function') {
|
|
72
|
+
await value(createStepContext(pkg, pkg.dirname));
|
|
73
|
+
continue;
|
|
74
|
+
}
|
|
75
|
+
await exec(value, { cwd: pkg.dirname, stdio: 'inherit' });
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
RunService.runLifecycleSlot = runLifecycleSlot;
|
|
39
79
|
function getConfig(pkg, script) {
|
|
40
80
|
const runCfg = pkg.config?.run;
|
|
41
81
|
const cfg = runCfg && typeof runCfg === 'object' ? runCfg[script] : undefined;
|
|
42
|
-
|
|
82
|
+
/** The bare-value shorthand. A function is `typeof 'function'`, not `'object'`, so without
|
|
83
|
+
* naming it here `run: { build: myFn }` fell through to the `{}` below - the long form would
|
|
84
|
+
* have worked and the short one silently done nothing, an arbitrary difference. */
|
|
85
|
+
if (typeof cfg === 'string' || typeof cfg === 'function' || Array.isArray(cfg))
|
|
43
86
|
return { exec: cfg };
|
|
44
87
|
return cfg && typeof cfg === 'object' ? cfg : {};
|
|
45
88
|
}
|
|
46
89
|
RunService.getConfig = getConfig;
|
|
90
|
+
/**
|
|
91
|
+
* The context a function step or `if` is handed - see [`RunStepContext`](../core/run-step.ts).
|
|
92
|
+
*
|
|
93
|
+
* `runBin` and `logger` are bound to *this run* rather than left to be imported, which is the
|
|
94
|
+
* whole reason they are handed over: an imported `runBin` knows neither the cwd nor the resolved
|
|
95
|
+
* log level.
|
|
96
|
+
*/
|
|
97
|
+
/**
|
|
98
|
+
* A `run.<script>.before`/`.exec`/`.after` (or `version.<slot>`) value: one step, or several to
|
|
99
|
+
* run in sequence. A shell command or a function, and a list may mix them.
|
|
100
|
+
*
|
|
101
|
+
* **Anything else throws, naming the path.** It used to `return []`, which meant a value rman did
|
|
102
|
+
* not recognize was dropped with no trace: writing a function here - the obvious guess, and now
|
|
103
|
+
* the supported form - produced `1 succeeded, 0 failed` with the step never run (measured). A
|
|
104
|
+
* configuration mistake has to be loud; silence here reads as success.
|
|
105
|
+
*
|
|
106
|
+
* Exported, and the only implementation: `VersionService` used to carry a second one that behaved
|
|
107
|
+
* differently, which is how `version.<slot>` came to join its array with `' && '`.
|
|
108
|
+
*/
|
|
109
|
+
function normalizeScriptValue(value, at) {
|
|
110
|
+
const items = Array.isArray(value) ? value : [value];
|
|
111
|
+
const steps = [];
|
|
112
|
+
for (let i = 0; i < items.length; i++) {
|
|
113
|
+
const item = items[i];
|
|
114
|
+
/** An empty string and an absent value are both "nothing here", which is how a `"[*]"` block
|
|
115
|
+
* declaring a slot some packages don't use has always behaved. */
|
|
116
|
+
if (item === undefined || item === null || item === '')
|
|
117
|
+
continue;
|
|
118
|
+
if (typeof item === 'string' || typeof item === 'function') {
|
|
119
|
+
steps.push(item);
|
|
120
|
+
continue;
|
|
121
|
+
}
|
|
122
|
+
const where = Array.isArray(value) ? `${at}[${i}]` : at;
|
|
123
|
+
throw new Error(`"${where}" must be a shell command or a function, but it is ${describeValue(item)}.\n` +
|
|
124
|
+
` A list of either (or both) runs them in sequence.`);
|
|
125
|
+
}
|
|
126
|
+
return steps;
|
|
127
|
+
}
|
|
128
|
+
RunService.normalizeScriptValue = normalizeScriptValue;
|
|
129
|
+
function createStepContext(pkg, cwd) {
|
|
130
|
+
const logLevel = resolveRootLogLevel(pkg.repository);
|
|
131
|
+
return {
|
|
132
|
+
pkg,
|
|
133
|
+
repository: pkg.repository,
|
|
134
|
+
cwd,
|
|
135
|
+
runBin: (bin, argv, opts) => runBin(bin, argv, { cwd, logLevel, ...opts }),
|
|
136
|
+
logger: new Logger(logLevel),
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
RunService.createStepContext = createStepContext;
|
|
47
140
|
function resolveEnvPlaceholders(value) {
|
|
48
141
|
return value.replace(/\{([A-Za-z_][A-Za-z0-9_]*)\}/g, (_, name) => process.env[name] ?? '');
|
|
49
142
|
}
|
|
@@ -151,7 +244,7 @@ export var RunService;
|
|
|
151
244
|
* abort using *its own* resolved bail setting (see `runSteps` below) - power-tasks' own
|
|
152
245
|
* `bail` is a single blanket policy for the whole batch, it can't vary per package. */
|
|
153
246
|
let rootTask;
|
|
154
|
-
const runSteps = async (ctx, pkgLabel, steps, cwd, pkgBail, pkgLogLevel) => {
|
|
247
|
+
const runSteps = async (ctx, pkg, pkgLabel, steps, cwd, pkgBail, pkgLogLevel) => {
|
|
155
248
|
ctx.status = 'running';
|
|
156
249
|
ctx.startedAt = Date.now();
|
|
157
250
|
try {
|
|
@@ -160,15 +253,14 @@ export var RunService;
|
|
|
160
253
|
ctx.currentStep = step.name;
|
|
161
254
|
ctx.stepIndex = i;
|
|
162
255
|
if (panel.enabled) {
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
}
|
|
171
|
-
});
|
|
256
|
+
const onLine = (line) => {
|
|
257
|
+
ctx.log.push(line);
|
|
258
|
+
ctx.lastLine = line;
|
|
259
|
+
};
|
|
260
|
+
if (step.run)
|
|
261
|
+
await runFunctionStep(step.run, pkg, cwd, onLine);
|
|
262
|
+
else
|
|
263
|
+
await exec(step.command, { cwd, stdio: 'pipe', onLine });
|
|
172
264
|
}
|
|
173
265
|
else {
|
|
174
266
|
/** Match the classic rman output: raw command output streams straight through
|
|
@@ -177,7 +269,12 @@ export var RunService;
|
|
|
177
269
|
const stepStart = Date.now();
|
|
178
270
|
let stepError;
|
|
179
271
|
try {
|
|
180
|
-
|
|
272
|
+
/** No capture with the panel off: the step owns the terminal, exactly as a shell
|
|
273
|
+
* step's `stdio: 'inherit'` does. */
|
|
274
|
+
if (step.run)
|
|
275
|
+
await runFunctionStep(step.run, pkg, cwd);
|
|
276
|
+
else
|
|
277
|
+
await exec(step.command, { cwd, stdio: 'inherit' });
|
|
181
278
|
}
|
|
182
279
|
catch (e) {
|
|
183
280
|
stepError = e;
|
|
@@ -209,19 +306,25 @@ export var RunService;
|
|
|
209
306
|
* Only in a monorepo. Without one the root *is* the single package, already in the loop below
|
|
210
307
|
* with the same hooks and the same directory - a bookend would simply run each of them a
|
|
211
308
|
* second time. */
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
const rootSkipped = !!cwdScope ||
|
|
309
|
+
/** Short-circuited deliberately: a root already out of the run for a structural reason must not
|
|
310
|
+
* have its `if` evaluated, now that evaluating one can mean calling the repository's own code. */
|
|
311
|
+
const rootSkipped = !!cwdScope ||
|
|
312
|
+
!repository.monorepo ||
|
|
313
|
+
rootCfg.skip === true ||
|
|
314
|
+
!(await passesIf(repository, repository.rootPackage, rootCfg.if, repository.dirname, ifStatusCache));
|
|
215
315
|
const rootSteps = rootSkipped ? [] : getScriptSteps(repository.rootPackage, script);
|
|
216
|
-
|
|
217
|
-
|
|
316
|
+
/** Filtered on the slot, not on `'pre' + script`: the step labels are `before`/`exec`/`after`
|
|
317
|
+
* now - the same words the config uses - rather than npm's `pre<script>` naming, which moved
|
|
318
|
+
* out with the package.json source. */
|
|
319
|
+
const rootPre = rootSteps.filter(s => s.name === 'before');
|
|
320
|
+
const rootPost = rootSteps.filter(s => s.name === 'after');
|
|
218
321
|
const rootPreName = 'root (pre)';
|
|
219
322
|
const rootPostName = 'root (post)';
|
|
220
323
|
if (rootPre.length) {
|
|
221
324
|
const ctx = panel.addItem(rootPreName, rootPre.length);
|
|
222
325
|
const pkgBail = resolveBail(options.bail, repository.rootPackage, script, true);
|
|
223
326
|
const pkgLogLevel = resolveLogLevel(options.logLevel, repository.rootPackage, script, logLevelDefault);
|
|
224
|
-
children.push(new Task(() => runSteps(ctx, 'root', rootPre, repository.dirname, pkgBail, pkgLogLevel), {
|
|
327
|
+
children.push(new Task(() => runSteps(ctx, repository.rootPackage, 'root', rootPre, repository.dirname, pkgBail, pkgLogLevel), {
|
|
225
328
|
name: ctx.name,
|
|
226
329
|
exclusive: true,
|
|
227
330
|
}));
|
|
@@ -231,8 +334,7 @@ export var RunService;
|
|
|
231
334
|
const pkgCfg = getConfig(pkg, script);
|
|
232
335
|
if (pkgCfg.skip === true)
|
|
233
336
|
continue;
|
|
234
|
-
|
|
235
|
-
if (pkgIf && !(await evaluateIf(repository, pkg, pkgIf, ifStatusCache)))
|
|
337
|
+
if (!(await passesIf(repository, pkg, pkgCfg.if, pkg.dirname, ifStatusCache)))
|
|
236
338
|
continue;
|
|
237
339
|
const steps = getScriptSteps(pkg, script);
|
|
238
340
|
if (steps.length)
|
|
@@ -246,10 +348,12 @@ export var RunService;
|
|
|
246
348
|
const pkgTopo = resolveBool(options.topo, pkg, script, 'topo', topo);
|
|
247
349
|
const pkgBail = resolveBail(options.bail, pkg, script, true);
|
|
248
350
|
const pkgLogLevel = resolveLogLevel(options.logLevel, pkg, script, logLevelDefault);
|
|
249
|
-
|
|
351
|
+
/** power-tasks identifies a task by its name string, so the graph is handed over as names -
|
|
352
|
+
* the references are what rman reasons with, the names are what the scheduler wants. */
|
|
353
|
+
const dependencies = pkgTopo ? pkg.dependencies.filter(d => stepsByPackage.has(d.name)).map(d => d.name) : [];
|
|
250
354
|
if (rootPre.length)
|
|
251
355
|
dependencies.push(rootPreName);
|
|
252
|
-
children.push(new Task(() => runSteps(ctx, pkg.name, steps, pkg.dirname, pkgBail, pkgLogLevel), {
|
|
356
|
+
children.push(new Task(() => runSteps(ctx, pkg, pkg.name, steps, pkg.dirname, pkgBail, pkgLogLevel), {
|
|
253
357
|
name: ctx.name,
|
|
254
358
|
dependencies,
|
|
255
359
|
}));
|
|
@@ -258,7 +362,7 @@ export var RunService;
|
|
|
258
362
|
const ctx = panel.addItem(rootPostName, rootPost.length);
|
|
259
363
|
const pkgBail = resolveBail(options.bail, repository.rootPackage, script, true);
|
|
260
364
|
const pkgLogLevel = resolveLogLevel(options.logLevel, repository.rootPackage, script, logLevelDefault);
|
|
261
|
-
children.push(new Task(() => runSteps(ctx, 'root', rootPost, repository.dirname, pkgBail, pkgLogLevel), {
|
|
365
|
+
children.push(new Task(() => runSteps(ctx, repository.rootPackage, 'root', rootPost, repository.dirname, pkgBail, pkgLogLevel), {
|
|
262
366
|
name: ctx.name,
|
|
263
367
|
exclusive: true,
|
|
264
368
|
/** Must wait for every package task to finish, not just be "exclusive" once it starts. */
|
|
@@ -341,7 +445,7 @@ function printLegacyExecutingLine(commandName, pkgLabel, step, level) {
|
|
|
341
445
|
if (LOG_LEVELS.indexOf(level) < LOG_LEVELS.indexOf('verbose'))
|
|
342
446
|
return;
|
|
343
447
|
const sep = colors.gray('┆');
|
|
344
|
-
console.log(colors.magenta('verbose'), commandName, colors.cyan(pkgLabel), sep, colors.cyanBright.bold(step.name), colors.cyanBright.bold('executing'), sep, step.
|
|
448
|
+
console.log(colors.magenta('verbose'), commandName, colors.cyan(pkgLabel), sep, colors.cyanBright.bold(step.name), colors.cyanBright.bold('executing'), sep, step.label);
|
|
345
449
|
}
|
|
346
450
|
function printLegacyStepLine(commandName, pkgLabel, step, durationMs, level, error) {
|
|
347
451
|
if (!error && LOG_LEVELS.indexOf(level) < LOG_LEVELS.indexOf('info'))
|
|
@@ -351,57 +455,129 @@ function printLegacyStepLine(commandName, pkgLabel, step, durationMs, level, err
|
|
|
351
455
|
const sep = colors.gray('┆');
|
|
352
456
|
const levelLabel = error ? colors.red('error') : colors.green('info');
|
|
353
457
|
const status = error ? colors.red.bold('failed') : colors.green.bold('success');
|
|
354
|
-
console.log(levelLabel, commandName, colors.cyan(pkgLabel), sep, colors.cyanBright.bold(step.name), status, sep, step.
|
|
458
|
+
console.log(levelLabel, commandName, colors.cyan(pkgLabel), sep, colors.cyanBright.bold(step.name), status, sep, step.label, colors.yellow(` (${durationMs} ms)`));
|
|
459
|
+
}
|
|
460
|
+
function describeValue(value) {
|
|
461
|
+
if (Array.isArray(value))
|
|
462
|
+
return 'a nested array';
|
|
463
|
+
if (value && typeof value === 'object')
|
|
464
|
+
return 'an object';
|
|
465
|
+
return `a ${typeof value} (${JSON.stringify(value)})`;
|
|
355
466
|
}
|
|
356
|
-
/**
|
|
357
|
-
function
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
if (
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
467
|
+
/** One step, with the label the panel and the per-step log show. A function's own name - so
|
|
468
|
+
* `function copyDocs()` and `const copyDocs = () => {}` both read as `copyDocs` - falling back to
|
|
469
|
+
* the slot's own word for one passed inline, which has no name at all. */
|
|
470
|
+
function toStep(slot, value) {
|
|
471
|
+
if (typeof value === 'function')
|
|
472
|
+
return { name: slot, label: value.name || `${slot} (js)`, run: value };
|
|
473
|
+
return { name: slot, label: value, command: value };
|
|
474
|
+
}
|
|
475
|
+
/**
|
|
476
|
+
* Runs a function step.
|
|
477
|
+
*
|
|
478
|
+
* **`console` is redirected while it runs, but only when the panel is on** - and that is the same
|
|
479
|
+
* split a shell step already makes. `exec` hands the panel its output through `stdio: 'pipe'` and
|
|
480
|
+
* `onLine`, so a function writing straight to the real stdout would print *over* the panel it is
|
|
481
|
+
* being rendered inside. With the panel off, `exec` uses `stdio: 'inherit'` and the step owns the
|
|
482
|
+
* terminal; a function gets the same, untouched.
|
|
483
|
+
*
|
|
484
|
+
* Restored in a `finally`, because a step that throws must not leave the rest of the run writing
|
|
485
|
+
* into a log nobody reads.
|
|
486
|
+
*/
|
|
487
|
+
async function runFunctionStep(run, pkg, cwd, onLine) {
|
|
488
|
+
const context = RunService.createStepContext(pkg, cwd);
|
|
489
|
+
if (!onLine) {
|
|
490
|
+
await run(context);
|
|
491
|
+
return;
|
|
365
492
|
}
|
|
366
|
-
|
|
493
|
+
const console_ = globalThis.console;
|
|
494
|
+
const original = {};
|
|
495
|
+
for (const method of CAPTURED_CONSOLE) {
|
|
496
|
+
original[method] = console_[method];
|
|
497
|
+
console_[method] = (...args) => {
|
|
498
|
+
/** Split, because one `console.log` may carry several lines and the panel's log is a list of
|
|
499
|
+
* them - a multi-line entry would render as one unreadable row. */
|
|
500
|
+
for (const line of format(args).split('\n'))
|
|
501
|
+
onLine(line);
|
|
502
|
+
};
|
|
503
|
+
}
|
|
504
|
+
try {
|
|
505
|
+
await run(context);
|
|
506
|
+
}
|
|
507
|
+
finally {
|
|
508
|
+
for (const method of CAPTURED_CONSOLE)
|
|
509
|
+
console_[method] = original[method];
|
|
510
|
+
}
|
|
511
|
+
}
|
|
512
|
+
const CAPTURED_CONSOLE = ['log', 'info', 'warn', 'error', 'debug'];
|
|
513
|
+
function format(args) {
|
|
514
|
+
return args.map(arg => (typeof arg === 'string' ? arg : inspect(arg))).join(' ');
|
|
367
515
|
}
|
|
368
516
|
/**
|
|
369
|
-
*
|
|
370
|
-
*
|
|
371
|
-
*
|
|
372
|
-
*
|
|
373
|
-
*
|
|
374
|
-
|
|
517
|
+
* Whether a script runs for `pkg` at all - `run.<script>.if`, in either of its two forms.
|
|
518
|
+
*
|
|
519
|
+
* The function form is checked **first**: `parseIfExpr` answers `undefined` for anything that is
|
|
520
|
+
* not a string, which the caller reads as "no condition given", so a function reaching it would be
|
|
521
|
+
* a condition that silently always passed.
|
|
522
|
+
*/
|
|
523
|
+
async function passesIf(repository, pkg, raw, cwd, statusCache) {
|
|
524
|
+
if (typeof raw === 'function') {
|
|
525
|
+
return !!(await raw(RunService.createStepContext(pkg, cwd)));
|
|
526
|
+
}
|
|
527
|
+
const node = RunService.parseIfExpr(raw);
|
|
528
|
+
return node ? RunService.evaluateIf(repository, pkg, node, statusCache) : true;
|
|
529
|
+
}
|
|
530
|
+
/**
|
|
531
|
+
* Resolves a package's steps for `script`, from its `.rmanrc` and from whatever sources plugins
|
|
532
|
+
* contributed (`RunService.addStepSource`).
|
|
533
|
+
*
|
|
534
|
+
* **Resolved slot by slot, not script by script**, and that is load-bearing: a package whose
|
|
535
|
+
* `package.json` defines only `build` still gets `before`/`after` from a `"[*]"` config block. A
|
|
536
|
+
* whole-script precedence would silently drop them.
|
|
537
|
+
*
|
|
538
|
+
* Precedence per slot, first match winning:
|
|
539
|
+
*
|
|
540
|
+
* 1. `run.<script>.override: true` -> the config, always. The escape hatch for "ignore what the
|
|
541
|
+
* package says it does".
|
|
542
|
+
* 2. a **contributed** source - `package.json#scripts` via `rman-node`. It wins over the config
|
|
543
|
+
* because it is the *package's own* declaration, while a config value typically arrives
|
|
544
|
+
* cascaded from the root or an `extends` base; the more specific statement wins, as everywhere
|
|
545
|
+
* else in rman.
|
|
546
|
+
* 3. the config's own `before`/`exec`/`after`, letting a package run a script it never declared.
|
|
547
|
+
* 4. nothing - and an empty result is what tells `runScript` this package defines no such script.
|
|
375
548
|
*/
|
|
376
549
|
function getScriptSteps(pkg, script) {
|
|
377
550
|
const cfg = RunService.getConfig(pkg, script);
|
|
378
|
-
const
|
|
379
|
-
const
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
if (cfg.override === true || !hasOwn)
|
|
385
|
-
json.scripts[key] = value;
|
|
551
|
+
const override = cfg.override === true;
|
|
552
|
+
const contributed = firstContributed(pkg, script);
|
|
553
|
+
const fromConfig = {
|
|
554
|
+
before: RunService.normalizeScriptValue(cfg.before, `run.${script}.before`),
|
|
555
|
+
exec: RunService.normalizeScriptValue(cfg.exec, `run.${script}.exec`),
|
|
556
|
+
after: RunService.normalizeScriptValue(cfg.after, `run.${script}.after`),
|
|
386
557
|
};
|
|
387
|
-
applyConfigScript(script, cfg.exec);
|
|
388
|
-
applyConfigScript('pre' + script, cfg.before);
|
|
389
|
-
applyConfigScript('post' + script, cfg.after);
|
|
390
|
-
json.scripts[script] = json.scripts[script] || '#';
|
|
391
|
-
const info = parseNpmScript(json, 'npm run ' + script);
|
|
392
|
-
if (!info?.raw?.length)
|
|
393
|
-
return [];
|
|
394
558
|
const steps = [];
|
|
395
|
-
for (const
|
|
396
|
-
const
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
steps.push(
|
|
401
|
-
}
|
|
559
|
+
for (const slot of SCRIPT_SLOTS) {
|
|
560
|
+
const own = contributed?.[slot] ?? [];
|
|
561
|
+
const configured = fromConfig[slot] ?? [];
|
|
562
|
+
const values = override ? (configured.length ? configured : own) : own.length ? own : configured;
|
|
563
|
+
for (const value of values)
|
|
564
|
+
steps.push(toStep(slot, value));
|
|
402
565
|
}
|
|
403
566
|
return steps;
|
|
404
567
|
}
|
|
568
|
+
/** The first source that says this package declares the script at all. Declaration order, so a
|
|
569
|
+
* repository listing two plugins gets a predictable answer rather than a merged one. */
|
|
570
|
+
function firstContributed(pkg, script) {
|
|
571
|
+
for (const source of stepSources) {
|
|
572
|
+
const slots = source(pkg, script);
|
|
573
|
+
if (slots && (slots.before?.length || slots.exec?.length || slots.after?.length))
|
|
574
|
+
return slots;
|
|
575
|
+
}
|
|
576
|
+
return undefined;
|
|
577
|
+
}
|
|
578
|
+
/** In execution order - `before`, the script itself, then `after`. */
|
|
579
|
+
const SCRIPT_SLOTS = ['before', 'exec', 'after'];
|
|
580
|
+
const stepSources = [];
|
|
405
581
|
/**
|
|
406
582
|
* Splits on whitespace, but a `(...)` group collapses to a single token holding its inner text
|
|
407
583
|
* (parens stripped, inner whitespace kept) - `evaluate` below re-tokenizes any token that still
|
|
@@ -8,15 +8,30 @@ export declare namespace SystemInfo {
|
|
|
8
8
|
root: string;
|
|
9
9
|
packageCount: number;
|
|
10
10
|
}
|
|
11
|
-
/** Whatever `envinfo` reports
|
|
12
|
-
* installed rman/typescript versions), grouped by category - shape is `envinfo`'s own, not ours. */
|
|
11
|
+
/** Whatever `envinfo` reports, grouped by category - shape is `envinfo`'s own, not ours. */
|
|
13
12
|
type SystemInfo = Record<string, Record<string, unknown>>;
|
|
14
13
|
/**
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
14
|
+
* **Deliberately empty of anything language-specific.** A plugin adds what its own ecosystem
|
|
15
|
+
* needs by augmenting this interface - see `rman-node`, which adds `packageManager` and turns it
|
|
16
|
+
* into `envinfo` categories. There is no `packageManager` here, and that is the point: the core
|
|
17
|
+
* has no opinion about npm, so a repository in another language cannot end up reporting
|
|
18
|
+
* "npm: Not Found", which is a wrong answer rather than a missing feature.
|
|
19
19
|
*/
|
|
20
|
-
|
|
20
|
+
interface Options {
|
|
21
|
+
/**
|
|
22
|
+
* The repository the report is about. The core uses it for nothing - `getRepositoryInfo` is a
|
|
23
|
+
* separate call - and it is here **for augmentations**, which need somewhere to read a setting
|
|
24
|
+
* from: `rman-node` takes `.rmanrc "packageManager"` off it.
|
|
25
|
+
*/
|
|
26
|
+
repository?: Repository;
|
|
27
|
+
/** Extra `envinfo` categories, merged **over** the defaults - so an augmentation can replace
|
|
28
|
+
* `Binaries` rather than only append to it. */
|
|
29
|
+
envinfo?: envinfo.RunConfig;
|
|
30
|
+
}
|
|
31
|
+
/** The shape of `getSystemInfo` - named so a plugin can wrap it without restating the signature
|
|
32
|
+
* (see `rman-node`'s system-info augmentation). */
|
|
33
|
+
type GetSystemInfo = (options?: Options) => Promise<SystemInfo.SystemInfo>;
|
|
34
|
+
/** What this machine is, in terms true of any repository: OS, CPU, memory, shell, Node, git. */
|
|
35
|
+
function getSystemInfo(options?: Options): Promise<SystemInfo.SystemInfo>;
|
|
21
36
|
function getRepositoryInfo(repository: Repository): SystemInfo.RepositoryInfo;
|
|
22
37
|
}
|
package/services/system-info.js
CHANGED
|
@@ -1,33 +1,18 @@
|
|
|
1
1
|
import envinfo from 'envinfo';
|
|
2
2
|
export var SystemInfo;
|
|
3
3
|
(function (SystemInfo) {
|
|
4
|
-
/**
|
|
5
|
-
|
|
6
|
-
const PACKAGE_MANAGER_BINARY = {
|
|
7
|
-
npm: 'npm',
|
|
8
|
-
yarn: 'Yarn',
|
|
9
|
-
pnpm: 'pnpm',
|
|
10
|
-
bun: 'bun',
|
|
11
|
-
};
|
|
12
|
-
/**
|
|
13
|
-
* `packageManager` (the repository's resolved `.rmanrc "packageManager"`, default `'npm'`)
|
|
14
|
-
* decides which package manager's version actually gets queried/reported - a pnpm-configured
|
|
15
|
-
* repo has no real use for npm's own version, since every package-manager-aware command
|
|
16
|
-
* (`ci`/`publish`) already shells out to pnpm, not npm, for it.
|
|
17
|
-
*/
|
|
18
|
-
async function getSystemInfo(packageManager, options) {
|
|
4
|
+
/** What this machine is, in terms true of any repository: OS, CPU, memory, shell, Node, git. */
|
|
5
|
+
async function getSystemInfo(options) {
|
|
19
6
|
return JSON.parse(await envinfo.run({
|
|
20
7
|
System: ['OS', 'CPU', 'Memory', 'Shell'],
|
|
21
|
-
Binaries: ['Node'
|
|
8
|
+
Binaries: ['Node'],
|
|
22
9
|
Utilities: ['Git'],
|
|
23
|
-
|
|
24
|
-
npmGlobalPackages: ['typescript'],
|
|
25
|
-
...options,
|
|
10
|
+
...options?.envinfo,
|
|
26
11
|
},
|
|
27
|
-
// showNotFound: without it, envinfo *omits* a configured-but-uninstalled
|
|
28
|
-
//
|
|
29
|
-
// "Not Found" - worth surfacing, since it means
|
|
30
|
-
//
|
|
12
|
+
// showNotFound: without it, envinfo *omits* a configured-but-uninstalled binary from
|
|
13
|
+
// Binaries entirely (indistinguishable from never having asked) instead of reporting
|
|
14
|
+
// "Not Found" - worth surfacing, since it means something a config points at isn't
|
|
15
|
+
// actually on this machine.
|
|
31
16
|
{ json: true, showNotFound: true }));
|
|
32
17
|
}
|
|
33
18
|
SystemInfo.getSystemInfo = getSystemInfo;
|