rman 2.9.0 → 2.10.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 +15 -0
- package/constants.js +1 -1
- package/package.json +1 -1
- package/services/changelog.service.js +120 -14
|
@@ -184,6 +184,21 @@ declare const config: {
|
|
|
184
184
|
* from commit type to section heading, and an option says `type: 'string'`.
|
|
185
185
|
*/
|
|
186
186
|
export interface ChangelogExtraKeys {
|
|
187
|
+
/**
|
|
188
|
+
* Where each **named** group's changelog is written under `groupBy: 'group'`, relative to the
|
|
189
|
+
* repository root - `{ core: 'packages/core/CHANGELOG.md', dialects: 'docs/dialects.md' }`.
|
|
190
|
+
* Read off the repository root only.
|
|
191
|
+
*
|
|
192
|
+
* A group not listed keeps the default rule, `CHANGELOG-<group>.md` beside the default file; the
|
|
193
|
+
* default group's own file is `changelog.filePath`. A key naming no group, a path outside the
|
|
194
|
+
* repository, and two groups resolving to one file are all refused before anything is written.
|
|
195
|
+
* An explicit `--file-path` overrides the whole map for that run.
|
|
196
|
+
*
|
|
197
|
+
* **Move an existing file with `git mv`, not by changing the path here.** A file's
|
|
198
|
+
* `documented-up-to` marker is what the next `--write` starts from; at a new path there is none,
|
|
199
|
+
* and the write reads the group's entire history.
|
|
200
|
+
*/
|
|
201
|
+
groupFiles?: ConfigValue<Record<string, string>>;
|
|
187
202
|
/**
|
|
188
203
|
* The heading each Conventional Commits type is listed under - `{ feat: 'New Features', dev:
|
|
189
204
|
* 'Development Changes' }`. Per-package cascaded.
|
package/constants.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export const version = '2.
|
|
1
|
+
export const version = '2.10.0';
|
package/package.json
CHANGED
|
@@ -600,33 +600,139 @@ function resolveFilePath(pkg, optionsFilePath) {
|
|
|
600
600
|
*/
|
|
601
601
|
function partitionTargets(repository, targets, options) {
|
|
602
602
|
const grouping = changelogGroupBy(repository, options);
|
|
603
|
-
const
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
else
|
|
610
|
-
byKey.set(key, [pkg]);
|
|
611
|
-
}
|
|
603
|
+
const groupFiles = grouping === 'group' ? readGroupFiles(repository, options) : {};
|
|
604
|
+
/** Checked against the **whole** repository, not against `targets`: a collision is a fact about
|
|
605
|
+
* the configuration, and a `--scope` that happens to leave one of the two groups out must not be
|
|
606
|
+
* what lets it through. */
|
|
607
|
+
if (grouping === 'group')
|
|
608
|
+
assertGroupFiles(repository, groupFiles, options);
|
|
612
609
|
const repositoryLabel = `${path.basename(repository.dirname)} repository`;
|
|
613
|
-
return [...
|
|
610
|
+
return [...groupTargets(targets, grouping)].map(([key, members]) => {
|
|
614
611
|
/** `targets` starts at the root, so a group holding it has it first - which is what makes the
|
|
615
612
|
* root the `home` of the default group without a second rule saying so. */
|
|
616
613
|
const home = members[0];
|
|
617
614
|
const grouped = !key.startsWith('solo:');
|
|
618
|
-
const named =
|
|
619
|
-
const filePkg = grouped ? repository.rootPackage : home;
|
|
615
|
+
const named = namedGroupOf(key);
|
|
620
616
|
return {
|
|
621
617
|
home,
|
|
622
|
-
filePkg,
|
|
618
|
+
filePkg: grouped ? repository.rootPackage : home,
|
|
623
619
|
grouped,
|
|
624
620
|
members: new Set(members),
|
|
625
621
|
label: named ?? (home === repository.rootPackage ? repositoryLabel : home.name),
|
|
626
|
-
file:
|
|
622
|
+
file: groupFile(repository, key, home, groupFiles, options),
|
|
627
623
|
};
|
|
628
624
|
});
|
|
629
625
|
}
|
|
626
|
+
/** The packages cut into what each file is about - by `.rmanrc group` under `groupBy: 'group'`, one
|
|
627
|
+
* package each otherwise. Insertion order, so the first member of each group is the first of
|
|
628
|
+
* `packages` to belong to it. */
|
|
629
|
+
function groupTargets(packages, grouping) {
|
|
630
|
+
const byKey = new Map();
|
|
631
|
+
for (const pkg of packages) {
|
|
632
|
+
const key = grouping === 'group' ? groupKeyOf(pkg) : `solo:${pkg.name}`;
|
|
633
|
+
const members = byKey.get(key);
|
|
634
|
+
if (members)
|
|
635
|
+
members.push(pkg);
|
|
636
|
+
else
|
|
637
|
+
byKey.set(key, [pkg]);
|
|
638
|
+
}
|
|
639
|
+
return byKey;
|
|
640
|
+
}
|
|
641
|
+
/** `named:core` -> `core`; `undefined` for the default group and for a package that is its own. */
|
|
642
|
+
function namedGroupOf(key) {
|
|
643
|
+
return key.startsWith('named:') ? key.slice('named:'.length) : undefined;
|
|
644
|
+
}
|
|
645
|
+
/**
|
|
646
|
+
* Where one group's changelog is written, absolute.
|
|
647
|
+
*
|
|
648
|
+
* In order: a named group listed in `changelog.groupFiles` goes where it says, relative to the
|
|
649
|
+
* repository root; any other named group takes the default file's name with its own suffixed
|
|
650
|
+
* (`CHANGELOG-core.md`), at the root; the default group takes the root's `changelog.filePath`; and a
|
|
651
|
+
* package that is a group of itself takes its own, in its own directory.
|
|
652
|
+
*
|
|
653
|
+
* **An explicit `--file-path` beats `groupFiles`**, as it already beats `changelog.filePath`: it
|
|
654
|
+
* names this run's file, and a run asking for one shape of name should not find half its groups
|
|
655
|
+
* somewhere a config put them. Named groups are still suffixed under it, so two cannot collide.
|
|
656
|
+
*/
|
|
657
|
+
function groupFile(repository, key, home, groupFiles, options) {
|
|
658
|
+
if (key.startsWith('solo:'))
|
|
659
|
+
return path.join(home.dirname, resolveFilePath(home, options.filePath));
|
|
660
|
+
const named = namedGroupOf(key);
|
|
661
|
+
const listed = named && !options.filePath ? groupFiles[named] : undefined;
|
|
662
|
+
if (listed)
|
|
663
|
+
return path.resolve(repository.dirname, listed);
|
|
664
|
+
return path.join(repository.dirname, suffixFileName(resolveFilePath(home, options.filePath), named));
|
|
665
|
+
}
|
|
666
|
+
/**
|
|
667
|
+
* `.rmanrc changelog.groupFiles`, off the repository root and nowhere else - a map from a named
|
|
668
|
+
* group to the file its changelog is written to, relative to the repository root.
|
|
669
|
+
*
|
|
670
|
+
* **The root's for the reason `groupBy` is the root's**: which file a group shares is a layout, and
|
|
671
|
+
* cascaded per package two members of one group could disagree about it, which has no answer.
|
|
672
|
+
*/
|
|
673
|
+
function readGroupFiles(repository, options) {
|
|
674
|
+
/** `--file-path` takes the whole decision - see `groupFile` - so the map is not even read. A
|
|
675
|
+
* mistake in it is still a mistake, but not one this run acts on. */
|
|
676
|
+
if (options.filePath)
|
|
677
|
+
return {};
|
|
678
|
+
const declared = repository.rootPackage.config?.changelog?.groupFiles;
|
|
679
|
+
if (declared === undefined || declared === null)
|
|
680
|
+
return {};
|
|
681
|
+
if (typeof declared !== 'object' || Array.isArray(declared)) {
|
|
682
|
+
throw new Error(`.rmanrc "changelog.groupFiles" must map a group name to a file path, ` +
|
|
683
|
+
`e.g. { core: "packages/core/CHANGELOG.md" }.`);
|
|
684
|
+
}
|
|
685
|
+
for (const [group, file] of Object.entries(declared)) {
|
|
686
|
+
if (typeof file !== 'string' || !file) {
|
|
687
|
+
throw new Error(`.rmanrc "changelog.groupFiles.${group}" must be a file path, relative to the repository root.`);
|
|
688
|
+
}
|
|
689
|
+
}
|
|
690
|
+
return declared;
|
|
691
|
+
}
|
|
692
|
+
/**
|
|
693
|
+
* Refuses a `changelog.groupFiles` that would write somewhere wrong - before anything is written.
|
|
694
|
+
*
|
|
695
|
+
* Three ways, and each is silent otherwise:
|
|
696
|
+
*
|
|
697
|
+
* - **A key naming no group.** rman drops an unknown config key without a word, so a typo here would
|
|
698
|
+
* leave the group on the default rule and nothing would say so. Here the groups are known, so the
|
|
699
|
+
* key can be checked against them.
|
|
700
|
+
* - **A path leaving the repository.** `version --changelog` commits the file with the release; a
|
|
701
|
+
* file outside the working tree is one it cannot add.
|
|
702
|
+
* - **Two groups sharing one file**, including the default group and any package that is a group of
|
|
703
|
+
* itself. The `rman:documented-up-to` marker is one per file, so two groups writing into it would
|
|
704
|
+
* each read the other's marker as its own boundary - entries missing or written twice, with
|
|
705
|
+
* nothing failing. The default rule cannot produce a collision; an explicit map can, and an
|
|
706
|
+
* unlisted group's suffixed name can land on a listed one's path.
|
|
707
|
+
*/
|
|
708
|
+
function assertGroupFiles(repository, groupFiles, options) {
|
|
709
|
+
const all = groupTargets([repository.rootPackage, ...repository.packages], 'group');
|
|
710
|
+
const named = [...all.keys()].map(namedGroupOf).filter((name) => !!name);
|
|
711
|
+
for (const [group, file] of Object.entries(groupFiles)) {
|
|
712
|
+
if (!named.includes(group)) {
|
|
713
|
+
throw new Error(`.rmanrc "changelog.groupFiles" names a group "${group}" that no package belongs to. ` +
|
|
714
|
+
(named.length ? `Named groups here: ${named.join(', ')}.` : 'This repository has no named groups.'));
|
|
715
|
+
}
|
|
716
|
+
const rel = path.relative(repository.dirname, path.resolve(repository.dirname, file));
|
|
717
|
+
if (rel.startsWith('..') || path.isAbsolute(rel)) {
|
|
718
|
+
throw new Error(`.rmanrc "changelog.groupFiles.${group}" points outside the repository (${file}). ` +
|
|
719
|
+
`The file is committed with the release, so it has to be inside the working tree.`);
|
|
720
|
+
}
|
|
721
|
+
}
|
|
722
|
+
const owner = new Map();
|
|
723
|
+
for (const [key, members] of all) {
|
|
724
|
+
const file = groupFile(repository, key, members[0], groupFiles, options);
|
|
725
|
+
const label = namedGroupOf(key) ?? (key === 'default' ? 'the default group' : members[0].name);
|
|
726
|
+
const other = owner.get(file);
|
|
727
|
+
if (other) {
|
|
728
|
+
throw new Error(`Two changelogs would be written to one file: ${other} and ${label} both resolve to ` +
|
|
729
|
+
`${path.relative(repository.dirname, file)}.\n` +
|
|
730
|
+
` Each file carries one "documented up to" marker, so two groups sharing it would read each ` +
|
|
731
|
+
`other's boundary. Give each its own path in .rmanrc "changelog.groupFiles".`);
|
|
732
|
+
}
|
|
733
|
+
owner.set(file, label);
|
|
734
|
+
}
|
|
735
|
+
}
|
|
630
736
|
/** `CHANGELOG.md` + `core` -> `CHANGELOG-core.md`, keeping whatever directory and extension
|
|
631
737
|
* `changelog.filePath` chose (`docs/HISTORY.md` -> `docs/HISTORY-core.md`). */
|
|
632
738
|
function suffixFileName(filePath, suffix) {
|