Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/swift-modules-changelog.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"changesets": minor
---

Add support for per-module changelogs, so each sub-module in a multi-module Maven project can maintain its own `CHANGELOG.md` instead of sharing a single aggregated one.
29 changes: 28 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,31 @@ independent mode, the BOM's pom is left untouched (version *and* pinned properti
standard per-module sections. The `bom` block in `.changeset/config.json` stays in place — `skipBom` is a per-run override,
not a config change. Use it when you want to ship a quick starter patch between full BOM releases.

## Per-module changelogs

By default the release block is prepended to a single `CHANGELOG.md` at the reactor root. With `independent` versioning you
can instead emit one `CHANGELOG.md` per bumped submodule by setting `changelog: "module"`:

```json
{
"versioning": "independent",
"changelog": "module"
}
```

Behavior:

- Each module that bumps gets its own `CHANGELOG.md` next to its `pom.xml`, containing only that module's changes.
- Modules without changesets for the release are skipped — no empty file is created.
- No reactor-root `CHANGELOG.md` is written. Any existing root `CHANGELOG.md` is left untouched.
- If a [BOM](#bom-bill-of-materials-support) is also configured, the reactor-root `CHANGELOG.md` is *additionally* written as
a rollup summary — one entry per bumped module under a single `consumer-parent@<bomVersion>` header, with each module's
changes nested below — so consumers see a dependabot-style overview of what moved in that BOM release while the
authoritative per-module history lives next to each module.
Comment on lines +103 to +107

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@AnteLund should we apply the suggestion?


`changelog: "module"` requires `versioning: "independent"`; combining it with `fixed` is a config validation error, since all
modules would share one version anyway. Omitting `changelog` keeps the previous behavior (a single root `CHANGELOG.md`).

## How `prepare` and `release` interact

`changesets:prepare` (aggregator goal, runs once at the reactor root) reads all changesets in `.changeset/`, computes a new
Expand Down Expand Up @@ -181,4 +206,6 @@ in history instead of two.

If you'd rather have `changesets:prepare` update the poms itself (e.g. to inspect them before triggering the
release-plugin), omit `useReleasePluginIntegration`. The trade-off is an extra "chore: prepare release" commit
before `release:prepare` runs.
before `release:prepare` runs.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change

This header seems to have been mistakenly added.

## Configuration reference
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ public record ChangesetsConfig(
fixed = fixed == null ? List.of() : List.copyOf(fixed);
changelog = changelog == null ? ChangelogMode.ROOT : changelog;
validateGroupsAreDisjoint(linked, fixed);
validateChangelogModeAgainstVersioning(versioning, changelog);
}

public static ChangesetsConfig defaults() {
Expand All @@ -55,6 +56,14 @@ public static ChangesetsConfig load(Path changesetsDir) {
}
}

private static void validateChangelogModeAgainstVersioning(VersioningStrategy versioning, ChangelogMode changelog) {
if (changelog == ChangelogMode.MODULE && versioning == VersioningStrategy.FIXED) {
throw new IllegalArgumentException(
"changelog mode 'module' requires versioning 'independent'; "
+ "with 'fixed' versioning all modules share a single version, so per-module changelogs are not meaningful");
}
}

private static void validateGroupsAreDisjoint(List<List<String>> linked, List<List<String>> fixed) {
Set<String> seen = new HashSet<>();
List<List<String>> allGroups = new ArrayList<>(linked.size() + fixed.size());
Expand Down Expand Up @@ -87,8 +96,20 @@ public static VersioningStrategy fromString(String value) {
}
}

