microsoft/hve-core

Public

mirrored fromhttps://github.com/microsoft/hve-coreAvailable

CodeCommitsIssuesPull requestsActionsInsightsSecurity
feat/add-pester-code-coverage

Branches

Tags

  • No tags available.
0Branches0Tags
Go to file
Add file
Code

Clone

HTTPS

Download ZIP

scripts/extension/Package-Extension.ps1

568lines ยท modecode

1#!/usr/bin/env pwsh
2
3<#
4.SYNOPSIS
5 Packages the HVE Core VS Code extension.
6
7.DESCRIPTION
8 This script packages the VS Code extension into a .vsix file.
9 It uses the version from package.json or a specified version.
10 Optionally adds a dev patch number for pre-release builds.
11 Supports VS Code Marketplace pre-release channel with -PreRelease switch.
12
13.PARAMETER Version
14 Optional. The version to use for the package.
15 If not specified, uses the version from package.json.
16
17.PARAMETER DevPatchNumber
18 Optional. Dev patch number to append (e.g., "123" creates "1.0.0-dev.123").
19
20.PARAMETER ChangelogPath
21 Optional. Path to a changelog file to include in the package.
22
23.PARAMETER PreRelease
24 Optional. When specified, packages the extension for VS Code Marketplace pre-release channel.
25 Uses vsce --pre-release flag which marks the extension for the pre-release track.
26
27.EXAMPLE
28 ./Package-Extension.ps1
29 # Packages using version from package.json
30
31.EXAMPLE
32 ./Package-Extension.ps1 -Version "2.0.0"
33 # Packages with specific version
34
35.EXAMPLE
36 ./Package-Extension.ps1 -DevPatchNumber "123"
37 # Packages with dev version (e.g., 1.0.0-dev.123)
38
39.EXAMPLE
40 ./Package-Extension.ps1 -Version "1.1.0" -DevPatchNumber "456"
41 # Packages with specific dev version (1.1.0-dev.456)
42
43.EXAMPLE
44 ./Package-Extension.ps1 -PreRelease
45 # Packages for VS Code Marketplace pre-release channel
46
47.EXAMPLE
48 ./Package-Extension.ps1 -Version "1.1.0" -PreRelease
49 # Packages with ODD minor version for pre-release channel
50
51.EXAMPLE
52 . ./Package-Extension.ps1
53 # Dot-source to import functions for testing without executing packaging.
54#>
55
56[CmdletBinding()]
57param(
58 [Parameter(Mandatory = $false)]
59 [string]$Version = "",
60
61 [Parameter(Mandatory = $false)]
62 [string]$DevPatchNumber = "",
63
64 [Parameter(Mandatory = $false)]
65 [string]$ChangelogPath = "",
66
67 [Parameter(Mandatory = $false)]
68 [switch]$PreRelease
69)
70
71#region Pure Functions
72
73function Test-VsceAvailable {
74 <#
75 .SYNOPSIS
76 Checks if vsce or npx is available for packaging.
77 .OUTPUTS
78 Hashtable with IsAvailable, CommandType ('vsce', 'npx', or $null), and Command path.
79 #>
80 [CmdletBinding()]
81 [OutputType([hashtable])]
82 param()
83
84 $vsceCmd = Get-Command vsce -ErrorAction SilentlyContinue
85 if ($vsceCmd) {
86 return @{
87 IsAvailable = $true
88 CommandType = 'vsce'
89 Command = $vsceCmd.Source
90 }
91 }
92
93 $npxCmd = Get-Command npx -ErrorAction SilentlyContinue
94 if ($npxCmd) {
95 return @{
96 IsAvailable = $true
97 CommandType = 'npx'
98 Command = $npxCmd.Source
99 }
100 }
101
102 return @{
103 IsAvailable = $false
104 CommandType = $null
105 Command = $null
106 }
107}
108
109function Get-ExtensionOutputPath {
110 <#
111 .SYNOPSIS
112 Constructs the expected .vsix output path from extension directory and version.
113 .PARAMETER ExtensionDirectory
114 The path to the extension directory.
115 .PARAMETER ExtensionName
116 The name of the extension (from package.json).
117 .PARAMETER PackageVersion
118 The version string to use in the filename.
119 .OUTPUTS
120 String path to the expected .vsix file.
121 #>
122 [CmdletBinding()]
123 [OutputType([string])]
124 param(
125 [Parameter(Mandatory = $true)]
126 [string]$ExtensionDirectory,
127
128 [Parameter(Mandatory = $true)]
129 [string]$ExtensionName,
130
131 [Parameter(Mandatory = $true)]
132 [string]$PackageVersion
133 )
134
135 $vsixFileName = "$ExtensionName-$PackageVersion.vsix"
136 return Join-Path $ExtensionDirectory $vsixFileName
137}
138
139function Test-ExtensionManifestValid {
140 <#
141 .SYNOPSIS
142 Validates an extension manifest (package.json content) for required fields and format.
143 .PARAMETER ManifestContent
144 The parsed package.json content as a PSObject.
145 .OUTPUTS
146 Hashtable with IsValid boolean and Errors array.
147 #>
148 [CmdletBinding()]
149 [OutputType([hashtable])]
150 param(
151 [Parameter(Mandatory = $true)]
152 [PSObject]$ManifestContent
153 )
154
155 $errors = @()
156
157 # Check required fields
158 if (-not $ManifestContent.PSObject.Properties['name']) {
159 $errors += "Missing required 'name' field"
160 }
161
162 if (-not $ManifestContent.PSObject.Properties['version']) {
163 $errors += "Missing required 'version' field"
164 } elseif ($ManifestContent.version -notmatch '^\d+\.\d+\.\d+') {
165 $errors += "Invalid version format: '$($ManifestContent.version)'. Expected semantic version (e.g., 1.0.0)"
166 }
167
168 if (-not $ManifestContent.PSObject.Properties['publisher']) {
169 $errors += "Missing required 'publisher' field"
170 }
171
172 if (-not $ManifestContent.PSObject.Properties['engines']) {
173 $errors += "Missing required 'engines' field"
174 } elseif (-not $ManifestContent.engines.PSObject.Properties['vscode']) {
175 $errors += "Missing required 'engines.vscode' field"
176 }
177
178 return @{
179 IsValid = ($errors.Count -eq 0)
180 Errors = $errors
181 }
182}
183
184function Get-VscePackageCommand {
185 <#
186 .SYNOPSIS
187 Builds the vsce package command arguments without executing.
188 .PARAMETER CommandType
189 The type of command to use ('vsce' or 'npx').
190 .PARAMETER PreRelease
191 Whether to include the --pre-release flag.
192 .OUTPUTS
193 Hashtable with Executable and Arguments array.
194 #>
195 [CmdletBinding()]
196 [OutputType([hashtable])]
197 param(
198 [Parameter(Mandatory = $true)]
199 [ValidateSet('vsce', 'npx')]
200 [string]$CommandType,
201
202 [Parameter(Mandatory = $false)]
203 [switch]$PreRelease
204 )
205
206 $vsceArgs = @('package', '--no-dependencies')
207 if ($PreRelease) {
208 $vsceArgs += '--pre-release'
209 }
210
211 if ($CommandType -eq 'npx') {
212 return @{
213 Executable = 'npx'
214 Arguments = @('@vscode/vsce') + $vsceArgs
215 }
216 }
217
218 return @{
219 Executable = 'vsce'
220 Arguments = $vsceArgs
221 }
222}
223
224function New-PackagingResult {
225 <#
226 .SYNOPSIS
227 Creates a standardized packaging result object.
228 .PARAMETER Success
229 Whether the packaging operation succeeded.
230 .PARAMETER OutputPath
231 Path to the generated .vsix file (if successful).
232 .PARAMETER Version
233 The package version used.
234 .PARAMETER ErrorMessage
235 Error message if the operation failed.
236 .OUTPUTS
237 Hashtable with Success, OutputPath, Version, and ErrorMessage.
238 #>
239 [CmdletBinding()]
240 [OutputType([hashtable])]
241 param(
242 [Parameter(Mandatory = $true)]
243 [bool]$Success,
244
245 [Parameter(Mandatory = $false)]
246 [string]$OutputPath = "",
247
248 [Parameter(Mandatory = $false)]
249 [string]$Version = "",
250
251 [Parameter(Mandatory = $false)]
252 [string]$ErrorMessage = ""
253 )
254
255 return @{
256 Success = $Success
257 OutputPath = $OutputPath
258 Version = $Version
259 ErrorMessage = $ErrorMessage
260 }
261}
262
263function Get-ResolvedPackageVersion {
264 <#
265 .SYNOPSIS
266 Resolves the package version from parameters or manifest content.
267 .PARAMETER SpecifiedVersion
268 Version specified via parameter (may be empty).
269 .PARAMETER ManifestVersion
270 Version from the package.json manifest.
271 .PARAMETER DevPatchNumber
272 Optional dev patch number to append.
273 .OUTPUTS
274 Hashtable with IsValid, BaseVersion, PackageVersion, and ErrorMessage.
275 #>
276 [CmdletBinding()]
277 [OutputType([hashtable])]
278 param(
279 [Parameter(Mandatory = $false)]
280 [string]$SpecifiedVersion = "",
281
282 [Parameter(Mandatory = $true)]
283 [string]$ManifestVersion,
284
285 [Parameter(Mandatory = $false)]
286 [string]$DevPatchNumber = ""
287 )
288
289 $baseVersion = ""
290
291 if ($SpecifiedVersion -and $SpecifiedVersion -ne "") {
292 # Validate specified version format
293 if ($SpecifiedVersion -notmatch '^\d+\.\d+\.\d+$') {
294 return @{
295 IsValid = $false
296 BaseVersion = ""
297 PackageVersion = ""
298 ErrorMessage = "Invalid version format specified: '$SpecifiedVersion'. Expected semantic version format (e.g., 1.0.0)."
299 }
300 }
301 $baseVersion = $SpecifiedVersion
302 } else {
303 # Validate manifest version
304 if ($ManifestVersion -notmatch '^\d+\.\d+\.\d+') {
305 return @{
306 IsValid = $false
307 BaseVersion = ""
308 PackageVersion = ""
309 ErrorMessage = "Invalid version format in package.json: '$ManifestVersion'. Expected semantic version format (e.g., 1.0.0)."
310 }
311 }
312 # Extract base version
313 $ManifestVersion -match '^(\d+\.\d+\.\d+)' | Out-Null
314 $baseVersion = $Matches[1]
315 }
316
317 # Apply dev patch number if provided
318 $packageVersion = if ($DevPatchNumber -and $DevPatchNumber -ne "") {
319 "$baseVersion-dev.$DevPatchNumber"
320 } else {
321 $baseVersion
322 }
323
324 return @{
325 IsValid = $true
326 BaseVersion = $baseVersion
327 PackageVersion = $packageVersion
328 ErrorMessage = ""
329 }
330}
331
332#endregion Pure Functions
333
334#region Entry Point
335
336# Only execute main logic when run directly, not when dot-sourced
337if ($MyInvocation.InvocationName -ne '.') {
338 $ErrorActionPreference = "Stop"
339
340# Determine script and repo paths
341$ScriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path
342$RepoRoot = (Get-Item "$ScriptDir/../..").FullName
343$ExtensionDir = Join-Path $RepoRoot "extension"
344$GitHubDir = Join-Path $RepoRoot ".github"
345$PackageJsonPath = Join-Path $ExtensionDir "package.json"
346
347Write-Host "๐Ÿ“ฆ HVE Core Extension Packager" -ForegroundColor Cyan
348Write-Host "==============================" -ForegroundColor Cyan
349Write-Host ""
350
351# Verify paths exist
352if (-not (Test-Path $ExtensionDir)) {
353 Write-Error "Extension directory not found: $ExtensionDir"
354 exit 1
355}
356
357if (-not (Test-Path $PackageJsonPath)) {
358 Write-Error "package.json not found: $PackageJsonPath"
359 exit 1
360}
361
362if (-not (Test-Path $GitHubDir)) {
363 Write-Error ".github directory not found: $GitHubDir"
364 exit 1
365}
366
367# Read current package.json
368Write-Host "๐Ÿ“– Reading package.json..." -ForegroundColor Yellow
369try {
370 $packageJson = Get-Content -Path $PackageJsonPath -Raw | ConvertFrom-Json
371} catch {
372 Write-Error "Failed to parse package.json: $_`nPlease check $PackageJsonPath for JSON syntax errors."
373 exit 1
374}
375
376# Validate package.json has required version field
377if (-not $packageJson.PSObject.Properties['version']) {
378 Write-Error "package.json is missing required 'version' field"
379 exit 1
380}
381
382# Determine version
383$baseVersion = if ($Version -and $Version -ne "") {
384 # Validate specified version format
385 if ($Version -notmatch '^\d+\.\d+\.\d+$') {
386 Write-Error "Invalid version format specified: '$Version'. Expected semantic version format (e.g., 1.0.0).`nPre-release suffixes like '-dev.123' should be added via -DevPatchNumber parameter, not in the version itself."
387 exit 1
388 }
389 $Version
390} else {
391 # Use version from package.json
392 $currentVersion = $packageJson.version
393 if ($currentVersion -notmatch '^\d+\.\d+\.\d+') {
394 $errorMessage = @(
395 "Invalid version format in package.json: '$currentVersion'.",
396 "Expected semantic version format (e.g., 1.0.0).",
397 "Pre-release suffixes should not be committed to package.json.",
398 "Use -DevPatchNumber parameter to add '-dev.N' suffix during packaging."
399 ) -join "`n"
400 Write-Error $errorMessage
401 exit 1
402 }
403 # Extract base version (validation above ensures this will match)
404 $currentVersion -match '^(\d+\.\d+\.\d+)' | Out-Null
405 $Matches[1]
406}
407
408# Apply dev patch number if provided
409$packageVersion = if ($DevPatchNumber -and $DevPatchNumber -ne "") {
410 "$baseVersion-dev.$DevPatchNumber"
411} else {
412 $baseVersion
413}
414
415Write-Host " Using version: $packageVersion" -ForegroundColor Green
416
417# Handle temporary version update for dev builds
418$originalVersion = $packageJson.version
419
420if ($packageVersion -ne $originalVersion) {
421 Write-Host ""
422 Write-Host "๐Ÿ“ Temporarily updating package.json version..." -ForegroundColor Yellow
423 $packageJson.version = $packageVersion
424 $packageJson | ConvertTo-Json -Depth 10 | Set-Content -Path $PackageJsonPath -Encoding UTF8NoBOM
425 Write-Host " Version: $originalVersion -> $packageVersion" -ForegroundColor Green
426}
427
428# Handle changelog if provided
429if ($ChangelogPath -and $ChangelogPath -ne "") {
430 Write-Host ""
431 Write-Host "๐Ÿ“‹ Processing changelog..." -ForegroundColor Yellow
432
433 if (Test-Path $ChangelogPath) {
434 $changelogDest = Join-Path $ExtensionDir "CHANGELOG.md"
435 Copy-Item -Path $ChangelogPath -Destination $changelogDest -Force
436 Write-Host " Copied changelog to extension directory" -ForegroundColor Green
437 } else {
438 Write-Warning "Changelog file not found: $ChangelogPath"
439 }
440}
441
442# Prepare extension directory
443Write-Host ""
444Write-Host "๐Ÿ—‚๏ธ Preparing extension directory..." -ForegroundColor Yellow
445
446# Clean any existing copied directories
447$dirsToClean = @(".github", "docs", "scripts")
448foreach ($dir in $dirsToClean) {
449 $dirPath = Join-Path $ExtensionDir $dir
450 if (Test-Path $dirPath) {
451 Remove-Item -Path $dirPath -Recurse -Force
452 Write-Host " Cleaned existing $dir directory" -ForegroundColor Gray
453 }
454}
455
456# Copy required directories
457Write-Host " Copying .github..." -ForegroundColor Gray
458Copy-Item -Path "$RepoRoot/.github" -Destination "$ExtensionDir/.github" -Recurse
459
460Write-Host " Copying scripts/dev-tools..." -ForegroundColor Gray
461New-Item -Path "$ExtensionDir/scripts" -ItemType Directory -Force | Out-Null
462Copy-Item -Path "$RepoRoot/scripts/dev-tools" -Destination "$ExtensionDir/scripts/dev-tools" -Recurse
463
464Write-Host " Copying docs/templates..." -ForegroundColor Gray
465New-Item -Path "$ExtensionDir/docs" -ItemType Directory -Force | Out-Null
466Copy-Item -Path "$RepoRoot/docs/templates" -Destination "$ExtensionDir/docs/templates" -Recurse
467
468Write-Host " โœ… Extension directory prepared" -ForegroundColor Green
469
470# Package extension
471Write-Host ""
472Write-Host "๐Ÿ“ฆ Packaging extension..." -ForegroundColor Yellow
473
474if ($PreRelease) {
475 Write-Host " Mode: Pre-release channel" -ForegroundColor Magenta
476}
477
478# Initialize vsixFile variable to avoid scope issues
479$vsixFile = $null
480
481# Build vsce arguments
482$vsceArgs = @('package', '--no-dependencies')
483if ($PreRelease) {
484 $vsceArgs += '--pre-release'
485}
486
487Push-Location $ExtensionDir
488
489try {
490 # Check if vsce is available
491 $vsceCmd = Get-Command vsce -ErrorAction SilentlyContinue
492 if (-not $vsceCmd) {
493 $vsceCmd = Get-Command npx -ErrorAction SilentlyContinue
494 if ($vsceCmd) {
495 Write-Host " Using npx @vscode/vsce..." -ForegroundColor Gray
496 & npx @vscode/vsce @vsceArgs
497 } else {
498 Write-Error "Neither vsce nor npx found. Please install @vscode/vsce globally or ensure npm is available."
499 exit 1
500 }
501 } else {
502 Write-Host " Using vsce..." -ForegroundColor Gray
503 & vsce @vsceArgs
504 }
505
506 if ($LASTEXITCODE -ne 0) {
507 Write-Error "Failed to package extension"
508 exit 1
509 }
510
511 # Find the generated vsix file
512 $vsixFile = Get-ChildItem -Path $ExtensionDir -Filter "*.vsix" | Sort-Object LastWriteTime -Descending | Select-Object -First 1
513
514 if ($vsixFile) {
515 Write-Host ""
516 Write-Host "โœ… Extension packaged successfully!" -ForegroundColor Green
517 Write-Host " File: $($vsixFile.Name)" -ForegroundColor Cyan
518 Write-Host " Size: $([math]::Round($vsixFile.Length / 1KB, 2)) KB" -ForegroundColor Cyan
519 Write-Host " Version: $packageVersion" -ForegroundColor Cyan
520 } else {
521 Write-Error "No .vsix file found after packaging"
522 exit 1
523 }
524
525} finally {
526 Pop-Location
527
528 # Cleanup copied directories
529 Write-Host ""
530 Write-Host "๐Ÿงน Cleaning up..." -ForegroundColor Yellow
531
532 foreach ($dir in $dirsToClean) {
533 $dirPath = Join-Path $ExtensionDir $dir
534 if (Test-Path $dirPath) {
535 Remove-Item -Path $dirPath -Recurse -Force
536 Write-Host " Removed $dir" -ForegroundColor Gray
537 }
538 }
539
540 # Restore original version if it was changed
541 if ($packageVersion -ne $originalVersion) {
542 Write-Host ""
543 Write-Host "๐Ÿ”„ Restoring original package.json version..." -ForegroundColor Yellow
544 $packageJson.version = $originalVersion
545 $packageJson | ConvertTo-Json -Depth 10 | Set-Content -Path $PackageJsonPath -Encoding UTF8NoBOM
546 Write-Host " Version restored to: $originalVersion" -ForegroundColor Green
547 }
548}
549
550 Write-Host ""
551 Write-Host "๐ŸŽ‰ Done!" -ForegroundColor Green
552 Write-Host ""
553
554 # Output for CI/CD consumption
555 if ($env:GITHUB_OUTPUT) {
556 if ($vsixFile) {
557 "version=$packageVersion" | Out-File -FilePath $env:GITHUB_OUTPUT -Append -Encoding utf8
558 "vsix-file=$($vsixFile.Name)" | Out-File -FilePath $env:GITHUB_OUTPUT -Append -Encoding utf8
559 "pre-release=$($PreRelease.IsPresent)" | Out-File -FilePath $env:GITHUB_OUTPUT -Append -Encoding utf8
560 } else {
561 Write-Warning "Cannot write GITHUB_OUTPUT: vsix file not available"
562 }
563 }
564
565 exit 0
566}
567
568#endregion Entry Point
569