microsoft/hve-core

Public

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

CodeCommitsIssuesPull requestsActionsInsightsSecurity
docs/621-ai-artifacts

Branches

Tags

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

Clone

HTTPS

Download ZIP

scripts/plugins/Generate-Plugins.ps1

336lines · 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 Generates Copilot CLI plugin directories from collection manifests.
9
10.DESCRIPTION
11 Reads collection YAML manifests from the collections/ directory and generates
12 plugin directories under plugins/ with symlinks to source artifacts, plugin.json
13 manifests, and auto-generated README files.
14
15 Supports generating all plugins or specific collections. Use -Refresh to
16 regenerate existing plugins (deletes and recreates).
17
18.PARAMETER CollectionIds
19 Optional. Array of collection IDs to generate. Generates all when omitted.
20
21.PARAMETER Refresh
22 Optional. Deletes and recreates existing plugin directories.
23
24.PARAMETER DryRun
25 Optional. Shows what would be done without making changes.
26
27.PARAMETER Channel
28 Optional. Release channel controlling eligible item maturities.
29 Stable includes only stable items. PreRelease includes stable, preview,
30 and experimental. Deprecated is excluded from both channels.
31
32.EXAMPLE
33 ./Generate-Plugins.ps1
34 # Generates all plugins (default: all + refresh)
35
36.EXAMPLE
37 ./Generate-Plugins.ps1 -CollectionIds rpi,github
38 # Generates only the rpi and github plugins
39
40.EXAMPLE
41 ./Generate-Plugins.ps1 -DryRun
42 # Shows what would be generated without making changes
43
44.EXAMPLE
45 ./Generate-Plugins.ps1 -Channel Stable
46 # Generates plugins with stable-only items
47
48.NOTES
49 Dependencies: PowerShell-Yaml module, scripts/plugins/Modules/PluginHelpers.psm1
50#>
51
52[CmdletBinding()]
53param(
54 [Parameter(Mandatory = $false)]
55 [string[]]$CollectionIds,
56
57 [Parameter(Mandatory = $false)]
58 [switch]$Refresh,
59
60 [Parameter(Mandatory = $false)]
61 [switch]$DryRun,
62
63 [Parameter(Mandatory = $false)]
64 [ValidateSet('Stable', 'PreRelease')]
65 [string]$Channel = 'PreRelease'
66)
67
68$ErrorActionPreference = 'Stop'
69
70Import-Module (Join-Path $PSScriptRoot 'Modules/PluginHelpers.psm1') -Force
71Import-Module (Join-Path $PSScriptRoot '../lib/Modules/CIHelpers.psm1') -Force
72
73#region Orchestration
74
75function Get-AllowedCollectionMaturities {
76 <#
77 .SYNOPSIS
78 Returns allowed collection item maturities for a channel.
79
80 .PARAMETER Channel
81 Release channel ('Stable' or 'PreRelease').
82
83 .OUTPUTS
84 [string[]] Allowed maturity values for collection items.
85 #>
86 [CmdletBinding()]
87 [OutputType([string[]])]
88 param(
89 [Parameter(Mandatory = $true)]
90 [ValidateSet('Stable', 'PreRelease')]
91 [string]$Channel
92 )
93
94 if ($Channel -eq 'Stable') {
95 return @('stable')
96 }
97
98 return @('stable', 'preview', 'experimental')
99}
100
101function Select-CollectionItemsByChannel {
102 <#
103 .SYNOPSIS
104 Filters collection items by channel using item maturity metadata.
105
106 .PARAMETER Collection
107 Collection manifest hashtable.
108
109 .PARAMETER Channel
110 Release channel ('Stable' or 'PreRelease').
111
112 .OUTPUTS
113 [hashtable] Collection clone with filtered items.
114 #>
115 [CmdletBinding()]
116 [OutputType([hashtable])]
117 param(
118 [Parameter(Mandatory = $true)]
119 [hashtable]$Collection,
120
121 [Parameter(Mandatory = $true)]
122 [ValidateSet('Stable', 'PreRelease')]
123 [string]$Channel
124 )
125
126 $allowedMaturities = Get-AllowedCollectionMaturities -Channel $Channel
127 $filteredItems = @()
128
129 foreach ($item in $Collection.items) {
130 $effectiveMaturity = Resolve-CollectionItemMaturity -Maturity $item.maturity
131 if ($allowedMaturities -contains $effectiveMaturity) {
132 $filteredItems += $item
133 }
134 }
135
136 $filteredCollection = @{}
137 foreach ($key in $Collection.Keys) {
138 $filteredCollection[$key] = $Collection[$key]
139 }
140 $filteredCollection['items'] = $filteredItems
141
142 return $filteredCollection
143}
144
145function Invoke-PluginGeneration {
146 <#
147 .SYNOPSIS
148 Orchestrates plugin directory generation from collection manifests.
149
150 .DESCRIPTION
151 Loads collection manifests from the collections/ directory, optionally
152 filters to specified IDs, and generates plugin directory structures
153 under plugins/. Each plugin receives symlinks to source artifacts,
154 a plugin.json manifest, and an auto-generated README.
155
156 .PARAMETER RepoRoot
157 Absolute path to the repository root directory.
158
159 .PARAMETER CollectionIds
160 Optional. Array of collection IDs to generate. Generates all when omitted.
161
162 .PARAMETER Refresh
163 When specified, removes existing plugin directories before regenerating.
164
165 .PARAMETER DryRun
166 When specified, logs actions without creating files or directories.
167
168 .PARAMETER Channel
169 Release channel controlling item maturity eligibility.
170
171 .OUTPUTS
172 Hashtable with Success, PluginCount, and ErrorMessage keys
173 via New-GenerateResult.
174 #>
175 [CmdletBinding()]
176 [OutputType([hashtable])]
177 param(
178 [Parameter(Mandatory = $true)]
179 [ValidateNotNullOrEmpty()]
180 [string]$RepoRoot,
181
182 [Parameter(Mandatory = $false)]
183 [string[]]$CollectionIds,
184
185 [Parameter(Mandatory = $false)]
186 [switch]$Refresh,
187
188 [Parameter(Mandatory = $false)]
189 [switch]$DryRun,
190
191 [Parameter(Mandatory = $false)]
192 [ValidateSet('Stable', 'PreRelease')]
193 [string]$Channel = 'PreRelease'
194 )
195
196 $collectionsDir = Join-Path -Path $RepoRoot -ChildPath 'collections'
197 $pluginsDir = Join-Path -Path $RepoRoot -ChildPath 'plugins'
198
199 # Read repo version from package.json for plugin manifests
200 $packageJsonPath = Join-Path -Path $RepoRoot -ChildPath 'package.json'
201 $repoVersion = (Get-Content -Path $packageJsonPath -Raw | ConvertFrom-Json).version
202
203 # Auto-update hve-core-all collection with discovered artifacts
204 $updateResult = Update-HveCoreAllCollection -RepoRoot $RepoRoot -DryRun:$DryRun
205 Write-Verbose "hve-core-all updated: $($updateResult.ItemCount) items ($($updateResult.AddedCount) added, $($updateResult.RemovedCount) removed)"
206
207 # Load all collection manifests
208 $allCollections = Get-AllCollections -CollectionsDir $collectionsDir
209
210 if ($allCollections.Count -eq 0) {
211 Write-Warning 'No collection manifests found in collections/'
212 return New-GenerateResult -Success $true -PluginCount 0
213 }
214
215 # Filter to requested IDs when provided
216 if ($CollectionIds -and $CollectionIds.Count -gt 0) {
217 $filtered = @($allCollections | Where-Object { $CollectionIds -contains $_.id })
218 $missing = @($CollectionIds | Where-Object { $_ -notin ($allCollections | ForEach-Object { $_.id }) })
219 if ($missing.Count -gt 0) {
220 Write-Warning "Collections not found: $($missing -join ', ')"
221 }
222 $allCollections = $filtered
223 }
224
225 Write-Host "`n=== Plugin Generation ===" -ForegroundColor Cyan
226 Write-Host "Collections: $($allCollections.Count)"
227 Write-Host "Channel: $Channel"
228 Write-Host "Plugins dir: $pluginsDir"
229 if ($DryRun) {
230 Write-Host '[DRY RUN] No changes will be made' -ForegroundColor Yellow
231 }
232
233 $generated = 0
234 $totalAgents = 0
235 $totalCommands = 0
236 $totalInstructions = 0
237 $totalSkills = 0
238
239 foreach ($collection in $allCollections) {
240 $id = $collection.id
241 $pluginDir = Join-Path -Path $pluginsDir -ChildPath $id
242
243 # Refresh: remove existing plugin directory
244 if ($Refresh -and (Test-Path -Path $pluginDir)) {
245 if ($DryRun) {
246 Write-Host " [DRY RUN] Would remove $pluginDir" -ForegroundColor Yellow
247 }
248 else {
249 Remove-Item -Path $pluginDir -Recurse -Force
250 Write-Verbose "Removed existing plugin directory: $pluginDir"
251 }
252 }
253
254 # Generate plugin directory structure
255 $filteredCollection = Select-CollectionItemsByChannel -Collection $collection -Channel $Channel
256
257 $result = Write-PluginDirectory -Collection $filteredCollection `
258 -PluginsDir $pluginsDir `
259 -RepoRoot $RepoRoot `
260 -Version $repoVersion `
261 -DryRun:$DryRun
262
263 $itemCount = $filteredCollection.items.Count
264 $totalAgents += $result.AgentCount
265 $totalCommands += $result.CommandCount
266 $totalInstructions += $result.InstructionCount
267 $totalSkills += $result.SkillCount
268 $generated++
269
270 Write-Host " $id ($itemCount items)" -ForegroundColor Green
271 }
272
273 # Generate marketplace.json from all collections
274 Write-MarketplaceManifest `
275 -RepoRoot $RepoRoot `
276 -Collections $allCollections `
277 -DryRun:$DryRun
278
279 Write-Host "`n--- Summary ---" -ForegroundColor Cyan
280 Write-Host " Plugins generated: $generated"
281 Write-Host " Agents: $totalAgents"
282 Write-Host " Commands: $totalCommands"
283 Write-Host " Instructions: $totalInstructions"
284 Write-Host " Skills: $totalSkills"
285
286 return New-GenerateResult -Success $true -PluginCount $generated
287}
288
289#endregion Orchestration
290
291#region Main Execution
292if ($MyInvocation.InvocationName -ne '.') {
293 try {
294 # Verify PowerShell-Yaml module
295 if (-not (Get-Module -ListAvailable -Name PowerShell-Yaml)) {
296 throw "Required module 'PowerShell-Yaml' is not installed."
297 }
298 Import-Module PowerShell-Yaml -ErrorAction Stop
299
300 # Resolve paths
301 $ScriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path
302 $RepoRoot = (Get-Item "$ScriptDir/../..").FullName
303
304 Write-Host 'HVE Core Plugin Generator' -ForegroundColor Cyan
305 Write-Host '==========================' -ForegroundColor Cyan
306
307 # Default to all + refresh when no args
308 $effectiveRefresh = $Refresh
309 if (-not $CollectionIds -and -not $Refresh.IsPresent -and -not $DryRun.IsPresent) {
310 $effectiveRefresh = [switch]::new($true)
311 }
312
313 $result = Invoke-PluginGeneration `
314 -RepoRoot $RepoRoot `
315 -CollectionIds $CollectionIds `
316 -Refresh:$effectiveRefresh `
317 -DryRun:$DryRun `
318 -Channel $Channel
319
320 if (-not $result.Success) {
321 throw $result.ErrorMessage
322 }
323
324 Write-Host ''
325 Write-Host 'Done!' -ForegroundColor Green
326 Write-Host " $($result.PluginCount) plugin(s) generated."
327
328 exit 0
329 }
330 catch {
331 Write-Error "Plugin generation failed: $($_.Exception.Message)"
332 Write-CIAnnotation -Message $_.Exception.Message -Level Error
333 exit 1
334 }
335}
336#endregion
337