Skip to content

Apply a DevOps process to your SPFx project (Part 3)

Goal of this article (reminder)

This article series will show how to apply a DevOps process by using tools like Azure DevOps or GitHub Actions, using multi-stage / environment pipelines to deploy on environments such as UAT, in order to validate an improvment or a hotfix before going to production.

The third part will show how to implement technical solutions by using CI / CD pipeline templates.

Scenarios context

When working on enterprise solutions, we are always facing the question of "testing" before pushing on production environment, to ensure that new features or fixes are working as expected.

Thanks to the site collection app catalog, we have this opportunity (for the use cases exposed in the first part)!

We saw previously two major scenarios that can be applied in a company as a DevOps process. This part will talk about applying those scenarios with two well-known CI / CD solutions: GitHub Actions & Azure DevOps (AzDO).

In this article, we'll focus on the scenario where a testing site already exists.

CI / CD pipelines digging

For both solutions, we will use "pipeline" (Azure DevOps) / "workflow" (GitHub Actions) templates. This will provide us guidance on how to reuse and adapt them to our business context.

Also, we have to "setup" (that's a quick word) the host that will run our pipeline. Think of this host as a "disposable" / ephemeral machine that is configured only to run specific tasks, then disapear. A host is required for a pipeline / workflow, so we will have at least two hosts needed: one for the CI, one for the CD and each of them will be independant.

It's important to keep that in mind for the rest of this article 😉.

Our SPFx solution will have a "main" pipeline, which will call our templates.

Templates structure

Below we'll find a schema for each platform, to understand how the templates are organized.

DevOps SPFx templates AzDO

  1. A generic pipeline (azure-pipelines.yml) will be triggered as the initiator
  2. It will call a global CI / CD template (build-deploy-spfx-cli-m365.yml), that will call subsequently

DevOps SPFx templates GitHub Actions

  1. A generic pipeline (main.yml) will be trigger as the initiator that will call subsequently

Security

If order to deploy securely our SPFx solution, we have to register credentials (certificate authentication) per environment (remember that we want to deploy first on UAT, then on PRD). Here are the main approaches for the two platforms.

DevOps SPFx templates AzDO

  1. Using secret variables, linked to environments through pipeline template (with permissions), to keep sensitive information safe
  2. Using secure file storage to hold certificate file, linked to environments through pipeline template (with permissions), used for authentication

DevOps SPFx templates GitHub Actions

  1. Using secret variables, linked to environments, to keep sensitive information

As we can see, there's no secure file storage available in GitHub Actions, but we'll see a little bit later that we can afford that 😁

(in the next part and for the AzDO only, we will skip the in-between template build-deploy-spfx-cli-m365.yml, in order to focus on the CI / CD)

Authentication with Azure AD

Whether we choose to use GitHub Actions, AzDO as the CI / CD platform, and PnP PowerShell or CLI for Microsoft 365 as the ALM tool, we'll have to authenticate to SharePolint Online to deploy our solution. As this can't be done with our own account in a DevOps philosophy, it's better to do so with a dedicated authentication, meaning application context, which is provided by Azure AD Applications.

So to be sure to authenticate correctly and to give the rights keys (as an Ops remember 👀), the AAD application will have to be configured with the following info:

  • Certificates & secrets
  • Add a certificate file (.pem, crt or .cer), for which we'll have the thumbprint info and encoded file or .pfx file (depending on the CI / CD platform)
  • API permission
  • Add SharePoint application permission "Sites.FullControl.All"

Continuous Integration

In this section, we will cover following steps:

  • Configuring the trigger (what will be done to run the pipeline)
  • Specifying input parameters
  • Determine the host OS
  • Checking out our last repo sources
  • Setting NodeJS version (as a prerequisite to build our SPFx package)
  • Installing required NPM packages (as mentioned in the package.json file)
  • Bundling the solution
  • (optionally) testing the solution
  • Packaging the solution
  • Uploading it as an artefact, to make it available for the CD part

CI Triggers & Inputs

First thing is to specify what will make the pipeline run.

1
2
3
4
5
# No triggers here, which makes the pipeline not callable except from another pipeline
parameters:
  - name: include_tests
    type: boolean
    default: false
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
name: build

on:
  workflow_call: # only callable from another workflow
    inputs:
      include_tests:
        description: 'Indicates whether build job has to run tests or not'
        default: false
        required: false
        type: string

CI Jobs

Then specify the job details, the core of the pipeline which has an ephemeral state. First, the host OS:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
# ...
jobs:
  - job: build
    pool:
      vmImage: 'ubuntu-latest'
      demands:
      - npm
      - node.js
      variables:
        npm_config_cache: $(Pipeline.Workspace)/.npm
1
2
3
4
# ...
jobs:
  build: # job name here, can be anything else
    runs-on: ubuntu-latest

CI Steps

Now the details steps that will call all the tasks to complete the CI part.

Starting with the repo checkout, the node configuration and npm packages installation.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
# ...
jobs:
  - job: build
      # ...
      steps:
        - checkout: self

        - task: NodeTool@0
          displayName: 'Use Node 14.x'
          inputs:
          versionSpec: 14.x
          checkLatest: true

        - script: npm ci
          displayName: 'npm ci'
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
# ...
jobs:
  build:
    # ...
    steps:
      - name: Checkout
        uses: actions/checkout@v2

      - name: Use Node 14.x
        uses: actions/setup-node@v2
        with:
        node-version: '14'
        cache: 'npm'

      - name: npm ci
        run: npm ci

Then bundling the sources and testing it when input include_tests is set to true.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
# ...
jobs:
  - job: build
    # ...
    steps:
      # ...
      - task: Gulp@0
        displayName: 'Bundle project'
        inputs:
        targets: bundle
        arguments: '--ship'

      - ${{ if eq(parameters.include_tests, true) }}:
        - script: npm test
          displayName: 'npm test'
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
# ...
jobs:
  build:
    # ...
    steps:
      # ...
      - name: Bundle project
        run: gulp bundle --ship

      - name: npm test
        if: ${{ inputs.include_tests == true }}
        run: npm test

Last part is to package the solution, then make it available for the CD part.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
# ...
jobs:
  - job: build
    # ...
    steps:
      # ...
      - task: Gulp@0
        displayName: 'Package Solution'
        inputs:
          targets: 'package-solution'
          arguments: '--ship'

      - task: CopyFiles@2
        displayName: 'Copy Files to: $(Build.ArtifactStagingDirectory)'
        inputs:
          Contents: |
            sharepoint/**/*.sppkg
        TargetFolder: '$(Build.ArtifactStagingDirectory)'

      - task: PublishBuildArtifacts@1
        displayName: 'Publish Artifact: drop'
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
# ...
jobs:
  build:
    # ...
    steps:
    # ...
    - name: Package solution
      run: gulp package-solution --ship

    - name: Upload artifact for deployment job
      uses: actions/upload-artifact@v2
      with:
        path: sharepoint/solution/*.sppkg

Continuous Delivery

Now that we've completed ou CI / build pipeline, let's rollout the deployment process!

To make things "readable" in this post, we'll take the existing testing site scenario as an example.

Below the steps of the CD part:

  • Configuring the trigger (what will be done to run the pipeline)
  • Specifying input parameters
  • Determine the host OS
  • Getting the SPFx package uploaded in the CI pipeline as an artefact
  • Setting NodeJS version (as a prerequisite to build our SPFx package)
  • Installing the CLI for Microsoft 365
  • Authenticating to SharePoint Online using the CLI for Microsoft 365
  • For the UAT environment
    • Deploying the SPFx solution to the site dedicated to tests
  • For the PRD environment
    • Deploying the SPFx solution to the targeted and specified app catalog

Keep in mind that this pipeline will be used twice: UAT & PRD environments.

Warning

This use case states that the SPFx app is already installed on both testing and production environments

CD Triggers & Inputs

First thing is to specify what will make the pipeline run.

1
2
3
4
5
6
# No triggers here, which makes the pipeline not callable except from another pipeline
parameters:
  - name: target_environment
    type: string
  - name: variable_group_name
    type: string
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
name: deploy with cli m365

on:
workflow_call:
  inputs:
    environment_name:
      description: 'Target environment to deploy to'
      required: true
      type: string
    site_url_prd:
      description: 'SharePoint PRD site URL'
      required: false
      type: string
    site_url_uat:
      description: 'SharePoint UAT site URL'
      required: false
      type: string
    app_catalog_site_url:
      description: 'SharePoint app catalog site URL (tenant or site collection)'
      default: ""
      required: false
      type: string
    app_catalog_scope:
      description: 'Indicates the PRD app catalog scope (tenant or site collection)'
      default: ""
      required: false
      type: string
  secrets:
    AAD_APP_ID:
      required: true
    AAD_APP_PASSWORD:
      required: true
    AAD_APP_ENCODED_CERTIFICATE:
      required: true
    AAD_APP_THUMBPRINT:
      required: true
    AAD_TENANT_ID:
      required: true
    # Like this, we are getting environment secrets as inputs
    # Otherwise we'll get the repository secrets

CD Jobs

Then specify the job details, the core of the pipeline which has an ephemeral state. First, the host OS:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
# ...
jobs:
  - deployment: deploy
    displayName: 'Upload & deploy *.sppkg to SharePoint app catalog'
    pool:
      vmImage: 'ubuntu-latest'
    environment: ${{ parameters.target_environment }}
    variables:
      - group: ${{ parameters.variable_group_name }}
      # This is specific to AzDO: it allows to use variables / secrets stored in a "library"
      # It's very useful with multi-stage pipelines
1
2
3
4
5
# ...
jobs:
  deploy:
    runs-on: ubuntu-latest
    environment: ${{ inputs.environment_name }}

CD Steps

Now the details steps that will call all the tasks to complete the CD part.

Starting with getting the SPFx package, configuring node and installing the deployment tool (CLI for Microsoft 365 or PnP PowerShell).

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
# ...
jobs:
  deployment:
    # ...
    strategy:
      runOnce:
        deploy:
          steps:
            - download: current
              artifact: drop
              patterns: '**/*.sppkg'
            - pwsh: sudo npm install -g @pnp/cli-microsoft365
              displayName: Install CLI for Microsoft365
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
# ...
jobs:
  deploy:
    # ...
    steps:
    - name: Download artifact from build job
      uses: actions/download-artifact@v2

    - name: Use Node 16.x
      uses: actions/setup-node@v3
      with:
        node-version: '16'

    - name: Install CLI for Microsoft 365
      run: npm install -g @pnp/cli-microsoft365

Then authenticating to SharePoint Online, configure error handling and download the built SPFx package

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
# ...
jobs:
  deployment:
    # ...
    strategy:
      runOnce:
        deploy:
          steps:
            # ...
            - task: DownloadSecureFile@1
              inputs:
                secureFile: PnP.SharePoint.AppOnly.pfx
              displayName: 'Download authentication certificate'
              name: AppCertificate
              # Thanks to the AzDO pipeline files management, we can store secure files
              # Such as a certificate file
            - pwsh: |
                m365 login --authType certificate --certificateFile $(AppCertificate.secureFilePath) -p "$(aad_app_password)" --appId $(aad_app_id) --tenant $(aad_tenant_id)
              displayName: Login to Microsoft 365
            - pwsh: |
                m365 cli config set --key output --value json
                m365 cli config set --key errorOutput --value stdout
              displayName: Configuring CLI for Microsoft 365 output error handling
            - pwsh: |
                $package = Get-ChildItem -Path $(Pipeline.Workspace)/drop -Recurse -Filter '*.sppkg' | Select Name | Select-Object -First 1
                Write-Host "##vso[task.setvariable variable=SpPkgFileName;isOutput=true]$($package.Name)"
              name: GetSharePointPackage
              displayName: Get generated *.sppkg filename
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
# ...
jobs:
  deploy:
    # ...
    steps:
    # ...
    - name: Login to Microsoft 365
      shell: pwsh
      run: |
        m365 login --authType certificate --certificateBase64Encoded ${{ secrets.AAD_APP_ENCODED_CERTIFICATE }} --thumbprint ${{ secrets.AAD_APP_THUMBPRINT }} -p ${{ secrets.AAD_APP_PASSWORD }} --appId ${{ secrets.AAD_APP_ID }} --tenant ${{ secrets.AAD_TENANT_ID }}
      # Through GitHub environment secrets, we can store many info
      # Including a Base64 encoded certificate, useful for authentication  

    - name: Configuring CLI for Microsoft 365 output error handling
      shell: pwsh
      run: |
        m365 cli config set --key output --value json
        m365 cli config set --key errorOutput --value stdout

    - name: Get generated *.sppkg filename
      id: package
      shell: pwsh
      run: |
          $package = Get-ChildItem -Path artifact -Recurse -Filter '*.sppkg' | Select Name | Select-Object -First 1
          echo "::set-output name=name::$($package.Name)"

Next step is to add & deploy the SPFx package to the testing site, then in Production

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
# ...
jobs:
  deployment:
    # ...
    strategy:
      runOnce:
        deploy:
          # ...
          steps:
            # ...
            - ${{ if ne(parameters.target_environment, 'PRD') }}:
              - pwsh: |
                  $uri = [System.Uri]"$(site_url_uat)"
                  m365 spo set --url $($uri.Scheme + "://" + $uri.Authority)
                displayName: Set site collection context (${{ parameters.target_environment }})
              - pwsh: |
                  m365 spo app add --filePath "$(Pipeline.Workspace)/drop/sharepoint/solution/$(GetSharePointPackage.SpPkgFileName)" --appCatalogUrl $(site_url_uat) --scope sitecollection --overwrite
                name: SPOPackage
                displayName: Upload SharePoint package
              - pwsh: |
                  m365 spo app deploy --name $(GetSharePointPackage.SpPkgFileName) --appCatalogUrl $(site_url_uat) --scope sitecollection
                displayName: Deploy SharePoint package

            - ${{ if eq(parameters.target_environment, 'PRD') }}:
              - pwsh: |
                  $uri = [System.Uri]"$(site_url_prd)"
                  m365 spo set --url $($uri.Scheme + "://" + $uri.Authority)
                displayName: Set site collection context (${{ parameters.target_environment }})
              - pwsh: m365 spo app add --filePath "$(Pipeline.Workspace)/drop/sharepoint/solution/$(GetSharePointPackage.SpPkgFileName)" --appCatalogUrl $(app_catalog_site_url) --scope $(app_catalog_scope) --overwrite
                displayName: Upload SharePoint package to $(app_catalog_scope) App Catalog
              - pwsh: m365 spo app deploy --name $(GetSharePointPackage.SpPkgFileName) --appCatalogUrl $(app_catalog_site_url) --scope $(app_catalog_scope)
                displayName: Deploy SharePoint package
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
# ...
jobs:
  deploy:
    # ...
    steps:
      # ...
      - name: Get target site URL (${{ inputs.environment_name }})
        id: site
        shell: pwsh
        run: |
            $uri = [System.Uri]"${{ inputs.site_url_uat }}"

            if ('${{ inputs.environment_name }}' -eq 'PRD') {
              $uri = [System.Uri]"${{ inputs.site_url_prd }}"
            }

            echo "::set-output name=url::$uri"

      - name: Set site collection context (${{ inputs.environment_name }})
        shell: pwsh
        run: |
          $uri = "${{ steps.site.outputs.url }}"
          m365 spo set --url $uri

      - name: Upload SharePoint package to ${{ inputs.app_catalog_scope }} App Catalog (${{ inputs.environment_name }})
        shell: pwsh
        run: |
            $appCatalogScope = "sitecollection"
            $appCatalogUrl = "${{ inputs.site_url_uat }}"

            if ('${{ inputs.environment_name }}' -eq 'PRD') {
              $appCatalogScope = "${{ inputs.app_catalog_scope }}"
              $appCatalogUrl = "${{ inputs.site_url_prd }}"
            }

            m365 spo app add --filePath "artifact/${{ steps.package.outputs.name }}" --appCatalogUrl $appCatalogUrl --scope $appCatalogScope --overwrite

      - name: Deploy SharePoint package (${{ inputs.environment_name }})
        shell: pwsh
        run: |
            $appCatalogScope = "sitecollection"
            $appCatalogUrl = "${{ inputs.site_url_uat }}"

            if ('${{ inputs.environment_name }}' -eq 'PRD') {
              $appCatalogScope = "${{ inputs.app_catalog_scope }}"
              $appCatalogUrl = "${{ inputs.app_catalog_site_url }}"
            }

            m365 spo app deploy --name ${{ steps.package.outputs.name }} --appCatalogUrl $appCatalogUrl --scope $appCatalogScope

Notice that in the PRD environment, we're not targeting the tenant app catalog, as it also can be a site collection app catalog.

Trigger pipeline

The last "step" is to configure the pipeline that will trigger our whole CI / CD process.

For Azure DevOps, we have the opportunity to reference a global CI / CD pipeline (build-deploy-spfx-cli-m365.yml), for which we'll provide important steps, such as environment variable groups.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
name: main

trigger:
  branches:
    include:
    - main

stages:
  - template: pipelines/stages/build-deploy-spfx-cli-m365.yml
    parameters:
      include_tests: false
      variable_group_uat: 'contoso-UAT'
      variable_group_prd: 'contoso-PRD'
      deploy_uat: true
      deploy_prd: true
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
parameters:
  - name: include_tests
    type: boolean
    default: true
  - name: variable_group_uat
    type: string
  - name: variable_group_prd
    type: string
  - name: deploy_uat
    type: boolean
    default: true
  - name: deploy_prd
    type: boolean
    default: true

stages:
  - stage: 'build'
    jobs:
      - template: ../jobs/build-spfx.yml
        parameters:
          include_tests: false
  - ${{ if eq(parameters.deploy_uat, true) }}:
    - stage: 'deploy_uat'
      displayName: Deploy on UAT
      dependsOn: [ build ]
      jobs:
        - template: ../jobs/deploy-spfx-cli-m365.yml
          parameters:
            target_environment: 'UAT'
            variable_group_name: ${{ parameters.variable_group_uat }}
  - ${{ if eq(parameters.deploy_prd, true) }}:
    - stage: 'deploy_prd'
      displayName: Deploy on PRD
      dependsOn: [ deploy_uat ]
      jobs:
        - template: ../jobs/deploy-spfx-cli-m365.yml
          parameters:
            target_environment: 'PRD'
            variable_group_name: ${{ parameters.variable_group_prd }}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
name: main

on:
  push:
    branches: 
    - main
  workflow_dispatch: # Can be triggered manually

jobs:
  build:
    uses: meganb/contoso/.github/workflows/build-spfx.yml@main
  deploy_uat:
    needs: build
    uses: meganb/contoso/.github/workflows/deploy-spfx-cli-m365.yml@main
    with:
      environment_name: UAT
      site_url_uat: https://contoso.sharepoint.com/sites/hr-uat
    secrets:
      AAD_APP_ID: ${{ secrets.AAD_APP_ID }}
      AAD_APP_PASSWORD: ${{ secrets.AAD_APP_PASSWORD }}
      AAD_APP_ENCODED_CERTIFICATE: ${{ secrets.AAD_APP_ENCODED_CERTIFICATE }}
      AAD_APP_THUMBPRINT: ${{ secrets.AAD_APP_THUMBPRINT }}
      AAD_TENANT_ID: ${{ secrets.AAD_TENANT_ID }}
  deploy_prd:
    needs: deploy_uat
    uses: meganb/contoso/.github/workflows/deploy-spfx-cli-m365.yml@main
    with:
      environment_name: PRD
      site_url_prd: https://contoso.sharepoint.com/sites/hr
      app_catalog_scope: tenant
      app_catalog_site_url: https://contoso.sharepoint.com/sites/app
    secrets:
      AAD_APP_ID: ${{ secrets.AAD_APP_ID }}
      AAD_APP_PASSWORD: ${{ secrets.AAD_APP_PASSWORD }}
      AAD_APP_ENCODED_CERTIFICATE: ${{ secrets.AAD_APP_ENCODED_CERTIFICATE }}
      AAD_APP_THUMBPRINT: ${{ secrets.AAD_APP_THUMBPRINT }}
      AAD_TENANT_ID: ${{ secrets.AAD_TENANT_ID }}

Pipeline files

Below you'll find the complete pipeline files with an abstract, which will be enough as a starter kit to setup everything on your side.

CLI for Microsoft 365

PnP PowerShell

Solution configuration

Below we'll have all the minimum configuration to make our pipeline working on both AzDO and GitHub Actions.

AzDO configuration

In this context, we'll use three important features:

  • Environments
  • Approvers
  • Variable groups

During the pipeline run, both CI and CD parts are considered as "Stages". As the UAT and PRD deployments will have to access to environments, secure files and variable groups, we will have to give permission for those stages once.

For each of those, we will have to permit the pipeline to access to them only on the first run. This will be seen on the pipeline run page, as we'll see that the UAT / PR deployments will be pending for approving permissions. More info here.

The environments will be necessary for approving a pipeline before being run. We'll have to add two environments:

  • UAT (testing)
  • PRD (production)

Environments can be configured here: https://dev.azure.com/[ORGANIZATION]/[PROJECT]/_environment.

For each environment, we can add an approver by clicking on the three dots located in the upper right of it, then "Approvals and checks" and select "Approvals".

Last part to configure are the variable groups, which will be called by our deployment pipeline. As this one will be called twice (one for testing, the other for production), the variable groups will be associated with the environments created before. Here again, two variable groups will be created:

  • contoso-UAT (testing)
  • contoso-PRD (production)

Variable groups can be configured here: https://dev.azure.com/[ORGANIZATION]/[PROJECT]/_library?itemType=VariableGroups.

Below the variables used in the pipelines, depending on the context:

Name UAT PRD Secret value? Definition
aad_app_id YES YES NO The Azure AD application ID with which the authentication to SharePoint will be done
aad_app_password YES YES YES The certificate password necessary for the authentication
aad_tenant_id YES YES NO ID or domain (for example "contoso.onmicrosoft.com") of the tenant from which accounts should be able to authenticate
app_catalog_scope NO YES NO Production app catalog scope (can be "tenant" or "site collection")
site_url_uat YES NO NO SharePoint UAT site URL where the SPFx package will be deployed
app_catalog_site_url NO YES NO Production app catalog site URL (can be a tenant or a site collection one)
site_url_prd NO YES NO SharePoint PRD site URL where the SPFx package will be deployed

Especially for AzDO, we will use the Secure files feature to store a PFX file in order to login to SharePoint in Application context. In this example, we'll use one PFX file for both testing and production environments: PnP.SharePoint.AppOnly.pfx.

Secure files can be configured here: https://dev.azure.com/[ORGANIZATION]/[PROJECT]/_library?itemType=SecureFiles.

GitHub Actions configuration

The environments will be necessary for approving a pipeline before being run. You'll have to add two environments:

  • UAT (testing)
  • PRD (production)

Environments can be configured here: https://github.com/[ORGANIZATION]/[PROJECT]/settings/environments.

Beware that in order to use multiple environments, your repo has to be public or your subscription has to be a Pro one.

For each environment, you can add an approver by enabling the checkbox marked "Required reviewers", then adding at least one reviewer account then click on "Save protection rules".

Environment secrets can be configured here: https://github.com/[ORGANIZATION]/[PROJECT]/settings/environments/[ENVIRONMENT_ID]/edit.

Below the secrets used in the workflows:

Name Description
AAD_APP_ID The Azure AD application ID with which the authentication to SharePoint will be done
AAD_APP_PASSWORD The certificate password necessary for the authentication
AAD_APP_ENCODED_CERTIFICATE Base64 encoded certificate
AAD_APP_THUMBPRINT Certificate thumbprint
AAD_TENANT_ID ID or domain (for example "contoso.onmicrosoft.com") of the tenant from which accounts should be able to authenticate

Below the parameters used in the deploy-spfx-cli-m365.yml workflow, depending on the environment:

Name UAT PRD Definition
environment_name YES YES The deployment environment (can be 'UAT' or 'PRD')
site_url_uat YES NO SharePoint UAT site URL where the SPFx package will be deployed
site_url_prd NO YES SharePoint PRD site URL where the SPFx package will be deployed
app_catalog_scope NO YES Production app catalog scope (can be "tenant" or "site collection")
app_catalog_site_url NO YES Production app catalog site URL (can be a tenant or a site collection one)

Other source code

Ephemeral testing site (which will run with PnP PowerShell only, as it will use the Provisioning Engine to create a copy of existing production site):

Conclusion

Doing so, we'll secure the credentials for each environment and give the entire control to the Ops.

The last part will point out advantages / drawbacks on the two solutions regarding this implementation.

Apply a DevOps process to your SPFx project (Part 1)

Apply a DevOps process to your SPFx project (Part 2)

Apply a DevOps process to your SPFx project (Part 3)