rman 1.2.5 → 2.0.0-beta.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +63 -19
- package/cli.d.ts +5 -0
- package/cli.js +214 -88
- package/commands/build.command.d.ts +177 -3
- package/commands/build.command.js +20 -10
- package/commands/changed.command.d.ts +80 -3
- package/commands/changed.command.js +19 -12
- package/commands/changelog.command.d.ts +192 -3
- package/commands/changelog.command.js +87 -43
- package/commands/config.command.d.ts +44 -3
- package/commands/config.command.js +30 -19
- package/commands/diff.command.d.ts +37 -3
- package/commands/diff.command.js +25 -16
- package/commands/exec.command.d.ts +193 -3
- package/commands/exec.command.js +60 -58
- package/commands/github-release.command.d.ts +154 -3
- package/commands/github-release.command.js +67 -37
- package/commands/import.command.d.ts +36 -3
- package/commands/import.command.js +28 -20
- package/commands/info.command.d.ts +35 -7
- package/commands/info.command.js +36 -30
- package/commands/list.command.d.ts +163 -3
- package/commands/list.command.js +109 -71
- package/commands/publish.command.d.ts +231 -0
- package/commands/publish.command.js +304 -0
- package/commands/run.command.d.ts +186 -6
- package/commands/run.command.js +26 -72
- package/commands/test.command.d.ts +173 -3
- package/commands/test.command.js +16 -10
- package/commands/version.command.d.ts +317 -3
- package/commands/version.command.js +149 -69
- package/commands.d.ts +32 -0
- package/commands.js +28 -0
- package/constants.js +1 -1
- package/core/application.d.ts +116 -0
- package/core/application.js +143 -0
- package/core/command-builder.d.ts +14 -0
- package/core/command-builder.js +78 -0
- package/core/config.d.ts +180 -50
- package/core/config.js +332 -153
- package/core/core-services.d.ts +14 -0
- package/core/core-services.js +30 -0
- package/core/core-targets.d.ts +14 -0
- package/core/core-targets.js +16 -0
- package/core/custom-command.d.ts +42 -6
- package/core/custom-command.js +44 -17
- package/core/extends-config.d.ts +13 -5
- package/core/extends-config.js +52 -12
- package/core/load-config-module.d.ts +28 -0
- package/core/load-config-module.js +42 -0
- package/core/manifest.d.ts +47 -25
- package/core/manifest.js +51 -69
- package/core/merge-config.d.ts +33 -34
- package/core/merge-config.js +138 -93
- package/core/package.d.ts +145 -17
- package/core/package.js +128 -36
- package/core/plugin-loader.d.ts +65 -0
- package/core/plugin-loader.js +234 -0
- package/core/plugin.d.ts +135 -90
- package/core/plugin.js +70 -173
- package/core/publish-target.d.ts +124 -0
- package/core/publish-target.js +30 -0
- package/core/registry.d.ts +30 -0
- package/core/registry.js +47 -0
- package/core/repository.d.ts +72 -9
- package/core/repository.js +293 -43
- package/core/resolve-target.d.ts +1 -1
- package/core/resolve-target.js +1 -1
- package/core/service.d.ts +49 -0
- package/core/service.js +40 -0
- package/core/version-scheme.d.ts +23 -1
- package/core/version-scheme.js +29 -1
- package/core/workspace.d.ts +84 -33
- package/core/workspace.js +63 -22
- package/index.d.ts +111 -14
- package/index.js +85 -9
- package/interfaces/rman-config.interface.d.ts +739 -212
- package/interfaces/rman-config.interface.js +61 -1
- package/package.json +2 -1
- package/plugins/builtins.d.ts +44 -0
- package/plugins/builtins.js +33 -0
- package/plugins/detect.d.ts +78 -0
- package/plugins/detect.js +70 -0
- package/plugins/node/augmentation/rman.augmentation.d.ts +84 -0
- package/plugins/node/augmentation/rman.augmentation.js +1 -0
- package/plugins/node/augmentation/system-info.augmentation.d.ts +26 -0
- package/plugins/node/augmentation/system-info.augmentation.js +79 -0
- package/plugins/node/commands/ci.command.d.ts +131 -0
- package/plugins/node/commands/ci.command.js +59 -0
- package/plugins/node/commands/clean.command.d.ts +183 -0
- package/plugins/node/commands/clean.command.js +73 -0
- package/plugins/node/index.d.ts +29 -0
- package/plugins/node/index.js +40 -0
- package/plugins/node/node-config.interface.d.ts +77 -0
- package/plugins/node/node-config.interface.js +7 -0
- package/plugins/node/node-manifest.provider.d.ts +68 -0
- package/plugins/node/node-manifest.provider.js +125 -0
- package/plugins/node/node.platform.d.ts +53 -0
- package/plugins/node/node.platform.js +134 -0
- package/plugins/node/npm-publish-target.d.ts +73 -0
- package/plugins/node/npm-publish-target.js +96 -0
- package/plugins/node/services/ci.service.d.ts +47 -0
- package/plugins/node/services/ci.service.js +213 -0
- package/plugins/node/services/clean.service.d.ts +53 -0
- package/plugins/node/services/clean.service.js +237 -0
- package/plugins/node/services/publish.service.d.ts +114 -0
- package/plugins/node/services/publish.service.js +371 -0
- package/plugins/node/services/version-plan.service.d.ts +44 -0
- package/plugins/node/services/version-plan.service.js +58 -0
- package/plugins/node/utils/npm-view.d.ts +48 -0
- package/plugins/node/utils/npm-view.js +71 -0
- package/plugins/node/utils/workspace-range.d.ts +26 -0
- package/plugins/node/utils/workspace-range.js +28 -0
- package/services/change-hash.service.d.ts +2 -2
- package/services/change-hash.service.js +2 -2
- package/services/changelog.service.d.ts +62 -51
- package/services/changelog.service.js +14 -11
- package/services/docker-publish.service.d.ts +50 -29
- package/services/docker-publish.service.js +43 -20
- package/services/exec.service.d.ts +23 -12
- package/services/exec.service.js +14 -9
- package/services/github-release.service.d.ts +44 -33
- package/services/github-release.service.js +13 -10
- package/services/import.service.d.ts +25 -14
- package/services/import.service.js +9 -5
- package/services/list.service.d.ts +62 -10
- package/services/list.service.js +62 -15
- package/services/run.service.d.ts +22 -13
- package/services/run.service.js +262 -223
- package/services/version-plan.service.d.ts +27 -4
- package/services/version-plan.service.js +42 -15
- package/services/version.service.d.ts +31 -11
- package/services/version.service.js +29 -13
- package/targets/docker.target.d.ts +53 -0
- package/targets/docker.target.js +40 -0
- package/utils/bin-path.d.ts +6 -7
- package/utils/bin-path.js +7 -18
- package/utils/branch-guard.d.ts +29 -0
- package/utils/branch-guard.js +31 -0
- package/utils/exec.d.ts +10 -0
- package/utils/exec.js +1 -1
- package/utils/logger.d.ts +1 -1
- package/utils/logger.js +1 -1
- package/utils/package-filter.d.ts +127 -7
- package/utils/package-filter.js +197 -16
- package/utils/printable-config.d.ts +1 -1
- package/utils/printable-config.js +1 -1
- package/utils/run-bin.d.ts +10 -0
- package/utils/run-bin.js +1 -1
- package/utils/run-options.d.ts +97 -0
- package/utils/run-options.js +81 -0
- package/utils/version-stamp.d.ts +1 -1
- package/utils/version-stamp.js +1 -1
package/services/run.service.js
CHANGED
|
@@ -3,221 +3,31 @@ 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
|
+
import { Service } from '../core/service.js';
|
|
6
7
|
import { exec } from '../utils/exec.js';
|
|
7
8
|
import { LOG_LEVELS, Logger, resolveRootLogLevel } from '../utils/logger.js';
|
|
8
9
|
import { filterPackages } from '../utils/package-filter.js';
|
|
9
10
|
import { ProgressPanel } from '../utils/progress-panel.js';
|
|
10
11
|
import { runBin } from '../utils/run-bin.js';
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
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.
|
|
62
|
-
*
|
|
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.
|
|
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;
|
|
79
|
-
function getConfig(pkg, script) {
|
|
80
|
-
const runCfg = pkg.config?.run;
|
|
81
|
-
const cfg = runCfg && typeof runCfg === 'object' ? runCfg[script] : undefined;
|
|
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))
|
|
86
|
-
return { exec: cfg };
|
|
87
|
-
return cfg && typeof cfg === 'object' ? cfg : {};
|
|
88
|
-
}
|
|
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;
|
|
140
|
-
function resolveEnvPlaceholders(value) {
|
|
141
|
-
return value.replace(/\{([A-Za-z_][A-Za-z0-9_]*)\}/g, (_, name) => process.env[name] ?? '');
|
|
142
|
-
}
|
|
143
|
-
/** Recursive-descent parser over `tokenizeIf`'s output: expr := or ; or := and ('or' and)* ;
|
|
144
|
-
* and := unary ('and' unary)* ; unary := 'not' unary | GROUP | NAME ['=' VALUE] */
|
|
145
|
-
function parseIfExpr(raw) {
|
|
146
|
-
if (typeof raw !== 'string' || !raw.trim())
|
|
147
|
-
return undefined;
|
|
148
|
-
const tokens = tokenizeIf(raw);
|
|
149
|
-
if (!tokens.length)
|
|
150
|
-
return undefined;
|
|
151
|
-
let i = 0;
|
|
152
|
-
const peek = () => tokens[i];
|
|
153
|
-
const next = () => tokens[i++];
|
|
154
|
-
function parseUnary() {
|
|
155
|
-
if (peek()?.toLowerCase() === 'not') {
|
|
156
|
-
next();
|
|
157
|
-
return { kind: 'not', node: parseUnary() };
|
|
158
|
-
}
|
|
159
|
-
const tok = next() ?? '';
|
|
160
|
-
/** A token containing whitespace can only be a collapsed `(...)` group - plain
|
|
161
|
-
* atom/operator tokens never do, since they're themselves split on whitespace. */
|
|
162
|
-
if (/\s/.test(tok)) {
|
|
163
|
-
const inner = parseIfExpr(tok);
|
|
164
|
-
if (!inner)
|
|
165
|
-
throw new Error(`Empty group in "if" expression: "${raw}"`);
|
|
166
|
-
return inner;
|
|
167
|
-
}
|
|
168
|
-
let value;
|
|
169
|
-
if (peek() === '=') {
|
|
170
|
-
next();
|
|
171
|
-
value = resolveEnvPlaceholders(next() ?? '');
|
|
172
|
-
}
|
|
173
|
-
return { kind: 'atom', name: tok, value };
|
|
174
|
-
}
|
|
175
|
-
function parseAnd() {
|
|
176
|
-
let node = parseUnary();
|
|
177
|
-
while (peek()?.toLowerCase() === 'and') {
|
|
178
|
-
next();
|
|
179
|
-
node = { kind: 'and', left: node, right: parseUnary() };
|
|
180
|
-
}
|
|
181
|
-
return node;
|
|
182
|
-
}
|
|
183
|
-
function parseOr() {
|
|
184
|
-
let node = parseAnd();
|
|
185
|
-
while (peek()?.toLowerCase() === 'or') {
|
|
186
|
-
next();
|
|
187
|
-
node = { kind: 'or', left: node, right: parseAnd() };
|
|
188
|
-
}
|
|
189
|
-
return node;
|
|
190
|
-
}
|
|
191
|
-
return parseOr();
|
|
192
|
-
}
|
|
193
|
-
RunService.parseIfExpr = parseIfExpr;
|
|
194
|
-
/** Evaluates a parsed `if` expression for one package. `statusCache` avoids repeat `git` calls
|
|
195
|
-
* for the same reference hash across packages/scripts in a single run. */
|
|
196
|
-
async function evaluateIf(repository, pkg, node, statusCache) {
|
|
197
|
-
if (node.kind === 'and') {
|
|
198
|
-
if (!(await evaluateIf(repository, pkg, node.left, statusCache)))
|
|
199
|
-
return false;
|
|
200
|
-
return evaluateIf(repository, pkg, node.right, statusCache);
|
|
201
|
-
}
|
|
202
|
-
if (node.kind === 'or') {
|
|
203
|
-
if (await evaluateIf(repository, pkg, node.left, statusCache))
|
|
204
|
-
return true;
|
|
205
|
-
return evaluateIf(repository, pkg, node.right, statusCache);
|
|
206
|
-
}
|
|
207
|
-
if (node.kind === 'not') {
|
|
208
|
-
return !(await evaluateIf(repository, pkg, node.node, statusCache));
|
|
209
|
-
}
|
|
210
|
-
return evaluateIfAtom(repository, pkg, node.name, node.value, statusCache);
|
|
211
|
-
}
|
|
212
|
-
RunService.evaluateIf = evaluateIf;
|
|
213
|
-
async function runScript(repository, script, options = {}) {
|
|
12
|
+
/**
|
|
13
|
+
* A service class - see `ListService` for the shape and `Service` for the three measured
|
|
14
|
+
* consequences a namespace had.
|
|
15
|
+
*
|
|
16
|
+
* **Only `runScript` became a method**, because only it takes a repository. `getConfig`,
|
|
17
|
+
* `parseIfExpr`, `normalizeScriptValue` and the rest take a `Package` or a raw value and stay
|
|
18
|
+
* functions on the namespace below - the same rule that leaves `ChangeHashService` a namespace
|
|
19
|
+
* entirely. Declared before the namespace, which TypeScript requires for the merge.
|
|
20
|
+
*/
|
|
21
|
+
export class RunService extends Service {
|
|
22
|
+
async runScript(script, options = {}) {
|
|
23
|
+
const repository = this.repository;
|
|
214
24
|
const commandName = options.commandName || 'run';
|
|
215
|
-
const rootCfg = getConfig(repository.rootPackage, script);
|
|
25
|
+
const rootCfg = RunService.getConfig(repository.rootPackage, script);
|
|
216
26
|
const logLevelDefault = resolveRootLogLevel(repository);
|
|
217
27
|
/** Standing inside a single package's own directory scopes the run to just that package
|
|
218
|
-
* (and drops the root bookend below) unless `--root` asks for the whole repository anyway -
|
|
28
|
+
* (and drops the root bookend below) unless `--from-root` asks for the whole repository anyway -
|
|
219
29
|
* a no-op when already at the root, or outside any known package. */
|
|
220
|
-
const cwdScope = options.
|
|
30
|
+
const cwdScope = options.fromRoot ? undefined : repository.currentPackage;
|
|
221
31
|
/** Global fallback for topo - individual packages can still override their own linking below,
|
|
222
32
|
* but the initial sort (topological vs alphabetical) has to be decided for the whole list at once. */
|
|
223
33
|
const topo = resolveBool(options.topo, repository.rootPackage, script, 'topo', true);
|
|
@@ -260,7 +70,7 @@ export var RunService;
|
|
|
260
70
|
if (step.run)
|
|
261
71
|
await runFunctionStep(step.run, pkg, cwd, onLine);
|
|
262
72
|
else
|
|
263
|
-
await exec(step.command, { cwd, stdio: 'pipe', onLine });
|
|
73
|
+
await exec(step.command, { cwd, stdio: 'pipe', onLine, app: pkg.repository.app });
|
|
264
74
|
}
|
|
265
75
|
else {
|
|
266
76
|
/** Match the classic rman output: raw command output streams straight through
|
|
@@ -274,10 +84,23 @@ export var RunService;
|
|
|
274
84
|
if (step.run)
|
|
275
85
|
await runFunctionStep(step.run, pkg, cwd);
|
|
276
86
|
else
|
|
277
|
-
await exec(step.command, { cwd, stdio: 'inherit' });
|
|
87
|
+
await exec(step.command, { cwd, stdio: 'inherit', app: pkg.repository.app });
|
|
278
88
|
}
|
|
279
89
|
catch (e) {
|
|
280
|
-
|
|
90
|
+
/**
|
|
91
|
+
* **Normalized to an `Error`, because a *falsy* throw was indistinguishable from no
|
|
92
|
+
* failure at all.** This was `let stepError: any` with `if (stepError) throw
|
|
93
|
+
* stepError` below, so a step doing `throw undefined` - legal JavaScript, and what a
|
|
94
|
+
* rejected promise carrying nothing gives you - left `stepError` falsy: the step
|
|
95
|
+
* line printed **success**, nothing was rethrown, and the run exited 0. Found by the
|
|
96
|
+
* spec written for the message-reporting fix above, which is the only reason it is
|
|
97
|
+
* not still there. A step that fails while reporting success is the one outcome this
|
|
98
|
+
* slot exists to rule out.
|
|
99
|
+
*
|
|
100
|
+
* Only the panel-off path had it: with the panel on there is no local catch, and the
|
|
101
|
+
* outer one runs whatever was thrown.
|
|
102
|
+
*/
|
|
103
|
+
stepError = e instanceof Error ? e : new Error(messageOf(e));
|
|
281
104
|
}
|
|
282
105
|
printLegacyStepLine(commandName, pkgLabel, step, Date.now() - stepStart, pkgLogLevel, stepError);
|
|
283
106
|
if (stepError)
|
|
@@ -331,7 +154,7 @@ export var RunService;
|
|
|
331
154
|
}
|
|
332
155
|
const stepsByPackage = new Map();
|
|
333
156
|
for (const pkg of packages) {
|
|
334
|
-
const pkgCfg = getConfig(pkg, script);
|
|
157
|
+
const pkgCfg = RunService.getConfig(pkg, script);
|
|
335
158
|
if (pkgCfg.skip === true)
|
|
336
159
|
continue;
|
|
337
160
|
if (!(await passesIf(repository, pkg, pkgCfg.if, pkg.dirname, ifStatusCache)))
|
|
@@ -431,7 +254,188 @@ export var RunService;
|
|
|
431
254
|
throw err;
|
|
432
255
|
}
|
|
433
256
|
}
|
|
434
|
-
|
|
257
|
+
}
|
|
258
|
+
(function (RunService) {
|
|
259
|
+
/**
|
|
260
|
+
* What the *package itself* declares for the lifecycle `script`, from the contributed sources
|
|
261
|
+
* alone - no `.rmanrc` involved. `undefined` when it declares nothing.
|
|
262
|
+
*
|
|
263
|
+
* Exported because `run` is not the only lifecycle rman wraps: `version` runs hooks around the
|
|
264
|
+
* version write, and npm spells those `preversion`/`version`/`postversion` in `package.json` -
|
|
265
|
+
* which is the same `pre<script>`/`<script>`/`post<script>` shape a step source already answers.
|
|
266
|
+
* So `VersionService` asks here for `'version'` instead of reading `manifest.raw.scripts` itself,
|
|
267
|
+
* and npm's version lifecycle keeps working with no second seam and no extra line in any plugin.
|
|
268
|
+
* A plugin for another ecosystem gets its own lifecycle hooks the moment it contributes steps.
|
|
269
|
+
*/
|
|
270
|
+
function contributedSlots(pkg, script) {
|
|
271
|
+
return contributedSlotsFor(pkg, script);
|
|
272
|
+
}
|
|
273
|
+
RunService.contributedSlots = contributedSlots;
|
|
274
|
+
/**
|
|
275
|
+
* Runs one slot of a lifecycle belonging to some operation other than `run` itself - `version`'s
|
|
276
|
+
* hooks around the version write are the only one so far.
|
|
277
|
+
*
|
|
278
|
+
* **Here rather than in `VersionService`, because the rule it applies is this service's**: the
|
|
279
|
+
* package's own declaration for `script` wins over the caller's `fallback`, slot by slot, exactly
|
|
280
|
+
* as `getScriptSteps` decides it for `run`. Kept in two places that rule would drift, and one of
|
|
281
|
+
* the copies would sit in the file that writes versions - which now runs no command of its own at
|
|
282
|
+
* all.
|
|
283
|
+
*
|
|
284
|
+
* `fallback` is the caller's own configured step(s), **already evaluated**: `version`'s three
|
|
285
|
+
* paths are in `DEFERRED_PATHS` precisely because only the caller can bind
|
|
286
|
+
* `${{ pkg.targetVersion }}`, so interpolating here would either be too early or need a scope this
|
|
287
|
+
* service has no business holding.
|
|
288
|
+
*
|
|
289
|
+
* **A list, not one joined string.** `VersionService` used to `join(' && ')` an array into a
|
|
290
|
+
* single shell line, which a function step cannot be part of - and which quietly changed the
|
|
291
|
+
* semantics of the shell case too, since `cd x && y` in one process is not the same as two.
|
|
292
|
+
*/
|
|
293
|
+
async function runLifecycleSlot(pkg, script, slot, fallback) {
|
|
294
|
+
const own = contributedSlots(pkg, script)?.[slot] ?? [];
|
|
295
|
+
const values = own.length ? own : (fallback ?? []);
|
|
296
|
+
for (const value of values) {
|
|
297
|
+
if (typeof value === 'function') {
|
|
298
|
+
await value(createStepContext(pkg, pkg.dirname));
|
|
299
|
+
continue;
|
|
300
|
+
}
|
|
301
|
+
await exec(value, { cwd: pkg.dirname, stdio: 'inherit', app: pkg.repository.app });
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
RunService.runLifecycleSlot = runLifecycleSlot;
|
|
305
|
+
function getConfig(pkg, script) {
|
|
306
|
+
const runCfg = pkg.config?.run;
|
|
307
|
+
const cfg = runCfg && typeof runCfg === 'object' ? runCfg[script] : undefined;
|
|
308
|
+
/** The bare-value shorthand. A function is `typeof 'function'`, not `'object'`, so without
|
|
309
|
+
* naming it here `run: { build: myFn }` fell through to the `{}` below - the long form would
|
|
310
|
+
* have worked and the short one silently done nothing, an arbitrary difference. */
|
|
311
|
+
if (typeof cfg === 'string' || typeof cfg === 'function' || Array.isArray(cfg))
|
|
312
|
+
return { exec: cfg };
|
|
313
|
+
return cfg && typeof cfg === 'object' ? cfg : {};
|
|
314
|
+
}
|
|
315
|
+
RunService.getConfig = getConfig;
|
|
316
|
+
/**
|
|
317
|
+
* The context a function step or `if` is handed - see [`RunStepContext`](../core/run-step.ts).
|
|
318
|
+
*
|
|
319
|
+
* `runBin` and `logger` are bound to *this run* rather than left to be imported, which is the
|
|
320
|
+
* whole reason they are handed over: an imported `runBin` knows neither the cwd nor the resolved
|
|
321
|
+
* log level.
|
|
322
|
+
*/
|
|
323
|
+
/**
|
|
324
|
+
* A `run.<script>.before`/`.exec`/`.after` (or `version.<slot>`) value: one step, or several to
|
|
325
|
+
* run in sequence. A shell command or a function, and a list may mix them.
|
|
326
|
+
*
|
|
327
|
+
* **Anything else throws, naming the path.** It used to `return []`, which meant a value rman did
|
|
328
|
+
* not recognize was dropped with no trace: writing a function here - the obvious guess, and now
|
|
329
|
+
* the supported form - produced `1 succeeded, 0 failed` with the step never run (measured). A
|
|
330
|
+
* configuration mistake has to be loud; silence here reads as success.
|
|
331
|
+
*
|
|
332
|
+
* Exported, and the only implementation: `VersionService` used to carry a second one that behaved
|
|
333
|
+
* differently, which is how `version.<slot>` came to join its array with `' && '`.
|
|
334
|
+
*/
|
|
335
|
+
function normalizeScriptValue(value, at) {
|
|
336
|
+
const items = Array.isArray(value) ? value : [value];
|
|
337
|
+
const steps = [];
|
|
338
|
+
for (let i = 0; i < items.length; i++) {
|
|
339
|
+
const item = items[i];
|
|
340
|
+
/** An empty string and an absent value are both "nothing here", which is how a `"[*]"` block
|
|
341
|
+
* declaring a slot some packages don't use has always behaved. */
|
|
342
|
+
if (item === undefined || item === null || item === '')
|
|
343
|
+
continue;
|
|
344
|
+
if (typeof item === 'string' || typeof item === 'function') {
|
|
345
|
+
steps.push(item);
|
|
346
|
+
continue;
|
|
347
|
+
}
|
|
348
|
+
const where = Array.isArray(value) ? `${at}[${i}]` : at;
|
|
349
|
+
throw new Error(`"${where}" must be a shell command or a function, but it is ${describeValue(item)}.\n` +
|
|
350
|
+
` A list of either (or both) runs them in sequence.`);
|
|
351
|
+
}
|
|
352
|
+
return steps;
|
|
353
|
+
}
|
|
354
|
+
RunService.normalizeScriptValue = normalizeScriptValue;
|
|
355
|
+
function createStepContext(pkg, cwd) {
|
|
356
|
+
const logLevel = resolveRootLogLevel(pkg.repository);
|
|
357
|
+
return {
|
|
358
|
+
pkg,
|
|
359
|
+
repository: pkg.repository,
|
|
360
|
+
cwd,
|
|
361
|
+
runBin: (bin, argv, opts) => runBin(bin, argv, { cwd, logLevel, app: pkg.repository.app, ...opts }),
|
|
362
|
+
logger: new Logger(logLevel),
|
|
363
|
+
};
|
|
364
|
+
}
|
|
365
|
+
RunService.createStepContext = createStepContext;
|
|
366
|
+
function resolveEnvPlaceholders(value) {
|
|
367
|
+
return value.replace(/\{([A-Za-z_][A-Za-z0-9_]*)\}/g, (_, name) => process.env[name] ?? '');
|
|
368
|
+
}
|
|
369
|
+
/** Recursive-descent parser over `tokenizeIf`'s output: expr := or ; or := and ('or' and)* ;
|
|
370
|
+
* and := unary ('and' unary)* ; unary := 'not' unary | GROUP | NAME ['=' VALUE] */
|
|
371
|
+
function parseIfExpr(raw) {
|
|
372
|
+
if (typeof raw !== 'string' || !raw.trim())
|
|
373
|
+
return undefined;
|
|
374
|
+
const tokens = tokenizeIf(raw);
|
|
375
|
+
if (!tokens.length)
|
|
376
|
+
return undefined;
|
|
377
|
+
let i = 0;
|
|
378
|
+
const peek = () => tokens[i];
|
|
379
|
+
const next = () => tokens[i++];
|
|
380
|
+
function parseUnary() {
|
|
381
|
+
if (peek()?.toLowerCase() === 'not') {
|
|
382
|
+
next();
|
|
383
|
+
return { kind: 'not', node: parseUnary() };
|
|
384
|
+
}
|
|
385
|
+
const tok = next() ?? '';
|
|
386
|
+
/** A token containing whitespace can only be a collapsed `(...)` group - plain
|
|
387
|
+
* atom/operator tokens never do, since they're themselves split on whitespace. */
|
|
388
|
+
if (/\s/.test(tok)) {
|
|
389
|
+
const inner = parseIfExpr(tok);
|
|
390
|
+
if (!inner)
|
|
391
|
+
throw new Error(`Empty group in "if" expression: "${raw}"`);
|
|
392
|
+
return inner;
|
|
393
|
+
}
|
|
394
|
+
let value;
|
|
395
|
+
if (peek() === '=') {
|
|
396
|
+
next();
|
|
397
|
+
value = resolveEnvPlaceholders(next() ?? '');
|
|
398
|
+
}
|
|
399
|
+
return { kind: 'atom', name: tok, value };
|
|
400
|
+
}
|
|
401
|
+
function parseAnd() {
|
|
402
|
+
let node = parseUnary();
|
|
403
|
+
while (peek()?.toLowerCase() === 'and') {
|
|
404
|
+
next();
|
|
405
|
+
node = { kind: 'and', left: node, right: parseUnary() };
|
|
406
|
+
}
|
|
407
|
+
return node;
|
|
408
|
+
}
|
|
409
|
+
function parseOr() {
|
|
410
|
+
let node = parseAnd();
|
|
411
|
+
while (peek()?.toLowerCase() === 'or') {
|
|
412
|
+
next();
|
|
413
|
+
node = { kind: 'or', left: node, right: parseAnd() };
|
|
414
|
+
}
|
|
415
|
+
return node;
|
|
416
|
+
}
|
|
417
|
+
return parseOr();
|
|
418
|
+
}
|
|
419
|
+
RunService.parseIfExpr = parseIfExpr;
|
|
420
|
+
/** Evaluates a parsed `if` expression for one package. `statusCache` avoids repeat `git` calls
|
|
421
|
+
* for the same reference hash across packages/scripts in a single run. */
|
|
422
|
+
async function evaluateIf(repository, pkg, node, statusCache) {
|
|
423
|
+
if (node.kind === 'and') {
|
|
424
|
+
if (!(await evaluateIf(repository, pkg, node.left, statusCache)))
|
|
425
|
+
return false;
|
|
426
|
+
return evaluateIf(repository, pkg, node.right, statusCache);
|
|
427
|
+
}
|
|
428
|
+
if (node.kind === 'or') {
|
|
429
|
+
if (await evaluateIf(repository, pkg, node.left, statusCache))
|
|
430
|
+
return true;
|
|
431
|
+
return evaluateIf(repository, pkg, node.right, statusCache);
|
|
432
|
+
}
|
|
433
|
+
if (node.kind === 'not') {
|
|
434
|
+
return !(await evaluateIf(repository, pkg, node.node, statusCache));
|
|
435
|
+
}
|
|
436
|
+
return evaluateIfAtom(repository, pkg, node.name, node.value, statusCache);
|
|
437
|
+
}
|
|
438
|
+
RunService.evaluateIf = evaluateIf;
|
|
435
439
|
})(RunService || (RunService = {}));
|
|
436
440
|
/**
|
|
437
441
|
* Classic one-line-per-step log ("info build pkg ┆ step success ┆ command (123 ms)"),
|
|
@@ -483,11 +487,30 @@ function toStep(slot, value) {
|
|
|
483
487
|
*
|
|
484
488
|
* Restored in a `finally`, because a step that throws must not leave the rest of the run writing
|
|
485
489
|
* into a log nobody reads.
|
|
490
|
+
*
|
|
491
|
+
* **Its failure message is reported here, because nothing else does it.** A shell step's reason
|
|
492
|
+
* reaches the user on its own - the output streams through `onLine` or straight to the terminal,
|
|
493
|
+
* and `exec` names the command and its exit code. A function step has neither: it fails by
|
|
494
|
+
* throwing, and the throw goes to the outer `catch` that marks the package failed and rethrows an
|
|
495
|
+
* error the CLI treats as already-logged. Measured on a real config whose build step threw a
|
|
496
|
+
* carefully worded message about a missing `tsconfig.json`: the run printed
|
|
497
|
+
* `error build pkg-forgot ┆ exec failed ┆ buildWithTsc` and exited 1, and the message appeared
|
|
498
|
+
* nowhere at all - so the one thing that said what to do was the one thing dropped.
|
|
499
|
+
*
|
|
500
|
+
* Written where a shell step's output goes, so it needs no second channel: through `onLine` with
|
|
501
|
+
* the panel on (the step's own log, which is what the panel shows for a failed item), and to
|
|
502
|
+
* stderr with it off, ahead of the `failed` line - the order a shell step already produces.
|
|
486
503
|
*/
|
|
487
504
|
async function runFunctionStep(run, pkg, cwd, onLine) {
|
|
488
505
|
const context = RunService.createStepContext(pkg, cwd);
|
|
489
506
|
if (!onLine) {
|
|
490
|
-
|
|
507
|
+
try {
|
|
508
|
+
await run(context);
|
|
509
|
+
}
|
|
510
|
+
catch (e) {
|
|
511
|
+
console.error(colors.red(messageOf(e)));
|
|
512
|
+
throw e;
|
|
513
|
+
}
|
|
491
514
|
return;
|
|
492
515
|
}
|
|
493
516
|
const console_ = globalThis.console;
|
|
@@ -504,11 +527,24 @@ async function runFunctionStep(run, pkg, cwd, onLine) {
|
|
|
504
527
|
try {
|
|
505
528
|
await run(context);
|
|
506
529
|
}
|
|
530
|
+
catch (e) {
|
|
531
|
+
/** Through the patched `console` deliberately - `onLine` is still installed at this point, so
|
|
532
|
+
* the message lands in this step's log rather than over the panel it is drawn inside. */
|
|
533
|
+
for (const line of messageOf(e).split('\n'))
|
|
534
|
+
onLine(line);
|
|
535
|
+
throw e;
|
|
536
|
+
}
|
|
507
537
|
finally {
|
|
508
538
|
for (const method of CAPTURED_CONSOLE)
|
|
509
539
|
console_[method] = original[method];
|
|
510
540
|
}
|
|
511
541
|
}
|
|
542
|
+
/** What a thrown value has to say for itself. A step may throw anything, and `String(undefined)`
|
|
543
|
+
* reading as `undefined` in a run log is worse than saying nothing was said. */
|
|
544
|
+
function messageOf(error) {
|
|
545
|
+
const message = error instanceof Error ? error.message : typeof error === 'string' ? error : '';
|
|
546
|
+
return message.trim() || `the step threw ${inspect(error)}`;
|
|
547
|
+
}
|
|
512
548
|
const CAPTURED_CONSOLE = ['log', 'info', 'warn', 'error', 'debug'];
|
|
513
549
|
function format(args) {
|
|
514
550
|
return args.map(arg => (typeof arg === 'string' ? arg : inspect(arg))).join(' ');
|
|
@@ -549,7 +585,7 @@ async function passesIf(repository, pkg, raw, cwd, statusCache) {
|
|
|
549
585
|
function getScriptSteps(pkg, script) {
|
|
550
586
|
const cfg = RunService.getConfig(pkg, script);
|
|
551
587
|
const override = cfg.override === true;
|
|
552
|
-
const contributed =
|
|
588
|
+
const contributed = contributedSlotsFor(pkg, script);
|
|
553
589
|
const fromConfig = {
|
|
554
590
|
before: RunService.normalizeScriptValue(cfg.before, `run.${script}.before`),
|
|
555
591
|
exec: RunService.normalizeScriptValue(cfg.exec, `run.${script}.exec`),
|
|
@@ -565,19 +601,22 @@ function getScriptSteps(pkg, script) {
|
|
|
565
601
|
}
|
|
566
602
|
return steps;
|
|
567
603
|
}
|
|
568
|
-
/**
|
|
569
|
-
*
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
604
|
+
/**
|
|
605
|
+
* What this package's own technology says it declares for `script`.
|
|
606
|
+
*
|
|
607
|
+
* **Its own, not every registered one.** This walked all the contributed sources and took the first
|
|
608
|
+
* that answered, which in a polyglot repository meant npm's `package.json#scripts` reader was handed
|
|
609
|
+
* a Cargo package and asked whether it declared `build` - its first line is
|
|
610
|
+
* `pkg.manifest.raw?.scripts`, so it was reading a manifest another technology produced. The package
|
|
611
|
+
* already knows which technology claimed it; asking anyone else was only ever a way of finding that
|
|
612
|
+
* out again.
|
|
613
|
+
*/
|
|
614
|
+
function contributedSlotsFor(pkg, script) {
|
|
615
|
+
const slots = pkg.platform.getRunSteps?.(pkg, script);
|
|
616
|
+
return slots && (slots.before?.length || slots.exec?.length || slots.after?.length) ? slots : undefined;
|
|
577
617
|
}
|
|
578
618
|
/** In execution order - `before`, the script itself, then `after`. */
|
|
579
619
|
const SCRIPT_SLOTS = ['before', 'exec', 'after'];
|
|
580
|
-
const stepSources = [];
|
|
581
620
|
/**
|
|
582
621
|
* Splits on whitespace, but a `(...)` group collapses to a single token holding its inner text
|
|
583
622
|
* (parens stripped, inner whitespace kept) - `evaluate` below re-tokenizes any token that still
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { RmanApplication } from '../core/application.js';
|
|
1
2
|
import type { Package } from '../core/package.js';
|
|
2
3
|
import type { Repository } from '../core/repository.js';
|
|
3
4
|
import { VersionScheme } from '../core/version-scheme.js';
|
|
@@ -97,6 +98,31 @@ export declare abstract class VersionPlanService {
|
|
|
97
98
|
* wrong one here is invisible - it produces a plan that simply releases too little.
|
|
98
99
|
*/
|
|
99
100
|
protected abstract cascade(bump: string): VersionPlanService.Cascade;
|
|
101
|
+
/**
|
|
102
|
+
* **Which planner answers for one package: its own technology's.**
|
|
103
|
+
*
|
|
104
|
+
* The two abstract members above are the ecosystem's, and a repository can hold more than one.
|
|
105
|
+
* `app.versionPlanner` is a single slot - last registration wins - so in a polyglot repository
|
|
106
|
+
* both of them used to be answered by whichever plugin happened to register last: a Cargo
|
|
107
|
+
* package's boundary fell back to `npm view`, and its cascade assumed npm's caret ranges. That is
|
|
108
|
+
* the same shape of bug the hardcoded `['npm']` publish default was, and the same fix - ask the
|
|
109
|
+
* technology that read the manifest.
|
|
110
|
+
*
|
|
111
|
+
* Falls back to `this`, which is what a single-technology repository always gets and what a
|
|
112
|
+
* `Plugin` contributing no planner of its own means.
|
|
113
|
+
*/
|
|
114
|
+
protected plannerFor(pkg: Package): VersionPlanService;
|
|
115
|
+
/**
|
|
116
|
+
* `cascade` for a whole group, which is the unit it is asked about - one answer applies to every
|
|
117
|
+
* member, because the group releases as one version line.
|
|
118
|
+
*
|
|
119
|
+
* **Members disagreeing take the widest answer**, and the direction is deliberate: a cascade that
|
|
120
|
+
* is too narrow produces a plan that silently *releases too little* (the failure `cascade`'s own
|
|
121
|
+
* doc calls invisible), while one that is too wide releases a package that did not strictly need
|
|
122
|
+
* it - visible, and harmless. A mixed-technology group is unusual; a mixed group quietly skipping
|
|
123
|
+
* a dependent that pins exact versions is a broken install.
|
|
124
|
+
*/
|
|
125
|
+
protected cascadeFor(members: Package[], bump: string): VersionPlanService.Cascade;
|
|
100
126
|
/**
|
|
101
127
|
* The largest bump `commits` ask for, in `scheme`'s own names.
|
|
102
128
|
*
|
|
@@ -230,9 +256,6 @@ export declare namespace VersionPlanService {
|
|
|
230
256
|
* repository layering its own policy plugin after `rman-node` could never take effect - which is
|
|
231
257
|
* the only reason to name two in the first place.
|
|
232
258
|
*/
|
|
233
|
-
function setPlanner(planner: VersionPlanService): void;
|
|
234
|
-
/** For tests, which would otherwise leak a planner into every later case in the process. */
|
|
235
|
-
function clearPlanner(): void;
|
|
236
259
|
/**
|
|
237
260
|
* The registered planner. **Throws** when there is none, rather than falling back to some
|
|
238
261
|
* built-in default: `Manifest` and `Workspace` can degrade honestly (a package named after its
|
|
@@ -240,5 +263,5 @@ export declare namespace VersionPlanService {
|
|
|
240
263
|
* merely a diminished one - a wrong cascade or a wrong boundary reports a release that is
|
|
241
264
|
* plausible and untrue.
|
|
242
265
|
*/
|
|
243
|
-
function getPlanner(): VersionPlanService;
|
|
266
|
+
function getPlanner(app: RmanApplication): VersionPlanService;
|
|
244
267
|
}
|