1818 - release
1919 - aka
2020 - brew
21+ - winget
2122 telemetry :
2223 description : " Send installer telemetry during this validation run"
2324 required : true
9394 throw 'Azure CLI is still available after removal'
9495 }
9596
96- - name : Install VS Code and Copilot CLI
97+ - name : Install VS Code, Copilot CLI, and Claude shim
9798 if : matrix.scenario == 'with-code'
9899 shell : pwsh
99100 run : |
@@ -112,6 +113,11 @@ jobs:
112113 if (-not $copilotPackage) { throw 'GitHub Copilot CLI package directory not found' }
113114 Add-Content -Path $env:GITHUB_PATH -Value $copilotPackage.FullName
114115
116+ $claudeBin = Join-Path $env:RUNNER_TEMP 'claude-bin'
117+ New-Item -ItemType Directory -Force -Path $claudeBin | Out-Null
118+ Set-Content -Path (Join-Path $claudeBin 'claude.cmd') -Value "@echo off`r`nexit /b 0`r`n" -NoNewline
119+ Add-Content -Path $env:GITHUB_PATH -Value $claudeBin
120+
115121 - name : Confirm Copilot CLI is absent
116122 if : matrix.scenario != 'with-code'
117123 shell : pwsh
@@ -121,6 +127,7 @@ jobs:
121127 }
122128
123129 - name : Download and verify signed bootstrap
130+ if : env.INSTALL_SOURCE != 'winget'
124131 shell : pwsh
125132 run : |
126133 $ErrorActionPreference = 'Stop'
@@ -165,7 +172,7 @@ jobs:
165172 "SIGNER_THUMBPRINT=$($signature.SignerCertificate.Thumbprint)" | Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append
166173 "SIGNER_WAS_TRUSTED=$wasTrusted" | Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append
167174
168- - name : Install through PowerShell bootstrap
175+ - name : Install DevPack
169176 shell : pwsh
170177 run : |
171178 $ErrorActionPreference = 'Stop'
@@ -177,16 +184,48 @@ jobs:
177184 if ($env:INSTALL_SOURCE -eq 'aka') {
178185 & powershell.exe -ExecutionPolicy AllSigned -Command `
179186 "Invoke-RestMethod 'https://aka.ms/foundry-devpack-install.ps1' | Invoke-Expression"
187+ } elseif ($env:INSTALL_SOURCE -eq 'winget') {
188+ $installerLog = Join-Path $env:RUNNER_TEMP 'foundry-devpack.log'
189+ $expectedVersion = $env:RELEASE_TAG -replace '^devpack-installer-', ''
190+ winget install --exact --id Microsoft.FoundryDevPack `
191+ --version $expectedVersion --source winget --force --silent `
192+ --disable-interactivity --accept-package-agreements --accept-source-agreements `
193+ --log $installerLog
180194 } else {
195+ $installerLog = Join-Path $env:RUNNER_TEMP 'foundry-devpack.log'
181196 & powershell.exe -NoProfile -NonInteractive -ExecutionPolicy AllSigned `
182- -File $env:DEVPACK_SCRIPT --verbose
197+ -File $env:DEVPACK_SCRIPT --verbose --log $installerLog
198+ }
199+ if ($LASTEXITCODE -ne 0) { throw "DevPack install exited $LASTEXITCODE" }
200+ if ($env:INSTALL_SOURCE -in @('release', 'winget')) {
201+ if (-not (Test-Path $installerLog)) { throw 'installer log was not created' }
202+ $logText = Get-Content $installerLog -Raw
203+ if ($logText -notmatch 'Finished \(failed=0, cancelled=False\)') {
204+ throw 'installer log does not contain the successful run result'
205+ }
183206 }
184- if ($LASTEXITCODE -ne 0) { throw "PowerShell bootstrap exited $LASTEXITCODE" }
185207 $leaked = @(Get-ChildItem $temp -Directory -Filter 'foundry-devpack-*' -ErrorAction SilentlyContinue)
186208 if ($leaked.Count) {
187209 throw "bootstrap left temporary directories: $($leaked.FullName -join ', ')"
188210 }
189211
212+ - name : Verify winget repair
213+ if : env.INSTALL_SOURCE == 'winget'
214+ shell : pwsh
215+ run : |
216+ $ErrorActionPreference = 'Stop'
217+ $repairLog = Join-Path $env:RUNNER_TEMP 'foundry-devpack-repair.log'
218+ $expectedVersion = $env:RELEASE_TAG -replace '^devpack-installer-', ''
219+ winget repair --exact --id Microsoft.FoundryDevPack `
220+ --version $expectedVersion --source winget --force --silent `
221+ --disable-interactivity --accept-package-agreements --accept-source-agreements `
222+ --log $repairLog
223+ if ($LASTEXITCODE -ne 0) { throw "winget repair exited $LASTEXITCODE" }
224+ if (-not (Test-Path $repairLog)) { throw 'repair log was not created' }
225+ if ((Get-Content $repairLog -Raw) -notmatch 'Finished \(failed=0, cancelled=False\)') {
226+ throw 'repair log does not contain the successful run result'
227+ }
228+
190229 - name : Verify installed components
191230 shell : pwsh
192231 run : |
@@ -223,6 +262,18 @@ jobs:
223262 throw 'Foundry Copilot plugin not installed'
224263 }
225264
265+ $canonicalSkill = Join-Path $env:USERPROFILE '.agents\skills\microsoft-foundry'
266+ $claudeSkill = Join-Path $env:USERPROFILE '.claude\skills\microsoft-foundry'
267+ $item = Get-Item -LiteralPath $claudeSkill -Force
268+ if (-not $item.LinkType) { throw 'Claude Code skill is not a link' }
269+ $resolved = $item.ResolveLinkTarget($true)
270+ if (-not $resolved -or -not $resolved.FullName.Equals($canonicalSkill, [StringComparison]::OrdinalIgnoreCase)) {
271+ throw "Claude Code skill link does not target $canonicalSkill"
272+ }
273+ if (Test-Path (Join-Path $claudeSkill '.foundry-devpack-copy')) {
274+ throw 'Claude Code skill unexpectedly used the copy fallback'
275+ }
276+
226277 - name : Verify ARP and uninstall
227278 shell : pwsh
228279 run : |
@@ -232,14 +283,30 @@ jobs:
232283 $entry = Get-ItemProperty $key
233284 if (-not (Test-Path $entry.InstallLocation -PathType Container)) { throw 'ARP install location not found' }
234285 if (-not (Test-Path $entry.DisplayIcon -PathType Leaf)) { throw 'persisted installer not found' }
286+ $binarySignature = Get-AuthenticodeSignature $entry.DisplayIcon
287+ if ($binarySignature.Status -ne 'Valid') {
288+ throw "invalid installer signature: $($binarySignature.Status) $($binarySignature.StatusMessage)"
289+ }
290+ if ($binarySignature.SignerCertificate.Subject -notlike 'CN=Microsoft Corporation,*') {
291+ throw "unexpected installer signer: $($binarySignature.SignerCertificate.Subject)"
292+ }
293+ $expectedVersion = $env:RELEASE_TAG -replace '^devpack-installer-', ''
294+ if ($entry.DisplayVersion -ne $expectedVersion) {
295+ throw "ARP version $($entry.DisplayVersion), expected $expectedVersion"
296+ }
235297 Write-Host "ARP: $($entry.DisplayName) $($entry.DisplayVersion)"
236298
237- & $entry.DisplayIcon uninstall
299+ if ($env:INSTALL_SOURCE -eq 'winget') {
300+ winget uninstall --exact --id Microsoft.FoundryDevPack --source winget `
301+ --silent --disable-interactivity --accept-source-agreements
302+ } else {
303+ & $entry.DisplayIcon uninstall
304+ }
238305 if ($LASTEXITCODE -ne 0) { throw "uninstall exited $LASTEXITCODE" }
239306 if (Test-Path $key) { throw 'ARP entry still present after uninstall' }
240307
241308 - name : Remove trusted publisher and bootstrap
242- if : always()
309+ if : always() && env.INSTALL_SOURCE != 'winget'
243310 shell : pwsh
244311 run : |
245312 if ($env:SIGNER_THUMBPRINT -and $env:SIGNER_WAS_TRUSTED -eq 'False') {
@@ -259,7 +326,7 @@ jobs:
259326 Remove-Item $env:DEVPACK_SCRIPT -Force -ErrorAction SilentlyContinue
260327
261328 linux :
262- if : ${{ (startsWith(github.event.release.tag_name, 'devpack-installer-') || startsWith(inputs.release_tag, 'devpack-installer-')) && (github.event_name == 'release' || inputs.install_source != 'brew') }}
329+ if : ${{ (startsWith(github.event.release.tag_name, 'devpack-installer-') || startsWith(inputs.release_tag, 'devpack-installer-')) && (github.event_name == 'release' || ( inputs.install_source != 'brew' && inputs.install_source != 'winget') ) }}
263330 name : linux ${{ matrix.platform.arch }} ${{ matrix.scenario }} (${{ inputs.install_source || 'release' }}, telemetry=${{ inputs.telemetry || false }})
264331 runs-on : ${{ matrix.platform.os }}
265332 timeout-minutes : 30
@@ -293,7 +360,7 @@ jobs:
293360 exit 1
294361 fi
295362
296- - name : Install VS Code and Copilot CLI
363+ - name : Install VS Code, Copilot CLI, and Claude shim
297364 if : matrix.scenario == 'with-code'
298365 shell : bash
299366 run : |
@@ -306,6 +373,10 @@ jobs:
306373 sudo apt-get install -y code
307374 curl -fsSL https://gh.io/copilot-install | bash
308375 echo "$HOME/.local/bin" >> "$GITHUB_PATH"
376+ mkdir -p "$RUNNER_TEMP/claude-bin"
377+ printf '#!/bin/sh\nexit 0\n' > "$RUNNER_TEMP/claude-bin/claude"
378+ chmod +x "$RUNNER_TEMP/claude-bin/claude"
379+ echo "$RUNNER_TEMP/claude-bin" >> "$GITHUB_PATH"
309380
310381 - name : Confirm Copilot CLI is absent
311382 if : matrix.scenario != 'with-code'
@@ -342,7 +413,11 @@ jobs:
342413 if [ "$INSTALL_SOURCE" = "aka" ]; then
343414 curl -fsSL https://aka.ms/foundry-devpack-install.sh | bash
344415 else
345- bash "$DEVPACK_SCRIPT" --verbose
416+ bash "$DEVPACK_SCRIPT" --verbose --log "$RUNNER_TEMP/foundry-devpack.log"
417+ fi
418+ if [ "$INSTALL_SOURCE" = "release" ]; then
419+ test -f "$RUNNER_TEMP/foundry-devpack.log"
420+ grep -F 'Finished (failed=0, cancelled=False)' "$RUNNER_TEMP/foundry-devpack.log"
346421 fi
347422 if find "$temp" -mindepth 1 -maxdepth 1 -type d -name 'foundry-devpack-*' -print -quit | grep -q .; then
348423 echo "error: bootstrap left temporary directories under $temp" >&2
@@ -371,14 +446,17 @@ jobs:
371446 code --list-extensions | grep -qi 'ms-windows-ai-studio.windows-ai-studio'
372447 copilot plugin list --no-color | tee "$RUNNER_TEMP/copilot-plugins.txt"
373448 grep -qi 'microsoft-foundry' "$RUNNER_TEMP/copilot-plugins.txt"
449+ test -L "$HOME/.claude/skills/microsoft-foundry"
450+ test "$(readlink "$HOME/.claude/skills/microsoft-foundry")" = "$HOME/.agents/skills/microsoft-foundry"
451+ test ! -e "$HOME/.claude/skills/microsoft-foundry/.foundry-devpack-copy"
374452
375453 - name : Remove bootstrap
376454 if : always()
377455 shell : bash
378456 run : rm -f "$DEVPACK_SCRIPT"
379457
380458 macos :
381- if : ${{ startsWith(github.event.release.tag_name, 'devpack-installer-') || startsWith(inputs.release_tag, 'devpack-installer-') }}
459+ if : ${{ ( startsWith(github.event.release.tag_name, 'devpack-installer-') || startsWith(inputs.release_tag, 'devpack-installer-')) && (github.event_name == 'release' || inputs.install_source != 'winget ') }}
382460 name : macos ${{ matrix.platform.arch }} ${{ matrix.scenario }} (${{ inputs.install_source || 'release' }}, telemetry=${{ inputs.telemetry || false }})
383461 runs-on : ${{ matrix.platform.os }}
384462 timeout-minutes : 30
@@ -416,7 +494,7 @@ jobs:
416494 exit 1
417495 fi
418496
419- - name : Install VS Code and Copilot CLI
497+ - name : Install VS Code, Copilot CLI, and Claude shim
420498 if : matrix.scenario == 'with-code'
421499 shell : bash
422500 run : |
@@ -425,6 +503,10 @@ jobs:
425503 brew install --cask copilot-cli
426504 sudo mkdir -p /usr/local/bin
427505 sudo ln -sf '/Applications/Visual Studio Code.app/Contents/Resources/app/bin/code' /usr/local/bin/code
506+ mkdir -p "$RUNNER_TEMP/claude-bin"
507+ printf '#!/bin/sh\nexit 0\n' > "$RUNNER_TEMP/claude-bin/claude"
508+ chmod +x "$RUNNER_TEMP/claude-bin/claude"
509+ echo "$RUNNER_TEMP/claude-bin" >> "$GITHUB_PATH"
428510
429511 - name : Confirm Copilot CLI is absent
430512 if : matrix.scenario != 'with-code'
@@ -470,13 +552,17 @@ jobs:
470552 foundry-devpack --version | grep -F "$expected_version"
471553 ;;
472554 release)
473- bash "$DEVPACK_SCRIPT" --verbose
555+ bash "$DEVPACK_SCRIPT" --verbose --log "$RUNNER_TEMP/foundry-devpack.log"
474556 ;;
475557 *)
476558 echo "error: unsupported install source: $INSTALL_SOURCE" >&2
477559 exit 1
478560 ;;
479561 esac
562+ if [ "$INSTALL_SOURCE" = "release" ]; then
563+ test -f "$RUNNER_TEMP/foundry-devpack.log"
564+ grep -F 'Finished (failed=0, cancelled=False)' "$RUNNER_TEMP/foundry-devpack.log"
565+ fi
480566 if find "$temp" -mindepth 1 -maxdepth 1 -type d -name 'foundry-devpack-*' -print -quit | grep -q .; then
481567 echo "error: bootstrap left temporary directories under $temp" >&2
482568 find "$temp" -mindepth 1 -maxdepth 1 -type d -name 'foundry-devpack-*' -print >&2
@@ -504,6 +590,9 @@ jobs:
504590 code --list-extensions | grep -qi 'ms-windows-ai-studio.windows-ai-studio'
505591 copilot plugin list --no-color | tee "$RUNNER_TEMP/copilot-plugins.txt"
506592 grep -qi 'microsoft-foundry' "$RUNNER_TEMP/copilot-plugins.txt"
593+ test -L "$HOME/.claude/skills/microsoft-foundry"
594+ test "$(readlink "$HOME/.claude/skills/microsoft-foundry")" = "$HOME/.agents/skills/microsoft-foundry"
595+ test ! -e "$HOME/.claude/skills/microsoft-foundry/.foundry-devpack-copy"
507596
508597 - name : Uninstall Homebrew cask
509598 if : env.INSTALL_SOURCE == 'brew'
0 commit comments