Deployn

Automatisierung des Release-Prozesses mit Commitizen und Semantic-release

Semantic-Release und Commitizen in ein Projekt integrieren, um automatisch neue Releases zu generieren. Optimisierung des Veröffentlichungsprozess und Zeitersparnis.

Automatisierung des Release-Prozesses mit Commitizen und Semantic-release-heroimage

Einleitung

In der Welt der Softwareentwicklung existiert der Grundsatz, dass alles automatisiert werden sollte, was möglich ist. Selbst wenn man alleine an einem Projekt arbeitet und verschiedene Features entwickelt, muss man jedes Mal nicht nur die Funktion testen, sondern sollte auch einen Changelog erstellen und eine Versionssystematik festlegen. Manuell kann dieser Vorgang viel Zeit in Anspruch nehmen. Doch mit automatischen Releases passiert das alles automatisch.

Automatische Releases spielen eine bedeutende Rolle im Software-Entwicklungsprozess und bieten zahlreiche Vorteile. Zunächst ermöglicht die Automatisierung des Release-Prozesses eine schnellere und effizientere Bereitstellung von Softwareupdates und -verbesserungen. Darüber hinaus verbessern automatische Releases die Zusammenarbeit und Kommunikation innerhalb eines Entwicklungsteams.

In diesem Artikel zeige ich, wie man den Release-Prozess in einem Node.js Repository automatisiert. Dazu binde ich Commitizen und Semantic-release in ein Next.js Projekt ein, welches auf Github gehostet wird.

Beide Tools arbeiten Hand in Hand, um den Software-Release-Prozess zu rationalisieren und Entwicklern Zeit und Aufwand zu ersparen.

Was ist Semantic Release?

Semantic Release automatisiert den Release-Prozess. Es analysiert Git-Commits, um basierend auf den Commit-Nachrichten die neue Versionsnummer zu bestimmen und automatisch einen Release zu erzeugen. Dabei richtet es sich nach dem Semantic Versioning (SemVer), einem branchenweiten Standard, der sicherstellt, dass Versionsnummern und Änderungen auf sinnvolle Weise dargestellt werden. Semantic Release ist ein Open-Source-Tool, das in Node.js geschrieben ist und mit jedem Git-Repository funktioniert. Es kann Releases für verschiedene Arten von Software generieren, darunter Bibliotheken, Frameworks, Plugins, Apps und mehr. Zudem ist es kompatibel mit einer Vielzahl von CI/CD-Tools wie Github Actions, Travis CI, CircleCI, Jenkins und GitLab CI.

Semantic Versioning

Vor über zehn Jahren führte Tom Preston-Werner, Mitbegründer von Github, das Konzept der semantischen Versionierung ein.

Die Versionsnummer einer Software besteht aus drei Teilen: Major, Minor und Patch. Diese Teile sind durch Punkte getrennt. Hier ist ein Beispiel:

{
	"name": "mein-projekt",
	"version": "1.0.0"
}

Wenn ein neues Feature hinzugefügt wird, erhöht sich die Minor-Version (1.1.0). Wenn ein Fehler behoben wird, erhöht sich die Patch-Version (1.0.1). Wenn die Major-Version erhöht wird (2.0.0), bedeutet dies, dass die Software nicht mehr abwärtskompatibel ist.

Beispiel:

  • 1.0.0: Erste Version
  • 1.0.1: Fehlerbehebung
  • 1.1.0: Neues Feature
  • 2.0.0: Änderung, die bestehende Funktionalität beeinträchtigt

In seinem Blogpost im Jahr 2022 argumentierte Tom Preston-Werner, dass Entwickler oft zögern, die Hauptversionsnummer zu erhöhen, selbst wenn es bedeutende Änderungen gibt. Er betonte, dass die Hauptversionsnummer für jede Änderung verwendet werden sollte, die die Abwärtskompatibilität bricht und nicht für Marketingzwecke missbraucht werden sollte.

Was ist Commitizen?

