rman 1.0.12 → 1.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +90 -70
- package/cli.js +226 -14
- package/commands/build.command.js +1 -0
- package/commands/changed.command.js +2 -2
- package/commands/changelog.command.js +13 -15
- package/commands/config.command.js +61 -0
- package/commands/diff.command.js +9 -4
- package/commands/exec.command.js +2 -8
- package/commands/github-release.command.js +1 -0
- package/commands/info.command.d.ts +9 -0
- package/commands/info.command.js +12 -2
- package/commands/run.command.js +5 -8
- package/commands/test.command.js +1 -0
- package/commands/version.command.js +53 -14
- package/constants.js +1 -1
- package/core/config.d.ts +265 -17
- package/core/config.js +651 -76
- package/core/custom-command.d.ts +133 -0
- package/core/custom-command.js +99 -0
- package/core/extends-config.d.ts +27 -0
- package/core/extends-config.js +89 -0
- package/core/manifest.d.ts +222 -0
- package/core/manifest.js +150 -0
- package/core/merge-config.d.ts +70 -0
- package/core/merge-config.js +193 -0
- package/core/package.d.ts +73 -7
- package/core/package.js +86 -24
- package/core/plugin.d.ts +112 -0
- package/core/plugin.js +189 -0
- package/core/repository.d.ts +91 -1
- package/core/repository.js +277 -132
- package/core/resolve-target.d.ts +12 -0
- package/core/resolve-target.js +33 -0
- package/core/run-step.d.ts +75 -0
- package/core/run-step.js +1 -0
- package/core/version-scheme.d.ts +134 -0
- package/core/version-scheme.js +148 -0
- package/core/workspace.d.ts +68 -0
- package/core/workspace.js +83 -0
- package/index.d.ts +55 -8
- package/index.js +42 -7
- package/interfaces/rman-config.interface.d.ts +222 -46
- package/package.json +16 -7
- package/services/change-hash.service.d.ts +88 -0
- package/services/change-hash.service.js +112 -0
- package/services/changelog.service.d.ts +8 -13
- package/services/changelog.service.js +12 -11
- package/services/conventional-commits.service.d.ts +73 -0
- package/services/conventional-commits.service.js +116 -0
- package/services/docker-publish.service.js +1 -1
- package/services/exec.service.js +1 -1
- package/services/github-release.service.d.ts +2 -2
- package/services/github-release.service.js +10 -5
- package/services/list.service.js +5 -2
- package/services/run.service.d.ts +112 -6
- package/services/run.service.js +265 -89
- package/services/system-info.d.ts +22 -7
- package/services/system-info.js +8 -23
- package/services/version-plan.service.d.ts +244 -0
- package/services/version-plan.service.js +414 -0
- package/services/version.service.d.ts +102 -82
- package/services/version.service.js +226 -434
- package/services.d.ts +5 -3
- package/services.js +5 -3
- package/utils/bin-path.d.ts +59 -0
- package/utils/bin-path.js +82 -0
- package/utils/child-tracker.d.ts +16 -0
- package/utils/child-tracker.js +30 -0
- package/utils/exec.d.ts +13 -2
- package/utils/exec.js +17 -17
- package/utils/git.d.ts +9 -3
- package/utils/git.js +10 -2
- package/utils/package-filter.d.ts +33 -2
- package/utils/package-filter.js +47 -7
- package/utils/printable-config.d.ts +15 -0
- package/utils/printable-config.js +42 -0
- package/utils/release-version.js +3 -3
- package/utils/run-bin.d.ts +46 -0
- package/utils/run-bin.js +63 -0
- package/utils/version-stamp.d.ts +14 -6
- package/utils/version-stamp.js +25 -13
- package/commands/ci.command.js +0 -30
- package/commands/clean.command.d.ts +0 -3
- package/commands/clean.command.js +0 -36
- package/commands/publish.command.d.ts +0 -3
- package/commands/publish.command.js +0 -225
- package/rmanrc.schema.json +0 -392
- package/services/ci.service.d.ts +0 -40
- package/services/ci.service.js +0 -204
- package/services/clean.service.d.ts +0 -42
- package/services/clean.service.js +0 -226
- package/services/publish.service.d.ts +0 -79
- package/services/publish.service.js +0 -273
- package/utils/change-hash.d.ts +0 -68
- package/utils/change-hash.js +0 -98
- package/utils/conventional-commits.d.ts +0 -52
- package/utils/conventional-commits.js +0 -90
- package/utils/npm-run-path.d.ts +0 -67
- package/utils/npm-run-path.js +0 -63
- package/utils/workspace-range.d.ts +0 -17
- package/utils/workspace-range.js +0 -28
- /package/commands/{ci.command.d.ts → config.command.d.ts} +0 -0
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What the commits said *happened*, in Conventional Commits' own terms: a `!` or `BREAKING CHANGE:`
|
|
3
|
+
* footer, a `feat:`, or anything else (a `fix:`, an unrecognized type, a non-conventional subject).
|
|
4
|
+
*
|
|
5
|
+
* Three, because three is how many sizes a commit message distinguishes - which is a fact about
|
|
6
|
+
* commit messages, not about any version format. It is deliberately **not** `patch`/`minor`/`major`:
|
|
7
|
+
* those are semver's words for how a *number* moves, and a scheme numbering
|
|
8
|
+
* `major.minor.build.revision` has four of those and no `patch` at all. `VersionScheme.bumpFor`
|
|
9
|
+
* translates one of these into a bump that scheme actually has a name for.
|
|
10
|
+
*/
|
|
11
|
+
export type ChangeKind = 'fix' | 'feature' | 'breaking';
|
|
12
|
+
/**
|
|
13
|
+
* How a package's versions are numbered: named, ordered, validated, and advanced.
|
|
14
|
+
*
|
|
15
|
+
* rman's release model was written in semver - tag patterns, changelog headings, release identity,
|
|
16
|
+
* "is this version on the registry yet", `"workspace:"` range rewriting all assume versions that
|
|
17
|
+
* can be ordered and incremented. This is the seam for an ecosystem that numbers differently (PEP
|
|
18
|
+
* 440, a date-based scheme, a build counter), and it is a *meaning* seam rather than a storage one:
|
|
19
|
+
* `ManifestProvider` answers "where is the version written", this answers "what does the next one
|
|
20
|
+
* look like".
|
|
21
|
+
*
|
|
22
|
+
* **`bumpNames` and `bumpFor` are the interesting part.** `fix:` -> patch, `feat:` -> minor,
|
|
23
|
+
* `feat!:` -> major is a sentence in *semver's* vocabulary from end to end, so a scheme that numbers
|
|
24
|
+
* differently does not need a different `inc()` - it needs its own set of bump names and its own
|
|
25
|
+
* answer to "what does a `feat:` mean for one of my version numbers". A CalVer scheme can
|
|
26
|
+
* legitimately answer all three with the same name and return today's date; that is the scheme's
|
|
27
|
+
* decision to make, which is why the seam is here and not around `semver.inc`.
|
|
28
|
+
*
|
|
29
|
+
* **An abstract class rather than an interface**, for the reason `VersionPlanService` is one:
|
|
30
|
+
* `highestVersion`, `highestBump` and `smallestBump` all *derive* from the members below them, so
|
|
31
|
+
* requiring every scheme to write them would be boilerplate and a second place for two answers to
|
|
32
|
+
* disagree - but each is a real decision a scheme may need to make differently (a repo maintaining
|
|
33
|
+
* parallel lines has its own idea of "highest"; a four-part scheme may want the ripple bump to be
|
|
34
|
+
* `build` rather than its actual smallest). Implemented here, overridable there. Subclass
|
|
35
|
+
* `SemverScheme` to keep semver's numbering and change only one of these.
|
|
36
|
+
*/
|
|
37
|
+
export declare abstract class VersionScheme {
|
|
38
|
+
/** For error messages, and for `info`. */
|
|
39
|
+
abstract readonly name: string;
|
|
40
|
+
/**
|
|
41
|
+
* Every bump this scheme accepts by name, **smallest first** - semver's
|
|
42
|
+
* `['patch', 'minor', 'major']`, a four-part `major.minor.build.revision` scheme's
|
|
43
|
+
* `['revision', 'build', 'minor', 'major']`.
|
|
44
|
+
*
|
|
45
|
+
* The scheme's, not rman's, because these are names for how a *version number* moves and only the
|
|
46
|
+
* scheme knows what parts it has. This is what `rman version <bump>` validates against and what
|
|
47
|
+
* `--help` lists, so a repository is offered the bumps its own numbering actually has.
|
|
48
|
+
*
|
|
49
|
+
* **The order is the default ranking** read by `highestBump` and `smallestBump`.
|
|
50
|
+
*/
|
|
51
|
+
abstract readonly bumpNames: readonly string[];
|
|
52
|
+
/** Which of `bumpNames` a change of this kind calls for - the translation from what a commit said
|
|
53
|
+
* to how this scheme's numbers move. A scheme that does not distinguish them may answer all
|
|
54
|
+
* three the same. */
|
|
55
|
+
abstract bumpFor(kind: ChangeKind): string;
|
|
56
|
+
/** Is this a version this scheme recognizes? Used to accept an explicitly given version. */
|
|
57
|
+
abstract isValid(version: string): boolean;
|
|
58
|
+
/** Negative, zero or positive - `Array.prototype.sort`'s contract. */
|
|
59
|
+
abstract compare(a: string, b: string): number;
|
|
60
|
+
/** The version after `current`, given one of this scheme's own `bumpNames`. `preid` asks for a
|
|
61
|
+
* prerelease line (`--preid`), which a scheme without such a concept may ignore. */
|
|
62
|
+
abstract next(current: string, bump: string, options?: {
|
|
63
|
+
preid?: string;
|
|
64
|
+
}): string;
|
|
65
|
+
/** Is this version a preview rather than a release? `github-release` reads it. */
|
|
66
|
+
abstract isPrerelease(version: string): boolean;
|
|
67
|
+
/**
|
|
68
|
+
* The highest of `versions` - a group's current version is the highest among its members, and a
|
|
69
|
+
* monorepo root's release identity the highest among the groups.
|
|
70
|
+
*
|
|
71
|
+
* Empty in, `undefined` out: a group with no versions has no highest one, and guessing `0.0.0`
|
|
72
|
+
* would put a real version line at risk. Override to answer differently - a repository
|
|
73
|
+
* maintaining parallel lines (a 1.x still receiving fixes beside a 2.x) may want the line being
|
|
74
|
+
* released rather than the numerically largest.
|
|
75
|
+
*/
|
|
76
|
+
highestVersion(versions: readonly string[]): string | undefined;
|
|
77
|
+
/**
|
|
78
|
+
* The largest of `bumps` - what a group takes when its changed members ask for different sizes.
|
|
79
|
+
* Empty in, `undefined` out.
|
|
80
|
+
*
|
|
81
|
+
* A name this scheme does not declare ranks **below** every name it does, rather than throwing -
|
|
82
|
+
* a comparison helper is the wrong place to fail a release. It is a second line of defence, not
|
|
83
|
+
* the guard: `detectBump` already drops an unrecognized `Release-As:` footer before it gets here,
|
|
84
|
+
* because ranking such a word low is not the same as ignoring it (ranked low it still *replaces*
|
|
85
|
+
* what the commit's own subject said - measured, and it turned a `feat:` into a patch).
|
|
86
|
+
*
|
|
87
|
+
* Override for bumps that are not totally ordered, or to refuse an unknown name outright.
|
|
88
|
+
*/
|
|
89
|
+
highestBump(bumps: readonly string[]): string | undefined;
|
|
90
|
+
/**
|
|
91
|
+
* What a package bumped *only* because a dependency of it moved receives - there being nothing
|
|
92
|
+
* about the package itself for a larger bump to describe. `bumpNames`' first entry by default.
|
|
93
|
+
*
|
|
94
|
+
* Throws when this scheme declares no bumps at all: such a scheme cannot express a release, and
|
|
95
|
+
* finding out here beats silently leaving every rippled package unbumped. Override to nominate a
|
|
96
|
+
* different one - a four-part scheme may reserve `revision` for something else and want `build`.
|
|
97
|
+
*/
|
|
98
|
+
smallestBump(): string;
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* Semver, exported as a class so a scheme that numbers in semver but decides one thing differently
|
|
102
|
+
* can subclass it instead of restating all of it.
|
|
103
|
+
*/
|
|
104
|
+
export declare class SemverScheme extends VersionScheme {
|
|
105
|
+
readonly name = "semver";
|
|
106
|
+
readonly bumpNames: readonly ["patch", "minor", "major"];
|
|
107
|
+
bumpFor(kind: ChangeKind): string;
|
|
108
|
+
isValid(version: string): boolean;
|
|
109
|
+
compare(a: string, b: string): number;
|
|
110
|
+
next(current: string, bump: string, options?: {
|
|
111
|
+
preid?: string;
|
|
112
|
+
}): string;
|
|
113
|
+
isPrerelease(version: string): boolean;
|
|
114
|
+
}
|
|
115
|
+
/**
|
|
116
|
+
* The default, and what every package gets unless something says otherwise - so nothing about
|
|
117
|
+
* rman's behaviour changes by this seam existing.
|
|
118
|
+
*/
|
|
119
|
+
export declare const semverScheme: VersionScheme;
|
|
120
|
+
/**
|
|
121
|
+
* Refuses a group whose packages do not agree on a scheme.
|
|
122
|
+
*
|
|
123
|
+
* A group is one version line: its members are compared against each other and bumped together, so
|
|
124
|
+
* two schemes in one group makes both of those undefined. Better to say so than to compare a PEP
|
|
125
|
+
* 440 version against a semver one and act on whatever falls out.
|
|
126
|
+
*
|
|
127
|
+
* A free function rather than a member, unlike the three above: this is a question about a *set of
|
|
128
|
+
* packages*, and asking one of the disagreeing schemes to arbitrate would be asking a party to the
|
|
129
|
+
* dispute.
|
|
130
|
+
*/
|
|
131
|
+
export declare function assertOneScheme(schemes: {
|
|
132
|
+
packageName: string;
|
|
133
|
+
scheme: VersionScheme;
|
|
134
|
+
}[], group: string): void;
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
import semver from 'semver';
|
|
2
|
+
/**
|
|
3
|
+
* How a package's versions are numbered: named, ordered, validated, and advanced.
|
|
4
|
+
*
|
|
5
|
+
* rman's release model was written in semver - tag patterns, changelog headings, release identity,
|
|
6
|
+
* "is this version on the registry yet", `"workspace:"` range rewriting all assume versions that
|
|
7
|
+
* can be ordered and incremented. This is the seam for an ecosystem that numbers differently (PEP
|
|
8
|
+
* 440, a date-based scheme, a build counter), and it is a *meaning* seam rather than a storage one:
|
|
9
|
+
* `ManifestProvider` answers "where is the version written", this answers "what does the next one
|
|
10
|
+
* look like".
|
|
11
|
+
*
|
|
12
|
+
* **`bumpNames` and `bumpFor` are the interesting part.** `fix:` -> patch, `feat:` -> minor,
|
|
13
|
+
* `feat!:` -> major is a sentence in *semver's* vocabulary from end to end, so a scheme that numbers
|
|
14
|
+
* differently does not need a different `inc()` - it needs its own set of bump names and its own
|
|
15
|
+
* answer to "what does a `feat:` mean for one of my version numbers". A CalVer scheme can
|
|
16
|
+
* legitimately answer all three with the same name and return today's date; that is the scheme's
|
|
17
|
+
* decision to make, which is why the seam is here and not around `semver.inc`.
|
|
18
|
+
*
|
|
19
|
+
* **An abstract class rather than an interface**, for the reason `VersionPlanService` is one:
|
|
20
|
+
* `highestVersion`, `highestBump` and `smallestBump` all *derive* from the members below them, so
|
|
21
|
+
* requiring every scheme to write them would be boilerplate and a second place for two answers to
|
|
22
|
+
* disagree - but each is a real decision a scheme may need to make differently (a repo maintaining
|
|
23
|
+
* parallel lines has its own idea of "highest"; a four-part scheme may want the ripple bump to be
|
|
24
|
+
* `build` rather than its actual smallest). Implemented here, overridable there. Subclass
|
|
25
|
+
* `SemverScheme` to keep semver's numbering and change only one of these.
|
|
26
|
+
*/
|
|
27
|
+
export class VersionScheme {
|
|
28
|
+
/**
|
|
29
|
+
* The highest of `versions` - a group's current version is the highest among its members, and a
|
|
30
|
+
* monorepo root's release identity the highest among the groups.
|
|
31
|
+
*
|
|
32
|
+
* Empty in, `undefined` out: a group with no versions has no highest one, and guessing `0.0.0`
|
|
33
|
+
* would put a real version line at risk. Override to answer differently - a repository
|
|
34
|
+
* maintaining parallel lines (a 1.x still receiving fixes beside a 2.x) may want the line being
|
|
35
|
+
* released rather than the numerically largest.
|
|
36
|
+
*/
|
|
37
|
+
highestVersion(versions) {
|
|
38
|
+
if (!versions.length)
|
|
39
|
+
return undefined;
|
|
40
|
+
return versions.reduce((highest, v) => (this.compare(v, highest) > 0 ? v : highest));
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* The largest of `bumps` - what a group takes when its changed members ask for different sizes.
|
|
44
|
+
* Empty in, `undefined` out.
|
|
45
|
+
*
|
|
46
|
+
* A name this scheme does not declare ranks **below** every name it does, rather than throwing -
|
|
47
|
+
* a comparison helper is the wrong place to fail a release. It is a second line of defence, not
|
|
48
|
+
* the guard: `detectBump` already drops an unrecognized `Release-As:` footer before it gets here,
|
|
49
|
+
* because ranking such a word low is not the same as ignoring it (ranked low it still *replaces*
|
|
50
|
+
* what the commit's own subject said - measured, and it turned a `feat:` into a patch).
|
|
51
|
+
*
|
|
52
|
+
* Override for bumps that are not totally ordered, or to refuse an unknown name outright.
|
|
53
|
+
*/
|
|
54
|
+
highestBump(bumps) {
|
|
55
|
+
if (!bumps.length)
|
|
56
|
+
return undefined;
|
|
57
|
+
return bumps.reduce((highest, b) => (this.bumpNames.indexOf(b) > this.bumpNames.indexOf(highest) ? b : highest));
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* What a package bumped *only* because a dependency of it moved receives - there being nothing
|
|
61
|
+
* about the package itself for a larger bump to describe. `bumpNames`' first entry by default.
|
|
62
|
+
*
|
|
63
|
+
* Throws when this scheme declares no bumps at all: such a scheme cannot express a release, and
|
|
64
|
+
* finding out here beats silently leaving every rippled package unbumped. Override to nominate a
|
|
65
|
+
* different one - a four-part scheme may reserve `revision` for something else and want `build`.
|
|
66
|
+
*/
|
|
67
|
+
smallestBump() {
|
|
68
|
+
const smallest = this.bumpNames[0];
|
|
69
|
+
if (!smallest) {
|
|
70
|
+
throw new Error(`Version scheme "${this.name}" declares no "bumpNames", so no version can be bumped.`);
|
|
71
|
+
}
|
|
72
|
+
return smallest;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Semver, exported as a class so a scheme that numbers in semver but decides one thing differently
|
|
77
|
+
* can subclass it instead of restating all of it.
|
|
78
|
+
*/
|
|
79
|
+
export class SemverScheme extends VersionScheme {
|
|
80
|
+
name = 'semver';
|
|
81
|
+
bumpNames = ['patch', 'minor', 'major'];
|
|
82
|
+
bumpFor(kind) {
|
|
83
|
+
switch (kind) {
|
|
84
|
+
case 'breaking':
|
|
85
|
+
return 'major';
|
|
86
|
+
case 'feature':
|
|
87
|
+
return 'minor';
|
|
88
|
+
case 'fix':
|
|
89
|
+
return 'patch';
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
isValid(version) {
|
|
93
|
+
return !!semver.valid(version);
|
|
94
|
+
}
|
|
95
|
+
compare(a, b) {
|
|
96
|
+
return semver.compare(a, b);
|
|
97
|
+
}
|
|
98
|
+
next(current, bump, options) {
|
|
99
|
+
const releaseType = bump;
|
|
100
|
+
const preid = options?.preid;
|
|
101
|
+
if (!preid)
|
|
102
|
+
return semver.inc(current, releaseType) ?? current;
|
|
103
|
+
/**
|
|
104
|
+
* Already on a prerelease under **this same identifier**: just advance its counter
|
|
105
|
+
* (`1.2.3-beta.0` -> `1.2.3-beta.1`), rather than jumping to a new base version every run of
|
|
106
|
+
* the same beta cycle. Anything else - a plain release, or a prerelease under a *different*
|
|
107
|
+
* identifier (`beta` -> `rc`) - starts a fresh prerelease of `bump`'s own type.
|
|
108
|
+
*
|
|
109
|
+
* Comparing the identifier, not merely "is a prerelease": treating any prerelease as the same
|
|
110
|
+
* line makes switching `beta` to `rc` silently continue the beta counter.
|
|
111
|
+
*/
|
|
112
|
+
const existing = semver.prerelease(current);
|
|
113
|
+
const sameLine = existing && String(existing[0]) === preid;
|
|
114
|
+
const preType = sameLine ? 'prerelease' : `pre${releaseType}`;
|
|
115
|
+
return semver.inc(current, preType, preid) ?? current;
|
|
116
|
+
}
|
|
117
|
+
isPrerelease(version) {
|
|
118
|
+
return !!semver.prerelease(version);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
/**
|
|
122
|
+
* The default, and what every package gets unless something says otherwise - so nothing about
|
|
123
|
+
* rman's behaviour changes by this seam existing.
|
|
124
|
+
*/
|
|
125
|
+
export const semverScheme = new SemverScheme();
|
|
126
|
+
/**
|
|
127
|
+
* Refuses a group whose packages do not agree on a scheme.
|
|
128
|
+
*
|
|
129
|
+
* A group is one version line: its members are compared against each other and bumped together, so
|
|
130
|
+
* two schemes in one group makes both of those undefined. Better to say so than to compare a PEP
|
|
131
|
+
* 440 version against a semver one and act on whatever falls out.
|
|
132
|
+
*
|
|
133
|
+
* A free function rather than a member, unlike the three above: this is a question about a *set of
|
|
134
|
+
* packages*, and asking one of the disagreeing schemes to arbitrate would be asking a party to the
|
|
135
|
+
* dispute.
|
|
136
|
+
*/
|
|
137
|
+
export function assertOneScheme(schemes, group) {
|
|
138
|
+
const first = schemes[0];
|
|
139
|
+
if (!first)
|
|
140
|
+
return;
|
|
141
|
+
const odd = schemes.find(s => s.scheme.name !== first.scheme.name);
|
|
142
|
+
if (!odd)
|
|
143
|
+
return;
|
|
144
|
+
throw new Error(`Group "${group}" mixes version schemes: "${first.packageName}" uses ${first.scheme.name} and ` +
|
|
145
|
+
`"${odd.packageName}" uses ${odd.scheme.name}.\n` +
|
|
146
|
+
` A group is one version line - its packages are compared and bumped together, which two ` +
|
|
147
|
+
`schemes make meaningless. Put them in separate groups (.rmanrc "group").`);
|
|
148
|
+
}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* How a repository is laid out, and who decides.
|
|
3
|
+
*
|
|
4
|
+
* A namespace for the same reason `Manifest` is one: it is what a plugin can *augment*
|
|
5
|
+
* (`declare module 'rman' { namespace Workspace { ... } }`), so whatever this seam grows later
|
|
6
|
+
* arrives as a member here rather than as another top-level export.
|
|
7
|
+
*/
|
|
8
|
+
export declare namespace Workspace {
|
|
9
|
+
/** What a repository looks like: where its root is, and which directories hold its packages. */
|
|
10
|
+
interface Layout {
|
|
11
|
+
/** Absolute path to the repository root. */
|
|
12
|
+
root: string;
|
|
13
|
+
/**
|
|
14
|
+
* Absolute paths to the package **directories**, outside the root. Empty for a repository that
|
|
15
|
+
* is itself the one package.
|
|
16
|
+
*
|
|
17
|
+
* Directories rather than `Package` objects, and not for want of trying: a provider runs before
|
|
18
|
+
* any package exists - `Repository.create` constructs them from these paths afterwards, through
|
|
19
|
+
* the *manifest* provider. A discovery provider returning packages would have to construct them
|
|
20
|
+
* itself, making discovery the owner of identity too.
|
|
21
|
+
*
|
|
22
|
+
* Unlike `Package.dependencies`, there is no identity question here: these are paths, and a
|
|
23
|
+
* path is unique by construction where a name is only unique if the ecosystem says so.
|
|
24
|
+
*/
|
|
25
|
+
packageDirs: string[];
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* How a repository's packages are found.
|
|
29
|
+
*
|
|
30
|
+
* **The core has no provider**, and that is the point: "packages are the `workspaces` globs in
|
|
31
|
+
* the root `package.json`, and a package is a directory with a `package.json` in it" is true of
|
|
32
|
+
* npm and of nothing else. `rman-node` contributes that one; a plugin for another ecosystem
|
|
33
|
+
* contributes its own (a Cargo workspace, a `go.work`, a `pyproject.toml`).
|
|
34
|
+
*
|
|
35
|
+
* Returns `undefined` for "this is not a repository I recognize", so the next provider gets a
|
|
36
|
+
* turn and a repository nobody recognizes falls back to being a single package.
|
|
37
|
+
*/
|
|
38
|
+
type Provider = (root: string) => Layout | undefined;
|
|
39
|
+
/**
|
|
40
|
+
* Registers a provider. Called by `loadPlugins` for each plugin's `workspace`, in `plugins`
|
|
41
|
+
* declaration order - so which provider answers is a function of the repository's own config.
|
|
42
|
+
*/
|
|
43
|
+
function addProvider(provider: Provider): void;
|
|
44
|
+
/** For tests, which would otherwise leak a provider into every later case in the process. */
|
|
45
|
+
function clearProviders(): void;
|
|
46
|
+
/** The first provider that recognizes `root`, in declaration order. */
|
|
47
|
+
function resolve(root: string): Layout | undefined;
|
|
48
|
+
/**
|
|
49
|
+
* Where the repository starts, decided **without knowing anything about any ecosystem** - it has
|
|
50
|
+
* to be, because the plugins that do know are named in the config file this walk is looking for.
|
|
51
|
+
*
|
|
52
|
+
* Walking up from `from`, stopping after a directory holding `.git`, the root is:
|
|
53
|
+
*
|
|
54
|
+
* 1. the **outermost** directory in that chain holding an `.rmanrc*` - outermost, because a
|
|
55
|
+
* *package* may have its own `.rmanrc` (that is a supported thing), and from inside such a
|
|
56
|
+
* package the nearest one is the package's, not the repository's;
|
|
57
|
+
* 2. otherwise the `.git` directory itself, the ordinary meaning of "repository root";
|
|
58
|
+
* 3. otherwise `from`, which is all that is left to go on.
|
|
59
|
+
*
|
|
60
|
+
* **What "outermost" costs**, since the two cases genuinely conflict and only one can win: a
|
|
61
|
+
* self-contained project nested inside a larger git repository *and sharing its `.git`* resolves
|
|
62
|
+
* to the outer root, not to itself (measured). That is the same repository by any definition git
|
|
63
|
+
* recognizes, so it is the defensible answer - and a nested project with a `.git` of its own is
|
|
64
|
+
* found correctly, because the walk stops there before the outer `.rmanrc` is ever seen (also
|
|
65
|
+
* measured). A per-package `.rmanrc` is the common case and it is the one served.
|
|
66
|
+
*/
|
|
67
|
+
function findRoot(from: string, deep?: number): string;
|
|
68
|
+
}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
/**
|
|
4
|
+
* How a repository is laid out, and who decides.
|
|
5
|
+
*
|
|
6
|
+
* A namespace for the same reason `Manifest` is one: it is what a plugin can *augment*
|
|
7
|
+
* (`declare module 'rman' { namespace Workspace { ... } }`), so whatever this seam grows later
|
|
8
|
+
* arrives as a member here rather than as another top-level export.
|
|
9
|
+
*/
|
|
10
|
+
export var Workspace;
|
|
11
|
+
(function (Workspace) {
|
|
12
|
+
/**
|
|
13
|
+
* Registers a provider. Called by `loadPlugins` for each plugin's `workspace`, in `plugins`
|
|
14
|
+
* declaration order - so which provider answers is a function of the repository's own config.
|
|
15
|
+
*/
|
|
16
|
+
function addProvider(provider) {
|
|
17
|
+
if (providers.includes(provider))
|
|
18
|
+
return;
|
|
19
|
+
providers.push(provider);
|
|
20
|
+
}
|
|
21
|
+
Workspace.addProvider = addProvider;
|
|
22
|
+
/** For tests, which would otherwise leak a provider into every later case in the process. */
|
|
23
|
+
function clearProviders() {
|
|
24
|
+
providers.length = 0;
|
|
25
|
+
}
|
|
26
|
+
Workspace.clearProviders = clearProviders;
|
|
27
|
+
/** The first provider that recognizes `root`, in declaration order. */
|
|
28
|
+
function resolve(root) {
|
|
29
|
+
for (const provider of providers) {
|
|
30
|
+
const layout = provider(root);
|
|
31
|
+
if (layout)
|
|
32
|
+
return layout;
|
|
33
|
+
}
|
|
34
|
+
return undefined;
|
|
35
|
+
}
|
|
36
|
+
Workspace.resolve = resolve;
|
|
37
|
+
/**
|
|
38
|
+
* Where the repository starts, decided **without knowing anything about any ecosystem** - it has
|
|
39
|
+
* to be, because the plugins that do know are named in the config file this walk is looking for.
|
|
40
|
+
*
|
|
41
|
+
* Walking up from `from`, stopping after a directory holding `.git`, the root is:
|
|
42
|
+
*
|
|
43
|
+
* 1. the **outermost** directory in that chain holding an `.rmanrc*` - outermost, because a
|
|
44
|
+
* *package* may have its own `.rmanrc` (that is a supported thing), and from inside such a
|
|
45
|
+
* package the nearest one is the package's, not the repository's;
|
|
46
|
+
* 2. otherwise the `.git` directory itself, the ordinary meaning of "repository root";
|
|
47
|
+
* 3. otherwise `from`, which is all that is left to go on.
|
|
48
|
+
*
|
|
49
|
+
* **What "outermost" costs**, since the two cases genuinely conflict and only one can win: a
|
|
50
|
+
* self-contained project nested inside a larger git repository *and sharing its `.git`* resolves
|
|
51
|
+
* to the outer root, not to itself (measured). That is the same repository by any definition git
|
|
52
|
+
* recognizes, so it is the defensible answer - and a nested project with a `.git` of its own is
|
|
53
|
+
* found correctly, because the walk stops there before the outer `.rmanrc` is ever seen (also
|
|
54
|
+
* measured). A per-package `.rmanrc` is the common case and it is the one served.
|
|
55
|
+
*/
|
|
56
|
+
function findRoot(from, deep = 10) {
|
|
57
|
+
const chain = [];
|
|
58
|
+
let dir = path.resolve(from);
|
|
59
|
+
let remaining = deep;
|
|
60
|
+
while (remaining-- >= 0 && fs.existsSync(dir)) {
|
|
61
|
+
chain.push(dir);
|
|
62
|
+
if (fs.existsSync(path.join(dir, '.git')))
|
|
63
|
+
break;
|
|
64
|
+
const parent = path.resolve(dir, '..');
|
|
65
|
+
if (parent === dir)
|
|
66
|
+
break;
|
|
67
|
+
dir = parent;
|
|
68
|
+
}
|
|
69
|
+
const withConfig = chain.filter(hasRmanConfig);
|
|
70
|
+
if (withConfig.length)
|
|
71
|
+
return withConfig[withConfig.length - 1];
|
|
72
|
+
const gitRoot = chain.find(d => fs.existsSync(path.join(d, '.git')));
|
|
73
|
+
return gitRoot ?? path.resolve(from);
|
|
74
|
+
}
|
|
75
|
+
Workspace.findRoot = findRoot;
|
|
76
|
+
/** Every file form a `.rmanrc` comes in - `package.json#rman` is deliberately *not* one of them
|
|
77
|
+
* here: it would make the root question npm-shaped again. */
|
|
78
|
+
const CONFIG_FILES = ['.rmanrc', '.rmanrc.yml', '.rmanrc.cjs', '.rmanrc.mjs', '.rmanrc.js'];
|
|
79
|
+
function hasRmanConfig(dir) {
|
|
80
|
+
return CONFIG_FILES.some(name => fs.existsSync(path.join(dir, name)));
|
|
81
|
+
}
|
|
82
|
+
const providers = [];
|
|
83
|
+
})(Workspace || (Workspace = {}));
|
package/index.d.ts
CHANGED
|
@@ -1,19 +1,66 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Programmatic API - the same logic the CLI commands run, importable directly without going
|
|
3
3
|
* through yargs/argv. Each domain's logic lives in a `<Name>` namespace under `./services/*.ts`
|
|
4
|
-
* (e.g. `ChangelogService`, `
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
4
|
+
* (e.g. `ChangelogService`, `ListService`, `RunService`, `VersionService`),
|
|
5
|
+
* re-exported here via `./services.js`. Purpose-specific functions, not one generic `run`/`get`
|
|
6
|
+
* per domain - CLI-only concerns (argv parsing, `--help` text, and all console/file presentation)
|
|
7
|
+
* stay in `cli.ts` and the individual `commands/*.command.ts` modules, which are not exported here.
|
|
8
|
+
*
|
|
9
|
+
* **This is also the plugin contract.** A plugin (`rman-node`, say) is an ordinary package that
|
|
10
|
+
* imports from here, so everything a command needs in order to live outside rman has to be
|
|
11
|
+
* exported - the progress panel, the package filter, the branch guard, the git helper. What is
|
|
12
|
+
* *not* exported is deliberately private: config resolution internals, the expression evaluator,
|
|
13
|
+
* the command registry.
|
|
10
14
|
*/
|
|
11
15
|
export { defineConfig } from './core/config.js';
|
|
16
|
+
export type { CommandContext, CustomCommand } from './core/custom-command.js';
|
|
17
|
+
export { defineCommand } from './core/custom-command.js';
|
|
18
|
+
/** The manifest seam: where a package's name and version are written, and how it is numbered.
|
|
19
|
+
* The core has no provider - `package.json` is npm's answer, and lives in `rman-node`. */
|
|
20
|
+
export type { ManifestProvider } from './core/manifest.js';
|
|
21
|
+
/** Both the shape and the registry: `const m: Manifest` and `Manifest.read(dir)` - merged onto one
|
|
22
|
+
* name so a plugin can augment it the way it augments `SystemInfo`. */
|
|
23
|
+
export { Manifest } from './core/manifest.js';
|
|
12
24
|
export { Package } from './core/package.js';
|
|
25
|
+
export type { RmanPlugin } from './core/plugin.js';
|
|
26
|
+
export { definePlugin } from './core/plugin.js';
|
|
13
27
|
export { Repository } from './core/repository.js';
|
|
28
|
+
export type { RunConditionFn, RunStepContext, RunStepFn, RunStepValue } from './core/run-step.js';
|
|
29
|
+
export type { ChangeKind } from './core/version-scheme.js';
|
|
30
|
+
/** The numbering seam. `VersionScheme` is abstract - `highestVersion`/`highestBump`/`smallestBump`
|
|
31
|
+
* are implemented from the members around them, so a scheme states only what it must and still
|
|
32
|
+
* overrides any of the three. `SemverScheme` is exported to subclass rather than restate. */
|
|
33
|
+
export { assertOneScheme, SemverScheme, semverScheme, VersionScheme } from './core/version-scheme.js';
|
|
34
|
+
/** The workspace seam: how a repository's packages are found. A plugin contributes a provider
|
|
35
|
+
* (see `RmanPlugin.workspace`); the core has none, so `workspaces` is npm's idea and lives in
|
|
36
|
+
* `rman-node`. */
|
|
37
|
+
/** `Workspace.Layout`, `Workspace.Provider`, `Workspace.addProvider`, `Workspace.resolve`,
|
|
38
|
+
* `Workspace.findRoot` - one namespace, so a plugin can augment it. */
|
|
39
|
+
export { Workspace } from './core/workspace.js';
|
|
14
40
|
export * from './interfaces/rman-config.interface.js';
|
|
15
41
|
export * from './services.js';
|
|
16
|
-
|
|
17
|
-
|
|
42
|
+
/** Branch guarding: `allowBranch`/`ignoreBranch`, so a plugin's release command refuses to run on
|
|
43
|
+
* the wrong branch exactly as `publish` and `version` do. */
|
|
44
|
+
export { applyBranchGuardOptions, assertAllowedBranch, type BranchGuardOptions, readBranchGuardOptions, } from './utils/branch-guard.js';
|
|
45
|
+
/** Where a repository's locally installed binaries live - the core spells the PATH variable, a
|
|
46
|
+
* plugin says which directories go on it (see `RmanPlugin.binPaths`). */
|
|
47
|
+
export { BinPath } from './utils/bin-path.js';
|
|
48
|
+
/** A shell command, with the repository's local binaries on PATH - for a command string an author
|
|
49
|
+
* wrote. `runBin` is the one to reach for when the arguments are assembled in code. */
|
|
50
|
+
export type { ExecOptions, ExecResult } from './utils/exec.js';
|
|
51
|
+
export { exec } from './utils/exec.js';
|
|
52
|
+
export { GitHelper } from './utils/git.js';
|
|
18
53
|
export type { LogLevel } from './utils/logger.js';
|
|
19
54
|
export { LOG_LEVELS, Logger, resolveRootLogLevel } from './utils/logger.js';
|
|
55
|
+
/** `--scope`/`--deps`/`--dependents`/`--private`, so a plugin's command filters packages the same
|
|
56
|
+
* way every built-in does rather than inventing its own flags. */
|
|
57
|
+
export { applyPackageFilterOptions, applyRootOption, filterPackages, type PackageFilterOptions, readPackageFilterOptions, readRootOption, } from './utils/package-filter.js';
|
|
58
|
+
/** The live panel `run`/`build`/`clean` print - a plugin's per-package command looks like the rest
|
|
59
|
+
* of rman instead of like a script someone bolted on. */
|
|
60
|
+
export { formatDuration, type ProgressItem, ProgressPanel, type ProgressStatus, type ProgressSummary, } from './utils/progress-panel.js';
|
|
61
|
+
/** Version stamping helpers a `ManifestProvider.stampVersion` can delegate to - the quoted-constant
|
|
62
|
+
* pattern most languages share, and the OCI Dockerfile label (which `version` stamps itself, since
|
|
63
|
+
* the label's value is by specification the package's version). */
|
|
64
|
+
export type { RunBinOptions, RunBinResult } from './utils/run-bin.js';
|
|
65
|
+
export { runBin } from './utils/run-bin.js';
|
|
66
|
+
export { OCI_VERSION_LABEL, stampVersionConstant, stampVersionLabel } from './utils/version-stamp.js';
|
package/index.js
CHANGED
|
@@ -1,17 +1,52 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Programmatic API - the same logic the CLI commands run, importable directly without going
|
|
3
3
|
* through yargs/argv. Each domain's logic lives in a `<Name>` namespace under `./services/*.ts`
|
|
4
|
-
* (e.g. `ChangelogService`, `
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
4
|
+
* (e.g. `ChangelogService`, `ListService`, `RunService`, `VersionService`),
|
|
5
|
+
* re-exported here via `./services.js`. Purpose-specific functions, not one generic `run`/`get`
|
|
6
|
+
* per domain - CLI-only concerns (argv parsing, `--help` text, and all console/file presentation)
|
|
7
|
+
* stay in `cli.ts` and the individual `commands/*.command.ts` modules, which are not exported here.
|
|
8
|
+
*
|
|
9
|
+
* **This is also the plugin contract.** A plugin (`rman-node`, say) is an ordinary package that
|
|
10
|
+
* imports from here, so everything a command needs in order to live outside rman has to be
|
|
11
|
+
* exported - the progress panel, the package filter, the branch guard, the git helper. What is
|
|
12
|
+
* *not* exported is deliberately private: config resolution internals, the expression evaluator,
|
|
13
|
+
* the command registry.
|
|
10
14
|
*/
|
|
11
15
|
export { defineConfig } from './core/config.js';
|
|
16
|
+
export { defineCommand } from './core/custom-command.js';
|
|
17
|
+
/** Both the shape and the registry: `const m: Manifest` and `Manifest.read(dir)` - merged onto one
|
|
18
|
+
* name so a plugin can augment it the way it augments `SystemInfo`. */
|
|
19
|
+
export { Manifest } from './core/manifest.js';
|
|
12
20
|
export { Package } from './core/package.js';
|
|
21
|
+
export { definePlugin } from './core/plugin.js';
|
|
13
22
|
export { Repository } from './core/repository.js';
|
|
23
|
+
/** The numbering seam. `VersionScheme` is abstract - `highestVersion`/`highestBump`/`smallestBump`
|
|
24
|
+
* are implemented from the members around them, so a scheme states only what it must and still
|
|
25
|
+
* overrides any of the three. `SemverScheme` is exported to subclass rather than restate. */
|
|
26
|
+
export { assertOneScheme, SemverScheme, semverScheme, VersionScheme } from './core/version-scheme.js';
|
|
27
|
+
/** The workspace seam: how a repository's packages are found. A plugin contributes a provider
|
|
28
|
+
* (see `RmanPlugin.workspace`); the core has none, so `workspaces` is npm's idea and lives in
|
|
29
|
+
* `rman-node`. */
|
|
30
|
+
/** `Workspace.Layout`, `Workspace.Provider`, `Workspace.addProvider`, `Workspace.resolve`,
|
|
31
|
+
* `Workspace.findRoot` - one namespace, so a plugin can augment it. */
|
|
32
|
+
export { Workspace } from './core/workspace.js';
|
|
14
33
|
export * from './interfaces/rman-config.interface.js';
|
|
15
34
|
export * from './services.js';
|
|
16
|
-
|
|
35
|
+
// --- what a command needs to behave like a built-in one -----------------------------------------
|
|
36
|
+
/** Branch guarding: `allowBranch`/`ignoreBranch`, so a plugin's release command refuses to run on
|
|
37
|
+
* the wrong branch exactly as `publish` and `version` do. */
|
|
38
|
+
export { applyBranchGuardOptions, assertAllowedBranch, readBranchGuardOptions, } from './utils/branch-guard.js';
|
|
39
|
+
/** Where a repository's locally installed binaries live - the core spells the PATH variable, a
|
|
40
|
+
* plugin says which directories go on it (see `RmanPlugin.binPaths`). */
|
|
41
|
+
export { BinPath } from './utils/bin-path.js';
|
|
42
|
+
export { exec } from './utils/exec.js';
|
|
43
|
+
export { GitHelper } from './utils/git.js';
|
|
17
44
|
export { LOG_LEVELS, Logger, resolveRootLogLevel } from './utils/logger.js';
|
|
45
|
+
/** `--scope`/`--deps`/`--dependents`/`--private`, so a plugin's command filters packages the same
|
|
46
|
+
* way every built-in does rather than inventing its own flags. */
|
|
47
|
+
export { applyPackageFilterOptions, applyRootOption, filterPackages, readPackageFilterOptions, readRootOption, } from './utils/package-filter.js';
|
|
48
|
+
/** The live panel `run`/`build`/`clean` print - a plugin's per-package command looks like the rest
|
|
49
|
+
* of rman instead of like a script someone bolted on. */
|
|
50
|
+
export { formatDuration, ProgressPanel, } from './utils/progress-panel.js';
|
|
51
|
+
export { runBin } from './utils/run-bin.js';
|
|
52
|
+
export { OCI_VERSION_LABEL, stampVersionConstant, stampVersionLabel } from './utils/version-stamp.js';
|