Skip to content

CI workflows

Everything in CI runs without a Fabric tenant. Validation uses source files, generated CSVs, Python checks, and the site build. Power BI Desktop and Fabric service access are not required.

.github/workflows/ci.yml
# Tenant-free CI. Nothing here needs Fabric, Power BI, or any secret.
name: CI
on:
pull_request:
push:
branches: [main]
workflow_dispatch:
permissions:
contents: read
concurrency:
group: ci-${{ github.ref }}
cancel-in-progress: true
jobs:
validate:
name: Validate (${{ matrix.os }})
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, windows-latest]
runs-on: ${{ matrix.os }}
timeout-minutes: 20
defaults:
run:
shell: pwsh
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
fetch-depth: 0
persist-credentials: false
- uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
with:
python-version: "3.12"
# The same bootstrap a learner runs. It installs the hashed requirements and runs every
# validation step (data, expected, questions, runs, tmdl, bindings, customizations, hygiene).
- name: Initialize and validate
run: ./scripts/Initialize-DevEnvironment.ps1
- name: pytest
run: |
$python = $IsWindows ? '.venv/Scripts/python.exe' : '.venv/bin/python'
& $python -m pytest tools/python/tests
if ($LASTEXITCODE) { exit $LASTEXITCODE }
- name: ruff
if: runner.os == 'Linux'
run: |
.venv/bin/python -m ruff check tools/python
if ($LASTEXITCODE) { exit $LASTEXITCODE }
.venv/bin/python -m ruff format --check tools/python
if ($LASTEXITCODE) { exit $LASTEXITCODE }
- name: Pester
run: |
$pester = Get-Module -ListAvailable Pester | Sort-Object Version -Descending | Select-Object -First 1
if (-not $pester -or $pester.Version -lt [version]'5.5.0') {
Install-Module Pester -MinimumVersion 5.5.0 -Scope CurrentUser -Force -SkipPublisherCheck
}
Import-Module Pester -MinimumVersion 5.5.0
$config = New-PesterConfiguration
$config.Run.Path = 'scripts/tests'
$config.Run.Exit = $true
$config.Output.Verbosity = 'Detailed'
Invoke-Pester -Configuration $config
- name: PSScriptAnalyzer
run: |
if (-not (Get-Module -ListAvailable PSScriptAnalyzer)) {
Install-Module PSScriptAnalyzer -Scope CurrentUser -Force
}
$findings = Invoke-ScriptAnalyzer -Path scripts -Recurse -Settings ./PSScriptAnalyzerSettings.psd1
$findings | Format-Table -AutoSize
if ($findings) { exit 1 }
# Warns instead of failing until the maintainer has pushed the checkpoint tags.
- name: Checkpoint tags
if: runner.os == 'Linux'
run: ./scripts/lab/Test-CheckpointTags.ps1 -AllowMissing
site:
name: Lab site build
runs-on: ubuntu-latest
timeout-minutes: 15
defaults:
run:
working-directory: site
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
with:
node-version: "22"
cache: npm
cache-dependency-path: site/package-lock.json
- run: npm ci --no-audit --no-fund
- run: npm run check
- run: npm run build
dependency-review:
name: Dependency review
if: github.event_name == 'pull_request'
runs-on: ubuntu-latest
steps:
- uses: actions/dependency-review-action@a1d282b36b6f3519aa1f3fc636f609c47dddb294 # v5.0.0
with:
fail-on-severity: moderate

Jobs:

JobRuns onPurpose
validateMatrix: ubuntu-latest, windows-latestPython install, validation, lab checks, conventions, hooks, and repo checks.
siteubuntu-latestInstalls site dependencies, runs content checks, and builds the Starlight site.
dependency-reviewubuntu-latest, pull requests onlyReviews dependency changes.

The workflow uses least-privilege permissions. The top-level workflow permission is contents: read.

Actions are pinned by SHA with comments that show the human-readable version. This keeps automation stable and reviewable.

