rman 1.0.12 → 1.1.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 +225 -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 +60 -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 +142 -13
- package/core/config.js +266 -43
- 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 +57 -0
- package/core/merge-config.js +146 -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 +77 -1
- package/core/repository.js +242 -129
- package/core/resolve-target.d.ts +12 -0
- package/core/resolve-target.js +33 -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 +54 -8
- package/index.js +42 -7
- package/interfaces/rman-config.interface.d.ts +171 -36
- package/package.json +15 -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 +60 -0
- package/services/run.service.js +109 -65
- 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 +92 -82
- package/services/version.service.js +219 -433
- 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/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
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import path from 'node:path';
|
|
2
|
+
import colors from 'ansi-colors';
|
|
3
|
+
import * as yaml from 'js-yaml';
|
|
4
|
+
import { DEFERRED_PATHS } from '../core/config.js';
|
|
5
|
+
import { applyRootOption, readRootOption } from '../utils/package-filter.js';
|
|
6
|
+
export function initCli(repository, program) {
|
|
7
|
+
program.command({
|
|
8
|
+
command: 'config',
|
|
9
|
+
describe: 'Prints the effective .rmanrc config for the package of the current directory',
|
|
10
|
+
builder: cmd => applyRootOption(cmd, 'Print the config for')
|
|
11
|
+
.example('$0 config', '# The config of the package you are standing in')
|
|
12
|
+
.example('$0 config --root', "# The repository root's own config instead")
|
|
13
|
+
.example('$0 config --json | jq .version', '# Machine-readable')
|
|
14
|
+
.option('json', {
|
|
15
|
+
describe: 'Print as JSON instead of YAML - nothing else on stdout, so it can be piped.',
|
|
16
|
+
type: 'boolean',
|
|
17
|
+
default: false,
|
|
18
|
+
}),
|
|
19
|
+
handler: args => {
|
|
20
|
+
const target = (!readRootOption(args) && repository.currentPackage) || repository.rootPackage;
|
|
21
|
+
if (args.json) {
|
|
22
|
+
console.log(JSON.stringify(target.config, undefined, 2));
|
|
23
|
+
return;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* **Colour only on a terminal, and here that is correctness rather than taste.** The header
|
|
27
|
+
* and notes are YAML `#` comments so the whole output stays loadable - and an escape sequence
|
|
28
|
+
* inside one makes it *unloadable*: `rman config > rmanrc.yml` wrote a file js-yaml rejects
|
|
29
|
+
* with "the stream contains non-printable characters" (measured; `ansi-colors` does not turn
|
|
30
|
+
* itself off for a pipe here). The repository's own convention for this is
|
|
31
|
+
* `process.stdout.isTTY`, as `run`/`exec`'s progress panel uses.
|
|
32
|
+
*/
|
|
33
|
+
const comment = (text) => (process.stdout.isTTY ? colors.gray(text) : text);
|
|
34
|
+
const relativeDir = path.relative(repository.dirname, target.dirname) || '.';
|
|
35
|
+
console.log(comment(`# ${target.name} (${relativeDir})`));
|
|
36
|
+
for (const note of deferredNotes(target))
|
|
37
|
+
console.log(comment(`# ${note}`));
|
|
38
|
+
/** `noRefs`: a value appearing twice in the config is the *same object* after merging, and
|
|
39
|
+
* js-yaml would otherwise emit the second as an `*anchor` reference - valid YAML that reads
|
|
40
|
+
* as a mistake in something meant to be looked at. */
|
|
41
|
+
console.log(yaml.dump(target.config, { noRefs: true, lineWidth: 100 }).trimEnd());
|
|
42
|
+
},
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* The one honest caveat about this output: **`version.before`/`.exec`/`.after` are printed raw**,
|
|
47
|
+
* expressions and all, because they are the paths in `DEFERRED_PATHS` - `${{ pkg.targetVersion }}`
|
|
48
|
+
* cannot be evaluated until `version` has computed a plan, so the repository deliberately leaves
|
|
49
|
+
* them unevaluated at load. Without saying so, a reader sees an uninterpolated `${{ ... }}` sitting
|
|
50
|
+
* among interpolated values and concludes interpolation is broken.
|
|
51
|
+
*/
|
|
52
|
+
function deferredNotes(pkg) {
|
|
53
|
+
const raw = DEFERRED_PATHS.filter(p => valueAt(pkg.config, p) !== undefined);
|
|
54
|
+
if (!raw.length)
|
|
55
|
+
return [];
|
|
56
|
+
return [`${raw.join(', ')}: printed raw - evaluated by "version" itself, once it knows the target version`];
|
|
57
|
+
}
|
|
58
|
+
function valueAt(config, dotted) {
|
|
59
|
+
return dotted.split('.').reduce((node, key) => (node == null ? undefined : node[key]), config);
|
|
60
|
+
}
|
package/commands/diff.command.js
CHANGED
|
@@ -1,14 +1,16 @@
|
|
|
1
1
|
import path from 'node:path';
|
|
2
2
|
import colors from 'ansi-colors';
|
|
3
|
-
import {
|
|
3
|
+
import { ChangeHashService } from '../services/change-hash.service.js';
|
|
4
4
|
import { GitHelper } from '../utils/git.js';
|
|
5
|
+
import { applyRootOption, readRootOption } from '../utils/package-filter.js';
|
|
5
6
|
export function initCli(repository, program) {
|
|
6
7
|
program.command({
|
|
7
8
|
command: 'diff [package]',
|
|
8
9
|
describe: "Shows the git diff since a package's (or the whole repository's) last release tag",
|
|
9
|
-
builder: cmd => cmd
|
|
10
|
+
builder: cmd => applyRootOption(cmd, 'Diff')
|
|
10
11
|
.example('$0 diff', "# Since the repository's own last tag")
|
|
11
12
|
.example('$0 diff pkg-a', "# Since pkg-a's own last tag, scoped to its directory")
|
|
13
|
+
.example('$0 diff --root', '# The whole repository, from inside a package')
|
|
12
14
|
.positional('package', {
|
|
13
15
|
describe: 'Package name - diffs just that package, since its own last tag. Omit to diff the whole ' +
|
|
14
16
|
"repository since its own last tag (or the current directory's package, if standing inside one).",
|
|
@@ -31,11 +33,14 @@ export function initCli(repository, program) {
|
|
|
31
33
|
target = pkg;
|
|
32
34
|
pathspec = path.relative(repository.dirname, pkg.dirname);
|
|
33
35
|
}
|
|
34
|
-
else if (repository.currentPackage) {
|
|
36
|
+
else if (!readRootOption(args) && repository.currentPackage) {
|
|
37
|
+
/** The measured gap this closes: `diff` narrowed to the current package like `run` and
|
|
38
|
+
* `changelog` do, but offered no way to say "the whole repository" without naming a
|
|
39
|
+
* package - and no package name means the repository, so there was nothing to type. */
|
|
35
40
|
target = repository.currentPackage;
|
|
36
41
|
pathspec = path.relative(repository.dirname, target.dirname);
|
|
37
42
|
}
|
|
38
|
-
const tag = await findLatestTag(git, target);
|
|
43
|
+
const tag = await ChangeHashService.findLatestTag(git, target);
|
|
39
44
|
if (!tag) {
|
|
40
45
|
console.log(colors.gray(`No release tag found for "${target.name}" - nothing to diff against.`));
|
|
41
46
|
return;
|
package/commands/exec.command.js
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import { ExecService } from '../services/exec.service.js';
|
|
2
2
|
import { applyBranchGuardOptions, assertAllowedBranch, readBranchGuardOptions } from '../utils/branch-guard.js';
|
|
3
|
-
import { applyPackageFilterOptions, readPackageFilterOptions } from '../utils/package-filter.js';
|
|
3
|
+
import { applyPackageFilterOptions, applyRootOption, readPackageFilterOptions } from '../utils/package-filter.js';
|
|
4
4
|
export function initCli(repository, program) {
|
|
5
5
|
program.command({
|
|
6
6
|
command: 'exec [command..]',
|
|
7
7
|
describe: 'Runs an arbitrary shell command in each package - unlike run, not tied to any npm script',
|
|
8
|
-
builder: cmd => applyBranchGuardOptions(applyPackageFilterOptions(cmd))
|
|
8
|
+
builder: cmd => applyRootOption(applyBranchGuardOptions(applyPackageFilterOptions(cmd)), 'Run')
|
|
9
9
|
.parserConfiguration({ 'populate--': true, 'unknown-options-as-args': true })
|
|
10
10
|
.example('$0 exec rm -rf dist', '# Not an npm script - runs directly in every package')
|
|
11
11
|
.example('$0 exec --scope pkg-a -- ls -la', '# "--" needed only if the command shares a flag name with exec\'s own')
|
|
@@ -50,12 +50,6 @@ export function initCli(repository, program) {
|
|
|
50
50
|
.option('changed-since', {
|
|
51
51
|
describe: 'Only run in packages that have changed since the given git commit/hash',
|
|
52
52
|
type: 'string',
|
|
53
|
-
})
|
|
54
|
-
.option('root', {
|
|
55
|
-
alias: 'r',
|
|
56
|
-
describe: 'Run across the whole repository even when the current directory is inside a single ' +
|
|
57
|
-
'package (which otherwise scopes the run to just that package). No effect elsewhere.',
|
|
58
|
-
type: 'boolean',
|
|
59
53
|
})
|
|
60
54
|
.conflicts('changed', 'changed-since'),
|
|
61
55
|
handler: async (args) => {
|
|
@@ -5,6 +5,7 @@ import { applyBranchGuardOptions, assertAllowedBranch, readBranchGuardOptions }
|
|
|
5
5
|
export function initCli(repository, program) {
|
|
6
6
|
program.command({
|
|
7
7
|
command: 'github-release',
|
|
8
|
+
configKeys: ['githubRelease', 'version.releaseTagPattern'],
|
|
8
9
|
describe: "Creates the repository's GitHub Release for the version that just shipped",
|
|
9
10
|
builder: cmd => applyBranchGuardOptions(cmd)
|
|
10
11
|
.example('$0 github-release', '# Show what would be released, then ask for confirmation')
|
|
@@ -1,3 +1,12 @@
|
|
|
1
1
|
import type { Argv } from 'yargs';
|
|
2
2
|
import type { Repository } from '../core/repository.js';
|
|
3
|
+
/**
|
|
4
|
+
* `rman info` - environment and repository, and back in the core because most of what it reports
|
|
5
|
+
* (OS, CPU, shell, git, the repository's own shape) is true of any repository.
|
|
6
|
+
*
|
|
7
|
+
* The Node half is not here at all: installing `rman-node` augments `SystemInfo` and the package
|
|
8
|
+
* manager, `npmPackages` and this plugin's own version start appearing. Without it nothing
|
|
9
|
+
* npm-shaped is asked for or printed, which is the right answer for a repository in any other
|
|
10
|
+
* language.
|
|
11
|
+
*/
|
|
3
12
|
export declare function initCli(repository: Repository, program: Argv): void;
|
package/commands/info.command.js
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import colors from 'ansi-colors';
|
|
2
2
|
import semver from 'semver';
|
|
3
|
-
import { CiService } from '../services/ci.service.js';
|
|
4
3
|
import { SystemInfo } from '../services/system-info.js';
|
|
5
4
|
function printSystemInfo(systemInfo) {
|
|
6
5
|
const maxName = Object.keys(systemInfo).reduce((l, p) => Object.keys(systemInfo[p]).reduce((i, x) => Math.max(i, x.length), l), 0);
|
|
@@ -36,6 +35,15 @@ function printRepositoryInfo(info) {
|
|
|
36
35
|
console.log(' ' + colors.reset('Packages') + ' :', colors.yellowBright(String(info.packageCount)), colors.gray('(run "list" to see them)'));
|
|
37
36
|
}
|
|
38
37
|
}
|
|
38
|
+
/**
|
|
39
|
+
* `rman info` - environment and repository, and back in the core because most of what it reports
|
|
40
|
+
* (OS, CPU, shell, git, the repository's own shape) is true of any repository.
|
|
41
|
+
*
|
|
42
|
+
* The Node half is not here at all: installing `rman-node` augments `SystemInfo` and the package
|
|
43
|
+
* manager, `npmPackages` and this plugin's own version start appearing. Without it nothing
|
|
44
|
+
* npm-shaped is asked for or printed, which is the right answer for a repository in any other
|
|
45
|
+
* language.
|
|
46
|
+
*/
|
|
39
47
|
export function initCli(repository, program) {
|
|
40
48
|
program.command({
|
|
41
49
|
command: 'info',
|
|
@@ -49,7 +57,9 @@ export function initCli(repository, program) {
|
|
|
49
57
|
type: 'boolean',
|
|
50
58
|
}),
|
|
51
59
|
handler: async (args) => {
|
|
52
|
-
|
|
60
|
+
/** Only the repository - which package manager to report, if any, is a question the core
|
|
61
|
+
* cannot ask. `rman-node`'s augmentation reads `.rmanrc "packageManager"` off this. */
|
|
62
|
+
const systemInfo = await SystemInfo.getSystemInfo({ repository });
|
|
53
63
|
const repositoryInfo = SystemInfo.getRepositoryInfo(repository);
|
|
54
64
|
if (args.json) {
|
|
55
65
|
console.log(JSON.stringify({ ...systemInfo, repository: repositoryInfo }, undefined, 2));
|
package/commands/run.command.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { RunService } from '../services/run.service.js';
|
|
2
2
|
import { applyBranchGuardOptions, assertAllowedBranch, readBranchGuardOptions } from '../utils/branch-guard.js';
|
|
3
|
-
import { applyPackageFilterOptions, readPackageFilterOptions } from '../utils/package-filter.js';
|
|
3
|
+
import { applyPackageFilterOptions, applyRootOption, readPackageFilterOptions } from '../utils/package-filter.js';
|
|
4
4
|
export function applyRunOptions(cmd) {
|
|
5
|
-
return applyBranchGuardOptions(applyPackageFilterOptions(cmd))
|
|
5
|
+
return applyRootOption(applyBranchGuardOptions(applyPackageFilterOptions(cmd)), 'Run')
|
|
6
6
|
.option('parallel', {
|
|
7
7
|
describe: 'Max packages to build at once: omit/true for CPU count (or .rmanrc run.<script>.concurrency), ' +
|
|
8
8
|
'a number for that many, false to run serially. Packages always build in dependency order.',
|
|
@@ -39,12 +39,6 @@ export function applyRunOptions(cmd) {
|
|
|
39
39
|
.option('changed-since', {
|
|
40
40
|
describe: 'Only run in packages that have changed since the given git commit/hash',
|
|
41
41
|
type: 'string',
|
|
42
|
-
})
|
|
43
|
-
.option('root', {
|
|
44
|
-
alias: 'r',
|
|
45
|
-
describe: 'Run across the whole repository even when the current directory is inside a single ' +
|
|
46
|
-
'package (which otherwise scopes the run to just that package). No effect elsewhere.',
|
|
47
|
-
type: 'boolean',
|
|
48
42
|
})
|
|
49
43
|
.conflicts('changed', 'changed-since');
|
|
50
44
|
}
|
|
@@ -64,6 +58,9 @@ export function readRunOptions(args) {
|
|
|
64
58
|
export function initCli(repository, program) {
|
|
65
59
|
program.command({
|
|
66
60
|
command: 'run <script>',
|
|
61
|
+
/** Which `.rmanrc` `--config` shows for this command: the script's own block, whichever
|
|
62
|
+
* script was asked for. */
|
|
63
|
+
configKeys: (args) => ['run.' + args.script],
|
|
67
64
|
describe: 'Run an npm script in each package',
|
|
68
65
|
builder: cmd => applyRunOptions(cmd)
|
|
69
66
|
.example('$0 run build', '')
|
package/commands/test.command.js
CHANGED
|
@@ -4,6 +4,7 @@ import { applyRunOptions, readRunOptions } from './run.command.js';
|
|
|
4
4
|
export function initCli(repository, program) {
|
|
5
5
|
program.command({
|
|
6
6
|
command: 'test',
|
|
7
|
+
configKeys: ['run.test'],
|
|
7
8
|
describe: 'Alias for "run test"',
|
|
8
9
|
builder: cmd => applyRunOptions(cmd).example('$0 test', '# Tests packages'),
|
|
9
10
|
handler: async (args) => {
|
|
@@ -2,19 +2,32 @@ import readline from 'node:readline/promises';
|
|
|
2
2
|
import colors from 'ansi-colors';
|
|
3
3
|
import EasyTable from 'easy-table';
|
|
4
4
|
import { VersionService } from '../services/version.service.js';
|
|
5
|
+
import { VersionPlanService } from '../services/version-plan.service.js';
|
|
5
6
|
import { applyBranchGuardOptions, assertAllowedBranch, readBranchGuardOptions } from '../utils/branch-guard.js';
|
|
6
7
|
import { applyPackageFilterOptions, readPackageFilterOptions } from '../utils/package-filter.js';
|
|
7
8
|
export function initCli(repository, program) {
|
|
9
|
+
/** `rman version <bump|version>` is validated by the root's scheme (see `getPlan`), so the help
|
|
10
|
+
* has to name that scheme's own words rather than semver's - otherwise `--help` in a repository
|
|
11
|
+
* numbering some other way documents keywords its own planner would reject. A four-part scheme
|
|
12
|
+
* lists four here. */
|
|
13
|
+
const { name: scheme, bumpNames } = repository.rootPackage.versionScheme;
|
|
14
|
+
const bumps = bumpNames.map(n => `"${n}"`).join('/');
|
|
15
|
+
/** For the examples. Not `smallestBump`, which throws: `--help` must still render for a scheme
|
|
16
|
+
* that declares none, and the placeholder says what to write there. */
|
|
17
|
+
const smallest = bumpNames[0] ?? '<bump>';
|
|
8
18
|
program.command({
|
|
9
19
|
command: 'version [bump]',
|
|
20
|
+
/** `changelog` too: `--changelog` folds one into the bump commit, and `group` is what decides
|
|
21
|
+
* which packages move together. */
|
|
22
|
+
configKeys: ['version', 'changelog', 'group'],
|
|
10
23
|
describe: 'Bumps versions of changed packages (and their dependents), grouped via .rmanrc "group"',
|
|
11
24
|
builder: cmd => applyBranchGuardOptions(applyPackageFilterOptions(cmd))
|
|
12
|
-
.example(
|
|
13
|
-
.example('$0 version', "# Auto-detect
|
|
25
|
+
.example(`$0 version ${smallest}`, `# Bump ${smallest} directly, applied immediately`)
|
|
26
|
+
.example('$0 version', "# Auto-detect the bump from commits, show the plan, don't write anything")
|
|
14
27
|
.example('$0 version --interactive', '# Show the plan either way, then ask for confirmation')
|
|
15
|
-
.example(
|
|
28
|
+
.example(`$0 version ${smallest} --show`, `# Preview what an explicit ${smallest} would do, without applying it`)
|
|
16
29
|
.positional('bump', {
|
|
17
|
-
describe:
|
|
30
|
+
describe: `A bump keyword (${bumps}) or an explicit ${scheme} version. ` +
|
|
18
31
|
'Omit to auto-detect from commits and only preview the plan.',
|
|
19
32
|
type: 'string',
|
|
20
33
|
})
|
|
@@ -25,15 +38,15 @@ export function initCli(repository, program) {
|
|
|
25
38
|
})
|
|
26
39
|
.option('show', {
|
|
27
40
|
describe: 'Show the resulting plan for the given bump without applying it - unlike omitting bump ' +
|
|
28
|
-
'entirely, this still uses the given
|
|
41
|
+
'entirely, this still uses the given bump keyword/version to compute the plan, ' +
|
|
29
42
|
'just never writes it.',
|
|
30
43
|
type: 'boolean',
|
|
31
44
|
})
|
|
32
45
|
.conflicts('show', 'interactive')
|
|
33
46
|
.option('yes', {
|
|
34
47
|
alias: 'y',
|
|
35
|
-
describe: 'Skip the confirmation prompt and apply the computed plan immediately - auto-detected ' +
|
|
36
|
-
'
|
|
48
|
+
describe: 'Skip the confirmation prompt and apply the computed plan immediately - an auto-detected ' +
|
|
49
|
+
'bump included, no explicit bump keyword required (same idea as "publish --yes").',
|
|
37
50
|
type: 'boolean',
|
|
38
51
|
})
|
|
39
52
|
.conflicts('yes', 'interactive')
|
|
@@ -61,13 +74,13 @@ export function initCli(repository, program) {
|
|
|
61
74
|
.option('preid', {
|
|
62
75
|
describe: 'Make the bump a prerelease with this identifier (e.g. "beta" -> 1.2.3-beta.0). ' +
|
|
63
76
|
'Running again with the same --preid increments it (-> 1.2.3-beta.1); a different ' +
|
|
64
|
-
|
|
77
|
+
`identifier starts a fresh prerelease line. Ignored when bump is an explicit ${scheme} version.`,
|
|
65
78
|
type: 'string',
|
|
66
79
|
}),
|
|
67
80
|
handler: async (args) => {
|
|
68
81
|
await assertAllowedBranch(repository, readBranchGuardOptions(args));
|
|
69
82
|
const bump = args.bump;
|
|
70
|
-
const plan = await
|
|
83
|
+
const plan = await VersionPlanService.getPlanner().getPlan(repository, {
|
|
71
84
|
...readPackageFilterOptions(args),
|
|
72
85
|
bump,
|
|
73
86
|
ignoreDirty: args.ignoreDirty,
|
|
@@ -107,14 +120,40 @@ export function initCli(repository, program) {
|
|
|
107
120
|
message: args.message,
|
|
108
121
|
changelog,
|
|
109
122
|
});
|
|
110
|
-
|
|
111
|
-
if (entry.status === 'bump') {
|
|
112
|
-
console.log(colors.green('updated'), colors.cyan(entry.package.name), entry.from, '->', colors.yellow(entry.to));
|
|
113
|
-
}
|
|
114
|
-
}
|
|
123
|
+
printApplied(applied);
|
|
115
124
|
},
|
|
116
125
|
});
|
|
117
126
|
}
|
|
127
|
+
/**
|
|
128
|
+
* What the run *did* - which is deliberately not the table again.
|
|
129
|
+
*
|
|
130
|
+
* The plan is printed above, so repeating `name from -> to` per package said nothing: `applyPlan`
|
|
131
|
+
* returned the same array it was handed, so the second list could not have differed. What it never
|
|
132
|
+
* reported is everything below - the commits (one per group, plus the root's informational sync),
|
|
133
|
+
* the tags, a tag that already existed and was left alone, and whether any of it was pushed. A
|
|
134
|
+
* release that is committed but not pushed looks identical to one that is, until someone looks.
|
|
135
|
+
*/
|
|
136
|
+
function printApplied(result) {
|
|
137
|
+
const count = result.updated.length;
|
|
138
|
+
console.log(`\n${colors.green('updated')} ${count} package${count === 1 ? '' : 's'}`);
|
|
139
|
+
for (const commit of result.commits) {
|
|
140
|
+
console.log(`${colors.green('commit')} ${colors.yellow(commit.sha)} ${colors.gray(commit.message.split('\n')[0])}`);
|
|
141
|
+
}
|
|
142
|
+
if (result.tags.length) {
|
|
143
|
+
const label = (tag) => colors.cyan(tag.name) + (tag.created ? '' : colors.gray(' (existing, left alone)'));
|
|
144
|
+
const release = result.tags.filter(t => t.release);
|
|
145
|
+
const perPackage = result.tags.filter(t => !t.release);
|
|
146
|
+
if (perPackage.length)
|
|
147
|
+
console.log(`${colors.green('tags')} ${perPackage.map(label).join(', ')}`);
|
|
148
|
+
/** Listed on its own line: it belongs to the repository rather than to any package, which is
|
|
149
|
+
* the whole reason it exists. */
|
|
150
|
+
if (release.length)
|
|
151
|
+
console.log(`${colors.green('tags')} ${release.map(label).join(', ')} ${colors.gray('(repository release)')}`);
|
|
152
|
+
}
|
|
153
|
+
console.log(result.pushed
|
|
154
|
+
? `${colors.green('push')} pushed, with tags`
|
|
155
|
+
: `${colors.gray('push')} ${colors.gray('not pushed - run with --push, or push it yourself')}`);
|
|
156
|
+
}
|
|
118
157
|
function printPlan(entries) {
|
|
119
158
|
const table = new EasyTable();
|
|
120
159
|
for (const e of entries) {
|
package/constants.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export const version = '1.
|
|
1
|
+
export const version = '1.1.1';
|
package/core/config.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import path from 'path';
|
|
1
2
|
import semver from 'semver';
|
|
2
3
|
import type { RmanConfig } from '../interfaces/rman-config.interface.js';
|
|
3
4
|
/**
|
|
@@ -32,9 +33,10 @@ export declare function readDirConfig(dirname: string): Promise<RmanConfig>;
|
|
|
32
33
|
* `.rmanrc` therefore configures the *root package* - which is where repo-wide settings
|
|
33
34
|
* (`packageManager`, `allowBranch`, `version.*`, `githubRelease.*`) are read from anyway - and
|
|
34
35
|
* not, silently, every package under it.
|
|
35
|
-
* - **A `"[selector]"` block configures the packages it names**
|
|
36
|
-
* `"[
|
|
37
|
-
*
|
|
36
|
+
* - **A `"[selector]"` block configures the packages it names** - `"[*]"` for all of them (the root
|
|
37
|
+
* included), `"[ws:*]"` for every one but the root, `"[/]"` for the root alone, `"[*-dialect]"`
|
|
38
|
+
* for a glob over package names. See `parseSelector`. This is the only way a directory speaks
|
|
39
|
+
* about anything but its own package.
|
|
38
40
|
*
|
|
39
41
|
* Splitting the two matters because the same key means different things to the two audiences. The
|
|
40
42
|
* clearest case is `run.<script>.postScript`: on a package it's that package's build hook, run in
|
|
@@ -42,21 +44,87 @@ export declare function readDirConfig(dirname: string): Promise<RmanConfig>;
|
|
|
42
44
|
* cascade that fed one declaration to both ran a package-relative command (`node
|
|
43
45
|
* ../../support/postbuild.cjs`) at the root, where it cannot resolve.
|
|
44
46
|
*
|
|
45
|
-
* `packageName` is what selectors match against; without it
|
|
46
|
-
*
|
|
47
|
+
* `packageName` is what selectors match against; without it, selector blocks contribute nothing at
|
|
48
|
+
* all. The root package passes its own, since `"[/]"` and `"[*]"` speak to it.
|
|
47
49
|
*/
|
|
48
50
|
export declare function resolveConfig(rootDir: string, targetDir: string, cache?: Map<string, RmanConfig>, packageName?: string): Promise<RmanConfig>;
|
|
49
|
-
/** A config key naming packages rather than settings: `"[*]"`, `"[
|
|
51
|
+
/** A config key naming packages rather than settings: `"[*]"`, `"[/]"`, `"[ws:*]"`, `"[pkg-a]"`. The
|
|
50
52
|
* brackets are what keep this space from colliding with real config keys - no setting starts with
|
|
51
53
|
* one - and in YAML they also mean the key always needs quoting (`"[*]":`), since a bare `[*]`
|
|
52
54
|
* parses as a flow sequence. */
|
|
53
55
|
export declare function isSelectorKey(key: string): boolean;
|
|
56
|
+
/**
|
|
57
|
+
* **Which packages a selector speaks for.** Three audiences, because a repository has three:
|
|
58
|
+
*
|
|
59
|
+
* | | |
|
|
60
|
+
* | --- | --- |
|
|
61
|
+
* | `"[/]"` | the **root package** only |
|
|
62
|
+
* | `"[*]"`, `"[pkg-a]"`, `"[*-dialect]"` | **every** package the glob matches, root included |
|
|
63
|
+
* | `"[ws:*]"`, `"[workspace:pkg-*]"` | every **non-root** package the glob matches |
|
|
64
|
+
*
|
|
65
|
+
* `/` for the root because that is what a repository root is called everywhere else, and it cannot
|
|
66
|
+
* collide with a package name. `ws:` is a qualifier on the glob rather than a separate spelling of
|
|
67
|
+
* `*`, so `"[ws:pkg-*]"` means what it looks like.
|
|
68
|
+
*
|
|
69
|
+
* **`"[*]"` includes the root, and that is a change from how it used to read.** Before, selectors
|
|
70
|
+
* were not applied to the root at all, so `"[*]"` silently meant "the workspace packages" - a
|
|
71
|
+
* catch-all with an exception nothing in the syntax mentioned. The three names above say which
|
|
72
|
+
* audience is meant; `"[ws:*]"` is the old behaviour, now spelled.
|
|
73
|
+
*/
|
|
74
|
+
export declare function parseSelector(key: string): {
|
|
75
|
+
scope: 'root' | 'all' | 'workspace';
|
|
76
|
+
test: (name: string) => boolean;
|
|
77
|
+
};
|
|
54
78
|
/** The glob inside a selector key, as a `RegExp` anchored at both ends - so `"[*-dialect]"` matches
|
|
55
79
|
* `mysql-dialect` but not `my-dialect-helper`. Glob rather than regex, to match every other
|
|
56
80
|
* pattern in rman (`allowBranch`, `changelog.tagPattern`, `clean.include`). */
|
|
57
81
|
export declare function selectorToRegExp(key: string): RegExp;
|
|
58
82
|
/** One package, as an expression sees it - the same shape for the package the config belongs to
|
|
59
83
|
* and for the repository itself, so `${{ repository.basename }}` reads the way `${{ pkg.basename }}` does. */
|
|
84
|
+
/**
|
|
85
|
+
* `file` in a `${{ ... }}` expression: what is actually on disk, resolved against **the package
|
|
86
|
+
* the config was resolved for** - so one declaration in a `"[*]"` block asks each package about
|
|
87
|
+
* its own directory.
|
|
88
|
+
*
|
|
89
|
+
* The pair exists because a config has two different questions about a path, and answering both
|
|
90
|
+
* with one function would mean picking a wrong default for the other:
|
|
91
|
+
*
|
|
92
|
+
* ```yaml
|
|
93
|
+
* "[*]":
|
|
94
|
+
* run:
|
|
95
|
+
* build:
|
|
96
|
+
* # first of these that exists, and an error naming the config path if none do
|
|
97
|
+
* exec: 'tsc -b ${{ file.exists("tsconfig-build.json") || file.resolve("tsconfig.json") }}'
|
|
98
|
+
* ```
|
|
99
|
+
*/
|
|
100
|
+
export interface FileScope {
|
|
101
|
+
/**
|
|
102
|
+
* The absolute path if it exists, **`''` if it does not** - so `a || b || c` picks the first one
|
|
103
|
+
* present, and so a miss never reaches the "nullish inside a string" guard that `undefined` would
|
|
104
|
+
* trip. Accepts a relative path (against the package directory) or an absolute one.
|
|
105
|
+
*
|
|
106
|
+
* It returns a path rather than a boolean on purpose: the caller almost always wants the path,
|
|
107
|
+
* and a separate `file.path()` to fetch it after a boolean test would read the disk twice and
|
|
108
|
+
* invite the two calls to disagree.
|
|
109
|
+
*/
|
|
110
|
+
exists(target: string): string;
|
|
111
|
+
/** The absolute path, or **throws** - for a file whose absence is a mistake rather than a case to
|
|
112
|
+
* handle. The error names the config path holding the expression, like any other. */
|
|
113
|
+
resolve(target: string): string;
|
|
114
|
+
/**
|
|
115
|
+
* The first of several that exists, or **throws** naming every candidate it tried:
|
|
116
|
+
*
|
|
117
|
+
* ```yaml
|
|
118
|
+
* exec: 'tsc -b ${{ file.resolveFirst("tsconfig-build.json", "tsconfig.build.json", "tsconfig.json") }}'
|
|
119
|
+
* ```
|
|
120
|
+
*
|
|
121
|
+
* The same thing an `exists() || exists() || resolve()` chain does, said once - and it cannot be
|
|
122
|
+
* got subtly wrong the way that chain can: ending it in `exists()` leaves `tsc -b ` with no
|
|
123
|
+
* argument when nothing matches, and tsc then silently falls back to the directory's default
|
|
124
|
+
* rather than reporting that the package has no build config.
|
|
125
|
+
*/
|
|
126
|
+
resolveFirst(...targets: string[]): string;
|
|
127
|
+
}
|
|
60
128
|
export interface PackageScope {
|
|
61
129
|
/** The package's own name, scope included (`@sqb/builder`). */
|
|
62
130
|
name: string;
|
|
@@ -73,9 +141,29 @@ export interface PackageScope {
|
|
|
73
141
|
/** That directory relative to the repository root (`packages/builder`), which is what a command
|
|
74
142
|
* addressing another package from the root usually needs. Empty string for the root itself. */
|
|
75
143
|
relativeDir: string;
|
|
76
|
-
/**
|
|
77
|
-
*
|
|
78
|
-
|
|
144
|
+
/**
|
|
145
|
+
* Which ecosystem this package belongs to - `'node'` for one read by `rman-node`, empty when no
|
|
146
|
+
* plugin claimed it. The same `Package.provider`, so one declaration can address a single
|
|
147
|
+
* ecosystem in a polyglot repository (`if: "${{ pkg.provider === 'node' }}"`).
|
|
148
|
+
*/
|
|
149
|
+
provider: string;
|
|
150
|
+
/**
|
|
151
|
+
* The whole manifest, as a copy - so an expression can reach a field rman itself has no opinion
|
|
152
|
+
* about (`${{ pkg.manifest.engines.node }}`).
|
|
153
|
+
*
|
|
154
|
+
* Named `manifest`, not `json`: which file a package's identity lives in is the ecosystem's
|
|
155
|
+
* business now (see `ManifestProvider`), and `json` was that assumption showing through the one
|
|
156
|
+
* remaining user-facing name. A config written against `${{ pkg.json... }}` needs the rename.
|
|
157
|
+
*/
|
|
158
|
+
manifest: Record<string, unknown>;
|
|
159
|
+
/**
|
|
160
|
+
* The version this run is about to write - **bound only during `version`**, and only once its
|
|
161
|
+
* plan is computed. Reading it anywhere else throws rather than yielding `undefined`: no other
|
|
162
|
+
* command has a target version, so an expression asking for one has been put in the wrong place,
|
|
163
|
+
* and a config that quietly evaluates to "undefined" is the failure this evaluator exists to
|
|
164
|
+
* prevent.
|
|
165
|
+
*/
|
|
166
|
+
targetVersion: string;
|
|
79
167
|
}
|
|
80
168
|
/** Facts about the repository, on top of the root package's own - because the repository root *is*
|
|
81
169
|
* a package (`repository.name` is what its `package.json` says, `repository.basename` the directory
|
|
@@ -99,18 +187,40 @@ export interface GitScope {
|
|
|
99
187
|
/** Whether the working tree has uncommitted changes. */
|
|
100
188
|
dirty: boolean | undefined;
|
|
101
189
|
}
|
|
102
|
-
/**
|
|
103
|
-
*
|
|
104
|
-
*
|
|
190
|
+
/**
|
|
191
|
+
* What a `${{ ... }}` expression can see - the bindings of the fresh global it is evaluated in.
|
|
192
|
+
* Namespaced rather than a flat bag of loose names: one obvious place per fact, and room to add
|
|
193
|
+
* helpers to `pkg`/`repository` later without crowding the global.
|
|
194
|
+
*
|
|
195
|
+
* Alongside these, **the config's own top-level keys are bound bare** (`${{ publish.directory }}`,
|
|
196
|
+
* `${{ clean.include }}`) - see `interpolateConfig`. They are not listed here because they come
|
|
197
|
+
* from the config being interpolated, not from this object; a name here wins over a config key of
|
|
198
|
+
* the same name.
|
|
199
|
+
*
|
|
200
|
+
* **Trap: bare `${{ version }}` is the `version` *options block*, not the package's version
|
|
201
|
+
* string** - that is `${{ pkg.version }}`. Same word, two different things, and the plain one
|
|
202
|
+
* belongs to the config because every other config key is reachable that way.
|
|
203
|
+
*/
|
|
105
204
|
export interface ConfigScope {
|
|
106
205
|
/** The package the config was resolved for - which is what lets one declaration at the root
|
|
107
206
|
* still say something package-specific. */
|
|
108
207
|
pkg: PackageScope;
|
|
109
208
|
repository: RepositoryScope;
|
|
209
|
+
/** Paths, resolved against the package the config was resolved for. */
|
|
210
|
+
file: FileScope;
|
|
110
211
|
env: Record<string, string | undefined>;
|
|
111
212
|
/** rman's own `semver`, for the arithmetic every release config eventually wants
|
|
112
213
|
* (`semver.major(pkg.version)`). */
|
|
113
214
|
semver: typeof semver;
|
|
215
|
+
/**
|
|
216
|
+
* Node's own `node:path` - `${{ path.join(pkg.dirname, 'LICENSE') }}`, rather than gluing
|
|
217
|
+
* strings with `+ "/" +` and getting a double separator or none.
|
|
218
|
+
*
|
|
219
|
+
* The platform's flavour, not `path.posix`, so a joined path is the one the shell on *this*
|
|
220
|
+
* machine understands; `path.posix` and `path.win32` are reachable through it when a config
|
|
221
|
+
* genuinely needs one of them (a Docker image path, say, which is always posix).
|
|
222
|
+
*/
|
|
223
|
+
path: typeof path;
|
|
114
224
|
}
|
|
115
225
|
/**
|
|
116
226
|
* Evaluates every `${{ ... }}` expression in **every** string value of a resolved config, against
|
|
@@ -121,8 +231,13 @@ export interface ConfigScope {
|
|
|
121
231
|
* clean:
|
|
122
232
|
* include: ["build", "../../coverage/${{ pkg.basename }}"]
|
|
123
233
|
* publish:
|
|
234
|
+
* directory: build
|
|
124
235
|
* docker:
|
|
125
236
|
* image: "panates/${{ pkg.basename }}:${{ semver.major(pkg.version) }}"
|
|
237
|
+
* run:
|
|
238
|
+
* build:
|
|
239
|
+
* # the config's own keys are in scope, so this is not a second copy of "build"
|
|
240
|
+
* after: "cp README.md ${{ publish.directory }}/"
|
|
126
241
|
* ```
|
|
127
242
|
*
|
|
128
243
|
* Every string, with no list of "interpolated keys" to memorize - a rule with exceptions is a rule
|
|
@@ -149,4 +264,18 @@ export interface ConfigScope {
|
|
|
149
264
|
* A failing expression throws with the config path that holds it, rather than being left in place:
|
|
150
265
|
* silently passing through a mistake is how a config ends up quietly doing nothing.
|
|
151
266
|
*/
|
|
152
|
-
export declare function interpolateConfig<T>(config: T, scope: ConfigScope
|
|
267
|
+
export declare function interpolateConfig<T>(config: T, scope: ConfigScope, options?: {
|
|
268
|
+
skip?: string[];
|
|
269
|
+
}): T;
|
|
270
|
+
/**
|
|
271
|
+
* Config paths left untouched when a repository's config is first resolved, and evaluated only by
|
|
272
|
+
* the command that runs them.
|
|
273
|
+
*
|
|
274
|
+
* `version`'s own hooks are the one place `${{ pkg.targetVersion }}` makes sense, and the version
|
|
275
|
+
* being written is not known until `version` has computed its plan - long after the config was
|
|
276
|
+
* resolved. Evaluating these eagerly would throw while merely *loading* the repository, so any
|
|
277
|
+
* command at all would fail on a config that mentions it.
|
|
278
|
+
*/
|
|
279
|
+
export declare const DEFERRED_PATHS: string[];
|
|
280
|
+
/** The `file` namespace for one package's directory - see `FileScope`. */
|
|
281
|
+
export declare function createFileScope(dirname: string): FileScope;
|