rman 2.0.1 → 2.1.0
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/commands/changelog.command.d.ts +34 -0
- package/commands/changelog.command.js +24 -0
- package/constants.js +1 -1
- package/core/classes/repository.js +5 -1
- package/core/classes/workspace.d.ts +9 -2
- package/core/classes/workspace.js +61 -11
- package/package.json +1 -1
- package/services/changelog.service.d.ts +5 -2
- package/services/changelog.service.js +93 -15
- package/services/conventional-commits.service.d.ts +1 -1
- package/services/conventional-commits.service.js +1 -1
- package/utils/package-filter.d.ts +7 -2
- package/utils/package-filter.js +15 -3
|
@@ -47,6 +47,23 @@ declare const config: {
|
|
|
47
47
|
describe: string;
|
|
48
48
|
type: "string";
|
|
49
49
|
};
|
|
50
|
+
/**
|
|
51
|
+
* **Default `true`, so the flag that does something is `--no-commit-hash`.** GitHub autolinks a
|
|
52
|
+
* bare abbreviated sha wherever it renders Markdown inside a repository, so the plain
|
|
53
|
+
* `(a1b2c3d)` this writes is a link on the page and stays readable in a terminal - which a
|
|
54
|
+
* hand-built `[a1b2c3d](https://.../commit/a1b2c3d)` would not be, and which rman could not build
|
|
55
|
+
* anyway without knowing the forge.
|
|
56
|
+
*
|
|
57
|
+
* `'both'` rather than config-only: it is a lasting preference, and it is also the one thing a
|
|
58
|
+
* caller may want to turn off for a single run - `github-release` bodies are rendered in the same
|
|
59
|
+
* repository, but notes pasted somewhere else lose the autolinking and keep the noise.
|
|
60
|
+
*/
|
|
61
|
+
commitHash: {
|
|
62
|
+
target: "both";
|
|
63
|
+
cliName: string;
|
|
64
|
+
describe: string;
|
|
65
|
+
type: "boolean";
|
|
66
|
+
};
|
|
50
67
|
tagPattern: {
|
|
51
68
|
target: "config";
|
|
52
69
|
describe: string;
|
|
@@ -208,6 +225,23 @@ declare const changelogCommand: (app: import("../index.js").RmanApplication) =>
|
|
|
208
225
|
describe: string;
|
|
209
226
|
type: "string";
|
|
210
227
|
};
|
|
228
|
+
/**
|
|
229
|
+
* **Default `true`, so the flag that does something is `--no-commit-hash`.** GitHub autolinks a
|
|
230
|
+
* bare abbreviated sha wherever it renders Markdown inside a repository, so the plain
|
|
231
|
+
* `(a1b2c3d)` this writes is a link on the page and stays readable in a terminal - which a
|
|
232
|
+
* hand-built `[a1b2c3d](https://.../commit/a1b2c3d)` would not be, and which rman could not build
|
|
233
|
+
* anyway without knowing the forge.
|
|
234
|
+
*
|
|
235
|
+
* `'both'` rather than config-only: it is a lasting preference, and it is also the one thing a
|
|
236
|
+
* caller may want to turn off for a single run - `github-release` bodies are rendered in the same
|
|
237
|
+
* repository, but notes pasted somewhere else lose the autolinking and keep the noise.
|
|
238
|
+
*/
|
|
239
|
+
commitHash: {
|
|
240
|
+
target: "both";
|
|
241
|
+
cliName: string;
|
|
242
|
+
describe: string;
|
|
243
|
+
type: "boolean";
|
|
244
|
+
};
|
|
211
245
|
tagPattern: {
|
|
212
246
|
target: "config";
|
|
213
247
|
describe: string;
|
|
@@ -62,6 +62,29 @@ const config = {
|
|
|
62
62
|
"{{package}}/{{version}} placeholders are that file's content, not config expressions.",
|
|
63
63
|
type: 'string',
|
|
64
64
|
},
|
|
65
|
+
/**
|
|
66
|
+
* **Default `true`, so the flag that does something is `--no-commit-hash`.** GitHub autolinks a
|
|
67
|
+
* bare abbreviated sha wherever it renders Markdown inside a repository, so the plain
|
|
68
|
+
* `(a1b2c3d)` this writes is a link on the page and stays readable in a terminal - which a
|
|
69
|
+
* hand-built `[a1b2c3d](https://.../commit/a1b2c3d)` would not be, and which rman could not build
|
|
70
|
+
* anyway without knowing the forge.
|
|
71
|
+
*
|
|
72
|
+
* `'both'` rather than config-only: it is a lasting preference, and it is also the one thing a
|
|
73
|
+
* caller may want to turn off for a single run - `github-release` bodies are rendered in the same
|
|
74
|
+
* repository, but notes pasted somewhere else lose the autolinking and keep the noise.
|
|
75
|
+
*/
|
|
76
|
+
/* **No `default: true` here, and that is the trap `unreleased` already avoids** - the default
|
|
77
|
+
* lives in `withCommitHash`. Declared as a yargs default, an omitted flag arrives as `true`
|
|
78
|
+
* rather than `undefined`, so the flag would win over `.rmanrc changelog.commitHash` on every run
|
|
79
|
+
* and the config key could never turn it off. The precedence rule everywhere is *the flag wins
|
|
80
|
+
* when it was given*, which needs an unset flag to stay unset. */
|
|
81
|
+
commitHash: {
|
|
82
|
+
target: 'both',
|
|
83
|
+
cliName: 'commit-hash',
|
|
84
|
+
describe: "Append each commit's short sha to its line (default true) - pass --no-commit-hash for " +
|
|
85
|
+
'notes that are read outside the repository, where GitHub does not autolink it',
|
|
86
|
+
type: 'boolean',
|
|
87
|
+
},
|
|
65
88
|
tagPattern: {
|
|
66
89
|
target: 'config',
|
|
67
90
|
describe: "The release tag naming scheme each package's changelog boundary is detected from - " +
|
|
@@ -141,6 +164,7 @@ const changelogCommand = registerCommand(app => {
|
|
|
141
164
|
version: args.releaseVersion,
|
|
142
165
|
startingAt: args.startingAt,
|
|
143
166
|
unreleased: args.unreleased,
|
|
167
|
+
commitHash: args.commitHash,
|
|
144
168
|
};
|
|
145
169
|
const changelog = app.getService('changelog');
|
|
146
170
|
const entries = write ? await changelog.generateToFile(options) : await changelog.getEntries(options);
|
package/constants.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export const version = '2.0
|
|
1
|
+
export const version = '2.1.0';
|
|
@@ -491,7 +491,11 @@ export class Repository extends Package {
|
|
|
491
491
|
app.publishTargets.add(target);
|
|
492
492
|
}
|
|
493
493
|
const packages = workspace.packages;
|
|
494
|
-
|
|
494
|
+
/** `workspace.monorepo`, not `packages.length > 0` again: the workspace already had to answer
|
|
495
|
+
* this during the cascade - it is what decides whether a `"[glob]"` speaks for the root - and
|
|
496
|
+
* two copies of the derivation could disagree silently, resolving a config under one answer
|
|
497
|
+
* and building the package list under the other. */
|
|
498
|
+
const repo = new Repository(app, rootDir, workspace.monorepo, packages, from, workspace.rootPackage.platform);
|
|
495
499
|
repo._adopt(workspace);
|
|
496
500
|
app.attachRepository(repo);
|
|
497
501
|
return Repository._init(repo);
|
|
@@ -45,6 +45,13 @@ export declare class Workspace {
|
|
|
45
45
|
static create(rootDir: string, options: Workspace.Options): Promise<Workspace>;
|
|
46
46
|
/** Use {@link Workspace.create}. */
|
|
47
47
|
protected constructor(rootDir: string, options: Workspace.Options);
|
|
48
|
+
/**
|
|
49
|
+
* Whether the walk found packages *below* the root - i.e. whether this is a monorepo.
|
|
50
|
+
*
|
|
51
|
+
* `false` means the root is the repository's one and only package, which is what decides whether
|
|
52
|
+
* a `"[glob]"` selector speaks for it (see {@link Workspace._speaksFor}).
|
|
53
|
+
*/
|
|
54
|
+
get monorepo(): boolean;
|
|
48
55
|
/** The package at a directory, root included, or `undefined` for one that holds none. */
|
|
49
56
|
packageAt(dirname: string): Package | undefined;
|
|
50
57
|
/** The whole resolution, in the one order the steps admit. */
|
|
@@ -109,8 +116,8 @@ export declare class Workspace {
|
|
|
109
116
|
/**
|
|
110
117
|
* Whether one selector block speaks for this package.
|
|
111
118
|
*
|
|
112
|
-
* `"[/]"` asks only whether this *is* the root.
|
|
113
|
-
*
|
|
119
|
+
* `"[/]"` asks only whether this *is* the root. A `"[glob]"` matches packages **below** the root -
|
|
120
|
+
* and, in a repository that has none, the root itself, because there it *is* the one package.
|
|
114
121
|
*/
|
|
115
122
|
protected _speaksFor(scope: ConfigReader.ParsedSelector['scope'], test: (value: string) => boolean, audience: Workspace.Audience): boolean;
|
|
116
123
|
/**
|
|
@@ -74,6 +74,24 @@ export class Workspace {
|
|
|
74
74
|
this.plugins = [...(options.plugins ?? [])];
|
|
75
75
|
this.platforms = [...(options.platforms ?? [])];
|
|
76
76
|
}
|
|
77
|
+
/**
|
|
78
|
+
* Whether the walk found packages *below* the root - i.e. whether this is a monorepo.
|
|
79
|
+
*
|
|
80
|
+
* `false` means the root is the repository's one and only package, which is what decides whether
|
|
81
|
+
* a `"[glob]"` selector speaks for it (see {@link Workspace._speaksFor}).
|
|
82
|
+
*/
|
|
83
|
+
/* **One derivation, read from two places.** `Repository.create` computed `packages.length > 0`
|
|
84
|
+
* itself and passed it in as the `monorepo` constructor argument; `_speaksFor` now needs the same
|
|
85
|
+
* fact *during* the cascade, which runs before any `Repository` exists. Two copies of a one-line
|
|
86
|
+
* derivation is two places to disagree, and the disagreement would be silent - a config resolved
|
|
87
|
+
* under one answer and a command's package list built under the other. `Repository.create` reads
|
|
88
|
+
* this getter now.
|
|
89
|
+
*
|
|
90
|
+
* Safe to ask at cascade time because `_discover` has already run: `_init` walks the tree first
|
|
91
|
+
* and only then resolves selectors and configs, so `packages` is final by the time either asks. */
|
|
92
|
+
get monorepo() {
|
|
93
|
+
return this.packages.length > 0;
|
|
94
|
+
}
|
|
77
95
|
/** The package at a directory, root included, or `undefined` for one that holds none. */
|
|
78
96
|
packageAt(dirname) {
|
|
79
97
|
const resolved = path.resolve(dirname);
|
|
@@ -369,17 +387,47 @@ export class Workspace {
|
|
|
369
387
|
/**
|
|
370
388
|
* Whether one selector block speaks for this package.
|
|
371
389
|
*
|
|
372
|
-
* `"[/]"` asks only whether this *is* the root.
|
|
373
|
-
*
|
|
390
|
+
* `"[/]"` asks only whether this *is* the root. A `"[glob]"` matches packages **below** the root -
|
|
391
|
+
* and, in a repository that has none, the root itself, because there it *is* the one package.
|
|
374
392
|
*/
|
|
375
|
-
/* **A glob
|
|
376
|
-
*
|
|
377
|
-
*
|
|
378
|
-
*
|
|
379
|
-
*
|
|
380
|
-
* `"[
|
|
381
|
-
*
|
|
382
|
-
*
|
|
393
|
+
/* **A glob is held off a MONOREPO root, and only a monorepo root** - which is narrower than the
|
|
394
|
+
* rule this replaces and is the whole of the change. Both justifications for the old blanket
|
|
395
|
+
* refusal are statements about a monorepo root, and neither survives being read in a
|
|
396
|
+
* single-package repository:
|
|
397
|
+
*
|
|
398
|
+
* - `"[my-*]"` must not quietly pick up a repository whose root package is called `my-repo`.
|
|
399
|
+
* That is an *accidental* name collision between a container and the things it contains.
|
|
400
|
+
* With nothing contained, the root is not a container that happens to share a name shape -
|
|
401
|
+
* it is the package, and `"[postgrejs]"` naming it is the author saying what they mean.
|
|
402
|
+
* - `"[*]"` must not hand a package-shaped setting to a root with no build directory to apply
|
|
403
|
+
* it to. A single-package root *has* the build directory: it is where the package is built.
|
|
404
|
+
*
|
|
405
|
+
* What the old rule cost, measured on `panates/postgrejs` before this: `@panates/rman-preset`
|
|
406
|
+
* declares its whole package block under `"[platform:node]" > "[*]"` - `run.build`,
|
|
407
|
+
* `publish.npm.directory`, `version.stamp`, the build directory's `clean.include` - and *none* of
|
|
408
|
+
* it reached that repository. `rman build` answered `No package defines a "build" script.` and
|
|
409
|
+
* `rman config` showed the four keys simply absent, with nothing reporting that a block had
|
|
410
|
+
* matched nobody. The workaround would have been for every single-package repository to restate
|
|
411
|
+
* the preset's block under `"[/]"`, which is the duplication a shared preset exists to remove -
|
|
412
|
+
* and it is not even writable in YAML, since two of those values are JavaScript functions the
|
|
413
|
+
* preset keeps to itself.
|
|
414
|
+
*
|
|
415
|
+
* **`"[/]"` still means the root and is still the right spelling for a repo-wide setting**, so in
|
|
416
|
+
* a single-package repository both selectors reach the one package and are layered in declaration
|
|
417
|
+
* order like any two siblings. That overlap is not a conflict to resolve: `"[/]"` says *the
|
|
418
|
+
* repository* and `"[*]"` says *its packages*, and in a repository of one those are the same
|
|
419
|
+
* object - which is the fact this rule finally states.
|
|
420
|
+
*
|
|
421
|
+
* Neither trap exists for a platform block - `platform:node` is not a name and cannot match by
|
|
422
|
+
* accident, and it is not a catch-all - so **a platform link answers about the root like any other
|
|
423
|
+
* package**, and what keeps a plain `"[platform:node]"` off the root is `_matchingSelectors`'
|
|
424
|
+
* chain rule rather than a refusal here. Asking `isRoot` first was what made
|
|
425
|
+
* `"[platform:node]" > "[/]"` unanswerable: the pair says *the root, when it is a node package*,
|
|
426
|
+
* and the short-circuit never let the platform question be put.
|
|
427
|
+
*
|
|
428
|
+
* **The CLI moves with this, and has to**: `--scope '*'` and `"[*]"` are documented as the same
|
|
429
|
+
* set, so `package-filter.ts`'s `selector()` carries the identical clause. Changing one alone
|
|
430
|
+
* would make a repository's config and its `--scope` disagree about what a package is.
|
|
383
431
|
*
|
|
384
432
|
* A package no technology claimed carries `basePlatform`, whose name is `''`, so it matches no
|
|
385
433
|
* platform block at all - `parseSelector`'s test refuses an empty name rather than letting an
|
|
@@ -389,7 +437,9 @@ export class Workspace {
|
|
|
389
437
|
return audience.isRoot;
|
|
390
438
|
if (scope === 'platform')
|
|
391
439
|
return test(audience.platform);
|
|
392
|
-
|
|
440
|
+
if (audience.isRoot && this.monorepo)
|
|
441
|
+
return false;
|
|
442
|
+
return audience.selector !== undefined && test(audience.selector);
|
|
393
443
|
}
|
|
394
444
|
/**
|
|
395
445
|
* The platform that claims a directory - the one its config names, or the first loaded platform
|
package/package.json
CHANGED
|
@@ -24,8 +24,8 @@ export declare class ChangelogService extends Service {
|
|
|
24
24
|
* outside every package) - unless it's broad enough to count as a repo-wide change (see
|
|
25
25
|
* `BROAD_COMMIT_THRESHOLD`/`ownersOf`), in which case it goes to root alone instead of being
|
|
26
26
|
* repeated verbatim across most of the repository. Subject lines are grouped ✨ Features/🐛 Bug
|
|
27
|
-
* Fixes
|
|
28
|
-
* just lands in
|
|
27
|
+
* Fixes/💬 General Changes on a best-effort Conventional Commits read; anything that doesn't parse
|
|
28
|
+
* just lands in General Changes as-is, so a repo that doesn't follow that convention still gets a
|
|
29
29
|
* usable list. `.rmanrc changelog.ignoreTypes` (cascaded, e.g. `[chore, dev]`) drops commits of
|
|
30
30
|
* those types entirely instead - see `ignoreTypesConfig`. A release marker - a bare version-bump
|
|
31
31
|
* commit (`"6.0.1"`), or any of the messages `version` itself writes - is always dropped
|
|
@@ -104,6 +104,9 @@ export declare namespace ChangelogService {
|
|
|
104
104
|
* omitted, and `true` when neither says. A caller passing `version` always gets it, since that
|
|
105
105
|
* names the release being cut; see `resolveUnreleased`. */
|
|
106
106
|
unreleased?: boolean;
|
|
107
|
+
/** Whether each line ends with its commit's short sha - `.rmanrc changelog.commitHash` when
|
|
108
|
+
* omitted, and `true` when neither says. See `withCommitHash`. */
|
|
109
|
+
commitHash?: boolean;
|
|
107
110
|
}
|
|
108
111
|
/** One package's (root included) generated changelog entry - what `getEntries`/`generate`
|
|
109
112
|
* return. */
|
|
@@ -48,8 +48,8 @@ export class ChangelogService extends Service {
|
|
|
48
48
|
* outside every package) - unless it's broad enough to count as a repo-wide change (see
|
|
49
49
|
* `BROAD_COMMIT_THRESHOLD`/`ownersOf`), in which case it goes to root alone instead of being
|
|
50
50
|
* repeated verbatim across most of the repository. Subject lines are grouped ✨ Features/🐛 Bug
|
|
51
|
-
* Fixes
|
|
52
|
-
* just lands in
|
|
51
|
+
* Fixes/💬 General Changes on a best-effort Conventional Commits read; anything that doesn't parse
|
|
52
|
+
* just lands in General Changes as-is, so a repo that doesn't follow that convention still gets a
|
|
53
53
|
* usable list. `.rmanrc changelog.ignoreTypes` (cascaded, e.g. `[chore, dev]`) drops commits of
|
|
54
54
|
* those types entirely instead - see `ignoreTypesConfig`. A release marker - a bare version-bump
|
|
55
55
|
* commit (`"6.0.1"`), or any of the messages `version` itself writes - is always dropped
|
|
@@ -143,7 +143,7 @@ export class ChangelogService extends Service {
|
|
|
143
143
|
const ownCommits = dropVersionBumps(segment.commits).filter(c => ownersOf(repository, c).has(pkg));
|
|
144
144
|
if (!ownCommits.length)
|
|
145
145
|
continue;
|
|
146
|
-
const sections = groupCommits(ownCommits
|
|
146
|
+
const sections = groupCommits(ownCommits, ignoreTypesConfig(pkg), titles, order, withCommitHash(pkg, options));
|
|
147
147
|
// every commit could have been dropped by ignoreTypes - skip this package's entry entirely
|
|
148
148
|
// rather than rendering a heading with nothing real underneath it.
|
|
149
149
|
if (!sections.length)
|
|
@@ -168,9 +168,9 @@ export class ChangelogService extends Service {
|
|
|
168
168
|
}
|
|
169
169
|
}
|
|
170
170
|
/** `.rmanrc changelog.ignoreTypes` (cascaded, per-package overridable) - Conventional Commits
|
|
171
|
-
* `type`s to drop entirely (e.g. `[chore, dev]`), not just fold into "
|
|
171
|
+
* `type`s to drop entirely (e.g. `[chore, dev]`), not just fold into "General Changes". Only
|
|
172
172
|
* applies to commits that actually parse as `type: ...` - a non-conventional message always
|
|
173
|
-
* still lands in
|
|
173
|
+
* still lands in General Changes, since it has no `type` to match against. */
|
|
174
174
|
function ignoreTypesConfig(pkg) {
|
|
175
175
|
const v = pkg.config?.changelog?.ignoreTypes;
|
|
176
176
|
return new Set(Array.isArray(v) ? v.map(t => String(t).toLowerCase()) : []);
|
|
@@ -204,36 +204,62 @@ function resolveTitles(pkg) {
|
|
|
204
204
|
*
|
|
205
205
|
* **The type prefix is stripped for every section, not just two.** It used to be `push(line)` for
|
|
206
206
|
* `feat`/`fix` and `push(subject)` for everything else, so Features read `- the first feature`
|
|
207
|
-
* while
|
|
208
|
-
* repeating it. With every type able to have a heading of its own that asymmetry has no
|
|
209
|
-
* left. A subject that is not Conventional Commits at all has no prefix to strip and is
|
|
207
|
+
* while General Changes read `- chore: write changelog` - the heading naming the type and the
|
|
208
|
+
* bullet repeating it. With every type able to have a heading of its own that asymmetry has no
|
|
209
|
+
* defence left. A subject that is not Conventional Commits at all has no prefix to strip and is
|
|
210
|
+
* kept whole.
|
|
210
211
|
*/
|
|
211
|
-
|
|
212
|
+
/* **A message repeated inside one section is written once.** Real histories hold runs of identical
|
|
213
|
+
* subjects - measured on `panates/postgrejs`, whose v2.22.1 entry read `Updated config` five times
|
|
214
|
+
* under one heading - and the repetition states nothing the first line did not. Deduplicated **per
|
|
215
|
+
* section**, not per entry: `feat: x` and `fix: x` are different claims that land under different
|
|
216
|
+
* headings, and collapsing those would lose a fact rather than a repetition.
|
|
217
|
+
*
|
|
218
|
+
* **Keyed on the message, before the sha is appended**, which is the whole subtlety: with the sha on
|
|
219
|
+
* the line every duplicate is textually unique and the check would never fire. So the survivor keeps
|
|
220
|
+
* the *first* commit's sha - and since `listCommits` returns oldest-first, that is the earliest of
|
|
221
|
+
* the run. The others are dropped entirely, which is the point rather than a cost: the entry says
|
|
222
|
+
* what changed, and `git log` says how many times it was committed.
|
|
223
|
+
*
|
|
224
|
+
* **The sha is written plain, never as a Markdown link.** GitHub autolinks an abbreviated sha
|
|
225
|
+
* wherever it renders Markdown inside the repository, so `(a1b2c3d)` is already a link on the page
|
|
226
|
+
* and still readable in a terminal; a hand-built URL would need to know the forge, which nothing
|
|
227
|
+
* here does. Seven characters because that is what `git log --abbrev-commit` and GitHub both show.
|
|
228
|
+
*/
|
|
229
|
+
function groupCommits(commits, ignoreTypes, titles, order, commitHash) {
|
|
212
230
|
const CATCH_ALL = '*';
|
|
213
231
|
const byTitle = new Map();
|
|
232
|
+
const seenByTitle = new Map();
|
|
214
233
|
const titleFor = (type) => titles.get(type) ?? titles.get(CATCH_ALL) ?? DEFAULT_TITLES.get(CATCH_ALL);
|
|
215
|
-
const push = (title,
|
|
234
|
+
const push = (title, message, sha) => {
|
|
235
|
+
let seen = seenByTitle.get(title);
|
|
236
|
+
if (!seen)
|
|
237
|
+
seenByTitle.set(title, (seen = new Set()));
|
|
238
|
+
if (seen.has(message))
|
|
239
|
+
return;
|
|
240
|
+
seen.add(message);
|
|
241
|
+
const line = commitHash ? `${message} (${sha.slice(0, SHORT_SHA_LENGTH)})` : message;
|
|
216
242
|
const lines = byTitle.get(title);
|
|
217
243
|
if (lines)
|
|
218
244
|
lines.push(line);
|
|
219
245
|
else
|
|
220
246
|
byTitle.set(title, [line]);
|
|
221
247
|
};
|
|
222
|
-
for (const subject of
|
|
248
|
+
for (const { subject, sha } of commits) {
|
|
223
249
|
const parsed = ConventionalCommitsService.parseSubject(subject);
|
|
224
250
|
if (!parsed) {
|
|
225
|
-
push(titleFor(CATCH_ALL), subject);
|
|
251
|
+
push(titleFor(CATCH_ALL), subject, sha);
|
|
226
252
|
continue;
|
|
227
253
|
}
|
|
228
254
|
const { type, scope, description } = parsed;
|
|
229
255
|
if (ignoreTypes.has(type))
|
|
230
256
|
continue;
|
|
231
|
-
push(titleFor(type), scope ? `**${scope}:** ${description}` : description);
|
|
257
|
+
push(titleFor(type), scope ? `**${scope}:** ${description}` : description, sha);
|
|
232
258
|
}
|
|
233
259
|
/**
|
|
234
260
|
* Only a type with a heading of its own takes a position. A type nobody named resolves to the
|
|
235
261
|
* catch-all, so ordering by it would drag the catch-all to wherever that type was listed -
|
|
236
|
-
* measured: `sortTitles: ['docs', 'fix', 'feat']` with no `docs` heading put "
|
|
262
|
+
* measured: `sortTitles: ['docs', 'fix', 'feat']` with no `docs` heading put "General Changes"
|
|
237
263
|
* first and swallowed the sections after it.
|
|
238
264
|
*/
|
|
239
265
|
const ordered = [...new Set(order.filter(t => t !== CATCH_ALL && titles.has(t)).map(titleFor))];
|
|
@@ -286,11 +312,57 @@ function legacyBuckets(sections, titles) {
|
|
|
286
312
|
}
|
|
287
313
|
/** `feat`/`fix`/everything else, which is what rman shipped before `changelog.titles` existed and
|
|
288
314
|
* so is what a repository that sets nothing still gets, wording and order alike. */
|
|
315
|
+
/**
|
|
316
|
+
* The heading each commit type is listed under, and - because `resolveOrder` falls back to
|
|
317
|
+
* `titles.keys()` - the order the sections come out in. `.rmanrc changelog.titles` is merged over
|
|
318
|
+
* this **per key**, so a repository renaming one keeps the rest and keeps its position.
|
|
319
|
+
*/
|
|
320
|
+
/* **Three entries was too few, and the measurement is what says so.** `feat`, `fix` and a catch-all
|
|
321
|
+
* were all rman named, so every other type fell into one heap: counted across four of this
|
|
322
|
+
* organization's repositories (rman, postgrejs, panates-javascript, syncbridge-public), that heap
|
|
323
|
+
* held 194 `chore`, 86 `docs`, 68 `refactor`, 29 `test`, 27 `ci` and 24 `perf` - more commits than
|
|
324
|
+
* the two named types put together, under a heading that says only "not one of the two".
|
|
325
|
+
*
|
|
326
|
+
* The set is Conventional Commits' own standard types and stops there. `dev` (32 across the same
|
|
327
|
+
* four) and `bench` (9) are this organization's inventions, and a repository names those in its own
|
|
328
|
+
* `changelog.titles` - or a shared preset does it once for all of them. rman shipping a heading for
|
|
329
|
+
* a type no convention defines would be guessing on everyone else's behalf.
|
|
330
|
+
*
|
|
331
|
+
* **These add sections; they do not hide anything.** `ignoreTypes` is what drops a type, and it
|
|
332
|
+
* stays the only thing that does - which is the one place this parts from conventional-changelog,
|
|
333
|
+
* whose default preset silently hides `chore`, `ci`, `build`, `style` and `test`. A changelog that
|
|
334
|
+
* quietly omits a third of the history is the failure mode worth avoiding; a reader who does not
|
|
335
|
+
* want those sections can say so in one key.
|
|
336
|
+
*
|
|
337
|
+
* Order: Features, Bug Fixes, Performance and Reverts first, as conventional-changelog has them -
|
|
338
|
+
* the four a reader is looking for - then the rest, then the catch-all, which `groupCommits` pins
|
|
339
|
+
* last whatever anyone writes.
|
|
340
|
+
*
|
|
341
|
+
* **Every emoji here is checked for U+FE0F and none carries it.** A variation selector survives
|
|
342
|
+
* github-slugger, so `### ♻️ Refactoring` gets an anchor nobody types by hand and every link to it
|
|
343
|
+
* silently lands at the top of the page - the trap already recorded for `⬆️`. That is why refactor
|
|
344
|
+
* is `🔧` (U+1F527, free now that the catch-all is `💬`) rather than `♻️`, and ci is `🤖` rather
|
|
345
|
+
* than `⚙️`.
|
|
346
|
+
*
|
|
347
|
+
* "General Changes", not "Other Changes", for the catch-all: even with eleven named types it holds
|
|
348
|
+
* whatever convention a repository invented for itself, and "Other" reads as the leftovers after
|
|
349
|
+
* the interesting part. */
|
|
289
350
|
const DEFAULT_TITLES = new Map([
|
|
290
351
|
['feat', '✨ Features'],
|
|
291
352
|
['fix', '🐛 Bug Fixes'],
|
|
292
|
-
['
|
|
353
|
+
['perf', '⚡ Performance and Optimizations'],
|
|
354
|
+
['revert', '⏪ Reverts'],
|
|
355
|
+
['refactor', '🔧 Refactoring'],
|
|
356
|
+
['docs', '📚 Documentation'],
|
|
357
|
+
['test', '🧪 Tests'],
|
|
358
|
+
['build', '📦 Build System'],
|
|
359
|
+
['ci', '🤖 Continuous Integration'],
|
|
360
|
+
['chore', '🧹 Chores'],
|
|
361
|
+
['style', '🎨 Code Style'],
|
|
362
|
+
['*', '💬 General Changes'],
|
|
293
363
|
]);
|
|
364
|
+
/** What `git log --abbrev-commit` prints and what GitHub autolinks. */
|
|
365
|
+
const SHORT_SHA_LENGTH = 7;
|
|
294
366
|
function bulletList(lines) {
|
|
295
367
|
return lines.map(l => `- ${l}`).join('\n');
|
|
296
368
|
}
|
|
@@ -353,6 +425,12 @@ function resolveUnreleased(pkg, options) {
|
|
|
353
425
|
const cfg = pkg.config?.changelog?.unreleased;
|
|
354
426
|
return options.unreleased ?? (typeof cfg === 'boolean' ? cfg : true);
|
|
355
427
|
}
|
|
428
|
+
/** Whether each line carries its commit's short sha - `.rmanrc changelog.commitHash`, or the flag,
|
|
429
|
+
* which wins as everywhere. Defaults to `true`; `--no-commit-hash` is what turns it off. */
|
|
430
|
+
function withCommitHash(pkg, options) {
|
|
431
|
+
const cfg = pkg.config?.changelog?.commitHash;
|
|
432
|
+
return options.commitHash ?? (typeof cfg === 'boolean' ? cfg : true);
|
|
433
|
+
}
|
|
356
434
|
/**
|
|
357
435
|
* **Where this package's changelog begins** - `.rmanrc changelog.startingAt`, or the CLI flag,
|
|
358
436
|
* which wins as everywhere.
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
export declare namespace ConventionalCommitsService {
|
|
14
14
|
/**
|
|
15
15
|
* `type(scope): description`, optionally with a `!` breaking-change marker - Conventional
|
|
16
|
-
* Commits' subject-line shape. Anything that doesn't match falls into "
|
|
16
|
+
* Commits' subject-line shape. Anything that doesn't match falls into "General Changes" as-is (for
|
|
17
17
|
* changelog entries) or defaults to a patch-level change (for version bump severity) - see
|
|
18
18
|
* `parseSubject`.
|
|
19
19
|
*/
|
|
@@ -14,7 +14,7 @@ export var ConventionalCommitsService;
|
|
|
14
14
|
(function (ConventionalCommitsService) {
|
|
15
15
|
/**
|
|
16
16
|
* `type(scope): description`, optionally with a `!` breaking-change marker - Conventional
|
|
17
|
-
* Commits' subject-line shape. Anything that doesn't match falls into "
|
|
17
|
+
* Commits' subject-line shape. Anything that doesn't match falls into "General Changes" as-is (for
|
|
18
18
|
* changelog entries) or defaults to a patch-level change (for version bump severity) - see
|
|
19
19
|
* `parseSubject`.
|
|
20
20
|
*/
|
|
@@ -46,12 +46,17 @@ export interface PackageFilterOptions {
|
|
|
46
46
|
/**
|
|
47
47
|
* **`--scope /` is the repository's own root package**, the one selector that is not a glob.
|
|
48
48
|
*
|
|
49
|
-
* The same `/` `.rmanrc`'s `"[/]"` block uses, and for the same reason stated there: *
|
|
50
|
-
* never selected by name.* A glob matches package names, and a name can be anything - so
|
|
49
|
+
* The same `/` `.rmanrc`'s `"[/]"` block uses, and for the same reason stated there: *a monorepo's
|
|
50
|
+
* root is never selected by name.* A glob matches package names, and a name can be anything - so
|
|
51
51
|
* `--scope rman-repo` happened to work (measured) while being exactly the name-based addressing the
|
|
52
52
|
* config selectors were redesigned to remove. `/` is structural, cannot collide with a package
|
|
53
53
|
* (nothing can be named it), and gives "the root" one spelling across config and CLI.
|
|
54
54
|
*
|
|
55
|
+
* **In a single-package repository a glob does reach the root**, because there it is not a container
|
|
56
|
+
* that happens to have a name - it is the one package, and `--scope '*'` meaning "no packages" was
|
|
57
|
+
* the same silent emptiness `"[*]"` had in a `.rmanrc`. `/` still selects it, so nothing that worked
|
|
58
|
+
* stops working; see `Workspace._speaksFor`, which carries the identical clause.
|
|
59
|
+
*
|
|
55
60
|
* Accepted by `--ignore` too. The asymmetry would be the thing to remember, and `--ignore /` -
|
|
56
61
|
* every package but the root - is a real thing to want of `clean`.
|
|
57
62
|
*
|
package/utils/package-filter.js
CHANGED
|
@@ -2,12 +2,17 @@ import micromatch from 'micromatch';
|
|
|
2
2
|
/**
|
|
3
3
|
* **`--scope /` is the repository's own root package**, the one selector that is not a glob.
|
|
4
4
|
*
|
|
5
|
-
* The same `/` `.rmanrc`'s `"[/]"` block uses, and for the same reason stated there: *
|
|
6
|
-
* never selected by name.* A glob matches package names, and a name can be anything - so
|
|
5
|
+
* The same `/` `.rmanrc`'s `"[/]"` block uses, and for the same reason stated there: *a monorepo's
|
|
6
|
+
* root is never selected by name.* A glob matches package names, and a name can be anything - so
|
|
7
7
|
* `--scope rman-repo` happened to work (measured) while being exactly the name-based addressing the
|
|
8
8
|
* config selectors were redesigned to remove. `/` is structural, cannot collide with a package
|
|
9
9
|
* (nothing can be named it), and gives "the root" one spelling across config and CLI.
|
|
10
10
|
*
|
|
11
|
+
* **In a single-package repository a glob does reach the root**, because there it is not a container
|
|
12
|
+
* that happens to have a name - it is the one package, and `--scope '*'` meaning "no packages" was
|
|
13
|
+
* the same silent emptiness `"[*]"` had in a `.rmanrc`. `/` still selects it, so nothing that worked
|
|
14
|
+
* stops working; see `Workspace._speaksFor`, which carries the identical clause.
|
|
15
|
+
*
|
|
11
16
|
* Accepted by `--ignore` too. The asymmetry would be the thing to remember, and `--ignore /` -
|
|
12
17
|
* every package but the root - is a real thing to want of `clean`.
|
|
13
18
|
*
|
|
@@ -297,5 +302,12 @@ function selector(value) {
|
|
|
297
302
|
* `/` above already is. It was `pkg.name`, and the two coincide for every Node repository; a
|
|
298
303
|
* package having a name at all is an ecosystem's promise, and a repository can assign a selector
|
|
299
304
|
* where its own does not offer one. */
|
|
300
|
-
|
|
305
|
+
/** **A glob reaches the root in a single-package repository**, exactly as `"[*]"` does in a
|
|
306
|
+
* `.rmanrc` - the two are documented as the same set, so they move together or a repository's
|
|
307
|
+
* config and its `--scope` disagree about what a package is. In a monorepo the root is still
|
|
308
|
+
* addressed by `/` alone; there it is a container that happens to have a name, and a glob picking
|
|
309
|
+
* it up by that name is the trap `/` was introduced to remove. `pkg.repository` is safe to read
|
|
310
|
+
* under `isRoot`, which is `false` until `Repository.create` assigns it. */
|
|
311
|
+
const matchesGlob = (pkg) => globs.length > 0 && micromatch.isMatch(pkg.selector, globs);
|
|
312
|
+
return pkg => (pkg.isRoot ? wantsRoot || (!pkg.repository.monorepo && matchesGlob(pkg)) : matchesGlob(pkg));
|
|
301
313
|
}
|