-
Notifications
You must be signed in to change notification settings - Fork 5.2k
/
Copy pathCreate-APIView.ps1
569 lines (476 loc) · 20.1 KB
/
Create-APIView.ps1
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
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
. $PSScriptRoot/ChangedFiles-Functions.ps1
. $PSScriptRoot/../common/scripts/logging.ps1
$defaultTagRegex = "^tag:\s*(?<tag>.+)"
$tagRegex = '^```\s*yaml\s*\$\(tag\)\s*==\s*''(?<tag>.+)'''
<#
.DESCRIPTION
Gets configuration info (tags and configFilePath) associated with a swagger file.
.PARAMETER SwaggerFile
Path to a swagger files inside the 'specification' directory.
.OUTPUTS
the configuration info (tags and configFilePath) or null if not found.
#>
function Get-AutoRestConfigInfo {
param (
[Parameter(Mandatory = $true)]
[string]$SwaggerFile
)
$currentPath = Resolve-Path $SwaggerFile
while ($currentPath -ne [System.IO.Path]::GetPathRoot($currentPath)) {
$currentPath = [System.IO.Path]::GetDirectoryName($currentPath)
$currentFilePath = [System.IO.Path]::GetFileName($currentPath)
if ($currentFilePath -eq "specification") {
break
}
$readmeFile = Get-ChildItem -Path $currentPath -Filter "readme.md" -File -ErrorAction SilentlyContinue
if ($readmeFile -and $readmeFile.Name -eq "readme.md") {
$tagInfo = Get-TagInformationFromReadMeFile -ReadMeFilePath $readmeFile.FullName
if ($tagInfo.DefaultTag) {
$tagInfo | Add-Member -MemberType NoteProperty -Name "ConfigPath" -Value $readmeFile
return $tagInfo
}
}
}
return $null
}
<#
.DESCRIPTION
Use the directory structure convention to get the resource provider name.
Append the service directory name if there are multiple services in the same resource provider
.PARAMETER ReadMeFilePath
ReadMe File Path for a resource provider.
.OUTPUTS
The resource provider name.
#>
function Get-ResourceProviderFromReadMePath {
param (
[Parameter(Mandatory = $true)]
[string]$ReadMeFilePath
)
$directoryPath = [System.IO.Path]::GetDirectoryName($ReadMeFilePath)
$pathName = [System.IO.Path]::GetFileName($directoryPath)
if ($pathName -eq "resource-manager" -or $pathName -eq "data-plane") {
$resourceProviderDirectory = Get-ChildItem -Path $directoryPath -Directory | Select-Object -First 1
return $resourceProviderDirectory.Name
}
else {
$currentPath = Resolve-Path $directoryPath
$serviceName = $pathName
while ($currentPath -ne [System.IO.Path]::GetPathRoot($currentPath)) {
$pathName = [System.IO.Path]::GetFileName($currentPath)
if ($pathName -eq "resource-manager" -or $pathName -eq "data-plane") {
$resourceProviderDirectory = Get-ChildItem -Path $currentPath -Directory | Select-Object -First 1
return $resourceProviderDirectory.Name + "-" + $serviceName
}
$currentPath = Resolve-Path ([System.IO.Path]::GetDirectoryName($currentPath))
}
}
return $null
}
<#
.DESCRIPTION
Invoke the swagger parset to generate APIView tokens.
.PARAMETER Type
New or Baseline swagger APIView tokens.
.PARAMETER ReadMeFilePath
The Swagger ReadMeFilePath.
.PARAMETER ResourceProvider
The ResourceProvider Name.
.PARAMETER Tag
The Tag to use for generating the APIView Tokens.
.PARAMETER TokenDirectory
The directory to store the generated APIView Tokens.
.OUTPUTS
The resource provider name.
#>
function Invoke-SwaggerAPIViewParser {
param (
[ValidateSet("New", "Baseline")]
[Parameter(Mandatory = $true)]
[string]$Type,
[Parameter(Mandatory = $true)]
[string]$ReadMeFilePath,
[Parameter(Mandatory = $true)]
[string]$ResourceProvider,
[Parameter(Mandatory = $true)]
[string]$TokenDirectory,
[Parameter(Mandatory = $true)]
[string]$TempDirectory,
[string]$Tag
)
$tempWorkingDirectoryName = [guid]::NewGuid().ToString()
$tempWorkingDirectoryPath = [System.IO.Path]::Combine($TempDirectory, $tempWorkingDirectoryName)
New-Item -ItemType Directory -Path $tempWorkingDirectoryPath > $null
Push-Location -Path $tempWorkingDirectoryPath
try {
# Generate Swagger APIView tokens
$command = "swaggerAPIParser"
$arguments = @("--readme", "$ReadMeFilePath", "--package-name", "$ResourceProvider")
if ($Tag) {
$arguments += "--tag"
$arguments += "$Tag"
}
LogInfo " $command $arguments"
LogGroupStart " Generating '$Type' APIView Tokens using '$ReadMeFilePath' for '$ResourceProvider'..."
& $command @arguments 2>&1 | ForEach-Object { Write-Host $_ }
LogGroupEnd
$generatedAPIViewTokenFile = Join-Path (Get-Location) "swagger.json"
if (Test-Path -Path $generatedAPIViewTokenFile) {
LogSuccess " Generated '$Type' APIView Token File using file, '$ReadMeFilePath' and tag '$Tag'"
$apiViewTokensFilePath = [System.IO.Path]::Combine($TokenDirectory, "$ResourceProvider.$Type.json")
LogInfo " Moving generated APIView Token file to '$apiViewTokensFilePath'"
Move-Item -Path $generatedAPIViewTokenFile -Destination $apiViewTokensFilePath -Force > $null
}
} catch {
LogError " Failed to generate '$Type' APIView Tokens using '$ReadMeFilePath' for '$ResourceProvider'"
throw
} finally {
Pop-Location
if (Test-Path -Path $tempWorkingDirectoryPath) {
Remove-Item -Path $tempWorkingDirectoryPath -Recurse -Force > $null
}
}
}
<#
.DESCRIPTION
Invoke the TypeSpec parser to generate APIView tokens.
.PARAMETER Type
New or Baseline TypeSpec APIView tokens.
.PARAMETER ProjectPath
The TypeSpec Project path.
.PARAMETER ResourceProvider
The ResourceProvider Name.
.PARAMETER Tag
The Tag to use for generating the APIView Tokens.
.PARAMETER TokenDirectory
The directory to store the generated APIView Tokens.
.OUTPUTS
The resource provider name.
#>
function Invoke-TypeSpecAPIViewParser {
param (
[ValidateSet("New", "Baseline")]
[Parameter(Mandatory = $true)]
[string]$Type,
[Parameter(Mandatory = $true)]
[string]$ProjectPath,
[Parameter(Mandatory = $true)]
[string]$ResourceProvider,
[Parameter(Mandatory = $true)]
[string]$TokenDirectory
)
$tempWorkingDirectoryName = [guid]::NewGuid().ToString()
$tempWorkingDirectoryPath = [System.IO.Path]::Combine($TempDirectory, $tempWorkingDirectoryName)
New-Item -ItemType Directory -Path $tempWorkingDirectoryPath > $null
try {
Write-Host "Compiling files and generating '$Type' APIView for '$resourceProvider'..."
Push-Location $ProjectPath
Write-Host "npm exec --no -- tsp compile . --emit=@azure-tools/typespec-apiview --option @azure-tools/typespec-apiview.emitter-output-dir=$tempWorkingDirectoryPath/output/apiview.json"
npm exec --no -- tsp compile . --emit=@azure-tools/typespec-apiview --option @azure-tools/typespec-apiview.emitter-output-dir=$tempWorkingDirectoryPath/output/apiview.json
if ($LASTEXITCODE) {
throw "Compilation error when running: 'npm exec --no -- tsp compile . --emit=@azure-tools/typespec-apiview --option @azure-tools/typespec-apiview.emitter-output-dir=$tempWorkingDirectoryPath/output/apiview.json'"
}
Pop-Location
$generatedAPIViewTokenFile = Get-ChildItem -File $tempWorkingDirectoryPath/output/apiview.json | Select-Object -First 1
$apiViewTokensFilePath = [System.IO.Path]::Combine($TokenDirectory, "$resourceProvider.$Type.json")
Write-Host "Moving generated APIView Token file to '$apiViewTokensFilePath'"
Move-Item -Path $generatedAPIViewTokenFile.FullName -Destination $apiViewTokensFilePath -Force > $null
} catch {
LogError " Failed to generate '$Type' APIView Tokens on '$ProjectPath' for '$resourceProvider', please check the detail log and make sure TypeSpec compiler version is the latest."
LogError $_
throw
} finally {
if (Test-Path -Path $tempWorkingDirectoryPath) {
Remove-Item -Path $tempWorkingDirectoryPath -Recurse -Force > $null
}
}
}
<#
.DESCRIPTION
Generate New and Baseline APIView tokens for the changed swagger files in the PR.
Detects the swagger files changed in the PR and generates APIView tokens for the swagger files.
New APIView tokens are generated using the default tag on the base branch.
Baseline APIView tokens are generated using the same tag on the target branch.
Script asumes that the merge commit is checked out. Such that Source commit = HEAD^ and Target commit = HEAD.
.PARAMETER TempDirectory
Temporary directory for files being processed. Use $(Agent.TempDirectory) on DevOps
.PARAMETER ArtifactsStagingDirectory
The directory where the APIView tokens will be stored. Use $(Build.ArtifactStagingDirectory) on DevOps
.PARAMETER APIViewArtifactsDirectoryName
Name for the subdirectory where the APIView tokens will be stored.
#>
function New-SwaggerAPIViewTokens {
param (
[Parameter(Mandatory = $true)]
[string]$TempDirectory,
[Parameter(Mandatory = $true)]
[string]$ArtifactsStagingDirectory,
[Parameter(Mandatory = $true)]
[string]$APIViewArtifactsDirectoryName
)
$SourceCommitId = $(git rev-parse HEAD^2)
$TargetCommitId = $(git rev-parse HEAD^1)
# Get Changed Swagger Files
LogInfo " Getting changed swagger files in PR, between $SourceCommitId and $TargetCommitId"
$changedFiles = Get-ChangedFiles
$changedSwaggerFiles = Get-ChangedSwaggerFiles -changedFiles $changedFiles
if ($changedSwaggerFiles.Count -eq 0) {
LogWarning " There are no changes to swagger files in the current PR..."
Write-Host "##vso[task.complete result=SucceededWithIssues;]DONE"
exit 0
}
LogGroupStart " Pullrequest has changes in these swagger files..."
$changedSwaggerFiles | ForEach-Object {
LogInfo " - $_"
}
LogGroupEnd
# Get Related AutoRest Configuration Information
$autoRestConfigInfo = [System.Collections.Generic.Dictionary[string, object]]::new()
$changedSwaggerFiles | ForEach-Object {
$configInfo = Get-AutoRestConfigInfo -swaggerFile $_
if ($null -ne $configInfo -and -not $autoRestConfigInfo.ContainsKey($configInfo.ConfigPath)) {
$autoRestConfigInfo[$configInfo.ConfigPath] = $configInfo
}
}
LogGroupStart " Swagger APIView Tokens will be generated for the following configuration files..."
$autoRestConfigInfo.GetEnumerator() | ForEach-Object {
LogInfo " - $($_.Key)"
}
LogGroupEnd
$currentBranch = git rev-parse --abbrev-ref HEAD
$swaggerAPIViewArtifactsDirectory = [System.IO.Path]::Combine($ArtifactsStagingDirectory, $APIViewArtifactsDirectoryName)
# Generate Swagger APIView Tokens
foreach ($entry in $autoRestConfigInfo.GetEnumerator()) {
$configInfo = $entry.Value
$readMeFile = $entry.Key
git checkout $SourceCommitId
if (Test-Path -Path $readmeFile) {
$resourceProvider = Get-ResourceProviderFromReadMePath -ReadMeFilePath $readMeFile
$tokenDirectory = [System.IO.Path]::Combine($swaggerAPIViewArtifactsDirectory, $resourceProvider)
New-Item -ItemType Directory -Path $tokenDirectory -Force | Out-Null
# Generate New APIView Token using default tag on source branch
Invoke-SwaggerAPIViewParser -Type "New" -ReadMeFilePath $readMeFile -ResourceProvider $resourceProvider -TokenDirectory $tokenDirectory `
-TempDirectory $TempDirectory -Tag $configInfo.DefaultTag | Out-Null
# Generate BaseLine APIView Token using source commit tag on target branch or defaukt tag if source commit tag does not exist
git checkout $TargetCommitId
if (Test-Path -Path $readMeFile) {
$targetTagInfo = Get-TagInformationFromReadMeFile -ReadMeFilePath $readMeFile
$baseLineTag = $targetTagInfo.DefaultTag
if ($targetTagInfo.Tags.Contains($configInfo.DefaultTag)) {
$baseLineTag = $configInfo.DefaultTag
}
Invoke-SwaggerAPIViewParser -Type "Baseline" -ReadMeFilePath $readMeFile -ResourceProvider $resourceProvider -TokenDirectory $tokenDirectory `
-TempDirectory $TempDirectory -Tag $baseLineTag | Out-Null
}
else {
LogWarning " Swagger ReadMe file '$readMeFile' not found on TargetBranch. Skipping APIView token generation."
}
}
else {
LogWarning " Swagger ReadMe file '$readMeFile' not found on SourceBranch. Skipping APIView token generation."
}
}
git checkout $currentBranch
LogGroupStart " See all generated Swagger APIView Artifacts..."
Get-ChildItem -Path $swaggerAPIViewArtifactsDirectory -Recurse
LogGroupEnd
}
<#
.DESCRIPTION
Generate New and Baseline APIView tokens for the changed TypeSpec files in the PR.
Detects the TypeSpec files changed in the PR and generates APIView tokens for the TypeSpec files.
New APIView tokens are generated using the default tag on the base branch.
Baseline APIView tokens are generated using the same tag on the target branch.
Script asumes that the merge commit is checked out. Such that Source commit = HEAD^ and Target commit = HEAD.
.PARAMETER TempDirectory
Temporary directory for files being processed. Use $(Agent.TempDirectory) on DevOps
.PARAMETER ArtifactsStagingDirectory
The directory where the APIView tokens will be stored. Use $(Build.ArtifactStagingDirectory) on DevOps
.PARAMETER APIViewArtifactsDirectoryName
Name for the subdirectory where the APIView tokens will be stored.
#>
function New-TypeSpecAPIViewTokens {
param (
[Parameter(Mandatory = $true)]
[string]$TempDirectory,
[Parameter(Mandatory = $true)]
[string]$ArtifactsStagingDirectory,
[Parameter(Mandatory = $true)]
[string]$APIViewArtifactsDirectoryName
)
$SourceCommitId = $(git rev-parse HEAD^)
$TargetCommitId = $(git rev-parse HEAD)
$typeSpecProjects, $null = &"$PSScriptRoot/Get-TypeSpec-Folders.ps1" `
-IgnoreCoreFiles:$true `
-BaseCommitish:$SourceCommitId `
-TargetCommitish:$TargetCommitId
$typeSpecProjects = $typeSpecProjects | Where-Object {Test-Path -Path "$_/main.tsp"}
LogGroupStart " TypeSpec APIView Tokens will be generated for the following configuration files..."
$typeSpecProjects | ForEach-Object {
LogInfo " - $_"
}
LogGroupEnd
$currentBranch = git rev-parse --abbrev-ref HEAD
$typeSpecAPIViewArtifactsDirectory = [System.IO.Path]::Combine($ArtifactsStagingDirectory, $APIViewArtifactsDirectoryName)
New-Item -ItemType Directory -Path $typeSpecAPIViewArtifactsDirectory -Force | Out-Null
try {
npm --version --loglevel info
# Generate New TypeSpec APIView Tokens
git checkout $SourceCommitId
Write-Host "Installing required dependencies to generate New API review"
npm ci
npm ls -a
foreach ($typeSpecProject in $typeSpecProjects) {
$tokenDirectory = [System.IO.Path]::Combine($typeSpecAPIViewArtifactsDirectory, $typeSpecProject.split([IO.Path]::DirectorySeparatorChar)[-1])
New-Item -ItemType Directory -Path $tokenDirectory -Force | Out-Null
Invoke-TypeSpecAPIViewParser -Type "New" -ProjectPath $typeSpecProject -ResourceProvider $($typeSpecProject.split([IO.Path]::DirectorySeparatorChar)[-1]) -TokenDirectory $tokenDirectory
}
# Generate Baseline TypeSpec APIView Tokens
git checkout $TargetCommitId
Write-Host "Installing required dependencies to generate Baseline API review"
npm ci
npm ls -a
foreach ($typeSpecProject in $typeSpecProjects) {
# Skip Baseline APIView Token for new projects
if (!(Test-Path -Path $typeSpecProject)) {
Write-Host "TypeSpec project $typeSpecProject is not found in pull request target branch. API review will not have a baseline revision."
}
else {
Invoke-TypeSpecAPIViewParser -Type "Baseline" -ProjectPath $typeSpecProject -ResourceProvider $($typeSpecProject.split([IO.Path]::DirectorySeparatorChar)[-1]) -TokenDirectory $tokenDirectory | Out-Null
}
}
}
finally {
git checkout $currentBranch
LogGroupStart " See all generated TypeSpec APIView Artifacts..."
Get-ChildItem -Path $typeSpecAPIViewArtifactsDirectory -Recurse
LogGroupEnd
}
}
<#
.DESCRIPTION
Create APIView for the published packages. Send DevOps artifacts information to APIView to create APIView for the published packages.
.PARAMETER ArtifactsStagingDirectory
The DevOps artifacts staging directory. Use $(Build.ArtifactStagingDirectory) on DevOps
.PARAMETER APIViewArtifactsDirectoryName
Temporary Directory for processing the APIView artifacts
.PARAMETER APIViewArtifactsName
The name of the APIView artifact
.PARAMETER APIViewUri
The EndPoint for creating APIView https://apiviewstagingtest.com/PullRequest/DetectAPIChanges
.PARAMETER BuildId
TGhe BuildId of the Run
.PARAMETER RepoName
Repo name eg Azure/azure-rest-api-specs
.PARAMETER PullRequestNumber
The PR number
.PARAMETER Language
The language of the resource provider
.PARAMETER CommitSha
The commit sha of the current branch. Uusally the merge commit of the PR.
#>
function New-RestSpecsAPIViewReviews {
param (
[Parameter(Mandatory = $true)]
[string]$ArtifactsStagingDirectory,
[Parameter(Mandatory = $true)]
[string]$APIViewArtifactsDirectoryName,
[Parameter(Mandatory = $true)]
[string]$APIViewArtifactsName,
[Parameter(Mandatory = $true)]
[string]$APIViewUri,
[Parameter(Mandatory = $true)]
[string]$BuildId,
[Parameter(Mandatory = $true)]
[string]$RepoName,
[Parameter(Mandatory = $true)]
[string]$PullRequestNumber,
[Parameter(Mandatory = $true)]
[string]$Language,
[Parameter(Mandatory = $true)]
[string]$CommitSha
)
$apiViewArtifactsDirectory = [System.IO.Path]::Combine($ArtifactsStagingDirectory, $APIViewArtifactsDirectoryName)
$publishedPackages = Get-ChildItem -Path $apiViewArtifactsDirectory -Directory -ErrorAction SilentlyContinue
Write-Host "Published packages: $publishedPackages"
$createAPIViewFailed = $false
$publishedPackages | ForEach-Object {
$apiViewArtifacts = Get-ChildItem -Path $_.FullName -File -Filter "*.json" -ErrorAction SilentlyContinue
$query = [System.Web.HttpUtility]::ParseQueryString('')
$apiViewArtifacts | ForEach-Object {
if ($_.BaseName.EndsWith("New")) {
$query.Add("codeFile", $_.Name)
}
elseif ($_.BaseName.EndsWith("Baseline")) {
$query.Add("baselineCodeFile", $_.Name)
}
}
if (-not $query['codeFile']) {
LogWarning "'New' APIView token file not found for resource provider '$($_.BaseName)'. Skipping APIView creation."
return
}
if (-not $query['baselineCodeFile']) {
LogWarning "'Baseline' APIView token file not found for resource provider '$($_.BaseName)'. Created APIView without baseline."
}
$query.Add('artifactName', $APIViewArtifactsName)
$query.Add('buildId', $BuildId)
$query.Add('commitSha', $CommitSha)
$query.Add('repoName', $RepoName)
$query.Add('pullRequestNumber', $PullRequestNumber)
$query.Add('packageName', $_.BaseName)
$query.Add('language', $Language)
$query.Add('commentOnPR', $true)
$uri = [System.UriBuilder]$APIViewUri
$uri.Query = $query.ToString()
LogInfo "Create APIView for resource provider '$($_.BaseName)'"
LogInfo "APIView Uri: $($uri.Uri)"
try {
Invoke-WebRequest -Method 'GET' -Uri $uri.Uri -MaximumRetryCount 3
}
catch {
LogError "Failed to create APIView for resource provider '$($_.BaseName)'. Error: $($_.Exception.Response)"
$createAPIViewFailed = $true
}
}
if ($createAPIViewFailed) {
LogError "Failed to create APIView for some resource providers. Check the logs for more details."
Write-Host "##vso[task.complete result=SucceededWithIssues;]DONE"
exit 1
}
}
<#
.DESCRIPTION
Get all the tags from the swagger readme file with the default tag indicated.
.PARAMETER ReadMeFilePath
The file path to the readme file.
#>
function Get-TagInformationFromReadMeFile {
param (
[Parameter(Mandatory = $true)]
[string]$ReadMeFilePath
)
$tags = [System.Collections.Generic.HashSet[string]]::new()
$markDownContent = Get-Content -Path $ReadMeFilePath
$checkForDefaultTag = $false
$defaultTag = $null
foreach ($line in $markDownContent) {
$line = $line.Trim()
if ($line -match "###\s+Basic\s+Information") {
$checkForDefaultTag = $true
}
if ($checkForDefaulttag -and ($null -eq $defaultTag)) {
if ($line -match $defaultTagRegex) {
$defaultTag = $matches["tag"]
$checkForDefaultTag = $false
}
}
if ($line -match $tagRegex) {
$tag = $matches["tag"]
$tags.Add($tag) | Out-Null
}
}
[PSCustomObject]@{
Tags = $tags
DefaultTag = $defaultTag
}
}