rman 2.17.0 → 2.18.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 +3 -2
- package/builtins/platforms/node/augmentation/rmanrc.augmentation.d.ts +5 -2
- package/builtins/platforms/node/augmentation/system-info.augmentation.js +7 -1
- package/builtins/platforms/node/node-config.interface.d.ts +10 -23
- package/builtins/platforms/node/services/ci.service.d.ts +6 -3
- package/builtins/platforms/node/services/ci.service.js +19 -8
- package/builtins/platforms/node/services/publish.service.js +1 -2
- package/builtins/publish-targets/docker/docker-publish.service.js +36 -1
- package/builtins/publish-targets/docker/docker.target.js +2 -0
- package/commands/publish.command.js +83 -12
- package/commands/version.command.js +2 -1
- package/constants.js +1 -1
- package/core/classes/version-scheme.d.ts +6 -0
- package/core/classes/version-scheme.js +28 -2
- package/core/interfaces/publish-target.d.ts +5 -0
- package/interfaces/rman-config.interface.d.ts +12 -0
- package/package.json +4 -12
- package/utils/git.js +13 -7
package/README.md
CHANGED
|
@@ -169,7 +169,7 @@ orders by dependencies instead, and `--json` keeps the inventory's own order.
|
|
|
169
169
|
|
|
170
170
|
Prints local environment (OS/CPU/memory, Node, git) and repository information. A platform adds its
|
|
171
171
|
own ecosystem's part - the `node` built-in reports whichever package manager
|
|
172
|
-
`.rmanrc "packageManager"` names (`npm` by default), plus the installed `rman` and `typescript`.
|
|
172
|
+
`.rmanrc "packageManager.node"` names (`npm` by default), plus the installed `rman` and `typescript`.
|
|
173
173
|
|
|
174
174
|
```bash
|
|
175
175
|
rman info
|
|
@@ -430,7 +430,8 @@ such as `"[*]"` the packages below.
|
|
|
430
430
|
|
|
431
431
|
```yaml
|
|
432
432
|
# .rmanrc.yml, at the repository root
|
|
433
|
-
packageManager:
|
|
433
|
+
packageManager:
|
|
434
|
+
node: pnpm
|
|
434
435
|
logLevel: info
|
|
435
436
|
allowBranch: [main, release/*]
|
|
436
437
|
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { cleanCommand, CleanExtraKeys } from '../commands/clean.command.js';
|
|
2
|
-
import type { NodeConfigKeys, RmanNodeConfig } from '../node-config.interface.js';
|
|
2
|
+
import type { NodeConfigKeys, NodePackageManagers, RmanNodeConfig } from '../node-config.interface.js';
|
|
3
3
|
import type { CiService } from '../services/ci.service.js';
|
|
4
4
|
/**
|
|
5
5
|
* **Every type this plugin adds to rman, in one `declare module` block.**
|
|
@@ -25,6 +25,9 @@ declare module '../../../../interfaces/rman-config.interface.js' {
|
|
|
25
25
|
*/
|
|
26
26
|
interface RmanConfigKeys extends NodeConfigKeys {
|
|
27
27
|
}
|
|
28
|
+
/** Node's entry in `.rmanrc "packageManager"` - `{ node: 'pnpm' }`. */
|
|
29
|
+
interface PackageManagers extends NodePackageManagers {
|
|
30
|
+
}
|
|
28
31
|
/**
|
|
29
32
|
* **`clean.*`, contributed by the command that reads it**, the way every built-in contributes its
|
|
30
33
|
* own key - `skip` derived from the command's `config` block, `include`/`exclude` hand-written in
|
|
@@ -74,7 +77,7 @@ declare module '../../../../services/system-info.js' {
|
|
|
74
77
|
/**
|
|
75
78
|
* Report this package manager's version under `Binaries`, plus the `npmPackages` sections.
|
|
76
79
|
*
|
|
77
|
-
* Defaults to `.rmanrc "packageManager"` (read off `Options.repository`), then to `npm` -
|
|
80
|
+
* Defaults to `.rmanrc "packageManager.node"` (read off `Options.repository`), then to `npm` -
|
|
78
81
|
* this package being installed is itself the statement that the repository is a Node one.
|
|
79
82
|
*/
|
|
80
83
|
packageManager?: PackageManager;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { SystemInfo as OrgSystemInfo } from '../../../../services/system-info.js';
|
|
2
|
+
import { CiService } from '../services/ci.service.js';
|
|
2
3
|
/**
|
|
3
4
|
* Adds the npm half of `SystemInfo` to rman's own types.
|
|
4
5
|
*
|
|
@@ -41,7 +42,12 @@ export function augmentSystemInfo() {
|
|
|
41
42
|
/** An explicit argument first, then the repository's own config, then npm. The config step is
|
|
42
43
|
* why the core declares `Options.repository`: `rman info` passes only that, so without it a
|
|
43
44
|
* pnpm repository would silently be reported as an npm one. */
|
|
44
|
-
|
|
45
|
+
/** The old bare `packageManager: pnpm` is refused here as `ci` and `publish` refuse it - read
|
|
46
|
+
* as a map it has no `node` entry, and `info` would report npm for a pnpm repository. */
|
|
47
|
+
const configured = options?.repository?.config?.packageManager;
|
|
48
|
+
if (typeof configured === 'string')
|
|
49
|
+
CiService.resolvePackageManager(options.repository);
|
|
50
|
+
const packageManager = options?.packageManager ?? asPackageManager(configured?.node) ?? 'npm';
|
|
45
51
|
return base({
|
|
46
52
|
...options,
|
|
47
53
|
envinfo: {
|
|
@@ -1,31 +1,18 @@
|
|
|
1
1
|
import type { ConfigValue, RmanConfig, ScopedVars } from '../../../interfaces/rman-config.interface.js';
|
|
2
2
|
import type { CiService } from './services/ci.service.js';
|
|
3
3
|
/**
|
|
4
|
-
* The `.rmanrc` keys that only mean something because the repository is a Node one
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
* *and* by the `npm` publish target, so neither can contribute it. Everything else this package
|
|
8
|
-
* adds to `.rmanrc` is declared by whoever reads it - `clean.*` by `clean.command.ts` (through
|
|
9
|
-
* `CommandContribution`), `publish.npm.*` by the target, below.
|
|
10
|
-
*
|
|
11
|
-
* **Two surfaces, and they are not alternatives:**
|
|
12
|
-
*
|
|
13
|
-
* - the `declare module 'rman'` block in [`../augmentation/rman.augmentation.ts`] merges this into
|
|
14
|
-
* `RmanConfigKeys`, so `repository.config.packageManager` stays typed wherever it is read with no
|
|
15
|
-
* cast. It lives there because one such block per package is the limit;
|
|
16
|
-
* - `RmanNodeConfig` is the name a *config author* annotates with, which is what makes the import
|
|
17
|
-
* carrying that augmentation explicit instead of a side effect someone has to remember.
|
|
4
|
+
* The `.rmanrc` keys that only mean something because the repository is a Node one, merged into
|
|
5
|
+
* `RmanConfigKeys` by `rmanrc.augmentation.ts`. Everything else this built-in adds is declared by
|
|
6
|
+
* whoever reads it - `clean.*` by `clean.command.ts`, `publish.npm.*` by the target, below.
|
|
18
7
|
*/
|
|
19
8
|
export interface NodeConfigKeys {
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
*/
|
|
28
|
-
packageManager?: CiService.PackageManager;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Node's entry in `.rmanrc "packageManager"`: which package manager `ci` installs with, `publish`
|
|
12
|
+
* publishes with and `info` reports - `packageManager: { node: pnpm }`. Default `npm`.
|
|
13
|
+
*/
|
|
14
|
+
export interface NodePackageManagers {
|
|
15
|
+
node?: CiService.PackageManager;
|
|
29
16
|
}
|
|
30
17
|
export declare namespace RmanNodeConfig {
|
|
31
18
|
/**
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { LogLevel } from '../../../../core/classes/logger.js';
|
|
2
|
+
import type { Package } from '../../../../core/classes/package.js';
|
|
2
3
|
import type { Repository } from '../../../../core/classes/repository.js';
|
|
3
4
|
import type { PackageFilterOptions } from '../../../../utils/package-filter.js';
|
|
4
5
|
export declare namespace CiService {
|
|
@@ -21,9 +22,11 @@ export declare namespace CiService {
|
|
|
21
22
|
* the root's `.rmanrc logLevel`, then 'info' - see `resolveRootLogLevel`. */
|
|
22
23
|
logLevel?: LogLevel;
|
|
23
24
|
}
|
|
24
|
-
/**
|
|
25
|
-
*
|
|
26
|
-
|
|
25
|
+
/**
|
|
26
|
+
* The package manager `pkg` uses: `cliValue` when given, else its `.rmanrc "packageManager.node"`,
|
|
27
|
+
* else `npm`. Pass the repository for the one the repository installs with.
|
|
28
|
+
*/
|
|
29
|
+
function resolvePackageManager(pkg: Package, cliValue?: PackageManager): PackageManager;
|
|
27
30
|
/** Deletes `node_modules` and any known lockfile directly under `dirname`. Returns the names
|
|
28
31
|
* that actually existed (and were removed), so the caller can log only those. */
|
|
29
32
|
function wipe(dirname: string): Promise<string[]>;
|
|
@@ -16,17 +16,28 @@ export var CiService;
|
|
|
16
16
|
* act on. One list, one place it is written down.
|
|
17
17
|
*/
|
|
18
18
|
CiService.PACKAGE_MANAGERS = ['npm', 'yarn', 'pnpm', 'bun'];
|
|
19
|
-
/**
|
|
20
|
-
*
|
|
21
|
-
|
|
19
|
+
/**
|
|
20
|
+
* The package manager `pkg` uses: `cliValue` when given, else its `.rmanrc "packageManager.node"`,
|
|
21
|
+
* else `npm`. Pass the repository for the one the repository installs with.
|
|
22
|
+
*/
|
|
23
|
+
/* **The bare string is refused rather than read.** `packageManager: pnpm` was the old spelling,
|
|
24
|
+
* and rman validates no config keys, so read as an object it would simply have no `node` entry -
|
|
25
|
+
* a pnpm repository installing with npm, saying nothing. No repository of this organization
|
|
26
|
+
* wrote it, so a clear refusal costs nobody anything. */
|
|
27
|
+
function resolvePackageManager(pkg, cliValue) {
|
|
22
28
|
if (cliValue)
|
|
23
29
|
return cliValue;
|
|
24
|
-
const configured =
|
|
25
|
-
if (configured ===
|
|
30
|
+
const configured = pkg.config?.packageManager;
|
|
31
|
+
if (typeof configured === 'string') {
|
|
32
|
+
throw new Error(`.rmanrc "packageManager" takes one entry per technology - write packageManager: { node: ${configured} } ` +
|
|
33
|
+
`instead of packageManager: ${configured}.`);
|
|
34
|
+
}
|
|
35
|
+
const node = configured?.node;
|
|
36
|
+
if (node === undefined)
|
|
26
37
|
return 'npm';
|
|
27
|
-
if (CiService.PACKAGE_MANAGERS.includes(
|
|
28
|
-
return
|
|
29
|
-
throw new Error(`Invalid "packageManager" in .rmanrc: "${
|
|
38
|
+
if (typeof node === 'string' && CiService.PACKAGE_MANAGERS.includes(node))
|
|
39
|
+
return node;
|
|
40
|
+
throw new Error(`Invalid "packageManager.node" in .rmanrc: "${String(node)}" (expected one of: ${CiService.PACKAGE_MANAGERS.join(', ')})`);
|
|
30
41
|
}
|
|
31
42
|
CiService.resolvePackageManager = resolvePackageManager;
|
|
32
43
|
/** Deletes `node_modules` and any known lockfile directly under `dirname`. Returns the names
|
|
@@ -305,7 +305,6 @@ export var PublishService;
|
|
|
305
305
|
* `consumerNeeds`).
|
|
306
306
|
*/
|
|
307
307
|
async function applyPlan(repository, plan, options = {}) {
|
|
308
|
-
const packageManager = CiService.resolvePackageManager(repository, options.packageManager);
|
|
309
308
|
const failed = new Set();
|
|
310
309
|
const result = [];
|
|
311
310
|
/** The tuple is explicit: `[p.name, p]` widens to `(string | Package)[]`, and whether `new Map`
|
|
@@ -331,7 +330,7 @@ export var PublishService;
|
|
|
331
330
|
/** The plan's own tag and staging decision, not recomputed ones: `getPlan` decided where
|
|
332
331
|
* this package goes, the reader confirmed that, and `applyPlan` is here to carry it out.
|
|
333
332
|
* `options.*` is the fallback only for a plan built by something other than `getPlan`. */
|
|
334
|
-
await exec(buildPublishCommand(packageManager, {
|
|
333
|
+
await exec(buildPublishCommand(CiService.resolvePackageManager(pkg, options.packageManager), {
|
|
335
334
|
...options,
|
|
336
335
|
tag: entry.distTag ?? options.tag,
|
|
337
336
|
staged: entry.staged ?? options.staged,
|
|
@@ -183,7 +183,42 @@ async function buildAndPush(repository, entry) {
|
|
|
183
183
|
args.push('--build-arg', `${name}="${expandEnvValue(value)}"`);
|
|
184
184
|
}
|
|
185
185
|
args.push('-f', `"${dockerfile}"`, '-t', `"${entry.image}:${entry.version}"`, '-t', `"${entry.image}:latest"`, '--push', '.');
|
|
186
|
-
|
|
186
|
+
/** **Shown as it runs and kept**, so a failure can say why. It ran with `stdio: 'inherit'`, and the
|
|
187
|
+
* one thing left to report was the exit code - measured on `panates/syncbridge`, the line that
|
|
188
|
+
* explained the failure sat forty lines above the plan's recap and the recap said
|
|
189
|
+
* `Command failed (1)`. On stderr, so a `--json` stdout stays one document. */
|
|
190
|
+
const lines = [];
|
|
191
|
+
const result = await exec(`docker ${args.join(' ')}`, {
|
|
192
|
+
cwd,
|
|
193
|
+
app: repository.app,
|
|
194
|
+
throwOnError: false,
|
|
195
|
+
onLine: line => {
|
|
196
|
+
lines.push(line);
|
|
197
|
+
process.stderr.write(line + '\n');
|
|
198
|
+
},
|
|
199
|
+
});
|
|
200
|
+
if (result.code)
|
|
201
|
+
throw new Error(`docker build exited with code ${result.code}${failureLines(lines)}`);
|
|
202
|
+
}
|
|
203
|
+
/**
|
|
204
|
+
* The lines of a failed build worth putting in a one-paragraph reason: those saying "error", with
|
|
205
|
+
* BuildKit's `#42 31.01 ` step prefix removed, each once - BuildKit prints a failing step's output
|
|
206
|
+
* twice, once as it runs and again in its summary - and without npm's pointer to its own log file.
|
|
207
|
+
* At most four, as an indented block under the first line.
|
|
208
|
+
*/
|
|
209
|
+
function failureLines(lines) {
|
|
210
|
+
const seen = new Set();
|
|
211
|
+
for (const raw of lines) {
|
|
212
|
+
const line = raw
|
|
213
|
+
.replace(/^#\d+\s+[\d.]+\s+/, '')
|
|
214
|
+
.replace(/^[\d.]+\s+/, '')
|
|
215
|
+
.trim();
|
|
216
|
+
if (!/\berror\b/i.test(line) || /complete log of this run/i.test(line))
|
|
217
|
+
continue;
|
|
218
|
+
seen.add(line);
|
|
219
|
+
}
|
|
220
|
+
const picked = [...seen].slice(0, 4);
|
|
221
|
+
return picked.length ? ':\n' + picked.map(l => ` ${l}`).join('\n') : '';
|
|
187
222
|
}
|
|
188
223
|
async function updateDescription(entry) {
|
|
189
224
|
const pkg = entry.package;
|
|
@@ -14,6 +14,8 @@ import { DOCKER_TARGET } from './docker-publish.service.js';
|
|
|
14
14
|
export const dockerPublishTarget = {
|
|
15
15
|
name: DOCKER_TARGET,
|
|
16
16
|
describe: 'Build and push a container image (docker buildx build --push)',
|
|
17
|
+
/** An image installs its dependencies from the registries the other targets push to. */
|
|
18
|
+
publishesLast: true,
|
|
17
19
|
options: {
|
|
18
20
|
dockerNamespace: {
|
|
19
21
|
target: 'cli',
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
1
2
|
import readline from 'node:readline/promises';
|
|
2
3
|
import colors from 'ansi-colors';
|
|
3
4
|
import { unknownTargets } from '../core/interfaces/publish-target.js';
|
|
@@ -109,7 +110,9 @@ const publishCommand = registerCommand(app => {
|
|
|
109
110
|
handler: async (args) => {
|
|
110
111
|
await assertAllowedBranch(repository, readBranchGuardOptions(args));
|
|
111
112
|
const requested = args.target;
|
|
112
|
-
|
|
113
|
+
/** A target built from what the others publish goes after them - see `publishesLast`. A
|
|
114
|
+
* stable sort, so registration order still decides among the rest. */
|
|
115
|
+
const selected = selectTargets(targets, requested).sort((a, b) => Number(!!a.publishesLast) - Number(!!b.publishesLast));
|
|
113
116
|
const ctx = {
|
|
114
117
|
app,
|
|
115
118
|
repository,
|
|
@@ -161,14 +164,21 @@ const publishCommand = registerCommand(app => {
|
|
|
161
164
|
}
|
|
162
165
|
if (!proceed)
|
|
163
166
|
return;
|
|
164
|
-
|
|
167
|
+
const outcomes = [];
|
|
165
168
|
for (const [target, plan] of plans) {
|
|
166
169
|
const applied = await target.applyPlan(ctx, plan);
|
|
167
|
-
|
|
168
|
-
|
|
170
|
+
outcomes.push(...printApplied(applied, plan, target.name));
|
|
171
|
+
}
|
|
172
|
+
if (!args.json)
|
|
173
|
+
printRecap(outcomes);
|
|
174
|
+
writeStepSummary(outcomes);
|
|
175
|
+
const failed = outcomes.filter(o => o.entry.status === 'error');
|
|
176
|
+
/** **The names are in the line itself**, not only in the recap above it: in CI stdout and
|
|
177
|
+
* stderr are separate pipes, and measured on `panates/syncbridge` this line landed in the
|
|
178
|
+
* middle of the `published` lines rather than after them. */
|
|
179
|
+
if (failed.length) {
|
|
180
|
+
throw logged(`"publish" failed for ${failed.map(o => `[${o.target}] ${o.entry.package.name}`).join(', ')}`);
|
|
169
181
|
}
|
|
170
|
-
if (failed)
|
|
171
|
-
throw logged('"publish" failed');
|
|
172
182
|
},
|
|
173
183
|
};
|
|
174
184
|
});
|
|
@@ -265,25 +275,86 @@ function printPlan(entries, label) {
|
|
|
265
275
|
}
|
|
266
276
|
}
|
|
267
277
|
/**
|
|
268
|
-
* What one target actually did. Returns
|
|
278
|
+
* What one target actually did, printed as it is known. Returns the outcome of every package the
|
|
279
|
+
* *plan* said to publish.
|
|
269
280
|
*
|
|
270
|
-
* An `'error'` is only
|
|
281
|
+
* An `'error'` is only an outcome when the plan said `'publish'` for that package: an entry that
|
|
271
282
|
* was already an error before anything ran has been printed once by `printPlan`, and printing it
|
|
272
283
|
* again under "failed" would read as a push that was attempted and did not work.
|
|
273
284
|
*/
|
|
274
285
|
function printApplied(applied, plan, label) {
|
|
275
286
|
const prefix = colors.gray(`[${label}] `);
|
|
276
|
-
|
|
287
|
+
const outcomes = [];
|
|
277
288
|
for (const entry of applied) {
|
|
289
|
+
if (plan.find(e => e.package === entry.package)?.status !== 'publish')
|
|
290
|
+
continue;
|
|
278
291
|
if (entry.status === 'publish') {
|
|
279
292
|
console.log(colors.green('published'), prefix + colors.cyan(entry.package.name), entry.detail ?? entry.version);
|
|
280
293
|
}
|
|
281
|
-
else if (entry.status === 'error'
|
|
282
|
-
failed = true;
|
|
294
|
+
else if (entry.status === 'error') {
|
|
283
295
|
console.log(colors.red('failed'), prefix + colors.cyan(entry.package.name), colors.red(entry.reason ?? ''));
|
|
284
296
|
}
|
|
297
|
+
else
|
|
298
|
+
continue;
|
|
299
|
+
outcomes.push({ target: label, entry });
|
|
300
|
+
}
|
|
301
|
+
return outcomes;
|
|
302
|
+
}
|
|
303
|
+
/**
|
|
304
|
+
* **One block at the end saying how it went**: how many went up, and every failure again with its
|
|
305
|
+
* reason. A target prints as it goes, so on a long release the failed line is hundreds of lines up,
|
|
306
|
+
* among the output of the build that failed - measured on `panates/syncbridge`, where the last
|
|
307
|
+
* thing in the log was `"publish" failed` and nothing said what.
|
|
308
|
+
*/
|
|
309
|
+
function printRecap(outcomes) {
|
|
310
|
+
if (!outcomes.length)
|
|
311
|
+
return;
|
|
312
|
+
const failed = outcomes.filter(o => o.entry.status === 'error');
|
|
313
|
+
const published = outcomes.length - failed.length;
|
|
314
|
+
console.log('\n' +
|
|
315
|
+
colors.bold('publish') +
|
|
316
|
+
' ' +
|
|
317
|
+
colors.green(`${published} published`) +
|
|
318
|
+
colors.gray(', ') +
|
|
319
|
+
(failed.length ? colors.red(`${failed.length} failed`) : colors.gray('0 failed')));
|
|
320
|
+
for (const { target, entry } of failed) {
|
|
321
|
+
const [first, ...rest] = (entry.reason ?? '').split('\n');
|
|
322
|
+
console.log(` ${colors.red('failed')} ${colors.gray(`[${target}]`)} ${colors.cyan(entry.package.name)} ${entry.version}`);
|
|
323
|
+
console.log(` ${colors.red(first ?? '')}`);
|
|
324
|
+
for (const line of rest)
|
|
325
|
+
console.log(colors.gray(` ${line}`));
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
/**
|
|
329
|
+
* The same outcome as a table on the job's summary page, when running in GitHub Actions - where a
|
|
330
|
+
* reader looks first, and where a failed release otherwise shows only "exit code 1".
|
|
331
|
+
*
|
|
332
|
+
* A no-op outside it (`GITHUB_STEP_SUMMARY` unset), and a summary that cannot be written is not a
|
|
333
|
+
* reason to fail a publish that worked.
|
|
334
|
+
*/
|
|
335
|
+
function writeStepSummary(outcomes) {
|
|
336
|
+
const file = process.env.GITHUB_STEP_SUMMARY;
|
|
337
|
+
if (!file || !outcomes.length)
|
|
338
|
+
return;
|
|
339
|
+
const cell = (text) => text.replace(/\|/g, '\\|').replace(/\n+/g, '<br>');
|
|
340
|
+
const rows = outcomes.map(({ target, entry }) => entry.status === 'error'
|
|
341
|
+
? `| ❌ failed | ${target} | \`${entry.package.name}\` | ${entry.version} | ${cell(entry.reason ?? '')} |`
|
|
342
|
+
: `| ✅ published | ${target} | \`${entry.package.name}\` | ${entry.version} | ${cell(entry.detail ?? '')} |`);
|
|
343
|
+
const failed = outcomes.filter(o => o.entry.status === 'error').length;
|
|
344
|
+
const markdown = [
|
|
345
|
+
`### rman publish - ${outcomes.length - failed} published, ${failed} failed`,
|
|
346
|
+
'',
|
|
347
|
+
'| Result | Target | Package | Version | Detail |',
|
|
348
|
+
'| --- | --- | --- | --- | --- |',
|
|
349
|
+
...rows,
|
|
350
|
+
'',
|
|
351
|
+
].join('\n');
|
|
352
|
+
try {
|
|
353
|
+
fs.appendFileSync(file, markdown + '\n');
|
|
354
|
+
}
|
|
355
|
+
catch {
|
|
356
|
+
/* The summary is a courtesy; the release itself is what the exit code reports. */
|
|
285
357
|
}
|
|
286
|
-
return failed;
|
|
287
358
|
}
|
|
288
359
|
/** The `logged` convention: printed here, so `runCli`'s catch does not print it a second time. */
|
|
289
360
|
/* On stderr, because under `--json` stdout is one JSON document: measured on opra's release, the
|
|
@@ -83,7 +83,8 @@ const config = {
|
|
|
83
83
|
target: 'both',
|
|
84
84
|
describe: 'Make the bump a prerelease with this identifier (e.g. "beta" -> 1.2.3-beta.0). ' +
|
|
85
85
|
'Running again with the same --preid increments it (-> 1.2.3-beta.1); a different ' +
|
|
86
|
-
'identifier
|
|
86
|
+
'identifier keeps the version when it already holds the change (2.19.0-alpha.1 -> ' +
|
|
87
|
+
'2.19.0-beta.0). Ignored when bump is an explicit version. ' +
|
|
87
88
|
'Default: .rmanrc "version.preid", which every member of a group has to agree on.',
|
|
88
89
|
type: 'string',
|
|
89
90
|
},
|
package/constants.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export const version = '2.
|
|
1
|
+
export const version = '2.18.1';
|
|
@@ -133,6 +133,12 @@ export declare class SemverScheme extends VersionScheme {
|
|
|
133
133
|
* inventing one. Same answer for a release, which has no prerelease at all.
|
|
134
134
|
*/
|
|
135
135
|
prereleaseId(version: string): string | undefined;
|
|
136
|
+
/**
|
|
137
|
+
* Whether the release a prerelease of `version` leads to already holds a change of size `bump` -
|
|
138
|
+
* `X.Y.0` a minor, `X.0.0` a major, and anything a patch. The rule `semver.inc` itself applies:
|
|
139
|
+
* `minor` on `2.19.0-alpha.1` is `2.19.0`, not `2.20.0`.
|
|
140
|
+
*/
|
|
141
|
+
protected coversBump(version: string, bump: string): boolean;
|
|
136
142
|
}
|
|
137
143
|
/**
|
|
138
144
|
* The default, and what every package gets unless something says otherwise - so nothing about
|
|
@@ -128,8 +128,18 @@ export class SemverScheme extends VersionScheme {
|
|
|
128
128
|
*/
|
|
129
129
|
const existing = semver.prerelease(current);
|
|
130
130
|
const sameLine = existing && String(existing[0]) === preid;
|
|
131
|
-
|
|
132
|
-
|
|
131
|
+
if (sameLine)
|
|
132
|
+
return semver.inc(current, 'prerelease', preid) ?? current;
|
|
133
|
+
/**
|
|
134
|
+
* **A prerelease moving to another identifier keeps its version when that version already
|
|
135
|
+
* holds the change** - `2.19.0-alpha.1` -> `2.19.0-beta.0` for a fix or a feature, since
|
|
136
|
+
* `2.19.0` is a minor's target already. It moves on only when the change needs more than the
|
|
137
|
+
* target gives: a breaking change on `2.19.0-alpha.1` is `3.0.0-beta.0`.
|
|
138
|
+
*/
|
|
139
|
+
if (existing && this.coversBump(current, bump)) {
|
|
140
|
+
return `${semver.major(current)}.${semver.minor(current)}.${semver.patch(current)}-${preid}.0`;
|
|
141
|
+
}
|
|
142
|
+
return semver.inc(current, `pre${releaseType}`, preid) ?? current;
|
|
133
143
|
}
|
|
134
144
|
isPrerelease(version) {
|
|
135
145
|
return !!semver.prerelease(version);
|
|
@@ -145,6 +155,22 @@ export class SemverScheme extends VersionScheme {
|
|
|
145
155
|
const first = semver.prerelease(version)?.[0];
|
|
146
156
|
return typeof first === 'string' ? first : undefined;
|
|
147
157
|
}
|
|
158
|
+
/**
|
|
159
|
+
* Whether the release a prerelease of `version` leads to already holds a change of size `bump` -
|
|
160
|
+
* `X.Y.0` a minor, `X.0.0` a major, and anything a patch. The rule `semver.inc` itself applies:
|
|
161
|
+
* `minor` on `2.19.0-alpha.1` is `2.19.0`, not `2.20.0`.
|
|
162
|
+
*/
|
|
163
|
+
/* **Only consulted when the identifier changes.** On the same identifier the counter advances
|
|
164
|
+
* whatever the size - a permanent `-rev.N` line (`version.preid: rev`) counts revisions of one
|
|
165
|
+
* upstream version, so a feature there is `rev.9`, not a new base. That is a statement about the
|
|
166
|
+
* line, and changing it would move `panates/browsery` off the version it wraps. */
|
|
167
|
+
coversBump(version, bump) {
|
|
168
|
+
if (bump === 'major')
|
|
169
|
+
return semver.minor(version) === 0 && semver.patch(version) === 0;
|
|
170
|
+
if (bump === 'minor')
|
|
171
|
+
return semver.patch(version) === 0;
|
|
172
|
+
return true;
|
|
173
|
+
}
|
|
148
174
|
}
|
|
149
175
|
/**
|
|
150
176
|
* The default, and what every package gets unless something says otherwise - so nothing about
|
|
@@ -41,6 +41,11 @@ export interface PublishTarget {
|
|
|
41
41
|
* Name a flag for the target when it genuinely belongs to one (`--docker-namespace`).
|
|
42
42
|
*/
|
|
43
43
|
readonly options?: Record<string, RmanConfig.CommandOption>;
|
|
44
|
+
/**
|
|
45
|
+
* Applied after every target without it. For a target whose artifact is built **from** what the
|
|
46
|
+
* others publish - an image whose `Dockerfile` runs `npm install` - and so has to wait for them.
|
|
47
|
+
*/
|
|
48
|
+
readonly publishesLast?: boolean;
|
|
44
49
|
/**
|
|
45
50
|
* The technologies this target is for, by `Platform.name` - `['node']` for `npm`. Absent means
|
|
46
51
|
* every platform, which is what a technology-agnostic target says: any package can carry a
|
|
@@ -72,6 +72,13 @@ export type CommandEntry = string | CustomCommand | RmanConfig.CommandRegisterFu
|
|
|
72
72
|
/** One entry of `.rmanrc "publishTargets"` - a target, or a **glob** naming `.js` modules that
|
|
73
73
|
* `export default` one. */
|
|
74
74
|
export type PublishTargetEntry = string | PublishTarget;
|
|
75
|
+
/**
|
|
76
|
+
* `.rmanrc "packageManager"`: one entry per technology, by `Platform.name`. Empty here - each
|
|
77
|
+
* platform that has a package manager to choose adds its own entry by declaration merging, the way
|
|
78
|
+
* `PublishTargetConfigs` collects the publish targets' blocks.
|
|
79
|
+
*/
|
|
80
|
+
export interface PackageManagers {
|
|
81
|
+
}
|
|
75
82
|
/**
|
|
76
83
|
* **The `.rmanrc` keys no command owns**, without `extends` - kept
|
|
77
84
|
* separate from `RmanConfig` only so the two halves stay legible. A key a *command* owns is
|
|
@@ -308,6 +315,11 @@ export interface RmanConfigKeys {
|
|
|
308
315
|
*/
|
|
309
316
|
vars?: Record<string, unknown>;
|
|
310
317
|
logLevel?: 'silent' | 'error' | 'info' | 'verbose';
|
|
318
|
+
/**
|
|
319
|
+
* Which package manager each technology uses, keyed by `Platform.name` - `{ node: 'pnpm' }`.
|
|
320
|
+
* Cascades per package. A technology with no choice to make (Cargo has `cargo`) adds no entry.
|
|
321
|
+
*/
|
|
322
|
+
packageManager?: PackageManagers;
|
|
311
323
|
allowBranch?: string | string[];
|
|
312
324
|
ignoreBranch?: string | string[];
|
|
313
325
|
/**
|
package/package.json
CHANGED
|
@@ -1,33 +1,25 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "rman",
|
|
3
3
|
"description": "Repository manager",
|
|
4
|
-
"version": "2.
|
|
4
|
+
"version": "2.18.1",
|
|
5
5
|
"author": "Panates",
|
|
6
6
|
"license": "MIT",
|
|
7
7
|
"dependencies": {
|
|
8
|
+
"@jsopen/objects": "^2.3.4",
|
|
8
9
|
"@netlify/parse-npm-script": "^0.1.2",
|
|
9
10
|
"@xmldom/xmldom": "^0.9.12",
|
|
10
|
-
"@jsopen/objects": "^2.3.4",
|
|
11
11
|
"ansi-colors": "^4.1.3",
|
|
12
|
-
"cross-dirname": "^0.1.0",
|
|
13
12
|
"easy-table": "^1.2.0",
|
|
14
13
|
"envinfo": "^7.21.0",
|
|
15
14
|
"fast-glob": "^3.3.3",
|
|
16
|
-
"fast-tokenizer": "^1.10.
|
|
15
|
+
"fast-tokenizer": "^1.10.1",
|
|
17
16
|
"ini": "^7.0.0",
|
|
18
|
-
"is-ci": "^4.1.0",
|
|
19
17
|
"js-yaml": "^5.4.1",
|
|
20
18
|
"micromatch": "^4.0.8",
|
|
21
|
-
"npmlog": "^7.0.1",
|
|
22
19
|
"power-tasks": "^1.14.2",
|
|
23
|
-
"putil-merge": "^3.13.0",
|
|
24
|
-
"putil-varhelpers": "^1.7.0",
|
|
25
20
|
"semver": "^7.8.5",
|
|
26
21
|
"signal-exit": "^4.1.0",
|
|
27
|
-
"
|
|
28
|
-
"strip-color": "^0.1.0",
|
|
29
|
-
"tslib": "^2.8.1",
|
|
30
|
-
"yargs": "^18.1.0"
|
|
22
|
+
"yargs": "^18.2.0"
|
|
31
23
|
},
|
|
32
24
|
"type": "module",
|
|
33
25
|
"module": "./index.js",
|
package/utils/git.js
CHANGED
|
@@ -228,7 +228,7 @@ export class GitHelper {
|
|
|
228
228
|
async moveHistory(file) {
|
|
229
229
|
let stdout;
|
|
230
230
|
try {
|
|
231
|
-
({ stdout } = await execFileAsync('git', ['log', '--follow', '--format=%x00%H', '--name-status', '--', path.relative(this.cwd, file)], { cwd: this.cwd
|
|
231
|
+
({ stdout } = await execFileAsync('git', ['log', '--follow', '--format=%x00%H', '--name-status', '--', path.relative(this.cwd, file)], { cwd: this.cwd }));
|
|
232
232
|
}
|
|
233
233
|
catch {
|
|
234
234
|
return [];
|
|
@@ -263,7 +263,6 @@ export class GitHelper {
|
|
|
263
263
|
if (!renames) {
|
|
264
264
|
renames = execFileAsync('git', ['show', '-M', '--name-status', '--format=', sha], {
|
|
265
265
|
cwd: this.cwd,
|
|
266
|
-
maxBuffer: 64 * 1024 * 1024,
|
|
267
266
|
}).then(({ stdout }) => stdout
|
|
268
267
|
.split(/\r?\n/)
|
|
269
268
|
.map(line => line.split('\t'))
|
|
@@ -275,7 +274,6 @@ export class GitHelper {
|
|
|
275
274
|
try {
|
|
276
275
|
const { stdout } = await execFileAsync('git', ['ls-tree', '-r', '--name-only', `${sha}^`, '--', `${fromDir}/`], {
|
|
277
276
|
cwd: this.cwd,
|
|
278
|
-
maxBuffer: 64 * 1024 * 1024,
|
|
279
277
|
});
|
|
280
278
|
held = stdout.split(/\r?\n/).filter(Boolean);
|
|
281
279
|
}
|
|
@@ -436,7 +434,7 @@ export class GitHelper {
|
|
|
436
434
|
if (pathspec)
|
|
437
435
|
args.push('--', pathspec);
|
|
438
436
|
try {
|
|
439
|
-
const { stdout } = await execFileAsync('git', args, { cwd: this.cwd
|
|
437
|
+
const { stdout } = await execFileAsync('git', args, { cwd: this.cwd });
|
|
440
438
|
return stdout;
|
|
441
439
|
}
|
|
442
440
|
catch (e) {
|
|
@@ -450,7 +448,6 @@ export class GitHelper {
|
|
|
450
448
|
try {
|
|
451
449
|
const { stdout } = await execFileAsync('git', ['format-patch', '--root', '-o', outputDir, 'HEAD'], {
|
|
452
450
|
cwd: this.cwd,
|
|
453
|
-
maxBuffer: 64 * 1024 * 1024,
|
|
454
451
|
});
|
|
455
452
|
return stdout.trim() ? stdout.trim().split(/\r?\n/) : [];
|
|
456
453
|
}
|
|
@@ -465,11 +462,20 @@ export class GitHelper {
|
|
|
465
462
|
if (!patchFiles.length)
|
|
466
463
|
return;
|
|
467
464
|
try {
|
|
468
|
-
await execFileAsync('git', ['am', '--3way', ...patchFiles], { cwd: this.cwd
|
|
465
|
+
await execFileAsync('git', ['am', '--3way', ...patchFiles], { cwd: this.cwd });
|
|
469
466
|
}
|
|
470
467
|
catch (e) {
|
|
471
468
|
throw new Error(`Unable to apply patches: ${e.message}`, { cause: e });
|
|
472
469
|
}
|
|
473
470
|
}
|
|
474
471
|
}
|
|
475
|
-
|
|
472
|
+
/* **One buffer for every git call, not one per call site.** Node's `execFile` defaults `maxBuffer`
|
|
473
|
+
* to 1 MiB and fails the call past it, and how much git prints depends on the repository, not on
|
|
474
|
+
* the command. Opting in call by call left the `git show --name-only` behind `listCommittedFiles`
|
|
475
|
+
* and `listCommits` on the default, so one vendored-library commit in `panates/formwave` - 28k
|
|
476
|
+
* files, 2.9 MB of names - failed `rman list` with "stdout maxBuffer length exceeded". */
|
|
477
|
+
const GIT_MAX_BUFFER = 64 * 1024 * 1024;
|
|
478
|
+
const execFileP = promisify(execFile);
|
|
479
|
+
function execFileAsync(file, args, options) {
|
|
480
|
+
return execFileP(file, args, { maxBuffer: GIT_MAX_BUFFER, ...options });
|
|
481
|
+
}
|