Commitizen ist ein Open-Source-Tool, das entwickelt wurde, um einheitliche Git-Commit-Nachrichten zu erstellen. Es bietet Entwicklern eine standardisierte Struktur und Syntax für Commit-Nachrichten, indem es sie durch den gesamten Commit-Prozess führt. Die Verwendung der “Conventional Commits”-Spezifikation ermöglicht eine einfachere Verwaltung und Navigation durch die Commit-Historie, da Konsistenz und Übersichtlichkeit gewährleistet werden.

Syntax der Commit-Nachrichten

Die Syntax der Commit-Nachrichten ist wie folgt aufgebaut:

<type>[optional scope]: <description>

[optional body]

[optional footer(s)]

Die einzelnen Teile sind:

  • type: Kategorie des Commits
  • scope: Bereich des Commits (optional)
  • description: Kurze Beschreibung des Commits
  • body: Ausführlichere Beschreibung des Commits (optional)
  • footer: Fußzeile des Commits (optional)

Beispiele

Beispiel für einen Commit zur Behebung eines Fehlers:

fix: resolve parsing error when uploading files

Beispiel für einen Commit zur Einführung einer neuen Funktion:

feat: add darkmode

Beispiel für einen Commit mit einer Breaking Change:

feat: replace obsolote API with new implementation

BREAKING CHANGE: replaced `getUserData` method with `fetchUserData` to allow more efficient data retrieval.

Beispiel für einen Commit mit einem optionalen Bereich (scope):

feat(api): implement endpoint to retrieve user profiles

Beispiel für einen Commit mit einer Fußzeile:

chore: update dependencies

Update dependencies to fix security vulnerabilities

closes #123

Mehr Informationen zu Conventional Commits und den genauen Regeln gibt es hier.

Integration in einem Next.js Projekt

Nun möchte ich diese beiden Tools in einem Next.js Projekt integrieren. Dafür erstelle ich ein neues Next.js Projekt und lege ein neues Github Repository an. Anschließend installiere ich die Dependencies und konfiguriere die Tools. Zum Schluss erstelle ich noch eine Github Action, die die Veröffentlichung automatisch durchführt.

Repository anlegen

Der erste Schritt besteht darin, ein leeres Repository in Github anzulegen. Dazu wie folgt vorgehen:

  1. Github besuchen und anmelden.
  2. “+”-Button in der oberen rechten Ecke anklicken und “New repository” auswählen.
  3. Einen Namen für das Repository eingeben und optional eine Beschreibung einfügen.
  4. Auswählen, ob das Repository öffentlich oder privat sein soll.
  5. Auf “Create repository” klicken, um das leere Repository zu erstellen.

Neues Repository

Nachdem das Repository erfolgreich erstellt ist, können wir mit der Einrichtung des Next.js-Projekts fortfahren.

Next.js Projekt erstellen

Um ein neues Next.js-Projekt zu erstellen, können folgende Schritte ausgeführt werden:

  1. Terminal öffnen und zum Verzeichnis wechseln, in dem das Projekt erstellt werden soll.
  2. Folgenden Befehl ausführen, um ein neues Next.js-Projekt zu erstellen:
npx create-next-app next-semantic-release

Repository als Remote hinzufügen

Danach navigiere ich in das Projektverzeichnis in einem Terminal und wechsle meine Branch auf main.

git branch -M main

Jetzt füge ich das Github Repository als Remote hinzu.

git remote add origin https://github.com/mein-benutzername/mein-repository.git

In VS Code sollte es nun so aussehen:

Remote hinzufügen

Unten sehe ich “main” als aktuelle Branch und als Remote meine Github Repository.

Jetzt kann ich den Code in Github pushen.

git push -u origin main

Jetzt sollte ich in Github den Code sehen können.

Code in Github

Dependencies installieren

Als Nächstes installiere ich die Dependencies für Semantic Release und Commitizen.

npm install --save-dev semantic-release @semantic-release/changelog @semantic-release/git @commitlint/cli @commitlint/config-conventional @commitlint/cz-commitlint commitizen cz-conventional-changelog

