-
-
Notifications
You must be signed in to change notification settings - Fork 20
402 lines (345 loc) · 16.3 KB
/
visualstudio-build.yml
File metadata and controls
402 lines (345 loc) · 16.3 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
name: Visual Studio Extension - Build & Package
on:
push:
branches: [ main, develop ]
tags:
- 'vs/v*' # Tag push: build + GitHub release (and optionally publish to marketplace)
paths:
- 'visualstudio-extension/**'
- 'cli/**'
- 'vscode-extension/src/sessionDiscovery.ts'
- 'vscode-extension/src/sessionParser.ts'
- 'vscode-extension/src/tokenEstimation.ts'
- 'vscode-extension/src/maturityScoring.ts'
- 'vscode-extension/src/usageAnalysis.ts'
- 'vscode-extension/src/opencode.ts'
- 'vscode-extension/src/visualstudio.ts'
- 'vscode-extension/src/types.ts'
- 'vscode-extension/src/tokenEstimators.json'
- 'vscode-extension/src/modelPricing.json'
- 'vscode-extension/src/toolNames.json'
- '.github/workflows/visualstudio-build.yml'
pull_request:
branches: [ main, develop ]
paths:
- 'visualstudio-extension/**'
- 'cli/**'
- 'vscode-extension/src/sessionDiscovery.ts'
- 'vscode-extension/src/sessionParser.ts'
- 'vscode-extension/src/tokenEstimation.ts'
- 'vscode-extension/src/maturityScoring.ts'
- 'vscode-extension/src/usageAnalysis.ts'
- 'vscode-extension/src/opencode.ts'
- 'vscode-extension/src/visualstudio.ts'
- 'vscode-extension/src/types.ts'
- 'vscode-extension/src/tokenEstimators.json'
- 'vscode-extension/src/modelPricing.json'
- 'vscode-extension/src/toolNames.json'
- '.github/workflows/visualstudio-build.yml'
workflow_dispatch:
inputs:
publish_marketplace:
description: 'Publish to VS Marketplace after packaging'
required: false
default: false
type: boolean
permissions:
contents: read
jobs:
build:
name: Build & Package
# Windows required: Node.js SEA (bundle-exe.ps1) and MSBuild/VSSDK are Windows-only
runs-on: windows-latest
permissions:
contents: write # needed to create GitHub releases on tag triggers
outputs:
vsix_path: ${{ steps.vsix.outputs.vsix_path }}
vsix_name: ${{ steps.vsix.outputs.vsix_name }}
release_version: ${{ steps.release_version.outputs.version }}
is_tag: ${{ steps.release_version.outputs.is_tag }}
steps:
- name: Harden the runner (Audit all outbound calls)
uses: step-security/harden-runner@fa2e9d605c4eeb9fcad4c99c224cee0c6c7f3594 # v2.16.0
with:
egress-policy: audit
- name: Checkout code
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Extract release version
id: release_version
shell: pwsh
run: |
if ($env:GITHUB_REF -like 'refs/tags/vs/v*') {
$version = $env:GITHUB_REF -replace 'refs/tags/vs/v', ''
Write-Host "Tag trigger detected: vs/v$version"
echo "is_tag=true" >> $env:GITHUB_OUTPUT
echo "version=$version" >> $env:GITHUB_OUTPUT
} else {
# Read version from vsixmanifest for non-tag builds
[xml]$manifest = Get-Content 'visualstudio-extension/src/CopilotTokenTracker/source.extension.vsixmanifest'
$version = $manifest.PackageManifest.Metadata.Identity.Version
Write-Host "Non-tag build, manifest version: $version"
echo "is_tag=false" >> $env:GITHUB_OUTPUT
echo "version=$version" >> $env:GITHUB_OUTPUT
}
- name: Update vsixmanifest version (tag trigger only)
if: steps.release_version.outputs.is_tag == 'true'
shell: pwsh
run: |
$version = "${{ steps.release_version.outputs.version }}"
$manifestPath = 'visualstudio-extension/src/CopilotTokenTracker/source.extension.vsixmanifest'
[xml]$manifest = Get-Content $manifestPath
$oldVersion = $manifest.PackageManifest.Metadata.Identity.Version
$manifest.PackageManifest.Metadata.Identity.Version = $version
$manifest.Save((Resolve-Path $manifestPath))
Write-Host "✅ Updated vsixmanifest version: $oldVersion → $version"
- name: Setup Node.js
uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0
with:
node-version: '22.x'
# ── Install dependencies ────────────────────────────────────────────────
- name: Install vscode-extension dependencies
run: npm ci
working-directory: vscode-extension
- name: Install CLI dependencies
run: npm ci
working-directory: cli
# ── Build & validate CLI (js bundle) ───────────────────────────────────
- name: Build CLI (production bundle)
working-directory: cli
run: npm run build:production
- name: Validate CLI --help
working-directory: cli
run: node dist/cli.js --help
- name: Validate CLI new commands
working-directory: cli
run: |
node dist/cli.js chart --json
node dist/cli.js usage-analysis --json
# ── Bundle CLI as Windows .exe (Node.js SEA) ───────────────────────────
- name: Bundle CLI as single executable
working-directory: cli
shell: pwsh
run: |
& pwsh -NoProfile -File bundle-exe.ps1 -SkipBuild
if ($LASTEXITCODE -ne 0) { throw "bundle-exe.ps1 failed" }
- name: Verify CLI exe was produced
working-directory: cli
shell: pwsh
run: |
$exe = "dist\copilot-token-tracker.exe"
if (-not (Test-Path $exe)) { throw "CLI exe not found at $exe" }
$sizeMB = [math]::Round((Get-Item $exe).Length / 1MB, 1)
Write-Host "✅ CLI exe: $exe ($sizeMB MB)"
# ── Copy CLI bundle into VS extension project ──────────────────────────
- name: Copy CLI exe + wasm to cli-bundle/
shell: pwsh
run: |
$vsCliDir = "visualstudio-extension\src\CopilotTokenTracker\cli-bundle"
New-Item -ItemType Directory -Path $vsCliDir -Force | Out-Null
Copy-Item "cli\dist\copilot-token-tracker.exe" "$vsCliDir\copilot-token-tracker.exe" -Force
Copy-Item "cli\dist\sql-wasm.wasm" "$vsCliDir\sql-wasm.wasm" -Force
Write-Host "✅ Copied cli-bundle assets"
# ── Build webview bundles (chart.js, usage.js, etc.) ──────────────────
- name: Build VS Code extension webview bundles
working-directory: vscode-extension
run: npm run package
- name: Copy webview bundles to VS extension project
shell: pwsh
run: |
$src = "vscode-extension\dist\webview"
$dst = "visualstudio-extension\src\CopilotTokenTracker\webview"
if (Test-Path $dst) { Remove-Item $dst -Recurse -Force }
Copy-Item $src $dst -Recurse -Force
Write-Host "✅ Copied webview bundles:"
Get-ChildItem $dst -Filter "*.js" | ForEach-Object { Write-Host " $($_.Name)" }
# ── Build Visual Studio extension (MSBuild / VSSDK) ───────────────────
- name: Add MSBuild to PATH
uses: microsoft/setup-msbuild@30375c66a4eea26614e0d39710365f22f8b0af57 # v3
- name: Restore NuGet packages
working-directory: visualstudio-extension
run: nuget restore CopilotTokenTracker.sln
- name: Restore test project (SDK-style)
working-directory: visualstudio-extension
run: dotnet restore src/CopilotTokenTracker.Tests/CopilotTokenTracker.Tests.csproj
- name: Build solution (Release)
working-directory: visualstudio-extension
run: msbuild CopilotTokenTracker.sln /p:Configuration=Release /t:Build /v:minimal
# ── Run unit tests with coverage ──────────────────────────────────────
- name: Run unit tests with coverage
working-directory: visualstudio-extension
run: >-
dotnet test src/CopilotTokenTracker.Tests/CopilotTokenTracker.Tests.csproj
--no-build
--configuration Release
--logger "trx;LogFileName=test-results.trx"
--collect:"XPlat Code Coverage"
--results-directory TestResults
- name: Generate coverage summary
if: always()
shell: pwsh
working-directory: visualstudio-extension
run: |
$coverageFile = Get-ChildItem -Path TestResults -Filter "coverage.cobertura.xml" -Recurse |
Select-Object -First 1
if (-not $coverageFile) {
Write-Host "⚠️ No coverage file found"
"## ⚠️ Unit Test Coverage`nNo coverage data collected." >> $env:GITHUB_STEP_SUMMARY
exit 0
}
Write-Host "Coverage file: $($coverageFile.FullName)"
[xml]$xml = Get-Content $coverageFile.FullName
$lineRate = [double]$xml.coverage.'line-rate' * 100
$branchRate = [double]$xml.coverage.'branch-rate' * 100
$linePct = [math]::Round($lineRate, 1)
$branchPct = [math]::Round($branchRate, 1)
# Build per-package (namespace) breakdown
$packageRows = ""
foreach ($pkg in $xml.coverage.packages.package) {
$pkgName = $pkg.name
$pkgLine = [math]::Round([double]$pkg.'line-rate' * 100, 1)
$pkgBranch = [math]::Round([double]$pkg.'branch-rate' * 100, 1)
$packageRows += "| ``$pkgName`` | $pkgLine% | $pkgBranch% |`n"
}
# Build per-class breakdown for classes with notable coverage data
$classRows = ""
foreach ($pkg in $xml.coverage.packages.package) {
foreach ($cls in $pkg.classes.class) {
$clsName = $cls.name
$clsLine = [math]::Round([double]$cls.'line-rate' * 100, 1)
$clsBranch = [math]::Round([double]$cls.'branch-rate' * 100, 1)
$classRows += "| ``$clsName`` | $clsLine% | $clsBranch% |`n"
}
}
# Write step summary
$summary = @"
## 🧪 Visual Studio Extension — Unit Test Coverage
| Metric | Coverage |
|--------|----------|
| **Line Coverage** | $linePct% |
| **Branch Coverage** | $branchPct% |
### By Namespace
| Namespace | Line | Branch |
|-----------|------|--------|
$packageRows
<details><summary>By Class</summary>
| Class | Line | Branch |
|-------|------|--------|
$classRows
</details>
"@
$summary >> $env:GITHUB_STEP_SUMMARY
Write-Host "✅ Coverage: $linePct% line, $branchPct% branch"
- name: Upload test results
if: always()
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
with:
name: vs-test-results-${{ github.sha }}
path: visualstudio-extension/TestResults/
retention-days: 30
# ── Collect the produced .vsix ─────────────────────────────────────────
- name: Find .vsix artifact
id: vsix
shell: pwsh
run: |
$vsix = Get-ChildItem -Path "visualstudio-extension" -Filter "*.vsix" -Recurse |
Sort-Object LastWriteTime -Descending |
Select-Object -First 1
if (-not $vsix) { throw "No .vsix file produced" }
$sizeMB = [math]::Round($vsix.Length / 1MB, 1)
Write-Host "✅ VSIX: $($vsix.FullName) ($sizeMB MB)"
echo "vsix_path=$($vsix.FullName)" >> $env:GITHUB_OUTPUT
echo "vsix_name=$($vsix.Name)" >> $env:GITHUB_OUTPUT
- name: Upload .vsix as artifact
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
with:
name: copilot-token-tracker-vs-${{ github.sha }}
path: ${{ steps.vsix.outputs.vsix_path }}
retention-days: 30
# ── (Optional) Publish to VS Marketplace ──────────────────────────────
- name: Publish to Visual Studio Marketplace
if: github.event_name == 'workflow_dispatch' && inputs.publish_marketplace == true
shell: pwsh
env:
VS_MARKETPLACE_PAT: ${{ secrets.VS_MARKETPLACE_PAT }}
run: |
if (-not $env:VS_MARKETPLACE_PAT) {
Write-Error "❌ VS_MARKETPLACE_PAT secret is not configured."
exit 1
}
# VsixPublisher.exe is the correct tool for Visual Studio IDE extensions
# (not @vscode/vsce, which is for VS Code extensions only)
$vsixPublisher = Get-ChildItem "C:\Program Files\Microsoft Visual Studio" `
-Recurse -Filter "VsixPublisher.exe" -ErrorAction SilentlyContinue |
Select-Object -First 1
if (-not $vsixPublisher) {
Write-Error "❌ VsixPublisher.exe not found on this runner."
exit 1
}
Write-Host "Found: $($vsixPublisher.FullName)"
$vsix = "${{ steps.vsix.outputs.vsix_path }}"
$manifest = "visualstudio-extension/publish-manifest.json"
Write-Host "Publishing $vsix to Visual Studio Marketplace..."
& $vsixPublisher.FullName publish -payload $vsix -publishManifest $manifest -personalAccessToken $env:VS_MARKETPLACE_PAT
if ($LASTEXITCODE -ne 0) { throw "Marketplace publish failed" }
Write-Host "✅ Published to Visual Studio Marketplace"
# ── Summary ────────────────────────────────────────────────────────────
- name: Build summary
if: always()
shell: pwsh
run: |
$vsixName = "${{ steps.vsix.outputs.vsix_name }}"
if ($vsixName) {
@"
## ✅ Visual Studio Extension Built Successfully
| Item | Value |
|------|-------|
| VSIX | ``$vsixName`` |
| Version | ``${{ steps.release_version.outputs.version }}`` |
| Trigger | ``${{ github.event_name }}`` |
| Commit | ``${{ github.sha }}`` |
"@ >> $env:GITHUB_STEP_SUMMARY
} else {
"## ❌ Build failed — no .vsix produced" >> $env:GITHUB_STEP_SUMMARY
}
github-release:
name: Create GitHub Release
needs: build
if: startsWith(github.ref, 'refs/tags/vs/v')
runs-on: ubuntu-latest
permissions:
contents: write
steps:
- name: Harden the runner (Audit all outbound calls)
uses: step-security/harden-runner@fa2e9d605c4eeb9fcad4c99c224cee0c6c7f3594 # v2.16.0
with:
egress-policy: audit
- name: Download VSIX artifact
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v4.2.0
with:
name: copilot-token-tracker-vs-${{ github.sha }}
path: dist
- name: Generate release notes
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
TAG_NAME="vs/v${{ needs.build.outputs.release_version }}"
echo "Generating release notes for $TAG_NAME..."
if gh api repos/${{ github.repository }}/releases/generate-notes \
-f tag_name="${TAG_NAME}" \
--jq '.body' > /tmp/release_notes.md 2>/dev/null && [ -s /tmp/release_notes.md ]; then
echo "✅ Generated release notes from merged PRs"
else
echo "Visual Studio Extension v${{ needs.build.outputs.release_version }}" > /tmp/release_notes.md
fi
- name: Create GitHub Release
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
TAG_NAME="vs/v${{ needs.build.outputs.release_version }}"
VSIX_FILE=$(find dist -name '*.vsix' | head -n 1)
echo "Creating release $TAG_NAME with $VSIX_FILE"
gh release create "$TAG_NAME" \
--title "Visual Studio Extension v${{ needs.build.outputs.release_version }}" \
--notes-file /tmp/release_notes.md \
"$VSIX_FILE"
echo "✅ GitHub release created: $TAG_NAME"