From 889dadaf96d36f61de105613a401a269f3177614 Mon Sep 17 00:00:00 2001 From: Joseph Mearman Date: Sat, 10 Oct 2026 15:38:04 +0100 Subject: [PATCH] build: release and publish notes for every commit type Every conventional commit type now earns a patch release (feat stays minor) and is shown in the changelog under its section, so a change to a package is never left unpublished. --- commit-types.ts | 18 +++++++++--------- release-workspace.config.ts | 13 +++++++------ release-workspace.config.unit.test.ts | 13 ++++--------- 3 files changed, 20 insertions(+), 24 deletions(-) diff --git a/commit-types.ts b/commit-types.ts index 2c39dad..83c5869 100644 --- a/commit-types.ts +++ b/commit-types.ts @@ -1,10 +1,10 @@ /** - * The single source of truth for which Conventional Commit types this workspace accepts, and which release level (if any) each one triggers. Both `commitlint.config.ts` and `release-workspace.config.ts` import this file so a commit type can never trigger a release without also passing commit-message validation, or the reverse. One definition, not two that could drift apart. + * The single source of truth for which Conventional Commit types this workspace accepts, and which release level each one triggers; every type releases, so no change to a package can go unpublished. Both `commitlint.config.ts` and `release-workspace.config.ts` import this file so a commit type can never trigger a release without also passing commit-message validation, or the reverse. One definition, not two that could drift apart. */ export interface CommitTypeDefinition { readonly type: string; readonly section: string; - readonly release: "patch" | "minor" | false; + readonly release: "patch" | "minor"; } export const commitTypes: readonly CommitTypeDefinition[] = [ @@ -12,13 +12,13 @@ export const commitTypes: readonly CommitTypeDefinition[] = [ { type: "fix", section: "Bug Fixes", release: "patch" }, { type: "perf", section: "Performance Improvements", release: "patch" }, { type: "revert", section: "Reverts", release: "patch" }, - { type: "docs", section: "Documentation", release: false }, - { type: "style", section: "Styles", release: false }, - { type: "refactor", section: "Code Refactoring", release: false }, - { type: "test", section: "Tests", release: false }, - { type: "build", section: "Build System", release: false }, - { type: "ci", section: "Continuous Integration", release: false }, - { type: "chore", section: "Chores", release: false }, + { type: "docs", section: "Documentation", release: "patch" }, + { type: "style", section: "Styles", release: "patch" }, + { type: "refactor", section: "Code Refactoring", release: "patch" }, + { type: "test", section: "Tests", release: "patch" }, + { type: "build", section: "Build System", release: "patch" }, + { type: "ci", section: "Continuous Integration", release: "patch" }, + { type: "chore", section: "Chores", release: "patch" }, ]; export const commitTypeNames: readonly string[] = commitTypes.map( diff --git a/release-workspace.config.ts b/release-workspace.config.ts index 4ff59ac..fd916ff 100644 --- a/release-workspace.config.ts +++ b/release-workspace.config.ts @@ -12,22 +12,23 @@ import { commitTypes } from "./commit-types.ts"; */ /** - * commit-analyzer only falls back to its default rules, which include "a breaking change is a major release", when no custom rule matches a commit. A breaking `feat` matches `feat -> minor` here, so without this rule it would release as a minor version. + * Every commit type releases, at the level `commit-types.ts` names, and appears in the notes under its own section. commit-analyzer only falls back to its default rules, which include "a breaking change is a major release", when no custom rule matches a commit. A breaking `feat` matches `feat -> minor` here, so without this rule it would release as a minor version. */ const releaseRules = [ { breaking: true, release: "major" }, - ...commitTypes - .filter((entry) => entry.release !== false) - .map((entry) => ({ type: entry.type, release: entry.release })), + ...commitTypes.map((entry) => ({ + type: entry.type, + release: entry.release, + })), ]; /** - * Every commit type gets its section name. A type that earns no release is hidden from the notes, which is the conventionalcommits preset's own default for those types, stated here so that a type earning a release can never be hidden by omission. + * Every commit type gets its section name and is shown. The conventionalcommits preset hides docs, style, refactor, test, build, ci and chore by default, so `hidden: false` is stated for each type rather than left to that default: a release must never publish empty notes. */ const noteTypes = commitTypes.map((entry) => ({ type: entry.type, section: entry.section, - hidden: entry.release === false, + hidden: false, })); const config: Pick< diff --git a/release-workspace.config.unit.test.ts b/release-workspace.config.unit.test.ts index d01a5be..e6a6db5 100644 --- a/release-workspace.config.unit.test.ts +++ b/release-workspace.config.unit.test.ts @@ -60,9 +60,7 @@ const noteEntries = requireArray( "generateNotes.presetConfig.types", ).filter(isTypedEntry); -const releasingTypes = commitTypes - .filter((entry) => entry.release !== false) - .map((entry) => entry.type); +const releasingTypes = commitTypes.map((entry) => entry.type); describe("release-workspace.config.ts", () => { it("analyses and renders commits under the same preset", () => { @@ -70,7 +68,7 @@ describe("release-workspace.config.ts", () => { expect(generateNotes.preset).toBe(analyzeCommits.preset); }); - it("releases exactly the commit types commit-types.ts marks as releasing", () => { + it("releases every commit type commit-types.ts lists", () => { expect(releaseRules.map((rule) => rule.type).sort()).toStrictEqual( [...releasingTypes].sort(), ); @@ -78,9 +76,6 @@ describe("release-workspace.config.ts", () => { it("releases each type at the level commit-types.ts names", () => { for (const { type, release } of commitTypes) { - if (release === false) { - continue; - } expect(releaseRules.find((rule) => rule.type === type)?.release).toBe( release, ); @@ -107,8 +102,8 @@ describe("release-workspace.config.ts", () => { } }); - it("hides no type that earns a release from the notes", () => { - // The preset hides docs, style, chore, refactor, test, build and ci by default; a type that releases must never inherit that, or its release would publish empty notes. + it("hides no type from the notes", () => { + // The preset hides docs, style, chore, refactor, test, build and ci by default; every type releases, so none may inherit that, or its release would publish empty notes. for (const type of releasingTypes) { expect( noteEntries.find((entry) => entry.type === type)?.hidden,