Dieser Befehl wird die benötigten Pakete herunterladen und in der package.json Datei speichern. Hier eine krurze Erklärung der einzelnen Pakete:

  • semantic-release: Dieses Paket ermöglicht automatische Releases basierend auf den Commit-Nachrichten.
  • @semantic-release/changelog: Dieses Paket generiert automatisch ein Changelog für jede Veröffentlichung.
  • @semantic-release/git: Dieses Paket ermöglicht das automatische Hinzufügen von Änderungen zum Git-Repository während des Release-Prozesses.
  • @commitlint/cli: Dieses Paket stellt eine CLI für das Validieren von Commit-Nachrichten gemäß den Commitlint-Regeln bereit.
  • @commitlint/config-conventional: Dieses Paket enthält die konventionellen Commitlint-Regeln für die Validierung von Commit-Nachrichten.
  • @commitlint/cz-commitlint: Dieses Paket integriert Commitlint mit Commitizen, um die Validierung von Commit-Nachrichten während des Commit-Prozesses zu ermöglichen.
  • commitizen: Dieses Paket bietet eine interaktive Benutzeroberfläche für das Erstellen von standardisierten Commit-Nachrichten.
  • cz-conventional-changelog: Dieses Paket stellt die konventionellen Commitizen-Vorlagen bereit, die den Commit-Nachrichten eine standardisierte Struktur geben.

Package.json anpassen

In der package.json füge ich folgende Zeilen hinzu und ändere einige Werte:

  • Unter “version” ändere ich den Wert von “0.1.0” auf “0.0.0-development”.
  • Unter “scripts” füge ich zwei weitere Skripte hinzu: “semantic-release”, um Semantic Release auszuführen, und “commit”, um Commitizen auszuführen.
  • Im “config” Abschnitt gebe ich den Pfad zu Commitizen an.
  • Schließlich füge ich ein “release” Objekt hinzu, in dem die Plugins für Semantic Release angegeben werden. Es ist wichtig, die Branches anzugeben, für die Veröffentlichungen durchgeführt werden sollen. In meinem Fall ist das der main-Branch. Außerdem gebe ich an, dass die Veröffentlichungen nicht auf npm veröffentlicht werden sollen. Das mache ich, weil ich das Projekt nicht als npm-Paket veröffentlichen möchte.

Insgesamt sieht die package.json dann ungefähr so aus:

{
	"name": "next-semantic-release",
	"version": "0.0.0-development",
	"private": true,
	"scripts": {
		"dev": "next dev",
		"build": "next build",
		"start": "next start",
		"lint": "next lint",
		"semantic-release": "semantic-release",
		"commit": "cz"
	},
	"dependencies": {
		//[...]
	},
	"devDependencies": {
		"@commitlint/cli": "^17.7.1",
		"@commitlint/config-conventional": "^17.7.0",
		"@commitlint/cz-commitlint": "^17.7.1",
		"@semantic-release/changelog": "^6.0.3",
		"@semantic-release/git": "^10.0.1",
		"commitizen": "^4.3.0",
		"cz-conventional-changelog": "^3.3.0",
		"semantic-release": "^21.1.1"
	},
	"repository": {
		"type": "git",
		"url": "https://meine-repository-url.git"
	},
	"publishConfig": {
		"access": "restricted"
	},
	"config": {
		"commitizen": {
			"path": "./node_modules/cz-conventional-changelog"
		}
	},
	"release": {
		"branches": ["main"],
		"plugins": [
			"@semantic-release/commit-analyzer",
			"@semantic-release/release-notes-generator",
			"@semantic-release/changelog",
			[
				"@semantic-release/npm",
				{
					"npmPublish": false
				}
			],
			"@semantic-release/git",
			"@semantic-release/github"
		]
	}
}

Commitizen konfigurieren