.github/workflows/ci.yml (lines 13-37)
concurrency:
group: ci-${{ github.ref }}
cancel-in-progress: true
jobs:
validate:
name: Validate (${{ matrix.os }})
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, windows-latest]
runs-on: ${{ matrix.os }}
timeout-minutes: 20
defaults:
run:
shell: pwsh
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
fetch-depth: 0
persist-credentials: false
- uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
with:
python-version: "3.12"
.github/workflows/pages.yml
# Publishes the self-paced lab site to GitHub Pages.
# Runs only when the repository variable LAB_SITE_ENABLED is 'true', so copies made from the
# template don't publish until their owner turns Pages on (Settings > Pages > Source: GitHub Actions).
name: Lab site
on:
push:
branches: [main]
paths:
- "site/**"
- "lab/**"
- "evaluation/questions.yaml"
- "rules/**"
- ".github/agents/**"
- ".github/prompts/**"
- ".github/skills/**"
- ".github/instructions/**"
- ".github/workflows/pages.yml"
workflow_dispatch:
permissions:
contents: read
concurrency:
group: pages
cancel-in-progress: false
jobs:
build:
if: vars.LAB_SITE_ENABLED == 'true'
runs-on: ubuntu-latest
timeout-minutes: 15
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- id: pages
uses: actions/configure-pages@45bfe0192ca1faeb007ade9deae92b16b8254a0d # v6.0.0
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
with:
node-version: "22"
cache: npm
cache-dependency-path: site/package-lock.json
- name: Build
working-directory: site
env:
SITE_ORIGIN: ${{ steps.pages.outputs.origin }}
SITE_BASE: ${{ steps.pages.outputs.base_path }}
SITE_REPO: ${{ github.repository }}
run: |
npm ci --no-audit --no-fund
npm run check
npm run build
- uses: actions/upload-pages-artifact@fc324d3547104276b827a68afc52ff2a11cc49c9 # v5.0.0
with:
path: site/dist
deploy:
needs: build
runs-on: ubuntu-latest
permissions:
pages: write
id-token: write
environment:
name: github-pages
url: ${{ steps.deployment.outputs.page_url }}
steps:
- id: deployment
uses: actions/deploy-pages@368f82528645a54fb793d4d04e342629a3f51346 # v5.0.1

Jobs:

JobPurpose
buildBuilds the site and uploads the Pages artifact when LAB_SITE_ENABLED is true.
deployDeploys to GitHub Pages using the artifact from build.

One-time setup:

  1. Go to Settings > Pages.
  2. Set Source to GitHub Actions.
  3. Add repository variable LAB_SITE_ENABLED=true.

If the variable is missing or false, Pages does not run.

.github/workflows/copilot-setup-steps.yml
# Prepares the Copilot cloud agent's environment before it starts work on an assigned issue (Lab 13).
# The job name must be copilot-setup-steps. See
# https://docs.github.com/copilot/how-tos/use-copilot-agents/coding-agent/customize-the-agent-environment
name: Copilot setup steps
on:
workflow_dispatch:
push:
paths: [".github/workflows/copilot-setup-steps.yml"]
pull_request:
paths: [".github/workflows/copilot-setup-steps.yml"]
permissions:
contents: read
jobs:
copilot-setup-steps:
runs-on: ubuntu-latest
timeout-minutes: 20
permissions:
contents: read
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
with:
python-version: "3.12"
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
with:
node-version: "22"
- name: Python tools and validation
shell: pwsh
run: ./scripts/Initialize-DevEnvironment.ps1
# Installs the pinned Skills for Fabric skill from apm.yml into .agents/skills, where the
# cloud agent discovers project skills. --only apm skips the MCP servers that package declares.
- name: Skills for Fabric (APM)
run: |
python -m venv "$RUNNER_TEMP/apm"
"$RUNNER_TEMP/apm/bin/pip" install --disable-pip-version-check --quiet apm-cli==0.31.0
"$RUNNER_TEMP/apm/bin/apm" install --only apm --target copilot

The job is named copilot-setup-steps. It prepares cloud agent sessions by installing Python dependencies and APM, then installing the pinned Skills for Fabric package.

.github/dependabot.yml
version: 2
updates:
- package-ecosystem: github-actions
directory: /
schedule:
interval: weekly
groups:
actions:
patterns: ["*"]
- package-ecosystem: pip
directory: /tools/python
schedule:
interval: weekly
groups:
python:
patterns: ["*"]
- package-ecosystem: npm
directory: /site
schedule:
interval: weekly
groups:
site:
patterns: ["*"]

Dependabot watches GitHub Actions, site npm dependencies, and Python dependencies. Keep Dependabot pull requests small and let CI prove the lab still works.

Enable GitHub secret scanning and push protection on the repository. The lab should never need committed tokens, tenant secrets, or connection strings. MCP values should come from inputs, environment variables, or GitHub MCP secrets.

Terminal window
pwsh ./scripts/Invoke-Validation.ps1 -Tests
cd site
npm run check
npm run build