Skip to content

CI/CD

Using bdoctor works best when you implement it on an automated CI/CD pipeline. For instance, each time you push a change to your source control system you use, let the pages rebuild themselves.

Azure DevOps and GitHub actions are a perfect choice for it, but bdoctor can run on any platform which allows you to run node.js CLI tools.

The quickest way to get started is the bdoctor workflow command. It generates the definition which publishes your documentation on each push to the main branch, for GitHub Actions or Azure DevOps.

Deploy the matching Better Doctor Markdown SPFx solution separately before enabling page publishing. Generated publishing workflows do not deploy the app catalog solution or prove that it works in the tenant.

Complete the deployment and migration pilot before adding --confirm for an approved legacy replacement. Do not add cleanup flags to bypass migration safeguards. Deployed-browser/tenant migration and rollback have not been executed for this change.

bdoctor publish checks that the web part is available at the target site and stops before any change when it is missing, so a pipeline that publishes pages has to deploy the solution first. A deploy step signs in with the same app registration the publish step uses and adds, deploys and installs the package:

Terminal window
m365 login --authType certificate --appId "$APP_ID" --tenant "$TENANT_ID" --certificateBase64Encoded "$CERTIFICATE" --password "$CERT_PASSWORD"
m365 spo app add --filePath ./bdoctor-markdown-webpart.sppkg --appCatalogScope sitecollection --appCatalogUrl "$SITE_URL" --overwrite
m365 spo app deploy --name bdoctor-markdown-webpart.sppkg --appCatalogScope sitecollection --appCatalogUrl "$SITE_URL"
m365 spo app install --name bdoctor-markdown-webpart.sppkg --appCatalogScope sitecollection --appCatalogUrl "$SITE_URL" --siteUrl "$SITE_URL"

The site needs a site collection app catalog, and the app registration needs rights to manage apps in it. Both are a one-time administrative setup — see web part deployment. app install fails once the app is on the site, so a pipeline that re-runs should tolerate that one case and no other.

Keep the CLI and solution versions aligned. Use the same math/render settings for status and publish; a renderer fingerprint change can require an update even when Markdown text did not change.

Serialize publishing per target site, including every pipeline, scheduled job, and local invocation. Queue later runs rather than cancelling an active migration. Page checkout and canvas comparisons do not provide atomic compare-and-swap, and concurrent writers using the same principal can still race. Better Doctor does not introduce a new locking flag.

Before retrying a failed migration, inspect any retained page checkout and staged backup. Have the owner or an authorized administrator review and check in the intended recovery state; never blindly discard the checkout or publish an unverified draft. Follow the interrupted-migration recovery procedure.

Use the azdo provider to generate an azure-pipelines.yml file in the root of your project.

Terminal window
bdoctor workflow --provider azdo

Check the workflow command section for the variables you need to add to your pipeline.

If you want to know more about setting up bdoctor on Azure DevOps, you can read the article from Elio at Using Better Doctor on Azure DevOps to generate your documentation.

The github provider is the default. It creates the .github/workflows folder, and generates a bdoctor.yml workflow file in it.

Terminal window
bdoctor workflow

Check the workflow command section for the secrets you need to add to your repository.

If you want to know more about using bdoctor in GitHub Actions, you can check out the following article from Elio: Using Better Doctor in GitHub Actions for your documentation.

The --output json argument makes the result of a run readable for your pipeline. A bdoctor status run on a pull request tells you which pages it is going to touch, before anything is published:

- name: Check which pages change
id: status
run: |
bdoctor status --output json \
--url ${{ secrets.SITE_URL }} \
--appId ${{ secrets.APP_ID }} \
--tenant ${{ secrets.TENANT_ID }} \
--certificate ${{ secrets.CERTIFICATE }} > status.json
{
echo "upToDate=$(jq -r '.summary.upToDate' status.json)"
echo "comment<<EOF"
jq -r '"**\(.summary.changed)** page(s) will be published.\n" +
((.pages.new + .pages.modified) | map("- `\(.file)`") | join("\n"))' status.json
echo "EOF"
} >> "$GITHUB_OUTPUT"
- name: Comment on the pull request
uses: peter-evans/create-or-update-comment@v4
with:
issue-number: ${{ github.event.pull_request.number }}
body: ${{ steps.status.outputs.comment }}

The same document lets you skip the publish step altogether when nothing changed:

- name: Publish
if: ${{ !fromJSON(steps.status.outputs.upToDate) }}
run: bdoctor publish