@@ -381,4 +381,153 @@ jobs:
381381 fi
382382 } >> "$GITHUB_STEP_SUMMARY"
383383
384+ build-visualstudio :
385+ needs : release
386+ # Windows required: Node.js SEA (bundle-exe.ps1) and MSBuild/VSSDK are Windows-only
387+ runs-on : windows-latest
388+ permissions :
389+ contents : write
390+
391+ steps :
392+ - name : Harden the runner (Audit all outbound calls)
393+ uses : step-security/harden-runner@fa2e9d605c4eeb9fcad4c99c224cee0c6c7f3594 # v2.16.0
394+ with :
395+ egress-policy : audit
396+
397+ - name : Checkout code
398+ uses : actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
399+
400+ - name : Setup Node.js
401+ uses : actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0
402+ with :
403+ node-version : ' 22.x'
404+
405+ # ── Install dependencies ────────────────────────────────────────────────
406+
407+ - name : Install vscode-extension dependencies
408+ run : npm ci
409+ working-directory : vscode-extension
410+
411+ - name : Install CLI dependencies
412+ run : npm ci
413+ working-directory : cli
414+
415+ # ── Build CLI (production bundle + Windows .exe) ───────────────────────
416+
417+ - name : Build CLI (production bundle)
418+ working-directory : cli
419+ run : npm run build:production
420+
421+ - name : Bundle CLI as single executable
422+ working-directory : cli
423+ shell : pwsh
424+ run : |
425+ & pwsh -NoProfile -File bundle-exe.ps1 -SkipBuild
426+ if ($LASTEXITCODE -ne 0) { throw "bundle-exe.ps1 failed" }
427+
428+ - name : Copy CLI exe + wasm to cli-bundle/
429+ shell : pwsh
430+ run : |
431+ $vsCliDir = "visualstudio-extension\src\CopilotTokenTracker\cli-bundle"
432+ New-Item -ItemType Directory -Path $vsCliDir -Force | Out-Null
433+ Copy-Item "cli\dist\copilot-token-tracker.exe" "$vsCliDir\copilot-token-tracker.exe" -Force
434+ Copy-Item "cli\dist\sql-wasm.wasm" "$vsCliDir\sql-wasm.wasm" -Force
435+ Write-Host "✅ Copied cli-bundle assets"
436+
437+ # ── Build webview bundles ──────────────────────────────────────────────
438+
439+ - name : Build VS Code extension webview bundles
440+ working-directory : vscode-extension
441+ run : npm run package
442+
443+ - name : Copy webview bundles to VS extension project
444+ shell : pwsh
445+ run : |
446+ $src = "vscode-extension\dist\webview"
447+ $dst = "visualstudio-extension\src\CopilotTokenTracker\webview"
448+ if (Test-Path $dst) { Remove-Item $dst -Recurse -Force }
449+ Copy-Item $src $dst -Recurse -Force
450+ Write-Host "✅ Copied webview bundles"
451+
452+ # ── Build Visual Studio extension (MSBuild / VSSDK) ───────────────────
453+
454+ - name : Add MSBuild to PATH
455+ uses : microsoft/setup-msbuild@6fb02220983dee41ce7ae257b6f4d8f9bf5ed4ce # v2.0.0
456+
457+ - name : Restore NuGet packages
458+ working-directory : visualstudio-extension
459+ run : nuget restore CopilotTokenTracker.sln
460+
461+ - name : Build solution (Release)
462+ working-directory : visualstudio-extension
463+ run : msbuild CopilotTokenTracker.sln /p:Configuration=Release /t:Build /v:minimal
464+
465+ # ── Collect the produced .vsix ─────────────────────────────────────────
466+
467+ - name : Find .vsix artifact
468+ id : vsix
469+ shell : pwsh
470+ run : |
471+ $vsix = Get-ChildItem -Path "visualstudio-extension" -Filter "*.vsix" -Recurse |
472+ Sort-Object LastWriteTime -Descending |
473+ Select-Object -First 1
474+ if (-not $vsix) { throw "No .vsix file produced" }
475+ $sizeMB = [math]::Round($vsix.Length / 1MB, 1)
476+ Write-Host "✅ VSIX: $($vsix.FullName) ($sizeMB MB)"
477+ echo "vsix_path=$($vsix.FullName)" >> $env:GITHUB_OUTPUT
478+ echo "vsix_name=$($vsix.Name)" >> $env:GITHUB_OUTPUT
479+
480+ # ── Upload .vsix to GitHub release ────────────────────────────────────
481+
482+ - name : Upload .vsix to GitHub release
483+ env :
484+ GITHUB_TOKEN : ${{ secrets.GITHUB_TOKEN }}
485+ shell : pwsh
486+ run : |
487+ $tag = "${{ needs.release.outputs.tag_name }}"
488+ $vsixPath = "${{ steps.vsix.outputs.vsix_path }}"
489+ Write-Host "Uploading $vsixPath to release $tag ..."
490+ gh release upload $tag $vsixPath --clobber
491+ if ($LASTEXITCODE -ne 0) { throw "Failed to upload .vsix to release" }
492+ Write-Host "✅ Uploaded to GitHub release"
493+
494+ # ── (Optional) Publish to VS Marketplace ──────────────────────────────
495+
496+ - name : Publish to Visual Studio Marketplace
497+ if : github.event_name == 'workflow_dispatch' && inputs.publish_marketplace == true
498+ shell : pwsh
499+ env :
500+ VS_MARKETPLACE_PAT : ${{ secrets.VS_MARKETPLACE_PAT }}
501+ run : |
502+ if (-not $env:VS_MARKETPLACE_PAT) {
503+ Write-Error "❌ VS_MARKETPLACE_PAT secret is not configured."
504+ exit 1
505+ }
506+ $vsix = "${{ steps.vsix.outputs.vsix_path }}"
507+ Write-Host "Publishing $vsix to Visual Studio Marketplace..."
508+ & npx @vscode/vsce publish --packagePath $vsix --pat $env:VS_MARKETPLACE_PAT
509+ if ($LASTEXITCODE -ne 0) { throw "Marketplace publish failed" }
510+ Write-Host "✅ Published to Visual Studio Marketplace"
511+
512+ # ── Summary ────────────────────────────────────────────────────────────
513+
514+ - name : Build summary
515+ if : always()
516+ shell : pwsh
517+ run : |
518+ $vsixName = "${{ steps.vsix.outputs.vsix_name }}"
519+ if ($vsixName) {
520+ @"
521+ ## ✅ Visual Studio Extension Built Successfully
522+
523+ | Item | Value |
524+ |------|-------|
525+ | VSIX | ``$vsixName`` |
526+ | Release | ``${{ needs.release.outputs.tag_name }}`` |
527+ | Commit | ``${{ github.sha }}`` |
528+ "@ >> $env:GITHUB_STEP_SUMMARY
529+ } else {
530+ "## ❌ Build failed — no .vsix produced" >> $env:GITHUB_STEP_SUMMARY
531+ }
532+
384533
0 commit comments