Bei der Konfiguration von Commitizen gibt es verschiedene Möglichkeiten und Erweiterungen. Eine gängige Konvention für Commitizen sind die sogenannten “konventionellen Commits”. Dabei wird der Commit in verschiedene Kategorien unterteilt. Hier sind einige der gängigen Kategorien:

  • fix: Bugfix
  • feat: Neue Funktion
  • BREAKING CHANGE: Änderung, die die bestehende Funktionalität bricht
  • docs: Änderungen in der Dokumentation
  • style: Änderungen, die keinen Einfluss auf den Code haben (z.B. Leerzeichen, Formatierung, Semikolons)
  • refactor: Code-Änderungen, die weder einen Bug beheben noch eine Funktion hinzufügen
  • perf: Code-Änderungen, die die Performance verbessern
  • test: Hinzufügen oder Ändern von Tests
  • build: Änderungen, die das Build-System oder externe Abhängigkeiten betreffen
  • ci: Änderungen an der CI-Konfiguration oder Skripten
  • chore: Sonstige Änderungen, die nicht in die anderen Kategorien passen

Manchmal ist es sinnvoll, die Kategorien zu erweitern. Zum Beispiel könnte die Kategorie deps für Updates der node_modules genutzt werden, wenn man diese nicht in build oder chore einsortieren möchte. Wenn ich Webseiten erstelle und der Inhalt teilweise oder vollständig im Repository liegt, hätte ich gerne eine eigene Kategorie für content. Die Kategorie revert ist für das Rückgängigmachen von Commits gedacht.

Derzeit (Version 3.3.0) gibt es in Commitizen keine eingebaute Möglichkeit, neue Kategorien hinzuzufügen, ohne die vorhandenen zu überschreiben. Legt man Types fest, werden die ursprünglichen Kategorien überschrieben. Ein offener Pull Request zu diesem Thema wurde bisher nicht übernommen.

In dieser Situation gibt es zwei Möglichkeiten. Man kann bei den vorhandenen Kategorien bleiben und den scope-Parameter verwenden, um Änderungen genauer zu kategorisieren (z. B. chore(content): aktualisiere die Inhalte) oder man kann die Kategorien, die man nicht verlieren möchte, noch einmal auflisten:

package.json:

{
	//[...]
	"config": {
		"commitizen": {
			"path": "./node_modules/cz-conventional-changelog",
			"types": {
				"feat": {
					"description": "A new feature",
					"title": "Features"
				},
				"fix": {
					"description": "A bug fix",
					"title": "Bug Fixes"
				},
				"docs": {
					"description": "Documentation only changes",
					"title": "Documentation"
				},
				"style": {
					"description": "Changes that do not affect the meaning of the code (white-space, formatting, missing semi-colons, etc)",
					"title": "Styles"
				},
				"refactor": {
					"description": "A code change that neither fixes a bug nor adds a feature",
					"title": "Code Refactoring"
				},
				"perf": {
					"description": "A code change that improves performance",
					"title": "Performance Improvements"
				},
				"test": {
					"description": "Adding missing tests or correcting existing tests",
					"title": "Tests"
				},
				"build": {
					"description": "Changes that affect the build system or external dependencies (example scopes: gulp, broccoli, npm)",
					"title": "Builds"
				},
				"ci": {
					"description": "Changes to our CI configuration files and scripts (example scopes: Travis, Circle, BrowserStack, SauceLabs)",
					"title": "Continuous Integrations"
				},
				"deps": {
					"description": "Updates to dependencies",
					"title": "Dependencies"
				},
				"content": {
					"description": "Changes that affect the content of the site",
					"title": "Content"
				},
				"chore": {
					"description": "Other changes that don't modify src or test files",
					"title": "Chores"
				},
				"revert": {
					"description": "Reverts a previous commit",
					"title": "Reverts"
				}
			}
		}
	}
	//[...]
}

Wenn ich nun npm run commit ausführe, wird mir die Auswahl der Kategorien angezeigt.

Commitizen

Semantic Release konfigurieren

Auch für Semantic Release gibt es einige Einstellmöglichkeiten. Interessant sind hier insbesondere die Branches und das Plugin Commit Analyzer.

Branches

In der package.json habe ich bereits die Branches angegeben, die veröffentlicht werden sollen. Hier kann ich auch angeben, dass nur bestimmte Branches veröffentlicht werden sollen. In meinem Fall ist das der main Branch.