/**
* Where release changelog entries are written.
*
* <ul>
* <li>{@code ROOT} (default): a single aggregated {@code CHANGELOG.md} at the reactor root.</li>
* <li>{@code MODULE}: each bumped module gets its own {@code CHANGELOG.md} next to its
* {@code pom.xml}. When a BOM is configured, the reactor-root {@code CHANGELOG.md} is
* additionally written as a rollup summarising the per-module bumps for that BOM version.
* Requires {@code versioning: independent}.</li>
* </ul>
*/
public enum ChangelogMode {
@JsonProperty("root") ROOT;
@JsonProperty("root") ROOT,
@JsonProperty("module") MODULE;

@JsonCreator
public static ChangelogMode fromString(String value) {
Expand All @@ -97,6 +118,7 @@ public static ChangelogMode fromString(String value) {
}
return switch (value.toLowerCase()) {
case "root" -> ROOT;
case "module" -> MODULE;
default -> throw new IllegalArgumentException("Unknown changelog mode: " + value);
};
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@

import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
import static se.fortnox.changesets.ChangesetsConfig.ChangelogMode.MODULE;
import static se.fortnox.changesets.ChangesetsConfig.ChangelogMode.ROOT;
import static se.fortnox.changesets.ChangesetsConfig.VersioningStrategy.FIXED;
import static se.fortnox.changesets.ChangesetsConfig.VersioningStrategy.INDEPENDENT;
Expand Down Expand Up @@ -107,6 +108,20 @@ void appliesDefaultsForMissingFields() throws IOException {
assertThat(config.changelog()).isEqualTo(ROOT);
}

@Test
void parsesModuleChangelogMode() throws IOException {
Files.writeString(tempDir.resolve("config.json"), """
{
"versioning": "independent",
"changelog": "module"
}
""");

var config = ChangesetsConfig.load(tempDir);

assertThat(config.changelog()).isEqualTo(MODULE);
}

@Test
void returnsDefaultsOnMalformedJson() throws IOException {
Files.writeString(tempDir.resolve("config.json"), "not json {");
Expand Down Expand Up @@ -143,6 +158,20 @@ void rejectsModuleInBothLinkedAndFixed() {
.hasMessageContaining("pkg-a");
}

@Test
void rejectsModuleChangelogWithFixedVersioning() {
assertThatThrownBy(() -> new ChangesetsConfig(FIXED, List.of(), List.of(), MODULE, null))
.isInstanceOf(IllegalArgumentException.class)
.hasMessageContaining("module");
}

@Test
void allowsModuleChangelogWithIndependentVersioning() {
var config = new ChangesetsConfig(INDEPENDENT, List.of(), List.of(), MODULE, null);

assertThat(config.changelog()).isEqualTo(MODULE);
}

@Test
void allowsDistinctGroups() {
var config = new ChangesetsConfig(
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"starter-a": minor
---

Added starter-a feature
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"starter-b": patch
---

Tiny starter-b fix
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"versioning": "independent",
"changelog": "module",
"bom": {
"module": "bom",
"consumerParent": "consumer-parent"
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
# Changelog

## consumer-parent@0.4.0

### starter-a@2.1.0

#### Minor Changes

- Added starter-a feature

### starter-b@3.0.1

#### Patch Changes

- Tiny starter-b fix

### bom@0.4.0

#### Pinned version updates

- starter-a@2.1.0
- starter-b@3.0.1

34 changes: 34 additions & 0 deletions changesets-maven-plugin/src/it/module-changelog-bom/bom/pom.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://maven.apache.org/POM/4.0.0"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>se.fortnox.maven.it</groupId>
<artifactId>module-changelog-bom-root</artifactId>
<version>1.0.0</version>
</parent>

<artifactId>bom</artifactId>
<version>0.3.0</version>
<packaging>pom</packaging>

<properties>
<starter-a.version>2.0.0</starter-a.version>
<starter-b.version>3.0.0</starter-b.version>
</properties>

<dependencyManagement>
<dependencies>
<dependency>
<groupId>se.fortnox.maven.it</groupId>
<artifactId>starter-a</artifactId>
<version>${starter-a.version}</version>
</dependency>
<dependency>
<groupId>se.fortnox.maven.it</groupId>
<artifactId>starter-b</artifactId>
<version>${starter-b.version}</version>
</dependency>
</dependencies>
</dependencyManagement>
</project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://maven.apache.org/POM/4.0.0"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>se.fortnox.maven.it</groupId>
<artifactId>bom</artifactId>
<version>0.3.0</version>
<relativePath>../bom</relativePath>
</parent>

<artifactId>consumer-parent</artifactId>
<packaging>pom</packaging>
</project>
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
invoker.goals=${project.groupId}:${project.artifactId}:${project.version}:prepare
22 changes: 22 additions & 0 deletions changesets-maven-plugin/src/it/module-changelog-bom/pom.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://maven.apache.org/POM/4.0.0"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>

<groupId>se.fortnox.maven.it</groupId>
<artifactId>module-changelog-bom-root</artifactId>
<version>1.0.0</version>
<packaging>pom</packaging>

<description>IT: BOM + per-module changelogs, with root CHANGELOG.md rollup.</description>

<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<modules>
<module>bom</module>
<module>consumer-parent</module>
<module>starter-a</module>
<module>starter-b</module>
</modules>
</project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
# Changelog

## starter-a@2.1.0

### Minor Changes

- Added starter-a feature

Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://maven.apache.org/POM/4.0.0"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>se.fortnox.maven.it</groupId>
<artifactId>module-changelog-bom-root</artifactId>
<version>1.0.0</version>
</parent>

<artifactId>starter-a</artifactId>
<version>2.0.0</version>
</project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
# Changelog

## starter-b@3.0.1

### Patch Changes

- Tiny starter-b fix

Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://maven.apache.org/POM/4.0.0"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>se.fortnox.maven.it</groupId>
<artifactId>module-changelog-bom-root</artifactId>
<version>1.0.0</version>
</parent>

<artifactId>starter-b</artifactId>
<version>3.0.0</version>
</project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import groovy.xml.XmlSlurper

import static org.assertj.core.api.Assertions.assertThat

def versions = new File(basedir, '.changeset/VERSIONS').text
assertThat(versions).contains("starter-a=2.1.0")
assertThat(versions).contains("starter-b=3.0.1")
assertThat(versions).contains("bom=0.4.0")

// BOM bumped to next-dev SNAPSHOT, properties rewritten
def bom = new XmlSlurper().parse(new File(basedir, 'bom/pom.xml'))
assertThat(bom.version).isEqualTo('0.4.1-SNAPSHOT')
assertThat(bom.properties.'starter-a.version'.text()).isEqualTo('2.1.1-SNAPSHOT')
assertThat(bom.properties.'starter-b.version'.text()).isEqualTo('3.0.2-SNAPSHOT')

// Starters bumped to their own next-dev SNAPSHOTs
def starterA = new XmlSlurper().parse(new File(basedir, 'starter-a/pom.xml'))
assertThat(starterA.version).isEqualTo('2.1.1-SNAPSHOT')

def starterB = new XmlSlurper().parse(new File(basedir, 'starter-b/pom.xml'))
assertThat(starterB.version).isEqualTo('3.0.2-SNAPSHOT')

// Per-module changelogs written next to each starter's pom
assertThat(new File(basedir, 'starter-a/CHANGELOG.md'))
.hasSameTextualContentAs(new File(basedir, 'starter-a/EXPECTED_CHANGELOG.md'))
assertThat(new File(basedir, 'starter-b/CHANGELOG.md'))
.hasSameTextualContentAs(new File(basedir, 'starter-b/EXPECTED_CHANGELOG.md'))

// No bom/CHANGELOG.md when BOM has no explicit changesets of its own
assertThat(new File(basedir, 'bom/CHANGELOG.md')).doesNotExist()

// Root CHANGELOG.md = the BOM rollup summary
assertThat(new File(basedir, 'CHANGELOG.md'))
.hasSameTextualContentAs(new File(basedir, 'EXPECTED_CHANGELOG.md'))

true
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"module-a": minor
---

Added module-a feature
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"module-b": patch
---

Tiny module-b fix
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"versioning": "independent",
"changelog": "module"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
invoker.goals=${project.groupId}:${project.artifactId}:${project.version}:prepare
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
# Changelog

## module-a@2.1.0

### Minor Changes

- Added module-a feature

Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://maven.apache.org/POM/4.0.0"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<artifactId>module-a</artifactId>
<version>2.0.0</version>
<parent>
<groupId>se.fortnox.maven.it</groupId>
<artifactId>module-changelog-root</artifactId>
<version>1.0.0</version>
</parent>
</project>
Loading
Loading