microsoft/hve-core
Publicmirrored fromhttps://github.com/microsoft/hve-coreAvailable
scripts/extension/Package-Extension.ps1
1082lines ยท modecode
| 1 | #!/usr/bin/env pwsh |
| 2 | # Copyright (c) Microsoft Corporation. |
| 3 | # SPDX-License-Identifier: MIT |
| 4 | #Requires -Version 7.0 |
| 5 | |
| 6 | <# |
| 7 | .SYNOPSIS |
| 8 | Packages the HVE Core VS Code extension. |
| 9 | |
| 10 | .DESCRIPTION |
| 11 | This script packages the VS Code extension into a .vsix file. |
| 12 | It uses the version from package.json or a specified version. |
| 13 | Optionally adds a dev patch number for pre-release builds. |
| 14 | Supports VS Code Marketplace pre-release channel with -PreRelease switch. |
| 15 | |
| 16 | .PARAMETER Version |
| 17 | Optional. The version to use for the package. |
| 18 | If not specified, uses the version from package.json. |
| 19 | |
| 20 | .PARAMETER DevPatchNumber |
| 21 | Optional. Dev patch number to append (e.g., "123" creates "1.0.0-dev.123"). |
| 22 | |
| 23 | .PARAMETER ChangelogPath |
| 24 | Optional. Path to a changelog file to include in the package. |
| 25 | |
| 26 | .PARAMETER PreRelease |
| 27 | Optional. When specified, packages the extension for VS Code Marketplace pre-release channel. |
| 28 | Uses vsce --pre-release flag which marks the extension for the pre-release track. |
| 29 | |
| 30 | .PARAMETER Collection |
| 31 | Optional. Path to a collection manifest file (YAML or JSON). When specified, only |
| 32 | collection-filtered artifacts are copied and the output filename uses the |
| 33 | collection ID. |
| 34 | |
| 35 | .PARAMETER DryRun |
| 36 | Optional. Validates packaging orchestration without invoking vsce. |
| 37 | |
| 38 | .EXAMPLE |
| 39 | ./Package-Extension.ps1 |
| 40 | # Packages using version from package.json |
| 41 | |
| 42 | .EXAMPLE |
| 43 | ./Package-Extension.ps1 -Version "2.0.0" |
| 44 | # Packages with specific version |
| 45 | |
| 46 | .EXAMPLE |
| 47 | ./Package-Extension.ps1 -DevPatchNumber "123" |
| 48 | # Packages with dev version (e.g., 1.0.0-dev.123) |
| 49 | |
| 50 | .EXAMPLE |
| 51 | ./Package-Extension.ps1 -Version "1.1.0" -DevPatchNumber "456" |
| 52 | # Packages with specific dev version (1.1.0-dev.456) |
| 53 | |
| 54 | .EXAMPLE |
| 55 | ./Package-Extension.ps1 -PreRelease |
| 56 | # Packages for VS Code Marketplace pre-release channel |
| 57 | |
| 58 | .EXAMPLE |
| 59 | ./Package-Extension.ps1 -Version "1.1.0" -PreRelease |
| 60 | # Packages with ODD minor version for pre-release channel |
| 61 | |
| 62 | .EXAMPLE |
| 63 | . ./Package-Extension.ps1 |
| 64 | # Dot-source to import functions for testing without executing packaging. |
| 65 | #> |
| 66 | |
| 67 | [CmdletBinding()] |
| 68 | param( |
| 69 | [Parameter(Mandatory = $false)] |
| 70 | [string]$Version = "", |
| 71 | |
| 72 | [Parameter(Mandatory = $false)] |
| 73 | [string]$DevPatchNumber = "", |
| 74 | |
| 75 | [Parameter(Mandatory = $false)] |
| 76 | [string]$ChangelogPath = "", |
| 77 | |
| 78 | [Parameter(Mandatory = $false)] |
| 79 | [switch]$PreRelease, |
| 80 | |
| 81 | [Parameter(Mandatory = $false)] |
| 82 | [string]$Collection = "", |
| 83 | |
| 84 | [Parameter(Mandatory = $false)] |
| 85 | [Alias('dry-run')] |
| 86 | [switch]$DryRun |
| 87 | ) |
| 88 | |
| 89 | $ErrorActionPreference = 'Stop' |
| 90 | |
| 91 | Import-Module (Join-Path $PSScriptRoot "../lib/Modules/CIHelpers.psm1") -Force |
| 92 | Import-Module (Join-Path $PSScriptRoot "../collections/Modules/CollectionHelpers.psm1") -Force |
| 93 | |
| 94 | #region Pure Functions |
| 95 | |
| 96 | function Test-VsceAvailable { |
| 97 | <# |
| 98 | .SYNOPSIS |
| 99 | Checks if vsce or npx is available for packaging. |
| 100 | .OUTPUTS |
| 101 | Hashtable with IsAvailable, CommandType ('vsce', 'npx', or $null), and Command path. |
| 102 | #> |
| 103 | [CmdletBinding()] |
| 104 | [OutputType([hashtable])] |
| 105 | param() |
| 106 | |
| 107 | $vsceCmd = Get-Command vsce -ErrorAction SilentlyContinue |
| 108 | if ($vsceCmd) { |
| 109 | return @{ |
| 110 | IsAvailable = $true |
| 111 | CommandType = 'vsce' |
| 112 | Command = $vsceCmd.Source |
| 113 | } |
| 114 | } |
| 115 | |
| 116 | $npxCmd = Get-Command npx -ErrorAction SilentlyContinue |
| 117 | if ($npxCmd) { |
| 118 | return @{ |
| 119 | IsAvailable = $true |
| 120 | CommandType = 'npx' |
| 121 | Command = $npxCmd.Source |
| 122 | } |
| 123 | } |
| 124 | |
| 125 | return @{ |
| 126 | IsAvailable = $false |
| 127 | CommandType = $null |
| 128 | Command = $null |
| 129 | } |
| 130 | } |
| 131 | |
| 132 | function Test-ExtensionManifestValid { |
| 133 | <# |
| 134 | .SYNOPSIS |
| 135 | Validates an extension manifest (package.json content) for required fields and format. |
| 136 | .PARAMETER ManifestContent |
| 137 | The parsed package.json content as a PSObject. |
| 138 | .OUTPUTS |
| 139 | Hashtable with IsValid boolean and Errors array. |
| 140 | #> |
| 141 | [CmdletBinding()] |
| 142 | [OutputType([hashtable])] |
| 143 | param( |
| 144 | [Parameter(Mandatory = $true)] |
| 145 | [PSObject]$ManifestContent |
| 146 | ) |
| 147 | |
| 148 | $errors = @() |
| 149 | |
| 150 | # Check required fields |
| 151 | if (-not $ManifestContent.PSObject.Properties['name']) { |
| 152 | $errors += "Missing required 'name' field" |
| 153 | } |
| 154 | |
| 155 | if (-not $ManifestContent.PSObject.Properties['version']) { |
| 156 | $errors += "Missing required 'version' field" |
| 157 | } elseif ($ManifestContent.version -notmatch '^\d+\.\d+\.\d+') { |
| 158 | $errors += "Invalid version format: '$($ManifestContent.version)'. Expected semantic version (e.g., 1.0.0)" |
| 159 | } |
| 160 | |
| 161 | if (-not $ManifestContent.PSObject.Properties['publisher']) { |
| 162 | $errors += "Missing required 'publisher' field" |
| 163 | } |
| 164 | |
| 165 | if (-not $ManifestContent.PSObject.Properties['engines']) { |
| 166 | $errors += "Missing required 'engines' field" |
| 167 | } elseif (-not $ManifestContent.engines.PSObject.Properties['vscode']) { |
| 168 | $errors += "Missing required 'engines.vscode' field" |
| 169 | } |
| 170 | |
| 171 | return @{ |
| 172 | IsValid = ($errors.Count -eq 0) |
| 173 | Errors = $errors |
| 174 | } |
| 175 | } |
| 176 | |
| 177 | function Get-VscePackageCommand { |
| 178 | <# |
| 179 | .SYNOPSIS |
| 180 | Builds the vsce package command arguments without executing. |
| 181 | .PARAMETER CommandType |
| 182 | The type of command to use ('vsce' or 'npx'). |
| 183 | .PARAMETER PreRelease |
| 184 | Whether to include the --pre-release flag. |
| 185 | .OUTPUTS |
| 186 | Hashtable with Executable and Arguments array. |
| 187 | #> |
| 188 | [CmdletBinding()] |
| 189 | [OutputType([hashtable])] |
| 190 | param( |
| 191 | [Parameter(Mandatory = $true)] |
| 192 | [ValidateSet('vsce', 'npx')] |
| 193 | [string]$CommandType, |
| 194 | |
| 195 | [Parameter(Mandatory = $false)] |
| 196 | [switch]$PreRelease |
| 197 | ) |
| 198 | |
| 199 | $vsceArgs = @('package', '--no-dependencies') |
| 200 | if ($PreRelease) { |
| 201 | $vsceArgs += '--pre-release' |
| 202 | } |
| 203 | |
| 204 | if ($CommandType -eq 'npx') { |
| 205 | # --yes auto-confirms npx package installation for non-interactive CI environments |
| 206 | return @{ |
| 207 | Executable = 'npx' |
| 208 | Arguments = @('--yes', '@vscode/vsce@3.7.1') + $vsceArgs |
| 209 | } |
| 210 | } |
| 211 | |
| 212 | return @{ |
| 213 | Executable = 'vsce' |
| 214 | Arguments = $vsceArgs |
| 215 | } |
| 216 | } |
| 217 | |
| 218 | function New-PackagingResult { |
| 219 | <# |
| 220 | .SYNOPSIS |
| 221 | Creates a standardized packaging result object. |
| 222 | .PARAMETER Success |
| 223 | Whether the packaging operation succeeded. |
| 224 | .PARAMETER OutputPath |
| 225 | Path to the generated .vsix file (if successful). |
| 226 | .PARAMETER Version |
| 227 | The package version used. |
| 228 | .PARAMETER ErrorMessage |
| 229 | Error message if the operation failed. |
| 230 | .OUTPUTS |
| 231 | Hashtable with Success, OutputPath, Version, and ErrorMessage. |
| 232 | #> |
| 233 | [CmdletBinding()] |
| 234 | [OutputType([hashtable])] |
| 235 | param( |
| 236 | [Parameter(Mandatory = $true)] |
| 237 | [bool]$Success, |
| 238 | |
| 239 | [Parameter(Mandatory = $false)] |
| 240 | [string]$OutputPath = "", |
| 241 | |
| 242 | [Parameter(Mandatory = $false)] |
| 243 | [string]$Version = "", |
| 244 | |
| 245 | [Parameter(Mandatory = $false)] |
| 246 | [string]$ErrorMessage = "" |
| 247 | ) |
| 248 | |
| 249 | return @{ |
| 250 | Success = $Success |
| 251 | OutputPath = $OutputPath |
| 252 | Version = $Version |
| 253 | ErrorMessage = $ErrorMessage |
| 254 | } |
| 255 | } |
| 256 | |
| 257 | function Get-CollectionReadmePath { |
| 258 | <# |
| 259 | .SYNOPSIS |
| 260 | Resolves the collection-specific README path from a collection manifest. |
| 261 | .DESCRIPTION |
| 262 | Maps a collection manifest to its collection-specific README file. Returns |
| 263 | null when the collection is the flagship package (hve-core) or when no |
| 264 | matching collection README exists on disk. Supports both YAML and JSON |
| 265 | manifest formats. |
| 266 | .PARAMETER CollectionPath |
| 267 | Path to the collection manifest file (YAML or JSON). |
| 268 | .PARAMETER ExtensionDirectory |
| 269 | Path to the extension directory containing README files. |
| 270 | .OUTPUTS |
| 271 | String path to the collection README, or $null if not applicable. |
| 272 | #> |
| 273 | [CmdletBinding()] |
| 274 | [OutputType([string])] |
| 275 | param( |
| 276 | [Parameter(Mandatory = $true)] |
| 277 | [string]$CollectionPath, |
| 278 | |
| 279 | [Parameter(Mandatory = $true)] |
| 280 | [string]$ExtensionDirectory |
| 281 | ) |
| 282 | |
| 283 | $manifest = Get-CollectionManifest -CollectionPath $CollectionPath |
| 284 | $collectionId = $manifest.id |
| 285 | |
| 286 | # Flagship package uses the default README.md |
| 287 | if ($collectionId -eq 'hve-core') { |
| 288 | return $null |
| 289 | } |
| 290 | |
| 291 | $collectionReadmePath = Join-Path $ExtensionDirectory "README.$collectionId.md" |
| 292 | if (Test-Path $collectionReadmePath) { |
| 293 | return $collectionReadmePath |
| 294 | } |
| 295 | |
| 296 | return $null |
| 297 | } |
| 298 | |
| 299 | function Get-ResolvedPackageVersion { |
| 300 | <# |
| 301 | .SYNOPSIS |
| 302 | Resolves the package version from parameters or manifest content. |
| 303 | .PARAMETER SpecifiedVersion |
| 304 | Version specified via parameter (may be empty). |
| 305 | .PARAMETER ManifestVersion |
| 306 | Version from the package.json manifest. |
| 307 | .PARAMETER DevPatchNumber |
| 308 | Optional dev patch number to append. |
| 309 | .OUTPUTS |
| 310 | Hashtable with IsValid, BaseVersion, PackageVersion, and ErrorMessage. |
| 311 | #> |
| 312 | [CmdletBinding()] |
| 313 | [OutputType([hashtable])] |
| 314 | param( |
| 315 | [Parameter(Mandatory = $false)] |
| 316 | [string]$SpecifiedVersion = "", |
| 317 | |
| 318 | [Parameter(Mandatory = $true)] |
| 319 | [string]$ManifestVersion, |
| 320 | |
| 321 | [Parameter(Mandatory = $false)] |
| 322 | [string]$DevPatchNumber = "" |
| 323 | ) |
| 324 | |
| 325 | $baseVersion = "" |
| 326 | |
| 327 | if ($SpecifiedVersion -and $SpecifiedVersion -ne "") { |
| 328 | # Validate specified version format |
| 329 | if ($SpecifiedVersion -notmatch '^\d+\.\d+\.\d+$') { |
| 330 | return @{ |
| 331 | IsValid = $false |
| 332 | BaseVersion = "" |
| 333 | PackageVersion = "" |
| 334 | ErrorMessage = "Invalid version format specified: '$SpecifiedVersion'. Expected semantic version format (e.g., 1.0.0)." |
| 335 | } |
| 336 | } |
| 337 | $baseVersion = $SpecifiedVersion |
| 338 | } else { |
| 339 | # Validate manifest version |
| 340 | if ($ManifestVersion -notmatch '^\d+\.\d+\.\d+') { |
| 341 | return @{ |
| 342 | IsValid = $false |
| 343 | BaseVersion = "" |
| 344 | PackageVersion = "" |
| 345 | ErrorMessage = "Invalid version format in package.json: '$ManifestVersion'. Expected semantic version format (e.g., 1.0.0)." |
| 346 | } |
| 347 | } |
| 348 | # Extract base version |
| 349 | $ManifestVersion -match '^(\d+\.\d+\.\d+)' | Out-Null |
| 350 | $baseVersion = $Matches[1] |
| 351 | } |
| 352 | |
| 353 | # Apply dev patch number if provided |
| 354 | $packageVersion = if ($DevPatchNumber -and $DevPatchNumber -ne "") { |
| 355 | "$baseVersion-dev.$DevPatchNumber" |
| 356 | } else { |
| 357 | $baseVersion |
| 358 | } |
| 359 | |
| 360 | return @{ |
| 361 | IsValid = $true |
| 362 | BaseVersion = $baseVersion |
| 363 | PackageVersion = $packageVersion |
| 364 | ErrorMessage = "" |
| 365 | } |
| 366 | } |
| 367 | |
| 368 | function Test-PackagingInputsValid { |
| 369 | <# |
| 370 | .SYNOPSIS |
| 371 | Validates all required paths for extension packaging. |
| 372 | .DESCRIPTION |
| 373 | Pure function that checks existence of ExtensionDirectory, package.json, |
| 374 | .github directory, and CIHelpers.psm1 module. Returns resolved paths for use |
| 375 | by downstream functions. |
| 376 | .PARAMETER ExtensionDirectory |
| 377 | Absolute path to the extension directory. |
| 378 | .PARAMETER RepoRoot |
| 379 | Absolute path to the repository root. |
| 380 | .OUTPUTS |
| 381 | Hashtable with IsValid, Errors array, and resolved paths. |
| 382 | #> |
| 383 | [CmdletBinding()] |
| 384 | [OutputType([hashtable])] |
| 385 | param( |
| 386 | [Parameter(Mandatory = $true)] |
| 387 | [string]$ExtensionDirectory, |
| 388 | |
| 389 | [Parameter(Mandatory = $true)] |
| 390 | [string]$RepoRoot |
| 391 | ) |
| 392 | |
| 393 | $errors = @() |
| 394 | |
| 395 | if (-not (Test-Path $ExtensionDirectory)) { |
| 396 | $errors += "Extension directory not found: $ExtensionDirectory" |
| 397 | } |
| 398 | |
| 399 | $packageJsonPath = Join-Path $ExtensionDirectory "package.json" |
| 400 | if (-not (Test-Path $packageJsonPath)) { |
| 401 | $errors += "package.json not found: $packageJsonPath" |
| 402 | } |
| 403 | |
| 404 | $githubDir = Join-Path $RepoRoot ".github" |
| 405 | if (-not (Test-Path $githubDir)) { |
| 406 | $errors += ".github directory not found: $githubDir" |
| 407 | } |
| 408 | |
| 409 | $ciHelpersPath = Join-Path $RepoRoot "scripts/lib/Modules/CIHelpers.psm1" |
| 410 | if (-not (Test-Path $ciHelpersPath)) { |
| 411 | $errors += "CIHelpers.psm1 not found: $ciHelpersPath" |
| 412 | } |
| 413 | |
| 414 | return @{ |
| 415 | IsValid = ($errors.Count -eq 0) |
| 416 | Errors = $errors |
| 417 | PackageJsonPath = $packageJsonPath |
| 418 | GitHubDir = $githubDir |
| 419 | CIHelpersPath = $ciHelpersPath |
| 420 | } |
| 421 | } |
| 422 | |
| 423 | function Get-PackagingDirectorySpec { |
| 424 | <# |
| 425 | .SYNOPSIS |
| 426 | Returns specification for directories to copy during packaging. |
| 427 | .DESCRIPTION |
| 428 | Pure function that defines source to destination mappings without performing I/O. |
| 429 | Each spec includes Source, Destination, Required flag, and optional IsFile flag. |
| 430 | .PARAMETER RepoRoot |
| 431 | Absolute path to the repository root. |
| 432 | .PARAMETER ExtensionDirectory |
| 433 | Absolute path to the extension directory. |
| 434 | .OUTPUTS |
| 435 | Array of hashtables with Source, Destination, Required, and IsFile properties. |
| 436 | #> |
| 437 | [CmdletBinding()] |
| 438 | [OutputType([hashtable[]])] |
| 439 | param( |
| 440 | [Parameter(Mandatory = $true)] |
| 441 | [string]$RepoRoot, |
| 442 | |
| 443 | [Parameter(Mandatory = $true)] |
| 444 | [string]$ExtensionDirectory |
| 445 | ) |
| 446 | |
| 447 | return @( |
| 448 | @{ |
| 449 | Source = Join-Path $RepoRoot ".github" |
| 450 | Destination = Join-Path $ExtensionDirectory ".github" |
| 451 | IsFile = $false |
| 452 | }, |
| 453 | @{ |
| 454 | Source = Join-Path $RepoRoot "scripts/lib/Modules/CIHelpers.psm1" |
| 455 | Destination = Join-Path $ExtensionDirectory "scripts/lib/Modules/CIHelpers.psm1" |
| 456 | IsFile = $true |
| 457 | }, |
| 458 | @{ |
| 459 | Source = Join-Path $RepoRoot "docs/templates" |
| 460 | Destination = Join-Path $ExtensionDirectory "docs/templates" |
| 461 | IsFile = $false |
| 462 | } |
| 463 | ) |
| 464 | } |
| 465 | |
| 466 | #endregion Pure Functions |
| 467 | |
| 468 | #region I/O Functions |
| 469 | |
| 470 | function Copy-CollectionArtifacts { |
| 471 | <# |
| 472 | .SYNOPSIS |
| 473 | Copies only collection-filtered artifacts to the extension directory. |
| 474 | .DESCRIPTION |
| 475 | Reads the prepared package.json to determine which artifacts were selected |
| 476 | by collection filtering, then copies only those files instead of the entire |
| 477 | .github directory. |
| 478 | .PARAMETER RepoRoot |
| 479 | Absolute path to the repository root. |
| 480 | .PARAMETER ExtensionDirectory |
| 481 | Absolute path to the extension directory. |
| 482 | .PARAMETER PrepareResult |
| 483 | Result hashtable from Invoke-PrepareExtension. Reserved for future collection metadata handling. |
| 484 | #> |
| 485 | [CmdletBinding()] |
| 486 | [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSReviewUnusedParameter', 'PrepareResult', Justification = 'Reserved for future collection metadata handling')] |
| 487 | param( |
| 488 | [Parameter(Mandatory = $true)] |
| 489 | [string]$RepoRoot, |
| 490 | |
| 491 | [Parameter(Mandatory = $true)] |
| 492 | [string]$ExtensionDirectory, |
| 493 | |
| 494 | [Parameter(Mandatory = $true)] |
| 495 | [hashtable]$PrepareResult |
| 496 | ) |
| 497 | |
| 498 | $preparedPkgJson = Get-Content -Path (Join-Path $ExtensionDirectory "package.json") -Raw | ConvertFrom-Json |
| 499 | |
| 500 | # Copy filtered agents |
| 501 | if ($preparedPkgJson.contributes.chatAgents) { |
| 502 | foreach ($agent in $preparedPkgJson.contributes.chatAgents) { |
| 503 | $srcPath = Join-Path $RepoRoot ($agent.path -replace '^\.[\\/]', '') |
| 504 | if (-not (Test-Path $srcPath)) { |
| 505 | Write-Warning "Skipping missing collection artifact: $srcPath (referenced by contributes.chatAgents in package.json)" |
| 506 | continue |
| 507 | } |
| 508 | $destPath = Join-Path $ExtensionDirectory ($agent.path -replace '^\.[\\/]', '') |
| 509 | $destDir = Split-Path $destPath -Parent |
| 510 | New-Item -Path $destDir -ItemType Directory -Force | Out-Null |
| 511 | Copy-Item -Path $srcPath -Destination $destPath -Force |
| 512 | } |
| 513 | } |
| 514 | |
| 515 | # Copy filtered prompts |
| 516 | if ($preparedPkgJson.contributes.chatPromptFiles) { |
| 517 | foreach ($prompt in $preparedPkgJson.contributes.chatPromptFiles) { |
| 518 | $srcPath = Join-Path $RepoRoot ($prompt.path -replace '^\.[\\/]', '') |
| 519 | if (-not (Test-Path $srcPath)) { |
| 520 | Write-Warning "Skipping missing collection artifact: $srcPath (referenced by contributes.chatPromptFiles in package.json)" |
| 521 | continue |
| 522 | } |
| 523 | $destPath = Join-Path $ExtensionDirectory ($prompt.path -replace '^\.[\\/]', '') |
| 524 | $destDir = Split-Path $destPath -Parent |
| 525 | New-Item -Path $destDir -ItemType Directory -Force | Out-Null |
| 526 | Copy-Item -Path $srcPath -Destination $destPath -Force |
| 527 | } |
| 528 | } |
| 529 | |
| 530 | # Copy filtered instructions |
| 531 | if ($preparedPkgJson.contributes.chatInstructions) { |
| 532 | foreach ($instr in $preparedPkgJson.contributes.chatInstructions) { |
| 533 | $srcPath = Join-Path $RepoRoot ($instr.path -replace '^\.[\\/]', '') |
| 534 | if (-not (Test-Path $srcPath)) { |
| 535 | Write-Warning "Skipping missing collection artifact: $srcPath (referenced by contributes.chatInstructions in package.json)" |
| 536 | continue |
| 537 | } |
| 538 | $destPath = Join-Path $ExtensionDirectory ($instr.path -replace '^\.[\\/]', '') |
| 539 | $destDir = Split-Path $destPath -Parent |
| 540 | New-Item -Path $destDir -ItemType Directory -Force | Out-Null |
| 541 | Copy-Item -Path $srcPath -Destination $destPath -Force |
| 542 | } |
| 543 | } |
| 544 | |
| 545 | # Copy filtered skills |
| 546 | if ($preparedPkgJson.contributes.chatSkills) { |
| 547 | foreach ($skill in $preparedPkgJson.contributes.chatSkills) { |
| 548 | $srcPath = Join-Path $RepoRoot ($skill.path -replace '^\.[\\/]', '') |
| 549 | if (-not (Test-Path $srcPath)) { |
| 550 | Write-Warning "Skipping missing collection artifact: $srcPath (referenced by contributes.chatSkills in package.json)" |
| 551 | continue |
| 552 | } |
| 553 | # Copy the full skill directory, not just SKILL.md |
| 554 | $srcDir = Split-Path $srcPath -Parent |
| 555 | $destPath = Join-Path $ExtensionDirectory ($skill.path -replace '^\.[\\/]', '') |
| 556 | $destDir = Split-Path $destPath -Parent |
| 557 | $destParent = Split-Path $destDir -Parent |
| 558 | New-Item -Path $destParent -ItemType Directory -Force | Out-Null |
| 559 | Copy-Item -Path $srcDir -Destination $destParent -Recurse -Force |
| 560 | |
| 561 | # Remove co-located test directories from packaged skills |
| 562 | Get-ChildItem -Path $destDir -Directory -Filter 'tests' -Recurse -ErrorAction SilentlyContinue | |
| 563 | Remove-Item -Recurse -Force |
| 564 | } |
| 565 | } |
| 566 | } |
| 567 | |
| 568 | function Set-CollectionReadme { |
| 569 | <# |
| 570 | .SYNOPSIS |
| 571 | Swaps or restores the collection-specific README for extension packaging. |
| 572 | .DESCRIPTION |
| 573 | In swap mode, backs up the original README.md and copies the collection |
| 574 | README in its place. In restore mode, copies the backup back and removes it. |
| 575 | .PARAMETER ExtensionDirectory |
| 576 | Path to the extension directory. |
| 577 | .PARAMETER CollectionReadmePath |
| 578 | Path to the collection-specific README file. Required for Swap operation. |
| 579 | .PARAMETER Operation |
| 580 | Either 'Swap' to replace README.md with collection content, or 'Restore' |
| 581 | to revert README.md from backup. |
| 582 | #> |
| 583 | [CmdletBinding()] |
| 584 | param( |
| 585 | [Parameter(Mandatory = $true)] |
| 586 | [string]$ExtensionDirectory, |
| 587 | |
| 588 | [Parameter(Mandatory = $false)] |
| 589 | [string]$CollectionReadmePath = "", |
| 590 | |
| 591 | [Parameter(Mandatory = $true)] |
| 592 | [ValidateSet('Swap', 'Restore')] |
| 593 | [string]$Operation |
| 594 | ) |
| 595 | |
| 596 | $readmePath = Join-Path $ExtensionDirectory "README.md" |
| 597 | $backupPath = Join-Path $ExtensionDirectory "README.md.bak" |
| 598 | |
| 599 | if ($Operation -eq 'Swap') { |
| 600 | if (-not $CollectionReadmePath -or $CollectionReadmePath -eq "") { |
| 601 | Write-Warning "No collection README path provided for swap operation" |
| 602 | return |
| 603 | } |
| 604 | Copy-Item -Path $readmePath -Destination $backupPath -Force |
| 605 | Copy-Item -Path $CollectionReadmePath -Destination $readmePath -Force |
| 606 | Write-Host " Swapped README.md with $(Split-Path $CollectionReadmePath -Leaf)" -ForegroundColor Green |
| 607 | } |
| 608 | elseif ($Operation -eq 'Restore') { |
| 609 | if (Test-Path $backupPath) { |
| 610 | Copy-Item -Path $backupPath -Destination $readmePath -Force |
| 611 | Remove-Item -Path $backupPath -Force |
| 612 | Write-Host " Restored original README.md" -ForegroundColor Green |
| 613 | } |
| 614 | } |
| 615 | } |
| 616 | |
| 617 | function Invoke-VsceCommand { |
| 618 | <# |
| 619 | .SYNOPSIS |
| 620 | Executes vsce package command with platform-appropriate wrapper. |
| 621 | .DESCRIPTION |
| 622 | Abstracts platform-specific execution of vsce/npx commands. On Windows with npx, |
| 623 | uses cmd /c to avoid PowerShell misinterpreting @ in @vscode/vsce as splatting. |
| 624 | The UseWindowsWrapper parameter enables deterministic platform behavior in tests. |
| 625 | .PARAMETER Executable |
| 626 | The executable to run ('vsce' or 'npx'). |
| 627 | .PARAMETER Arguments |
| 628 | Array of arguments to pass to the executable. |
| 629 | .PARAMETER WorkingDirectory |
| 630 | Directory to execute the command in. |
| 631 | .PARAMETER UseWindowsWrapper |
| 632 | When true and Executable is 'npx', uses cmd /c wrapper for Windows compatibility. |
| 633 | .OUTPUTS |
| 634 | Hashtable with Success boolean and ExitCode integer. |
| 635 | #> |
| 636 | [CmdletBinding()] |
| 637 | [OutputType([hashtable])] |
| 638 | param( |
| 639 | [Parameter(Mandatory = $true)] |
| 640 | [string]$Executable, |
| 641 | |
| 642 | [Parameter(Mandatory = $true)] |
| 643 | [string[]]$Arguments, |
| 644 | |
| 645 | [Parameter(Mandatory = $true)] |
| 646 | [string]$WorkingDirectory, |
| 647 | |
| 648 | [Parameter(Mandatory = $false)] |
| 649 | [switch]$UseWindowsWrapper |
| 650 | ) |
| 651 | |
| 652 | Push-Location $WorkingDirectory |
| 653 | try { |
| 654 | $global:LASTEXITCODE = 0 |
| 655 | |
| 656 | if ($UseWindowsWrapper -and $Executable -eq 'npx') { |
| 657 | $cmdArgs = @('/c', 'npx') + $Arguments |
| 658 | & cmd @cmdArgs |
| 659 | } else { |
| 660 | & $Executable @Arguments |
| 661 | } |
| 662 | |
| 663 | return @{ |
| 664 | Success = ($LASTEXITCODE -eq 0) |
| 665 | ExitCode = $LASTEXITCODE |
| 666 | } |
| 667 | } |
| 668 | finally { |
| 669 | Pop-Location |
| 670 | } |
| 671 | } |
| 672 | |
| 673 | function Remove-PackagingArtifacts { |
| 674 | <# |
| 675 | .SYNOPSIS |
| 676 | Removes temporary directories created during packaging. |
| 677 | .DESCRIPTION |
| 678 | Cleans up directories copied to the extension folder during the packaging process. |
| 679 | Silently skips directories that do not exist. |
| 680 | .PARAMETER ExtensionDirectory |
| 681 | Absolute path to the extension directory. |
| 682 | .PARAMETER DirectoryNames |
| 683 | Array of directory names to remove. Defaults to .github, docs, scripts. |
| 684 | #> |
| 685 | [CmdletBinding()] |
| 686 | param( |
| 687 | [Parameter(Mandatory = $true)] |
| 688 | [string]$ExtensionDirectory, |
| 689 | |
| 690 | [Parameter(Mandatory = $false)] |
| 691 | [string[]]$DirectoryNames = @(".github", "docs", "scripts") |
| 692 | ) |
| 693 | |
| 694 | foreach ($dir in $DirectoryNames) { |
| 695 | $dirPath = Join-Path $ExtensionDirectory $dir |
| 696 | if (Test-Path $dirPath) { |
| 697 | Remove-Item -Path $dirPath -Recurse -Force |
| 698 | Write-Host " Removed $dir" -ForegroundColor Gray |
| 699 | } |
| 700 | } |
| 701 | } |
| 702 | |
| 703 | function Restore-PackageJsonVersion { |
| 704 | <# |
| 705 | .SYNOPSIS |
| 706 | Restores original version in package.json after packaging. |
| 707 | .DESCRIPTION |
| 708 | Writes the original version back to package.json if it was temporarily modified |
| 709 | during packaging. Safely handles null inputs by returning early. |
| 710 | .PARAMETER PackageJsonPath |
| 711 | Absolute path to the package.json file. |
| 712 | .PARAMETER PackageJson |
| 713 | The parsed package.json object to modify. |
| 714 | .PARAMETER OriginalVersion |
| 715 | The original version string to restore. |
| 716 | #> |
| 717 | [CmdletBinding()] |
| 718 | param( |
| 719 | [Parameter(Mandatory = $false)] |
| 720 | [AllowNull()] |
| 721 | [string]$PackageJsonPath, |
| 722 | |
| 723 | [Parameter(Mandatory = $false)] |
| 724 | [AllowNull()] |
| 725 | [PSObject]$PackageJson, |
| 726 | |
| 727 | [Parameter(Mandatory = $false)] |
| 728 | [AllowNull()] |
| 729 | [string]$OriginalVersion |
| 730 | ) |
| 731 | |
| 732 | # Handle null coercion: PowerShell converts $null to empty string for [string] params |
| 733 | if ([string]::IsNullOrEmpty($OriginalVersion) -or $null -eq $PackageJson -or [string]::IsNullOrEmpty($PackageJsonPath)) { |
| 734 | return |
| 735 | } |
| 736 | |
| 737 | try { |
| 738 | $PackageJson.version = $OriginalVersion |
| 739 | $PackageJson | ConvertTo-Json -Depth 10 | Set-Content -Path $PackageJsonPath -Encoding UTF8NoBOM |
| 740 | Write-Host " Version restored to: $OriginalVersion" -ForegroundColor Green |
| 741 | } |
| 742 | catch { |
| 743 | Write-Warning "Failed to restore original package.json version to '$OriginalVersion': $($_.Exception.Message)" |
| 744 | } |
| 745 | } |
| 746 | |
| 747 | #endregion I/O Functions |
| 748 | |
| 749 | #region Orchestration Functions |
| 750 | |
| 751 | function Invoke-PackageExtension { |
| 752 | <# |
| 753 | .SYNOPSIS |
| 754 | Orchestrates VS Code extension packaging with full error handling. |
| 755 | .DESCRIPTION |
| 756 | Executes the complete packaging workflow: validates paths, resolves version, |
| 757 | prepares directories, invokes vsce, and handles cleanup. |
| 758 | .PARAMETER ExtensionDirectory |
| 759 | Absolute path to the extension directory containing package.json. |
| 760 | .PARAMETER RepoRoot |
| 761 | Absolute path to the repository root directory. |
| 762 | .PARAMETER Version |
| 763 | Optional explicit version string (e.g., "1.2.3"). |
| 764 | .PARAMETER DevPatchNumber |
| 765 | Optional dev build patch number for pre-release versions. |
| 766 | .PARAMETER ChangelogPath |
| 767 | Optional path to changelog file to include in package. |
| 768 | .PARAMETER PreRelease |
| 769 | Switch to mark the package as a pre-release version. |
| 770 | .PARAMETER Collection |
| 771 | Optional path to a collection manifest file (YAML or JSON). When specified, only |
| 772 | collection-filtered artifacts are copied and the output filename uses the |
| 773 | collection ID. |
| 774 | .PARAMETER DryRun |
| 775 | When specified, validates packaging orchestration without invoking vsce. |
| 776 | .OUTPUTS |
| 777 | Hashtable with Success, OutputPath, Version, and ErrorMessage properties. |
| 778 | #> |
| 779 | [CmdletBinding()] |
| 780 | [OutputType([hashtable])] |
| 781 | param( |
| 782 | [Parameter(Mandatory = $true)] |
| 783 | [ValidateNotNullOrEmpty()] |
| 784 | [string]$ExtensionDirectory, |
| 785 | |
| 786 | [Parameter(Mandatory = $true)] |
| 787 | [ValidateNotNullOrEmpty()] |
| 788 | [string]$RepoRoot, |
| 789 | |
| 790 | [Parameter(Mandatory = $false)] |
| 791 | [string]$Version = "", |
| 792 | |
| 793 | [Parameter(Mandatory = $false)] |
| 794 | [string]$DevPatchNumber = "", |
| 795 | |
| 796 | [Parameter(Mandatory = $false)] |
| 797 | [string]$ChangelogPath = "", |
| 798 | |
| 799 | [Parameter(Mandatory = $false)] |
| 800 | [switch]$PreRelease, |
| 801 | |
| 802 | [Parameter(Mandatory = $false)] |
| 803 | [string]$Collection = "", |
| 804 | |
| 805 | [Parameter(Mandatory = $false)] |
| 806 | [switch]$DryRun |
| 807 | ) |
| 808 | |
| 809 | $dirsToClean = @(".github", "docs", "scripts") |
| 810 | $originalVersion = $null |
| 811 | $packageJson = $null |
| 812 | $PackageJsonPath = $null |
| 813 | $packageVersion = $null |
| 814 | $versionWasModified = $false |
| 815 | |
| 816 | try { |
| 817 | # Validate all inputs using pure function |
| 818 | $inputValidation = Test-PackagingInputsValid -ExtensionDirectory $ExtensionDirectory -RepoRoot $RepoRoot |
| 819 | if (-not $inputValidation.IsValid) { |
| 820 | return New-PackagingResult -Success $false -ErrorMessage ($inputValidation.Errors -join '; ') |
| 821 | } |
| 822 | |
| 823 | $PackageJsonPath = $inputValidation.PackageJsonPath |
| 824 | |
| 825 | Write-Host "๐ฆ HVE Core Extension Packager" -ForegroundColor Cyan |
| 826 | Write-Host "==============================" -ForegroundColor Cyan |
| 827 | Write-Host "" |
| 828 | |
| 829 | # Read and validate package.json |
| 830 | Write-Host "๐ Reading package.json..." -ForegroundColor Yellow |
| 831 | try { |
| 832 | $packageJson = Get-Content -Path $PackageJsonPath -Raw | ConvertFrom-Json |
| 833 | } |
| 834 | catch { |
| 835 | return New-PackagingResult -Success $false -ErrorMessage "Failed to parse package.json: $($_.Exception.Message)" |
| 836 | } |
| 837 | |
| 838 | $manifestValidation = Test-ExtensionManifestValid -ManifestContent $packageJson |
| 839 | if (-not $manifestValidation.IsValid) { |
| 840 | return New-PackagingResult -Success $false -ErrorMessage "Invalid package.json: $($manifestValidation.Errors -join '; ')" |
| 841 | } |
| 842 | |
| 843 | # Resolve version using pure function |
| 844 | $versionResult = Get-ResolvedPackageVersion ` |
| 845 | -SpecifiedVersion $Version ` |
| 846 | -ManifestVersion $packageJson.version ` |
| 847 | -DevPatchNumber $DevPatchNumber |
| 848 | |
| 849 | if (-not $versionResult.IsValid) { |
| 850 | return New-PackagingResult -Success $false -ErrorMessage $versionResult.ErrorMessage |
| 851 | } |
| 852 | |
| 853 | $packageVersion = $versionResult.PackageVersion |
| 854 | Write-Host " Using version: $packageVersion" -ForegroundColor Green |
| 855 | |
| 856 | # Handle temporary version update for dev builds |
| 857 | $originalVersion = $packageJson.version |
| 858 | |
| 859 | if ($packageVersion -ne $originalVersion) { |
| 860 | Write-Host "" |
| 861 | Write-Host "๐ Temporarily updating package.json version..." -ForegroundColor Yellow |
| 862 | $packageJson.version = $packageVersion |
| 863 | $packageJson | ConvertTo-Json -Depth 10 | Set-Content -Path $PackageJsonPath -Encoding UTF8NoBOM |
| 864 | Write-Host " Version: $originalVersion -> $packageVersion" -ForegroundColor Green |
| 865 | $versionWasModified = $true |
| 866 | } |
| 867 | |
| 868 | # Handle changelog if provided |
| 869 | if ($ChangelogPath -and $ChangelogPath -ne "") { |
| 870 | Write-Host "" |
| 871 | Write-Host "๐ Processing changelog..." -ForegroundColor Yellow |
| 872 | |
| 873 | if (Test-Path $ChangelogPath) { |
| 874 | $changelogDest = Join-Path $ExtensionDirectory "CHANGELOG.md" |
| 875 | Copy-Item -Path $ChangelogPath -Destination $changelogDest -Force |
| 876 | Write-Host " Copied changelog to extension directory" -ForegroundColor Green |
| 877 | } |
| 878 | else { |
| 879 | Write-Warning "Changelog file not found: $ChangelogPath" |
| 880 | } |
| 881 | } |
| 882 | |
| 883 | # Prepare extension directory |
| 884 | Write-Host "" |
| 885 | Write-Host "๐๏ธ Preparing extension directory..." -ForegroundColor Yellow |
| 886 | |
| 887 | # Clean any existing copied directories |
| 888 | foreach ($dir in $dirsToClean) { |
| 889 | $dirPath = Join-Path $ExtensionDirectory $dir |
| 890 | if (Test-Path $dirPath) { |
| 891 | Remove-Item -Path $dirPath -Recurse -Force |
| 892 | Write-Host " Cleaned existing $dir directory" -ForegroundColor Gray |
| 893 | } |
| 894 | } |
| 895 | |
| 896 | # Get and execute copy specifications |
| 897 | $copySpecs = Get-PackagingDirectorySpec -RepoRoot $RepoRoot -ExtensionDirectory $ExtensionDirectory |
| 898 | |
| 899 | if ($Collection -and $Collection -ne "") { |
| 900 | # Collection mode: copy only filtered artifacts for .github content |
| 901 | Write-Host " Using collection-filtered artifact copy..." -ForegroundColor Gray |
| 902 | |
| 903 | # Copy non-.github specs normally |
| 904 | foreach ($spec in $copySpecs) { |
| 905 | if ($spec.Source -like "*/.github*" -or $spec.Source -like "*\.github*") { |
| 906 | continue |
| 907 | } |
| 908 | $specName = Split-Path $spec.Source -Leaf |
| 909 | Write-Host " Copying $specName..." -ForegroundColor Gray |
| 910 | |
| 911 | if ($spec.IsFile) { |
| 912 | $parentDir = Split-Path $spec.Destination -Parent |
| 913 | New-Item -Path $parentDir -ItemType Directory -Force | Out-Null |
| 914 | Copy-Item -Path $spec.Source -Destination $spec.Destination -Force |
| 915 | } else { |
| 916 | $parentDir = Split-Path $spec.Destination -Parent |
| 917 | if (-not (Test-Path $parentDir)) { |
| 918 | New-Item -Path $parentDir -ItemType Directory -Force | Out-Null |
| 919 | } |
| 920 | Copy-Item -Path $spec.Source -Destination $spec.Destination -Recurse -Force |
| 921 | } |
| 922 | } |
| 923 | |
| 924 | # Copy collection-specific artifacts |
| 925 | Copy-CollectionArtifacts -RepoRoot $RepoRoot -ExtensionDirectory $ExtensionDirectory -PrepareResult @{} |
| 926 | } else { |
| 927 | # Full mode: copy everything as before |
| 928 | foreach ($spec in $copySpecs) { |
| 929 | $specName = Split-Path $spec.Source -Leaf |
| 930 | Write-Host " Copying $specName..." -ForegroundColor Gray |
| 931 | |
| 932 | if ($spec.IsFile) { |
| 933 | $parentDir = Split-Path $spec.Destination -Parent |
| 934 | New-Item -Path $parentDir -ItemType Directory -Force | Out-Null |
| 935 | Copy-Item -Path $spec.Source -Destination $spec.Destination -Force |
| 936 | } else { |
| 937 | $parentDir = Split-Path $spec.Destination -Parent |
| 938 | if (-not (Test-Path $parentDir)) { |
| 939 | New-Item -Path $parentDir -ItemType Directory -Force | Out-Null |
| 940 | } |
| 941 | Copy-Item -Path $spec.Source -Destination $spec.Destination -Recurse -Force |
| 942 | } |
| 943 | } |
| 944 | } |
| 945 | |
| 946 | Write-Host " โ
Extension directory prepared" -ForegroundColor Green |
| 947 | |
| 948 | # Swap collection README if collection specifies one |
| 949 | if ($Collection -and $Collection -ne "") { |
| 950 | $collectionReadmePath = Get-CollectionReadmePath -CollectionPath $Collection -ExtensionDirectory $ExtensionDirectory |
| 951 | if ($collectionReadmePath) { |
| 952 | Write-Host "" |
| 953 | Write-Host "๐ Applying collection README..." -ForegroundColor Yellow |
| 954 | Set-CollectionReadme -ExtensionDirectory $ExtensionDirectory -CollectionReadmePath $collectionReadmePath -Operation Swap |
| 955 | } |
| 956 | } |
| 957 | |
| 958 | if ($DryRun) { |
| 959 | Write-Host "" |
| 960 | Write-Host "๐งช Dry-run complete: packaging orchestration validated without VSIX creation." -ForegroundColor Yellow |
| 961 | return New-PackagingResult -Success $true -Version $packageVersion |
| 962 | } |
| 963 | |
| 964 | # Check vsce availability using pure function |
| 965 | $vsceAvailability = Test-VsceAvailable |
| 966 | if (-not $vsceAvailability.IsAvailable) { |
| 967 | return New-PackagingResult -Success $false -ErrorMessage "Neither vsce nor npx found. Please install @vscode/vsce globally or ensure npm is available." |
| 968 | } |
| 969 | |
| 970 | # Build vsce command using pure function |
| 971 | $vsceCommand = Get-VscePackageCommand -CommandType $vsceAvailability.CommandType -PreRelease:$PreRelease |
| 972 | |
| 973 | # Package extension |
| 974 | Write-Host "" |
| 975 | Write-Host "๐ฆ Packaging extension..." -ForegroundColor Yellow |
| 976 | |
| 977 | if ($PreRelease) { |
| 978 | Write-Host " Mode: Pre-release channel" -ForegroundColor Magenta |
| 979 | } |
| 980 | |
| 981 | Write-Host " Using $($vsceAvailability.CommandType)..." -ForegroundColor Gray |
| 982 | |
| 983 | # Execute vsce command using I/O function |
| 984 | $useWindowsWrapper = ($IsWindows -or $env:OS -eq 'Windows_NT') -and ($vsceCommand.Executable -eq 'npx') |
| 985 | $vsceResult = Invoke-VsceCommand ` |
| 986 | -Executable $vsceCommand.Executable ` |
| 987 | -Arguments $vsceCommand.Arguments ` |
| 988 | -WorkingDirectory $ExtensionDirectory ` |
| 989 | -UseWindowsWrapper:$useWindowsWrapper |
| 990 | |
| 991 | if (-not $vsceResult.Success) { |
| 992 | return New-PackagingResult -Success $false -ErrorMessage "vsce package command failed with exit code $($vsceResult.ExitCode)" |
| 993 | } |
| 994 | |
| 995 | # Find the generated vsix file |
| 996 | $vsixFile = Get-ChildItem -Path $ExtensionDirectory -Filter "*.vsix" | Sort-Object LastWriteTime -Descending | Select-Object -First 1 |
| 997 | |
| 998 | if (-not $vsixFile) { |
| 999 | return New-PackagingResult -Success $false -ErrorMessage "No .vsix file found after packaging" |
| 1000 | } |
| 1001 | |
| 1002 | Write-Host "" |
| 1003 | Write-Host "โ
Extension packaged successfully!" -ForegroundColor Green |
| 1004 | Write-Host " File: $($vsixFile.Name)" -ForegroundColor Cyan |
| 1005 | Write-Host " Size: $([math]::Round($vsixFile.Length / 1KB, 2)) KB" -ForegroundColor Cyan |
| 1006 | Write-Host " Version: $packageVersion" -ForegroundColor Cyan |
| 1007 | |
| 1008 | # Output for CI/CD consumption |
| 1009 | Set-CIOutput -Name 'version' -Value $packageVersion |
| 1010 | Set-CIOutput -Name 'vsix-file' -Value $vsixFile.Name |
| 1011 | Set-CIOutput -Name 'pre-release' -Value $PreRelease.IsPresent |
| 1012 | |
| 1013 | Write-Host "" |
| 1014 | Write-Host "๐ Done!" -ForegroundColor Green |
| 1015 | Write-Host "" |
| 1016 | |
| 1017 | return New-PackagingResult -Success $true -OutputPath $vsixFile.FullName -Version $packageVersion |
| 1018 | } |
| 1019 | catch { |
| 1020 | return New-PackagingResult -Success $false -ErrorMessage $_.Exception.Message |
| 1021 | } |
| 1022 | finally { |
| 1023 | # Restore canonical package.json from collection template backup |
| 1024 | $backupPath = Join-Path $ExtensionDirectory "package.json.bak" |
| 1025 | if (Test-Path $backupPath) { |
| 1026 | Copy-Item -Path $backupPath -Destination $PackageJsonPath -Force |
| 1027 | Remove-Item -Path $backupPath -Force |
| 1028 | Write-Host " Restored canonical package.json from backup" -ForegroundColor Green |
| 1029 | |
| 1030 | # Re-read restored package.json for downstream restore steps |
| 1031 | $packageJson = Get-Content -Path $PackageJsonPath -Raw | ConvertFrom-Json |
| 1032 | } |
| 1033 | |
| 1034 | # Restore collection README if it was swapped |
| 1035 | Set-CollectionReadme -ExtensionDirectory $ExtensionDirectory -Operation Restore |
| 1036 | |
| 1037 | # Cleanup copied directories using I/O function |
| 1038 | Write-Host "" |
| 1039 | Write-Host "๐งน Cleaning up..." -ForegroundColor Yellow |
| 1040 | Remove-PackagingArtifacts -ExtensionDirectory $ExtensionDirectory -DirectoryNames $dirsToClean |
| 1041 | |
| 1042 | # Restore original version if it was changed using I/O function |
| 1043 | if ($versionWasModified) { |
| 1044 | Write-Host "" |
| 1045 | Write-Host "๐ Restoring original package.json version..." -ForegroundColor Yellow |
| 1046 | Restore-PackageJsonVersion -PackageJsonPath $PackageJsonPath -PackageJson $packageJson -OriginalVersion $originalVersion |
| 1047 | } |
| 1048 | } |
| 1049 | } |
| 1050 | |
| 1051 | #endregion Orchestration Functions |
| 1052 | |
| 1053 | #region Main Execution |
| 1054 | if ($MyInvocation.InvocationName -ne '.') { |
| 1055 | try { |
| 1056 | $ScriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path |
| 1057 | $RepoRoot = (Get-Item "$ScriptDir/../..").FullName |
| 1058 | $ExtensionDir = Join-Path $RepoRoot "extension" |
| 1059 | |
| 1060 | $result = Invoke-PackageExtension ` |
| 1061 | -ExtensionDirectory $ExtensionDir ` |
| 1062 | -RepoRoot $RepoRoot ` |
| 1063 | -Version $Version ` |
| 1064 | -DevPatchNumber $DevPatchNumber ` |
| 1065 | -ChangelogPath $ChangelogPath ` |
| 1066 | -PreRelease:$PreRelease ` |
| 1067 | -Collection $Collection ` |
| 1068 | -DryRun:$DryRun |
| 1069 | |
| 1070 | if (-not $result.Success) { |
| 1071 | Write-Error -ErrorAction Continue $result.ErrorMessage |
| 1072 | exit 1 |
| 1073 | } |
| 1074 | exit 0 |
| 1075 | } |
| 1076 | catch { |
| 1077 | Write-Error -ErrorAction Continue "Package-Extension failed: $($_.Exception.Message)" |
| 1078 | Write-CIAnnotation -Message $_.Exception.Message -Level Error |
| 1079 | exit 1 |
| 1080 | } |
| 1081 | } |
| 1082 | #endregion Main Execution |
| 1083 | |