CI/CD mit SharePoint Framework

von Roland Rickborn
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:

  1. bump-version-job
  2. unit-test-job
  3. build-job
  4. upload-sbom-job
  5. fetch-metrics-job
  6. package-artefacts-job
  7. release-job

Die einzelnen Schritte und ihre Jobs werden in der folgenden Grafik veranschaulicht:

Visualisierung der Pipeline in GitLab

Endlosschleife verhindern bei CI/CD-Pipeline mit SharePoint Framework

Der Schritt bump-version-job hat zwei Aufgaben:

  1. Verhindern einer Endlosschleife und
  2. 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!

Zurück

© 2006-2026 exensio GmbH
Einstellungen gespeichert
Datenschutzeinstellungen

Wir nutzen Cookies auf unserer Website. Einige von ihnen sind essenziell, während andere uns helfen, diese Website und Ihre Erfahrung zu verbessern.

Sie können Ihre Einwilligung jederzeit ändern oder widerrufen, indem Sie auf den Link in der Datenschutzerklärung klicken.

Zu den gesetzlichen Rechenschaftspflichten gehört die Einwilligung (Opt-In) zu protokollieren und archivieren. Aus diesem Grund wird Ihre Opt-In Entscheidung in eine LOG-Datei geschrieben. In dieser Datei werden folgende Daten gespeichert:

 

  • IP-Adresse des Besuchers
  • Vom Besucher gewählte Datenschutzeinstellung (Privacy Level)
  • Datum und Zeit des Speicherns
  • Domain
×
Irving Tschepke
Irving Tschepke
Dipl.-Wirtsch.-Ing.
Peter Soth
Peter Soth
Dipl.-Inform. (FH)
Wir antworten in wenigen Stunden.
Oder rufen Sie einfach an:
×
Irving Tschepke
Irving Tschepke
Dipl.-Wirtsch.-Ing.
Peter Soth
Peter Soth
Dipl.-Inform. (FH)
Wir antworten in wenigen Stunden.
Oder rufen Sie einfach an:
You are using an outdated browser. The website may not be displayed correctly. Close