{
	//[...]
	"release": {
		"branches": ["main"]
		//[...]
	}
	//[...]
}

Man kann natürlich auch mehrere Branches für eine Veröffentlichung angeben.

{
	//[...]
	"release": {
		"branches": [
			"+([0-9])?(.{+([0-9]),x}).x",
			"main",
			"next",
			"next-major",
			{ "name": "beta", "prerelease": true },
			{ "name": "alpha", "prerelease": true }
		]
		//[...]
	}
	//[...]
}

Hier werden alle Branches veröffentlicht, die mit einer Zahl beginnen. Also z. B. 1.0.0, 2.0.0 usw. Außerdem werden die Branches main, next, next-major, beta und alpha veröffentlicht. Die Branches beta und alpha werden als Prerelease veröffentlicht.

Commit Analyzer

Das Plugin “Commit Analyzer” in Semantic Release analysiert die Commit-Nachrichten, um die nächste Version zu ermitteln. Dabei werden die Kategorien der Commits berücksichtigt. Je nach Kategorie wird die Versionsnummer entsprechend erhöht:

  • Wenn ein Commit mit der Kategorie “fix” erstellt wird, erhöht sich die Patch-Version. Dies deutet auf einen Commit hin, der einen Fehler behebt, ohne dabei neue Funktionen einzuführen oder bestehende Funktionalitäten zu brechen.
  • Wenn ein Commit mit der Kategorie “feat” erstellt wird, erhöht sich die Minor-Version. Dies bedeutet, dass eine neue Funktion hinzugefügt wurde, ohne dass dabei bestehende Funktionalitäten beeinflusst oder gebrochen wurden.
  • Wenn ein Commit den Zusatz “BREAKING CHANGE” enthält, erhöht sich die Major-Version. Dies weist darauf hin, dass eine Änderung vorgenommen wurde, die bestehende Funktionalitäten bricht und eventuell Anpassungen erfordert.

Dieses Verhalten kann angepasst werden:

{
	//[...]
	"release": {
		"branches": ["main"],
		"plugins": [
			[
				"@semantic-release/commit-analyzer",
				{
					"releaseRules": [
						{ "type": "deps", "release": "patch" },
						{ "scope": "no-release", "release": false }
					]
				}
			]
			//[...]
		]
	}
	//[...]
}

Hier wird zusätzlich die Patch-Version erhöht, wenn die Kategorie deps verwendet wird. Außerdem wird die Version nicht erhöht, wenn bei Scope no-release angegeben wird (z. B. fix(no-release): ...). Jedoch kann dieses Verhalten zu einem unerwünschten Ergebnis führen. Wenn man ein “breaking change” mit der Kategorie “deps” veröffentlicht, wird das als “Patch-Release” behandelt. Möchte man auch hier ein Major-Release, muss man noch eine Zeile zur Konfiguration hinzufügen:

{
  //[...]
  "release": {
    "branches": ["main"],
    "plugins": [
      [
        "@semantic-release/commit-analyzer",
        {
          "releaseRules": [
            { "breaking": true, "release": "major"}
            { "type": "deps", "release": "patch" },
            { "scope": "no-release", "release": false }
          ]
        }
      ]
      //[...]
    ]
  }
  //[...]
}

Github Action erstellen

Als Letztes muss ich nur noch dafür sorgen, dass Github die Veröffentlichung automatisch durchführt.

Zunächst gehe ich in die Einstellungen meines Repositorys und wähle den Punkt “Actions” und dann “General” aus. Hier ändere ich die “Workflow permissions” auf “Read and write permissions”.

Workflow permissions

Als Nächstes erstelle ich eine neue Datei .github/workflows/release.yml und füge folgenden Inhalt ein:

name: Release

on:
    push:
        branches:
            - main

permissions:
    contents: read

