Pre-Commit Validation Checks
Run these checks after making changes to SDK packages under sdk/ and before staging, committing, or pushing. These steps may produce additional file changes that must be included in the commit.
General rule: Steps 3–5 are conditional to save time, but if you are unsure whether a step is needed, always run it. It is better to run an unnecessary script than to miss a required regeneration.
- Determine scope from changed files
-
Derive the changed file list from git status --porcelain (or by combining git diff --name-only HEAD with git ls-files -o --exclude-standard ) so that untracked (new) files are included.
-
From the changed file list, extract all unique ServiceDirectories — the first path segment after sdk/ (e.g., sdk/storage/... → storage , sdk/ai/... → ai ).
-
For each ServiceDirectory, identify all affected packages = distinct sdk/{ServiceDirectory}/{PackageName}/ paths.
-
For each affected package, identify:
-
All csproj files that had changes beneath them (src, tests, samples, perf, stress, or any other project type).
-
The src csproj specifically = sdk/{service}/{package}/src/{Package}.csproj — this is used for GenerateCode.
-
The remaining steps must be run for each affected project/ServiceDirectory. If changes span multiple ServiceDirectories or packages, run for each one.
- Run dotnet format (for every changed csproj)
For every csproj that had files change beneath it — src, tests, samples, perf, stress, or any other project type — run:
dotnet format <path-to-changed.csproj>
Identify affected csproj files by looking at the full changed file list: include any project where either (a) there is a changed .cs file with that .csproj as its nearest parent, or (b) the .csproj itself changed, or files such as .props , .targets , or .md changed under that project. This fixes style/formatting issues and may modify .cs files.
- Conditionally run dotnet build /t:GenerateCode (per src csproj)
Exclusion: Skip this step entirely for the provisioning service directory. Provisioning libraries use their own generator and are not generated via TypeSpec/AutoRest.
Trigger condition — for each affected package's src csproj: Run this if ANY of the following files are in the changed file list:
-
Any file matching sdk/{service}/{package}/src/Generated/**
-
sdk/{service}/{package}/tsp-location.yaml
-
sdk/{service}/{package}/src/autorest.md
-
Any .cs file under sdk/{service}/{package}/src/ where a CodeGen* attribute was added, removed, or modified (e.g., CodeGenType , CodeGenModel , CodeGenClient , CodeGenMember , CodeGenSuppress , CodeGenSerialization , CodeGenVisibility ). These attributes in custom (non-generated) code influence the generated output.
Important: Do not manually edit files under src/Generated/ . Changes to generated code should only come from running the GenerateCode target.
Command:
dotnet build sdk/{service}/{package}/src/{Package}.csproj /t:GenerateCode
This regenerates code from TypeSpec/AutoRest specs. It may modify files under src/Generated/ .
- Conditionally run Export-API.ps1 (per ServiceDirectory)
Trigger condition — per ServiceDirectory: Run this if ANY of the following are true for ANY affected package within this ServiceDirectory:
-
Step 3 above ran for any package in this ServiceDirectory (generated code may affect public API).
-
Changed .cs files under sdk/{service}/{package}/src/ for any package in this ServiceDirectory contain public API surface changes. To determine this, inspect the diffs for:
-
New, removed, or renamed public or protected types (class , interface , struct , enum , record ).
-
New, removed, or changed signatures of public or protected members (methods, properties, constructors, events, fields).
-
Visibility changes (e.g., internal → public ).
-
Changes to base classes or implemented interfaces on public types.
-
New .cs files under src/ that contain public types.
Skip Export-API if changes are limited to:
-
Method body implementations (no signature changes).
-
internal or private members only.
-
XML doc comments or code comments.
-
.csproj configuration changes.
Command:
eng\scripts\Export-API.ps1 {ServiceDirectory}
This regenerates API listing files under sdk/{service}/{package}/api/*.cs . Uses dotnet build /t:ExportApi internally.
Note: Export-API operates at the ServiceDirectory level (not per-csproj), so run it once per affected ServiceDirectory.
- Conditionally run Update-Snippets.ps1 (per ServiceDirectory)
Trigger condition — for each affected ServiceDirectory: Run this if ANY of the following are true:
-
Changed *.md files under sdk/{service}/ contain snippet references (```C# Snippet: markers).
-
Changed .cs files contain snippet regions (#region Snippet: ).
Command:
eng\scripts\Update-Snippets.ps1 {ServiceDirectory}
Note: Update-Snippets operates at the ServiceDirectory level (not per-csproj), so run it once per affected ServiceDirectory.
This runs snippet-generator to sync code snippets into markdown files. May modify *.md and *.cs files.
- Verify and include all changes
-
After running all applicable steps for all affected projects and ServiceDirectories, check for new/modified files with git status .
-
All changes produced by these steps must be included in the commit.
-
Do NOT commit if any of the above steps failed.
Execution order
Run the following steps in this order, respecting their scope (per package vs per ServiceDirectory):
-
dotnet format first — run on all changed csproj files (src, tests, etc.) for each affected package.
-
GenerateCode second — per src csproj, regenerates from specs (may override format changes in Generated/) for each affected package.
-
Export-API third — run once per affected ServiceDirectory, after all GenerateCode runs for packages in that ServiceDirectory are complete; captures the final public API surface after generation.
-
Update-Snippets fourth — run once per affected ServiceDirectory, after Export-API; captures any snippet changes from all prior steps.
When multiple packages/ServiceDirectories are affected, run steps 1–2 for each affected package, then steps 3–4 once per affected ServiceDirectory. Step 6 (verify) runs once at the end.