CI/CD mit SharePoint Framework
In dieser Blogpost-Serie beschreibe ich meine Vorstellung der idealen CI/CD-Pipeline einer Adaptive Card Extension (ACE) für Viva Connections mit Hilfe des SharePoint Frameworks (SPFx) und unter Verwendung von Dependency-Track für Continuous Transparency. Der erste Teil der Serie befasst sich mit der automatisierten Aktualisierung von Abhängigkeiten. Im zweiten Teil geht es dann um die Verwaltung von Abhängigkeiten und die Auslieferung.
Warum es Sinn macht, jetzt über CI/CD bei SharePoint Framework nachzudenken
Im Dezember 2025 wurde Version 1.22 des SharePoint Frameworks SPFx veröffentlicht. Bei diesem Release ersetzt Microsoft die alte Gulp-basierte Toolchain durch Heft [1]. Durch die Umstellung wird zwar eine Migration Ihres Projektes erforderlich, ein passendes Migrations-Skript finden Sie z. B. hier [2], aber die Vorteile von Version 1.22 überwiegen! Durch die Einführung der Heft-basierten Toolchain verringern sich die Abhängigkeiten und damit sinkt die Anzahl der Schwachstellen drastisch. Die Heft-basierte Toolchain ist außerdem sehr einfach und sehr genau an eigene Bedürfnisse anpassbar. All diese Tatsachen erlauben es erstmals, ernsthaft über den Einsatz von CI/CD im Projekt nachzudenken, finde ich!
Randbedingungen von CI/CD bei SharePoint Framework
Zu meiner Vorstellung einer idealen CI/CD-Pipeline für ein SPFx Projekt gehört die vollständige Automation der Pipeline, vom Bau und der Überprüfung der Abhängigkeiten, über die Tests bis zum Deployment im zentralen SharePoint App-Katalog. Außerdem sollte sich die Pipeline selbstständig erneuern, also selbstständig und automatisch nach Updates suchen, diese testweise importieren und bei erfolgreichen Tests dann auch automatisch in den Hauptzweig des Projekts einfügen. Komplettiert wird die Pipeline durch ein Schwachstellen Management, das den gesamten Lebenszyklus der Anwendung überdeckt und aktiv bei hohem Risiko warnt.
Beispielprojekt mit SharePoint Framework und CI/CD
Zu Test- und Demonstrations-Zwecken habe ich eine ACE erstellt, die den Einsatz von verschiedenen Technologien demonstriert. Die Dashboard-Karte, bzw. deren QuickView, verwendet natürlich das deklarative JSON-Schema, zeigt aber auch statische HTML-Inhalte und enthält React-Komponenten. Außerdem verwendet sie die Standortfunktionen aus SPFx v1.15 [3]. Für meine Tests verwende ich das JavaScript Testing Framework Jest [4], das erfreulicherweise jetzt auch bei der neuen Heft-Toolchain von SPFx zum Einsatz kommt!
Aktualisierung von Abhängigkeiten bei SharePoint Framework mit CI/CD-Pipeline
Zwar sorgte die Einführung der Heft-basierten Toolchain bei SPFx für eine umfangreiche Reduzierung der Anzahl der Schwachstellen (im Beispielprojekt von ca. 200 auf < 5), dennoch bleibt die Anzahl der Abhängigkeiten sehr hoch. In meinem Beispielprojekt liegt sie bei über 1300 Komponenten! Deshalb muss die ideale CI/CD-Pipeline sich selbst um die Aktualisierung von Abhängigkeiten kümmern. In meinem Beispielprojekt kommt dafür Renovate [5] zum Einsatz. Renovate ist dabei so eingerichtet, dass Entwicklungs-Abhängigkeiten (devDependencies) automatisch in den Hauptzweig eingespielt werden. Ggf. wird diese Option später auch für die eigentlichen Abhängigkeiten übernommen. Nachfolgend finden Sie die verwendete renovate.json:
{
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
"extends": [
"config:recommended",
":prConcurrentLimitNone",
":prHourlyLimitNone"
],
"baseBranches": [
"main"
],
"rangeStrategy": "bump",
"labels": [
"renovate"
],
"commitMessagePrefix": "RenovateMergeRequest: ",
"packageRules": [
{
"matchDepTypes": [
"devDependencies"
],
"automerge": true,
"automergeType": "pr",
"matchUpdateTypes": [
"minor",
"patch"
],
"platformAutomerge": true
},
{
"matchDatasources": [
"npm"
],
"dependencyDashboard": true
},
{
"matchPackageNames": [
"cheerio"
],
"allowedVersions": "1.0.0-rc.10",
"description": "Do not update cheerio, keep at 1.0.0-rc.10"
},
{
"matchPackageNames": [
"react",
"react-dom"
],
"allowedVersions": "<=17.0.1",
"description": "SPFx v1.22.1 supports React 17.0.1 only"
},
{
"extends": [
"monorepo:spfx"
],
"groupName": "spfx monorepo",
"matchUpdateTypes": [
"digest",
"patch",
"minor",
"major"
]
},
{
"groupName": "react monorepo",
"matchPackageNames": [
"@types/react",
"@types/react-dom",
"@types/react-is"
]
},
{
"extends": [
"monorepo:jest"
],
"groupName": "jest monorepo",
"matchUpdateTypes": [
"digest",
"patch",
"minor",
"major"
]
},
{
"matchPackageNames": [
"eslint",
"eslint-config-next"
],
"groupName": "eslint packages",
"rangeStrategy": "bump"
},
{
"matchPackageNames": [
"node"
],
"matchDatasources": [
"node-version"
],
"allowedVersions": "22.x",
"description": "Only allow Node.js v22",
"groupName": "node package",
"rangeStrategy": "bump"
},
{
"matchPackageNames": [
"typescript",
"@types/node",
"@types/react",
"@types/react-dom"
],
"groupName": "typescript-related packages",
"rangeStrategy": "bump"
}
]
}
Alle durch Renovate erstellten Merge Requests enthalten das Schlüsselwort RenovateMergeRequest in der Commit Nachricht. Aktualisierungen der devDependencies dürfen automatisch übernommen werden (automergeType), sofern Tests und der Bau der Anwendung erfolgreich waren. Die weiteren Regeln im Abschnitt packageRules basieren auf vergangenen Merge Requests, die fehl schlugen. Dadurch kenne ich bereits feste Abhängigkeiten zwischen einzelnen Paketen (z. B. der Pakete react und react-dom) oder möchte Renovate anweißen, bestimmte Pakete (z. B. alle SPFx-Pakete) als ein einziges Paket zu behandeln (sog. monorepo). In dieser renovate.json Konfigurationsdatei nicht hinterlegt ist die zentrale Konfiguration für alle Repos, die Renovate einbinden (z. B. der minimumReleaseAge, also wie lange Renovate wartet, bis es ein Update für einen Merge Request in Betracht zieht).
Aufbau der CI/CD-Pipeline mit SharePoint Framework
Das Beispielprojekt verwalte ich mit GitLab [6], das auch als Build-Server verwendet wird. Die Pipeline hat folgenden Aufbau:
- bump-version-job
- unit-test-job
- build-job
- upload-sbom-job
- fetch-metrics-job
- package-artefacts-job
- release-job
Die einzelnen Schritte und ihre Jobs werden in der folgenden Grafik veranschaulicht:

Endlosschleife verhindern bei CI/CD-Pipeline mit SharePoint Framework
Der Schritt bump-version-job hat zwei Aufgaben:
- Verhindern einer Endlosschleife und
- Eindeutige Versionsnummern sicherstellen
Durch den Einsatz von Renovate mit der Option automerge kommt es zu Commits im Hauptzweig des Repositorys. Dabei wird eine eindeutige Versionsnummer vergeben (version bump). Die neue Versionsnummer wird ihrerseits im Repository gespeichert, was einen erneuten Commit darstellt.
Da jeder Commit einen erneuten Start der Pipeline bewirkt, würde es zu einer Endlosschleife kommen. Der Schritt bump-version-job überprüft daher, ob es sich um einen Version-Bump-Commit handelt und stoppt die Pipeline in diesem Fall.
Wenn es sich beim aktuellen Commit nicht um einen Version-Bump-Commit handelt, überprüft die Pipeline, ob es sich um einen Renovate-Commit handelt. In diesem Fall würde die Version der Anwendung erhöht, was zu einem Bug-Fix Release führt. Zusätzlich wird das Kommando heft sync-version ausgeführt. Dabei handelt es sich um eine Anpassung der Heft-basierten Toolchain [7], die dafür sorgt, dass die Versionsnummer aus package.json auch als Version in die Datei config/package-solution.json übernommen wird. Damit ist projektweit eine einheitliche Versionsnummerierung sichergestellt, was für zusätzliche Transparenz sorgt.
Wenn es sich beim aktuellen Commit weder um einen Version-Bump-Commit, noch um einen Renovate-Commit handelt, dann wird der Schritt bump-version-job einfach übersprungen.
bump-version-job:
stage: version
rules:
- if: '$CI_COMMIT_MESSAGE =~ /bump version/'
when: never
- if: $CI_COMMIT_BRANCH == "main"
before_script:
- npm install -g @rushstack/heft
- git config user.name "gitlab-ci"
- git config user.email "ci@gitlab"
- git fetch origin main
script:
- |
COMMIT_MSG=$(git log -1 --pretty=%B)
echo "Commit message:"
echo "$COMMIT_MSG"
if echo "$COMMIT_MSG" | grep -q "RenovateMergeRequest"; then
echo "✅ Renovate merge detected - bumping version"
npm ci
npm version patch --no-git-tag-version
NEW_VERSION=$(node -p "require('./package.json').version")
echo "Bumped to version $NEW_VERSION"
echo "$NEW_VERSION" > VERSION
heft sync-version
git add package.json package-lock.json config/package-solution.json || true
git commit -m "chore(release): bump version to $NEW_VERSION"
git push origin HEAD:main
else
echo "ℹ️ Not a Renovate commit - skipping version bump"
export VERSION_NUMBER=$(node -p "require('./package.json').version")
echo "$VERSION_NUMBER" > VERSION
fi
artifacts:
paths:
- VERSION
when: always
expire_in: 14 days
Testen und bauen bei CI/CD-Pipeline mit SharePoint Framework
Die beiden nächsten Schritte, unit-test-job und build-job, sind relativ einfach und wurden anderswo bereits häufig dokumentiert. Beim unit-test-job ist vielleicht zu erwähnen, dass SPFx v1.22 von Microsoft direkt mit Jest Plugin bereitgestellt wird. Die Job-Konfiguration in der .gitlab-ci.yml ist entsprechend kurz:
unit-test-job:
stage: test
rules:
- if: $CI_COMMIT_BRANCH == "main"
before_script:
- node -v
- npm install -g @rushstack/heft
- npm ci
script:
- npm test
coverage: /All files[^\n]*\n[^\n]*\s+\(([0-9.]+)\)/
artifacts:
paths:
- jest/
- junit.xml
when: always
reports:
junit:
- junit.xml
expire_in: 14 days
Beim Schritt build-job besteht die einzige Besonderheit in der Erzeugung der Datei VERSION (die den aktuellen Versionsstring enthält) und einer aktuellen Software-Stückliste (SBOM) [8] der Anwendung, im Format CycloneDX (JSON). Die Job-Konfiguration ist:
build-job:
stage: build
rules:
- if: $CI_COMMIT_BRANCH == "main"
needs:
- job: bump-version-job
artifacts: true
before_script:
- node -v
- npm install -g @rushstack/heft
- npm install -g @cyclonedx/cyclonedx-npm
- npm ci
script:
- heft clean
- heft build --clean
- heft package-solution --production
- cyclonedx-npm --ignore-npm-errors --gather-license-texts --output-file $SBOM_FILE
artifacts:
paths:
- sharepoint/solution/hello-world.sppkg
- $SBOM_FILE
when: always
expire_in: 14 days
Zusammenfassung und Ausblick
In diesem Teil der Blogpost-Serie habe ich meine Vorstellung einer idealen Pipeline vorgestellt. Ich habe beschrieben, weshalb es (erst) jetzt Sinn macht, bei SPFx-Projekten über CI/CD nachzudenken, wie ich die Aktualisierung meines Projekts durch die Pipeline sicherstelle, wie die Pipeline aufgebaut ist und wie die ersten Schritte der Pipeline funktionieren.
Im zweiten Teil der Blogpost-Serie beschreibe ich die weiteren Schritte der Pipeline, also insbesondere die Verwaltung der Abhängigkeiten und das Release-Bundle. Für diese Schritte wird auch die SBOM-Datei verwendet, die im zuvor vorgestellten Schritt build-job erstellt wurde.
Seien Sie gespannt!
Links
[1] SharePoint Framework Toolchain: Heft & Webpack
[2] upgrade-spfx-project Overview
[3] Location capabilities in Adaptive Card Extension
[4] JavaScript Testing Framework JEST
[5] Renovate