jobs:
    release:
        name: Release
        runs-on: ubuntu-latest
        permissions:
            contents: write
            issues: write
            pull-requests: write
            id-token: write

        steps:
            - name: Checkout code
              uses: actions/checkout@v3
              with:
                  fetch-depth: 0

            - name: Setup Node.js
              uses: actions/setup-node@v3
              with:
                  node-version: 18

            - name: Install dependencies
              run: npm clean-install

            - name: Build Next app
              run: npm run build

            - name: Semantic Release
              env:
                  GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
              run: npx semantic-release

Hier wird die Veröffentlichung nur für den main Branch ausgeführt. Zudem wird die Github Action nur ausgeführt, wenn der Code in Github gepusht wird. Wenn ich lokal einen Commit erstelle und pushe, wird die Github Action nicht ausgeführt. Nur, wenn der Build-Prozess erfolgreich ist, wird Semantic Release ausgeführt.

Test

Nun wird es Zeit für den ersten Commit.

git add .
npm run commit

Ich wähle die Kategorie “feat” aus und gebe eine kurze Beschreibung ein.

git push
# oder genauer
git push -u origin main

Jetzt sehe ich in Github, dass mein Repository drei Commits enthält. Außerdem wurde ein Release erstellt und CHANGELOG.md-Dokument generiert.

Release

Commits

Möchte man semantic-release lokal verwenden, kann man das mit dem Befehl npm run semantic-release. Hierbei ist zu beachten, dass ein GITHUB_TOKEN erstellt und in der .env Datei gespeichert werden muss. Dieser Token muss dann auch als Secret in Github hinterlegt werden.

Es ist wichtig darauf zu achten, dass der Token nicht versehentlich veröffentlicht wird. In meinem Fall habe ich den Token in der .env Datei hinterlegt und diese Datei in der .gitignore Datei eingetragen. So wird die Datei nicht in Github veröffentlicht.

Zusammenfassung der Vorteile von automatischen Releases

Die automatische Generierung von Releases mithilfe von Tools wie Commitizen und Semantic-release bietet eine Vielzahl von Vorteilen.

  1. Automatisierter Prozess: Durch die Verwendung von Commitizen und Semantic-release wird der gesamte Prozess der Release-Erstellung automatisiert. Neue Versionen werden basierend auf den definierten Regeln direkt aus dem Code-Repository generiert.

  2. Klare und konsistente Commit-Meldungen: Commitizen stellt sicher, dass die Commit-Nachrichten klar, konsistent und gemäß den Conventional Commits-Spezifikationen strukturiert sind. Dadurch wird die Verwaltung von Softwareversionen vereinfacht und Änderungen lassen sich leichter nachverfolgen.

  3. Automatische Versionsinkrementierung: Semantic-release analysiert die Commit-Meldungen und erhöht die Versionsnummern automatisch entsprechend der Art der Änderungen. Dadurch wird sichergestellt, dass die Versionsnummern korrekt und konsistent sind.

  4. Generierung von Änderungsprotokollen: Semantic-release erstellt automatisch Änderungsprotokolle (Changelogs) basierend auf den Commit-Meldungen. Dadurch können Entwickler und Benutzer die durchgeführten Änderungen leichter nachvollziehen.

  5. Verbesserte Softwarequalität: Durch die Automatisierung des Release-Prozesses wird die Bereitstellung neuer Funktionen beschleunigt und gleichzeitig sichergestellt, dass der Betrieb reibungslos läuft. Dies trägt zur Verbesserung der Softwarequalität bei und minimiert mögliche Fehler, die bei manuellen Prozessen auftreten könnten.

Insgesamt bieten Commitizen und Semantic-release einen strukturierten, zuverlässigen und effizienten Ansatz zur Verwaltung von Software-Releases. Sie ermöglichen eine präzisere Versionsverwaltung, beschleunigen den Entwicklungsprozess und tragen zur Steigerung der Softwarequalität bei.

Bei Anmerkungen kann gerne ein Kommentar hinterlassen werden. Ich freue mich über Feedback.


Diese Website verwendet Cookies. Diese sind notwendig, um die Funktionalität der Website zu gewährleisten. Weitere Informationen finden Sie in der Datenschutzerklärung