| name | update-xamltools-version |
| description | Guide for updating the xamlTools (Microsoft.VisualStudio.DesignToolsBase) component version in the vscode-csharp repository. Use this when asked to update xamlTools, bump the xamlTools version, or upgrade the XAML/DesignTools component. |
Update xamlTools Version
This skill describes how to update the xamlTools component version in the vscode-csharp repository.
Unlike Roslyn (which lives on GitHub at dotnet/roslyn), xamlTools lives entirely in Azure DevOps, in the VS source repository under the src/Xaml folder.
The general mechanism is the same as Roslyn: changes are merged into the VS repo, CI produces a NuGet package (Microsoft.VisualStudio.DesignToolsBase) that flows to the upstream feed, and updating vscode-csharp means bumping the version in package.json, refreshing the component, and adding a CHANGELOG.md entry via a PR.
Because there is no cross-repo tooling equivalent to roslyn-tools pr-finder, the update is generally simpler than the Roslyn update, but the changelog still requires a manual review/trim step (even if the raw PR list is generated via the REST API).
Input Required
- New xamlTools Version (required): The
Microsoft.VisualStudio.DesignToolsBase NuGet package version to update to (e.g., 18.9.11921.35). Ask the user to provide it.
- The user is responsible for ensuring that version has been saved from upstream into the
msft_consumption feed (see Ensure the Package Is Available in the Feed). If a version is not in the feed, restoring it will fail.
- Changelog entries (recommended): A short list of notable XAML/DesignTools changes between the previous and new version, with their AzDO PR numbers. These can be generated fully automatically from the VS repo via the Azure DevOps REST API — no VS source enlistment is required (see Step 4). If you lack
az access to that AzDO organization, ask the user to provide the list.
Prerequisites
- A local clone of
dotnet/vscode-csharp.
- The .NET SDK installed.
- The Azure Artifacts NuGet Credential Provider for interactive authentication when restoring packages from the feed.
- For the automated changelog (Step 4): the Azure CLI signed in (
az login) with access to the Azure DevOps organization that hosts the VS source repo. The concrete org/project/repo coordinates are environment-specific — supply them from your own notes or parse them from a VS repo URL the user provides (see Step 4); they are intentionally not hard-coded in this public skill. This is used to call the VS repo REST API — no VS source enlistment is required.
(Optional) Ensure the Package Is Available in the Feed
This is normally the user's responsibility, but here is the full process to locate and save the package. vscode-csharp restores from the msft_consumption feed (see NuGet.config), which pulls versions from an upstream source. A new version must be present (and saved) in that feed before it can be restored.
- After your changes merge into the VS repo, it may take up to one day for a NuGet package build to appear in the upstream feed.
- Find your merge commit's CloudBuild number in the AzDO CI.
- Open the package in Azure Artifacts: Microsoft.VisualStudio.DesignToolsBase. Switch to "Upstream versions" and search for your version.
- If it is there, click the three dots (
...) on the right and select "Save to feed".
Note: These Azure DevOps feed web pages require authentication and often fail to load in automated scenarios. If you cannot access them, ask the user to confirm the version has been saved to the feed and to provide the exact version string. Do not attempt to guess or fabricate a version.
Process
Step 1: Create a New Branch
Create a new git branch for the update:
git checkout -B update/xamltools-<version>
Replace <version> with the new xamlTools version, using dashes instead of dots for the branch name (e.g., update/xamltools-18-9-11921-35).
Step 2: Update package.json
Update the defaults.xamlTools field in package.json:
"defaults": {
...
"xamlTools": "<new-version>"
}
Step 3: Acquire the Package
Refresh the local component and ensure the package is present in the consumption feed:
npm run installDependencies
xamlTools (Microsoft.VisualStudio.DesignToolsBase) is a non-platform-specific package, so unlike Roslyn there is no dedicated updateXamlToolsVersion task and no per-platform acquisition. Running installDependencies downloads and installs xamlTools into ./.xamlTools/; a successful restore validates that the requested version is available via the msft_consumption feed (if it isn’t, follow Ensure the Package Is Available in the Feed).
Note: You may need the Azure Artifacts NuGet Credential Provider for interactive authentication. If the restore fails with a "package not found" error, the version is likely not yet saved to the feed — see Ensure the Package Is Available in the Feed.
Step 4: Gather Changelog Entries
Because xamlTools lives in Azure DevOps, there is no pr-finder equivalent. The change list can be generated fully automatically from the VS repo via the Azure DevOps REST API — no VS source enlistment is required (Option A). If you lack az access to that AzDO organization, fall back to a user-provided list (Option B). Each entry references its AzDO PR number rather than a GitHub PR link.
Option A (preferred): Generate from the VS repo automatically
The flow is: (1) resolve the exact VS-repo commit that produced each package version, (2) list the /src/Xaml commits between them, (3) map those commits to their PRs, (4) trim to user/developer-facing changes.
All REST calls target the VS source repo and need an Azure DevOps bearer token.
Resolve the VS repo coordinates first. The org/project/repo are intentionally not hard-coded in this public skill, so obtain them, in order of preference:
- From your own private notes/environment if you have them (this is the expected path for maintainers), otherwise
- Ask the user for the VS repo web URL and parse
<org>/<project>/<repo> from it. Both AzDO URL shapes work:
https://<org>.visualstudio.com/<project>/_git/<repo>
https://dev.azure.com/<org>/<project>/_git/<repo>
Then set the coordinates (the REST API uses the https://dev.azure.com/<org> host form):
# AzDO coordinates for the VS source repo. Fill these in from your notes or the URL the user provides.
$org = '<azdo-org-url>' # e.g. https://dev.azure.com/<org>
$repo = "$org/<project>/_apis/git/repositories/<vs-repo>" # the repo that hosts src/Xaml
# 499b84ac-... is the well-known Azure DevOps application ID; `az login` must have access to that org.
$tok = az account get-access-token --resource 499b84ac-1321-427f-aa17-267ca6975798 --query accessToken -o tsv
$hdr = @{ Authorization = "Bearer $tok" }
4.1 Resolve each version's VS-repo commit from the DLL (not from a build)
The Microsoft.VisualStudio.DesignToolsBase package/nuspec does not embed a commit SHA, but its assemblies do. Microsoft.VisualStudio.DesignTools.Markup.dll (stamped by Nerdbank.GitVersioning) carries the commit two ways:
ProductVersion = 18.x.NNN+<short-10-char-sha> — the short SHA.
- internal const
ThisAssembly.GitCommitId — the full 40-char SHA, which is what the REST API requires (short SHAs are rejected with HTTP 400).
The new version's DLL is already in ./.xamlTools/ after Step 3. For the old version (the value in package.json before your Step 2 edit), restore it into a throwaway project first:
$old = '<old-version>'
$probe = Join-Path $env:TEMP 'xamlProbe'
New-Item -ItemType Directory -Force $probe | Out-Null
Copy-Item ./NuGet.config $probe -Force
@"
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup><TargetFramework>net8.0</TargetFramework><RestorePackagesPath>packages</RestorePackagesPath></PropertyGroup>
<ItemGroup><PackageReference Include="Microsoft.VisualStudio.DesignToolsBase" Version="$old" /></ItemGroup>
</Project>
"@ | Set-Content (Join-Path $probe 'probe.csproj')
dotnet restore (Join-Path $probe 'probe.csproj') --interactive | Out-Null
Read the full SHA from either DLL using an isolated load context (both DLLs share the same assembly identity, so a plain Assembly.LoadFrom of the second would just return the first):
function Get-XamlCommit($dll) {
$alc = [System.Runtime.Loader.AssemblyLoadContext]::new([string][guid]::NewGuid(), $true)
try {
$asm = $alc.LoadFromStream([System.IO.MemoryStream]::new([System.IO.File]::ReadAllBytes($dll)))
$f = $asm.GetType('ThisAssembly').GetField('GitCommitId', [System.Reflection.BindingFlags]'NonPublic,Static,Public')
[string]$f.GetRawConstantValue()
} finally { $alc.Unload() }
}
$newCommit = Get-XamlCommit './.xamlTools/Microsoft.VisualStudio.DesignTools.Markup.dll'
$oldCommit = Get-XamlCommit (Get-ChildItem $probe -Recurse -Filter 'Microsoft.VisualStudio.DesignTools.Markup.dll' | Select-Object -First 1).FullName
4.2 List the /src/Xaml commits between the two versions
Do not use the obvious "commits between two commits" query (searchCriteria.compareVersion + itemVersion): the two build source commits frequently live on divergent branches, so that query returns 0. Instead, walk /src/Xaml history back from the new commit and stop at the old build's most-recent /src/Xaml commit (the "boundary", already shipped in the previous version):
function Get-XamlCommits($version, $top) {
$q = @{ 'searchCriteria.itemPath'='/src/Xaml'; 'searchCriteria.itemVersion.version'=$version;
'searchCriteria.itemVersion.versionType'='commit'; 'searchCriteria.$top'="$top"; 'api-version'='7.1' }
(Invoke-RestMethod -Uri "$repo/commits" -Headers $hdr -Body $q -Method Get).value
}
$boundary = (Get-XamlCommits $oldCommit 1)[0].commitId
$range = New-Object System.Collections.Generic.List[object]
$foundBoundary = $false
foreach ($c in (Get-XamlCommits $newCommit 1000)) { if ($c.commitId -eq $boundary) { $foundBoundary = $true; break }; $range.Add($c) }
if (-not $foundBoundary) { throw "Boundary commit not found in first page of /src/Xaml history. Increase `$top or page results (searchCriteria.`$skip) and retry." }
"in-range /src/Xaml commits: $($range.Count)"
Use the list API as above; the single-commit GET /commits/{sha} endpoint is flaky in scripts and best avoided.
4.3 Map the in-range commits to PRs — cover both completion styles
Parsing Merged PR <id>: <title> out of commit messages only finds squash-completed PRs. Merge-completed PRs instead contribute their individual dev commits (whose messages are not Merged PR ...), and their merge commit doesn't change the path so it never shows up in the filtered list. Missing these is the single most common cause of an incomplete changelog — so combine both detections:
# (a) squash-completed PRs — from "Merged PR <id>: <title>" commit messages
$squash = foreach ($c in $range) {
$m = [regex]::Match($c.comment.Split([char]10)[0], '^Merged PR (\d+): (.*)$')
if ($m.Success) { [pscustomobject]@{ Id = $m.Groups[1].Value; Title = $m.Groups[2].Value.Trim() } }
}
# (b) merge-completed PRs — map the in-range commit ids to their PRs
$body = @{ queries = @(@{ type = 'commit'; items = @($range.commitId) }) } | ConvertTo-Json -Depth 4
$res = Invoke-RestMethod -Uri "$repo/pullRequestQuery?api-version=7.1" -Headers $hdr -Method Post -Body $body -ContentType 'application/json'
$merged = foreach ($p in $res.results[0].PSObject.Properties) { foreach ($pr in $p.Value) { [pscustomobject]@{ Id = "$($pr.pullRequestId)"; Title = $pr.title } } }
# union both, de-duplicated
@($squash + $merged) | Sort-Object Id -Unique | ForEach-Object { " * $($_.Title) (PR: AzDO#$($_.Id))" }
4.4 Trim to user/developer-facing changes
Keep features, fixes, telemetry, and Hot Reload behavior. Drop infrastructure noise — the pullRequestQuery step in particular sweeps in integration PRs that merely carried the commits along:
- branch-sync / integration PRs:
Merge main into ..., Sync vscode -> ..., Merge for <build>;
- component insertions:
... Roslyn ... Insertion into main;
- build/test/pipeline/localization churn and pure refactors.
Option B (fallback): User-provided list
If you cannot reach the VS repo (no az access to that AzDO organization) or cannot resolve the commits, ask the user for the list of notable XAML/DesignTools changes with their AzDO PR numbers.
(Alternative) Local VS enlistment
With a VS enlistment, git log is a convenient cross-check — but it does not replace step 4.3, because --first-parent ... -- src/Xaml only surfaces merge-commit PRs. Prefer the REST flow above as the source of truth.
git log --first-parent "<old-commit>..<new-commit>" --pretty=format:"%s" -- src/Xaml |
Select-String -Pattern '^Merged PR (\d+): (.*)$' |
ForEach-Object { " * $($_.Matches.Groups[2].Value) (PR: AzDO#$($_.Matches.Groups[1].Value))" }
(Optional) Confirm a specific PR shipped in a version
To check whether a given AzDO PR is part of a version, compare its individual commits against the /src/Xaml range (its merge commit will not appear in the range):
$id = <pr-id>
$pr = Invoke-RestMethod -Uri "$repo/pullRequests/$id?api-version=7.1" -Headers $hdr # status / targetRefName / closedDate
$last = (Invoke-RestMethod -Uri "$repo/pullRequests/$id/iterations?api-version=7.1" -Headers $hdr).value[-1].id
$paths = (Invoke-RestMethod -Uri "$repo/pullRequests/$id/iterations/$last/changes?api-version=7.1" -Headers $hdr).changeEntries.item.path
$prCommits = (Invoke-RestMethod -Uri "$repo/pullRequests/$id/commits?api-version=7.1" -Headers $hdr).value.commitId
$inRange = $prCommits | Where-Object { $range.commitId -contains $_ }
"under /src/Xaml: $(@($paths | Where-Object { $_ -like '/src/Xaml*' }).Count)/$($paths.Count); in-range commits: $($inRange.Count)/$($prCommits.Count)"
It is included when at least one of its commits is in $range and it changed files under /src/Xaml (a PR that touches no /src/Xaml files is not part of the package build).
Step 5: Update CHANGELOG.md
Add an entry to CHANGELOG.md under the current in-development version section (the top-most # 2.x.x header). Use the AzDO reference format for the child entries:
* Update xamlTools to <new-version> (PR: [#](https://github.com/dotnet/vscode-csharp/pull/))
* <change description> (PR: AzDO#<number>)
* <change description> (PR: AzDO#<number>, AzDO#<number>)
Notes:
- Leave the vscode-csharp PR number blank initially (just
[#]) — it is filled in after the PR is created (Step 8).
- Child entries use
AzDO#<number> (the VS source repo PR/work item number), not a GitHub link, because those changes live in Azure DevOps. Multiple references are comma-separated, e.g. (PR: AzDO#728266, AzDO#730837).
Step 6: Commit and Push
git add package.json CHANGELOG.md
git commit -m "Update xamlTools to <new-version>"
git push -u origin update/xamltools-<version>
Step 7: Create Pull Request
Create a pull request on GitHub:
- Title:
Update xamlTools to <new-version>
- Base:
main
- Description: Summarize the bump (from
<old-version> to <new-version>) and note that the CHANGELOG.md entry documents the notable XAML/DesignTools changes.
Step 8: Update Changelog with PR Number
xamlTools update PRs have an "interesting" changelog flow: the vscode-csharp PR number isn't known until the PR exists, so it is backfilled after creation.
After the PR is created, note the PR number (e.g., #9461), then:
-
Update the CHANGELOG.md entry to include the actual PR number:
* Update xamlTools to <new-version> (PR: [#9461](https://github.com/dotnet/vscode-csharp/pull/9461))
You can also try to guess the number ahead of time by inspecting the latest open/closed PRs and issues on GitHub, but backfilling after creation is the reliable approach.
-
Commit and push the update — either a follow-up commit, or amend and force-push:
git add CHANGELOG.md
git commit -m "Update changelog with PR number"
git push
Example
For updating from 18.9.11909.33 to 18.9.11921.35 (see PR #9461):
- Branch:
update/xamltools-18-9-11921-35
package.json change: "xamlTools": "18.9.11921.35"
- Run
npm run installDependencies
- Add a
CHANGELOG.md entry under the current version section:
* Update xamlTools to 18.9.11921.35 (PR: [#9461](https://github.com/dotnet/vscode-csharp/pull/9461))
* MAUI Hot Reload exceptions reporting (PR: AzDO#749102)
* C# XAML Expressions (XEXPR) support improvements and fixes (PR: AzDO#748362)
- Create PR titled "Update xamlTools to 18.9.11921.35"
- Backfill the PR number in the changelog
Reference PR
See PR #9461 as an example of a xamlTools version update.
Files Modified
package.json - Update defaults.xamlTools version
CHANGELOG.md - Add changelog entry with the xamlTools AzDO change list
Troubleshooting
Restore Fails With "Package Not Found"
The requested version is likely not yet available/saved in the msft_consumption feed. Either:
- Wait (it can take up to a day after the VS repo merge for the upstream package to build), or
- Save it from upstream to the feed (see Ensure the Package Is Available in the Feed), then re-run
npm run installDependencies.
Authentication Issues with installDependencies
If you encounter authentication errors:
- Install the Azure Artifacts Credential Provider.
- Run
npm run installDependencies again — it should prompt for interactive authentication.
Can't Access the Azure Artifacts Feed Pages
The Azure DevOps feed web pages require authentication and may not load in automated scenarios. Ask the user to confirm the version has been saved to the feed and to provide the exact Microsoft.VisualStudio.DesignToolsBase version string. Do not guess a version.
Commit REST Query Returns HTTP 400 or an Empty Range (Step 4)
- HTTP 400 "object ID must be 40 characters": you passed a short SHA. The REST API needs the full 40-char commit id — take it from
ThisAssembly.GitCommitId (Step 4.1), not from ProductVersion (which only has the short SHA).
compareVersion/itemVersion range returns 0: the two build source commits are on divergent branches. Don't use the compare query; use the boundary-walk in Step 4.2 instead.
Changelog Is Missing PRs (Step 4)
Almost always because only squash-completed PRs were detected. Re-run Step 4.3 and make sure you also run the pullRequestQuery (type: commit) mapping, which catches merge-completed PRs whose commits carry ordinary dev messages instead of Merged PR ....