From 5807d00ed0a3acd293057d8a9c06a9d68b6030db Mon Sep 17 00:00:00 2001 From: Howard Wolosky Date: Thu, 28 May 2020 10:55:49 -0700 Subject: [PATCH 01/60] Fix named branch querying in Get-GitHubRepositoryBranch (#188) We erroneously were putting a question mark in the middle of the URI fragment when a branch name was being specified. Resolves #187 --- GitHubBranches.ps1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/GitHubBranches.ps1 b/GitHubBranches.ps1 index 3770c436..3bbb297a 100644 --- a/GitHubBranches.ps1 +++ b/GitHubBranches.ps1 @@ -89,7 +89,7 @@ function Get-GitHubRepositoryBranch 'RepositoryName' = (Get-PiiSafeString -PlainText $RepositoryName) } - $uriFragment = "repos/$OwnerName/$RepositoryName/branches`?" + $uriFragment = "repos/$OwnerName/$RepositoryName/branches" if (-not [String]::IsNullOrEmpty($Name)) { $uriFragment = $uriFragment + "/$Name" } $getParams = @() From 3c70b8d2702a4a7b5674bb72decacb385f1a47a8 Mon Sep 17 00:00:00 2001 From: Howard Wolosky Date: Thu, 28 May 2020 13:39:28 -0700 Subject: [PATCH 02/60] Remove references to paid plans from documentation (#191) GitHub free plans have changed and now private repos no longer require a paid plan. Resolves #190 --- GitHubRepositories.ps1 | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/GitHubRepositories.ps1 b/GitHubRepositories.ps1 index ef44fdb2..5f58b450 100644 --- a/GitHubRepositories.ps1 +++ b/GitHubRepositories.ps1 @@ -41,7 +41,7 @@ function New-GitHubRepository .PARAMETER Private By default, this repository will created Public. Specify this to create - a private repository. Creating private repositories requires a paid GitHub account. + a private repository. .PARAMETER NoIssues By default, this repository will support Issues. Specify this to disable Issues. @@ -710,8 +710,7 @@ function Update-GitHubRepository Update the default branch for this repository. .PARAMETER Private - Specify this to make the repository repository. Creating private repositories requires a - paid GitHub account. + Specify this to make the repository private. To change a repository to be public, specify -Private:$false .PARAMETER NoIssues From b4439f4a6b12f89d755851b313eff0e9ea0b3ab5 Mon Sep 17 00:00:00 2001 From: Howard Wolosky Date: Thu, 28 May 2020 16:29:18 -0700 Subject: [PATCH 03/60] Fixing the CI build (#183) There were a number of issues going on: * Needed to force Pester `v4.10.1` for the install because `5.0` was just released and has some breaking changes to investigate. * Pester was failing to save its output because the output directory didn't exist. Without that, it couldn't actually publish the results. * The `ciOrganizationName` pipeline variable had a typo in it, which meant that we were passing an empty string as the `OrganizationName` to tests that needed it * We needed to ensure consistent line endings for `Tests/Config/Settings.ps1` so that the hash generated on all platforms (Windows/mac/Linux) would be the same. * GitHub changed the default HTML generated for README.md which was causing two RepositoryContents tests to fail. * Needed to make sure that the three platforms (Windows/mac/Linux) run the unit tests _serially_ instead of in parallel, because they each modify the shared state of the same account, and when running at the same time they were stomping over each other and causing erroneous failures. Resolves #182 --- .gitattributes | 4 ++++ CONTRIBUTING.md | 2 +- Tests/GitHubContents.tests.ps1 | 2 +- build/pipelines/azure-pipelines.ci.yaml | 4 ++++ build/pipelines/templates/run-unitTests.yaml | 5 +++-- 5 files changed, 13 insertions(+), 4 deletions(-) create mode 100644 .gitattributes diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 00000000..477d176a --- /dev/null +++ b/.gitattributes @@ -0,0 +1,4 @@ +# Need to make sure that this file always has consistent line endings since we store the hash +# of it in GitHubConfiguration.ps1 in order to be able to determine if it has been modified +# or not. +Tests/Config/Settings.ps1 text eol=crlf \ No newline at end of file diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index e62fffd8..1cf400fa 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -295,7 +295,7 @@ This module supports testing using the [Pester UT framework](https://github.com/ To install it: ```powershell -Install-Module -Name Pester +Install-Module -Name Pester -RequiredVersion 4.10.1 -AllowClobber -SkipPublisherCheck -Force ``` #### Configuring Your Environment diff --git a/Tests/GitHubContents.tests.ps1 b/Tests/GitHubContents.tests.ps1 index 554ce61b..474d4f1d 100644 --- a/Tests/GitHubContents.tests.ps1 +++ b/Tests/GitHubContents.tests.ps1 @@ -22,7 +22,7 @@ try # Need two separate blocks to set constants because we need to reference a constant from the first block in this block. @{ - htmlOutput = "

$repoGuid

" + htmlOutput = "

$repoGuid

" rawOutput = "# $repoGuid" }.GetEnumerator() | ForEach-Object { Set-Variable -Force -Scope Script -Option ReadOnly -Visibility Private -Name $_.Key -Value $_.Value diff --git a/build/pipelines/azure-pipelines.ci.yaml b/build/pipelines/azure-pipelines.ci.yaml index d294cc72..9adc9f1a 100644 --- a/build/pipelines/azure-pipelines.ci.yaml +++ b/build/pipelines/azure-pipelines.ci.yaml @@ -30,13 +30,17 @@ jobs: - job: Linux pool: vmImage: 'ubuntu-16.04' + dependsOn: Windows # Run in series instead of parallel because the UT's are modifying the same shared state steps: + - template: ./templates/verify-testConfigSettingsHash.yaml - template: ./templates/run-staticAnalysis.yaml - template: ./templates/run-unitTests.yaml - job: macOS pool: vmImage: 'macOS-10.14' + dependsOn: Linux # Run in series instead of parallel because the UT's are modifying the same shared state steps: + - template: ./templates/verify-testConfigSettingsHash.yaml - template: ./templates/run-staticAnalysis.yaml - template: ./templates/run-unitTests.yaml diff --git a/build/pipelines/templates/run-unitTests.yaml b/build/pipelines/templates/run-unitTests.yaml index 58b83298..53d64828 100644 --- a/build/pipelines/templates/run-unitTests.yaml +++ b/build/pipelines/templates/run-unitTests.yaml @@ -15,17 +15,18 @@ steps: - powershell: | - Install-Module -Name Pester -Repository PSGallery -Scope CurrentUser -AllowClobber -SkipPublisherCheck -Force -Verbose + Install-Module -Name Pester -Repository PSGallery -Scope CurrentUser -AllowClobber -SkipPublisherCheck -RequiredVersion 4.10.1 -Force -Verbose displayName: 'Install Pester' - powershell: | + $null = New-Item -Path ..\ -Name Pester -ItemType Directory -Force Invoke-Pester -CodeCoverage .\*.ps*1 -CodeCoverageOutputFile ../Pester/coverage.xml -CodeCoverageOutputFileFormat JaCoCo -EnableExit -Strict -OutputFile ../Pester/test-results.xml -OutputFormat NUnitXml workingDirectory: '$(System.DefaultWorkingDirectory)' displayName: 'Run Unit Tests via Pester' env: ciAccessToken: $(GitHubAccessToken) ciOwnerName: $(GitHubOwnerName) - ciOrganizatioName: $(GitHubOrganizationName) + ciOrganizationName: $(GitHubOrganizationName) - task: PublishTestResults@2 displayName: 'Publish Test Results' From 587e2042621091c79cc06be2aa9cc6ea836561f4 Mon Sep 17 00:00:00 2001 From: Howard Wolosky Date: Fri, 29 May 2020 08:36:36 -0700 Subject: [PATCH 04/60] Exception in New-GitHubRepository when specifying TeamId (#196) Incorrectly used `$PSBoundParameters.Contains` instead of `$PSBoundParameters.ContainsKey`. Resolves #195 --- GitHubRepositories.ps1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/GitHubRepositories.ps1 b/GitHubRepositories.ps1 index 5f58b450..bff7c159 100644 --- a/GitHubRepositories.ps1 +++ b/GitHubRepositories.ps1 @@ -139,7 +139,7 @@ function New-GitHubRepository $uriFragment = "orgs/$OrganizationName/repos" } - if ($PSBoundParameters.ContainsKey('TeamId') -and (-not $PSBoundParameters.Contains('OrganizationName'))) + if ($PSBoundParameters.ContainsKey('TeamId') -and (-not $PSBoundParameters.ContainsKey('OrganizationName'))) { $message = 'TeamId may only be specified when creating a repository under an organization.' Write-Log -Message $message -Level Error From bcd0a5616e1395ca480bc7f3b64776eada2a6670 Mon Sep 17 00:00:00 2001 From: Howard Wolosky Date: Sat, 30 May 2020 15:42:04 -0700 Subject: [PATCH 05/60] Fix multi-result behavior across all versions of PowerShell (fixes CI UT's on all platforms) (#199) Tests were failing on Mac and Linux, but not Windows ([recent test run](https://dev.azure.com/ms/PowerShellForGitHub/_build/results?buildId=83887&view=logs&j=0da5d1d9-276d-5173-c4c4-9d4d4ed14fdb)). That's because Windows CI was running against PoSh 5.x while Linux and Mac were running on PoSh 7.x. There's a slight difference in behavior for how those two treat arrays. The real root cause for this was the behavior of `Invoke-GHRestMethodMultipleResult`. When creating `$finalResult`, it was always blindly adding the result to the existing array: https://github.com/microsoft/PowerShellForGitHub/blob/587e2042621091c79cc06be2aa9cc6ea836561f4/GitHubCore.ps1#L648 `...` https://github.com/microsoft/PowerShellForGitHub/blob/587e2042621091c79cc06be2aa9cc6ea836561f4/GitHubCore.ps1#L670 Oddly enough, this created a difference in behavior between PowerShell versions when making the result an array on the caller side. Now I ensure that I don't add anything to `$finalResult` unless there's actually a value. With that change, we can now be sure that when we grab the result as an array, it'll be appropriately empty or populated (and not populated with a single `$null` entry, thus making `Count` 1, erroneously). I removed the attempt to force the results to be an array, because this is pointless. PowerShell will always unwrap an array of 0 or 1 in a return result. If you want to ensure that a result is always an array, you have to [wrap the result in an object](https://stackoverflow.com/a/60330501) or you have to do wrap the result in an array on the caller side. https://github.com/microsoft/PowerShellForGitHub/blob/587e2042621091c79cc06be2aa9cc6ea836561f4/GitHubCore.ps1#L684-L685 I also normalized some naming in all of the tests, so that when we're getting back a singular result (by querying for a specific item) that we use a singular variable name, and a plural variable name otherwise. With this change, we should now be passing CI on all OS platforms and across PowerShell 4+. Resolves #198 --- GitHubCore.ps1 | 9 ++- Tests/GitHubAnalytics.tests.ps1 | 58 ++++++++------- Tests/GitHubAssignees.tests.ps1 | 2 +- Tests/GitHubLabels.tests.ps1 | 24 +++---- Tests/GitHubMilestones.tests.ps1 | 6 +- Tests/GitHubProjectCards.tests.ps1 | 48 ++++++------- Tests/GitHubProjectColumns.tests.ps1 | 32 +++++---- Tests/GitHubProjects.tests.ps1 | 94 +++++++++++++++---------- Tests/GitHubReleases.tests.ps1 | 20 +++--- Tests/GitHubRepositories.tests.ps1 | 16 ++--- Tests/GitHubRepositoryForks.tests.ps1 | 14 ++-- Tests/GitHubRepositoryTraffic.tests.ps1 | 4 +- 12 files changed, 175 insertions(+), 152 deletions(-) diff --git a/GitHubCore.ps1 b/GitHubCore.ps1 index 2bf3f185..59acd6f4 100644 --- a/GitHubCore.ps1 +++ b/GitHubCore.ps1 @@ -667,7 +667,11 @@ function Invoke-GHRestMethodMultipleResult } $result = Invoke-GHRestMethod @params - $finalResult += $result.result + if ($null -ne $result.result) + { + $finalResult += $result.result + } + $nextLink = $result.nextLink $currentDescription = "$Description (getting additional results)" } @@ -681,8 +685,7 @@ function Invoke-GHRestMethodMultipleResult Set-TelemetryEvent -EventName $TelemetryEventName -Properties $TelemetryProperties -Metrics $telemetryMetrics } - # Ensure we're always returning our results as an array, even if there is a single result. - return @($finalResult) + return $finalResult } catch { diff --git a/Tests/GitHubAnalytics.tests.ps1 b/Tests/GitHubAnalytics.tests.ps1 index 0fb70198..8c5c51c2 100644 --- a/Tests/GitHubAnalytics.tests.ps1 +++ b/Tests/GitHubAnalytics.tests.ps1 @@ -16,10 +16,10 @@ try $repo = New-GitHubRepository -RepositoryName ([Guid]::NewGuid().Guid) -AutoInit Context 'When initially created, there are no issues' { - $issues = Get-GitHubIssue -Uri $repo.svn_url + $issues = @(Get-GitHubIssue -Uri $repo.svn_url) It 'Should return expected number of issues' { - @($issues).Count | Should be 0 + $issues.Count | Should be 0 } } @@ -34,29 +34,29 @@ try $newIssues[0] = Update-GitHubIssue -OwnerName $script:ownerName -RepositoryName $repo.name -Issue $newIssues[0].number -State Closed $newIssues[-1] = Update-GitHubIssue -OwnerName $script:ownerName -RepositoryName $repo.name -Issue $newIssues[-1].number -State Closed - $issues = Get-GitHubIssue -Uri $repo.svn_url + $issues = @(Get-GitHubIssue -Uri $repo.svn_url) It 'Should return only open issues' { - @($issues).Count | Should be 2 + $issues.Count | Should be 2 } - $issues = Get-GitHubIssue -Uri $repo.svn_url -State All + $issues = @(Get-GitHubIssue -Uri $repo.svn_url -State All) It 'Should return all issues' { - @($issues).Count | Should be 4 + $issues.Count | Should be 4 } $createdOnOrAfterDate = Get-Date -Date $newIssues[0].created_at $createdOnOrBeforeDate = Get-Date -Date $newIssues[2].created_at - $issues = (Get-GitHubIssue -Uri $repo.svn_url) | Where-Object { ($_.created_at -ge $createdOnOrAfterDate) -and ($_.created_at -le $createdOnOrBeforeDate) } + $issues = @((Get-GitHubIssue -Uri $repo.svn_url) | Where-Object { ($_.created_at -ge $createdOnOrAfterDate) -and ($_.created_at -le $createdOnOrBeforeDate) }) It 'Smart object date conversion works for comparing dates' { - @($issues).Count | Should be 2 + $issues.Count | Should be 2 } $createdDate = Get-Date -Date $newIssues[1].created_at - $issues = Get-GitHubIssue -Uri $repo.svn_url -State All | Where-Object { ($_.created_at -ge $createdDate) -and ($_.state -eq 'closed') } + $issues = @(Get-GitHubIssue -Uri $repo.svn_url -State All | Where-Object { ($_.created_at -ge $createdDate) -and ($_.state -eq 'closed') }) It 'Able to filter based on date and state' { - @($issues).Count | Should be 1 + $issues.Count | Should be 1 } } @@ -88,18 +88,18 @@ try $issueCounts = $issueCounts | Sort-Object -Property Count -Descending It 'Should return expected number of issues for each repository' { - @($issueCounts[0].Count) | Should be 3 - @($issueCounts[1].Count) | Should be 0 + $issueCounts[0].Count | Should be 3 + $issueCounts[1].Count | Should be 0 } It 'Should return expected repository names' { - @($issueCounts[0].Uri) | Should be ($repo1.svn_url) - @($issueCounts[1].Uri) | Should be ($repo2.svn_url) + $issueCounts[0].Uri | Should be $repo1.svn_url + $issueCounts[1].Uri | Should be $repo2.svn_url } } - $null = Remove-GitHubRepository -Uri ($repo1.svn_url) - $null = Remove-GitHubRepository -Uri ($repo2.svn_url) + $null = Remove-GitHubRepository -Uri $repo1.svn_url + $null = Remove-GitHubRepository -Uri $repo2.svn_url } @@ -186,10 +186,10 @@ try $null = New-GitHubRepository -RepositoryName $repositoryName -AutoInit $repositoryUrl = "https://github.com/$script:ownerName/$repositoryName" - $collaborators = Get-GitHubRepositoryCollaborator -Uri $repositoryUrl + $collaborators = @(Get-GitHubRepositoryCollaborator -Uri $repositoryUrl) It 'Should return expected number of collaborators' { - @($collaborators).Count | Should be 1 + $collaborators.Count | Should be 1 } $null = Remove-GitHubRepository -OwnerName $script:ownerName -RepositoryName $repositoryName @@ -201,10 +201,10 @@ try $null = New-GitHubRepository -RepositoryName $repositoryName -AutoInit $repositoryUrl = "https://github.com/$script:ownerName/$repositoryName" - $contributors = Get-GitHubRepositoryContributor -Uri $repositoryUrl -IncludeStatistics + $contributors = @(Get-GitHubRepositoryContributor -Uri $repositoryUrl -IncludeStatistics) It 'Should return expected number of contributors' { - @($contributors).Count | Should be 1 + $contributors.Count | Should be 1 } $null = Remove-GitHubRepository -OwnerName $script:ownerName -RepositoryName $repositoryName @@ -242,15 +242,13 @@ try } Describe 'Getting repositories from organization' { - <# Temporary hack due to issues with this test in ADO #> . (Join-Path -Path $moduleRootPath -ChildPath 'Tests\Config\Settings.ps1') - - $original = Get-GitHubRepository -OrganizationName $script:organizationName + $original = @(Get-GitHubRepository -OrganizationName $script:organizationName) $repo = New-GitHubRepository -RepositoryName ([guid]::NewGuid().Guid) -OrganizationName $script:organizationName - $current = Get-GitHubRepository -OrganizationName $script:organizationName + $current = @(Get-GitHubRepository -OrganizationName $script:organizationName) It 'Should return expected number of organization repositories' { - (@($current).Count - @($original).Count) | Should be 1 + ($current.Count - $original.Count) | Should be 1 } $null = Remove-GitHubRepository -Uri $repo.svn_url @@ -260,7 +258,7 @@ try $repositoryName = [guid]::NewGuid().Guid $null = New-GitHubRepository -RepositoryName $repositoryName -AutoInit - $contributors = Get-GitHubRepositoryContributor -OwnerName $script:ownerName -RepositoryName $repositoryName -IncludeStatistics + $contributors = @(Get-GitHubRepositoryContributor -OwnerName $script:ownerName -RepositoryName $repositoryName -IncludeStatistics) $uniqueContributors = $contributors | Select-Object -ExpandProperty author | @@ -268,7 +266,7 @@ try Sort-Object It 'Should return expected number of unique contributors' { - @($uniqueContributors).Count | Should be 1 + $uniqueContributors.Count | Should be 1 } $null = Remove-GitHubRepository -OwnerName $script:ownerName -RepositoryName $repositoryName @@ -298,14 +296,14 @@ try $repositoryName = [guid]::NewGuid().Guid $null = New-GitHubRepository -RepositoryName $repositoryName -AutoInit - $branches = Get-GitHubRepositoryBranch -OwnerName $script:ownerName -RepositoryName $repositoryName + $branches = @(Get-GitHubRepositoryBranch -OwnerName $script:ownerName -RepositoryName $repositoryName) It 'Should return expected number of repository branches' { - @($branches).Count | Should be 1 + $branches.Count | Should be 1 } It 'Should return the name of the branches' { - @($branches[0].name) | Should be "master" + $branches[0].name | Should be 'master' } $null = Remove-GitHubRepository -OwnerName $script:ownerName -RepositoryName $repositoryName diff --git a/Tests/GitHubAssignees.tests.ps1 b/Tests/GitHubAssignees.tests.ps1 index 9ea07346..fc1a89de 100644 --- a/Tests/GitHubAssignees.tests.ps1 +++ b/Tests/GitHubAssignees.tests.ps1 @@ -44,7 +44,7 @@ try Context 'For adding an assignee to an issue'{ $assigneeList = @(Get-GitHubAssignee -Uri $repo.svn_url) $assigneeUserName = $assigneeList[0].login - $assignees = @($assigneeUserName) + $assignees = $assigneeUserName New-GithubAssignee -Uri $repo.svn_url -Issue $issue.number -Assignee $assignees $issue = Get-GitHubIssue -Uri $repo.svn_url -Issue $issue.number diff --git a/Tests/GitHubLabels.tests.ps1 b/Tests/GitHubLabels.tests.ps1 index 9d1158b3..e040579b 100644 --- a/Tests/GitHubLabels.tests.ps1 +++ b/Tests/GitHubLabels.tests.ps1 @@ -79,10 +79,10 @@ try Set-GitHubLabel -OwnerName $ownerName -RepositoryName $repositoryName -Label $defaultLabels Context 'When querying for all labels' { - $labels = Get-GitHubLabel -OwnerName $ownerName -RepositoryName $repositoryName + $labels = @(Get-GitHubLabel -OwnerName $ownerName -RepositoryName $repositoryName) It 'Should return expected number of labels' { - $($labels).Count | Should be $:defaultLabels.Count + $labels.Count | Should be $:defaultLabels.Count } } @@ -123,17 +123,17 @@ try $labelName = [Guid]::NewGuid().Guid New-GitHubLabel -OwnerName $ownerName -RepositoryName $repositoryName -Name $labelName -Color BBBBBB - $labels = Get-GitHubLabel -OwnerName $ownerName -RepositoryName $repositoryName + $labels = @(Get-GitHubLabel -OwnerName $ownerName -RepositoryName $repositoryName) It 'Should return increased number of labels' { - $($labels).Count | Should be ($defaultLabels.Count + 1) + $labels.Count | Should be ($defaultLabels.Count + 1) } Remove-GitHubLabel -OwnerName $ownerName -RepositoryName $repositoryName -Name $labelName - $labels = Get-GitHubLabel -OwnerName $ownerName -RepositoryName $repositoryName + $labels = @(Get-GitHubLabel -OwnerName $ownerName -RepositoryName $repositoryName) It 'Should return expected number of labels' { - $($labels).Count | Should be $defaultLabels.Count + $labels.Count | Should be $defaultLabels.Count } $null = Remove-GitHubRepository -OwnerName $ownerName -RepositoryName $repositoryName @@ -187,7 +187,7 @@ try # Add new label New-GitHubLabel -OwnerName $ownerName -RepositoryName $repositoryName -Name $labelName -Color BBBBBB - $labels = Get-GitHubLabel -OwnerName $ownerName -RepositoryName $repositoryName + $labels = @(Get-GitHubLabel -OwnerName $ownerName -RepositoryName $repositoryName) # Change color of existing label Update-GitHubLabel -OwnerName $ownerName -RepositoryName $repositoryName -Name "bug" -NewName "bug" -Color BBBBBB @@ -200,10 +200,10 @@ try } Set-GitHubLabel -OwnerName $ownerName -RepositoryName $repositoryName -Label $defaultLabels - $labels = Get-GitHubLabel -OwnerName $ownerName -RepositoryName $repositoryName + $labels = @(Get-GitHubLabel -OwnerName $ownerName -RepositoryName $repositoryName) It 'Should return expected number of labels' { - $($labels).Count | Should be $defaultLabels.Count + $labels.Count | Should be $defaultLabels.Count $bugLabel = $labels | Where-Object {$_.name -eq "bug"} $bugLabel.color | Should be "fc2929" } @@ -269,7 +269,7 @@ try Remove-GitHubIssueLabel -OwnerName $ownerName -RepositoryName $repositoryName -Name "discussion" -Issue $issue.number Remove-GitHubIssueLabel -OwnerName $ownerName -RepositoryName $repositoryName -Name "question" -Issue $issue.number Remove-GitHubIssueLabel -OwnerName $ownerName -RepositoryName $repositoryName -Name "bug" -Issue $issue.number - $labelIssues = Get-GitHubLabel -OwnerName $ownerName -RepositoryName $repositoryName -Issue $issue.number + $labelIssues = @(Get-GitHubLabel -OwnerName $ownerName -RepositoryName $repositoryName -Issue $issue.number) It 'Should have removed three labels from the issue' { $labelIssues.Count | Should be ($defaultLabels.Count - 3) @@ -278,7 +278,7 @@ try Context 'For removing all issues'{ Remove-GitHubIssueLabel -OwnerName $ownerName -RepositoryName $repositoryName -Issue $issue.number - $labelIssues = Get-GitHubLabel -OwnerName $ownerName -RepositoryName $repositoryName -Issue $issue.number + $labelIssues = @(Get-GitHubLabel -OwnerName $ownerName -RepositoryName $repositoryName -Issue $issue.number) It 'Should have removed all labels from the issue' { $labelIssues.Count | Should be 0 @@ -312,7 +312,7 @@ try $labelIssues.Count | Should be $defaultLabels.Count } - $updatedIssueLabels = @($labelsToAdd[0]) + $updatedIssueLabels = $labelsToAdd[0] $updatedIssue = Update-GitHubIssue -OwnerName $ownerName -RepositoryName $repositoryName -Issue $issue.number -Label $updatedIssueLabels It 'Should have 1 label after updating the issue' { diff --git a/Tests/GitHubMilestones.tests.ps1 b/Tests/GitHubMilestones.tests.ps1 index 9e943908..5255afd4 100644 --- a/Tests/GitHubMilestones.tests.ps1 +++ b/Tests/GitHubMilestones.tests.ps1 @@ -71,7 +71,7 @@ try } Context 'For getting milestones from a repo' { - $existingMilestones = @(Get-GitHubMilestone -Uri $repo.svn_url -State Closed) + $existingMilestones =@(Get-GitHubMilestone -Uri $repo.svn_url -State Closed) $issue = Get-GitHubIssue -Uri $repo.svn_url -Issue $issue.number It 'Should have the expected number of milestones' { @@ -110,11 +110,11 @@ try $existingMilestones.Count | Should be 4 } - foreach($milestone in $existingMilestones) { + foreach ($milestone in $existingMilestones) { Remove-GitHubMilestone -Uri $repo.svn_url -Milestone $milestone.number } - $existingMilestones = @(Get-GitHubMilestone -Uri $repo.svn_url) + $existingMilestones = @(Get-GitHubMilestone -Uri $repo.svn_url -State All) $issue = Get-GitHubIssue -Uri $repo.svn_url -Issue $issue.number It 'Should have no milestones' { diff --git a/Tests/GitHubProjectCards.tests.ps1 b/Tests/GitHubProjectCards.tests.ps1 index e7a571d9..9ffbf7e2 100644 --- a/Tests/GitHubProjectCards.tests.ps1 +++ b/Tests/GitHubProjectCards.tests.ps1 @@ -52,7 +52,7 @@ try } Context 'Get cards for a column' { - $results = Get-GitHubProjectCard -Column $column.id + $results = @(Get-GitHubProjectCard -Column $column.id) It 'Should get cards' { $results | Should Not BeNullOrEmpty } @@ -63,24 +63,24 @@ try } Context 'Get all cards for a column' { - $results = Get-GitHubProjectCard -Column $column.id -ArchivedState All + $results = @(Get-GitHubProjectCard -Column $column.id -ArchivedState All) It 'Should get all cards' { $results.Count | Should Be 2 } } Context 'Get archived cards for a column' { - $results = Get-GitHubProjectCard -Column $column.id -ArchivedState Archived + $result = Get-GitHubProjectCard -Column $column.id -ArchivedState Archived It 'Should get archived card' { - $results | Should Not BeNullOrEmpty + $result | Should Not BeNullOrEmpty } It 'Note is correct' { - $results.note | Should be $defaultArchivedCard + $result.note | Should be $defaultArchivedCard } It 'Should be archived' { - $results.Archived | Should be $true + $result.Archived | Should be $true } } } @@ -103,46 +103,46 @@ try Context 'Modify card note' { $null = Set-GitHubProjectCard -Card $card.id -Note $defaultCardUpdated - $results = Get-GitHubProjectCard -Card $card.id + $result = Get-GitHubProjectCard -Card $card.id It 'Should get card' { - $results | Should Not BeNullOrEmpty + $result | Should Not BeNullOrEmpty } It 'Note has been updated' { - $results.note | Should be $defaultCardUpdated + $result.note | Should be $defaultCardUpdated } } Context 'Archive a card' { $null = Set-GitHubProjectCard -Card $cardArchived.id -Archive - $results = Get-GitHubProjectCard -Card $cardArchived.id + $result = Get-GitHubProjectCard -Card $cardArchived.id It 'Should get card' { - $results | Should Not BeNullOrEmpty + $result | Should Not BeNullOrEmpty } It 'Card is archived' { - $results.Archived | Should be $true + $result.Archived | Should be $true } } Context 'Restore a card' { $null = Set-GitHubProjectCard -Card $cardArchived.id -Restore - $results = Get-GitHubProjectCard -Card $cardArchived.id + $result = Get-GitHubProjectCard -Card $cardArchived.id It 'Should get card' { - $results | Should Not BeNullOrEmpty + $result | Should Not BeNullOrEmpty } It 'Card is not archived' { - $results.Archived | Should be $false + $result.Archived | Should be $false } } Context 'Move card position within column' { $null = Move-GitHubProjectCard -Card $cardTwo.id -Top - $results = Get-GitHubProjectCard -Column $column.id + $results = @(Get-GitHubProjectCard -Column $column.id) It 'Card is now top' { $results[0].note | Should be $defaultCardTwo @@ -151,7 +151,7 @@ try Context 'Move card using after parameter' { $null = Move-GitHubProjectCard -Card $cardTwo.id -After $card.id - $results = Get-GitHubProjectCard -Column $column.id + $results = @(Get-GitHubProjectCard -Column $column.id) It 'Card now exists in new column' { $results[1].note | Should be $defaultCardTwo @@ -160,7 +160,7 @@ try Context 'Move card to another column' { $null = Move-GitHubProjectCard -Card $cardTwo.id -Top -ColumnId $columnTwo.id - $results = Get-GitHubProjectCard -Column $columnTwo.id + $results = @(Get-GitHubProjectCard -Column $columnTwo.id) It 'Card now exists in new column' { $results[0].note | Should be $defaultCardTwo @@ -189,14 +189,14 @@ try } $card.id = (New-GitHubProjectCard -Column $column.id -Note $defaultCard).id - $results = Get-GitHubProjectCard -Card $card.id + $result = Get-GitHubProjectCard -Card $card.id It 'Card exists' { - $results | Should Not BeNullOrEmpty + $result | Should Not BeNullOrEmpty } It 'Note is correct' { - $results.note | Should be $defaultCard + $result.note | Should be $defaultCard } } @@ -214,14 +214,14 @@ try } $card.id = (New-GitHubProjectCard -Column $column.id -ContentId $issue.id -ContentType 'Issue').id - $results = Get-GitHubProjectCard -Card $card.id + $result = Get-GitHubProjectCard -Card $card.id It 'Card exists' { - $results | Should Not BeNullOrEmpty + $result | Should Not BeNullOrEmpty } It 'Content url is for an issue' { - $results.content_url | Should match 'issues' + $result.content_url | Should match 'issues' } } } diff --git a/Tests/GitHubProjectColumns.tests.ps1 b/Tests/GitHubProjectColumns.tests.ps1 index 2adf2a26..4591b677 100644 --- a/Tests/GitHubProjectColumns.tests.ps1 +++ b/Tests/GitHubProjectColumns.tests.ps1 @@ -37,13 +37,17 @@ try } Context 'Get columns for a project' { - $results = Get-GitHubProjectColumn -Project $project.id + $results = @(Get-GitHubProjectColumn -Project $project.id) It 'Should get column' { $results | Should Not BeNullOrEmpty } + It 'Should only have one column' { + $results.Count | Should Be 1 + } + It 'Name is correct' { - $results.name | Should be $defaultColumn + $results[0].name | Should Be $defaultColumn } } } @@ -65,32 +69,36 @@ try Context 'Modify column name' { $null = Set-GitHubProjectColumn -Column $column.id -Name $defaultColumnUpdate - $results = Get-GitHubProjectColumn -Column $column.id + $result = Get-GitHubProjectColumn -Column $column.id It 'Should get column' { - $results | Should Not BeNullOrEmpty + $result | Should Not BeNullOrEmpty } It 'Name has been updated' { - $results.name | Should be $defaultColumnUpdate + $result.name | Should Be $defaultColumnUpdate } } Context 'Move column to first position' { $null = Move-GitHubProjectColumn -Column $columntwo.id -First - $results = Get-GitHubProjectColumn -Project $project.id + $results = @(Get-GitHubProjectColumn -Project $project.id) + + It 'Should still have more than one column in the project' { + $results.Count | Should Be 2 + } It 'Column is now in the first position' { - $results[0].name | Should be $defaultColumnTwo + $results[0].name | Should Be $defaultColumnTwo } } Context 'Move column using after parameter' { $null = Move-GitHubProjectColumn -Column $columntwo.id -After $column.id - $results = Get-GitHubProjectColumn -Project $project.id + $results = @(Get-GitHubProjectColumn -Project $project.id) It 'Column is now not in the first position' { - $results[1].name | Should be $defaultColumnTwo + $results[1].name | Should Be $defaultColumnTwo } } @@ -116,14 +124,14 @@ try } $column.id = (New-GitHubProjectColumn -Project $project.id -Name $defaultColumn).id - $results = Get-GitHubProjectColumn -Column $column.id + $result = Get-GitHubProjectColumn -Column $column.id It 'Column exists' { - $results | Should Not BeNullOrEmpty + $result | Should Not BeNullOrEmpty } It 'Name is correct' { - $results.name | Should be $defaultColumn + $result.name | Should Be $defaultColumn } } } diff --git a/Tests/GitHubProjects.tests.ps1 b/Tests/GitHubProjects.tests.ps1 index 1ae123b8..931aa9c7 100644 --- a/Tests/GitHubProjects.tests.ps1 +++ b/Tests/GitHubProjects.tests.ps1 @@ -47,17 +47,21 @@ try $null = Remove-GitHubProject -Project $project.id -Confirm:$false } - $results = Get-GitHubProject -UserName $script:ownerName | Where-Object Name -eq $defaultUserProject + $results = @(Get-GitHubProject -UserName $script:ownerName | Where-Object Name -eq $defaultUserProject) It 'Should get project' { $results | Should Not BeNullOrEmpty } + It 'Should only get a single project' { + $results.Count | Should Be 1 + } + It 'Name is correct' { - $results.name | Should be $defaultUserProject + $results[0].name | Should be $defaultUserProject } It 'Description is correct' { - $results.body | Should be $defaultUserProjectDesc + $results[0].body | Should be $defaultUserProjectDesc } } @@ -73,17 +77,21 @@ try $null = Remove-GitHubProject -Project $project.id -Confirm:$false } - $results = Get-GitHubProject -OrganizationName $script:organizationName | Where-Object Name -eq $defaultOrgProject + $results = @(Get-GitHubProject -OrganizationName $script:organizationName | Where-Object Name -eq $defaultOrgProject) It 'Should get project' { $results | Should Not BeNullOrEmpty } + It 'Should only get a single project' { + $results.Count | Should Be 1 + } + It 'Name is correct' { - $results.name | Should be $defaultOrgProject + $results[0].name | Should be $defaultOrgProject } It 'Description is correct' { - $results.body | Should be $defaultOrgProjectDesc + $results[0].body | Should be $defaultOrgProjectDesc } } @@ -99,17 +107,21 @@ try $null = Remove-GitHubProject -Project $project.id -Confirm:$false } - $results = Get-GitHubProject -OwnerName $script:ownerName -RepositoryName $repo.name | Where-Object Name -eq $defaultRepoProject + $results = @(Get-GitHubProject -OwnerName $script:ownerName -RepositoryName $repo.name | Where-Object Name -eq $defaultRepoProject) It 'Should get project' { $results | Should Not BeNullOrEmpty } + It 'Should only get a single project' { + $results.Count | Should Be 1 + } + It 'Name is correct' { - $results.name | Should be $defaultRepoProject + $results[0].name | Should be $defaultRepoProject } It 'Description is correct' { - $results.body | Should be $defaultRepoProjectDesc + $results[0].body | Should be $defaultRepoProjectDesc } } @@ -126,21 +138,25 @@ try $null = Remove-GitHubProject -Project $project.id -Confirm:$false } - $results = Get-GitHubProject -OwnerName $script:ownerName -RepositoryName $repo.name -State 'Closed' | Where-Object Name -eq $defaultProjectClosed + $results = @(Get-GitHubProject -OwnerName $script:ownerName -RepositoryName $repo.name -State 'Closed' | Where-Object Name -eq $defaultProjectClosed) It 'Should get project' { $results | Should Not BeNullOrEmpty } + It 'Should only get a single project' { + $results.Count | Should Be 1 + } + It 'Name is correct' { - $results.name | Should be $defaultProjectClosed + $results[0].name | Should be $defaultProjectClosed } It 'Description is correct' { - $results.body | Should be $defaultProjectClosedDesc + $results[0].body | Should be $defaultProjectClosedDesc } It 'State is correct' { - $results.state | Should be "Closed" + $results[0].state | Should be "Closed" } } } @@ -159,17 +175,17 @@ try } $null = Set-GitHubProject -Project $project.id -Description $modifiedUserProjectDesc - $results = Get-GitHubProject -Project $project.id + $result = Get-GitHubProject -Project $project.id It 'Should get project' { - $results | Should Not BeNullOrEmpty + $result | Should Not BeNullOrEmpty } It 'Name is correct' { - $results.name | Should be $defaultUserProject + $result.name | Should be $defaultUserProject } It 'Description should be updated' { - $results.body | Should be $modifiedUserProjectDesc + $result.body | Should be $modifiedUserProjectDesc } } @@ -186,25 +202,25 @@ try } $null = Set-GitHubProject -Project $project.id -Description $modifiedOrgProjectDesc -Private:$false -OrganizationPermission Admin - $results = Get-GitHubProject -Project $project.id + $result = Get-GitHubProject -Project $project.id It 'Should get project' { - $results | Should Not BeNullOrEmpty + $result | Should Not BeNullOrEmpty } It 'Name is correct' { - $results.name | Should be $defaultOrgProject + $result.name | Should be $defaultOrgProject } It 'Description should be updated' { - $results.body | Should be $modifiedOrgProjectDesc + $result.body | Should be $modifiedOrgProjectDesc } It 'Visibility should be updated to public' { - $results.private | Should be $false + $result.private | Should be $false } It 'Organization permission should be updated to admin' { - $results.organization_permission | Should be 'admin' + $result.organization_permission | Should be 'admin' } } @@ -222,17 +238,17 @@ try } $null = Set-GitHubProject -Project $project.id -Description $modifiedRepoProjectDesc - $results = Get-GitHubProject -Project $project.id + $result = Get-GitHubProject -Project $project.id It 'Should get project' { - $results | Should Not BeNullOrEmpty + $result | Should Not BeNullOrEmpty } It 'Name is correct' { - $results.name | Should be $defaultRepoProject + $result.name | Should be $defaultRepoProject } It 'Description should be updated' { - $results.body | Should be $modifiedRepoProjectDesc + $result.body | Should be $modifiedRepoProjectDesc } } } @@ -252,17 +268,17 @@ try } $project.id = (New-GitHubProject -UserProject -Name $defaultUserProject -Description $defaultUserProjectDesc).id - $results = Get-GitHubProject -Project $project.id + $result = Get-GitHubProject -Project $project.id It 'Project exists' { - $results | Should Not BeNullOrEmpty + $result | Should Not BeNullOrEmpty } It 'Name is correct' { - $results.name | Should be $defaultUserProject + $result.name | Should be $defaultUserProject } It 'Description should be updated' { - $results.body | Should be $defaultUserProjectDesc + $result.body | Should be $defaultUserProjectDesc } } @@ -280,17 +296,17 @@ try } $project.id = (New-GitHubProject -OrganizationName $script:organizationName -Name $defaultOrgProject -Description $defaultOrgProjectDesc).id - $results = Get-GitHubProject -Project $project.id + $result = Get-GitHubProject -Project $project.id It 'Project exists' { - $results | Should Not BeNullOrEmpty + $result | Should Not BeNullOrEmpty } It 'Name is correct' { - $results.name | Should be $defaultOrgProject + $result.name | Should be $defaultOrgProject } It 'Description should be updated' { - $results.body | Should be $defaultOrgProjectDesc + $result.body | Should be $defaultOrgProjectDesc } } @@ -308,17 +324,17 @@ try } $project.id = (New-GitHubProject -OwnerName $script:ownerName -RepositoryName $repo.name -Name $defaultRepoProject -Description $defaultRepoProjectDesc).id - $results = Get-GitHubProject -Project $project.id + $result = Get-GitHubProject -Project $project.id It 'Project Exists' { - $results | Should Not BeNullOrEmpty + $result | Should Not BeNullOrEmpty } It 'Name is correct' { - $results.name | Should be $defaultRepoProject + $result.name | Should be $defaultRepoProject } It 'Description should be updated' { - $results.body | Should be $defaultRepoProjectDesc + $result.body | Should be $defaultRepoProjectDesc } } } diff --git a/Tests/GitHubReleases.tests.ps1 b/Tests/GitHubReleases.tests.ps1 index 9a4fbd5e..53d5ed31 100644 --- a/Tests/GitHubReleases.tests.ps1 +++ b/Tests/GitHubReleases.tests.ps1 @@ -17,7 +17,7 @@ try Describe 'Getting releases from repository' { $ownerName = "dotnet" $repositoryName = "core" - $releases = Get-GitHubRelease -OwnerName $ownerName -RepositoryName $repositoryName + $releases = @(Get-GitHubRelease -OwnerName $ownerName -RepositoryName $repositoryName) Context 'When getting all releases' { It 'Should return multiple releases' { @@ -26,24 +26,24 @@ try } Context 'When getting the latest releases' { - $latest = Get-GitHubRelease -OwnerName $ownerName -RepositoryName $repositoryName -Latest + $latest = @(Get-GitHubRelease -OwnerName $ownerName -RepositoryName $repositoryName -Latest) It 'Should return one value' { - @($latest).Count | Should Be 1 + $latest.Count | Should Be 1 } It 'Should return the first release from the full releases list' { - $releases[0].url | Should Be $releases[0].url - $releases[0].name | Should Be $releases[0].name + $latest[0].url | Should Be $releases[0].url + $latest[0].name | Should Be $releases[0].name } } Context 'When getting a specific release' { $specificIndex = 5 - $specific = Get-GitHubRelease -OwnerName $ownerName -RepositoryName $repositoryName -ReleaseId $releases[$specificIndex].id + $specific = @(Get-GitHubRelease -OwnerName $ownerName -RepositoryName $repositoryName -ReleaseId $releases[$specificIndex].id) It 'Should return one value' { - @($specific).Count | Should Be 1 + $specific.Count | Should Be 1 } It 'Should return the correct release' { @@ -53,10 +53,10 @@ try Context 'When getting a tagged release' { $taggedIndex = 8 - $tagged = Get-GitHubRelease -OwnerName $ownerName -RepositoryName $repositoryName -Tag $releases[$taggedIndex].tag_name + $tagged = @(Get-GitHubRelease -OwnerName $ownerName -RepositoryName $repositoryName -Tag $releases[$taggedIndex].tag_name) It 'Should return one value' { - @($tagged).Count | Should Be 1 + $tagged.Count | Should Be 1 } It 'Should return the correct release' { @@ -72,7 +72,7 @@ try try { Set-GitHubConfiguration -DefaultOwnerName "dotnet" Set-GitHubConfiguration -DefaultRepositoryName "core" - $releases = Get-GitHubRelease + $releases = @(Get-GitHubRelease) Context 'When getting all releases' { It 'Should return multiple releases' { diff --git a/Tests/GitHubRepositories.tests.ps1 b/Tests/GitHubRepositories.tests.ps1 index a313d40c..2a7361be 100644 --- a/Tests/GitHubRepositories.tests.ps1 +++ b/Tests/GitHubRepositories.tests.ps1 @@ -25,8 +25,8 @@ try $privateRepo = $privateRepo } - $publicRepos = Get-GitHubRepository -Visibility Public - $privateRepos = Get-GitHubRepository -Visibility Private + $publicRepos = @(Get-GitHubRepository -Visibility Public) + $privateRepos = @(Get-GitHubRepository -Visibility Private) It "Should have the public repo" { $publicRepo.Name | Should BeIn $publicRepos.Name @@ -50,7 +50,7 @@ try } Context 'For any user' { - $repos = Get-GitHubRepository -OwnerName 'octocat' -Type Public + $repos = @(Get-GitHubRepository -OwnerName 'octocat' -Type Public) It "Should have results for The Octocat" { $repos.Count | Should -BeGreaterThan 0 @@ -66,7 +66,7 @@ try $repo = $repo } - $repos = Get-GitHubRepository -OrganizationName $script:organizationName -Type All + $repos = @(Get-GitHubRepository -OrganizationName $script:organizationName -Type All) It "Should have results for the organization" { $repo.name | Should BeIn $repos.name } @@ -89,14 +89,14 @@ try $repo = $repo } - $returned = Get-GitHubRepository -Uri $repo.svn_url + $result = Get-GitHubRepository -Uri $repo.svn_url It "Should be a single result using Uri ParameterSet" { - $returned | Should -BeOfType PSCustomObject + $result | Should -BeOfType PSCustomObject } - $returned = Get-GitHubRepository -OwnerName $repo.owner.login -RepositoryName $repo.Name + $result = Get-GitHubRepository -OwnerName $repo.owner.login -RepositoryName $repo.Name It "Should be a single result using Elements ParameterSet" { - $returned | Should -BeOfType PSCustomObject + $result | Should -BeOfType PSCustomObject } It 'Should not permit additional parameters' { diff --git a/Tests/GitHubRepositoryForks.tests.ps1 b/Tests/GitHubRepositoryForks.tests.ps1 index 7c80ab7a..be56f282 100644 --- a/Tests/GitHubRepositoryForks.tests.ps1 +++ b/Tests/GitHubRepositoryForks.tests.ps1 @@ -13,14 +13,14 @@ $moduleRootPath = Split-Path -Path $PSScriptRoot -Parent try { Describe 'Creating a new fork for user' { - $originalForks = Get-GitHubRepositoryFork -OwnerName Microsoft -RepositoryName PowerShellForGitHub + $originalForks = @(Get-GitHubRepositoryFork -OwnerName Microsoft -RepositoryName PowerShellForGitHub) Context 'When a new fork is created' { $repo = New-GitHubRepositoryFork -OwnerName Microsoft -RepositoryName PowerShellForGitHub - $newForks = Get-GitHubRepositoryFork -OwnerName Microsoft -RepositoryName PowerShellForGitHub -Sort Newest + $newForks = @(Get-GitHubRepositoryFork -OwnerName Microsoft -RepositoryName PowerShellForGitHub -Sort Newest) It 'Should have one more fork than before' { - (@($newForks).Count - @($originalForks).Count) | Should be 1 + ($newForks.Count - $originalForks.Count) | Should be 1 } It 'Should be the latest fork in the list' { @@ -32,16 +32,14 @@ try } Describe 'Creating a new fork for an org' { - $originalForks = Get-GitHubRepositoryFork -OwnerName Microsoft -RepositoryName PowerShellForGitHub + $originalForks = @(Get-GitHubRepositoryFork -OwnerName Microsoft -RepositoryName PowerShellForGitHub) Context 'When a new fork is created' { - <# Temporary hack due to issues with this test in ADO #> . (Join-Path -Path $moduleRootPath -ChildPath 'Tests\Config\Settings.ps1') - $repo = New-GitHubRepositoryFork -OwnerName Microsoft -RepositoryName PowerShellForGitHub -OrganizationName $script:organizationName - $newForks = Get-GitHubRepositoryFork -OwnerName Microsoft -RepositoryName PowerShellForGitHub -Sort Newest + $newForks = @(Get-GitHubRepositoryFork -OwnerName Microsoft -RepositoryName PowerShellForGitHub -Sort Newest) It 'Should have one more fork than before' { - (@($newForks).Count - @($originalForks).Count) | Should be 1 + ($newForks.Count - $originalForks.Count) | Should be 1 } It 'Should be the latest fork in the list' { diff --git a/Tests/GitHubRepositoryTraffic.tests.ps1 b/Tests/GitHubRepositoryTraffic.tests.ps1 index 19d76d8e..53adb0d5 100644 --- a/Tests/GitHubRepositoryTraffic.tests.ps1 +++ b/Tests/GitHubRepositoryTraffic.tests.ps1 @@ -19,7 +19,7 @@ try $referrerList = Get-GitHubReferrerTraffic -Uri $repo.svn_url It 'Should return expected number of referrers' { - @($referrerList).Count | Should be 0 + $referrerList.Count | Should be 0 } Remove-GitHubRepository -Uri $repo.svn_url @@ -33,7 +33,7 @@ try $pathList = Get-GitHubPathTraffic -Uri $repo.svn_url It 'Should return expected number of popular content' { - @($pathList).Count | Should be 0 + $pathList.Count | Should be 0 } Remove-GitHubRepository -Uri $repo.svn_url From 296a221d4cdb710365c5a175da1abf9f90d67259 Mon Sep 17 00:00:00 2001 From: Howard Wolosky Date: Sat, 30 May 2020 15:43:09 -0700 Subject: [PATCH 06/60] Add security reporting policy documentation (#201) --- README.md | 7 +++++++ SECURITY.md | 48 ++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 55 insertions(+) create mode 100644 SECURITY.md diff --git a/README.md b/README.md index 9d35e6cc..c7417fc3 100644 --- a/README.md +++ b/README.md @@ -15,6 +15,7 @@ * [Legal and Licensing](#legal-and-licensing) * [Governance](#governance) * [Code of Conduct](#code-of-conduct) +* [Reporting Security Issues](#reporting-security-issues) * [Privacy Policy](#privacy-policy) ---------- @@ -159,6 +160,12 @@ For more info, see [CODE_OF_CONDUCT](CODE_OF_CONDUCT.md) ---------- +## Reporting Security Issues + +Please refer to [SECURITY.md](./SECURITY.md). + +---------- + ## Privacy Policy For more information, refer to Microsoft's [Privacy Policy](https://go.microsoft.com/fwlink/?LinkID=521839). diff --git a/SECURITY.md b/SECURITY.md new file mode 100644 index 00000000..6b29593d --- /dev/null +++ b/SECURITY.md @@ -0,0 +1,48 @@ + + +## Security + +Microsoft takes the security of our software products and services seriously, which includes all +source code repositories managed through our GitHub organizations, which include +[Microsoft](https://github.com/Microsoft), [Azure](https://github.com/Azure), +[DotNet](https://github.com/dotnet), [AspNet](https://github.com/aspnet), +[Xamarin](https://github.com/xamarin), and [many more](https://opensource.microsoft.com/). + +If you believe you have found a security vulnerability in any Microsoft-owned repository that meets +Microsoft's [definition](https://docs.microsoft.com/en-us/previous-versions/tn-archive/cc751383(v=technet.10)) +of a security vulnerability, please report it to us as described below. + +## Reporting Security Issues + +**Please do not report security vulnerabilities through public GitHub issues.** Instead, please +report them to the Microsoft Security Response Center at [secure@microsoft.com](mailto:secure@microsoft.com). +If possible, encrypt your message with our PGP key; please download it from the +[Microsoft Security Response Center PGP Key page](https://technet.microsoft.com/en-us/security/dn606155). + +You should receive a response within 24 hours. If for some reason you do not, please follow up via +email to ensure we received your original message. Additional information can be found at +[microsoft.com/msrc](https://www.microsoft.com/msrc). + +Please include the requested information listed below (as much as you can provide) to help us better +understand the nature and scope of the possible issue: + + * Type of issue (e.g. buffer overflow, SQL injection, cross-site scripting, etc.) + * Full paths of source file(s) related to the manifestation of the issue + * The location of the affected source code (tag/branch/commit or direct URL) + * Any special configuration required to reproduce the issue + * Step-by-step instructions to reproduce the issue + * Proof-of-concept or exploit code (if possible) + * Impact of the issue, including how an attacker might exploit the issue + +This information will help us triage your report more quickly. + +## Preferred Languages + +We prefer all communications to be in English. + +## Policy + +Microsoft follows the principle of +[Coordinated Vulnerability Disclosure](https://www.microsoft.com/en-us/msrc/cvd). + + \ No newline at end of file From a1ba15e94f4f51370042ec2237596a696aad3e02 Mon Sep 17 00:00:00 2001 From: Howard Wolosky Date: Sat, 30 May 2020 16:17:48 -0700 Subject: [PATCH 07/60] Add some additional badges to the README (#202) --- README.md | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index c7417fc3..15758273 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,19 @@ # PowerShellForGitHub PowerShell Module -[![[GitHub version]](https://badge.fury.io/gh/microsoft%2FPowerShellForGitHub.svg)](https://badge.fury.io/gh/microsoft%2FPowerShellForGitHub) +[![[GitHub version]](https://badge.fury.io/gh/microsoft%2FPowerShellForGitHub.svg)](https://github.com/microsoft/PowerShellForGitHub/releases) +[![powershellgallery](https://img.shields.io/powershellgallery/v/PowerShellForGitHub)](https://www.powershellgallery.com/packages/PowerShellForGitHub) +[![downloads](https://img.shields.io/powershellgallery/dt/PowerShellForGitHub.svg?label=downloads)](https://www.powershellgallery.com/packages/PowerShellForGitHub) +![GitHub code size in bytes](https://img.shields.io/github/languages/code-size/microsoft/PowerShellForGitHub) +[![downloads](https://img.shields.io/badge/license-MIT-green)](https://github.com/HowardWolosky/PowerShellForGitHub/blob/master/LICENSE) +[![CII Best Practices](https://bestpractices.coreinfrastructure.org/projects/3990/badge)](https://bestpractices.coreinfrastructure.org/projects/3990) +[![tweet](https://img.shields.io/twitter/url?url=https%3A%2F%2Ftwitter.com%2FQuackFu)](https://twitter.com/intent/tweet?text=%23PowerShellForGitHub%20%40QuackFu%20&original_referer=https://github.com/microsoft/PowerShellForGitHub) +
[![Build status](https://dev.azure.com/ms/PowerShellForGitHub/_apis/build/status/PowerShellForGitHub-CI?branchName=master)](https://dev.azure.com/ms/PowerShellForGitHub/_build/latest?definitionId=109&branchName=master) +[![Azure DevOps tests](https://img.shields.io/azure-devops/tests/ms/PowerShellForGitHub/109)](https://dev.azure.com/ms/PowerShellForGitHub/_build/latest?definitionId=109&branchName=master) +[![Azure DevOps coverage](https://img.shields.io/azure-devops/coverage/ms/PowerShellForGitHub/109)](https://dev.azure.com/ms/PowerShellForGitHub/_build/latest?definitionId=109&branchName=master) +
+[![Help Wanted Issues](https://img.shields.io/github/issues/microsoft/PowerShellForGitHub/help%20wanted)](https://github.com/microsoft/PowerShellForGitHub/issues?q=is%3Aissue+is%3Aopen+label%3A%22help+wanted%22) +[![GitHub last commit](https://img.shields.io/github/last-commit/microsoft/PowerShellForGitHub)](https://github.com/HowardWolosky/PowerShellForGitHub/commits/master) #### Table of Contents From a9f48a8aec796195664c3d86eb11755a1394d34e Mon Sep 17 00:00:00 2001 From: Howard Wolosky Date: Sat, 30 May 2020 16:30:38 -0700 Subject: [PATCH 08/60] Add automatic daily update checking (#185) The module will now check daily with PowerShellGallery to see if there is a newer version of the module available. This check can be disabled with the new `DisableUpdateCheck` config property. The check happens asynchronously. The web request is kicked off when the module is loaded, but the result is only checked when the user runs a command. The result is logged at the Verbose level in any failure case as well as when the module is up-to-date. When there is a newer version available, it will write the message out as a Warning. The web request will only run once per day, and the user message will only be written once per day as well. --- GitHubConfiguration.ps1 | 8 +++ GitHubCore.ps1 | 2 + PowerShellForGitHub.psd1 | 3 +- README.md | 2 +- UpdateCheck.ps1 | 145 +++++++++++++++++++++++++++++++++++++++ 5 files changed, 158 insertions(+), 2 deletions(-) create mode 100644 UpdateCheck.ps1 diff --git a/GitHubConfiguration.ps1 b/GitHubConfiguration.ps1 index 604fea5e..0d6e9f21 100644 --- a/GitHubConfiguration.ps1 +++ b/GitHubConfiguration.ps1 @@ -110,6 +110,10 @@ function Set-GitHubConfiguration Specify this switch to stop the module from reporting any of its usage (which would be used for diagnostics purposes). + .PARAMETER DisableUpdateCheck + Specify this switch to stop the daily update check with PowerShellGallery which can + inform you when there is a newer version of this module available. + .PARAMETER LogPath The location of the log file that all activity will be written to if DisableLogging remains $false. @@ -194,6 +198,8 @@ function Set-GitHubConfiguration [switch] $DisableTelemetry, + [switch] $DisableUpdateCheck, + [string] $LogPath, [switch] $LogProcessId, @@ -281,6 +287,7 @@ function Get-GitHubConfiguration 'DisablePiiProtection', 'DisableSmarterObjects', 'DisableTelemetry', + 'DisableUpdateCheck', 'LogPath', 'LogProcessId', 'LogRequestBody', @@ -615,6 +622,7 @@ function Import-GitHubConfiguration 'disablePiiProtection' = $false 'disableSmarterObjects' = $false 'disableTelemetry' = $false + 'disableUpdateCheck' = $false 'defaultNoStatus' = $false 'defaultOwnerName' = [String]::Empty 'defaultRepositoryName' = [String]::Empty diff --git a/GitHubCore.ps1 b/GitHubCore.ps1 index 59acd6f4..7a5850c6 100644 --- a/GitHubCore.ps1 +++ b/GitHubCore.ps1 @@ -129,6 +129,8 @@ function Invoke-GHRestMethod [switch] $NoStatus ) + Invoke-UpdateCheck + # Normalize our Uri fragment. It might be coming from a method implemented here, or it might # be coming from the Location header in a previous response. Either way, we don't want there # to be a leading "/" or trailing '/' diff --git a/PowerShellForGitHub.psd1 b/PowerShellForGitHub.psd1 index 7bcec540..02be20c7 100644 --- a/PowerShellForGitHub.psd1 +++ b/PowerShellForGitHub.psd1 @@ -43,7 +43,8 @@ 'GitHubTeams.ps1', 'GitHubUsers.ps1', 'NugetTools.ps1', - 'Telemetry.ps1') + 'Telemetry.ps1', + 'UpdateCheck.ps1') # Minimum version of the Windows PowerShell engine required by this module PowerShellVersion = '4.0' diff --git a/README.md b/README.md index 15758273..4cedbeb4 100644 --- a/README.md +++ b/README.md @@ -85,7 +85,7 @@ access token. If you ever wish to clear it in the future, just call `Clear-GitHubAuthentication`). > For automated scenarios (like GithHub Actions) where you are dynamically getting the access token -> needed for authentication, refer to `Example 2` in `Get-Help Set-StoreBrokerAuthentication -Examples` +> needed for authentication, refer to `Example 2` in `Get-Help Set-GitHubAuthentication -Examples` > for how to configure in a promptless fashion. > > Alternatively, you _could_ configure PowerShell itself to always pass in a plain-text access token diff --git a/UpdateCheck.ps1 b/UpdateCheck.ps1 new file mode 100644 index 00000000..b2ec5099 --- /dev/null +++ b/UpdateCheck.ps1 @@ -0,0 +1,145 @@ +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. + +# We'll cache our job name and result so that we don't check more than once per day. +$script:UpdateCheckJobName = $null +$script:HasLatestVersion = $null + +function Invoke-UpdateCheck +{ +<# + .SYNOPSIS + Checks PowerShellGallery to see if a newer version of this module has been published. + + .DESCRIPTION + Checks PowerShellGallery to see if a newer version of this module has been published. + + The check will only run once per day. + + Runs asynchronously, so the user won't see any message until after they run their first + API request after the module has been imported. + + Will always assume true in the event of an incomplete or failed check. + + Reports the result to the user via a Warning message (if a newer version is available) + or a Verbose message (if they're running the latest version or the version check failed). + + The Git repo for this module can be found here: http://aka.ms/PowerShellForGitHub + + .EXAMPLE + Invoke-UpdateCheck + + .NOTES + Internal-only helper method. +#> + param() + + if (Get-GitHubConfiguration -Name DisableUpdateCheck) + { + return + } + + $moduleName = $MyInvocation.MyCommand.Module.Name + $moduleVersion = $MyInvocation.MyCommand.Module.Version + + $jobNameToday = "Invoke-UpdateCheck-" + (Get-Date -format 'yyyyMMdd') + + # We only check once per day + if ($jobNameToday -eq $script:UpdateCheckJobName) + { + # Have we retrieved the status yet? $null means we haven't. + if ($null -ne $script:HasLatestVersion) + { + # We've already completed the check for today. No further action required. + return + } + + $state = (Get-Job -Name $script:UpdateCheckJobName).state + if ($state -eq 'Failed') + { + # We'll just assume we're up-to-date if we failed to check today. + Write-Log -Message '[$moduleName] update check failed for today (web request failed). Assuming up-to-date.' -Level Verbose + $script:HasLatestVersion = $true + + # Clear out the job info (even though we don't need the result) + $null = Receive-Job -Name $script:UpdateCheckJobName -AutoRemoveJob -Wait -ErrorAction SilentlyContinue -ErrorVariable errorInfo + return + } + elseif ($state -eq 'Completed') + { + $result = Receive-Job -Name $script:UpdateCheckJobName -AutoRemoveJob -Wait + try + { + # Occasionally the API puts two nearly identical XML responses in the body (each on a separate line). + # We'll try to avoid an unnecessary failure by always using the first line of the response. + $xml = [xml]($result.Content.Split([Environment]::NewLine) | Select-Object -First 1) + $latestVersion = $xml.feed.entry.properties.version | + ForEach-Object {[System.Version]$_} | + Sort-Object -Descending | + Select-Object -First 1 + + $script:HasLatestVersion = $latestVersion -eq $moduleVersion + if ($script:HasLatestVersion) + { + Write-Log "[$moduleName] update check complete. Running latest version: $($latestVersion.ToString())" -Level Verbose + } + else + { + Write-Log "A newer version of $moduleName is available ($latestVersion). Your current version is $moduleVersion. Run 'Update-Module $moduleName' to get up-to-date." -Level Warning + } + } + catch + { + # This could happen if the server returned back an invalid (non-XML) response for the request + # for some reason. + Write-Log -Message "[$moduleName] update check failed for today (invalid XML response). Assuming up-to-date." -Level Verbose + $script:HasLatestVersion = $true + } + + return + } + else + { + # It's still running. Nothing further for us to do right now. We'll check back + # again next time. + return + } + } + else + { + # We either haven't checked yet, or it's a new day so we should check again. + $script:UpdateCheckJobName = $jobNameToday + $script:HasLatestVersion = $null + } + + [scriptblock]$scriptBlock = { + param($ModuleName) + + $params = @{} + $params.Add('Uri', "https://www.powershellgallery.com/api/v2/FindPackagesById()?id='$ModuleName'") + $params.Add('Method', 'Get') + $params.Add('UseDefaultCredentials', $true) + $params.Add('UseBasicParsing', $true) + + try + { + Invoke-WebRequest @params + } + catch + { + # We will silently ignore any errors that occurred, but we need to make sure that + # we are explicitly catching and throwing them so that our reported state is Failed. + throw + } + } + + $null = Start-Job -Name $script:UpdateCheckJobName -ScriptBlock $scriptBlock -Arg @($moduleName) + + # We're letting this run asynchronously so that users can immediately start using the module. + # We'll check back in on the result of this the next time they run any command. +} + +# We will explicitly run this as soon as the module has been loaded, +# and then it will be called again during every major function call in the module +# so that the result can be reported. +Invoke-UpdateCheck \ No newline at end of file From ed9b114fe25dfb2d91752cbf06d0b5c0389b30ca Mon Sep 17 00:00:00 2001 From: Howard Wolosky Date: Sat, 30 May 2020 22:14:51 -0700 Subject: [PATCH 09/60] Update module to 0.14.0 (#203) --- CHANGELOG.md | 45 ++++++++++++++++++++++++++++++++++++++++ PowerShellForGitHub.psd1 | 4 ++-- 2 files changed, 47 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e1f4505c..a0049c52 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,51 @@ # PowerShellForGitHub PowerShell Module ## Changelog + [0.14.0](https://github.com/PowerShell/PowerShellForGitHub/tree/0.14.0) - (2020/05/30) +### Features: ++ The module will now asynchronously check for updates up to once per day. This can be disabled + if desired with the `Set-GitHubConfiguration -DisableUpdateCheck`. + [[pr]](https://github.com/PowerShell/PowerShellForGitHub/pull/185) | [[cl]](https://github.com/microsoft/PowerShellForGitHub/commit/a9f48a8aec796195664c3d86eb11755a1394d34e) ++ It turns out that `Group-GitHubPullRequest` which was written back in `0.2.0` was never actually + exported. Now it is. + [[pr]](https://github.com/PowerShell/PowerShellForGitHub/pull/180) | [[cl]](https://github.com/microsoft/PowerShellForGitHub/commit/b7e1ea1cb912493e110b9854b0ec7700462254a0) + +### Fixes: +- Fixes the behavior of `Get-GitHubRepository`. It actually had a number of issues: + [[pr]](https://github.com/PowerShell/PowerShellForGitHub/pull/179) | [[cl]](https://github.com/microsoft/PowerShellForGitHub/commit/c4c1ec344a357489d248b9cf1bc2837484d4915f) + - `-GetAllPublicRepositories` didn't acutally work. Now it does, along with the newly + added `Since` parameters. + - Fixed the ParameterSet handling for all parameters to make sure that users can only specify + the correct combination of parameters. +- Fixes multi-result behavior across all versions of PowerShell. You can now reliably capture + the result of an API call like this: `@(Get-GitHubRepository ...)` and be assured that you'll + get an array result with the proper count of items. As a result, this fixes all remaining failing + UT's on PowerShell 7. + [[pr]](https://github.com/PowerShell/PowerShellForGitHub/pull/199) | [[cl]](https://github.com/microsoft/PowerShellForGitHub/commit/bcd0a5616e1395ca480bc7f3b64776eada2a6670) +- Fixed an erroneous exception that occurred when calling `New-GitHubRepository` when specifying + a `TeamId`. + [[pr]](https://github.com/PowerShell/PowerShellForGitHub/pull/196) | [[cl]](https://github.com/microsoft/PowerShellForGitHub/commit/587e2042621091c79cc06be2aa9cc6ea836561f4) +- The module is now PSScriptAnalyzer clean (again). This also fixed pipeline handling in + `Group-GitHubPullRequest`, `Group-GitHubIssue` and `ConvertFrom-GitHubMarkdown`. + [[pr]](https://github.com/PowerShell/PowerShellForGitHub/pull/180) | [[cl]](https://github.com/microsoft/PowerShellForGitHub/commit/b7e1ea1cb912493e110b9854b0ec7700462254a0) +- Fixed some documentation which referenced that private repos were only available to paid GitHub + plans. + [[pr]](https://github.com/PowerShell/PowerShellForGitHub/pull/191) | [[cl]](https://github.com/microsoft/PowerShellForGitHub/commit/3c70b8d2702a4a7b5674bb72decacb385f1a47a8) +- Fixed a bug preventing quering for a specifically named branch with `Get-GitHubRepositoryBranch`. + [[pr]](https://github.com/PowerShell/PowerShellForGitHub/pull/188) | [[cl]](https://github.com/microsoft/PowerShellForGitHub/commit/5807d00ed0a3acd293057d8a9c06a9d68b6030db) +- Correctly fixed the hash that catches whether or not a developer has updated the settings file used + when running this module's unit tests. It involved updating the hash and then also ensuring we + always check the file out with consistent line endings. + [[pr]](https://github.com/PowerShell/PowerShellForGitHub/pull/181) | [[cl]](https://github.com/microsoft/PowerShellForGitHub/commit/93689d69eedc50f084982a6fba21183857507dbb) && + [[pr]](https://github.com/PowerShell/PowerShellForGitHub/pull/183) | [[cl]](https://github.com/microsoft/PowerShellForGitHub/commit/b4439f4a6b12f89d755851b313eff0e9ea0b3ab5) +- Documentation updates around configuring unattended authentication. + [[pr]](https://github.com/PowerShell/PowerShellForGitHub/pull/173) | [[cl]](https://github.com/microsoft/PowerShellForGitHub/commit/3440909f5f1264865ccfca85ce2364af3ce85425) + +Authors: + * [**@HowardWolosky**](https://github.com/HowardWolosky) + +------ + [0.13.1](https://github.com/PowerShell/PowerShellForGitHub/tree/0.13.1) - (2020/05/12) ### Fixes: - Ensure progress bar for Wait-JobWithAnimation gets marked as Completed diff --git a/PowerShellForGitHub.psd1 b/PowerShellForGitHub.psd1 index 02be20c7..33392da2 100644 --- a/PowerShellForGitHub.psd1 +++ b/PowerShellForGitHub.psd1 @@ -7,7 +7,7 @@ CompanyName = 'Microsoft Corporation' Copyright = 'Copyright (C) Microsoft Corporation. All rights reserved.' - ModuleVersion = '0.13.1' + ModuleVersion = '0.14.0' Description = 'PowerShell wrapper for GitHub API' # Script module or binary module file associated with this manifest. @@ -180,7 +180,7 @@ # IconUri = '' # ReleaseNotes of this module - # ReleaseNotes = '' + ReleaseNotes = 'https://github.com/microsoft/PowerShellForGitHub/blob/master/CHANGELOG.md' } } From efdcbfa4a086bd4606ec2c32ef67db8553711781 Mon Sep 17 00:00:00 2001 From: Howard Wolosky Date: Sun, 31 May 2020 00:24:35 -0700 Subject: [PATCH 10/60] UpdateCheck must handle when module is newer than published version (#204) Publishing a new version to PowerShellGallery happens a few hours after the version is updated in GitHub. This edge-case meant that someone in this state would have been told that the older published version was newer than the version that they were currently using. --- UpdateCheck.ps1 | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/UpdateCheck.ps1 b/UpdateCheck.ps1 index b2ec5099..c76bb253 100644 --- a/UpdateCheck.ps1 +++ b/UpdateCheck.ps1 @@ -81,7 +81,11 @@ function Invoke-UpdateCheck $script:HasLatestVersion = $latestVersion -eq $moduleVersion if ($script:HasLatestVersion) { - Write-Log "[$moduleName] update check complete. Running latest version: $($latestVersion.ToString())" -Level Verbose + Write-Log "[$moduleName] update check complete. Running latest version: $latestVersion" -Level Verbose + } + elseif ($moduleVersion -gt $latestVersion) + { + Write-Log "[$moduleName] update check complete. This version ($moduleVersion) is newer than the latest published version ($latestVersion)." -Level Verbose } else { From b9dc12f63d19ff70b9d7d5fa65751fa3ee5e07f7 Mon Sep 17 00:00:00 2001 From: Howard Wolosky Date: Sun, 31 May 2020 09:58:36 -0700 Subject: [PATCH 11/60] Speed up Pester tests by using -NoStatus Suppressing Status (e.g. using `-NoStatus`) reduces the overall runtime of the UT's across all 3 platforms (Windows, Linux, Mac) by 25-30 min. It also has the side-effect of improving the readability of the raw logs on CI for Linux and Mac. [Most recent run](https://dev.azure.com/ms/PowerShellForGitHub/_build/results?buildId=84315) without this change: 1h 19m [Run with this change](https://dev.azure.com/ms/PowerShellForGitHub/_build/results?buildId=84357) without this change: 54m Resolves #206 --- Tests/Common.ps1 | 1 + 1 file changed, 1 insertion(+) diff --git a/Tests/Common.ps1 b/Tests/Common.ps1 index 77673eda..de736fe2 100644 --- a/Tests/Common.ps1 +++ b/Tests/Common.ps1 @@ -89,6 +89,7 @@ function Initialize-CommonTestSetup Reset-GitHubConfiguration Set-GitHubConfiguration -DisableTelemetry # We don't want UT's to impact telemetry Set-GitHubConfiguration -LogRequestBody # Make it easier to debug UT failures + Set-GitHubConfiguration -DefaultNoStatus # Status corrupts the raw CI logs for Linux and Mac, and makes runs take slightly longer. } Initialize-CommonTestSetup From 738d825e73a2a2781b2d1f10270132839fe2add7 Mon Sep 17 00:00:00 2001 From: Howard Wolosky Date: Sun, 31 May 2020 17:10:52 -0700 Subject: [PATCH 12/60] Pipeline badges in README should be reporting status from master (#209) --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 4cedbeb4..2a8bc645 100644 --- a/README.md +++ b/README.md @@ -9,8 +9,8 @@ [![tweet](https://img.shields.io/twitter/url?url=https%3A%2F%2Ftwitter.com%2FQuackFu)](https://twitter.com/intent/tweet?text=%23PowerShellForGitHub%20%40QuackFu%20&original_referer=https://github.com/microsoft/PowerShellForGitHub)
[![Build status](https://dev.azure.com/ms/PowerShellForGitHub/_apis/build/status/PowerShellForGitHub-CI?branchName=master)](https://dev.azure.com/ms/PowerShellForGitHub/_build/latest?definitionId=109&branchName=master) -[![Azure DevOps tests](https://img.shields.io/azure-devops/tests/ms/PowerShellForGitHub/109)](https://dev.azure.com/ms/PowerShellForGitHub/_build/latest?definitionId=109&branchName=master) -[![Azure DevOps coverage](https://img.shields.io/azure-devops/coverage/ms/PowerShellForGitHub/109)](https://dev.azure.com/ms/PowerShellForGitHub/_build/latest?definitionId=109&branchName=master) +[![Azure DevOps tests](https://img.shields.io/azure-devops/tests/ms/PowerShellForGitHub/109/master)](https://dev.azure.com/ms/PowerShellForGitHub/_build/latest?definitionId=109&branchName=master) +[![Azure DevOps coverage](https://img.shields.io/azure-devops/coverage/ms/PowerShellForGitHub/109/master)](https://dev.azure.com/ms/PowerShellForGitHub/_build/latest?definitionId=109&branchName=master)
[![Help Wanted Issues](https://img.shields.io/github/issues/microsoft/PowerShellForGitHub/help%20wanted)](https://github.com/microsoft/PowerShellForGitHub/issues?q=is%3Aissue+is%3Aopen+label%3A%22help+wanted%22) [![GitHub last commit](https://img.shields.io/github/last-commit/microsoft/PowerShellForGitHub)](https://github.com/HowardWolosky/PowerShellForGitHub/commits/master) From b9e17084c73e70078534b0da03182eca7a30aa3a Mon Sep 17 00:00:00 2001 From: Howard Wolosky Date: Sun, 31 May 2020 17:17:48 -0700 Subject: [PATCH 13/60] Disable the module update check during Pester Tests (#210) We don't get any value from this update check when running the tests. --- Tests/Common.ps1 | 1 + 1 file changed, 1 insertion(+) diff --git a/Tests/Common.ps1 b/Tests/Common.ps1 index de736fe2..f28dc534 100644 --- a/Tests/Common.ps1 +++ b/Tests/Common.ps1 @@ -90,6 +90,7 @@ function Initialize-CommonTestSetup Set-GitHubConfiguration -DisableTelemetry # We don't want UT's to impact telemetry Set-GitHubConfiguration -LogRequestBody # Make it easier to debug UT failures Set-GitHubConfiguration -DefaultNoStatus # Status corrupts the raw CI logs for Linux and Mac, and makes runs take slightly longer. + Set-GitHubConfiguration -DisableUpdateCheck # The update check is unnecessary during tests. } Initialize-CommonTestSetup From a3d8483abb4df9d1f315723a941dda95498f6a3c Mon Sep 17 00:00:00 2001 From: Howard Wolosky Date: Sun, 31 May 2020 19:48:58 -0700 Subject: [PATCH 14/60] Add NUnit Test handling for PSScriptAnalyzer results (#211) This will now convert the output of `Invoke-ScriptAnalyzer` into an NUnitXml file that can then be published through the CI system so that we can fail on ScriptAnalyzer failures, as well as easily see them in the test output. Got the initial idea for this approach from @MathieuBuisson's [blog post](https://mathieubuisson.github.io/psscriptanalyzer-first-class-citizen/) (which directly converted the ScriptAnalyzer results into an NUnit XML file, but only had a single passing test for the success scenario). I then combined that idea with one from a [Dr. Scripto post](https://devblogs.microsoft.com/scripting/psscriptanalyzer-deep-dive-part-3-of-4/) which used Pester to invoke PSScriptAnalyzer, ensuring that there was a test created for each possible Rule. The problem with the Dr. Scripto approach was that multiple failures of the same rule still resulted in a single failure, and the test output lost the actual message from ScriptAnalyzer. I ended up implementing this differently than Mathieu, directly working with the XML class to build up the XML document, but really appreciated the initial ideas that he had with this approach. Sample build test result when there was a Script Analysis failure: https://dev.azure.com/ms/PowerShellForGitHub/_build/results?buildId=84441&view=ms.vss-test-web.build-test-results-tab --- .../templates/run-staticAnalysis.yaml | 14 + build/scripts/ConvertTo-NUnitXml.ps1 | 478 ++++++++++++++++++ 2 files changed, 492 insertions(+) create mode 100644 build/scripts/ConvertTo-NUnitXml.ps1 diff --git a/build/pipelines/templates/run-staticAnalysis.yaml b/build/pipelines/templates/run-staticAnalysis.yaml index f1035e12..feddaa9b 100644 --- a/build/pipelines/templates/run-staticAnalysis.yaml +++ b/build/pipelines/templates/run-staticAnalysis.yaml @@ -11,4 +11,18 @@ steps: - powershell: | $results = Invoke-ScriptAnalyzer -Path ./ –Recurse $results | ForEach-Object { Write-Host "##vso[task.logissue type=$($_.Severity);sourcepath=$($_.ScriptPath);linenumber=$($_.Line);columnnumber=$($_.Column);]$($_.Message)" } + + $null = New-Item -Path ..\ -Name ScriptAnalyzer -ItemType Directory -Force + ./build/scripts/ConvertTo-NUnitXml.ps1 -ScriptAnalyzerResult $results -Path ../ScriptAnalyzer/test-results.xml + workingDirectory: '$(System.DefaultWorkingDirectory)' displayName: 'Run Static Code Analysis (PSScriptAnalyzer)' + + - task: PublishTestResults@2 + displayName: 'Publish ScriptAnalyzer Test Results' + inputs: + testRunTitle: 'Windows Test Results for PSScriptAnalyzer' + buildPlatform: 'Windows' + testRunner: NUnit + testResultsFiles: '../ScriptAnalyzer/test-results.xml' + failTaskOnFailedTests: true # required to fail build when tests fail + condition: succeededOrFailed() diff --git a/build/scripts/ConvertTo-NUnitXml.ps1 b/build/scripts/ConvertTo-NUnitXml.ps1 new file mode 100644 index 00000000..fc65224b --- /dev/null +++ b/build/scripts/ConvertTo-NUnitXml.ps1 @@ -0,0 +1,478 @@ +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. + +<# + .DESCRIPTION + This script takes the output of PSScriptAnalyzer and converts it into an NUnit XML schema + file which can be fed into CI test reporting. + + .PARAMETER ScriptAnalyzerResult + The output from Invoke-PSScriptAnalzyer to be written to an NUnit XML file. + + .PARAMETER Path + The path the xml config file should be written to. + + .PARAMETER Force + Overwrite the file at Path if it exists. + + .EXAMPLE + $results = Invoke-ScriptAnalyzer -Path ./ -Recurse + .\ConverTo-NUnitXml.ps1 -ScriptAnalyzerResult $results -Path ./PSScriptAnalyzerFailures.xml +#> +[CmdletBinding()] +[Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSAvoidUsingWriteHost", "", Justification = "This is the preferred way of writing output for Azure DevOps.")] +param( + [Parameter(Mandatory)] + [AllowNull()] + [Microsoft.Windows.PowerShell.ScriptAnalyzer.Generic.DiagnosticRecord[]] $ScriptAnalyzerResult, + + [Parameter(Mandatory)] + [string] $Path, + + [switch] $Force +) + +# Convert sucess/failure into the appropriate terms for certain NUnit attributes. +$script:SuccessTerms = @{ + $true = [PSCustomObject]@{ + 'result' = 'Success' + 'success' = 'True'} + + $false = [PSCustomObject]@{ + 'result' = 'Failure' + 'success' = 'False'} +} + +function Resolve-UnverifiedPath +{ +<# + .SYNOPSIS + A wrapper around Resolve-Path that works for paths that exist as well + as for paths that don't (Resolve-Path normally throws an exception if + the path doesn't exist.) + + .DESCRIPTION + A wrapper around Resolve-Path that works for paths that exist as well + as for paths that don't (Resolve-Path normally throws an exception if + the path doesn't exist.) + + .EXAMPLE + Resolve-UnverifiedPath -Path 'c:\windows\notepad.exe' + + Returns the string 'c:\windows\notepad.exe'. + + .EXAMPLE + Resolve-UnverifiedPath -Path '..\notepad.exe' + + Returns the string 'c:\windows\notepad.exe', assuming that it's executed from + within 'c:\windows\system32' or some other sub-directory. + + .EXAMPLE + Resolve-UnverifiedPath -Path '..\foo.exe' + + Returns the string 'c:\windows\foo.exe', assuming that it's executed from + within 'c:\windows\system32' or some other sub-directory, even though this + file doesn't exist. + + .OUTPUTS + [string] - The fully resolved path + +#> + [CmdletBinding()] + param( + [Parameter( + Position=0, + ValueFromPipeline)] + [string] $Path + ) + + process + { + $resolvedPath = Resolve-Path -Path $Path -ErrorVariable resolvePathError -ErrorAction SilentlyContinue + + if ($null -eq $resolvedPath) + { + Write-Output -InputObject ($resolvePathError[0].TargetObject) + } + else + { + Write-Output -InputObject ($resolvedPath.ProviderPath) + } + } +} + +function New-NUnitXml +{ +<# + .SYNOPSIS + Creates a new, empty NUnit Xml file. + + .OUTPUTS + XmlDocument + + .NOTES + It's expected that the "total" and "failures" attributes on test-results will be updated + by the caller after this object has been returned. +#> + [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSUseShouldProcessForStateChangingFunctions", "", Justification="This does not change any system state.")] + param() + + $date = Get-Date + $dateString = $date.ToString("yyyy-MM-dd") + $timeString = $date.ToString("HH:mm:ss") + + $xml = [xml]([String]::Format(' + ', + $dateString, $timeString)) + + return $xml +} + +function Add-Environment +{ +<# + .SYNOPSIS + Adds the environment node to the NUnit Xml document. + + .PARAMETER Parent + The parent element that the element is being added to. +#> + param( + [Parameter(Mandatory)] + [System.Xml.XmlElement] $Parent + ) + + try + { + $environment = $Parent.OwnerDocument.CreateElement('environment', $Parent.OwnerDocument.NamespaceURI) + $null = $Parent.AppendChild($environment) + $environment.SetAttribute('user', $env:USERNAME) + $environment.SetAttribute('machine-name', $env:COMPUTERNAME) + $environment.SetAttribute('cwd', (Get-Location)) + $environment.SetAttribute('user-domain', $env:USERDOMAIN) + $environment.SetAttribute('nunit-version', '2.5.8.0') + $environment.SetAttribute('platform', ([System.Environment]::OSVersion.Platform)) + $environment.SetAttribute('os-version', ([System.Environment]::OSVersion.Version.ToString())) + $environment.SetAttribute('clr-version', $PSVersionTable.CLRVersion) + + return $environment + } + catch + { + throw + } +} + +function Add-CultureInfo +{ +<# + .SYNOPSIS + Adds the culture-info node to the NUnit Xml document. + + .PARAMETER Parent + The parent element that the element is being added to. +#> + param( + [Parameter(Mandatory)] + [System.Xml.XmlElement] $Parent + ) + + try + { + $cultureInfo = $Parent.OwnerDocument.CreateElement('culture-info', $Parent.OwnerDocument.NamespaceURI) + $null = $Parent.AppendChild($cultureInfo) + $cultureInfo.SetAttribute('current-culture', ((Get-Culture).Name)) + $cultureInfo.SetAttribute('current-uiculture', ((Get-UICulture).Name)) + + return $cultureInfo + } + catch + { + throw + } +} + +function Add-TestSuite +{ +<# + .SYNOPSIS + Adds a test-suite node to the NUnit Xml document. + + .PARAMETER Parent + The parent element that the element is being added to. +#> + param( + [Parameter(Mandatory)] + [System.Xml.XmlElement] $Parent, + + [Parameter(Mandatory)] + [string] $Type, + + [Parameter(Mandatory)] + [string] $Name, + + [string] $Description, + + [switch] $Succeeded + ) + + try + { + $testSuite = $Parent.OwnerDocument.CreateElement('test-suite', $Parent.OwnerDocument.NamespaceURI) + $null = $Parent.AppendChild($testSuite) + $testSuite.SetAttribute('type', $Type) + $testSuite.SetAttribute('name', $Name) + if ($PSBoundParameters.ContainsKey('Description')) { $testSuite.SetAttribute('description', $Description) } + $testSuite.SetAttribute('executed', 'True') + $testSuite.SetAttribute('time', '0.0') + $testSuite.SetAttribute('asserts', '0') + $testSuite.SetAttribute('result', $script:SuccessTerms[$Succeeded.ToBool()].result) + $testSuite.SetAttribute('success', $script:SuccessTerms[$Succeeded.ToBool()].success) + + return $testSuite + } + catch + { + throw + } +} + +function Add-Results +{ +<# + .SYNOPSIS + Adds a results node to the NUnit Xml document. + + .PARAMETER Parent + The parent element that the element is being added to. +#> + [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSUseSingularNouns", "", Justification="This is intended to reflect the actual name of the node being added.")] + param( + [Parameter(Mandatory)] + [System.Xml.XmlElement] $Parent + ) + + try + { + $results = $Parent.OwnerDocument.CreateElement('results', $Parent.OwnerDocument.NamespaceURI) + $null = $Parent.AppendChild($results) + + return $results + } + catch + { + throw + } +} + +function Add-TestCase +{ +<# + .SYNOPSIS + Adds a test case result to an existing XMLElement results node. + + .PARAMETER Parent + The parent element that the element is being added to. + + .PARAMETER Name + The name of the test case. + + .PARAMETER Description + A descrition of the test case. + + .PARAMETER ScriptAnalyzerResult + The PSScriptAnalyzer result record which explains the specific failure. + + .OUTPUTS + XmlElement. Returns a reference to the newly created test-case element. + + .EXAMPLE + Add-TestCase -Parent $element -Name 'All entries for this rule succeeded' -Description 'All entries for this rule succeeded' + + Adds a successful test-case element to the parent element provided. + + .EXAMPLE + Add-TestCase -Parent $element -ScriptAnalyzerResult $result + + Adds a failure test-case element to the parent element provided, with the relevant + information extracted from the PSScriptAnalyzer result object. +#> + [CmdletBinding(DefaultParameterSetName='Success')] + param( + [Parameter( + Mandatory, + ParameterSetName='Success')] + [Parameter( + Mandatory, + ParameterSetName='Failure')] + [System.Xml.XmlElement] $Parent, + + [Parameter( + Mandatory, + ParameterSetName='Success')] + [string] $Name, + + [Parameter( + Mandatory, + ParameterSetName='Success')] + [string] $Description, + + [Parameter( + Mandatory, + ParameterSetName='Failure')] + [Microsoft.Windows.PowerShell.ScriptAnalyzer.Generic.DiagnosticRecord] $ScriptAnalyzerResult + ) + + try + { + $testCase = $Parent.OwnerDocument.CreateElement('test-case', $Parent.OwnerDocument.NamespaceURI) + $null = $Parent.AppendChild($testCase) + + $succeeded = ($PSCmdlet.ParameterSetName -eq 'Success') + if (-not $succeeded) + { + $Name = "[$($ScriptAnalyzerResult.RuleName)] - $($ScriptAnalyzerResult.ScriptPath):$($ScriptAnalyzerResult.Line), $($ScriptAnalyzerResult.Column)" + + $rules = Get-ScriptAnalyzerRule + $Description = ($rules | Where-Object { $_.RuleName -eq $ScriptAnalyzerResult.RuleName }).Description + } + + $testCase.SetAttribute('name', $Name) + $testCase.SetAttribute('description', $Description) + $testCase.SetAttribute('time', '0.0') + $testCase.SetAttribute('executed', 'True') + $testCase.SetAttribute('asserts', '0') + $testCase.SetAttribute('result', $script:SuccessTerms[$succeeded].result) + $testCase.SetAttribute('success', $script:SuccessTerms[$succeeded].success) + + if (-not $succeeded) + { + $failure = $Parent.OwnerDocument.CreateElement('failure', $Parent.OwnerDocument.NamespaceURI) + $null = $testCase.AppendChild($failure) + + $message = $Parent.OwnerDocument.CreateElement('message', $Parent.OwnerDocument.NamespaceURI) + $null = $failure.AppendChild($message) + $message.InnerText = $ScriptAnalyzerResult.Message + + $stackTraceElement = $Parent.OwnerDocument.CreateElement('stack-trace', $Parent.OwnerDocument.NamespaceURI) + $null = $failure.AppendChild($stackTraceElement) + + $generatedStackTrace = @( + "at line: $($ScriptAnalyzerResult.line) in $($ScriptAnalyzerResult.ScriptPath)", + " $($ScriptAnalyzerResult.Extent.Text)", + " Severity: $($ScriptAnalyzerResult.Severity)") + $stackTraceElement.InnerText = ($generatedStackTrace -join [Environment]::NewLine) + } + + return $testCase + } + catch + { + throw + } + +} + +function ConvertTo-NUnitXml +{ +<# + .DESCRIPTION + Takes the output of PSScriptAnalyzer and converts it into an NUnit XML schema + object. + + .PARAMETER ScriptAnalyzerResult + One or more PSScriptAnalyzer result records to be written to the NUnit XML file. + + .OUTPUTS + XmlDocument +#> + param( + [Parameter(Mandatory)] + [AllowNull()] + [Microsoft.Windows.PowerShell.ScriptAnalyzer.Generic.DiagnosticRecord[]] $ScriptAnalyzerResult + ) + + try + { + $hasFailures = ($ScriptAnalyzerResult.Count -gt 0) + $totalTests = 0 + $totalFailures = 0 + + $xml = New-NUnitXml + $null = Add-Environment -Parent $xml.DocumentElement + $null = Add-CultureInfo -Parent $xml.DocumentElement + $mainTestSuite = Add-TestSuite -Parent $xml.DocumentElement -Type 'PowerShell' -Name -'PSScriptAnalyzer' -Succeeded:(-not $hasFailures) + $mainResults = Add-Results -Parent $mainTestSuite + + $rules = Get-ScriptAnalyzerRule + foreach ($rule in $rules) + { + $failures = $ScriptAnalyzerResult | Where-Object { $_.RuleName -eq $rule } + + $testSuite = Add-TestSuite -Parent $mainResults -Type 'TestFixture' -Name $rule.RuleName -Description $rule.Description -Succeeded:($failures.Count -eq 0) + $results = Add-Results -Parent $testSuite + + if ($failures.Count -eq 0) + { + $name = "All files pass rule [$($rule.RuleName)]" + $null = Add-TestCase -Parent $results -Name $name -Description $rule.Description + $totalTests++ + } + else + { + foreach ($failure in $failures) + { + $null = Add-TestCase -Parent $results -ScriptAnalyzerResult $failure + $totalTests++ + $totalFailures++ + } + } + } + + # Finally, we need to update a few attributes in the root test-results + $xml.'test-results'.total = $totalTests.ToString() + $xml.'test-results'.failures = $totalFailures.ToString() + + # Catch an odd edge case if somehow there was a rule that failed that wasn't in the list + # of rules returned. + if ($totalFailures -ne $ScriptAnalyzerResult.Count) + { + Write-Error "The total generated number of failures ($totalFailures) does not match the expected number of failures ($($ScriptAnalyzerResult.Count))." + } + + return $xml + } + catch + { + throw + } +} + +# Script body + +$scriptName = Split-Path -Leaf -Path $PSCommandPath +try +{ + Write-Host "$($scriptName): Trying to create NUnit XML file based off of the provided PSScriptAnalyzer results." + + $Path = Resolve-UnverifiedPath -Path $Path + if ((Test-Path -Path $Path -PathType Leaf) -and (-not $Force)) + { + throw "File at [$Path] already exists, but -Force was not specified. Exiting without replacing it." + } + + $xml = ConvertTo-NUnitXml -ScriptAnalyzerResult $ScriptAnalyzerResult + $xml.Save($Path) + + Write-Host "$($scriptName): Successfully created the NUnit XML file" +} +catch +{ + Write-Host "$($scriptName): Failed to create the NUnit XML file." + throw +} From 21e6139983962e63b3d985ccd3102612c083e2ad Mon Sep 17 00:00:00 2001 From: Howard Wolosky Date: Mon, 1 Jun 2020 07:40:13 -0700 Subject: [PATCH 15/60] Publish code coverage results for all builds, including forks (#212) --- build/pipelines/templates/run-unitTests.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build/pipelines/templates/run-unitTests.yaml b/build/pipelines/templates/run-unitTests.yaml index 53d64828..925d2a2f 100644 --- a/build/pipelines/templates/run-unitTests.yaml +++ b/build/pipelines/templates/run-unitTests.yaml @@ -44,4 +44,4 @@ steps: codeCoverageTool: 'JaCoCo' summaryFileLocation: '../Pester/coverage.xml' failIfCoverageEmpty: true - condition: and(succeededOrFailed(), eq(variables['System.PullRequest.IsFork'], false)) + condition: succeededOrFailed() From a6a27aa0aa1129d97bb6e5188707ff3ef6d53549 Mon Sep 17 00:00:00 2001 From: Giuseppe Campanelli Date: Mon, 1 Jun 2020 14:03:30 -0400 Subject: [PATCH 16/60] BREAKING CHANGE: Add confirmation prompts and examples for Remove- functions (#174) Updates all Remove-* functions to set `ConfirmImpact='High'`, wraps the execution in `$PSCmdlet.ShouldProcess()`, and adds examples which show how to call the functions with `-Confirm:$false` to avoid the confirmation prompt. ## Breaking Change This will be considered a breaking change for anyone using this module in an automated, scripted basis and calling any Remove-* methods. They will need to add `-Confirm:$false` to those API calls, similar to what was done for the unit tests. Fixes #171 --- GitHubAssignees.ps1 | 36 ++++++++----- GitHubComments.ps1 | 32 ++++++----- GitHubLabels.ps1 | 70 +++++++++++++++---------- GitHubMilestones.ps1 | 32 ++++++----- GitHubProjectCards.ps1 | 1 - GitHubProjectColumns.ps1 | 1 - GitHubProjects.ps1 | 6 ++- GitHubRepositories.ps1 | 31 ++++++----- Tests/GitHubAnalytics.tests.ps1 | 16 +++--- Tests/GitHubAssignees.tests.ps1 | 4 +- Tests/GitHubComments.tests.ps1 | 4 +- Tests/GitHubContents.tests.ps1 | 2 +- Tests/GitHubEvents.tests.ps1 | 6 +-- Tests/GitHubLabels.tests.ps1 | 36 ++++++------- Tests/GitHubMilestones.tests.ps1 | 6 +-- Tests/GitHubProjects.tests.ps1 | 2 +- Tests/GitHubRepositories.tests.ps1 | 10 ++-- Tests/GitHubRepositoryForks.tests.ps1 | 4 +- Tests/GitHubRepositoryTraffic.tests.ps1 | 8 +-- 19 files changed, 178 insertions(+), 129 deletions(-) diff --git a/GitHubAssignees.ps1 b/GitHubAssignees.ps1 index f88ce789..e9311c71 100644 --- a/GitHubAssignees.ps1 +++ b/GitHubAssignees.ps1 @@ -328,11 +328,16 @@ function Remove-GithubAssignee Remove-GithubAssignee -OwnerName Microsoft -RepositoryName PowerShellForGitHub -Assignee $assignees Lists the available assignees for issues from the Microsoft\PowerShellForGitHub project. + + .EXAMPLE + Remove-GithubAssignee -OwnerName Microsoft -RepositoryName PowerShellForGitHub -Assignee $assignees -Confirm:$false + + Lists the available assignees for issues from the Microsoft\PowerShellForGitHub project. Will not prompt for confirmation, as -Confirm:$false was specified. #> [CmdletBinding( SupportsShouldProcess, - DefaultParameterSetName='Elements')] - [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSShouldProcess", "", Justification="Methods called within here make use of PSShouldProcess, and the switch is passed on to them inherently.")] + DefaultParameterSetName='Elements', + ConfirmImpact="High")] [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSReviewUnusedParameter", "", Justification="One or more parameters (like NoStatus) are only referenced by helper methods which get access to it from the stack via Get-Variable -Scope 1.")] param( [Parameter(ParameterSetName='Elements')] @@ -374,17 +379,20 @@ function Remove-GithubAssignee 'assignees' = $Assignee } - $params = @{ - 'UriFragment' = "repos/$OwnerName/$RepositoryName/issues/$Issue/assignees" - 'Body' = (ConvertTo-Json -InputObject $hashBody) - 'Method' = 'Delete' - 'Description' = "Removing assignees from issue $Issue for $RepositoryName" - 'AccessToken' = $AccessToken - 'AcceptHeader' = 'application/vnd.github.symmetra-preview+json' - 'TelemetryEventName' = $MyInvocation.MyCommand.Name - 'TelemetryProperties' = $telemetryProperties - 'NoStatus' = (Resolve-ParameterWithDefaultConfigurationValue -Name NoStatus -ConfigValueName DefaultNoStatus) + if ($PSCmdlet.ShouldProcess($Assignee -join ', ', "Remove assignee(s)")) + { + $params = @{ + 'UriFragment' = "repos/$OwnerName/$RepositoryName/issues/$Issue/assignees" + 'Body' = (ConvertTo-Json -InputObject $hashBody) + 'Method' = 'Delete' + 'Description' = "Removing assignees from issue $Issue for $RepositoryName" + 'AccessToken' = $AccessToken + 'AcceptHeader' = 'application/vnd.github.symmetra-preview+json' + 'TelemetryEventName' = $MyInvocation.MyCommand.Name + 'TelemetryProperties' = $telemetryProperties + 'NoStatus' = (Resolve-ParameterWithDefaultConfigurationValue -Name NoStatus -ConfigValueName DefaultNoStatus) + } + + return Invoke-GHRestMethod @params } - - return Invoke-GHRestMethod @params } diff --git a/GitHubComments.ps1 b/GitHubComments.ps1 index 82aef916..ea66366d 100644 --- a/GitHubComments.ps1 +++ b/GitHubComments.ps1 @@ -452,12 +452,17 @@ function Remove-GitHubComment Remove-GitHubComment -OwnerName Microsoft -RepositoryName PowerShellForGitHub -CommentID 1 Deletes a Github comment from the Microsoft\PowerShellForGitHub project. + + .EXAMPLE + Remove-GitHubComment -OwnerName Microsoft -RepositoryName PowerShellForGitHub -CommentID 1 -Confirm:$false + + Deletes a Github comment from the Microsoft\PowerShellForGitHub project without prompting confirmation. #> [CmdletBinding( SupportsShouldProcess, - DefaultParameterSetName='Elements')] + DefaultParameterSetName='Elements', + ConfirmImpact="High")] [Alias('Delete-GitHubComment')] - [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSShouldProcess", "", Justification="Methods called within here make use of PSShouldProcess, and the switch is passed on to them inherently.")] [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSReviewUnusedParameter", "", Justification="One or more parameters (like NoStatus) are only referenced by helper methods which get access to it from the stack via Get-Variable -Scope 1.")] param( [Parameter(ParameterSetName='Elements')] @@ -491,16 +496,19 @@ function Remove-GitHubComment 'CommentID' = (Get-PiiSafeString -PlainText $CommentID) } - $params = @{ - 'UriFragment' = "repos/$OwnerName/$RepositoryName/issues/comments/$CommentID" - 'Method' = 'Delete' - 'Description' = "Removing comment $CommentID for $RepositoryName" - 'AccessToken' = $AccessToken - 'TelemetryEventName' = $MyInvocation.MyCommand.Name - 'TelemetryProperties' = $telemetryProperties - 'NoStatus' = (Resolve-ParameterWithDefaultConfigurationValue -Name NoStatus -ConfigValueName DefaultNoStatus) - } + if ($PSCmdlet.ShouldProcess($CommentID, "Remove comment")) + { + $params = @{ + 'UriFragment' = "repos/$OwnerName/$RepositoryName/issues/comments/$CommentID" + 'Method' = 'Delete' + 'Description' = "Removing comment $CommentID for $RepositoryName" + 'AccessToken' = $AccessToken + 'TelemetryEventName' = $MyInvocation.MyCommand.Name + 'TelemetryProperties' = $telemetryProperties + 'NoStatus' = (Resolve-ParameterWithDefaultConfigurationValue -Name NoStatus -ConfigValueName DefaultNoStatus) + } - return Invoke-GHRestMethod @params + return Invoke-GHRestMethod @params + } } diff --git a/GitHubLabels.ps1 b/GitHubLabels.ps1 index 172361e7..04eb3ed3 100644 --- a/GitHubLabels.ps1 +++ b/GitHubLabels.ps1 @@ -316,11 +316,16 @@ function Remove-GitHubLabel Remove-GitHubLabel -OwnerName Microsoft -RepositoryName PowerShellForGitHub -Name TestLabel Removes the label called "TestLabel" from the PowerShellForGitHub project. + + .EXAMPLE + Remove-GitHubLabel -OwnerName Microsoft -RepositoryName PowerShellForGitHub -Name TestLabel -Confirm:$false + + Removes the label called "TestLabel" from the PowerShellForGitHub project. Will not prompt for confirmation, as -Confirm:$false was specified. #> [CmdletBinding( SupportsShouldProcess, - DefaultParameterSetName='Elements')] - [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSShouldProcess", "", Justification="Methods called within here make use of PSShouldProcess, and the switch is passed on to them inherently.")] + DefaultParameterSetName='Elements', + ConfirmImpact="High")] [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSReviewUnusedParameter", "", Justification="One or more parameters (like NoStatus) are only referenced by helper methods which get access to it from the stack via Get-Variable -Scope 1.")] [Alias('Delete-GitHubLabel')] param( @@ -356,18 +361,21 @@ function Remove-GitHubLabel 'RepositoryName' = (Get-PiiSafeString -PlainText $RepositoryName) } - $params = @{ - 'UriFragment' = "repos/$OwnerName/$RepositoryName/labels/$Name" - 'Method' = 'Delete' - 'Description' = "Deleting label $Name from $RepositoryName" - 'AcceptHeader' = 'application/vnd.github.symmetra-preview+json' - 'AccessToken' = $AccessToken - 'TelemetryEventName' = $MyInvocation.MyCommand.Name - 'TelemetryProperties' = $telemetryProperties - 'NoStatus' = (Resolve-ParameterWithDefaultConfigurationValue -Name NoStatus -ConfigValueName DefaultNoStatus) - } + if ($PSCmdlet.ShouldProcess($Name, "Remove label")) + { + $params = @{ + 'UriFragment' = "repos/$OwnerName/$RepositoryName/labels/$Name" + 'Method' = 'Delete' + 'Description' = "Deleting label $Name from $RepositoryName" + 'AcceptHeader' = 'application/vnd.github.symmetra-preview+json' + 'AccessToken' = $AccessToken + 'TelemetryEventName' = $MyInvocation.MyCommand.Name + 'TelemetryProperties' = $telemetryProperties + 'NoStatus' = (Resolve-ParameterWithDefaultConfigurationValue -Name NoStatus -ConfigValueName DefaultNoStatus) + } - return Invoke-GHRestMethod @params + return Invoke-GHRestMethod @params + } } function Update-GitHubLabel @@ -614,7 +622,7 @@ function Set-GitHubLabel if ($labelName -notin $labelNames) { # Remove label if it exists but is not in desired label list - $null = Remove-GitHubLabel -Name $labelName @commonParams + $null = Remove-GitHubLabel -Name $labelName @commonParams -Confirm:$false } } } @@ -865,11 +873,16 @@ function Remove-GitHubIssueLabel Remove-GitHubIssueLabel -OwnerName Microsoft -RepositoryName PowerShellForGitHub -Name TestLabel -Issue 1 Removes the label called "TestLabel" from issue 1 in the PowerShellForGitHub project. + + .EXAMPLE + Remove-GitHubIssueLabel -OwnerName Microsoft -RepositoryName PowerShellForGitHub -Name TestLabel -Issue 1 -Confirm:$false + + Removes the label called "TestLabel" from issue 1 in the PowerShellForGitHub project. Will not prompt for confirmation, as -Confirm:$false was specified. #> [CmdletBinding( SupportsShouldProcess, - DefaultParameterSetName='Elements')] - [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSShouldProcess", "", Justification="Methods called within here make use of PSShouldProcess, and the switch is passed on to them inherently.")] + DefaultParameterSetName='Elements', + ConfirmImpact="High")] [Alias('Delete-GitHubLabel')] param( [Parameter(Mandatory, ParameterSetName='Elements')] @@ -915,18 +928,21 @@ function Remove-GitHubIssueLabel $description = "Deleting all labels from issue $Issue in $RepositoryName" } - $params = @{ - 'UriFragment' = "/repos/$OwnerName/$RepositoryName/issues/$Issue/labels/$Name" - 'Method' = 'Delete' - 'Description' = $description - 'AcceptHeader' = 'application/vnd.github.symmetra-preview+json' - 'AccessToken' = $AccessToken - 'TelemetryEventName' = $MyInvocation.MyCommand.Name - 'TelemetryProperties' = $telemetryProperties - 'NoStatus' = (Resolve-ParameterWithDefaultConfigurationValue -Name NoStatus -ConfigValueName DefaultNoStatus) - } + if ($PSCmdlet.ShouldProcess($Name, "Remove label")) + { + $params = @{ + 'UriFragment' = "/repos/$OwnerName/$RepositoryName/issues/$Issue/labels/$Name" + 'Method' = 'Delete' + 'Description' = $description + 'AcceptHeader' = 'application/vnd.github.symmetra-preview+json' + 'AccessToken' = $AccessToken + 'TelemetryEventName' = $MyInvocation.MyCommand.Name + 'TelemetryProperties' = $telemetryProperties + 'NoStatus' = (Resolve-ParameterWithDefaultConfigurationValue -Name NoStatus -ConfigValueName DefaultNoStatus) + } - return Invoke-GHRestMethod @params + return Invoke-GHRestMethod @params + } } # A set of labels that a project might want to initially populate their repository with diff --git a/GitHubMilestones.ps1 b/GitHubMilestones.ps1 index c67ecef7..11bf627b 100644 --- a/GitHubMilestones.ps1 +++ b/GitHubMilestones.ps1 @@ -500,12 +500,17 @@ function Remove-GitHubMilestone Remove-GitHubMilestone -OwnerName Microsoft -RepositoryName PowerShellForGitHub -Milestone 1 Deletes a Github milestone from the Microsoft\PowerShellForGitHub project. + + .EXAMPLE + Remove-GitHubMilestone -OwnerName Microsoft -RepositoryName PowerShellForGitHub -Milestone 1 -Confirm:$false + + Deletes a Github milestone from the Microsoft\PowerShellForGitHub project. Will not prompt for confirmation, as -Confirm:$false was specified. #> [CmdletBinding( SupportsShouldProcess, - DefaultParameterSetName='Elements')] + DefaultParameterSetName='Elements', + ConfirmImpact="High")] [Alias('Delete-GitHubMilestone')] - [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSShouldProcess", "", Justification="Methods called within here make use of PSShouldProcess, and the switch is passed on to them inherently.")] [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSReviewUnusedParameter", "", Justification="One or more parameters (like NoStatus) are only referenced by helper methods which get access to it from the stack via Get-Variable -Scope 1.")] param( [Parameter(Mandatory, ParameterSetName='Elements')] @@ -538,15 +543,18 @@ function Remove-GitHubMilestone 'Milestone' = (Get-PiiSafeString -PlainText $Milestone) } - $params = @{ - 'UriFragment' = "repos/$OwnerName/$RepositoryName/milestones/$Milestone" - 'Method' = 'Delete' - 'Description' = "Removing milestone $Milestone for $RepositoryName" - 'AccessToken' = $AccessToken - 'TelemetryEventName' = $MyInvocation.MyCommand.Name - 'TelemetryProperties' = $telemetryProperties - 'NoStatus' = (Resolve-ParameterWithDefaultConfigurationValue -Name NoStatus -ConfigValueName DefaultNoStatus) - } + if ($PSCmdlet.ShouldProcess($Milestone, "Remove milestone")) + { + $params = @{ + 'UriFragment' = "repos/$OwnerName/$RepositoryName/milestones/$Milestone" + 'Method' = 'Delete' + 'Description' = "Removing milestone $Milestone for $RepositoryName" + 'AccessToken' = $AccessToken + 'TelemetryEventName' = $MyInvocation.MyCommand.Name + 'TelemetryProperties' = $telemetryProperties + 'NoStatus' = (Resolve-ParameterWithDefaultConfigurationValue -Name NoStatus -ConfigValueName DefaultNoStatus) + } - return Invoke-GHRestMethod @params + return Invoke-GHRestMethod @params + } } diff --git a/GitHubProjectCards.ps1 b/GitHubProjectCards.ps1 index 86ea7988..5eeb0bff 100644 --- a/GitHubProjectCards.ps1 +++ b/GitHubProjectCards.ps1 @@ -370,7 +370,6 @@ function Remove-GitHubProjectCard SupportsShouldProcess, ConfirmImpact = 'High')] [Alias('Delete-GitHubProjectCard')] - [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSShouldProcess", "", Justification = "Methods called within here make use of PSShouldProcess, and the switch is passed on to them inherently.")] [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSReviewUnusedParameter", "", Justification="One or more parameters (like NoStatus) are only referenced by helper methods which get access to it from the stack via Get-Variable -Scope 1.")] param( [Parameter(Mandatory)] diff --git a/GitHubProjectColumns.ps1 b/GitHubProjectColumns.ps1 index c1e84173..aa1b61a8 100644 --- a/GitHubProjectColumns.ps1 +++ b/GitHubProjectColumns.ps1 @@ -266,7 +266,6 @@ function Remove-GitHubProjectColumn SupportsShouldProcess, ConfirmImpact = 'High')] [Alias('Delete-GitHubProjectColumn')] - [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSShouldProcess", "", Justification = "Methods called within here make use of PSShouldProcess, and the switch is passed on to them inherently.")] [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSReviewUnusedParameter", "", Justification="One or more parameters (like NoStatus) are only referenced by helper methods which get access to it from the stack via Get-Variable -Scope 1.")] param( [Parameter(Mandatory)] diff --git a/GitHubProjects.ps1 b/GitHubProjects.ps1 index df4ecc50..95060198 100644 --- a/GitHubProjects.ps1 +++ b/GitHubProjects.ps1 @@ -465,6 +465,11 @@ function Remove-GitHubProject Remove project with ID '4387531'. + .EXAMPLE + Remove-GitHubProject -Project 4387531 -Confirm:$false + + Remove project with ID '4387531' without prompting for confirmation. + .EXAMPLE $project = Get-GitHubProject -OwnerName Microsoft -RepositoryName PowerShellForGitHub | Where-Object Name -eq 'TestProject' Remove-GitHubProject -Project $project.id @@ -476,7 +481,6 @@ function Remove-GitHubProject SupportsShouldProcess, ConfirmImpact = 'High')] [Alias('Delete-GitHubProject')] - [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSShouldProcess", "", Justification = "Methods called within here make use of PSShouldProcess, and the switch is passed on to them inherently.")] [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSReviewUnusedParameter", "", Justification="One or more parameters (like NoStatus) are only referenced by helper methods which get access to it from the stack via Get-Variable -Scope 1.")] param( [Parameter(Mandatory)] diff --git a/GitHubRepositories.ps1 b/GitHubRepositories.ps1 index bff7c159..29228af7 100644 --- a/GitHubRepositories.ps1 +++ b/GitHubRepositories.ps1 @@ -217,12 +217,17 @@ function Remove-GitHubRepository .EXAMPLE Remove-GitHubRepository -Uri https://github.com/You/YourRepoToDelete + + .EXAMPLE + Remove-GitHubRepository -Uri https://github.com/You/YourRepoToDelete -Confirm:$false + + Remove repository with the given URI, without prompting for confirmation. #> [CmdletBinding( SupportsShouldProcess, - DefaultParameterSetName='Elements')] + DefaultParameterSetName='Elements', + ConfirmImpact="High")] [Alias('Delete-GitHubRepository')] - [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSShouldProcess", "", Justification="Methods called within here make use of PSShouldProcess, and the switch is passed on to them inherently.")] param( [Parameter(ParameterSetName='Elements')] [string] $OwnerName, @@ -250,18 +255,20 @@ function Remove-GitHubRepository 'OwnerName' = (Get-PiiSafeString -PlainText $OwnerName) 'RepositoryName' = (Get-PiiSafeString -PlainText $RepositoryName) } + if ($PSCmdlet.ShouldProcess($RepositoryName, "Remove repository")) + { + $params = @{ + 'UriFragment' = "repos/$OwnerName/$RepositoryName" + 'Method' = 'Delete' + 'Description' = "Deleting $RepositoryName" + 'AccessToken' = $AccessToken + 'TelemetryEventName' = $MyInvocation.MyCommand.Name + 'TelemetryProperties' = $telemetryProperties + 'NoStatus' = (Resolve-ParameterWithDefaultConfigurationValue -BoundParameters $PSBoundParameters -Name NoStatus -ConfigValueName DefaultNoStatus) + } - $params = @{ - 'UriFragment' = "repos/$OwnerName/$RepositoryName" - 'Method' = 'Delete' - 'Description' = "Deleting $RepositoryName" - 'AccessToken' = $AccessToken - 'TelemetryEventName' = $MyInvocation.MyCommand.Name - 'TelemetryProperties' = $telemetryProperties - 'NoStatus' = (Resolve-ParameterWithDefaultConfigurationValue -BoundParameters $PSBoundParameters -Name NoStatus -ConfigValueName DefaultNoStatus) + return Invoke-GHRestMethod @params } - - return Invoke-GHRestMethod @params } function Get-GitHubRepository diff --git a/Tests/GitHubAnalytics.tests.ps1 b/Tests/GitHubAnalytics.tests.ps1 index 8c5c51c2..44015009 100644 --- a/Tests/GitHubAnalytics.tests.ps1 +++ b/Tests/GitHubAnalytics.tests.ps1 @@ -69,7 +69,7 @@ try } } - $null = Remove-GitHubRepository -Uri ($repo.svn_url) + $null = Remove-GitHubRepository -Uri ($repo.svn_url) -Confirm:$false } Describe 'Obtaining repository with biggest number of issues' { @@ -98,8 +98,8 @@ try } } - $null = Remove-GitHubRepository -Uri $repo1.svn_url - $null = Remove-GitHubRepository -Uri $repo2.svn_url + $null = Remove-GitHubRepository -Uri ($repo1.svn_url) -Confirm:$false + $null = Remove-GitHubRepository -Uri ($repo2.svn_url) -Confirm:$false } @@ -192,7 +192,7 @@ try $collaborators.Count | Should be 1 } - $null = Remove-GitHubRepository -OwnerName $script:ownerName -RepositoryName $repositoryName + $null = Remove-GitHubRepository -OwnerName $script:ownerName -RepositoryName $repositoryName -Confirm:$false } } @@ -207,7 +207,7 @@ try $contributors.Count | Should be 1 } - $null = Remove-GitHubRepository -OwnerName $script:ownerName -RepositoryName $repositoryName + $null = Remove-GitHubRepository -OwnerName $script:ownerName -RepositoryName $repositoryName -Confirm:$false } if ($script:accessTokenConfigured) @@ -251,7 +251,7 @@ try ($current.Count - $original.Count) | Should be 1 } - $null = Remove-GitHubRepository -Uri $repo.svn_url + $null = Remove-GitHubRepository -Uri $repo.svn_url -Confirm:$false } Describe 'Getting unique contributors from contributors array' { @@ -269,7 +269,7 @@ try $uniqueContributors.Count | Should be 1 } - $null = Remove-GitHubRepository -OwnerName $script:ownerName -RepositoryName $repositoryName + $null = Remove-GitHubRepository -OwnerName $script:ownerName -RepositoryName $repositoryName -Confirm:$false } Describe 'Getting repository name from url' { @@ -306,7 +306,7 @@ try $branches[0].name | Should be 'master' } - $null = Remove-GitHubRepository -OwnerName $script:ownerName -RepositoryName $repositoryName + $null = Remove-GitHubRepository -OwnerName $script:ownerName -RepositoryName $repositoryName -Confirm:$false } } finally diff --git a/Tests/GitHubAssignees.tests.ps1 b/Tests/GitHubAssignees.tests.ps1 index fc1a89de..5e4738d5 100644 --- a/Tests/GitHubAssignees.tests.ps1 +++ b/Tests/GitHubAssignees.tests.ps1 @@ -52,7 +52,7 @@ try $issue.assignee.login | Should be $assigneeUserName } - Remove-GithubAssignee -Uri $repo.svn_url -Issue $issue.number -Assignee $assignees + Remove-GithubAssignee -Uri $repo.svn_url -Issue $issue.number -Assignee $assignees -Confirm:$false $issue = Get-GitHubIssue -Uri $repo.svn_url -Issue $issue.number It 'Should have removed the user from issue' { @@ -61,7 +61,7 @@ try } } - Remove-GitHubRepository -Uri $repo.svn_url + Remove-GitHubRepository -Uri $repo.svn_url -Confirm:$false } finally { diff --git a/Tests/GitHubComments.tests.ps1 b/Tests/GitHubComments.tests.ps1 index 9bdd5a62..66c9242b 100644 --- a/Tests/GitHubComments.tests.ps1 +++ b/Tests/GitHubComments.tests.ps1 @@ -76,7 +76,7 @@ try } foreach($comment in $existingComments) { - Remove-GitHubComment -Uri $repo.svn_url -CommentID $comment.id + Remove-GitHubComment -Uri $repo.svn_url -CommentID $comment.id -Confirm:$false } $existingComments = @(Get-GitHubComment -Uri $repo.svn_url) @@ -86,7 +86,7 @@ try } } - Remove-GitHubRepository -Uri $repo.svn_url + Remove-GitHubRepository -Uri $repo.svn_url -Confirm:$false } } finally diff --git a/Tests/GitHubContents.tests.ps1 b/Tests/GitHubContents.tests.ps1 index 474d4f1d..f0b15b29 100644 --- a/Tests/GitHubContents.tests.ps1 +++ b/Tests/GitHubContents.tests.ps1 @@ -162,7 +162,7 @@ try } } - Remove-GitHubRepository -Uri $repo.svn_url + Remove-GitHubRepository -Uri $repo.svn_url -Confirm:$false } } finally diff --git a/Tests/GitHubEvents.tests.ps1 b/Tests/GitHubEvents.tests.ps1 index 4364dcf0..4411d640 100644 --- a/Tests/GitHubEvents.tests.ps1 +++ b/Tests/GitHubEvents.tests.ps1 @@ -37,7 +37,7 @@ try } } - $null = Remove-GitHubRepository -OwnerName $ownerName -RepositoryName $repositoryName + $null = Remove-GitHubRepository -OwnerName $ownerName -RepositoryName $repositoryName -Confirm:$false } Describe 'Getting events from an issue' { @@ -63,7 +63,7 @@ try } } - $null = Remove-GitHubRepository -OwnerName $ownerName -RepositoryName $repositoryName + $null = Remove-GitHubRepository -OwnerName $ownerName -RepositoryName $repositoryName -Confirm:$false } Describe 'Getting an event directly' { @@ -82,7 +82,7 @@ try } } - $null = Remove-GitHubRepository -OwnerName $ownerName -RepositoryName $repositoryName + $null = Remove-GitHubRepository -OwnerName $ownerName -RepositoryName $repositoryName -Confirm:$false } } } diff --git a/Tests/GitHubLabels.tests.ps1 b/Tests/GitHubLabels.tests.ps1 index e040579b..88d7dfd9 100644 --- a/Tests/GitHubLabels.tests.ps1 +++ b/Tests/GitHubLabels.tests.ps1 @@ -94,7 +94,7 @@ try } } - $null = Remove-GitHubRepository -OwnerName $ownerName -RepositoryName $repositoryName + $null = Remove-GitHubRepository -OwnerName $ownerName -RepositoryName $repositoryName -Confirm:$false } Describe 'Creating new label' { @@ -110,10 +110,10 @@ try } AfterEach { - Remove-GitHubLabel -OwnerName $ownerName -RepositoryName $repositoryName -Name $labelName + Remove-GitHubLabel -OwnerName $ownerName -RepositoryName $repositoryName -Name $labelName -Confirm:$false } - $null = Remove-GitHubRepository -OwnerName $ownerName -RepositoryName $repositoryName + $null = Remove-GitHubRepository -OwnerName $ownerName -RepositoryName $repositoryName -Confirm:$false } Describe 'Removing label' { @@ -129,14 +129,14 @@ try $labels.Count | Should be ($defaultLabels.Count + 1) } - Remove-GitHubLabel -OwnerName $ownerName -RepositoryName $repositoryName -Name $labelName + Remove-GitHubLabel -OwnerName $ownerName -RepositoryName $repositoryName -Name $labelName -Confirm:$false $labels = @(Get-GitHubLabel -OwnerName $ownerName -RepositoryName $repositoryName) It 'Should return expected number of labels' { $labels.Count | Should be $defaultLabels.Count } - $null = Remove-GitHubRepository -OwnerName $ownerName -RepositoryName $repositoryName + $null = Remove-GitHubRepository -OwnerName $ownerName -RepositoryName $repositoryName -Confirm:$false } Describe 'Updating label' { @@ -151,7 +151,7 @@ try $label = Get-GitHubLabel -OwnerName $ownerName -RepositoryName $repositoryName -Name $labelName AfterEach { - Remove-GitHubLabel -OwnerName $ownerName -RepositoryName $repositoryName -Name $labelName + Remove-GitHubLabel -OwnerName $ownerName -RepositoryName $repositoryName -Name $labelName -Confirm:$false } It 'Label should have different color' { @@ -166,7 +166,7 @@ try $label = Get-GitHubLabel -OwnerName $ownerName -RepositoryName $repositoryName -Name $newLabelName AfterEach { - Remove-GitHubLabel -OwnerName $ownerName -RepositoryName $repositoryName -Name $newLabelName + Remove-GitHubLabel -OwnerName $ownerName -RepositoryName $repositoryName -Name $newLabelName -Confirm:$false } It 'Label should have different color' { @@ -175,7 +175,7 @@ try } } - $null = Remove-GitHubRepository -OwnerName $ownerName -RepositoryName $repositoryName + $null = Remove-GitHubRepository -OwnerName $ownerName -RepositoryName $repositoryName -Confirm:$false } Describe 'Applying set of labels on repository' { @@ -193,7 +193,7 @@ try Update-GitHubLabel -OwnerName $ownerName -RepositoryName $repositoryName -Name "bug" -NewName "bug" -Color BBBBBB # Remove one of approved labels" - Remove-GitHubLabel -OwnerName $ownerName -RepositoryName $repositoryName -Name "discussion" + Remove-GitHubLabel -OwnerName $ownerName -RepositoryName $repositoryName -Name "discussion" -Confirm:$false It 'Should return increased number of labels' { $($labels).Count | Should be ($defaultLabels.Count + 1) @@ -208,7 +208,7 @@ try $bugLabel.color | Should be "fc2929" } - $null = Remove-GitHubRepository -OwnerName $ownerName -RepositoryName $repositoryName + $null = Remove-GitHubRepository -OwnerName $ownerName -RepositoryName $repositoryName -Confirm:$false } Describe 'Adding labels to an issue'{ @@ -235,7 +235,7 @@ try } } - $null = Remove-GitHubRepository -OwnerName $ownerName -RepositoryName $repositoryName + $null = Remove-GitHubRepository -OwnerName $ownerName -RepositoryName $repositoryName -Confirm:$false } Describe 'Creating a new Issue with labels' { @@ -251,7 +251,7 @@ try $issue.labels.Count | Should be $issueLabels.Count } - $null = Remove-GitHubRepository -OwnerName $ownerName -RepositoryName $repositoryName + $null = Remove-GitHubRepository -OwnerName $ownerName -RepositoryName $repositoryName -Confirm:$false } Describe 'Removing labels on an issue'{ @@ -266,9 +266,9 @@ try Add-GitHubIssueLabel -OwnerName $ownerName -RepositoryName $repositoryName -Issue $issue.number -LabelName $labelsToAdd Context 'For removing individual issues'{ - Remove-GitHubIssueLabel -OwnerName $ownerName -RepositoryName $repositoryName -Name "discussion" -Issue $issue.number - Remove-GitHubIssueLabel -OwnerName $ownerName -RepositoryName $repositoryName -Name "question" -Issue $issue.number - Remove-GitHubIssueLabel -OwnerName $ownerName -RepositoryName $repositoryName -Name "bug" -Issue $issue.number + Remove-GitHubIssueLabel -OwnerName $ownerName -RepositoryName $repositoryName -Name "discussion" -Issue $issue.number -Confirm:$false + Remove-GitHubIssueLabel -OwnerName $ownerName -RepositoryName $repositoryName -Name "question" -Issue $issue.number -Confirm:$false + Remove-GitHubIssueLabel -OwnerName $ownerName -RepositoryName $repositoryName -Name "bug" -Issue $issue.number -Confirm:$false $labelIssues = @(Get-GitHubLabel -OwnerName $ownerName -RepositoryName $repositoryName -Issue $issue.number) It 'Should have removed three labels from the issue' { @@ -277,7 +277,7 @@ try } Context 'For removing all issues'{ - Remove-GitHubIssueLabel -OwnerName $ownerName -RepositoryName $repositoryName -Issue $issue.number + Remove-GitHubIssueLabel -OwnerName $ownerName -RepositoryName $repositoryName -Issue $issue.number -Confirm:$false $labelIssues = @(Get-GitHubLabel -OwnerName $ownerName -RepositoryName $repositoryName -Issue $issue.number) It 'Should have removed all labels from the issue' { @@ -285,7 +285,7 @@ try } } - $null = Remove-GitHubRepository -OwnerName $ownerName -RepositoryName $repositoryName + $null = Remove-GitHubRepository -OwnerName $ownerName -RepositoryName $repositoryName -Confirm:$false } Describe 'Replacing labels on an issue'{ @@ -319,7 +319,7 @@ try $updatedIssue.labels.Count | Should be $updatedIssueLabels.Count } - $null = Remove-GitHubRepository -OwnerName $ownerName -RepositoryName $repositoryName + $null = Remove-GitHubRepository -OwnerName $ownerName -RepositoryName $repositoryName -Confirm:$false } } } diff --git a/Tests/GitHubMilestones.tests.ps1 b/Tests/GitHubMilestones.tests.ps1 index 5255afd4..db9ab8cf 100644 --- a/Tests/GitHubMilestones.tests.ps1 +++ b/Tests/GitHubMilestones.tests.ps1 @@ -110,8 +110,8 @@ try $existingMilestones.Count | Should be 4 } - foreach ($milestone in $existingMilestones) { - Remove-GitHubMilestone -Uri $repo.svn_url -Milestone $milestone.number + foreach($milestone in $existingMilestones) { + Remove-GitHubMilestone -Uri $repo.svn_url -Milestone $milestone.number -Confirm:$false } $existingMilestones = @(Get-GitHubMilestone -Uri $repo.svn_url -State All) @@ -123,7 +123,7 @@ try } } - Remove-GitHubRepository -Uri $repo.svn_url + Remove-GitHubRepository -Uri $repo.svn_url -Confirm:$false } } finally diff --git a/Tests/GitHubProjects.tests.ps1 b/Tests/GitHubProjects.tests.ps1 index 931aa9c7..d523b103 100644 --- a/Tests/GitHubProjects.tests.ps1 +++ b/Tests/GitHubProjects.tests.ps1 @@ -383,7 +383,7 @@ try } } - Remove-GitHubRepository -Uri $repo.svn_url + Remove-GitHubRepository -Uri $repo.svn_url -Confirm:$false } finally { diff --git a/Tests/GitHubRepositories.tests.ps1 b/Tests/GitHubRepositories.tests.ps1 index 2a7361be..e2f4d99d 100644 --- a/Tests/GitHubRepositories.tests.ps1 +++ b/Tests/GitHubRepositories.tests.ps1 @@ -44,8 +44,8 @@ try } AfterAll -ScriptBlock { - Remove-GitHubRepository -Uri $publicRepo.svn_url - Remove-GitHubRepository -Uri $privateRepo.svn_url + Remove-GitHubRepository -Uri $publicRepo.svn_url -Confirm:$false + Remove-GitHubRepository -Uri $privateRepo.svn_url -Confirm:$false } } @@ -72,7 +72,7 @@ try } AfterAll -ScriptBlock { - Remove-GitHubRepository -Uri $repo.svn_url + Remove-GitHubRepository -Uri $repo.svn_url -Confirm:$false } } @@ -109,7 +109,7 @@ try } AfterAll -ScriptBlock { - Remove-GitHubRepository -Uri $repo.svn_url + Remove-GitHubRepository -Uri $repo.svn_url -Confirm:$false } } } @@ -134,7 +134,7 @@ try ## cleanup temp testing repository AfterEach -Scriptblock { ## variables from BeforeEach scriptblock are accessible here, but not variables from It scriptblocks, so need to make URI (instead of being able to use $renamedRepo variable from It scriptblock) - Remove-GitHubRepository -Uri "$($repo.svn_url)$suffixToAddToRepo" + Remove-GitHubRepository -Uri "$($repo.svn_url)$suffixToAddToRepo" -Confirm:$false } } } diff --git a/Tests/GitHubRepositoryForks.tests.ps1 b/Tests/GitHubRepositoryForks.tests.ps1 index be56f282..16e15669 100644 --- a/Tests/GitHubRepositoryForks.tests.ps1 +++ b/Tests/GitHubRepositoryForks.tests.ps1 @@ -27,7 +27,7 @@ try $newForks[0].full_name | Should be "$($script:ownerName)/PowerShellForGitHub" } - Remove-GitHubRepository -Uri $repo.svn_url + Remove-GitHubRepository -Uri $repo.svn_url -Confirm:$false } } @@ -46,7 +46,7 @@ try $newForks[0].full_name | Should be "$($script:organizationName)/PowerShellForGitHub" } - Remove-GitHubRepository -Uri $repo.svn_url + Remove-GitHubRepository -Uri $repo.svn_url -Confirm:$false } } } diff --git a/Tests/GitHubRepositoryTraffic.tests.ps1 b/Tests/GitHubRepositoryTraffic.tests.ps1 index 53adb0d5..ea542e19 100644 --- a/Tests/GitHubRepositoryTraffic.tests.ps1 +++ b/Tests/GitHubRepositoryTraffic.tests.ps1 @@ -22,7 +22,7 @@ try $referrerList.Count | Should be 0 } - Remove-GitHubRepository -Uri $repo.svn_url + Remove-GitHubRepository -Uri $repo.svn_url -Confirm:$false } } @@ -36,7 +36,7 @@ try $pathList.Count | Should be 0 } - Remove-GitHubRepository -Uri $repo.svn_url + Remove-GitHubRepository -Uri $repo.svn_url -Confirm:$false } } @@ -50,7 +50,7 @@ try $viewList.Count | Should be 0 } - Remove-GitHubRepository -Uri $repo.svn_url + Remove-GitHubRepository -Uri $repo.svn_url -Confirm:$false } } @@ -64,7 +64,7 @@ try $cloneList.Count | Should be 0 } - Remove-GitHubRepository -Uri $repo.svn_url + Remove-GitHubRepository -Uri $repo.svn_url -Confirm:$false } } } From 3d94c3f5ba450d2b8e534c67ba140d5a376c41e5 Mon Sep 17 00:00:00 2001 From: Howard Wolosky Date: Mon, 1 Jun 2020 12:19:18 -0700 Subject: [PATCH 17/60] Add issue and pull request templates (#214) These were heavily influenced by the wonderful templates in use at https://github.com/dsccommunity/ActiveDirectoryDsc/tree/master/.github/ --- .github/ISSUE_TEMPLATE/Documentation.md | 79 +++++++++++++++++++ .github/ISSUE_TEMPLATE/Feature_request.md | 80 +++++++++++++++++++ .github/ISSUE_TEMPLATE/Problem.md | 93 +++++++++++++++++++++++ .github/ISSUE_TEMPLATE/Support.md | 80 +++++++++++++++++++ .github/ISSUE_TEMPLATE/config.yml | 5 ++ .github/PULL_REQUEST_TEMPLATE.md | 37 +++++++++ 6 files changed, 374 insertions(+) create mode 100644 .github/ISSUE_TEMPLATE/Documentation.md create mode 100644 .github/ISSUE_TEMPLATE/Feature_request.md create mode 100644 .github/ISSUE_TEMPLATE/Problem.md create mode 100644 .github/ISSUE_TEMPLATE/Support.md create mode 100644 .github/ISSUE_TEMPLATE/config.yml create mode 100644 .github/PULL_REQUEST_TEMPLATE.md diff --git a/.github/ISSUE_TEMPLATE/Documentation.md b/.github/ISSUE_TEMPLATE/Documentation.md new file mode 100644 index 00000000..964f9b9c --- /dev/null +++ b/.github/ISSUE_TEMPLATE/Documentation.md @@ -0,0 +1,79 @@ +--- +name: Documentation issue +about: If you found a problem with documentation in the module or in this repo. +labels: 'triage needed', 'documentation' +assignees: '' +--- + + +#### Issue Details + + +#### Suggested solution to the issue + + +#### Requested Assignment + + + +#### Operating System + + + +#### PowerShell Version + + + +#### Module Version + diff --git a/.github/ISSUE_TEMPLATE/Feature_request.md b/.github/ISSUE_TEMPLATE/Feature_request.md new file mode 100644 index 00000000..e115fd5b --- /dev/null +++ b/.github/ISSUE_TEMPLATE/Feature_request.md @@ -0,0 +1,80 @@ +--- +name: Feature request or suggestion +about: If you have an idea for a feature that this module is missing. +labels: 'triage needed', 'enhancement' +assignees: '' +--- + + +#### Feature Idea Summary + + +#### Feature Idea Additional Details + + +#### Requested Assignment + + + +#### Operating System + + + +#### PowerShell Version + + + +#### Module Version + diff --git a/.github/ISSUE_TEMPLATE/Problem.md b/.github/ISSUE_TEMPLATE/Problem.md new file mode 100644 index 00000000..6664b742 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/Problem.md @@ -0,0 +1,93 @@ +--- +name: Bug or problem report +about: If you have a general question or request for help. +labels: 'triage needed', 'bug' +assignees: '' +--- + + +#### Issue Details + + +#### Steps to reproduce the issue +```powershell + # Include your repro steps here +``` + +#### Verbose logs showing the problem + + + +#### Suggested solution to the issue + + + +#### Requested Assignment + + + +#### Operating System + + + +#### PowerShell Version + + + +#### Module Version + diff --git a/.github/ISSUE_TEMPLATE/Support.md b/.github/ISSUE_TEMPLATE/Support.md new file mode 100644 index 00000000..e28fd048 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/Support.md @@ -0,0 +1,80 @@ +--- +name: Support request or question +about: If you have a general question or request for help. +labels: 'triage needed', 'support' +assignees: '' +--- + + +#### A description of your problem or question + + +#### Steps to reproduce the issue +```powershell + # Include your repro steps here +``` + + +#### Verbose logs showing the problem + + + +#### Suggested solution to the issue + + + +#### Operating System + + +#### PowerShell Version + + + +#### Module Version + diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml new file mode 100644 index 00000000..fd3ef4ed --- /dev/null +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -0,0 +1,5 @@ +blank_issues_enabled: false +contact_links: + - name: Security Issue Reporting + url: https://github.com/microsoft/PowerShellForGitHub/blob/master/SECURITY.md + about: Please report security vulnerabilities by following the directions here. diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 00000000..ab5cd63f --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,37 @@ + +#### Description + + +#### Issues Fixed + + + +#### Checklist + +- [ ] You actually ran the code that you just wrote, especially if you did just "one last quick change". +- [ ] Comment-based help added/updated, including examples. +- [ ] [Static analysis](https://github.com/microsoft/PowerShellForGitHub/blob/master/CONTRIBUTING.md#static-analysis) +is reporting back clean. +- [ ] New/changed code adheres to our [coding guidelines](https://github.com/microsoft/PowerShellForGitHub/blob/master/CONTRIBUTING.md#coding-guidelines). +- [ ] Changes to the manifest file follow the [manifest guidance](https://github.com/microsoft/PowerShellForGitHub/blob/master/CONTRIBUTING.md#module-manifest). +- [ ] Unit tests were added/updated and are all passing. See [testing guidelines](https://github.com/microsoft/PowerShellForGitHub/blob/master/CONTRIBUTING.md#testing). +- [ ] Relevant usage examples have been added/updated in [USAGE.md](https://github.com/microsoft/PowerShellForGitHub/blob/master/USAGE.md). +- [ ] If desired, ensure your name is added to our [Contributors list](https://github.com/microsoft/PowerShellForGitHub/blob/master/CONTRIBUTING.md#contributors) From 4b7fdd62f6e784b525569e79c595a1edffd7edca Mon Sep 17 00:00:00 2001 From: Howard Wolosky Date: Mon, 1 Jun 2020 12:34:50 -0700 Subject: [PATCH 18/60] Try to fix issue templates Existing templates are not appearing. Seeing if adding this one fixes that. --- .github/ISSUE_TEMPLATE/custom.md | 10 ++++++++++ 1 file changed, 10 insertions(+) create mode 100644 .github/ISSUE_TEMPLATE/custom.md diff --git a/.github/ISSUE_TEMPLATE/custom.md b/.github/ISSUE_TEMPLATE/custom.md new file mode 100644 index 00000000..48d5f81f --- /dev/null +++ b/.github/ISSUE_TEMPLATE/custom.md @@ -0,0 +1,10 @@ +--- +name: Custom issue template +about: Describe this issue template's purpose here. +title: '' +labels: '' +assignees: '' + +--- + + From f6cde22f3f2232905fba14c42954a02a0bd3d601 Mon Sep 17 00:00:00 2001 From: Howard Wolosky Date: Mon, 1 Jun 2020 12:41:36 -0700 Subject: [PATCH 19/60] Trying to fix issue templates --- .github/ISSUE_TEMPLATE/Documentation.md | 2 +- .github/ISSUE_TEMPLATE/Feature_request.md | 2 +- .github/ISSUE_TEMPLATE/Problem.md | 2 +- .github/ISSUE_TEMPLATE/Support.md | 2 +- .github/ISSUE_TEMPLATE/custom.md | 10 ---------- 5 files changed, 4 insertions(+), 14 deletions(-) delete mode 100644 .github/ISSUE_TEMPLATE/custom.md diff --git a/.github/ISSUE_TEMPLATE/Documentation.md b/.github/ISSUE_TEMPLATE/Documentation.md index 964f9b9c..281a328f 100644 --- a/.github/ISSUE_TEMPLATE/Documentation.md +++ b/.github/ISSUE_TEMPLATE/Documentation.md @@ -1,7 +1,7 @@ --- name: Documentation issue about: If you found a problem with documentation in the module or in this repo. -labels: 'triage needed', 'documentation' +labels: triage needed, documentation assignees: '' --- diff --git a/.github/ISSUE_TEMPLATE/Feature_request.md b/.github/ISSUE_TEMPLATE/Feature_request.md index e115fd5b..9a3d13fc 100644 --- a/.github/ISSUE_TEMPLATE/Feature_request.md +++ b/.github/ISSUE_TEMPLATE/Feature_request.md @@ -1,7 +1,7 @@ --- name: Feature request or suggestion about: If you have an idea for a feature that this module is missing. -labels: 'triage needed', 'enhancement' +labels: triage needed, enhancement assignees: '' --- diff --git a/.github/ISSUE_TEMPLATE/Problem.md b/.github/ISSUE_TEMPLATE/Problem.md index 6664b742..6e56dc88 100644 --- a/.github/ISSUE_TEMPLATE/Problem.md +++ b/.github/ISSUE_TEMPLATE/Problem.md @@ -1,7 +1,7 @@ --- name: Bug or problem report about: If you have a general question or request for help. -labels: 'triage needed', 'bug' +labels: triage needed, bug assignees: '' --- diff --git a/.github/ISSUE_TEMPLATE/Support.md b/.github/ISSUE_TEMPLATE/Support.md index e28fd048..a9e2a0d2 100644 --- a/.github/ISSUE_TEMPLATE/Support.md +++ b/.github/ISSUE_TEMPLATE/Support.md @@ -1,7 +1,7 @@ --- name: Support request or question about: If you have a general question or request for help. -labels: 'triage needed', 'support' +labels: triage needed, support assignees: '' --- diff --git a/.github/ISSUE_TEMPLATE/custom.md b/.github/ISSUE_TEMPLATE/custom.md deleted file mode 100644 index 48d5f81f..00000000 --- a/.github/ISSUE_TEMPLATE/custom.md +++ /dev/null @@ -1,10 +0,0 @@ ---- -name: Custom issue template -about: Describe this issue template's purpose here. -title: '' -labels: '' -assignees: '' - ---- - - From ff6ab5cfe58510a998cb90157e582593f0b8a7e2 Mon Sep 17 00:00:00 2001 From: Howard Wolosky Date: Mon, 1 Jun 2020 12:42:32 -0700 Subject: [PATCH 20/60] GitHub automatically adds a link to Security Reporting policy. --- .github/ISSUE_TEMPLATE/config.yml | 4 ---- 1 file changed, 4 deletions(-) diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml index fd3ef4ed..3ba13e0c 100644 --- a/.github/ISSUE_TEMPLATE/config.yml +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -1,5 +1 @@ blank_issues_enabled: false -contact_links: - - name: Security Issue Reporting - url: https://github.com/microsoft/PowerShellForGitHub/blob/master/SECURITY.md - about: Please report security vulnerabilities by following the directions here. From ad15657551d137c5db063b64f15c2760f74ac5af Mon Sep 17 00:00:00 2001 From: Howard Wolosky Date: Mon, 1 Jun 2020 12:48:01 -0700 Subject: [PATCH 21/60] Fix handling of -WhatIf in core functions (#213) WhatIf support has now been simiplified in all API's that eventually call into `Invoke-GHRestMethod`. There's now a single check at the top of that function which checks if it should continue or not. If it shouldn't, it early returns in order to avoid any code that might access uninitialized content from outside of the ShouldProcess blocks later on. Resolves #197 --- GitHubCore.ps1 | 171 ++++++++++++++++++++++++------------------------- 1 file changed, 82 insertions(+), 89 deletions(-) diff --git a/GitHubCore.ps1 b/GitHubCore.ps1 index 7a5850c6..dbbe1f2f 100644 --- a/GitHubCore.ps1 +++ b/GitHubCore.ps1 @@ -193,130 +193,123 @@ function Invoke-GHRestMethod $headers.Add("Content-Type", "application/json; charset=UTF-8") } + if (-not $PSCmdlet.ShouldProcess($url, "Invoke-WebRequest")) + { + return + } + try { Write-Log -Message $Description -Level Verbose Write-Log -Message "Accessing [$Method] $url [Timeout = $(Get-GitHubConfiguration -Name WebRequestTimeoutSec))]" -Level Verbose + $result = $null $NoStatus = Resolve-ParameterWithDefaultConfigurationValue -Name NoStatus -ConfigValueName DefaultNoStatus if ($NoStatus) { - if ($PSCmdlet.ShouldProcess($url, "Invoke-WebRequest")) + $params = @{} + $params.Add("Uri", $url) + $params.Add("Method", $Method) + $params.Add("Headers", $headers) + $params.Add("UseDefaultCredentials", $true) + $params.Add("UseBasicParsing", $true) + $params.Add("TimeoutSec", (Get-GitHubConfiguration -Name WebRequestTimeoutSec)) + + if ($Method -in $ValidBodyContainingRequestMethods -and (-not [String]::IsNullOrEmpty($Body))) + { + $bodyAsBytes = [System.Text.Encoding]::UTF8.GetBytes($Body) + $params.Add("Body", $bodyAsBytes) + Write-Log -Message "Request includes a body." -Level Verbose + if (Get-GitHubConfiguration -Name LogRequestBody) + { + Write-Log -Message $Body -Level Verbose + } + } + + [Net.ServicePointManager]::SecurityProtocol=[Net.SecurityProtocolType]::Tls12 + $result = Invoke-WebRequest @params + if ($Method -eq 'Delete') { + Write-Log -Message "Successfully removed." -Level Verbose + } + } + else + { + $jobName = "Invoke-GHRestMethod-" + (Get-Date).ToFileTime().ToString() + + [scriptblock]$scriptBlock = { + param($Url, $Method, $Headers, $Body, $ValidBodyContainingRequestMethods, $TimeoutSec, $LogRequestBody, $ScriptRootPath) + + # We need to "dot invoke" Helpers.ps1 and GitHubConfiguration.ps1 within + # the context of this script block since we're running in a different + # PowerShell process and need access to Get-HttpWebResponseContent and + # config values referenced within Write-Log. + . (Join-Path -Path $ScriptRootPath -ChildPath 'Helpers.ps1') + . (Join-Path -Path $ScriptRootPath -ChildPath 'GitHubConfiguration.ps1') + $params = @{} - $params.Add("Uri", $url) + $params.Add("Uri", $Url) $params.Add("Method", $Method) - $params.Add("Headers", $headers) + $params.Add("Headers", $Headers) $params.Add("UseDefaultCredentials", $true) $params.Add("UseBasicParsing", $true) - $params.Add("TimeoutSec", (Get-GitHubConfiguration -Name WebRequestTimeoutSec)) + $params.Add("TimeoutSec", $TimeoutSec) if ($Method -in $ValidBodyContainingRequestMethods -and (-not [String]::IsNullOrEmpty($Body))) { $bodyAsBytes = [System.Text.Encoding]::UTF8.GetBytes($Body) $params.Add("Body", $bodyAsBytes) Write-Log -Message "Request includes a body." -Level Verbose - if (Get-GitHubConfiguration -Name LogRequestBody) + if ($LogRequestBody) { Write-Log -Message $Body -Level Verbose } } - [Net.ServicePointManager]::SecurityProtocol=[Net.SecurityProtocolType]::Tls12 - $result = Invoke-WebRequest @params - if ($Method -eq 'Delete') + try { - Write-Log -Message "Successfully removed." -Level Verbose + [Net.ServicePointManager]::SecurityProtocol=[Net.SecurityProtocolType]::Tls12 + Invoke-WebRequest @params } - } - } - else - { - $jobName = "Invoke-GHRestMethod-" + (Get-Date).ToFileTime().ToString() - - if ($PSCmdlet.ShouldProcess($jobName, "Start-Job")) - { - [scriptblock]$scriptBlock = { - param($Url, $Method, $Headers, $Body, $ValidBodyContainingRequestMethods, $TimeoutSec, $LogRequestBody, $ScriptRootPath) - - # We need to "dot invoke" Helpers.ps1 and GitHubConfiguration.ps1 within - # the context of this script block since we're running in a different - # PowerShell process and need access to Get-HttpWebResponseContent and - # config values referenced within Write-Log. - . (Join-Path -Path $ScriptRootPath -ChildPath 'Helpers.ps1') - . (Join-Path -Path $ScriptRootPath -ChildPath 'GitHubConfiguration.ps1') - - $params = @{} - $params.Add("Uri", $Url) - $params.Add("Method", $Method) - $params.Add("Headers", $Headers) - $params.Add("UseDefaultCredentials", $true) - $params.Add("UseBasicParsing", $true) - $params.Add("TimeoutSec", $TimeoutSec) - - if ($Method -in $ValidBodyContainingRequestMethods -and (-not [String]::IsNullOrEmpty($Body))) - { - $bodyAsBytes = [System.Text.Encoding]::UTF8.GetBytes($Body) - $params.Add("Body", $bodyAsBytes) - Write-Log -Message "Request includes a body." -Level Verbose - if ($LogRequestBody) - { - Write-Log -Message $Body -Level Verbose - } - } - + catch [System.Net.WebException] + { + # We need to access certain headers in the exception handling, + # but the actual *values* of the headers of a WebException don't get serialized + # when the RemoteException wraps it. To work around that, we'll extract the + # information that we actually care about *now*, and then we'll throw our own exception + # that is just a JSON object with the data that we'll later extract for processing in + # the main catch. + $ex = @{} + $ex.Message = $_.Exception.Message + $ex.StatusCode = $_.Exception.Response.StatusCode + $ex.StatusDescription = $_.Exception.Response.StatusDescription + $ex.InnerMessage = $_.ErrorDetails.Message try { - [Net.ServicePointManager]::SecurityProtocol=[Net.SecurityProtocolType]::Tls12 - Invoke-WebRequest @params + $ex.RawContent = Get-HttpWebResponseContent -WebResponse $_.Exception.Response } - catch [System.Net.WebException] + catch { - # We need to access certain headers in the exception handling, - # but the actual *values* of the headers of a WebException don't get serialized - # when the RemoteException wraps it. To work around that, we'll extract the - # information that we actually care about *now*, and then we'll throw our own exception - # that is just a JSON object with the data that we'll later extract for processing in - # the main catch. - $ex = @{} - $ex.Message = $_.Exception.Message - $ex.StatusCode = $_.Exception.Response.StatusCode - $ex.StatusDescription = $_.Exception.Response.StatusDescription - $ex.InnerMessage = $_.ErrorDetails.Message - try - { - $ex.RawContent = Get-HttpWebResponseContent -WebResponse $_.Exception.Response - } - catch - { - Write-Log -Message "Unable to retrieve the raw HTTP Web Response:" -Exception $_ -Level Warning - } - - throw (ConvertTo-Json -InputObject $ex -Depth 20) + Write-Log -Message "Unable to retrieve the raw HTTP Web Response:" -Exception $_ -Level Warning } - } - $null = Start-Job -Name $jobName -ScriptBlock $scriptBlock -Arg @( - $url, - $Method, - $headers, - $Body, - $ValidBodyContainingRequestMethods, - (Get-GitHubConfiguration -Name WebRequestTimeoutSec), - (Get-GitHubConfiguration -Name LogRequestBody), - $PSScriptRoot) - - if ($PSCmdlet.ShouldProcess($jobName, "Wait-JobWithAnimation")) - { - Wait-JobWithAnimation -Name $jobName -Description $Description - } - - if ($PSCmdlet.ShouldProcess($jobName, "Receive-Job")) - { - $result = Receive-Job $jobName -AutoRemoveJob -Wait -ErrorAction SilentlyContinue -ErrorVariable remoteErrors + throw (ConvertTo-Json -InputObject $ex -Depth 20) } } + $null = Start-Job -Name $jobName -ScriptBlock $scriptBlock -Arg @( + $url, + $Method, + $headers, + $Body, + $ValidBodyContainingRequestMethods, + (Get-GitHubConfiguration -Name WebRequestTimeoutSec), + (Get-GitHubConfiguration -Name LogRequestBody), + $PSScriptRoot) + + Wait-JobWithAnimation -Name $jobName -Description $Description + $result = Receive-Job $jobName -AutoRemoveJob -Wait -ErrorAction SilentlyContinue -ErrorVariable remoteErrors + if ($remoteErrors.Count -gt 0) { throw $remoteErrors[0].Exception From ae8467f74a8bae1b97ca808a3b6eec727d15fc7e Mon Sep 17 00:00:00 2001 From: Howard Wolosky Date: Mon, 1 Jun 2020 13:56:03 -0700 Subject: [PATCH 22/60] Removing binary dependencies for telemetry (#186) This reverse engineers the REST API for Application Insights so that we no longer need to download / depend on the 3 .dll files that were necessary to use the Application Insights .NET SDK. As a result, this also removes the `AssemblyPath` configuration property since there are no longer any assemblies that this module needs. --- .gitignore | 3 - GitHubConfiguration.ps1 | 9 - GitHubCore.ps1 | 22 +- GitHubIssues.ps1 | 21 +- NugetTools.ps1 | 383 ----------------------- PowerShellForGitHub.psd1 | 1 - Telemetry.ps1 | 657 +++++++++++++++++++++------------------ 7 files changed, 369 insertions(+), 727 deletions(-) delete mode 100644 NugetTools.ps1 diff --git a/.gitignore b/.gitignore index 3f2d6014..8e0d3cf4 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1 @@ Tests/Config/Settings.ps1 -Microsoft.ApplicationInsights.dll -Microsoft.Diagnostics.Tracing.EventSource.dll -Microsoft.Threading.Tasks.dll diff --git a/GitHubConfiguration.ps1 b/GitHubConfiguration.ps1 index 0d6e9f21..dcd2b90a 100644 --- a/GitHubConfiguration.ps1 +++ b/GitHubConfiguration.ps1 @@ -76,11 +76,6 @@ function Set-GitHubConfiguration Change the Application Insights instance that telemetry will be reported to (if telemetry hasn't been disabled via DisableTelemetry). - .PARAMETER AssemblyPath - The location that any dependent assemblies that this module depends on can be located. - If the assemblies can't be found at this location, nor in a temporary cache or in - the module's directory, the assemblies will be downloaded and temporarily cached. - .PARAMETER DefaultNoStatus Control if the -NoStatus switch should be passed-in by default to all methods. @@ -182,8 +177,6 @@ function Set-GitHubConfiguration [string] $ApplicationInsightsKey, - [string] $AssemblyPath, - [switch] $DefaultNoStatus, [string] $DefaultOwnerName, @@ -279,7 +272,6 @@ function Get-GitHubConfiguration [ValidateSet( 'ApiHostName', 'ApplicationInsightsKey', - 'AssemblyPath', 'DefaultNoStatus', 'DefaultOwnerName', 'DefaultRepositoryName', @@ -617,7 +609,6 @@ function Import-GitHubConfiguration $config = [PSCustomObject]@{ 'apiHostName' = 'github.com' 'applicationInsightsKey' = '66d83c52-3070-489b-886b-09860e05e78a' - 'assemblyPath' = [String]::Empty 'disableLogging' = ([String]::IsNullOrEmpty($logPath)) 'disablePiiProtection' = $false 'disableSmarterObjects' = $false diff --git a/GitHubCore.ps1 b/GitHubCore.ps1 index dbbe1f2f..a324a5bb 100644 --- a/GitHubCore.ps1 +++ b/GitHubCore.ps1 @@ -98,8 +98,7 @@ function Invoke-GHRestMethod .NOTES This wraps Invoke-WebRequest as opposed to Invoke-RestMethod because we want access to the headers - that are returned in the response (specifically 'MS-ClientRequestId') for logging purposes, and - Invoke-RestMethod drops those headers. + that are returned in the response, and Invoke-RestMethod drops those headers. #> [CmdletBinding(SupportsShouldProcess)] param( @@ -144,10 +143,7 @@ function Invoke-GHRestMethod # Telemetry-related $stopwatch = New-Object -TypeName System.Diagnostics.Stopwatch - $localTelemetryProperties = @{ - 'UriFragment' = $UriFragment - 'WaitForCompletion' = ($WaitForCompletion -eq $true) - } + $localTelemetryProperties = @{} $TelemetryProperties.Keys | ForEach-Object { $localTelemetryProperties[$_] = $TelemetryProperties[$_] } $errorBucket = $TelemetryExceptionBucket if ([String]::IsNullOrEmpty($errorBucket)) @@ -198,13 +194,14 @@ function Invoke-GHRestMethod return } + $NoStatus = Resolve-ParameterWithDefaultConfigurationValue -Name NoStatus -ConfigValueName DefaultNoStatus + try { Write-Log -Message $Description -Level Verbose Write-Log -Message "Accessing [$Method] $url [Timeout = $(Get-GitHubConfiguration -Name WebRequestTimeoutSec))]" -Level Verbose $result = $null - $NoStatus = Resolve-ParameterWithDefaultConfigurationValue -Name NoStatus -ConfigValueName DefaultNoStatus if ($NoStatus) { $params = @{} @@ -293,7 +290,8 @@ function Invoke-GHRestMethod Write-Log -Message "Unable to retrieve the raw HTTP Web Response:" -Exception $_ -Level Warning } - throw (ConvertTo-Json -InputObject $ex -Depth 20) + $jsonConversionDepth = 20 # Seems like it should be more than sufficient + throw (ConvertTo-Json -InputObject $ex -Depth $jsonConversionDepth) } } @@ -326,7 +324,7 @@ function Invoke-GHRestMethod if (-not [String]::IsNullOrEmpty($TelemetryEventName)) { $telemetryMetrics = @{ 'Duration' = $stopwatch.Elapsed.TotalSeconds } - Set-TelemetryEvent -EventName $TelemetryEventName -Properties $localTelemetryProperties -Metrics $telemetryMetrics + Set-TelemetryEvent -EventName $TelemetryEventName -Properties $localTelemetryProperties -Metrics $telemetryMetrics -NoStatus:$NoStatus } $finalResult = $result.Content @@ -454,14 +452,14 @@ function Invoke-GHRestMethod { # Will be thrown if $ex.Message isn't JSON content Write-Log -Exception $_ -Level Error - Set-TelemetryException -Exception $ex -ErrorBucket $errorBucket -Properties $localTelemetryProperties + Set-TelemetryException -Exception $ex -ErrorBucket $errorBucket -Properties $localTelemetryProperties -NoStatus:$NoStatus throw } } else { Write-Log -Exception $_ -Level Error - Set-TelemetryException -Exception $_.Exception -ErrorBucket $errorBucket -Properties $localTelemetryProperties + Set-TelemetryException -Exception $_.Exception -ErrorBucket $errorBucket -Properties $localTelemetryProperties -NoStatus:$NoStatus throw } @@ -524,7 +522,7 @@ function Invoke-GHRestMethod $newLineOutput = ($output -join [Environment]::NewLine) Write-Log -Message $newLineOutput -Level Error - Set-TelemetryException -Exception $ex -ErrorBucket $errorBucket -Properties $localTelemetryProperties + Set-TelemetryException -Exception $ex -ErrorBucket $errorBucket -Properties $localTelemetryProperties -NoStatus:$NoStatus throw $newLineOutput } } diff --git a/GitHubIssues.ps1 b/GitHubIssues.ps1 index c12aefd6..d27bf93e 100644 --- a/GitHubIssues.ps1 +++ b/GitHubIssues.ps1 @@ -322,16 +322,21 @@ function Get-GitHubIssue 'NoStatus' = (Resolve-ParameterWithDefaultConfigurationValue -Name NoStatus -ConfigValueName DefaultNoStatus) } - $result = Invoke-GHRestMethodMultipleResult @params - - if ($IgnorePullRequests) + try { - return ($result | Where-Object { $null -eq (Get-Member -InputObject $_ -Name pull_request) }) - } - else - { - return $result + $result = Invoke-GHRestMethodMultipleResult @params + + if ($IgnorePullRequests) + { + return ($result | Where-Object { $null -eq (Get-Member -InputObject $_ -Name pull_request) }) + } + else + { + return $result + } + } + finally {} } function Get-GitHubIssueTimeline diff --git a/NugetTools.ps1 b/NugetTools.ps1 deleted file mode 100644 index 3c6755f7..00000000 --- a/NugetTools.ps1 +++ /dev/null @@ -1,383 +0,0 @@ -# Copyright (c) Microsoft Corporation. All rights reserved. -# Licensed under the MIT License. - -# The cached location of nuget.exe -$script:nugetExePath = [String]::Empty - -# The directory where we'll store the assemblies that we dynamically download during this session. -$script:tempAssemblyCacheDir = [String]::Empty - -function Get-NugetExe -{ -<# - .SYNOPSIS - Downloads nuget.exe from http://nuget.org to a new local temporary directory - and returns the path to the local copy. - - .DESCRIPTION - Downloads nuget.exe from http://nuget.org to a new local temporary directory - and returns the path to the local copy. - - The Git repo for this module can be found here: http://aka.ms/PowerShellForGitHub - - .EXAMPLE - Get-NugetExe - Creates a new directory with a GUID under $env:TEMP and then downloads - http://nuget.org/nuget.exe to that location. - - .OUTPUTS - System.String - The path to the newly downloaded nuget.exe -#> - [CmdletBinding(SupportsShouldProcess)] - [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSShouldProcess", "", Justification="Methods called within here make use of PSShouldProcess, and the switch is passed on to them inherently.")] - param() - - if ([String]::IsNullOrEmpty($script:nugetExePath)) - { - $sourceNugetExe = "https://dist.nuget.org/win-x86-commandline/latest/nuget.exe" - $script:nugetExePath = Join-Path $(New-TemporaryDirectory) "nuget.exe" - - Write-Log -Message "Downloading $sourceNugetExe to $script:nugetExePath" -Level Verbose - Invoke-WebRequest $sourceNugetExe -OutFile $script:nugetExePath - } - - return $script:nugetExePath -} - -function Get-NugetPackage -{ -<# - .SYNOPSIS - Downloads a nuget package to the specified directory. - - .DESCRIPTION - Downloads a nuget package to the specified directory (or the current - directory if no TargetPath was specified). - - The Git repo for this module can be found here: http://aka.ms/PowerShellForGitHub - - .PARAMETER PackageName - The name of the nuget package to download - - .PARAMETER TargetPath - The nuget package will be downloaded to this location. - - .PARAMETER Version - If provided, this indicates the version of the package to download. - If not specified, downloads the latest version. - - .PARAMETER NoStatus - If this switch is specified, long-running commands will run on the main thread - with no commandline status update. When not specified, those commands run in - the background, enabling the command prompt to provide status information. - - .EXAMPLE - Get-NugetPackage "Microsoft.AzureStorage" -Version "6.0.0.0" -TargetPath "c:\foo" - Downloads v6.0.0.0 of the Microsoft.AzureStorage nuget package to the c:\foo directory. - - .EXAMPLE - Get-NugetPackage "Microsoft.AzureStorage" "c:\foo" - Downloads the most recent version of the Microsoft.AzureStorage - nuget package to the c:\foo directory. -#> - [CmdletBinding(SupportsShouldProcess)] - param( - [Parameter(Mandatory)] - [string] $PackageName, - - [Parameter(Mandatory)] - [ValidateScript({if (Test-Path -Path $_ -PathType Container) { $true } else { throw "$_ does not exist." }})] - [string] $TargetPath, - - [string] $Version, - - [switch] $NoStatus - ) - - Write-Log -Message "Downloading nuget package [$PackageName] to [$TargetPath]" -Level Verbose - - $nugetPath = Get-NugetExe - - if ($NoStatus) - { - if ($PSCmdlet.ShouldProcess($PackageName, $nugetPath)) - { - if (-not [System.String]::IsNullOrEmpty($Version)) - { - & $nugetPath install $PackageName -o $TargetPath -version $Version -source nuget.org -NonInteractive | Out-Null - } - else - { - & $nugetPath install $PackageName -o $TargetPath -source nuget.org -NonInteractive | Out-Null - } - } - } - else - { - $jobName = "Get-NugetPackage-" + (Get-Date).ToFileTime().ToString() - - if ($PSCmdlet.ShouldProcess($jobName, "Start-Job")) - { - [scriptblock]$scriptBlock = { - param($NugetPath, $PackageName, $TargetPath, $Version) - - if (-not [System.String]::IsNullOrEmpty($Version)) - { - & $NugetPath install $PackageName -o $TargetPath -version $Version -source nuget.org - } - else - { - & $NugetPath install $PackageName -o $TargetPath -source nuget.org - } - } - - Start-Job -Name $jobName -ScriptBlock $scriptBlock -Arg @($nugetPath, $PackageName, $TargetPath, $Version) | Out-Null - - if ($PSCmdlet.ShouldProcess($jobName, "Wait-JobWithAnimation")) - { - Wait-JobWithAnimation -Name $jobName -Description "Retrieving nuget package: $PackageName" - } - - if ($PSCmdlet.ShouldProcess($jobName, "Receive-Job")) - { - Receive-Job $jobName -AutoRemoveJob -Wait -ErrorAction SilentlyContinue -ErrorVariable remoteErrors | Out-Null - } - } - - if ($remoteErrors.Count -gt 0) - { - throw $remoteErrors[0].Exception - } - } -} - -function Test-AssemblyIsDesiredVersion -{ - <# - .SYNOPSIS - Checks if the specified file is the expected version. - - .DESCRIPTION - Checks if the specified file is the expected version. - - Does a best effort match. If you only specify a desired version of "6", - any version of the file that has a "major" version of 6 will be considered - a match, where we use the terminology of a version being: - Major.Minor.Build.PrivateInfo. - - The Git repo for this module can be found here: http://aka.ms/PowerShellForGitHub - - .PARAMETER AssemblyPath - The full path to the assembly file being tested. - - .PARAMETER DesiredVersion - The desired version of the assembly. Specify the version as specifically as - necessary. - - .EXAMPLE - Test-AssemblyIsDesiredVersion "c:\Microsoft.WindowsAzure.Storage.dll" "6" - - Returns back $true if "c:\Microsoft.WindowsAzure.Storage.dll" has a major version - of 6, regardless of its Minor, Build or PrivateInfo numbers. - - .OUTPUTS - Boolean - $true if the assembly at the specified path exists and meets the specified - version criteria, $false otherwise. -#> - param( - [Parameter(Mandatory)] - [ValidateScript( { if (Test-Path -PathType Leaf -Path $_) { $true } else { throw "'$_' cannot be found." } })] - [string] $AssemblyPath, - - [Parameter(Mandatory)] - [ValidateScript( { if ($_ -match '^\d+(\.\d+){0,3}$') { $true } else { throw "'$_' not a valid version format." } })] - [string] $DesiredVersion - ) - - $splitTargetVer = $DesiredVersion.Split('.') - - $file = Get-Item -Path $AssemblyPath -ErrorVariable ev - if (($null -ne $ev) -and ($ev.Count -gt 0)) - { - Write-Log "Problem accessing [$Path]: $($ev[0].Exception.Message)" -Level Warning - return $false - } - - $versionInfo = $file.VersionInfo - $splitSourceVer = @( - $versionInfo.ProductMajorPart, - $versionInfo.ProductMinorPart, - $versionInfo.ProductBuildPart, - $versionInfo.ProductPrivatePart - ) - - # The cmdlet contract states that we only care about matching - # as much of the version number as the user has supplied. - for ($i = 0; $i -lt $splitTargetVer.Count; $i++) - { - if ($splitSourceVer[$i] -ne $splitTargetVer[$i]) - { - return $false - } - } - - return $true -} - -function Get-NugetPackageDllPath -{ -<# - .SYNOPSIS - Makes sure that the specified assembly from a nuget package is available - on the machine, and returns the path to it. - - .DESCRIPTION - Makes sure that the specified assembly from a nuget package is available - on the machine, and returns the path to it. - - This will first look for the assembly in the module's script directory. - - Next it will look for the assembly in the location defined by the configuration - property AssemblyPath. - - If not found there, it will look in a temp folder established during this - PowerShell session. - - If still not found, it will download the nuget package - for it to a temp folder accessible during this PowerShell session. - - The Git repo for this module can be found here: http://aka.ms/PowerShellForGitHub - - .PARAMETER NugetPackageName - The name of the nuget package to download - - .PARAMETER NugetPackageVersion - Indicates the version of the package to download. - - .PARAMETER AssemblyPackageTailDirectory - The sub-path within the nuget package download location where the assembly should be found. - - .PARAMETER AssemblyName - The name of the actual assembly that the user is looking for. - - .PARAMETER NoStatus - If this switch is specified, long-running commands will run on the main thread - with no commandline status update. When not specified, those commands run in - the background, enabling the command prompt to provide status information. - - .EXAMPLE - Get-NugetPackageDllPath "WindowsAzure.Storage" "6.0.0" "WindowsAzure.Storage.6.0.0\lib\net40\" "Microsoft.WindowsAzure.Storage.dll" - - Returns back the path to "Microsoft.WindowsAzure.Storage.dll", which is part of the - "WindowsAzure.Storage" nuget package. If the package has to be downloaded via nuget, - the command prompt will show a time duration status counter while the package is being - downloaded. - - .EXAMPLE - Get-NugetPackageDllPath "WindowsAzure.Storage" "6.0.0" "WindowsAzure.Storage.6.0.0\lib\net40\" "Microsoft.WindowsAzure.Storage.dll" -NoStatus - - Returns back the path to "Microsoft.WindowsAzure.Storage.dll", which is part of the - "WindowsAzure.Storage" nuget package. If the package has to be downloaded via nuget, - the command prompt will appear to hang during this time. - - .OUTPUTS - System.String - The full path to $AssemblyName. -#> - [CmdletBinding(SupportsShouldProcess)] - [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSShouldProcess", "", Justification="Methods called within here make use of PSShouldProcess, and the switch is passed on to them inherently.")] - param( - [Parameter(Mandatory)] - [string] $NugetPackageName, - - [Parameter(Mandatory)] - [string] $NugetPackageVersion, - - [Parameter(Mandatory)] - [string] $AssemblyPackageTailDirectory, - - [Parameter(Mandatory)] - [string] $AssemblyName, - - [switch] $NoStatus - ) - - Write-Log -Message "Looking for $AssemblyName" -Level Verbose - - # First we'll check to see if the user has cached the assembly into the module's script directory - $moduleAssembly = Join-Path -Path $PSScriptRoot -ChildPath $AssemblyName - if (Test-Path -Path $moduleAssembly -PathType Leaf -ErrorAction Ignore) - { - if (Test-AssemblyIsDesiredVersion -AssemblyPath $moduleAssembly -DesiredVersion $NugetPackageVersion) - { - Write-Log -Message "Found $AssemblyName in module directory ($PSScriptRoot)." -Level Verbose - return $moduleAssembly - } - else - { - Write-Log -Message "Found $AssemblyName in module directory ($PSScriptRoot), but its version number [$moduleAssembly] didn't match required [$NugetPackageVersion]." -Level Verbose - } - } - - # Next, we'll check to see if the user has defined an alternate path to get the assembly from - $alternateAssemblyPath = Get-GitHubConfiguration -Name AssemblyPath - if (-not [System.String]::IsNullOrEmpty($alternateAssemblyPath)) - { - $assemblyPath = Join-Path -Path $alternateAssemblyPath -ChildPath $AssemblyName - if (Test-Path -Path $assemblyPath -PathType Leaf -ErrorAction Ignore) - { - if (Test-AssemblyIsDesiredVersion -AssemblyPath $assemblyPath -DesiredVersion $NugetPackageVersion) - { - Write-Log -Message "Found $AssemblyName in alternate directory ($alternateAssemblyPath)." -Level Verbose - return $assemblyPath - } - else - { - Write-Log -Message "Found $AssemblyName in alternate directory ($alternateAssemblyPath), but its version number [$moduleAssembly] didn't match required [$NugetPackageVersion]." -Level Verbose - } - } - } - - # Then we'll check to see if we've previously cached the assembly in a temp folder during this PowerShell session - if ([System.String]::IsNullOrEmpty($script:tempAssemblyCacheDir)) - { - $script:tempAssemblyCacheDir = New-TemporaryDirectory - } - else - { - $cachedAssemblyPath = Join-Path -Path $(Join-Path $script:tempAssemblyCacheDir $AssemblyPackageTailDirectory) $AssemblyName - if (Test-Path -Path $cachedAssemblyPath -PathType Leaf -ErrorAction Ignore) - { - if (Test-AssemblyIsDesiredVersion -AssemblyPath $cachedAssemblyPath -DesiredVersion $NugetPackageVersion) - { - Write-Log -Message "Found $AssemblyName in temp directory ($script:tempAssemblyCacheDir)." -Level Verbose - return $cachedAssemblyPath - } - else - { - Write-Log -Message "Found $AssemblyName in temp directory ($script:tempAssemblyCacheDir), but its version number [$moduleAssembly] didn't match required [$NugetPackageVersion]." -Level Verbose - } - } - } - - # Still not found, so we'll go ahead and download the package via nuget. - Write-Log -Message "$AssemblyName is needed and wasn't found. Acquiring it via nuget..." -Level Verbose - Get-NugetPackage -PackageName $NugetPackageName -Version $NugetPackageVersion -TargetPath $script:tempAssemblyCacheDir -NoStatus:$NoStatus - - $cachedAssemblyPath = Join-Path -Path $(Join-Path -Path $script:tempAssemblyCacheDir -ChildPath $AssemblyPackageTailDirectory) -ChildPath $AssemblyName - if (Test-Path -Path $cachedAssemblyPath -PathType Leaf -ErrorAction Ignore) - { - Write-Log -Message @( - "To avoid this download delay in the future, copy the following file:", - " [$cachedAssemblyPath]", - "either to:", - " [$PSScriptRoot]", - "or to:", - " a directory of your choosing, and store that directory as 'AssemblyPath' with 'Set-GitHubConfiguration'") - - return $cachedAssemblyPath - } - - $message = "Unable to acquire a reference to $AssemblyName." - Write-Log -Message $message -Level Error - throw $message -} diff --git a/PowerShellForGitHub.psd1 b/PowerShellForGitHub.psd1 index 33392da2..5ef27773 100644 --- a/PowerShellForGitHub.psd1 +++ b/PowerShellForGitHub.psd1 @@ -42,7 +42,6 @@ 'GitHubRepositoryTraffic.ps1', 'GitHubTeams.ps1', 'GitHubUsers.ps1', - 'NugetTools.ps1', 'Telemetry.ps1', 'UpdateCheck.ps1') diff --git a/Telemetry.ps1 b/Telemetry.ps1 index 6128e825..02296ae2 100644 --- a/Telemetry.ps1 +++ b/Telemetry.ps1 @@ -1,9 +1,10 @@ # Copyright (c) Microsoft Corporation. All rights reserved. # Licensed under the MIT License. -# Singleton telemetry client. Don't directly access this though....always get it -# by calling Get-TelemetryClient to ensure that the singleton is properly initialized. -$script:GHTelemetryClient = $null +# Singleton. Don't directly access this though....always get it +# by calling Get-BaseTelemetryEvent to ensure that it has been initialized and that you're always +# getting a fresh copy. +$script:GHBaseTelemetryEvent = $null function Get-PiiSafeString { @@ -51,278 +52,327 @@ function Get-PiiSafeString } } -function Get-ApplicationInsightsDllPath +function Get-BaseTelemetryEvent { -<# + <# .SYNOPSIS - Makes sure that the Microsoft.ApplicationInsights.dll assembly is available - on the machine, and returns the path to it. + Returns back the base object for an Application Insights telemetry event. .DESCRIPTION - Makes sure that the Microsoft.ApplicationInsights.dll assembly is available - on the machine, and returns the path to it. - - This will first look for the assembly in the module's script directory. - - Next it will look for the assembly in the location defined by - $SBAlternateAssemblyDir. This value would have to be defined by the user - prior to execution of this cmdlet. - - If not found there, it will look in a temp folder established during this - PowerShell session. - - If still not found, it will download the nuget package - for it to a temp folder accessible during this PowerShell session. + Returns back the base object for an Application Insights telemetry event. The Git repo for this module can be found here: http://aka.ms/PowerShellForGitHub - .PARAMETER NoStatus - If this switch is specified, long-running commands will run on the main thread - with no commandline status update. When not specified, those commands run in - the background, enabling the command prompt to provide status information. - .EXAMPLE - Get-ApplicationInsightsDllPath - - Returns back the path to the assembly as found. If the package has to - be downloaded via nuget, the command prompt will show a time duration - status counter while the package is being downloaded. + Get-BaseTelemetryEvent - .EXAMPLE - Get-ApplicationInsightsDllPath -NoStatus - - Returns back the path to the assembly as found. If the package has to - be downloaded via nuget, the command prompt will appear to hang during - this time. + Returns back a base telemetry event, populated with the minimum properties necessary + to correctly report up to this project's telemetry. Callers can then add on to the + event as nececessary. .OUTPUTS - System.String - The path to the Microsoft.ApplicationInsights.dll assembly. + [PSCustomObject] #> - [CmdletBinding(SupportsShouldProcess)] - [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSShouldProcess", "", Justification="Methods called within here make use of PSShouldProcess, and the switch is passed on to them inherently.")] - param( - [switch] $NoStatus - ) - - $nugetPackageName = "Microsoft.ApplicationInsights" - $nugetPackageVersion = "2.0.1" - $assemblyPackageTailDir = "Microsoft.ApplicationInsights.2.0.1\lib\net45" - $assemblyName = "Microsoft.ApplicationInsights.dll" - - return Get-NugetPackageDllPath -NugetPackageName $nugetPackageName -NugetPackageVersion $nugetPackageVersion -AssemblyPackageTailDirectory $assemblyPackageTailDir -AssemblyName $assemblyName -NoStatus:$NoStatus -} - -function Get-DiagnosticsTracingDllPath -{ -<# - .SYNOPSIS - Makes sure that the Microsoft.Diagnostics.Tracing.EventSource.dll assembly is available - on the machine, and returns the path to it. - - .DESCRIPTION - Makes sure that the Microsoft.Diagnostics.Tracing.EventSource.dll assembly is available - on the machine, and returns the path to it. - - This will first look for the assembly in the module's script directory. - - Next it will look for the assembly in the location defined by - $SBAlternateAssemblyDir. This value would have to be defined by the user - prior to execution of this cmdlet. - - If not found there, it will look in a temp folder established during this - PowerShell session. - - If still not found, it will download the nuget package - for it to a temp folder accessible during this PowerShell session. - - The Git repo for this module can be found here: http://aka.ms/PowerShellForGitHub - - .PARAMETER NoStatus - If this switch is specified, long-running commands will run on the main thread - with no commandline status update. When not specified, those commands run in - the background, enabling the command prompt to provide status information. - - .EXAMPLE - Get-DiagnosticsTracingDllPath - - Returns back the path to the assembly as found. If the package has to - be downloaded via nuget, the command prompt will show a time duration - status counter while the package is being downloaded. - - .EXAMPLE - Get-DiagnosticsTracingDllPath -NoStatus + [CmdletBinding()] + param() - Returns back the path to the assembly as found. If the package has to - be downloaded via nuget, the command prompt will appear to hang during - this time. + if ($null -eq $script:GHBaseTelemetryEvent) + { + if (-not (Get-GitHubConfiguration -Name SuppressTelemetryReminder)) + { + Write-Log -Message 'Telemetry is currently enabled. It can be disabled by calling "Set-GitHubConfiguration -DisableTelemetry". Refer to USAGE.md#telemetry for more information. Stop seeing this message in the future by calling "Set-GitHubConfiguration -SuppressTelemetryReminder".' + } - .OUTPUTS - System.String - The path to the Microsoft.ApplicationInsights.dll assembly. -#> - [CmdletBinding(SupportsShouldProcess)] - [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSShouldProcess", "", Justification="Methods called within here make use of PSShouldProcess, and the switch is passed on to them inherently.")] - param( - [switch] $NoStatus - ) + $username = Get-PiiSafeString -PlainText $env:USERNAME - $nugetPackageName = "Microsoft.Diagnostics.Tracing.EventSource.Redist" - $nugetPackageVersion = "1.1.24" - $assemblyPackageTailDir = "Microsoft.Diagnostics.Tracing.EventSource.Redist.1.1.24\lib\net35" - $assemblyName = "Microsoft.Diagnostics.Tracing.EventSource.dll" + $script:GHBaseTelemetryEvent = [PSCustomObject] @{ + 'name' = 'Microsoft.ApplicationInsights.66d83c523070489b886b09860e05e78a.Event' + 'time' = (Get-Date).ToUniversalTime().ToString("O") + 'iKey' = (Get-GitHubConfiguration -Name ApplicationInsightsKey) + 'tags' = [PSCustomObject] @{ + 'ai.user.id' = $username + 'ai.session.id' = [System.GUID]::NewGuid().ToString() + 'ai.application.ver' = $MyInvocation.MyCommand.Module.Version.ToString() + 'ai.internal.sdkVersion' = '2.0.1.33027' # The version this schema was based off of. + } + + 'data' = [PSCustomObject] @{ + 'baseType' = 'EventData' + 'baseData' = [PSCustomObject] @{ + 'ver' = 2 + 'properties' = [PSCustomObject] @{ + 'DayOfWeek' = (Get-Date).DayOfWeek.ToString() + 'Username' = $username + } + } + } + } + } - return Get-NugetPackageDllPath -NugetPackageName $nugetPackageName -NugetPackageVersion $nugetPackageVersion -AssemblyPackageTailDirectory $assemblyPackageTailDir -AssemblyName $assemblyName -NoStatus:$NoStatus + return $script:GHBaseTelemetryEvent.PSObject.Copy() # Get a new instance, not a reference } -function Get-ThreadingTasksDllPath +function Invoke-SendTelemetryEvent { <# .SYNOPSIS - Makes sure that the Microsoft.Threading.Tasks.dll assembly is available - on the machine, and returns the path to it. + Sends an event to Application Insights directly using its REST API. .DESCRIPTION - Makes sure that the Microsoft.Threading.Tasks.dll assembly is available - on the machine, and returns the path to it. - - This will first look for the assembly in the module's script directory. - - Next it will look for the assembly in the location defined by - $SBAlternateAssemblyDir. This value would have to be defined by the user - prior to execution of this cmdlet. - - If not found there, it will look in a temp folder established during this - PowerShell session. + Sends an event to Application Insights directly using its REST API. - If still not found, it will download the nuget package - for it to a temp folder accessible during this PowerShell session. + A very heavy wrapper around Invoke-WebRequest that understands Application Insights and + how to perform its requests with and without console status updates. It also + understands how to parse and handle errors from the REST calls. The Git repo for this module can be found here: http://aka.ms/PowerShellForGitHub + .PARAMETER TelemetryEvent + The raw object representing the event data to send to Application Insights. + .PARAMETER NoStatus If this switch is specified, long-running commands will run on the main thread with no commandline status update. When not specified, those commands run in the background, enabling the command prompt to provide status information. - .EXAMPLE - Get-ThreadingTasksDllPath - - Returns back the path to the assembly as found. If the package has to - be downloaded via nuget, the command prompt will show a time duration - status counter while the package is being downloaded. - - .EXAMPLE - Get-ThreadingTasksDllPath -NoStatus - - Returns back the path to the assembly as found. If the package has to - be downloaded via nuget, the command prompt will appear to hang during - this time. - .OUTPUTS - System.String - The path to the Microsoft.ApplicationInsights.dll assembly. + [PSCustomObject] - The result of the REST operation, in whatever form it comes in. + + .NOTES + This mirrors Invoke-GHRestMethod extensively, however the error handling is slightly + different. There wasn't a clear way to refactor the code to make both of these + Invoke-* methods share a common base code. Leaving this as-is to make this file + easier to share out with other PowerShell projects. #> [CmdletBinding(SupportsShouldProcess)] - [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSShouldProcess", "", Justification="Methods called within here make use of PSShouldProcess, and the switch is passed on to them inherently.")] + [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSAvoidGlobalVars", "", Justification="We use global variables sparingly and intentionally for module configuration, and employ a consistent naming convention.")] param( + [Parameter(Mandatory)] + [PSCustomObject] $TelemetryEvent, + [switch] $NoStatus ) - $nugetPackageName = "Microsoft.Bcl.Async" - $nugetPackageVersion = "1.0.168.0" - $assemblyPackageTailDir = "Microsoft.Bcl.Async.1.0.168\lib\net40" - $assemblyName = "Microsoft.Threading.Tasks.dll" - - return Get-NugetPackageDllPath -NugetPackageName $nugetPackageName -NugetPackageVersion $nugetPackageVersion -AssemblyPackageTailDirectory $assemblyPackageTailDir -AssemblyName $assemblyName -NoStatus:$NoStatus -} - -function Get-TelemetryClient -{ -<# - .SYNOPSIS - Returns back the singleton instance of the Application Insights TelemetryClient for - this module. - - .DESCRIPTION - Returns back the singleton instance of the Application Insights TelemetryClient for - this module. - - If the singleton hasn't been initialized yet, this will ensure all dependent assemblies - are available on the machine, create the client and initialize its properties. - - This will first look for the dependent assemblies in the module's script directory. + # Temporarily forcing NoStatus to always be true to see if it improves user experience. + $NoStatus = $true - Next it will look for the assemblies in the location defined by - $SBAlternateAssemblyDir. This value would have to be defined by the user - prior to execution of this cmdlet. + $jsonConversionDepth = 20 # Seems like it should be more than sufficient + $uri = 'https://dc.services.visualstudio.com/v2/track' + $method = 'POST' + $headers = @{'Content-Type' = 'application/json; charset=UTF-8'} - If not found there, it will look in a temp folder established during this - PowerShell session. + $body = ConvertTo-Json -InputObject $TelemetryEvent -Depth $jsonConversionDepth -Compress + $bodyAsBytes = [System.Text.Encoding]::UTF8.GetBytes($body) - If still not found, it will download the nuget package - for it to a temp folder accessible during this PowerShell session. - - The Git repo for this module can be found here: http://aka.ms/PowerShellForGitHub - - .PARAMETER NoStatus - If this switch is specified, long-running commands will run on the main thread - with no commandline status update. When not specified, those commands run in - the background, enabling the command prompt to provide status information. - - .EXAMPLE - Get-TelemetryClient - - Returns back the singleton instance to the TelemetryClient for the module. - If any nuget packages have to be downloaded in order to load the TelemetryClient, the - command prompt will show a time duration status counter during the download process. - - .EXAMPLE - Get-TelemetryClient -NoStatus - - Returns back the singleton instance to the TelemetryClient for the module. - If any nuget packages have to be downloaded in order to load the TelemetryClient, the - command prompt will appear to hang during this time. + try + { + Write-Log -Message "Sending telemetry event data to $uri [Timeout = $(Get-GitHubConfiguration -Name WebRequestTimeoutSec))]" -Level Verbose - .OUTPUTS - Microsoft.ApplicationInsights.TelemetryClient -#> - [CmdletBinding(SupportsShouldProcess)] - [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSShouldProcess", "", Justification="Methods called within here make use of PSShouldProcess, and the switch is passed on to them inherently.")] - param( - [switch] $NoStatus - ) + if ($NoStatus) + { + if ($PSCmdlet.ShouldProcess($url, "Invoke-WebRequest")) + { + $params = @{} + $params.Add("Uri", $uri) + $params.Add("Method", $method) + $params.Add("Headers", $headers) + $params.Add("UseDefaultCredentials", $true) + $params.Add("UseBasicParsing", $true) + $params.Add("TimeoutSec", (Get-GitHubConfiguration -Name WebRequestTimeoutSec)) + $params.Add("Body", $bodyAsBytes) + + $result = Invoke-WebRequest @params + } + } + else + { + $jobName = "Invoke-SendTelemetryEvent-" + (Get-Date).ToFileTime().ToString() + + if ($PSCmdlet.ShouldProcess($jobName, "Start-Job")) + { + [scriptblock]$scriptBlock = { + param($Uri, $Method, $Headers, $BodyAsBytes, $TimeoutSec, $ScriptRootPath) + + # We need to "dot invoke" Helpers.ps1 and GitHubConfiguration.ps1 within + # the context of this script block since we're running in a different + # PowerShell process and need access to Get-HttpWebResponseContent and + # config values referenced within Write-Log. + . (Join-Path -Path $ScriptRootPath -ChildPath 'Helpers.ps1') + . (Join-Path -Path $ScriptRootPath -ChildPath 'GitHubConfiguration.ps1') + + $params = @{} + $params.Add("Uri", $Uri) + $params.Add("Method", $Method) + $params.Add("Headers", $Headers) + $params.Add("UseDefaultCredentials", $true) + $params.Add("UseBasicParsing", $true) + $params.Add("TimeoutSec", $TimeoutSec) + $params.Add("Body", $BodyAsBytes) + + try + { + Invoke-WebRequest @params + } + catch [System.Net.WebException] + { + # We need to access certain headers in the exception handling, + # but the actual *values* of the headers of a WebException don't get serialized + # when the RemoteException wraps it. To work around that, we'll extract the + # information that we actually care about *now*, and then we'll throw our own exception + # that is just a JSON object with the data that we'll later extract for processing in + # the main catch. + $ex = @{} + $ex.Message = $_.Exception.Message + $ex.StatusCode = $_.Exception.Response.StatusCode + $ex.StatusDescription = $_.Exception.Response.StatusDescription + $ex.InnerMessage = $_.ErrorDetails.Message + try + { + $ex.RawContent = Get-HttpWebResponseContent -WebResponse $_.Exception.Response + } + catch + { + Write-Log -Message "Unable to retrieve the raw HTTP Web Response:" -Exception $_ -Level Warning + } + + $jsonConversionDepth = 20 # Seems like it should be more than sufficient + throw (ConvertTo-Json -InputObject $ex -Depth $jsonConversionDepth) + } + } + + $null = Start-Job -Name $jobName -ScriptBlock $scriptBlock -Arg @( + $uri, + $method, + $headers, + $bodyAsBytes, + (Get-GitHubConfiguration -Name WebRequestTimeoutSec), + $PSScriptRoot) + + if ($PSCmdlet.ShouldProcess($jobName, "Wait-JobWithAnimation")) + { + $description = 'Sending telemetry data' + Wait-JobWithAnimation -Name $jobName -Description $Description + } + + if ($PSCmdlet.ShouldProcess($jobName, "Receive-Job")) + { + $result = Receive-Job $jobName -AutoRemoveJob -Wait -ErrorAction SilentlyContinue -ErrorVariable remoteErrors + } + } + + if ($remoteErrors.Count -gt 0) + { + throw $remoteErrors[0].Exception + } + } - if ($null -eq $script:GHTelemetryClient) + return $result + } + catch { - if (-not (Get-GitHubConfiguration -Name SuppressTelemetryReminder)) + # We only know how to handle WebExceptions, which will either come in "pure" when running with -NoStatus, + # or will come in as a RemoteException when running normally (since it's coming from the asynchronous Job). + $ex = $null + $message = $null + $statusCode = $null + $statusDescription = $null + $innerMessage = $null + $rawContent = $null + + if ($_.Exception -is [System.Net.WebException]) { - Write-Log -Message 'Telemetry is currently enabled. It can be disabled by calling "Set-GitHubConfiguration -DisableTelemetry". Refer to USAGE.md#telemetry for more information. Stop seeing this message in the future by calling "Set-GitHubConfiguration -SuppressTelemetryReminder".' + $ex = $_.Exception + $message = $_.Exception.Message + $statusCode = $ex.Response.StatusCode.value__ # Note that value__ is not a typo. + $statusDescription = $ex.Response.StatusDescription + $innerMessage = $_.ErrorDetails.Message + try + { + $rawContent = Get-HttpWebResponseContent -WebResponse $ex.Response + } + catch + { + Write-Log -Message "Unable to retrieve the raw HTTP Web Response:" -Exception $_ -Level Warning + } + } + elseif (($_.Exception -is [System.Management.Automation.RemoteException]) -and + ($_.Exception.SerializedRemoteException.PSObject.TypeNames[0] -eq 'Deserialized.System.Management.Automation.RuntimeException')) + { + $ex = $_.Exception + try + { + $deserialized = $ex.Message | ConvertFrom-Json + $message = $deserialized.Message + $statusCode = $deserialized.StatusCode + $statusDescription = $deserialized.StatusDescription + $innerMessage = $deserialized.InnerMessage + $rawContent = $deserialized.RawContent + } + catch [System.ArgumentException] + { + # Will be thrown if $ex.Message isn't the JSON content we prepared + # in the System.Net.WebException handler earlier in $scriptBlock. + Write-Log -Exception $_ -Level Error + throw + } + } + else + { + Write-Log -Exception $_ -Level Error + throw } - Write-Log -Message "Initializing telemetry client." -Level Verbose + $output = @() + $output += $message - $dlls = @( - (Get-ThreadingTasksDllPath -NoStatus:$NoStatus), - (Get-DiagnosticsTracingDllPath -NoStatus:$NoStatus), - (Get-ApplicationInsightsDllPath -NoStatus:$NoStatus) - ) + if (-not [string]::IsNullOrEmpty($statusCode)) + { + $output += "$statusCode | $($statusDescription.Trim())" + } - foreach ($dll in $dlls) + if (-not [string]::IsNullOrEmpty($innerMessage)) { - $bytes = [System.IO.File]::ReadAllBytes($dll) - [System.Reflection.Assembly]::Load($bytes) | Out-Null + try + { + $innerMessageJson = ($innerMessage | ConvertFrom-Json) + if ($innerMessageJson -is [String]) + { + $output += $innerMessageJson.Trim() + } + elseif (-not [String]::IsNullOrWhiteSpace($innerMessageJson.itemsReceived)) + { + $output += "Items Received: $($innerMessageJson.itemsReceived)" + $output += "Items Accepted: $($innerMessageJson.itemsAccepted)" + if ($innerMessageJson.errors.Count -gt 0) + { + $output += "Errors:" + $output += ($innerMessageJson.errors | Format-Table | Out-String) + } + } + else + { + # In this case, it's probably not a normal message from the API + $output += ($innerMessageJson | Out-String) + } + } + catch [System.ArgumentException] + { + # Will be thrown if $innerMessage isn't JSON content + $output += $innerMessage.Trim() + } } - $username = Get-PiiSafeString -PlainText $env:USERNAME + # It's possible that the API returned JSON content in its error response. + if (-not [String]::IsNullOrWhiteSpace($rawContent)) + { + $output += $rawContent + } - $script:GHTelemetryClient = New-Object Microsoft.ApplicationInsights.TelemetryClient - $script:GHTelemetryClient.InstrumentationKey = (Get-GitHubConfiguration -Name ApplicationInsightsKey) - $script:GHTelemetryClient.Context.User.Id = $username - $script:GHTelemetryClient.Context.Session.Id = [System.GUID]::NewGuid().ToString() - $script:GHTelemetryClient.Context.Properties['Username'] = $username - $script:GHTelemetryClient.Context.Properties['DayOfWeek'] = (Get-Date).DayOfWeek - $script:GHTelemetryClient.Context.Component.Version = $MyInvocation.MyCommand.Module.Version.ToString() + $output += "Original body: $body" + $newLineOutput = ($output -join [Environment]::NewLine) + Write-Log -Message $newLineOutput -Level Error + throw $newLineOutput } - - return $script:GHTelemetryClient } function Set-TelemetryEvent @@ -400,25 +450,38 @@ function Set-TelemetryEvent try { - $telemetryClient = Get-TelemetryClient -NoStatus:$NoStatus + $telemetryEvent = Get-BaseTelemetryEvent - $propertiesDictionary = New-Object 'System.Collections.Generic.Dictionary[string, string]' - $propertiesDictionary['DayOfWeek'] = (Get-Date).DayOfWeek - $Properties.Keys | ForEach-Object { $propertiesDictionary[$_] = $Properties[$_] } + Add-Member -InputObject $telemetryEvent.data.baseData -Name 'name' -Value $EventName -MemberType NoteProperty -Force - $metricsDictionary = New-Object 'System.Collections.Generic.Dictionary[string, double]' - $Metrics.Keys | ForEach-Object { $metricsDictionary[$_] = $Metrics[$_] } + # Properties + foreach ($property in $Properties.GetEnumerator()) + { + Add-Member -InputObject $telemetryEvent.data.baseData.properties -Name $property.Key -Value $property.Value -MemberType NoteProperty -Force + } - $telemetryClient.TrackEvent($EventName, $propertiesDictionary, $metricsDictionary); + # Measurements + if ($Metrics.Count -gt 0) + { + $measurements = @{} + foreach ($metric in $Metrics.GetEnumerator()) + { + $measurements[$metric.Key] = $metric.Value + } - # Flushing should increase the chance of success in uploading telemetry logs - Flush-TelemetryClient -NoStatus:$NoStatus + Add-Member -InputObject $telemetryEvent.data.baseData -Name 'measurements' -Value ([PSCustomObject] $measurements) -MemberType NoteProperty -Force + } + + $null = Invoke-SendTelemetryEvent -TelemetryEvent $telemetryEvent -NoStatus:$NoStatus } catch { - # Telemetry should be best-effort. Failures while trying to handle telemetry should not - # cause exceptions in the app itself. - Write-Log -Message "Set-TelemetryEvent failed:" -Exception $_ -Level Error + Write-Log -Level Warning -Message @( + "Encountered a problem while trying to record telemetry events.", + "This is non-fatal, but it would be helpful if you could report this problem", + "to the PowerShellForGitHub team for further investigation:" + "", + $_.Exception) } } @@ -487,8 +550,6 @@ function Set-TelemetryException [hashtable] $Properties = @{}, - [switch] $NoFlush, - [switch] $NoStatus ) @@ -502,99 +563,73 @@ function Set-TelemetryException try { - $telemetryClient = Get-TelemetryClient -NoStatus:$NoStatus + $telemetryEvent = Get-BaseTelemetryEvent - $propertiesDictionary = New-Object 'System.Collections.Generic.Dictionary[string,string]' - $propertiesDictionary['Message'] = $Exception.Message - $propertiesDictionary['HResult'] = "0x{0}" -f [Convert]::ToString($Exception.HResult, 16) - $Properties.Keys | ForEach-Object { $propertiesDictionary[$_] = $Properties[$_] } + $telemetryEvent.data.baseType = 'ExceptionData' + Add-Member -InputObject $telemetryEvent.data.baseData -Name 'handledAt' -Value 'UserCode' -MemberType NoteProperty -Force + # Properties if (-not [String]::IsNullOrWhiteSpace($ErrorBucket)) { - $propertiesDictionary['ErrorBucket'] = $ErrorBucket + Add-Member -InputObject $telemetryEvent.data.baseData.properties -Name 'ErrorBucket' -Value $ErrorBucket -MemberType NoteProperty -Force } - $telemetryClient.TrackException($Exception, $propertiesDictionary); - - # Flushing should increase the chance of success in uploading telemetry logs - if (-not $NoFlush) + Add-Member -InputObject $telemetryEvent.data.baseData.properties -Name 'Message' -Value $Exception.Message -MemberType NoteProperty -Force + Add-Member -InputObject $telemetryEvent.data.baseData.properties -Name 'HResult' -Value ("0x{0}" -f [Convert]::ToString($Exception.HResult, 16)) -MemberType NoteProperty -Force + foreach ($property in $Properties.GetEnumerator()) { - Flush-TelemetryClient -NoStatus:$NoStatus + Add-Member -InputObject $telemetryEvent.data.baseData.properties -Name $property.Key -Value $property.Value -MemberType NoteProperty -Force } - } - catch - { - # Telemetry should be best-effort. Failures while trying to handle telemetry should not - # cause exceptions in the app itself. - Write-Log -Message "Set-TelemetryException failed:" -Exception $_ -Level Error - } -} - -function Flush-TelemetryClient -{ -<# - .SYNOPSIS - Flushes the buffer of stored telemetry events to the configured Applications Insights instance. - - .DESCRIPTION - Flushes the buffer of stored telemetry events to the configured Applications Insights instance. - - The Git repo for this module can be found here: http://aka.ms/PowerShellForGitHub - - .PARAMETER NoStatus - If this switch is specified, long-running commands will run on the main thread - with no commandline status update. When not specified, those commands run in - the background, enabling the command prompt to provide status information. - - .EXAMPLE - Flush-TelemetryClient - - Attempts to push all buffered telemetry events for this telemetry client immediately to - Application Insights. If the telemetry client needs to be created to accomplish this, - and the required assemblies are not available on the local machine, the download status - will be presented at the command prompt. - .EXAMPLE - Flush-TelemetryClient -NoStatus - - Attempts to push all buffered telemetry events for this telemetry client immediately to - Application Insights. If the telemetry client needs to be created to accomplish this, - and the required assemblies are not available on the local machine, the command prompt - will appear to hang while they are downloaded. -#> - [CmdletBinding(SupportsShouldProcess)] - [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSUseApprovedVerbs", "", Justification="Internal-only helper method. Matches the internal method that is called.")] - [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSShouldProcess", "", Justification="Methods called within here make use of PSShouldProcess, and the switch is passed on to them inherently.")] - param( - [switch] $NoStatus - ) - - Write-InvocationLog - - if (Get-GitHubConfiguration -Name DisableTelemetry) - { - Write-Log -Message "Telemetry has been disabled via configuration. Skipping flushing of the telemetry client." -Level Verbose - return - } + # Re-create the stack. We'll start with what's in Invocation Info since it's already + # been broken down for us (although it doesn't supply the method name). + $parsedStack = @( + [PSCustomObject] @{ + 'assembly' = $MyInvocation.MyCommand.Module.Name + 'method' = '' + 'fileName' = $Exception.ErrorRecord.InvocationInfo.ScriptName + 'level' = 0 + 'line' = $Exception.ErrorRecord.InvocationInfo.ScriptLineNumber + } + ) - $telemetryClient = Get-TelemetryClient -NoStatus:$NoStatus + # And then we'll try to parse ErrorRecord's ScriptStackTrace and make this as useful + # as possible. + $stackFrames = $Exception.ErrorRecord.ScriptStackTrace -split [Environment]::NewLine + for ($i = 0; $i -lt $stackFrames.Count; $i++) + { + $frame = $stackFrames[$i] + if ($frame -match '^at (.+), (.+): line (\d+)$') + { + $parsedStack += [PSCustomObject] @{ + 'assembly' = $MyInvocation.MyCommand.Module.Name + 'method' = $Matches[1] + 'fileName' = $Matches[2] + 'level' = $i + 1 + 'line' = $Matches[3] + } + } + } - try - { - $telemetryClient.Flush() - } - catch [System.Net.WebException] - { - Write-Log -Message "Encountered exception while trying to flush telemetry events:" -Exception $_ -Level Warning + # Finally, we'll build up the Exception data object. + $exceptionData = [PSCustomObject] @{ + 'id' = (Get-Date).ToFileTime() + 'typeName' = $Exception.GetType().FullName + 'message' = $Exception.Message + 'hasFullStack' = $true + 'parsedStack' = $parsedStack + } - Set-TelemetryException -Exception ($_.Exception) -ErrorBucket "TelemetryFlush" -NoFlush -NoStatus:$NoStatus + Add-Member -InputObject $telemetryEvent.data.baseData -Name 'exceptions' -Value @($exceptionData) -MemberType NoteProperty -Force + $null = Invoke-SendTelemetryEvent -TelemetryEvent $telemetryEvent -NoStatus:$NoStatus } catch { - # Any other scenario is one that we want to identify and fix so that we don't miss telemetry - Write-Log -Level Warning -Exception $_ -Message @( + Write-Log -Level Warning -Message @( "Encountered a problem while trying to record telemetry events.", "This is non-fatal, but it would be helpful if you could report this problem", - "to the PowerShellForGitHub team for further investigation:") + "to the PowerShellForGitHub team for further investigation:", + "", + $_.Exception) } } From 00f099fa81d25908735dc32d86736180f6209d34 Mon Sep 17 00:00:00 2001 From: Howard Wolosky Date: Mon, 1 Jun 2020 17:08:31 -0700 Subject: [PATCH 23/60] Add 'References' section to pull request template --- .github/PULL_REQUEST_TEMPLATE.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index ab5cd63f..b5ecc397 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -6,7 +6,6 @@ --> #### Description - #### Issues Fixed +#### References + #### Checklist - [ ] You actually ran the code that you just wrote, especially if you did just "one last quick change". - [ ] Comment-based help added/updated, including examples. -- [ ] [Static analysis](https://github.com/microsoft/PowerShellForGitHub/blob/master/CONTRIBUTING.md#static-analysis) -is reporting back clean. +- [ ] [Static analysis](https://github.com/microsoft/PowerShellForGitHub/blob/master/CONTRIBUTING.md#static-analysis) is reporting back clean. - [ ] New/changed code adheres to our [coding guidelines](https://github.com/microsoft/PowerShellForGitHub/blob/master/CONTRIBUTING.md#coding-guidelines). +- [ ] New/changed code continues to [support the pipeline](https://github.com/microsoft/PowerShellForGitHub/blob/master/CONTRIBUTING.md#pipeline-support). - [ ] Changes to the manifest file follow the [manifest guidance](https://github.com/microsoft/PowerShellForGitHub/blob/master/CONTRIBUTING.md#module-manifest). -- [ ] Unit tests were added/updated and are all passing. See [testing guidelines](https://github.com/microsoft/PowerShellForGitHub/blob/master/CONTRIBUTING.md#testing). +- [ ] Unit tests were added/updated and are all passing. See [testing guidelines](https://github.com/microsoft/PowerShellForGitHub/blob/master/CONTRIBUTING.md#testing). This includes making sure that all pipeline input variations have been covered. - [ ] Relevant usage examples have been added/updated in [USAGE.md](https://github.com/microsoft/PowerShellForGitHub/blob/master/USAGE.md). - [ ] If desired, ensure your name is added to our [Contributors list](https://github.com/microsoft/PowerShellForGitHub/blob/master/CONTRIBUTING.md#contributors) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 3bb7e8d8..1f690f71 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -23,6 +23,7 @@ Looking for information on how to use this module? Head on over to [README.md]( * [Adding New Configuration Properties](#adding-new-configuration-properties) * [Code Comments](#code-comments) * [Debugging Tips](#debugging-tips) +* [Pipeline Support](#pipeline-support) * [Testing](#testing) * [Installing Pester](#installing-pester) * [Configuring Your Environment](#configuring-your-environment) @@ -286,8 +287,71 @@ Set-GitHubConfiguration -LogRequestBody ---------- +### Pipeline Support + +This module has comprehensive support for the PowerShell pipeline. It is imperative that all +new functionality added to the module embraces this design. + + * Most functions are declared as a `filter`. This is the equivalent of a `function` where the + body of the function is the `process` block, and the `begin/end` blocks are empty. + + * In limited cases where one of the inputs is an array of something, and you specifically want that + to be processed as a single command (like adding a bunch of labels to a single issue at once), + you can implement it as a `function` where you use `begin/process` to gather all of the values + into a single internal array, and then do the actual command execution in the `end` block. A + good example of that which you can follow can be seen with `Add-GitHubIssueLabel`. + + * Any function that requires the repo's `Uri` to be provided should be additionally aliased with + `[Alias('RepositoryUrl')]` and its `[Parameter()]` definition should include `ValueFromPipelineByPropertyName`. + + * Do not use any generic term like `Name` in your parameters. That will end up causing unintended + pipeline issues down the line. For instance, if it's a label, call it `Label`, even though `Name` + would make sense, other objects in the pipeline (like a `GitHub.Respository` object) also have + a `name` property that would conflict. + + * You should plan on adding additional properties to all objects being returned from an API call. + Any object that is specific to a repository should have a `RepositoryUrl` `NoteProperty` added + to it, enabling it to be piped-in to any other command that requires knowing which repository + you're talking about. Additionally, any other property that might be necessary to uniquely + identify that object in a different command should get added properties. For example, with Issues, + we add both an `IssueNumber` property and an `IssueId` property to it, as the Issue commands + need to interact with the `IssueNumber` while the Event commands interact with the `IssueId`. + We prefer to _only_ add additional properties that are believed to be needed as input to other + commands (as opposed to creating alias properties for all of the object's properties). + + * For every major file, you will find an `Add-GitHub*AdditionalProperties` filter method at the end. + If you're writing a new file, you'll need to create this yourself (and model it after an existing + one). The goal of this is that you can simply pipe the output of your `Invoke-GHRestMethod` + directly into this method to update the result with the additional properties, and then return + that modified version to the user. The benefit of this approach is that you can then apply that + filter on child objects within the primary object. For instance, a `GitHub.Issue` has multiple + `GitHub.User` objects, `GitHub.Label` objects, a `GitHub.Milestone` object and more. Within + `Add-GitHubIssueAdditionalProperties`, it just needs to know to call the appropriate + `Add-GitHub*AdditionalProperties` method on the qualifying child properties, without needing to + know anything more about them. + + * That method will also "type" information to each object. This is forward-looking work to ease + support for providing formatting of various object types in the future. That type should be + defined at the top of the current file at the script level (see other files for an example), + and you should be sure to both specify it in the `.OUTPUTS` section of the Comment Based Help (CBH) + for the command, as well as with `[OutputType({$script:GitHubUserTypeName})]` (for example). + + * Going along with the `.OUTPUTS` is the `.INPUTS` section. Please maintain this section as well. + If you add any new type that will gain a `RepositoryUrl` property, then you'll need to update + virtually _all_ of the `.INPUTS` entries across all of the files where the function has a `Uri` + parameter. Please keep these type names alphabetical. + + * To enable debugging issues involving pipeline support, there is an additional configuration + property that you might use: `Set-GitHubConfiguration -DisablePipelineSupport`. That will + prevent the module from adding _any_ additional properties to the objects. + +---------- + ### Testing [![Build status](https://dev.azure.com/ms/PowerShellForGitHub/_apis/build/status/PowerShellForGitHub-CI?branchName=master)](https://dev.azure.com/ms/PowerShellForGitHub/_build/latest?definitionId=109&branchName=master) +[![Azure DevOps tests](https://img.shields.io/azure-devops/tests/ms/PowerShellForGitHub/109/master)](https://dev.azure.com/ms/PowerShellForGitHub/_build/latest?definitionId=109&branchName=master) +[![Azure DevOps coverage](https://img.shields.io/azure-devops/coverage/ms/PowerShellForGitHub/109/master)](https://dev.azure.com/ms/PowerShellForGitHub/_build/latest?definitionId=109&branchName=master) + #### Installing Pester This module supports testing using the [Pester UT framework](https://github.com/pester/Pester). @@ -350,6 +414,8 @@ There are many more nuances to code-coverage, see #### Automated Tests [![Build status](https://dev.azure.com/ms/PowerShellForGitHub/_apis/build/status/PowerShellForGitHub-CI?branchName=master)](https://dev.azure.com/ms/PowerShellForGitHub/_build/latest?definitionId=109&branchName=master) +[![Azure DevOps tests](https://img.shields.io/azure-devops/tests/ms/PowerShellForGitHub/109/master)](https://dev.azure.com/ms/PowerShellForGitHub/_build/latest?definitionId=109&branchName=master) +[![Azure DevOps coverage](https://img.shields.io/azure-devops/coverage/ms/PowerShellForGitHub/109/master)](https://dev.azure.com/ms/PowerShellForGitHub/_build/latest?definitionId=109&branchName=master) These test are configured to automatically execute upon any update to the `master` branch of `microsoft/PowerShellForGitHub`. @@ -362,9 +428,9 @@ as well...it is stored, encrypted, within Azure DevOps. It is not accessible fo the CI pipeline. To run the tests locally with your own account, see [configuring-your-environment](#configuring-your-environment). -> NOTE: We're currently encountering issues with the tests successfully running within the pipeline. -> They do complete successfully locally, so please test your changes locally before submitting a -> pull request. +> Your change must successfully pass all tests before they will be merged. While we will run a CI +> build on your behalf for any submitted pull request, it's to your benefit to verify your changes +> locally first. #### New Test Guidelines Your tests should have NO dependencies on an account being set up in a specific way. They should diff --git a/GitHubAnalytics.ps1 b/GitHubAnalytics.ps1 index e4bba220..0ee2310b 100644 --- a/GitHubAnalytics.ps1 +++ b/GitHubAnalytics.ps1 @@ -24,8 +24,12 @@ function Group-GitHubIssue The date property that should be inspected when determining which week grouping the issue if part of. + .INPUTS + GitHub.Issue + .OUTPUTS - [PSCustomObject[]] Collection of issues and counts, by week, along with the total count of issues. + [PSCustomObject[]] + Collection of issues and counts, by week, along with the total count of issues. .EXAMPLE $issues = @() @@ -90,8 +94,12 @@ function Group-GitHubIssue foreach ($week in $weekDates) { $filteredIssues = @($Issue | Where-Object { - (($DateType -eq 'Created') -and ($_.created_at -ge $week) -and ($_.created_at -le $endOfWeek)) -or - (($DateType -eq 'Closed') -and ($_.closed_at -ge $week) -and ($_.closed_at -le $endOfWeek)) + (($DateType -eq 'Created') -and + ($_.created_at -ge $week) -and + ($_.created_at -le $endOfWeek)) -or + (($DateType -eq 'Closed') -and + ($_.closed_at -ge $week) -and + ($_.closed_at -le $endOfWeek)) }) $endOfWeek = $week @@ -144,6 +152,9 @@ function Group-GitHubPullRequest The date property that should be inspected when determining which week grouping the pull request if part of. + .INPUTS + GitHub.PullRequest + .OUTPUTS [PSCustomObject[]] Collection of pull requests and counts, by week, along with the total count of pull requests. @@ -211,8 +222,12 @@ function Group-GitHubPullRequest foreach ($week in $weekDates) { $filteredPullRequests = @($PullRequest | Where-Object { - (($DateType -eq 'Created') -and ($_.created_at -ge $week) -and ($_.created_at -le $endOfWeek)) -or - (($DateType -eq 'Merged') -and ($_.merged_at -ge $week) -and ($_.merged_at -le $endOfWeek)) + (($DateType -eq 'Created') -and + ($_.created_at -ge $week) -and + ($_.created_at -le $endOfWeek)) -or + (($DateType -eq 'Merged') -and + ($_.merged_at -ge $week) -and + ($_.merged_at -le $endOfWeek)) }) $endOfWeek = $week @@ -265,6 +280,7 @@ function Get-WeekDate Get-WeekDate -Weeks 10 #> [CmdletBinding()] + [OutputType([DateTime[]])] param( [ValidateRange(0, 10000)] [int] $Weeks = 12 diff --git a/GitHubAssignees.ps1 b/GitHubAssignees.ps1 index 6a2c1309..eccc9810 100644 --- a/GitHubAssignees.ps1 +++ b/GitHubAssignees.ps1 @@ -1,7 +1,7 @@ # Copyright (c) Microsoft Corporation. All rights reserved. # Licensed under the MIT License. -function Get-GitHubAssignee +filter Get-GitHubAssignee { <# .DESCRIPTION @@ -32,14 +32,40 @@ function Get-GitHubAssignee the background, enabling the command prompt to provide status information. If not supplied here, the DefaultNoStatus configuration property value will be used. + .INPUTS + GitHub.Branch + GitHub.Content + GitHub.Event + GitHub.Issue + GitHub.IssueComment + GitHub.Label + GitHub.Milestone + GitHub.PullRequest + GitHub.Project + GitHub.ProjectCard + GitHub.ProjectColumn + GitHub.Release + GitHub.Repository + GitHub.User + + .OUTPUTS + GitHub.User + .EXAMPLE - Get-GitHubAssigneeList -OwnerName Microsoft -RepositoryName PowerShellForGitHub + Get-GitHubAssigneeList -OwnerName microsoft -RepositoryName PowerShellForGitHub + + Lists the available assignees for issues from the microsoft\PowerShellForGitHub project. - Lists the available assignees for issues from the Microsoft\PowerShellForGitHub project. + .EXAMPLE + $repo = Get-GitHubRepository -OwnerName microsoft -RepositoryName PowerShellForGitHub + $repo | Get-GitHubAssigneeList + + Lists the available assignees for issues from the microsoft\PowerShellForGitHub project. #> [CmdletBinding( SupportsShouldProcess, DefaultParameterSetName='Elements')] + [OutputType({$script:GitHubUserTypeName})] [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSShouldProcess", "", Justification="Methods called within here make use of PSShouldProcess, and the switch is passed on to them inherently.")] [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSReviewUnusedParameter", "", Justification="One or more parameters (like NoStatus) are only referenced by helper methods which get access to it from the stack via Get-Variable -Scope 1.")] param( @@ -51,7 +77,9 @@ function Get-GitHubAssignee [Parameter( Mandatory, + ValueFromPipelineByPropertyName, ParameterSetName='Uri')] + [Alias('RepositoryUrl')] [string] $Uri, [string] $AccessToken, @@ -79,14 +107,14 @@ function Get-GitHubAssignee 'NoStatus' = (Resolve-ParameterWithDefaultConfigurationValue -Name NoStatus -ConfigValueName DefaultNoStatus) } - return Invoke-GHRestMethodMultipleResult @params + return (Invoke-GHRestMethodMultipleResult @params | Add-GitHubUserAdditionalProperties) } -function Test-GitHubAssignee +filter Test-GitHubAssignee { <# .DESCRIPTION - Checks if a user has permission to be assigned to an issue in this repository. Returns a boolean. + Checks if a user has permission to be assigned to an issue in this repository. The Git repo for this module can be found here: http://aka.ms/PowerShellForGitHub @@ -116,18 +144,50 @@ function Test-GitHubAssignee the background, enabling the command prompt to provide status information. If not supplied here, the DefaultNoStatus configuration property value will be used. + .INPUTS + GitHub.Branch + GitHub.Content + GitHub.Event + GitHub.Issue + GitHub.IssueComment + GitHub.Label + GitHub.Milestone + GitHub.PullRequest + GitHub.Project + GitHub.ProjectCard + GitHub.ProjectColumn + GitHub.Release + GitHub.Repository + GitHub.User + .OUTPUTS - [bool] If the assignee can be assigned to issues in the repository. + [bool] + If the assignee can be assigned to issues in the repository. .EXAMPLE - Test-GitHubAssignee -OwnerName Microsoft -RepositoryName PowerShellForGitHub -Assignee "LoginID123" + Test-GitHubAssignee -OwnerName microsoft -RepositoryName PowerShellForGitHub -Assignee "LoginID123" - Checks if a user has permission to be assigned to an issue from the Microsoft\PowerShellForGitHub project. + Checks if a user has permission to be assigned to an issue + from the microsoft\PowerShellForGitHub project. + + .EXAMPLE + $repo = Get-GitHubRepository -OwnerName microsoft -RepositoryName PowerShellForGitHub + $repo | Test-GitHubAssignee -Assignee 'octocat' + + Checks if a user has permission to be assigned to an issue + from the microsoft\PowerShellForGitHub project. + + .EXAMPLE + $octocat = Get-GitHubUser -UserName 'octocat' + $repo = $octocat | Test-GitHubAssignee -OwnerName microsoft -RepositoryName PowerShellForGitHub + + Checks if a user has permission to be assigned to an issue + from the microsoft\PowerShellForGitHub project. #> [CmdletBinding( SupportsShouldProcess, DefaultParameterSetName='Elements')] - [OutputType([bool])] + [OutputType([bool])] [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSShouldProcess", "", Justification="Methods called within here make use of PSShouldProcess, and the switch is passed on to them inherently.")] [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSReviewUnusedParameter", "", Justification="One or more parameters (like NoStatus) are only referenced by helper methods which get access to it from the stack via Get-Variable -Scope 1.")] param( @@ -139,9 +199,13 @@ function Test-GitHubAssignee [Parameter( Mandatory, + ValueFromPipelineByPropertyName, ParameterSetName='Uri')] + [Alias('RepositoryUrl')] [string] $Uri, + [Parameter(ValueFromPipelineByPropertyName)] + [Alias('UserName')] [string] $Assignee, [string] $AccessToken, @@ -183,11 +247,11 @@ function Test-GitHubAssignee } } -function New-GithubAssignee +function New-GitHubAssignee { <# .DESCRIPTION - Adds a list of assignees to a Github Issue for the given repository. + Adds a list of assignees to a GitHub Issue for the given repository. The Git repo for this module can be found here: http://aka.ms/PowerShellForGitHub @@ -208,7 +272,9 @@ function New-GithubAssignee Issue number to add the assignees to. .PARAMETER Assignee - Usernames of users to assign this issue to. NOTE: Only users with push access can add assignees to an issue. + Usernames of users to assign this issue to. + + NOTE: Only users with push access can add assignees to an issue. Assignees are silently ignored otherwise. .PARAMETER AccessToken @@ -221,14 +287,60 @@ function New-GithubAssignee the background, enabling the command prompt to provide status information. If not supplied here, the DefaultNoStatus configuration property value will be used. + .INPUTS + GitHub.Branch + GitHub.Content + GitHub.Event + GitHub.Issue + GitHub.IssueComment + GitHub.Label + GitHub.Milestone + GitHub.PullRequest + GitHub.Project + GitHub.ProjectCard + GitHub.ProjectColumn + GitHub.Release + GitHub.Repository + GitHub.User + + .OUTPUTS + GitHub.Issue + .EXAMPLE - New-GithubAssignee -OwnerName Microsoft -RepositoryName PowerShellForGitHub -Assignee $assignee + $assignees = @('octocat') + New-GitHubAssignee -OwnerName microsoft -RepositoryName PowerShellForGitHub -Issue 1 -Assignee $assignee + + Additionally assigns the usernames in $assignee to Issue #1 + from the microsoft\PowerShellForGitHub project. + + .EXAMPLE + $assignees = @('octocat') + $repo = Get-GitHubRepository -OwnerName microsoft -RepositoryName PowerShellForGitHub + $repo | New-GitHubAssignee -Issue 1 -Assignee $assignee + + Additionally assigns the usernames in $assignee to Issue #1 + from the microsoft\PowerShellForGitHub project. - Lists the available assignees for issues from the Microsoft\PowerShellForGitHub project. + .EXAMPLE + $assignees = @('octocat') + Get-GitHubRepository -OwnerName microsoft -RepositoryName PowerShellForGitHub | + Get-GitHubIssue -Issue 1 | + New-GitHubAssignee -Assignee $assignee + + Additionally assigns the usernames in $assignee to Issue #1 + from the microsoft\PowerShellForGitHub project. + + .EXAMPLE + $octocat = Get-GitHubUser -UserName 'octocat' + $octocat | New-GitHubAssignee -OwnerName microsoft -RepositoryName PowerShellForGitHub -Issue 1 + + Additionally assigns the user 'octocat' to Issue #1 + from the microsoft\PowerShellForGitHub project. #> [CmdletBinding( SupportsShouldProcess, DefaultParameterSetName='Elements')] + [OutputType({$script:GitHubIssueTypeName})] [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSShouldProcess", "", Justification="Methods called within here make use of PSShouldProcess, and the switch is passed on to them inherently.")] [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSReviewUnusedParameter", "", Justification="One or more parameters (like NoStatus) are only referenced by helper methods which get access to it from the stack via Get-Variable -Scope 1.")] param( @@ -240,14 +352,22 @@ function New-GithubAssignee [Parameter( Mandatory, + ValueFromPipelineByPropertyName, ParameterSetName='Uri')] + [Alias('RepositoryUrl')] [string] $Uri, - [Parameter(Mandatory)] + [Parameter( + Mandatory, + ValueFromPipelineByPropertyName)] + [Alias('IssueNumber')] [int64] $Issue, - [Parameter(Mandatory)] + [Parameter( + Mandatory, + ValueFromPipelineByPropertyName)] [ValidateCount(1, 10)] + [Alias('UserName')] [string[]] $Assignee, [string] $AccessToken, @@ -255,43 +375,59 @@ function New-GithubAssignee [switch] $NoStatus ) - Write-InvocationLog - - $elements = Resolve-RepositoryElements - $OwnerName = $elements.ownerName - $RepositoryName = $elements.repositoryName - - $telemetryProperties = @{ - 'OwnerName' = (Get-PiiSafeString -PlainText $OwnerName) - 'RepositoryName' = (Get-PiiSafeString -PlainText $RepositoryName) - 'AssigneeCount' = $Assignee.Count - 'Issue' = (Get-PiiSafeString -PlainText $Issue) + begin + { + $userNames = @() } - $hashBody = @{ - 'assignees' = $Assignee + process + { + foreach ($name in $Assignee) + { + $userNames += $name + } } - $params = @{ - 'UriFragment' = "repos/$OwnerName/$RepositoryName/issues/$Issue/assignees" - 'Body' = (ConvertTo-Json -InputObject $hashBody) - 'Method' = 'Post' - 'Description' = "Add assignees to issue $Issue for $RepositoryName" - 'AccessToken' = $AccessToken - 'AcceptHeader' = 'application/vnd.github.symmetra-preview+json' - 'TelemetryEventName' = $MyInvocation.MyCommand.Name - 'TelemetryProperties' = $telemetryProperties - 'NoStatus' = (Resolve-ParameterWithDefaultConfigurationValue -Name NoStatus -ConfigValueName DefaultNoStatus) - } + end + { + Write-InvocationLog + + $elements = Resolve-RepositoryElements + $OwnerName = $elements.ownerName + $RepositoryName = $elements.repositoryName - return Invoke-GHRestMethod @params + $telemetryProperties = @{ + 'OwnerName' = (Get-PiiSafeString -PlainText $OwnerName) + 'RepositoryName' = (Get-PiiSafeString -PlainText $RepositoryName) + 'AssigneeCount' = $userNames.Count + 'Issue' = (Get-PiiSafeString -PlainText $Issue) + } + + $hashBody = @{ + 'assignees' = $userNames + } + + $params = @{ + 'UriFragment' = "repos/$OwnerName/$RepositoryName/issues/$Issue/assignees" + 'Body' = (ConvertTo-Json -InputObject $hashBody) + 'Method' = 'Post' + 'Description' = "Add assignees to issue $Issue for $RepositoryName" + 'AccessToken' = $AccessToken + 'AcceptHeader' = $script:symmetraAcceptHeader + 'TelemetryEventName' = $MyInvocation.MyCommand.Name + 'TelemetryProperties' = $telemetryProperties + 'NoStatus' = (Resolve-ParameterWithDefaultConfigurationValue -Name NoStatus -ConfigValueName DefaultNoStatus) + } + + return (Invoke-GHRestMethod @params | Add-GitHubIssueAdditionalProperties) + } } -function Remove-GithubAssignee +function Remove-GitHubAssignee { <# .DESCRIPTION - Removes an assignee from a Github issue. + Removes an assignee from a GitHub issue. The Git repo for this module can be found here: http://aka.ms/PowerShellForGitHub @@ -312,7 +448,7 @@ function Remove-GithubAssignee Issue number to remove the assignees from. .PARAMETER Assignee - Usernames of assignees to remove from an issue. NOTE: Only users with push access can remove assignees from an issue. Assignees are silently ignored otherwise. + Usernames of assignees to remove from an issue. .PARAMETER Force If this switch is specified, you will not be prompted for confirmation of command execution. @@ -327,25 +463,68 @@ function Remove-GithubAssignee the background, enabling the command prompt to provide status information. If not supplied here, the DefaultNoStatus configuration property value will be used. + .INPUTS + GitHub.Branch + GitHub.Content + GitHub.Event + GitHub.Issue + GitHub.IssueComment + GitHub.Label + GitHub.Milestone + GitHub.PullRequest + GitHub.Project + GitHub.ProjectCard + GitHub.ProjectColumn + GitHub.Release + GitHub.Repository + + .OUTPUTS + GitHub.Issue + .EXAMPLE - Remove-GithubAssignee -OwnerName Microsoft -RepositoryName PowerShellForGitHub -Assignee $assignees + $assignees = @('octocat') + Remove-GitHubAssignee -OwnerName microsoft -RepositoryName PowerShellForGitHub -Issue 1 -Assignee $assignee - Removes the available assignees for issues from the Microsoft\PowerShellForGitHub project. + Removes the specified usernames from the assignee list for Issue #1 + in the microsoft\PowerShellForGitHub project. .EXAMPLE - Remove-GithubAssignee -OwnerName Microsoft -RepositoryName PowerShellForGitHub -Assignee $assignees -Confirm:$false + $assignees = @('octocat') + $repo = Get-GitHubRepository -OwnerName microsoft -RepositoryName PowerShellForGitHub + $repo | Remove-GitHubAssignee -Issue 1 -Assignee $assignee + + Removes the specified usernames from the assignee list for Issue #1 + in the microsoft\PowerShellForGitHub project. - Removes the available assignees for issues from the Microsoft\PowerShellForGitHub project. Will not prompt for confirmation, as -Confirm:$false was specified. + Will not prompt for confirmation because -Confirm:$false was specified .EXAMPLE - Remove-GithubAssignee -OwnerName Microsoft -RepositoryName PowerShellForGitHub -Assignee $assignees -Force + $assignees = @('octocat') + Get-GitHubRepository -OwnerName microsoft -RepositoryName PowerShellForGitHub | + Get-GitHubIssue -Issue 1 | + Remove-GitHubAssignee -Assignee $assignee + + Removes the specified usernames from the assignee list for Issue #1 + in the microsoft\PowerShellForGitHub project. + + Will not prompt for confirmation because -Force was specified + + .EXAMPLE + $octocat = Get-GitHubUser -UserName 'octocat' + $octocat | Remove-GitHubAssignee -OwnerName microsoft -RepositoryName PowerShellForGitHub -Issue 1 + + Removes the specified usernames from the assignee list for Issue #1 + in the microsoft\PowerShellForGitHub project. - Removes the available assignees for issues from the Microsoft\PowerShellForGitHub project. Will not prompt for confirmation, as -Force was specified. + .NOTES + Only users with push access can remove assignees from an issue. + Assignees are silently ignored otherwise. #> [CmdletBinding( SupportsShouldProcess, DefaultParameterSetName='Elements', ConfirmImpact="High")] + [OutputType({$script:GitHubIssueTypeName})] [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSReviewUnusedParameter", "", Justification="One or more parameters (like NoStatus) are only referenced by helper methods which get access to it from the stack via Get-Variable -Scope 1.")] param( [Parameter(ParameterSetName='Elements')] @@ -356,13 +535,22 @@ function Remove-GithubAssignee [Parameter( Mandatory, + ValueFromPipelineByPropertyName, ParameterSetName='Uri')] + [Alias('RepositoryUrl')] [string] $Uri, - [Parameter(Mandatory)] + [Parameter( + Mandatory, + ValueFromPipelineByPropertyName)] + [Alias('IssueNumber')] [int64] $Issue, - [Parameter(Mandatory)] + [Parameter( + Mandatory, + ValueFromPipelineByPropertyName)] + [ValidateNotNullOrEmpty()] + [Alias('UserName')] [string[]] $Assignee, [switch] $Force, @@ -372,42 +560,58 @@ function Remove-GithubAssignee [switch] $NoStatus ) - Write-InvocationLog - - $elements = Resolve-RepositoryElements - $OwnerName = $elements.ownerName - $RepositoryName = $elements.repositoryName - - $telemetryProperties = @{ - 'OwnerName' = (Get-PiiSafeString -PlainText $OwnerName) - 'RepositoryName' = (Get-PiiSafeString -PlainText $RepositoryName) - 'AssigneeCount' = $Assignee.Count - 'Issue' = (Get-PiiSafeString -PlainText $Issue) - } - - $hashBody = @{ - 'assignees' = $Assignee + begin + { + $userNames = @() } - if ($Force -and (-not $Confirm)) + process { - $ConfirmPreference = 'None' + foreach ($name in $Assignee) + { + $userNames += $name + } } - if ($PSCmdlet.ShouldProcess($Assignee -join ', ', "Remove assignee(s)")) + end { - $params = @{ - 'UriFragment' = "repos/$OwnerName/$RepositoryName/issues/$Issue/assignees" - 'Body' = (ConvertTo-Json -InputObject $hashBody) - 'Method' = 'Delete' - 'Description' = "Removing assignees from issue $Issue for $RepositoryName" - 'AccessToken' = $AccessToken - 'AcceptHeader' = 'application/vnd.github.symmetra-preview+json' - 'TelemetryEventName' = $MyInvocation.MyCommand.Name - 'TelemetryProperties' = $telemetryProperties - 'NoStatus' = (Resolve-ParameterWithDefaultConfigurationValue -Name NoStatus -ConfigValueName DefaultNoStatus) + Write-InvocationLog + + $elements = Resolve-RepositoryElements + $OwnerName = $elements.ownerName + $RepositoryName = $elements.repositoryName + + $telemetryProperties = @{ + 'OwnerName' = (Get-PiiSafeString -PlainText $OwnerName) + 'RepositoryName' = (Get-PiiSafeString -PlainText $RepositoryName) + 'AssigneeCount' = $Assignee.Count + 'Issue' = (Get-PiiSafeString -PlainText $Issue) + } + + $hashBody = @{ + 'assignees' = $userNames } - return Invoke-GHRestMethod @params + if ($Force -and (-not $Confirm)) + { + $ConfirmPreference = 'None' + } + + if ($PSCmdlet.ShouldProcess($userNames -join ', ', "Remove assignee(s)")) + { + $params = @{ + 'UriFragment' = "repos/$OwnerName/$RepositoryName/issues/$Issue/assignees" + 'Body' = (ConvertTo-Json -InputObject $hashBody) + 'Method' = 'Delete' + 'Description' = "Removing assignees from issue $Issue for $RepositoryName" + 'AccessToken' = $AccessToken + 'AcceptHeader' = $script:symmetraAcceptHeader + 'TelemetryEventName' = $MyInvocation.MyCommand.Name + 'TelemetryProperties' = $telemetryProperties + 'NoStatus' = (Resolve-ParameterWithDefaultConfigurationValue -Name NoStatus -ConfigValueName DefaultNoStatus) + } + + return (Invoke-GHRestMethod @params | Add-GitHubIssueAdditionalProperties) + } } } diff --git a/GitHubBranches.ps1 b/GitHubBranches.ps1 index 3bbb297a..357d5792 100644 --- a/GitHubBranches.ps1 +++ b/GitHubBranches.ps1 @@ -1,7 +1,13 @@ # Copyright (c) Microsoft Corporation. All rights reserved. # Licensed under the MIT License. -function Get-GitHubRepositoryBranch +@{ + GitHubBranchTypeName = 'GitHub.Branch' + }.GetEnumerator() | ForEach-Object { + Set-Variable -Scope Script -Option ReadOnly -Name $_.Key -Value $_.Value + } + +filter Get-GitHubRepositoryBranch { <# .SYNOPSIS @@ -38,22 +44,60 @@ function Get-GitHubRepositoryBranch the background, enabling the command prompt to provide status information. If not supplied here, the DefaultNoStatus configuration property value will be used. + .INPUTS + GitHub.Branch + GitHub.Content + GitHub.Event + GitHub.Issue + GitHub.IssueComment + GitHub.Label + GitHub.Milestone + GitHub.PullRequest + GitHub.Project + GitHub.ProjectCard + GitHub.ProjectColumn + GitHub.Release + GitHub.Repository + .OUTPUTS - [PSCustomObject[]] List of branches within the given repository. + GitHub.Branch + List of branches within the given repository. .EXAMPLE - Get-GitHubRepositoryBranch -OwnerName Microsoft -RepositoryName PowerShellForGitHub + Get-GitHubRepositoryBranch -OwnerName microsoft -RepositoryName PowerShellForGitHub Gets all branches for the specified repository. .EXAMPLE - Get-GitHubRepositoryBranch -Uri 'https://github.com/PowerShell/PowerShellForGitHub' -Name master + $repo = Get-GitHubRepository -OwnerName microsoft -RepositoryName PowerShellForGitHub + $repo | Get-GitHubRepositoryBranch + + Gets all branches for the specified repository. + + .EXAMPLE + Get-GitHubRepositoryBranch -Uri 'https://github.com/PowerShell/PowerShellForGitHub' -BranchName master Gets information only on the master branch for the specified repository. + + .EXAMPLE + $repo = Get-GitHubRepository -OwnerName microsoft -RepositoryName PowerShellForGitHub + $repo | Get-GitHubRepositoryBranch -BranchName master + + Gets information only on the master branch for the specified repository. + + .EXAMPLE + $repo = Get-GitHubRepository -OwnerName microsoft -RepositoryName PowerShellForGitHub + $branch = $repo | Get-GitHubRepositoryBranch -BranchName master + $branch | Get-GitHubRepositoryBranch + + Gets information only on the master branch for the specified repository, and then does it + again. This tries to show some of the different types of objects you can pipe into this + function. #> [CmdletBinding( SupportsShouldProcess, DefaultParameterSetName='Elements')] + [OutputType({$script:GitHubBranchTypeName})] [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSShouldProcess", "", Justification="Methods called within here make use of PSShouldProcess, and the switch is passed on to them inherently.")] [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSReviewUnusedParameter", "", Justification="One or more parameters (like NoStatus) are only referenced by helper methods which get access to it from the stack via Get-Variable -Scope 1.")] [Alias('Get-GitHubBranch')] @@ -66,10 +110,13 @@ function Get-GitHubRepositoryBranch [Parameter( Mandatory, + ValueFromPipelineByPropertyName, ParameterSetName='Uri')] + [Alias('RepositoryUrl')] [string] $Uri, - [string] $Name, + [Parameter(ValueFromPipelineByPropertyName)] + [string] $BranchName, [switch] $ProtectedOnly, @@ -90,7 +137,7 @@ function Get-GitHubRepositoryBranch } $uriFragment = "repos/$OwnerName/$RepositoryName/branches" - if (-not [String]::IsNullOrEmpty($Name)) { $uriFragment = $uriFragment + "/$Name" } + if (-not [String]::IsNullOrEmpty($BranchName)) { $uriFragment = $uriFragment + "/$BranchName" } $getParams = @() if ($ProtectedOnly) { $getParams += 'protected=true' } @@ -104,6 +151,54 @@ function Get-GitHubRepositoryBranch 'NoStatus' = (Resolve-ParameterWithDefaultConfigurationValue -Name NoStatus -ConfigValueName DefaultNoStatus) } - return Invoke-GHRestMethodMultipleResult @params + return (Invoke-GHRestMethodMultipleResult @params | Add-GitHubBranchAdditionalProperties) } +filter Add-GitHubBranchAdditionalProperties +{ +<# + .SYNOPSIS + Adds type name and additional properties to ease pipelining to GitHub Branch objects. + + .PARAMETER InputObject + The GitHub object to add additional properties to. + + .PARAMETER TypeName + The type that should be assigned to the object. + + .INPUTS + [PSCustomObject] + + .OUTPUTS + GitHub.Branch +#> + [CmdletBinding()] + [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSUseSingularNouns", "", Justification="Internal helper that is definitely adding more than one property.")] + param( + [Parameter( + Mandatory, + ValueFromPipeline)] + [AllowNull()] + [AllowEmptyCollection()] + [PSCustomObject[]] $InputObject, + + [ValidateNotNullOrEmpty()] + [string] $TypeName = $script:GitHubBranchTypeName + ) + + foreach ($item in $InputObject) + { + $item.PSObject.TypeNames.Insert(0, $TypeName) + + if (-not (Get-GitHubConfiguration -Name DisablePipelineSupport)) + { + $elements = Split-GitHubUri -Uri $item.commit.url + $repositoryUrl = Join-GitHubUri @elements + Add-Member -InputObject $item -Name 'RepositoryUrl' -Value $repositoryUrl -MemberType NoteProperty -Force + + Add-Member -InputObject $item -Name 'BranchName' -Value $item.name -MemberType NoteProperty -Force + } + + Write-Output $item + } +} diff --git a/GitHubConfiguration.ps1 b/GitHubConfiguration.ps1 index c66c4b77..0320f399 100644 --- a/GitHubConfiguration.ps1 +++ b/GitHubConfiguration.ps1 @@ -95,6 +95,13 @@ function Set-GitHubConfiguration Specify this switch to disable the hashing of potential PII data prior to submitting the data to telemetry (if telemetry hasn't been disabled via DisableTelemetry). + .PARAMETER DisablePipelineSupport + By default, this module will modify all objects returned by the API calls by adding + additional, consistent properties to those objects which ease pipelining those objects + into other functions. This is highly convenient functionality. You would only want to + disable this functionality if you are experiencing some edge case problems and are awaiting + a proper fix. + .PARAMETER DisableSmarterObjects By default, this module will modify all objects returned by the API calls to update any properties that can be converted to objects (like strings for Date/Time's being @@ -189,6 +196,8 @@ function Set-GitHubConfiguration [switch] $DisablePiiProtection, + [switch] $DisablePipelineSupport, + [switch] $DisableSmarterObjects, [switch] $DisableTelemetry, @@ -279,6 +288,7 @@ function Get-GitHubConfiguration 'DefaultRepositoryName', 'DisableLogging', 'DisablePiiProtection', + 'DisablePipelineSupport', 'DisableSmarterObjects', 'DisableTelemetry', 'DisableUpdateCheck', @@ -338,7 +348,8 @@ function Save-GitHubConfiguration if (($null -ne $ev) -and ($ev.Count -gt 0)) { - Write-Log -Message "Failed to persist these updated settings to disk. They will remain for this PowerShell session only." -Level Warning -Exception $ev[0] + $message = "Failed to persist these updated settings to disk. They will remain for this PowerShell session only." + Write-Log -Message $message -Level Warning -Exception $ev[0] } } @@ -449,7 +460,8 @@ function Resolve-PropertyValue } else { - Write-Log "The locally cached $Name configuration was not of type $Type. Reverting to default value." -Level Warning + $message = "The locally cached $Name configuration was not of type $Type. Reverting to default value." + Write-Log -Message $message -Level Warning return $DefaultValue } } @@ -485,7 +497,8 @@ function Reset-GitHubConfiguration Deletes the local configuration file and loads in all default configuration values. .NOTES - This command will not clear your authentication token. Please use Clear-GitHubAuthentication to accomplish that. + This command will not clear your authentication token. + Please use Clear-GitHubAuthentication to accomplish that. #> [CmdletBinding(SupportsShouldProcess)] param( @@ -503,13 +516,15 @@ function Reset-GitHubConfiguration if (($null -ne $ev) -and ($ev.Count -gt 0) -and ($ev[0].FullyQualifiedErrorId -notlike 'PathNotFound*')) { - Write-Log -Message "Reset was unsuccessful. Experienced a problem trying to remove the file [$script:configurationFilePath]." -Level Warning -Exception $ev[0] + $message = "Reset was unsuccessful. Experienced a problem trying to remove the file [$script:configurationFilePath]." + Write-Log -Message $message -Level Warning -Exception $ev[0] } } Initialize-GitHubConfiguration - Write-Log -Message "This has not cleared your authentication token. Call Clear-GitHubAuthentication to accomplish that." -Level Verbose + $message = "This has not cleared your authentication token. Call Clear-GitHubAuthentication to accomplish that." + Write-Log -Message $message -Level Verbose } function Read-GitHubConfiguration @@ -555,7 +570,8 @@ function Read-GitHubConfiguration } catch { - Write-Log -Message 'The configuration file for this module is in an invalid state. Use Reset-GitHubConfiguration to recover.' -Level Warning + $message = 'The configuration file for this module is in an invalid state. Use Reset-GitHubConfiguration to recover.' + Write-Log -Message $message -Level Warning } } @@ -613,6 +629,7 @@ function Import-GitHubConfiguration 'applicationInsightsKey' = '66d83c52-3070-489b-886b-09860e05e78a' 'disableLogging' = ([String]::IsNullOrEmpty($logPath)) 'disablePiiProtection' = $false + 'disablePipelineSupport' = $false 'disableSmarterObjects' = $false 'disableTelemetry' = $false 'disableUpdateCheck' = $false @@ -628,11 +645,13 @@ function Import-GitHubConfiguration 'suppressTelemetryReminder' = $false 'webRequestTimeoutSec' = 0 - # This hash is generated by using Helper.ps1's Get-Sha512Hash in Tests/Config/Settings.ps1 like so: + # This hash is generated by using Helper.ps1's Get-Sha512Hash in Tests/Config/Settings.ps1 + # like so: # . ./Helpers.ps1; Get-Sha512Hash -PlainText (Get-Content -Path ./Tests/Config/Settings.ps1 -Raw -Encoding Utf8) - # The hash is used to identify if the user has made changes to the config file prior to running the UT's locally. - # It intentionally cannot be modified via Set-GitHubConfiguration and must be updated directly in the - # source code here should the default Settings.ps1 file ever be changed. + # The hash is used to identify if the user has made changes to the config file prior to + # running the UT's locally. It intentionally cannot be modified via Set-GitHubConfiguration + # and must be updated directly in the source code here should the default Settings.ps1 file + # ever be changed. 'testConfigSettingsHash' = '272EE14CED396100A7AFD23EA21CA262470B7F4D80E47B7ABD90508B86210775F020EEF79D322F4C22A53835F700E1DFD13D0509C1D08DD6F9771B3F0133EDAB' } @@ -664,7 +683,7 @@ function Backup-GitHubConfiguration The path to store the user's current configuration file. .PARAMETER Force - If specified, will overwrite the contents of any file with the same name at th + If specified, will overwrite the contents of any file with the same name at the location specified by Path. .EXAMPLE @@ -717,7 +736,9 @@ function Restore-GitHubConfiguration [CmdletBinding(SupportsShouldProcess)] [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSShouldProcess", "", Justification="Methods called within here make use of PSShouldProcess, and the switch is passed on to them inherently.")] param( - [ValidateScript({if (Test-Path -Path $_ -PathType Leaf) { $true } else { throw "$_ does not exist." }})] + [ValidateScript({ + if (Test-Path -Path $_ -PathType Leaf) { $true } + else { throw "$_ does not exist." }})] [string] $Path ) @@ -924,7 +945,8 @@ function Clear-GitHubAuthentication Clears out any GitHub API token from memory, as well as from local file storage. .NOTES - This command will not clear your configuration settings. Please use Reset-GitHubConfiguration to accomplish that. + This command will not clear your configuration settings. + Please use Reset-GitHubConfiguration to accomplish that. #> [CmdletBinding(SupportsShouldProcess)] param( @@ -946,14 +968,18 @@ function Clear-GitHubAuthentication { Remove-Item -Path $script:accessTokenFilePath -Force -ErrorAction SilentlyContinue -ErrorVariable ev - if (($null -ne $ev) -and ($ev.Count -gt 0) -and ($ev[0].FullyQualifiedErrorId -notlike 'PathNotFound*')) + if (($null -ne $ev) -and + ($ev.Count -gt 0) -and + ($ev[0].FullyQualifiedErrorId -notlike 'PathNotFound*')) { - Write-Log -Message "Experienced a problem trying to remove the file that persists the Access Token [$script:accessTokenFilePath]." -Level Warning -Exception $ev[0] + $message = "Experienced a problem trying to remove the file that persists the Access Token [$script:accessTokenFilePath]." + Write-Log -Message $message -Level Warning -Exception $ev[0] } } } - Write-Log -Message "This has not cleared your configuration settings. Call Reset-GitHubConfiguration to accomplish that." -Level Verbose + $message = "This has not cleared your configuration settings. Call Reset-GitHubConfiguration to accomplish that." + Write-Log -Message $message -Level Verbose } function Get-AccessToken @@ -1010,19 +1036,22 @@ function Get-AccessToken { $secureString = $content | ConvertTo-SecureString - Write-Log -Message "Restoring Access Token from file. This value can be cleared in the future by calling Clear-GitHubAuthentication." -Level Verbose + $message = "Restoring Access Token from file. This value can be cleared in the future by calling Clear-GitHubAuthentication." + Write-Log -Message $messsage -Level Verbose $script:accessTokenCredential = New-Object System.Management.Automation.PSCredential "", $secureString return $script:accessTokenCredential.GetNetworkCredential().Password } catch { - Write-Log -Message 'The Access Token file for this module contains an invalid SecureString (files can''t be shared by users or computers). Use Set-GitHubAuthentication to update it.' -Level Warning + $message = 'The Access Token file for this module contains an invalid SecureString (files can''t be shared by users or computers). Use Set-GitHubAuthentication to update it.' + Write-Log -Message $message -Level Warning } } if (-not [String]::IsNullOrEmpty($global:gitHubApiToken)) { - Write-Log -Message 'Storing the Access Token in `$global:gitHubApiToken` is insecure and is no longer recommended. To cache your Access Token for use across future PowerShell sessions, please use Set-GitHubAuthentication instead.' -Level Warning + $message = 'Storing the Access Token in `$global:gitHubApiToken` is insecure and is no longer recommended. To cache your Access Token for use across future PowerShell sessions, please use Set-GitHubAuthentication instead.' + Write-Log -Message $message -Level Warning return $global:gitHubApiToken } @@ -1030,7 +1059,8 @@ function Get-AccessToken (-not $script:seenTokenWarningThisSession)) { $script:seenTokenWarningThisSession = $true - Write-Log -Message 'This module has not yet been configured with a personal GitHub Access token. The module can still be used, but GitHub will limit your usage to 60 queries per hour. You can get a GitHub API token from https://github.com/settings/tokens/new (provide a description and check any appropriate scopes).' -Level Warning + $message = 'This module has not yet been configured with a personal GitHub Access token. The module can still be used, but GitHub will limit your usage to 60 queries per hour. You can get a GitHub API token from https://github.com/settings/tokens/new (provide a description and check any appropriate scopes).' + Write-Log -Message $message -Level Warning } return $null diff --git a/GitHubContents.ps1 b/GitHubContents.ps1 index fce4b6c4..1514382e 100644 --- a/GitHubContents.ps1 +++ b/GitHubContents.ps1 @@ -1,4 +1,10 @@ -function Get-GitHubContent +@{ + GitHubContentTypeName = 'GitHub.Content' + }.GetEnumerator() | ForEach-Object { + Set-Variable -Scope Script -Option ReadOnly -Name $_.Key -Value $_.Value + } + + filter Get-GitHubContent { <# .SYNOPSIS @@ -26,14 +32,19 @@ function Get-GitHubContent .PARAMETER MediaType The format in which the API will return the body of the issue. - Object - Return a json object representation a file or folder. This is the default if you do not pass any specific media type. - Raw - Return the raw contents of a file. - Html - For markup files such as Markdown or AsciiDoc, you can retrieve the rendered HTML using the Html media type. + + Object - Return a json object representation a file or folder. + This is the default if you do not pass any specific media type. + Raw - Return the raw contents of a file. + Html - For markup files such as Markdown or AsciiDoc, + you can retrieve the rendered HTML using the Html media type. .PARAMETER ResultAsString - If this switch is specified and the MediaType is either Raw or Html then the resulting bytes will be decoded the result will be - returned as a string instead of bytes. If the MediaType is Object, then an additional property on the object is returned 'contentAsString' - which will be the decoded base64 result as a string. + If this switch is specified and the MediaType is either Raw or Html then the + resulting bytes will be decoded the result will be returned as a string instead of bytes. + If the MediaType is Object, then an additional property on the object named + 'contentAsString' will be included and its value will be the decoded base64 result + as a string. .PARAMETER AccessToken If provided, this will be used as the AccessToken for authentication with the @@ -45,6 +56,25 @@ function Get-GitHubContent the background, enabling the command prompt to provide status information. If not supplied here, the DefaultNoStatus configuration property value will be used. + .INPUTS + GitHub.Branch + GitHub.Content + GitHub.Event + GitHub.Issue + GitHub.IssueComment + GitHub.Label + GitHub.Milestone + GitHub.PullRequest + GitHub.Project + GitHub.ProjectCard + GitHub.ProjectColumn + GitHub.Release + GitHub.Repository + + .OUTPUTS + [String] + GitHub.Content + .EXAMPLE Get-GitHubContent -OwnerName microsoft -RepositoryName PowerShellForGitHub -Path README.md -MediaType Html @@ -59,21 +89,39 @@ function Get-GitHubContent Get-GitHubContent -OwnerName microsoft -RepositoryName PowerShellForGitHub -Path Tests List the files within the "Tests" path of the repository + + .EXAMPLE + $repo = Get-GitHubRepository -OwnerName microsoft -RepositoryName PowerShellForGitHub + $repo | Get-GitHubContent -Path Tests + + List the files within the "Tests" path of the repository + + .NOTES + Unable to specify Path as ValueFromPipeline because a Repository object may be incorrectly + coerced into a string used for Path, thus confusing things. #> [CmdletBinding( SupportsShouldProcess, DefaultParameterSetName = 'Elements')] + [OutputType([String])] + [OutputType({$script:GitHubContentTypeName})] [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSShouldProcess", "", Justification = "Methods called within here make use of PSShouldProcess, and the switch is passed on to them inherently.")] param( - [Parameter(Mandatory, ParameterSetName = 'Elements')] + [Parameter( + Mandatory, + ParameterSetName = 'Elements')] [string] $OwnerName, - [Parameter(Mandatory, ParameterSetName = 'Elements')] + [Parameter( + Mandatory, + ParameterSetName = 'Elements')] [string] $RepositoryName, [Parameter( Mandatory, + ValueFromPipelineByPropertyName, ParameterSetName='Uri')] + [Alias('RepositoryUrl')] [string] $Uri, [string] $Path, @@ -141,5 +189,57 @@ function Get-GitHubContent } } + if ($MediaType -eq 'Object') + { + $null = $result | Add-GitHubContentAdditionalProperties + } + return $result } + +filter Add-GitHubContentAdditionalProperties +{ +<# + .SYNOPSIS + Adds type name and additional properties to ease pipelining to GitHub Content objects. + + .PARAMETER InputObject + The GitHub object to add additional properties to. + + .PARAMETER TypeName + The type that should be assigned to the object. + + .INPUTS + [PSCustomObject] + + .OUTPUTS + GitHub.Content +#> + [CmdletBinding()] + [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSUseSingularNouns", "", Justification="Internal helper that is definitely adding more than one property.")] + param( + [Parameter( + Mandatory, + ValueFromPipeline)] + [AllowNull()] + [AllowEmptyCollection()] + [PSCustomObject[]] $InputObject, + + [ValidateNotNullOrEmpty()] + [string] $TypeName = $script:GitHubContentTypeName + ) + + foreach ($item in $InputObject) + { + $item.PSObject.TypeNames.Insert(0, $TypeName) + + if (-not (Get-GitHubConfiguration -Name DisablePipelineSupport)) + { + $elements = Split-GitHubUri -Uri $item.url + $repositoryUrl = Join-GitHubUri @elements + Add-Member -InputObject $item -Name 'RepositoryUrl' -Value $repositoryUrl -MemberType NoteProperty -Force + } + + Write-Output $item + } +} \ No newline at end of file diff --git a/GitHubCore.ps1 b/GitHubCore.ps1 index 4a095b65..542ae5a8 100644 --- a/GitHubCore.ps1 +++ b/GitHubCore.ps1 @@ -4,15 +4,21 @@ @{ defaultAcceptHeader = 'application/vnd.github.v3+json' mediaTypeVersion = 'v3' - squirrelAcceptHeader = 'application/vnd.github.squirrel-girl-preview' - symmetraAcceptHeader = 'application/vnd.github.symmetra-preview+json' - mercyAcceptHeader = 'application/vnd.github.mercy-preview+json' - nebulaAcceptHeader = 'application/vnd.github.nebula-preview+json' baptisteAcceptHeader = 'application/vnd.github.baptiste-preview+json' - scarletWitchAcceptHeader = 'application/vnd.github.scarlet-witch-preview+json' dorianAcceptHeader = 'application/vnd.github.dorian-preview+json' + hagarAcceptHeader = 'application/vnd.github.hagar-preview+json' + hellcatAcceptHeader = 'application/vnd.github.hellcat-preview+json' + inertiaAcceptHeader = 'application/vnd.github.inertia-preview+json' londonAcceptHeader = 'application/vnd.github.london-preview+json' - + machineManAcceptHeader = 'application/vnd.github.machine-man-preview' + mercyAcceptHeader = 'application/vnd.github.mercy-preview+json' + mockingbirdAcceptHeader = 'application/vnd.github.mockingbird-preview' + nebulaAcceptHeader = 'application/vnd.github.nebula-preview+json' + sailorVAcceptHeader = 'application/vnd.github.sailor-v-preview+json' + scarletWitchAcceptHeader = 'application/vnd.github.scarlet-witch-preview+json' + squirrelGirlAcceptHeader = 'application/vnd.github.squirrel-girl-preview' + starfoxAcceptHeader = 'application/vnd.github.starfox-preview+json' + symmetraAcceptHeader = 'application/vnd.github.symmetra-preview+json' }.GetEnumerator() | ForEach-Object { Set-Variable -Scope Script -Option ReadOnly -Name $_.Key -Value $_.Value } @@ -97,8 +103,8 @@ function Invoke-GHRestMethod no additional status shown to the user until a response is returned from the REST request. .NOTES - This wraps Invoke-WebRequest as opposed to Invoke-RestMethod because we want access to the headers - that are returned in the response, and Invoke-RestMethod drops those headers. + This wraps Invoke-WebRequest as opposed to Invoke-RestMethod because we want access + to the headers that are returned in the response, and Invoke-RestMethod drops those headers. #> [CmdletBinding(SupportsShouldProcess)] param( @@ -239,7 +245,15 @@ function Invoke-GHRestMethod $jobName = "Invoke-GHRestMethod-" + (Get-Date).ToFileTime().ToString() [scriptblock]$scriptBlock = { - param($Url, $Method, $Headers, $Body, $ValidBodyContainingRequestMethods, $TimeoutSec, $LogRequestBody, $ScriptRootPath) + param( + $Url, + $Method, + $Headers, + $Body, + $ValidBodyContainingRequestMethods, + $TimeoutSec, + $LogRequestBody, + $ScriptRootPath) # We need to "dot invoke" Helpers.ps1 and GitHubConfiguration.ps1 within # the context of this script block since we're running in a different @@ -341,7 +355,8 @@ function Invoke-GHRestMethod } catch [ArgumentException] { - # The content must not be JSON (which is a legitimate situation). We'll return the raw content result instead. + # The content must not be JSON (which is a legitimate situation). + # We'll return the raw content result instead. # We do this unnecessary assignment to avoid PSScriptAnalyzer's PSAvoidUsingEmptyCatchBlock. $finalResult = $finalResult } @@ -353,7 +368,9 @@ function Invoke-GHRestMethod # a lot of time. Let's optimize here by not bothering to send in something that we # know is definitely not convertible ([int32] on PS5, [long] on PS7). if (($finalResult -isnot [Object[]]) -or - (($finalResult.Count -gt 0) -and ($finalResult[0] -isnot [int]) -and ($finalResult[0] -isnot [long]))) + (($finalResult.Count -gt 0) -and + ($finalResult[0] -isnot [int]) -and + ($finalResult[0] -isnot [long]))) { $finalResult = ConvertTo-SmarterObject -InputObject $finalResult } @@ -418,8 +435,9 @@ function Invoke-GHRestMethod } catch { - # We only know how to handle WebExceptions, which will either come in "pure" when running with -NoStatus, - # or will come in as a RemoteException when running normally (since it's coming from the asynchronous Job). + # We only know how to handle WebExceptions, which will either come in "pure" + # when running with -NoStatus, or will come in as a RemoteException when running + # normally (since it's coming from the asynchronous Job). $ex = $null $message = $null $statusCode = $null @@ -524,7 +542,9 @@ function Invoke-GHRestMethod if ($statusCode -eq 404) { - $output += "This typically happens when the current user isn't properly authenticated. You may need an Access Token with additional scopes checked." + $explanation = @('This typically happens when the current user isn''t properly authenticated.', + 'You may need an Access Token with additional scopes checked.') + $output += ($explanation -join ' ') } if (-not [String]::IsNullOrEmpty($requestId)) @@ -701,7 +721,7 @@ function Invoke-GHRestMethodMultipleResult } } -function Split-GitHubUri +filter Split-GitHubUri { <# .SYNOPSIS @@ -723,28 +743,39 @@ function Split-GitHubUri .PARAMETER RepositoryName Returns the Repository Name from the Uri if it can be identified. + .INPUTS + [String] + .OUTPUTS [PSCustomObject] - The OwnerName and RepositoryName elements from the provided URL .EXAMPLE - Split-GitHubUri -Uri 'https://github.com/PowerShell/PowerShellForGitHub' + Split-GitHubUri -Uri 'https://github.com/microsoft/PowerShellForGitHub' PowerShellForGitHub .EXAMPLE - Split-GitHubUri -Uri 'https://github.com/PowerShell/PowerShellForGitHub' -RepositoryName + Split-GitHubUri -Uri 'https://github.com/microsoft/PowerShellForGitHub' -RepositoryName PowerShellForGitHub .EXAMPLE - Split-GitHubUri -Uri 'https://github.com/PowerShell/PowerShellForGitHub' -OwnerName + Split-GitHubUri -Uri 'https://github.com/microsoft/PowerShellForGitHub' -OwnerName + + microsoft + + .EXAMPLE + Split-GitHubUri -Uri 'https://github.com/microsoft/PowerShellForGitHub' - PowerShell + @{'ownerName' = 'microsoft'; 'repositoryName' = 'PowerShellForGitHub'} #> [CmdletBinding(DefaultParameterSetName='RepositoryName')] + [OutputType([Hashtable])] param ( - [Parameter(Mandatory)] + [Parameter( + Mandatory, + ValueFromPipeline)] [ValidateNotNullOrEmpty()] [string] $Uri, @@ -776,10 +807,50 @@ function Split-GitHubUri { return $components.ownerName } - elseif ($RepositoryName -or ($PSCmdlet.ParameterSetName -eq 'RepositoryName')) + elseif ($RepositoryName) { return $components.repositoryName } + else + { + return $components + } +} + +function Join-GitHubUri +{ +<# + .SYNOPSIS + Combines the provided repository elements into a repository URL. + + .DESCRIPTION + Combines the provided repository elements into a repository URL. + + The Git repo for this module can be found here: http://aka.ms/PowerShellForGitHub + + .PARAMETER OwnerName + Owner of the repository. + + .PARAMETER RepositoryName + Name of the repository. + + .OUTPUTS + [String] - The repository URL. +#> + [CmdletBinding()] + [OutputType([String])] + param + ( + [Parameter(Mandatory)] + [string] $OwnerName, + + [Parameter(Mandatory)] + [string] $RepositoryName + ) + + + $hostName = (Get-GitHubConfiguration -Name 'ApiHostName') + return "https://$hostName/$OwnerName/$RepositoryName" } function Resolve-RepositoryElements @@ -892,6 +963,12 @@ filter ConvertTo-SmarterObject .PARAMETER InputObject The object to update + + .INPUTS + [object] + + .OUTPUTS + [object] #> [CmdletBinding()] param( @@ -937,7 +1014,8 @@ filter ConvertTo-SmarterObject } catch { - Write-Log -Message "Unable to convert $($property.Name) value of $($property.Value) to a [DateTime] object. Leaving as-is." -Level Verbose + $message = "Unable to convert $($property.Name) value of $($property.Value) to a [DateTime] object. Leaving as-is." + Write-Log -Message $message -Level Verbose } } @@ -970,10 +1048,15 @@ function Get-MediaAcceptHeader .PARAMETER MediaType The format in which the API will return the body of the comment or issue. - Raw - Return the raw markdown body. Response will include body. This is the default if you do not pass any specific media type. - Text - Return a text only representation of the markdown body. Response will include body_text. - Html - Return HTML rendered from the body's markdown. Response will include body_html. - Full - Return raw, text and HTML representations. Response will include body, body_text, and body_html. + Raw - Return the raw markdown body. + Response will include body. + This is the default if you do not pass any specific media type. + Text - Return a text only representation of the markdown body. + Response will include body_text. + Html - Return HTML rendered from the body's markdown. + Response will include body_html. + Full - Return raw, text and HTML representations. + Response will include body, body_text, and body_html. Object - Return a json object representation a file or folder. .PARAMETER AsJson @@ -982,12 +1065,16 @@ function Get-MediaAcceptHeader .PARAMETER AcceptHeader The accept header that should be included with the MediaType accept header. + .OUTPUTS + [String] + .EXAMPLE Get-MediaAcceptHeader -MediaType Raw Returns a formatted AcceptHeader for v3 of the response object #> [CmdletBinding()] + [OutputType([String])] param( [ValidateSet('Raw', 'Text', 'Html', 'Full', 'Object')] [string] $MediaType = 'Raw', diff --git a/GitHubEvents.ps1 b/GitHubEvents.ps1 index 7dae9bc9..c904bc6a 100644 --- a/GitHubEvents.ps1 +++ b/GitHubEvents.ps1 @@ -1,7 +1,13 @@ # Copyright (c) Microsoft Corporation. All rights reserved. # Licensed under the MIT License. -function Get-GitHubEvent +@{ + GitHubEventTypeName = 'GitHub.Event' + }.GetEnumerator() | ForEach-Object { + Set-Variable -Scope Script -Option ReadOnly -Name $_.Key -Value $_.Value + } + +filter Get-GitHubEvent { <# .DESCRIPTION @@ -22,11 +28,13 @@ function Get-GitHubEvent The OwnerName and RepositoryName will be extracted from here instead of needing to provide them individually. - .PARAMETER EventID - The ID of a specific event to get. If not supplied, will return back all events for this repository. + .PARAMETER EventId + The ID of a specific event to get. + If not supplied, will return back all events for this repository. .PARAMETER Issue - Issue number to get events for. If not supplied, will return back all events for this repository. + Issue number to get events for. + If not supplied, will return back all events for this repository. .PARAMETER AccessToken If provided, this will be used as the AccessToken for authentication with the @@ -38,37 +46,91 @@ function Get-GitHubEvent the background, enabling the command prompt to provide status information. If not supplied here, the DefaultNoStatus configuration property value will be used. + .INPUTS + GitHub.Branch + GitHub.Content + GitHub.Event + GitHub.Issue + GitHub.IssueComment + GitHub.Label + GitHub.Milestone + GitHub.PullRequest + GitHub.Project + GitHub.ProjectCard + GitHub.ProjectColumn + GitHub.Release + GitHub.Repository + + .OUTPUTS + GitHub.Event + .EXAMPLE - Get-GitHubEvent -OwnerName Microsoft -RepositoryName PowerShellForGitHub + Get-GitHubEvent -OwnerName microsoft -RepositoryName PowerShellForGitHub - Get the events for the Microsoft\PowerShellForGitHub project. + Get the events for the microsoft\PowerShellForGitHub project. #> [CmdletBinding( SupportsShouldProcess, DefaultParameterSetName='RepositoryElements')] + [OutputType({$script:GitHubEventTypeName})] [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSShouldProcess", "", Justification="Methods called within here make use of PSShouldProcess, and the switch is passed on to them inherently.")] param( - [Parameter(Mandatory, ParameterSetName='RepositoryElements')] - [Parameter(Mandatory, ParameterSetName='IssueElements')] - [Parameter(Mandatory, ParameterSetName='EventElements')] + [Parameter( + Mandatory, + ParameterSetName='RepositoryElements')] + [Parameter( + Mandatory, + ParameterSetName='IssueElements')] + [Parameter( + Mandatory, + ParameterSetName='EventElements')] [string] $OwnerName, - [Parameter(Mandatory, ParameterSetName='RepositoryElements')] - [Parameter(Mandatory, ParameterSetName='IssueElements')] - [Parameter(Mandatory, ParameterSetName='EventElements')] + [Parameter( + Mandatory, + ParameterSetName='RepositoryElements')] + [Parameter( + Mandatory, + ParameterSetName='IssueElements')] + [Parameter( + Mandatory, + ParameterSetName='EventElements')] [string] $RepositoryName, - [Parameter(Mandatory, ParameterSetName='RepositoryUri')] - [Parameter(Mandatory, ParameterSetName='IssueUri')] - [Parameter(Mandatory, ParameterSetName='EventUri')] + [Parameter( + Mandatory, + ValueFromPipelineByPropertyName, + ParameterSetName='RepositoryUri')] + [Parameter( + Mandatory, + ValueFromPipelineByPropertyName, + ParameterSetName='IssueUri')] + [Parameter( + Mandatory, + ValueFromPipelineByPropertyName, + ParameterSetName='EventUri')] + [Alias('RepositoryUrl')] [string] $Uri, - [Parameter(Mandatory, ParameterSetName='EventUri')] - [Parameter(Mandatory, ParameterSetName='EventElements')] - [int64] $EventID, + [Parameter( + Mandatory, + ValueFromPipelineByPropertyName, + ParameterSetName='EventUri')] + [Parameter( + Mandatory, + ValueFromPipelineByPropertyName, + ParameterSetName='EventElements')] + [int64] $EventId, - [Parameter(Mandatory, ParameterSetName='IssueUri')] - [Parameter(Mandatory, ParameterSetName='IssueElements')] + [Parameter( + Mandatory, + ValueFromPipelineByPropertyName, + ParameterSetName='IssueUri')] + [Parameter( + Mandatory, + ValueFromPipelineByPropertyName, + ParameterSetName='IssueElements')] + [Alias('IssueNumber')] [int64] $Issue, [string] $AccessToken, @@ -86,16 +148,16 @@ function Get-GitHubEvent 'OwnerName' = (Get-PiiSafeString -PlainText $OwnerName) 'RepositoryName' = (Get-PiiSafeString -PlainText $RepositoryName) 'ProvidedIssue' = $PSBoundParameters.ContainsKey('Issue') - 'ProvidedEvent' = $PSBoundParameters.ContainsKey('EventID') + 'ProvidedEvent' = $PSBoundParameters.ContainsKey('EventId') } $uriFragment = "repos/$OwnerName/$RepositoryName/issues/events" $description = "Getting events for $RepositoryName" - if ($PSBoundParameters.ContainsKey('EventID')) + if ($PSBoundParameters.ContainsKey('EventId')) { - $uriFragment = "repos/$OwnerName/$RepositoryName/issues/events/$EventID" - $description = "Getting event $EventID for $RepositoryName" + $uriFragment = "repos/$OwnerName/$RepositoryName/issues/events/$EventId" + $description = "Getting event $EventId for $RepositoryName" } elseif ($PSBoundParameters.ContainsKey('Issue')) { @@ -104,10 +166,10 @@ function Get-GitHubEvent } $acceptHeaders = @( - 'application/vnd.github.starfox-preview+json', - 'application/vnd.github.sailer-v-preview+json', - 'application/vnd.github.symmetra-preview+json', - 'application/vnd.github.machine-man-preview') + $script:starfoxAcceptHeader, + $script:sailorVAcceptHeader, + $script:symmetraAcceptHeader, + $script:machineManAcceptHeader) $params = @{ 'UriFragment' = $uriFragment @@ -119,5 +181,134 @@ function Get-GitHubEvent 'NoStatus' = (Resolve-ParameterWithDefaultConfigurationValue -Name NoStatus -ConfigValueName DefaultNoStatus) } - return Invoke-GHRestMethodMultipleResult @params + return (Invoke-GHRestMethodMultipleResult @params | Add-GitHubEventAdditionalProperties) +} + +filter Add-GitHubEventAdditionalProperties +{ +<# + .SYNOPSIS + Adds type name and additional properties to ease pipelining to GitHub Event objects. + + .PARAMETER InputObject + The GitHub object to add additional properties to. + + .PARAMETER TypeName + The type that should be assigned to the object. + + .INPUTS + [PSCustomObject] + + .OUTPUTS + GitHub.Event +#> + [CmdletBinding()] + [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSUseSingularNouns", "", Justification="Internal helper that is definitely adding more than one property.")] + param( + [Parameter( + Mandatory, + ValueFromPipeline)] + [AllowNull()] + [AllowEmptyCollection()] + [PSCustomObject[]] $InputObject, + + [ValidateNotNullOrEmpty()] + [string] $TypeName = $script:GitHubEventTypeName + ) + + foreach ($item in $InputObject) + { + $item.PSObject.TypeNames.Insert(0, $TypeName) + + if (-not (Get-GitHubConfiguration -Name DisablePipelineSupport)) + { + $elements = Split-GitHubUri -Uri $item.url + $repositoryUrl = Join-GitHubUri @elements + Add-Member -InputObject $item -Name 'RepositoryUrl' -Value $repositoryUrl -MemberType NoteProperty -Force + Add-Member -InputObject $item -Name 'EventId' -Value $item.id -MemberType NoteProperty -Force + + @('actor', 'assignee', 'assigner', 'assignees', 'committer', 'requested_reviewer', 'review_requester', 'user') | + ForEach-Object { + if ($null -ne $item.$_) + { + $null = Add-GitHubUserAdditionalProperties -InputObject $item.$_ + } + } + + if ($null -ne $item.issue) + { + $null = Add-GitHubIssueAdditionalProperties -InputObject $item.issue + Add-Member -InputObject $item -Name 'IssueId' -Value $item.issue.id -MemberType NoteProperty -Force + Add-Member -InputObject $item -Name 'IssueNumber' -Value $item.issue.number -MemberType NoteProperty -Force + } + + if ($null -ne $item.label) + { + $null = Add-GitHubLabelAdditionalProperties -InputObject $item.label + } + + if ($null -ne $item.labels) + { + $null = Add-GitHubLabelAdditionalProperties -InputObject $item.labels + } + + if ($null -ne $item.milestone) + { + $null = Add-GitHubMilestoneAdditionalProperties -InputObject $item.milestone + } + + if ($null -ne $item.project_id) + { + Add-Member -InputObject $item -Name 'ProjectId' -Value $item.project_id -MemberType NoteProperty -Force + } + + if ($null -ne $item.project_card) + { + $null = Add-GitHubProjectCardAdditionalProperties -InputObject $item.project_card + Add-Member -InputObject $item -Name 'CardId' -Value $item.project_card.id -MemberType NoteProperty -Force + } + + if ($null -ne $item.column_name) + { + Add-Member -InputObject $item -Name 'ColumnName' -Value $item.column_name -MemberType NoteProperty -Force + } + + if ($null -ne $item.source) + { + $null = Add-GitHubIssueAdditionalProperties -InputObject $item.source + if ($item.source.PSObject.TypeNames[0] -eq 'GitHub.PullRequest') + { + Add-Member -InputObject $item -Name 'PullRequestId' -Value $item.source.id -MemberType NoteProperty -Force + Add-Member -InputObject $item -Name 'PullRequestNumber' -Value $item.source.number -MemberType NoteProperty -Force + } + else + { + Add-Member -InputObject $item -Name 'IssueId' -Value $item.source.id -MemberType NoteProperty -Force + Add-Member -InputObject $item -Name 'IssueNumber' -Value $item.source.number -MemberType NoteProperty -Force + } + } + + if ($item.issue_url -match '^.*/issues/(\d+)$') + { + $issueNumber = $Matches[1] + Add-Member -InputObject $item -Name 'IssueNumber' -Value $issueNumber -MemberType NoteProperty -Force + } + + if ($item.pull_request_url -match '^.*/pull/(\d+)$') + { + $pullRequestNumber = $Matches[1] + Add-Member -InputObject $item -Name 'PullRequestNumber' -Value $pullRequestNumber -MemberType NoteProperty -Force + } + + if ($null -ne $item.dismissed_review) + { + # TODO: Add dismissed_review (object) and dismissed_review[review_id] once Reviews are supported + + # $null = Add-GitHubPullRequestReviewAdditionalProperties -InputObject $item.dismissed_review + # Add-Member -InputObject $item -Name 'ReviewId' -Value $item.dismissed_review.review_id -MemberType NoteProperty -Force + } + } + + Write-Output $item + } } diff --git a/GitHubComments.ps1 b/GitHubIssueComments.ps1 similarity index 58% rename from GitHubComments.ps1 rename to GitHubIssueComments.ps1 index ce8ac615..a388db93 100644 --- a/GitHubComments.ps1 +++ b/GitHubIssueComments.ps1 @@ -1,11 +1,18 @@ # Copyright (c) Microsoft Corporation. All rights reserved. # Licensed under the MIT License. -function Get-GitHubComment +@{ + GitHubCommentTypeName = 'GitHub.Comment' + GitHubIssueCommentTypeName = 'GitHub.IssueComment' + }.GetEnumerator() | ForEach-Object { + Set-Variable -Scope Script -Option ReadOnly -Name $_.Key -Value $_.Value + } + +filter Get-GitHubIssueComment { <# .DESCRIPTION - Get the comments for a given Github repository. + Get the Issue comments for a given GitHub repository. The Git repo for this module can be found here: http://aka.ms/PowerShellForGitHub @@ -22,7 +29,7 @@ function Get-GitHubComment The OwnerName and RepositoryName will be extracted from here instead of needing to provide them individually. - .PARAMETER CommentID + .PARAMETER Comment The ID of a specific comment to get. If not supplied, will return back all comments for this repository. .PARAMETER Issue @@ -40,10 +47,15 @@ function Get-GitHubComment .PARAMETER MediaType The format in which the API will return the body of the comment. - Raw - Return the raw markdown body. Response will include body. This is the default if you do not pass any specific media type. - Text - Return a text only representation of the markdown body. Response will include body_text. - Html - Return HTML rendered from the body's markdown. Response will include body_html. - Full - Return raw, text and HTML representations. Response will include body, body_text, and body_html. + Raw - Return the raw markdown body. + Response will include body. + This is the default if you do not pass any specific media type. + Text - Return a text only representation of the markdown body. + Response will include body_text. + Html - Return HTML rendered from the body's markdown. + Response will include body_html. + Full - Return raw, text and HTML representations. + Response will include body, body_text, and body_html. .PARAMETER AccessToken If provided, this will be used as the AccessToken for authentication with the @@ -55,52 +67,136 @@ function Get-GitHubComment the background, enabling the command prompt to provide status information. If not supplied here, the DefaultNoStatus configuration property value will be used. + .INPUTS + GitHub.Branch + GitHub.Content + GitHub.Event + GitHub.Issue + GitHub.IssueComment + GitHub.Label + GitHub.Milestone + GitHub.PullRequest + GitHub.Project + GitHub.ProjectCard + GitHub.ProjectColumn + GitHub.Release + GitHub.Repository + + .OUTPUTS + GitHub.IssueComment + + .EXAMPLE + Get-GitHubIssueComment -OwnerName microsoft -RepositoryName PowerShellForGitHub + + Get all of the Issue comments for the microsoft\PowerShellForGitHub project. + + .EXAMPLE + $repo = Get-GitHubRepository -OwnerName microsoft -RepositoryName PowerShellForGitHub + $repo | Get-GitHubIssueComment -Since ([DateTime]::Now).AddDays(-1) + + Get all of the Issue comments for the microsoft\PowerShellForGitHub project since yesterday. + .EXAMPLE - Get-GitHubComment-OwnerName Microsoft -RepositoryName PowerShellForGitHub + $issue = $repo | Get-GitHubIssueComment -OwnerName microsoft -RepositoryName PowerShellForGitHub -Issue 1 - Get the comments for the Microsoft\PowerShellForGitHub project. + Get the comments Issue #1 in the microsoft\PowerShellForGitHub project. + + .EXAMPLE + $repo = Get-GitHubRepository -OwnerName microsoft -RepositoryName PowerShellForGitHub + $issue = $repo | Get-GitHubIssue -Issue 1 + $issue | Get-GitHubIssueComment + + Get the comments Issue #1 in the microsoft\PowerShellForGitHub project. + + .EXAMPLE + $repo = Get-GitHubRepository -OwnerName microsoft -RepositoryName PowerShellForGitHub + $issue = $repo | Get-GitHubIssue -Issue 1 + $comments = $issue | Get-GitHubIssueComment + $comment[0] | Get-GitHubIssueComment + + Get the most recent comment on Issue #1 in the microsoft\PowerShellForGitHub project by + passing it in via the pipeline. This shows some of the different types of objects you + can pipe into this function. #> [CmdletBinding( SupportsShouldProcess, DefaultParameterSetName='RepositoryElements')] + [Alias('Get-GitHubComment')] # Aliased to avoid a breaking change after v0.14.0 + [OutputType({$script:GitHubIssueCommentTypeName})] [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSShouldProcess", "", Justification="Methods called within here make use of PSShouldProcess, and the switch is passed on to them inherently.")] param( - [Parameter(Mandatory, ParameterSetName='RepositoryElements')] - [Parameter(Mandatory, ParameterSetName='IssueElements')] - [Parameter(Mandatory, ParameterSetName='CommentElements')] + [Parameter( + Mandatory, + ParameterSetName='RepositoryElements')] + [Parameter( + Mandatory, + ParameterSetName='IssueElements')] + [Parameter( + Mandatory, + ParameterSetName='CommentElements')] [string] $OwnerName, - [Parameter(Mandatory, ParameterSetName='RepositoryElements')] - [Parameter(Mandatory, ParameterSetName='IssueElements')] - [Parameter(Mandatory, ParameterSetName='CommentElements')] + [Parameter( + Mandatory, + ParameterSetName='RepositoryElements')] + [Parameter( + Mandatory, + ParameterSetName='IssueElements')] + [Parameter( + Mandatory, + ParameterSetName='CommentElements')] [string] $RepositoryName, - [Parameter(Mandatory, ParameterSetName='RepositoryUri')] - [Parameter(Mandatory, ParameterSetName='IssueUri')] - [Parameter(Mandatory, ParameterSetName='CommentUri')] + [Parameter( + Mandatory, + ValueFromPipelineByPropertyName, + ParameterSetName='RepositoryUri')] + [Parameter( + Mandatory, + ValueFromPipelineByPropertyName, + ParameterSetName='IssueUri')] + [Parameter( + Mandatory, + ValueFromPipelineByPropertyName, + ParameterSetName='CommentUri')] + [Alias('RepositoryUrl')] [string] $Uri, - [Parameter(Mandatory, ParameterSetName='CommentUri')] - [Parameter(Mandatory, ParameterSetName='CommentElements')] - [string] $CommentID, + [Parameter( + Mandatory, + ValueFromPipelineByPropertyName, + ParameterSetName='CommentElements')] + [Parameter( + Mandatory, + ValueFromPipelineByPropertyName, + ParameterSetName='CommentUri')] + [Alias('CommentId')] + [string] $Comment, - [Parameter(Mandatory, ParameterSetName='IssueUri')] - [Parameter(Mandatory, ParameterSetName='IssueElements')] + [Parameter( + Mandatory, + ValueFromPipelineByPropertyName, + ParameterSetName='IssueElements')] + [Parameter( + Mandatory, + ValueFromPipelineByPropertyName, + ParameterSetName='IssueUri')] + [Alias('IssueNumber')] [int64] $Issue, - [Parameter(ParameterSetName='RepositoryUri')] [Parameter(ParameterSetName='RepositoryElements')] + [Parameter(ParameterSetName='RepositoryUri')] [Parameter(ParameterSetName='IssueElements')] [Parameter(ParameterSetName='IssueUri')] [DateTime] $Since, - [Parameter(ParameterSetName='RepositoryUri')] [Parameter(ParameterSetName='RepositoryElements')] + [Parameter(ParameterSetName='RepositoryUri')] [ValidateSet('Created', 'Updated')] [string] $Sort, - [Parameter(ParameterSetName='RepositoryUri')] [Parameter(ParameterSetName='RepositoryElements')] + [Parameter(ParameterSetName='RepositoryUri')] [ValidateSet('Ascending', 'Descending')] [string] $Direction, @@ -130,13 +226,13 @@ function Get-GitHubComment 'OwnerName' = (Get-PiiSafeString -PlainText $OwnerName) 'RepositoryName' = (Get-PiiSafeString -PlainText $RepositoryName) 'ProvidedIssue' = $PSBoundParameters.ContainsKey('Issue') - 'ProvidedComment' = $PSBoundParameters.ContainsKey('CommentID') + 'ProvidedComment' = $PSBoundParameters.ContainsKey('Comment') } - if ($PSBoundParameters.ContainsKey('CommentID')) + if ($PSBoundParameters.ContainsKey('Comment')) { - $uriFragment = "repos/$OwnerName/$RepositoryName/issues/comments/$CommentId" - $description = "Getting comment $CommentID for $RepositoryName" + $uriFragment = "repos/$OwnerName/$RepositoryName/issues/comments/$Comment" + $description = "Getting comment $Comment for $RepositoryName" } elseif ($PSBoundParameters.ContainsKey('Issue')) { @@ -181,20 +277,20 @@ function Get-GitHubComment 'UriFragment' = $uriFragment 'Description' = $description 'AccessToken' = $AccessToken - 'AcceptHeader' = (Get-MediaAcceptHeader -MediaType $MediaType -AsJson -AcceptHeader $squirrelAcceptHeader) + 'AcceptHeader' = (Get-MediaAcceptHeader -MediaType $MediaType -AsJson -AcceptHeader $squirrelGirlAcceptHeader) 'TelemetryEventName' = $MyInvocation.MyCommand.Name 'TelemetryProperties' = $telemetryProperties 'NoStatus' = (Resolve-ParameterWithDefaultConfigurationValue -Name NoStatus -ConfigValueName DefaultNoStatus) } - return Invoke-GHRestMethodMultipleResult @params + return (Invoke-GHRestMethodMultipleResult @params | Add-GitHubIssueCommentAdditionalProperties) } -function New-GitHubComment +filter New-GitHubIssueComment { <# .DESCRIPTION - Creates a new Github comment in an issue for the given repository + Creates a new GitHub comment for an issue for the given repository The Git repo for this module can be found here: http://aka.ms/PowerShellForGitHub @@ -235,14 +331,35 @@ function New-GitHubComment the background, enabling the command prompt to provide status information. If not supplied here, the DefaultNoStatus configuration property value will be used. + .INPUTS + GitHub.Branch + GitHub.Content + GitHub.Event + GitHub.Issue + GitHub.IssueComment + GitHub.Label + GitHub.Milestone + GitHub.PullRequest + GitHub.Project + GitHub.ProjectCard + GitHub.ProjectColumn + GitHub.Release + GitHub.Repository + GitHub.User + + .OUTPUTS + GitHub.IssueComment + .EXAMPLE - New-GitHubComment -OwnerName Microsoft -RepositoryName PowerShellForGitHub -Issue 1 -Body "Testing this API" + New-GitHubIssueComment -OwnerName microsoft -RepositoryName PowerShellForGitHub -Issue 1 -Body "Testing this API" - Creates a new Github comment in an issue for the Microsoft\PowerShellForGitHub project. + Creates a new GitHub comment for an issue for the microsoft\PowerShellForGitHub project. #> [CmdletBinding( SupportsShouldProcess, DefaultParameterSetName='Elements')] + [Alias('New-GitHubComment')] # Aliased to avoid a breaking change after v0.14.0 + [OutputType({$script:GitHubIssueCommentTypeName})] [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSShouldProcess", "", Justification="Methods called within here make use of PSShouldProcess, and the switch is passed on to them inherently.")] [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSReviewUnusedParameter", "", Justification="One or more parameters (like NoStatus) are only referenced by helper methods which get access to it from the stack via Get-Variable -Scope 1.")] param( @@ -254,10 +371,15 @@ function New-GitHubComment [Parameter( Mandatory, + ValueFromPipelineByPropertyName, ParameterSetName='Uri')] + [Alias('RepositoryUrl')] [string] $Uri, - [Parameter(Mandatory)] + [Parameter( + Mandatory, + ValueFromPipelineByPropertyName)] + [Alias('IssueNumber')] [int64] $Issue, [Parameter(Mandatory)] @@ -293,20 +415,20 @@ function New-GitHubComment 'Method' = 'Post' 'Description' = "Creating comment under issue $Issue for $RepositoryName" 'AccessToken' = $AccessToken - 'AcceptHeader' = (Get-MediaAcceptHeader -MediaType $MediaType -AsJson -AcceptHeader $squirrelAcceptHeader) + 'AcceptHeader' = (Get-MediaAcceptHeader -MediaType $MediaType -AsJson -AcceptHeader $squirrelGirlAcceptHeader) 'TelemetryEventName' = $MyInvocation.MyCommand.Name 'TelemetryProperties' = $telemetryProperties 'NoStatus' = (Resolve-ParameterWithDefaultConfigurationValue -Name NoStatus -ConfigValueName DefaultNoStatus) } - return Invoke-GHRestMethod @params + return (Invoke-GHRestMethod @params | Add-GitHubIssueCommentAdditionalProperties) } -function Set-GitHubComment +filter Set-GitHubIssueComment { <# .DESCRIPTION - Set an existing comment in an issue for the given repository + Modifies an existing comment in an issue for the given repository The Git repo for this module can be found here: http://aka.ms/PowerShellForGitHub @@ -323,8 +445,8 @@ function Set-GitHubComment The OwnerName and RepositoryName will be extracted from here instead of needing to provide them individually. - .PARAMETER CommentID - The comment ID of the comment to edit. + .PARAMETER Comment + The ID of the comment to edit. .PARAMETER Body The new contents of the comment. @@ -332,10 +454,15 @@ function Set-GitHubComment .PARAMETER MediaType The format in which the API will return the body of the comment. - Raw - Return the raw markdown body. Response will include body. This is the default if you do not pass any specific media type. - Text - Return a text only representation of the markdown body. Response will include body_text. - Html - Return HTML rendered from the body's markdown. Response will include body_html. - Full - Return raw, text and HTML representations. Response will include body, body_text, and body_html. + Raw - Return the raw markdown body. + Response will include body. + This is the default if you do not pass any specific media type. + Text - Return a text only representation of the markdown body. + Response will include body_text. + Html - Return HTML rendered from the body's markdown. + Response will include body_html. + Full - Return raw, text and HTML representations. + Response will include body, body_text, and body_html. .PARAMETER AccessToken If provided, this will be used as the AccessToken for authentication with the @@ -347,14 +474,35 @@ function Set-GitHubComment the background, enabling the command prompt to provide status information. If not supplied here, the DefaultNoStatus configuration property value will be used. + .INPUTS + GitHub.Branch + GitHub.Content + GitHub.Event + GitHub.Issue + GitHub.IssueComment + GitHub.Label + GitHub.Milestone + GitHub.PullRequest + GitHub.Project + GitHub.ProjectCard + GitHub.ProjectColumn + GitHub.Release + GitHub.Repository + GitHub.User + + .OUTPUTS + GitHub.IssueComment + .EXAMPLE - Set-GitHubComment -OwnerName Microsoft -RepositoryName PowerShellForGitHub -CommentID 1 -Body "Testing this API" + Set-GitHubIssueComment -OwnerName microsoft -RepositoryName PowerShellForGitHub -Comment 1 -Body "Testing this API" - Update an existing comment in an issue for the Microsoft\PowerShellForGitHub project. + Updates an existing comment in an issue for the microsoft\PowerShellForGitHub project. #> [CmdletBinding( SupportsShouldProcess, DefaultParameterSetName='Elements')] + [Alias('Set-GitHubComment')] # Aliased to avoid a breaking change after v0.14.0 + [OutputType({$script:GitHubIssueCommentTypeName})] [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSShouldProcess", "", Justification="Methods called within here make use of PSShouldProcess, and the switch is passed on to them inherently.")] [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSReviewUnusedParameter", "", Justification="One or more parameters (like NoStatus) are only referenced by helper methods which get access to it from the stack via Get-Variable -Scope 1.")] param( @@ -366,11 +514,16 @@ function Set-GitHubComment [Parameter( Mandatory, + ValueFromPipelineByPropertyName, ParameterSetName='Uri')] + [Alias('RepositoryUrl')] [string] $Uri, - [Parameter(Mandatory)] - [string] $CommentID, + [Parameter( + Mandatory, + ValueFromPipelineByPropertyName)] + [Alias('CommentId')] + [int64] $Comment, [Parameter(Mandatory)] [string] $Body, @@ -392,7 +545,7 @@ function Set-GitHubComment $telemetryProperties = @{ 'OwnerName' = (Get-PiiSafeString -PlainText $OwnerName) 'RepositoryName' = (Get-PiiSafeString -PlainText $RepositoryName) - 'CommentID' = (Get-PiiSafeString -PlainText $CommentID) + 'Comment' = (Get-PiiSafeString -PlainText $Comment) } $hashBody = @{ @@ -400,25 +553,25 @@ function Set-GitHubComment } $params = @{ - 'UriFragment' = "repos/$OwnerName/$RepositoryName/issues/comments/$CommentID" + 'UriFragment' = "repos/$OwnerName/$RepositoryName/issues/comments/$Comment" 'Body' = (ConvertTo-Json -InputObject $hashBody) 'Method' = 'Patch' - 'Description' = "Update comment $CommentID for $RepositoryName" + 'Description' = "Update comment $Comment for $RepositoryName" 'AccessToken' = $AccessToken - 'AcceptHeader' = (Get-MediaAcceptHeader -MediaType $MediaType -AsJson -AcceptHeader $squirrelAcceptHeader) + 'AcceptHeader' = (Get-MediaAcceptHeader -MediaType $MediaType -AsJson -AcceptHeader $squirrelGirlAcceptHeader) 'TelemetryEventName' = $MyInvocation.MyCommand.Name 'TelemetryProperties' = $telemetryProperties 'NoStatus' = (Resolve-ParameterWithDefaultConfigurationValue -Name NoStatus -ConfigValueName DefaultNoStatus) } - return Invoke-GHRestMethod @params + return (Invoke-GHRestMethod @params | Add-GitHubIssueCommentAdditionalProperties) } -function Remove-GitHubComment +filter Remove-GitHubIssueComment { <# .DESCRIPTION - Deletes a Github comment for the given repository + Deletes a GitHub comment from an Issue in the given repository The Git repo for this module can be found here: http://aka.ms/PowerShellForGitHub @@ -435,8 +588,8 @@ function Remove-GitHubComment The OwnerName and RepositoryName will be extracted from here instead of needing to provide them individually. - .PARAMETER CommentID - The id of the comment to delete. + .PARAMETER Comment + The ID of the comment to delete. .PARAMETER Force If this switch is specified, you will not be prompted for confirmation of command execution. @@ -451,26 +604,45 @@ function Remove-GitHubComment the background, enabling the command prompt to provide status information. If not supplied here, the DefaultNoStatus configuration property value will be used. + .INPUTS + GitHub.Branch + GitHub.Content + GitHub.Event + GitHub.Issue + GitHub.IssueComment + GitHub.Label + GitHub.Milestone + GitHub.PullRequest + GitHub.Project + GitHub.ProjectCard + GitHub.ProjectColumn + GitHub.Release + GitHub.Repository + .EXAMPLE - Remove-GitHubComment -OwnerName Microsoft -RepositoryName PowerShellForGitHub -CommentID 1 + Remove-GitHubIssueComment -OwnerName microsoft -RepositoryName PowerShellForGitHub -Comment 1 - Deletes a Github comment from the Microsoft\PowerShellForGitHub project. + Deletes a GitHub comment from an Issue in the microsoft\PowerShellForGitHub project. .EXAMPLE - Remove-GitHubComment -OwnerName Microsoft -RepositoryName PowerShellForGitHub -CommentID 1 -Confirm:$false + Remove-GitHubIssueComment -OwnerName microsoft -RepositoryName PowerShellForGitHub -Comment 1 -Confirm:$false - Deletes a Github comment from the Microsoft\PowerShellForGitHub project without prompting confirmation. + Deletes a Github comment from an Issue in the microsoft\PowerShellForGitHub project + without prompting confirmation. .EXAMPLE - Remove-GitHubComment -OwnerName Microsoft -RepositoryName PowerShellForGitHub -CommentID 1 -Force + Remove-GitHubIssueComment -OwnerName microsoft -RepositoryName PowerShellForGitHub -Comment 1 -Force - Deletes a Github comment from the Microsoft\PowerShellForGitHub project without prompting confirmation. + Deletes a GitHub comment from an Issue in the microsoft\PowerShellForGitHub project + without prompting confirmation. #> [CmdletBinding( SupportsShouldProcess, DefaultParameterSetName='Elements', ConfirmImpact="High")] [Alias('Delete-GitHubComment')] + [Alias('Delete-GitHubIssueComment')] + [Alias('Remove-GitHubComment')] # Aliased to avoid a breaking change after v0.14.0 [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSReviewUnusedParameter", "", Justification="One or more parameters (like NoStatus) are only referenced by helper methods which get access to it from the stack via Get-Variable -Scope 1.")] param( [Parameter(ParameterSetName='Elements')] @@ -481,11 +653,16 @@ function Remove-GitHubComment [Parameter( Mandatory, + ValueFromPipelineByPropertyName, ParameterSetName='Uri')] + [Alias('RepositoryUrl')] [string] $Uri, - [Parameter(Mandatory)] - [string] $CommentID, + [Parameter( + Mandatory, + ValueFromPipelineByPropertyName)] + [Alias('CommentId')] + [int64] $Comment, [switch] $Force, @@ -503,7 +680,7 @@ function Remove-GitHubComment $telemetryProperties = @{ 'OwnerName' = (Get-PiiSafeString -PlainText $OwnerName) 'RepositoryName' = (Get-PiiSafeString -PlainText $RepositoryName) - 'CommentID' = (Get-PiiSafeString -PlainText $CommentID) + 'Comment' = (Get-PiiSafeString -PlainText $Comment) } if ($Force -and (-not $Confirm)) @@ -511,12 +688,12 @@ function Remove-GitHubComment $ConfirmPreference = 'None' } - if ($PSCmdlet.ShouldProcess($CommentID, "Remove comment")) + if ($PSCmdlet.ShouldProcess($Comment, "Remove comment")) { $params = @{ - 'UriFragment' = "repos/$OwnerName/$RepositoryName/issues/comments/$CommentID" + 'UriFragment' = "repos/$OwnerName/$RepositoryName/issues/comments/$Comment" 'Method' = 'Delete' - 'Description' = "Removing comment $CommentID for $RepositoryName" + 'Description' = "Removing comment $Comment for $RepositoryName" 'AccessToken' = $AccessToken 'TelemetryEventName' = $MyInvocation.MyCommand.Name 'TelemetryProperties' = $telemetryProperties @@ -527,3 +704,60 @@ function Remove-GitHubComment } } +filter Add-GitHubIssueCommentAdditionalProperties +{ +<# + .SYNOPSIS + Adds type name and additional properties to ease pipelining to GitHub Issue Comment objects. + + .PARAMETER InputObject + The GitHub object to add additional properties to. + + .PARAMETER TypeName + The type that should be assigned to the object. + + .INPUTS + [PSCustomObject] + + .OUTPUTS + GitHub.IssueComment +#> + [CmdletBinding()] + [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSUseSingularNouns", "", Justification="Internal helper that is definitely adding more than one property.")] + param( + [Parameter( + Mandatory, + ValueFromPipeline)] + [AllowNull()] + [AllowEmptyCollection()] + [PSCustomObject[]] $InputObject, + + [ValidateNotNullOrEmpty()] + [string] $TypeName = $script:GitHubIssueCommentTypeName + ) + + foreach ($item in $InputObject) + { + # Provide a generic comment type too + $item.PSObject.TypeNames.Insert(0, $script:GitHubCommentTypeName) + + # But we want the specific type on top + $item.PSObject.TypeNames.Insert(0, $TypeName) + + if (-not (Get-GitHubConfiguration -Name DisablePipelineSupport)) + { + $elements = Split-GitHubUri -Uri $item.html_url + $repositoryUrl = Join-GitHubUri @elements + Add-Member -InputObject $item -Name 'RepositoryUrl' -Value $repositoryUrl -MemberType NoteProperty -Force + + Add-Member -InputObject $item -Name 'CommentId' -Value $item.id -MemberType NoteProperty -Force + + if ($null -ne $item.user) + { + $null = Add-GitHubUserAdditionalProperties -InputObject $item.user + } + } + + Write-Output $item + } +} \ No newline at end of file diff --git a/GitHubIssues.ps1 b/GitHubIssues.ps1 index d27bf93e..8e903c00 100644 --- a/GitHubIssues.ps1 +++ b/GitHubIssues.ps1 @@ -1,7 +1,13 @@ # Copyright (c) Microsoft Corporation. All rights reserved. # Licensed under the MIT License. -function Get-GitHubIssue +@{ + GitHubIssueTypeName = 'GitHub.Issue' + }.GetEnumerator() | ForEach-Object { + Set-Variable -Scope Script -Option ReadOnly -Name $_.Key -Value $_.Value + } + +filter Get-GitHubIssue { <# .SYNOPSIS @@ -29,7 +35,7 @@ function Get-GitHubIssue The organization whose issues should be retrieved. .PARAMETER RepositoryType - all: Retrieve issues across owned, member and org repositories + all: Retrieve issues across owned, member and org repositories ownedAndMember: Retrieve issues across owned and member repositories .PARAMETER Issue @@ -69,7 +75,7 @@ function Get-GitHubIssue all: All milestones will be returned. none: Only issues without milestones will be returned. - .PARAMETER Milestone + .PARAMETER MilestoneNumber Only issues with this milestone will be returned. .PARAMETER AssigneeType @@ -85,15 +91,20 @@ function Get-GitHubIssue Only issues created by this specified user will be returned. .PARAMETER Mentioned - Only issues that mention this specified user will be returned. + Only issues that mention this specified user will be returned. .PARAMETER MediaType The format in which the API will return the body of the issue. - Raw - Return the raw markdown body. Response will include body. This is the default if you do not pass any specific media type. - Text - Return a text only representation of the markdown body. Response will include body_text. - Html - Return HTML rendered from the body's markdown. Response will include body_html. - Full - Return raw, text and HTML representations. Response will include body, body_text, and body_html. + Raw - Return the raw markdown body. + Response will include body. + This is the default if you do not pass any specific media type. + Text - Return a text only representation of the markdown body. + Response will include body_text. + Html - Return HTML rendered from the body's markdown. + Response will include body_html. + Full - Return raw, text and HTML representations. + Response will include body, body_text, and body_html. .PARAMETER AccessToken If provided, this will be used as the AccessToken for authentication with the @@ -105,19 +116,39 @@ function Get-GitHubIssue the background, enabling the command prompt to provide status information. If not supplied here, the DefaultNoStatus configuration property value will be used. + .INPUTS + GitHub.Branch + GitHub.Content + GitHub.Event + GitHub.Issue + GitHub.IssueComment + GitHub.Label + GitHub.Milestone + GitHub.PullRequest + GitHub.Project + GitHub.ProjectCard + GitHub.ProjectColumn + GitHub.Release + GitHub.Repository + GitHub.User + + .OUTPUTS + GitHub.Issue + .EXAMPLE - Get-GitHubIssue -OwnerName Microsoft -RepositoryName PowerShellForGitHub -State Open + Get-GitHubIssue -OwnerName microsoft -RepositoryName PowerShellForGitHub -State Open - Gets all the currently open issues in the Microsoft\PowerShellForGitHub repository. + Gets all the currently open issues in the microsoft\PowerShellForGitHub repository. .EXAMPLE - Get-GitHubIssue -OwnerName Microsoft -RepositoryName PowerShellForGitHub -State All -Assignee Octocat + Get-GitHubIssue -OwnerName microsoft -RepositoryName PowerShellForGitHub -State All -Assignee Octocat - Gets every issue in the Microsoft\PowerShellForGitHub repository that is assigned to Octocat. + Gets every issue in the microsoft\PowerShellForGitHub repository that is assigned to Octocat. #> [CmdletBinding( SupportsShouldProcess, DefaultParameterSetName='Elements')] + [OutputType({$script:GitHubIssueTypeName})] [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSShouldProcess", "", Justification="Methods called within here make use of PSShouldProcess, and the switch is passed on to them inherently.")] param( [Parameter(ParameterSetName='Elements')] @@ -128,14 +159,19 @@ function Get-GitHubIssue [Parameter( Mandatory, + ValueFromPipelineByPropertyName, ParameterSetName='Uri')] + [Alias('RepositoryUrl')] [string] $Uri, + [Parameter(ValueFromPipelineByPropertyName)] [string] $OrganizationName, [ValidateSet('All', 'OwnedAndMember')] [string] $RepositoryType = 'All', + [Parameter(ValueFromPipelineByPropertyName)] + [Alias('IssueNumber')] [int64] $Issue, [switch] $IgnorePullRequests, @@ -159,13 +195,16 @@ function Get-GitHubIssue [ValidateSet('Specific', 'All', 'None')] [string] $MilestoneType, - [string] $Milestone, + [Parameter(ValueFromPipelineByPropertyName)] + [int64] $MilestoneNumber, [ValidateSet('Specific', 'All', 'None')] [string] $AssigneeType, [string] $Assignee, + [Parameter(ValueFromPipelineByPropertyName)] + [Alias('UserName')] [string] $Creator, [string] $Mentioned, @@ -266,17 +305,17 @@ function Get-GitHubIssue { $getParams += 'mentioned=none' } - elseif ([String]::IsNullOrEmpty($Milestone)) + elseif ($PSBoundParameters.ContainsKey('$MilestoneNumber')) { - $message = "MilestoneType was set to [$MilestoneType], but no value for Milestone was provided." + $message = "MilestoneType was set to [$MilestoneType], but no value for MilestoneNumber was provided." Write-Log -Message $message -Level Error throw $message } } - if ($PSBoundParameters.ContainsKey('Milestone')) + if ($PSBoundParameters.ContainsKey('MilestoneNumber')) { - $getParams += "milestone=$Milestone" + $getParams += "milestone=$MilestoneNumber" } if ($PSBoundParameters.ContainsKey('AssigneeType')) @@ -324,7 +363,7 @@ function Get-GitHubIssue try { - $result = Invoke-GHRestMethodMultipleResult @params + $result = (Invoke-GHRestMethodMultipleResult @params | Add-GitHubIssueAdditionalProperties) if ($IgnorePullRequests) { @@ -339,7 +378,7 @@ function Get-GitHubIssue finally {} } -function Get-GitHubIssueTimeline +filter Get-GitHubIssueTimeline { <# .SYNOPSIS @@ -376,12 +415,31 @@ function Get-GitHubIssueTimeline the background, enabling the command prompt to provide status information. If not supplied here, the DefaultNoStatus configuration property value will be used. + .INPUTS + GitHub.Branch + GitHub.Content + GitHub.Event + GitHub.Issue + GitHub.IssueComment + GitHub.Label + GitHub.Milestone + GitHub.PullRequest + GitHub.Project + GitHub.ProjectCard + GitHub.ProjectColumn + GitHub.Release + GitHub.Repository + + .OUTPUTS + GitHub.Event + .EXAMPLE - Get-GitHubIssueTimeline -OwnerName Microsoft -RepositoryName PowerShellForGitHub -Issue 24 + Get-GitHubIssueTimeline -OwnerName microsoft -RepositoryName PowerShellForGitHub -Issue 24 #> [CmdletBinding( SupportsShouldProcess, DefaultParameterSetName='Elements')] + [OutputType({$script:GitHubEventTypeName})] [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSShouldProcess", "", Justification="Methods called within here make use of PSShouldProcess, and the switch is passed on to them inherently.")] [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSReviewUnusedParameter", "", Justification="One or more parameters (like NoStatus) are only referenced by helper methods which get access to it from the stack via Get-Variable -Scope 1.")] param( @@ -393,10 +451,15 @@ function Get-GitHubIssueTimeline [Parameter( Mandatory, + ValueFromPipelineByPropertyName, ParameterSetName='Uri')] + [Alias('RepositoryUrl')] [string] $Uri, - [Parameter(Mandatory)] + [Parameter( + Mandatory, + ValueFromPipelineByPropertyName)] + [Alias('IssueNumber')] [int64] $Issue, [string] $AccessToken, @@ -418,17 +481,17 @@ function Get-GitHubIssueTimeline $params = @{ 'UriFragment' = "repos/$OwnerName/$RepositoryName/issues/$Issue/timeline" 'Description' = "Getting timeline for Issue #$Issue in $RepositoryName" - 'AcceptHeader' = 'application/vnd.github.mockingbird-preview' + 'AcceptHeader' = $script:mockingbirdAcceptHeader 'AccessToken' = $AccessToken 'TelemetryEventName' = $MyInvocation.MyCommand.Name 'TelemetryProperties' = $telemetryProperties 'NoStatus' = (Resolve-ParameterWithDefaultConfigurationValue -Name NoStatus -ConfigValueName DefaultNoStatus) } - return Invoke-GHRestMethodMultipleResult @params + return (Invoke-GHRestMethodMultipleResult @params | Add-GitHubEventAdditionalProperties) } -function New-GitHubIssue +filter New-GitHubIssue { <# .SYNOPSIS @@ -470,10 +533,15 @@ function New-GitHubIssue .PARAMETER MediaType The format in which the API will return the body of the issue. - Raw - Return the raw markdown body. Response will include body. This is the default if you do not pass any specific media type. - Text - Return a text only representation of the markdown body. Response will include body_text. - Html - Return HTML rendered from the body's markdown. Response will include body_html. - Full - Return raw, text and HTML representations. Response will include body, body_text, and body_html. + Raw - Return the raw markdown body. + Response will include body. + This is the default if you do not pass any specific media type. + Text - Return a text only representation of the markdown body. + Response will include body_text. + Html - Return HTML rendered from the body's markdown. + Response will include body_html. + Full - Return raw, text and HTML representations. + Response will include body, body_text, and body_html. .PARAMETER AccessToken If provided, this will be used as the AccessToken for authentication with the @@ -485,12 +553,31 @@ function New-GitHubIssue the background, enabling the command prompt to provide status information. If not supplied here, the DefaultNoStatus configuration property value will be used. + .INPUTS + GitHub.Branch + GitHub.Content + GitHub.Event + GitHub.Issue + GitHub.IssueComment + GitHub.Label + GitHub.Milestone + GitHub.PullRequest + GitHub.Project + GitHub.ProjectCard + GitHub.ProjectColumn + GitHub.Release + GitHub.Repository + + .OUTPUTS + GitHub.Issue + .EXAMPLE - New-GitHubIssue -OwnerName Microsoft -RepositoryName PowerShellForGitHub -Title 'Test Issue' + New-GitHubIssue -OwnerName microsoft -RepositoryName PowerShellForGitHub -Title 'Test Issue' #> [CmdletBinding( SupportsShouldProcess, DefaultParameterSetName='Elements')] + [OutputType({$script:GitHubIssueTypeName})] [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSShouldProcess", "", Justification="Methods called within here make use of PSShouldProcess, and the switch is passed on to them inherently.")] param( [Parameter(ParameterSetName='Elements')] @@ -501,7 +588,9 @@ function New-GitHubIssue [Parameter( Mandatory, + ValueFromPipelineByPropertyName, ParameterSetName='Uri')] + [Alias('RepositoryUrl')] [string] $Uri, [Parameter(Mandatory)] @@ -512,6 +601,8 @@ function New-GitHubIssue [string[]] $Assignee, + [Parameter(ValueFromPipelineByPropertyName)] + [Alias('MilestoneNumber')] [int64] $Milestone, [string[]] $Label, @@ -556,10 +647,10 @@ function New-GitHubIssue 'NoStatus' = (Resolve-ParameterWithDefaultConfigurationValue -Name NoStatus -ConfigValueName DefaultNoStatus) } - return Invoke-GHRestMethod @params + return (Invoke-GHRestMethod @params | Add-GitHubIssueAdditionalProperties) } -function Update-GitHubIssue +filter Update-GitHubIssue { <# .SYNOPSIS @@ -596,7 +687,7 @@ function Update-GitHubIssue Login(s) for Users to assign to the issue. Provide an empty array to clear all existing assignees. - .PARAMETER Milestone + .PARAMETER MilestoneNumber The number of the milestone to associate this issue with. Set to 0/$null to remove current. @@ -610,10 +701,15 @@ function Update-GitHubIssue .PARAMETER MediaType The format in which the API will return the body of the issue. - Raw - Return the raw markdown body. Response will include body. This is the default if you do not pass any specific media type. - Text - Return a text only representation of the markdown body. Response will include body_text. - Html - Return HTML rendered from the body's markdown. Response will include body_html. - Full - Return raw, text and HTML representations. Response will include body, body_text, and body_html. + Raw - Return the raw markdown body. + Response will include body. + This is the default if you do not pass any specific media type. + Text - Return a text only representation of the markdown body. + Response will include body_text. + Html - Return HTML rendered from the body's markdown. + Response will include body_html. + Full - Return raw, text and HTML representations. + Response will include body, body_text, and body_html. .PARAMETER AccessToken If provided, this will be used as the AccessToken for authentication with the @@ -625,12 +721,31 @@ function Update-GitHubIssue the background, enabling the command prompt to provide status information. If not supplied here, the DefaultNoStatus configuration property value will be used. + .INPUTS + GitHub.Branch + GitHub.Content + GitHub.Event + GitHub.Issue + GitHub.IssueComment + GitHub.Label + GitHub.Milestone + GitHub.PullRequest + GitHub.Project + GitHub.ProjectCard + GitHub.ProjectColumn + GitHub.Release + GitHub.Repository + + .OUTPUTS + GitHub.Issue + .EXAMPLE - Update-GitHubIssue -OwnerName Microsoft -RepositoryName PowerShellForGitHub -Issue 4 -Title 'Test Issue' -State Closed + Update-GitHubIssue -OwnerName microsoft -RepositoryName PowerShellForGitHub -Issue 4 -Title 'Test Issue' -State Closed #> [CmdletBinding( SupportsShouldProcess, DefaultParameterSetName='Elements')] + [OutputType({$script:GitHubIssueTypeName})] [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSShouldProcess", "", Justification="Methods called within here make use of PSShouldProcess, and the switch is passed on to them inherently.")] param( [Parameter(ParameterSetName='Elements')] @@ -641,10 +756,15 @@ function Update-GitHubIssue [Parameter( Mandatory, + ValueFromPipelineByPropertyName, ParameterSetName='Uri')] + [Alias('RepositoryUrl')] [string] $Uri, - [Parameter(Mandatory)] + [Parameter( + Mandatory, + ValueFromPipelineByPropertyName)] + [Alias('IssueNumber')] [int64] $Issue, [string] $Title, @@ -653,7 +773,7 @@ function Update-GitHubIssue [string[]] $Assignee, - [int64] $Milestone, + [int64] $MilestoneNumber, [string[]] $Label, @@ -686,10 +806,10 @@ function Update-GitHubIssue if ($PSBoundParameters.ContainsKey('Assignee')) { $hashBody['assignees'] = @($Assignee) } if ($PSBoundParameters.ContainsKey('Label')) { $hashBody['labels'] = @($Label) } if ($PSBoundParameters.ContainsKey('State')) { $hashBody['state'] = $State.ToLower() } - if ($PSBoundParameters.ContainsKey('Milestone')) + if ($PSBoundParameters.ContainsKey('MilestoneNumber')) { - $hashBody['milestone'] = $Milestone - if ($Milestone -in (0, $null)) + $hashBody['milestone'] = $MilestoneNumber + if ($MilestoneNumber -in (0, $null)) { $hashBody['milestone'] = $null } @@ -707,10 +827,10 @@ function Update-GitHubIssue 'NoStatus' = (Resolve-ParameterWithDefaultConfigurationValue -Name NoStatus -ConfigValueName DefaultNoStatus) } - return Invoke-GHRestMethod @params + return (Invoke-GHRestMethod @params | Add-GitHubIssueAdditionalProperties) } -function Lock-GitHubIssue +filter Lock-GitHubIssue { <# .SYNOPSIS @@ -750,8 +870,23 @@ function Lock-GitHubIssue the background, enabling the command prompt to provide status information. If not supplied here, the DefaultNoStatus configuration property value will be used. + .INPUTS + GitHub.Branch + GitHub.Content + GitHub.Event + GitHub.Issue + GitHub.IssueComment + GitHub.Label + GitHub.Milestone + GitHub.PullRequest + GitHub.Project + GitHub.ProjectCard + GitHub.ProjectColumn + GitHub.Release + GitHub.Repository + .EXAMPLE - Lock-GitHubIssue -OwnerName Microsoft -RepositoryName PowerShellForGitHub -Issue 4 -Title 'Test Issue' -Reason Spam + Lock-GitHubIssue -OwnerName microsoft -RepositoryName PowerShellForGitHub -Issue 4 -Title 'Test Issue' -Reason Spam #> [CmdletBinding( SupportsShouldProcess, @@ -766,10 +901,15 @@ function Lock-GitHubIssue [Parameter( Mandatory, + ValueFromPipelineByPropertyName, ParameterSetName='Uri')] + [Alias('RepositoryUrl')] [string] $Uri, - [Parameter(Mandatory)] + [Parameter( + Mandatory, + ValueFromPipelineByPropertyName)] + [Alias('IssueNumber')] [int64] $Issue, [ValidateSet('OffTopic', 'TooHeated', 'Resolved', 'Spam')] @@ -805,7 +945,7 @@ function Lock-GitHubIssue } $telemetryProperties['Reason'] = $Reason - $hashBody['active_lock_reason'] = $reasonConverter[$Reason] + $hashBody['lock_reason'] = $reasonConverter[$Reason] } $params = @{ @@ -813,7 +953,7 @@ function Lock-GitHubIssue 'Body' = (ConvertTo-Json -InputObject $hashBody) 'Method' = 'Put' 'Description' = "Locking Issue #$Issue on $RepositoryName" - 'AcceptHeader' = 'application/vnd.github.sailor-v-preview+json' + 'AcceptHeader' = $script:sailorVAcceptHeader 'AccessToken' = $AccessToken 'TelemetryEventName' = $MyInvocation.MyCommand.Name 'TelemetryProperties' = $telemetryProperties @@ -823,7 +963,7 @@ function Lock-GitHubIssue return Invoke-GHRestMethod @params } -function Unlock-GitHubIssue +filter Unlock-GitHubIssue { <# .SYNOPSIS @@ -860,55 +1000,162 @@ function Unlock-GitHubIssue the background, enabling the command prompt to provide status information. If not supplied here, the DefaultNoStatus configuration property value will be used. + .INPUTS + GitHub.Branch + GitHub.Content + GitHub.Event + GitHub.Issue + GitHub.IssueComment + GitHub.Label + GitHub.Milestone + GitHub.PullRequest + GitHub.Project + GitHub.ProjectCard + GitHub.ProjectColumn + GitHub.Release + GitHub.Repository + .EXAMPLE - Unlock-GitHubIssue -OwnerName Microsoft -RepositoryName PowerShellForGitHub -Issue 4 + Unlock-GitHubIssue -OwnerName microsoft -RepositoryName PowerShellForGitHub -Issue 4 #> -[CmdletBinding( - SupportsShouldProcess, - DefaultParameterSetName='Elements')] -[Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSShouldProcess", "", Justification="Methods called within here make use of PSShouldProcess, and the switch is passed on to them inherently.")] -[Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSReviewUnusedParameter", "", Justification="One or more parameters (like NoStatus) are only referenced by helper methods which get access to it from the stack via Get-Variable -Scope 1.")] -param( - [Parameter(ParameterSetName='Elements')] - [string] $OwnerName, + [CmdletBinding( + SupportsShouldProcess, + DefaultParameterSetName='Elements')] + [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSShouldProcess", "", Justification="Methods called within here make use of PSShouldProcess, and the switch is passed on to them inherently.")] + [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSReviewUnusedParameter", "", Justification="One or more parameters (like NoStatus) are only referenced by helper methods which get access to it from the stack via Get-Variable -Scope 1.")] + param( + [Parameter(ParameterSetName='Elements')] + [string] $OwnerName, - [Parameter(ParameterSetName='Elements')] - [string] $RepositoryName, + [Parameter(ParameterSetName='Elements')] + [string] $RepositoryName, - [Parameter( - Mandatory, - ParameterSetName='Uri')] - [string] $Uri, + [Parameter( + Mandatory, + ValueFromPipelineByPropertyName, + ParameterSetName='Uri')] + [Alias('RepositoryUrl')] + [string] $Uri, - [Parameter(Mandatory)] - [int64] $Issue, + [Parameter( + Mandatory, + ValueFromPipelineByPropertyName)] + [Alias('IssueNumber')] + [int64] $Issue, - [string] $AccessToken, + [string] $AccessToken, - [switch] $NoStatus -) + [switch] $NoStatus + ) -Write-InvocationLog + Write-InvocationLog -$elements = Resolve-RepositoryElements -$OwnerName = $elements.ownerName -$RepositoryName = $elements.repositoryName + $elements = Resolve-RepositoryElements + $OwnerName = $elements.ownerName + $RepositoryName = $elements.repositoryName -$telemetryProperties = @{ - 'OwnerName' = (Get-PiiSafeString -PlainText $OwnerName) - 'RepositoryName' = (Get-PiiSafeString -PlainText $RepositoryName) -} + $telemetryProperties = @{ + 'OwnerName' = (Get-PiiSafeString -PlainText $OwnerName) + 'RepositoryName' = (Get-PiiSafeString -PlainText $RepositoryName) + } -$params = @{ - 'UriFragment' = "/repos/$OwnerName/$RepositoryName/issues/$Issue/lock" - 'Method' = 'Delete' - 'Description' = "Unlocking Issue #$Issue on $RepositoryName" - 'AcceptHeader' = 'application/vnd.github.sailor-v-preview+json' - 'AccessToken' = $AccessToken - 'TelemetryEventName' = $MyInvocation.MyCommand.Name - 'TelemetryProperties' = $telemetryProperties - 'NoStatus' = (Resolve-ParameterWithDefaultConfigurationValue -Name NoStatus -ConfigValueName DefaultNoStatus) + $params = @{ + 'UriFragment' = "/repos/$OwnerName/$RepositoryName/issues/$Issue/lock" + 'Method' = 'Delete' + 'Description' = "Unlocking Issue #$Issue on $RepositoryName" + 'AcceptHeader' = $script:sailorVAcceptHeader + 'AccessToken' = $AccessToken + 'TelemetryEventName' = $MyInvocation.MyCommand.Name + 'TelemetryProperties' = $telemetryProperties + 'NoStatus' = (Resolve-ParameterWithDefaultConfigurationValue -Name NoStatus -ConfigValueName DefaultNoStatus) + } + + return Invoke-GHRestMethod @params } -return Invoke-GHRestMethod @params +filter Add-GitHubIssueAdditionalProperties +{ +<# + .SYNOPSIS + Adds type name and additional properties to ease pipelining to GitHub Issue objects. + + .PARAMETER InputObject + The GitHub object to add additional properties to. + + .PARAMETER TypeName + The type that should be assigned to the object. + + .INPUTS + [PSCustomObject] + + .OUTPUTS + GitHub.Issue +#> + [CmdletBinding()] + [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSUseSingularNouns", "", Justification="Internal helper that is definitely adding more than one property.")] + param( + [Parameter( + Mandatory, + ValueFromPipeline)] + [AllowNull()] + [AllowEmptyCollection()] + [PSCustomObject[]] $InputObject, + + [ValidateNotNullOrEmpty()] + [string] $TypeName = $script:GitHubIssueTypeName + ) + + foreach ($item in $InputObject) + { + # Pull requests are _also_ issues. A pull request that is retrieved through the + # Issue endpoint will also have a 'pull_request' property. Let's make sure that + # we mark it up appropriately. + if ($null -ne $item.pull_request) + { + $null = Add-GitHubPullRequestAdditionalProperties -InputObject $item + Write-Output $item + continue + } + + $item.PSObject.TypeNames.Insert(0, $TypeName) + + if (-not (Get-GitHubConfiguration -Name DisablePipelineSupport)) + { + $elements = Split-GitHubUri -Uri $item.html_url + $repositoryUrl = Join-GitHubUri @elements + Add-Member -InputObject $item -Name 'RepositoryUrl' -Value $repositoryUrl -MemberType NoteProperty -Force + Add-Member -InputObject $item -Name 'IssueId' -Value $item.id -MemberType NoteProperty -Force + Add-Member -InputObject $item -Name 'IssueNumber' -Value $item.number -MemberType NoteProperty -Force + + @('assignee', 'assignees', 'user') | + ForEach-Object { + if ($null -ne $item.$_) + { + $null = Add-GitHubUserAdditionalProperties -InputObject $item.$_ + } + } + + if ($null -ne $item.labels) + { + $null = Add-GitHubLabelAdditionalProperties -InputObject $item.labels + } + + if ($null -ne $item.milestone) + { + $null = Add-GitHubMilestoneAdditionalProperties -InputObject $item.milestone + } + + if ($null -ne $item.closed_by) + { + $null = Add-GitHubUserAdditionalProperties -InputObject $item.closed_by + } + + if ($null -ne $item.repository) + { + $null = Add-GitHubRepositoryAdditionalProperties -InputObject $item.repository + } + } + + Write-Output $item + } } diff --git a/GitHubLabels.ps1 b/GitHubLabels.ps1 index a134e08e..b020e54c 100644 --- a/GitHubLabels.ps1 +++ b/GitHubLabels.ps1 @@ -1,7 +1,13 @@ # Copyright (c) Microsoft Corporation. All rights reserved. # Licensed under the MIT License. -function Get-GitHubLabel +@{ + GitHubLabelTypeName = 'GitHub.Label' + }.GetEnumerator() | ForEach-Object { + Set-Variable -Scope Script -Option ReadOnly -Name $_.Key -Value $_.Value + } + +filter Get-GitHubLabel { <# .SYNOPSIS @@ -25,15 +31,15 @@ function Get-GitHubLabel The OwnerName and RepositoryName will be extracted from here instead of needing to provide them individually. - .PARAMETER Name + .PARAMETER Label Name of the specific label to be retrieved. If not supplied, all labels will be retrieved. Emoji and codes are supported. For more information, see here: https://www.webpagefx.com/tools/emoji-cheat-sheet/ .PARAMETER Issue If provided, will return all of the labels for this particular issue. - .PARAMETER Milestone - If provided, will return all of the labels for this particular milestone. + .PARAMETER MilestoneNumber + If provided, will return all of the labels assigned to issues for this particular milestone. .PARAMETER AccessToken If provided, this will be used as the AccessToken for authentication with the @@ -45,52 +51,74 @@ function Get-GitHubLabel the background, enabling the command prompt to provide status information. If not supplied here, the DefaultNoStatus configuration property value will be used. + .INPUTS + GitHub.Branch + GitHub.Content + GitHub.Event + GitHub.Issue + GitHub.IssueComment + GitHub.Label + GitHub.Milestone + GitHub.PullRequest + GitHub.Project + GitHub.ProjectCard + GitHub.ProjectColumn + GitHub.Release + GitHub.Repository + + .OUTPUTS + GitHub.Label + .EXAMPLE - Get-GitHubLabel -OwnerName Microsoft -RepositoryName PowerShellForGitHub + Get-GitHubLabel -OwnerName microsoft -RepositoryName PowerShellForGitHub - Gets the information for every label from the Microsoft\PowerShellForGitHub project. + Gets the information for every label from the microsoft\PowerShellForGitHub project. .EXAMPLE - Get-GitHubLabel -OwnerName Microsoft -RepositoryName PowerShellForGitHub -LabelName TestLabel + Get-GitHubLabel -OwnerName microsoft -RepositoryName PowerShellForGitHub -Label TestLabel - Gets the information for the label named "TestLabel" from the Microsoft\PowerShellForGitHub + Gets the information for the label named "TestLabel" from the microsoft\PowerShellForGitHub project. + + .NOTES + There were a lot of complications with the ParameterSets with this function due to pipeline + input. For the time being, the ParameterSets have been simplified and the validation of + parameter combinations is happening within the function itself. #> [CmdletBinding( SupportsShouldProcess, - DefaultParameterSetName='Elements')] + DefaultParameterSetName='NameUri')] + [OutputType({$script:GitHubLabelTypeName})] [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSShouldProcess", "", Justification="Methods called within here make use of PSShouldProcess, and the switch is passed on to them inherently.")] param( - [Parameter(Mandatory, ParameterSetName='Elements')] - [Parameter(Mandatory, ParameterSetName='NameElements')] - [Parameter(Mandatory, ParameterSetName='IssueElements')] - [Parameter(Mandatory, ParameterSetName='MilestoneElements')] + [Parameter( + Mandatory, + ParameterSetName='Elements')] [string] $OwnerName, - [Parameter(Mandatory, ParameterSetName='Elements')] - [Parameter(Mandatory, ParameterSetName='NameElements')] - [Parameter(Mandatory, ParameterSetName='IssueElements')] - [Parameter(Mandatory, ParameterSetName='MilestoneElements')] + [Parameter( + Mandatory, + ParameterSetName='Elements')] [string] $RepositoryName, - [Parameter(Mandatory, ParameterSetName='Uri')] - [Parameter(Mandatory, ParameterSetName='NameUri')] - [Parameter(Mandatory, ParameterSetName='IssueUri')] - [Parameter(Mandatory, ParameterSetName='MilestoneUri')] + [Parameter( + Mandatory, + ValueFromPipelineByPropertyName, + ParameterSetName='Uri')] + [Alias('RepositoryUrl')] [string] $Uri, - [Parameter(Mandatory, ParameterSetName='NameUri')] - [Parameter(Mandatory, ParameterSetName='NameElements')] + [Parameter(ValueFromPipelineByPropertyName)] + [ValidateNotNullOrEmpty()] [Alias('LabelName')] - [string] $Name, + [string] $Label, - [Parameter(Mandatory, ParameterSetName='IssueUri')] - [Parameter(Mandatory, ParameterSetName='IssueElements')] + [Parameter(ValueFromPipelineByPropertyName)] + [Alias('IssueNumber')] [int64] $Issue, - [Parameter(Mandatory, ParameterSetName='MilestoneUri')] - [Parameter(Mandatory, ParameterSetName='MilestoneElements')] - [int64] $Milestone, + [Parameter(ValueFromPipelineByPropertyName)] + [int64] $MilestoneNumber, [string] $AccessToken, @@ -108,6 +136,23 @@ function Get-GitHubLabel 'RepositoryName' = (Get-PiiSafeString -PlainText $RepositoryName) } + # There were a lot of complications trying to get pipelining working right when using all of + # the necessary ParameterSets, so we'll do internal parameter validation instead until someone + # can figure out the right way to do the parameter sets here _with_ pipeline support. + if ($PSBoundParameters.ContainsKey('Label') -or + $PSBoundParameters.ContainsKey('Issue') -or + $PSBoundParameters.ContainsKey('MilestoneNumber')) + { + if (-not ($PSBoundParameters.ContainsKey('Label') -xor + $PSBoundParameters.ContainsKey('Issue') -xor + $PSBoundParameters.ContainsKey('MilestoneNumber'))) + { + $message = 'Label, Issue and Milestone are mutually exclusive. Only one can be specified in a single command.' + Write-Log -Message $message -Level Error + throw $message + } + } + $uriFragment = [String]::Empty $description = [String]::Empty @@ -116,18 +161,18 @@ function Get-GitHubLabel $uriFragment = "/repos/$OwnerName/$RepositoryName/issues/$Issue/labels" $description = "Getting labels for Issue $Issue in $RepositoryName" } - elseif ($PSBoundParameters.ContainsKey('Milestone')) + elseif ($PSBoundParameters.ContainsKey('MilestoneNumber')) { - $uriFragment = "/repos/$OwnerName/$RepositoryName/milestones/$Milestone/labels" - $description = "Getting labels for Milestone $Milestone in $RepositoryName" + $uriFragment = "/repos/$OwnerName/$RepositoryName/milestones/$MilestoneNumber/labels" + $description = "Getting labels for issues in Milestone $MilestoneNumber in $RepositoryName" } else { - $uriFragment = "repos/$OwnerName/$RepositoryName/labels/$Name" + $uriFragment = "repos/$OwnerName/$RepositoryName/labels/$Label" if ($PSBoundParameters.ContainsKey('Name')) { - $description = "Getting label $Name for $RepositoryName" + $description = "Getting label $Label for $RepositoryName" } else { @@ -138,22 +183,17 @@ function Get-GitHubLabel $params = @{ 'UriFragment' = $uriFragment 'Description' = $description - 'AcceptHeader' = 'application/vnd.github.symmetra-preview+json' + 'AcceptHeader' = $script:symmetraAcceptHeader 'AccessToken' = $AccessToken 'TelemetryEventName' = $MyInvocation.MyCommand.Name 'TelemetryProperties' = $telemetryProperties 'NoStatus' = (Resolve-ParameterWithDefaultConfigurationValue -Name NoStatus -ConfigValueName DefaultNoStatus) } - if (-not [String]::IsNullOrWhiteSpace($Name)) - { - $params["Description"] = "Getting label $Name for $RepositoryName" - } - - return Invoke-GHRestMethodMultipleResult @params + return (Invoke-GHRestMethodMultipleResult @params | Add-GitHubLabelAdditionalProperties) } -function New-GitHubLabel +filter New-GitHubLabel { <# .SYNOPSIS @@ -177,12 +217,13 @@ function New-GitHubLabel The OwnerName and RepositoryName will be extracted from here instead of needing to provide them individually. - .PARAMETER Name + .PARAMETER Label Name of the label to be created. - Emoji and codes are supported. For more information, see here: https://www.webpagefx.com/tools/emoji-cheat-sheet/ + Emoji and codes are supported. + For more information, see here: https://www.webpagefx.com/tools/emoji-cheat-sheet/ .PARAMETER Color - Color (in HEX) for the new label, without the leading # sign. + Color (in HEX) for the new label. .PARAMETER Description A short description of the label. @@ -197,14 +238,33 @@ function New-GitHubLabel the background, enabling the command prompt to provide status information. If not supplied here, the DefaultNoStatus configuration property value will be used. + .INPUTS + GitHub.Branch + GitHub.Content + GitHub.Event + GitHub.Issue + GitHub.IssueComment + GitHub.Label + GitHub.Milestone + GitHub.PullRequest + GitHub.Project + GitHub.ProjectCard + GitHub.ProjectColumn + GitHub.Release + GitHub.Repository + + .OUTPUTS + GitHub.Label + .EXAMPLE - New-GitHubLabel -OwnerName Microsoft -RepositoryName PowerShellForGitHub -Name TestLabel -Color BBBBBB + New-GitHubLabel -OwnerName microsoft -RepositoryName PowerShellForGitHub -Label TestLabel -Color BBBBBB Creates a new, grey-colored label called "TestLabel" in the PowerShellForGitHub project. #> [CmdletBinding( SupportsShouldProcess, DefaultParameterSetName='Elements')] + [OutputType({$script:GitHubLabelTypeName})] [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSShouldProcess", "", Justification="Methods called within here make use of PSShouldProcess, and the switch is passed on to them inherently.")] [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSReviewUnusedParameter", "", Justification="One or more parameters (like NoStatus) are only referenced by helper methods which get access to it from the stack via Get-Variable -Scope 1.")] param( @@ -216,16 +276,20 @@ function New-GitHubLabel [Parameter( Mandatory, + ValueFromPipelineByPropertyName, ParameterSetName='Uri')] + [Alias('RepositoryUrl')] [string] $Uri, - [Parameter(Mandatory)] + [Parameter( + Mandatory, + ValueFromPipeline)] [Alias('LabelName')] - [string] $Name, + [string] $Label, [Parameter(Mandatory)] - [Alias('LabelColor')] [ValidateScript({if ($_ -match '^#?[ABCDEF0-9]{6}$') { $true } else { throw "Color must be provided in hex." }})] + [Alias('LabelColor')] [string] $Color = "EEEEEE", [string] $Description, @@ -254,7 +318,7 @@ function New-GitHubLabel } $hashBody = @{ - 'name' = $Name + 'name' = $Label 'color' = $Color 'description' = $Description } @@ -263,18 +327,18 @@ function New-GitHubLabel 'UriFragment' = "repos/$OwnerName/$RepositoryName/labels" 'Body' = (ConvertTo-Json -InputObject $hashBody) 'Method' = 'Post' - 'Description' = "Creating label $Name in $RepositoryName" - 'AcceptHeader' = 'application/vnd.github.symmetra-preview+json' + 'Description' = "Creating label $Label in $RepositoryName" + 'AcceptHeader' = $script:symmetraAcceptHeader 'AccessToken' = $AccessToken 'TelemetryEventName' = $MyInvocation.MyCommand.Name 'TelemetryProperties' = $telemetryProperties 'NoStatus' = (Resolve-ParameterWithDefaultConfigurationValue -Name NoStatus -ConfigValueName DefaultNoStatus) } - return Invoke-GHRestMethod @params + return (Invoke-GHRestMethod @params | Add-GitHubLabelAdditionalProperties) } -function Remove-GitHubLabel +filter Remove-GitHubLabel { <# .SYNOPSIS @@ -298,7 +362,7 @@ function Remove-GitHubLabel The OwnerName and RepositoryName will be extracted from here instead of needing to provide them individually. - .PARAMETER Name + .PARAMETER Label Name of the label to be deleted. Emoji and codes are supported. For more information, see here: https://www.webpagefx.com/tools/emoji-cheat-sheet/ @@ -315,20 +379,44 @@ function Remove-GitHubLabel the background, enabling the command prompt to provide status information. If not supplied here, the DefaultNoStatus configuration property value will be used. + .INPUTS + GitHub.Branch + GitHub.Content + GitHub.Event + GitHub.Issue + GitHub.IssueComment + GitHub.Label + GitHub.Milestone + GitHub.PullRequest + GitHub.Project + GitHub.ProjectCard + GitHub.ProjectColumn + GitHub.Release + GitHub.Repository + .EXAMPLE - Remove-GitHubLabel -OwnerName Microsoft -RepositoryName PowerShellForGitHub -Name TestLabel + Remove-GitHubLabel -OwnerName microsoft -RepositoryName PowerShellForGitHub -Label TestLabel Removes the label called "TestLabel" from the PowerShellForGitHub project. .EXAMPLE - Remove-GitHubLabel -OwnerName Microsoft -RepositoryName PowerShellForGitHub -Name TestLabel -Confirm:$false + $label = $repo | Get-GitHubLabel -Label 'Test Label' -Color '#AAAAAA' + $label | Remove-GitHubLabel + + Removes the label we just created using the pipeline, but will prompt for confirmation + because neither -Confirm:$false nor -Force was specified. - Removes the label called "TestLabel" from the PowerShellForGitHub project. Will not prompt for confirmation, as -Confirm:$false was specified. + .EXAMPLE + Remove-GitHubLabel -OwnerName microsoft -RepositoryName PowerShellForGitHub -Label TestLabel -Confirm:$false + + Removes the label called "TestLabel" from the PowerShellForGitHub project. + Will not prompt for confirmation, as -Confirm:$false was specified. .EXAMPLE - Remove-GitHubLabel -OwnerName Microsoft -RepositoryName PowerShellForGitHub -Name TestLabel -Force + Remove-GitHubLabel -OwnerName microsoft -RepositoryName PowerShellForGitHub -Label TestLabel -Force - Removes the label called "TestLabel" from the PowerShellForGitHub project. Will not prompt for confirmation, as -Force was specified. + Removes the label called "TestLabel" from the PowerShellForGitHub project. + Will not prompt for confirmation, as -Force was specified. #> [CmdletBinding( SupportsShouldProcess, @@ -345,13 +433,18 @@ function Remove-GitHubLabel [Parameter( Mandatory, + ValueFromPipelineByPropertyName, ParameterSetName='Uri')] + [Alias('RepositoryUrl')] [string] $Uri, - [Parameter(Mandatory)] + [Parameter( + Mandatory, + ValueFromPipeline, + ValueFromPipelineByPropertyName)] [ValidateNotNullOrEmpty()] [Alias('LabelName')] - [string] $Name, + [string] $Label, [switch] $Force, @@ -376,13 +469,13 @@ function Remove-GitHubLabel $ConfirmPreference = 'None' } - if ($PSCmdlet.ShouldProcess($Name, "Remove label")) + if ($PSCmdlet.ShouldProcess($Label, "Remove label")) { $params = @{ - 'UriFragment' = "repos/$OwnerName/$RepositoryName/labels/$Name" + 'UriFragment' = "repos/$OwnerName/$RepositoryName/labels/$Label" 'Method' = 'Delete' - 'Description' = "Deleting label $Name from $RepositoryName" - 'AcceptHeader' = 'application/vnd.github.symmetra-preview+json' + 'Description' = "Deleting label $Label from $RepositoryName" + 'AcceptHeader' = $script:symmetraAcceptHeader 'AccessToken' = $AccessToken 'TelemetryEventName' = $MyInvocation.MyCommand.Name 'TelemetryProperties' = $telemetryProperties @@ -393,7 +486,7 @@ function Remove-GitHubLabel } } -function Update-GitHubLabel +filter Update-GitHubLabel { <# .SYNOPSIS @@ -417,16 +510,18 @@ function Update-GitHubLabel The OwnerName and RepositoryName will be extracted from here instead of needing to provide them individually. - .PARAMETER Name + .PARAMETER Label Current name of the label to be updated. - Emoji and codes are supported. For more information, see here: https://www.webpagefx.com/tools/emoji-cheat-sheet/ + Emoji and codes are supported. + For more information, see here: https://www.webpagefx.com/tools/emoji-cheat-sheet/ .PARAMETER NewName New name for the label being updated. - Emoji and codes are supported. For more information, see here: https://www.webpagefx.com/tools/emoji-cheat-sheet/ + Emoji and codes are supported. + For more information, see here: https://www.webpagefx.com/tools/emoji-cheat-sheet/ .PARAMETER Color - Color (in HEX) for the new label, without the leading # sign. + Color (in HEX) for the new label. .PARAMETER Description A short description of the label. @@ -441,8 +536,26 @@ function Update-GitHubLabel the background, enabling the command prompt to provide status information. If not supplied here, the DefaultNoStatus configuration property value will be used. + .INPUTS + GitHub.Branch + GitHub.Content + GitHub.Event + GitHub.Issue + GitHub.IssueComment + GitHub.Label + GitHub.Milestone + GitHub.PullRequest + GitHub.Project + GitHub.ProjectCard + GitHub.ProjectColumn + GitHub.Release + GitHub.Repository + + .OUTPUTS + GitHub.Label + .EXAMPLE - Update-GitHubLabel -OwnerName Microsoft -RepositoryName PowerShellForGitHub -Name TestLabel -NewName NewTestLabel -LabelColor BBBB00 + Update-GitHubLabel -OwnerName microsoft -RepositoryName PowerShellForGitHub -Label TestLabel -NewName NewTestLabel -Color BBBB00 Updates the existing label called TestLabel in the PowerShellForGitHub project to be called 'NewTestLabel' and be colored yellow. @@ -450,6 +563,7 @@ function Update-GitHubLabel [CmdletBinding( SupportsShouldProcess, DefaultParameterSetName='Elements')] + [OutputType({$script:GitHubLabelTypeName})] [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSShouldProcess", "", Justification="Methods called within here make use of PSShouldProcess, and the switch is passed on to them inherently.")] param( [Parameter(ParameterSetName='Elements')] @@ -460,18 +574,20 @@ function Update-GitHubLabel [Parameter( Mandatory, + ValueFromPipelineByPropertyName, ParameterSetName='Uri')] + [Alias('RepositoryUrl')] [string] $Uri, - [Parameter(Mandatory)] + [Parameter( + Mandatory, + ValueFromPipelineByPropertyName)] [Alias('LabelName')] - [string] $Name, + [string] $Label, - [Parameter(Mandatory)] [Alias('NewLabelName')] [string] $NewName, - [Parameter(Mandatory)] [Alias('LabelColor')] [ValidateScript({if ($_ -match '^#?[ABCDEF0-9]{6}$') { $true } else { throw "Color must be provided in hex." }})] [string] $Color = "EEEEEE", @@ -494,27 +610,34 @@ function Update-GitHubLabel 'RepositoryName' = (Get-PiiSafeString -PlainText $RepositoryName) } + # Be robust to users who choose to provide a color in hex by specifying the leading # sign + # (by just stripping it out). + if ($Color.StartsWith('#')) + { + $Color = $Color.Substring(1) + } + $hashBody = @{} if ($PSBoundParameters.ContainsKey('NewName')) { $hashBody['name'] = $NewName } - if ($PSBoundParameters.ContainsKey('Color')) { $hashBody['color'] = $Color } if ($PSBoundParameters.ContainsKey('Description')) { $hashBody['description'] = $Description } + if ($PSBoundParameters.ContainsKey('Color')) { $hashBody['color'] = $Color } $params = @{ - 'UriFragment' = "repos/$OwnerName/$RepositoryName/labels/$Name" + 'UriFragment' = "repos/$OwnerName/$RepositoryName/labels/$Label" 'Body' = (ConvertTo-Json -InputObject $hashBody) 'Method' = 'Patch' - 'Description' = "Updating label $Name" - 'AcceptHeader' = 'application/vnd.github.symmetra-preview+json' + 'Description' = "Updating label $Label" + 'AcceptHeader' = $script:symmetraAcceptHeader 'AccessToken' = $AccessToken 'TelemetryEventName' = $MyInvocation.MyCommand.Name 'TelemetryProperties' = $telemetryProperties 'NoStatus' = (Resolve-ParameterWithDefaultConfigurationValue -Name NoStatus -ConfigValueName DefaultNoStatus) } - return Invoke-GHRestMethod @params + return (Invoke-GHRestMethod @params | Add-GitHubLabelAdditionalProperties) } -function Set-GitHubLabel +filter Set-GitHubLabel { <# .SYNOPSIS @@ -558,19 +681,34 @@ function Set-GitHubLabel the background, enabling the command prompt to provide status information. If not supplied here, the DefaultNoStatus configuration property value will be used. - .NOTES - This method does not rename any existing labels, as it doesn't have any context regarding - which Issue the new name is for. Therefore, it is possible that by running this function - on a repository with Issues that have already been assigned Labels, you may experience data - loss as a minor correction to you (maybe fixing a typo) will result in the old Label being - removed (and thus unassigned from existing Issues) and then the new one created. + .INPUTS + GitHub.Branch + GitHub.Content + GitHub.Event + GitHub.Issue + GitHub.IssueComment + GitHub.Label + GitHub.Milestone + GitHub.PullRequest + GitHub.Project + GitHub.ProjectCard + GitHub.ProjectColumn + GitHub.Release + GitHub.Repository .EXAMPLE - Set-GitHubLabel -OwnerName Microsoft -RepositoryName PowerShellForGitHub -Label @(@{'name' = 'TestLabel'; 'color' = 'EEEEEE'}, @{'name' = 'critical'; 'color' = 'FF000000'; 'description' = 'Needs immediate attention'}) + Set-GitHubLabel -OwnerName microsoft -RepositoryName PowerShellForGitHub -Label @(@{'name' = 'TestLabel'; 'color' = 'EEEEEE'}, @{'name' = 'critical'; 'color' = 'FF000000'; 'description' = 'Needs immediate attention'}) Removes any labels not in this Label array, ensure the current assigned color and descriptions match what's in the array for the labels that do already exist, and then creates new labels for any remaining ones in the Label list. + + .NOTES + This method does not rename any existing labels, as it doesn't have any context regarding + which Label the new name is for. Therefore, it is possible that by running this function + on a repository with Issues that have already been assigned Labels, you may experience data + loss as a minor correction to you (maybe fixing a typo) will result in the old Label being + removed (and thus unassigned from existing Issues) and then the new one created. #> [CmdletBinding( SupportsShouldProcess, @@ -586,9 +724,12 @@ function Set-GitHubLabel [Parameter( Mandatory, + ValueFromPipelineByPropertyName, ParameterSetName='Uri')] + [Alias('RepositoryUrl')] [string] $Uri, + [Parameter(ValueFromPipelineByPropertyName)] [object[]] $Label, [string] $AccessToken, @@ -623,12 +764,12 @@ function Set-GitHubLabel if ($labelToConfigure.name -notin $existingLabelNames) { # Create label if it doesn't exist - $null = New-GitHubLabel -Name $labelToConfigure.name -Color $labelToConfigure.color @commonParams + $null = New-GitHubLabel -Label $labelToConfigure.name -Color $labelToConfigure.color @commonParams } else { # Update label's color if it already exists - $null = Update-GitHubLabel -Name $labelToConfigure.name -NewName $labelToConfigure.name -Color $labelToConfigure.color @commonParams + $null = Update-GitHubLabel -Label $labelToConfigure.name -NewName $labelToConfigure.name -Color $labelToConfigure.color @commonParams } } @@ -637,7 +778,7 @@ function Set-GitHubLabel if ($labelName -notin $labelNames) { # Remove label if it exists but is not in desired label list - $null = Remove-GitHubLabel -Name $labelName @commonParams -Confirm:$false + $null = Remove-GitHubLabel -Label $labelName @commonParams -Confirm:$false } } } @@ -666,7 +807,7 @@ function Add-GitHubIssueLabel .PARAMETER Issue Issue number to add the label to. - .PARAMETER Name + .PARAMETER Label Array of label names to add to the issue .PARAMETER AccessToken @@ -679,69 +820,124 @@ function Add-GitHubIssueLabel the background, enabling the command prompt to provide status information. If not supplied here, the DefaultNoStatus configuration property value will be used. + .INPUTS + GitHub.Branch + GitHub.Content + GitHub.Event + GitHub.Issue + GitHub.IssueComment + GitHub.Label + GitHub.Milestone + GitHub.PullRequest + GitHub.Project + GitHub.ProjectCard + GitHub.ProjectColumn + GitHub.Release + GitHub.Repository + + .OUTPUTS + GitHub.Label + .EXAMPLE - Add-GitHubIssueLabel -OwnerName Microsoft -RepositoryName PowerShellForGitHub -Issue 1 -Name $labels + Add-GitHubIssueLabel -OwnerName microsoft -RepositoryName PowerShellForGitHub -Issue 1 -Label $labels Adds labels to an issue in the PowerShellForGitHub project. + + .NOTES + This is implemented as a function rather than a filter because the ValueFromPipeline + parameter (Name) is itself an array which we want to ensure is processed only a single time. + This API endpoint doesn't add labels to a repository, it replaces the existing labels with + the new set provided, so we need to make sure that we have all the requested labels available + to us at the time that the API endpoint is called. #> [CmdletBinding( SupportsShouldProcess, DefaultParameterSetName='Elements')] + [OutputType({$script:GitHubLabelTypeName})] [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSShouldProcess", "", Justification="Methods called within here make use of PSShouldProcess, and the switch is passed on to them inherently.")] [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSReviewUnusedParameter", "", Justification="One or more parameters (like NoStatus) are only referenced by helper methods which get access to it from the stack via Get-Variable -Scope 1.")] param( - [Parameter(Mandatory, ParameterSetName='Elements')] + [Parameter( + Mandatory, + ParameterSetName='Elements')] [string] $OwnerName, - [Parameter(Mandatory, ParameterSetName='Elements')] + [Parameter( + Mandatory, + ParameterSetName='Elements')] [string] $RepositoryName, [Parameter( Mandatory, + ValueFromPipelineByPropertyName, ParameterSetName='Uri')] + [Alias('RepositoryUrl')] [string] $Uri, - [Parameter(Mandatory)] + [Parameter( + Mandatory, + ValueFromPipelineByPropertyName)] + [Alias('IssueNumber')] [int64] $Issue, - [Parameter(Mandatory)] + [Parameter( + Mandatory, + ValueFromPipeline, + ValueFromPipelineByPropertyName)] [Alias('LabelName')] - [string[]] $Name, + [ValidateNotNullOrEmpty()] + [string[]] $Label, [string] $AccessToken, [switch] $NoStatus ) - Write-InvocationLog - - $elements = Resolve-RepositoryElements - $OwnerName = $elements.ownerName - $RepositoryName = $elements.repositoryName - - $telemetryProperties = @{ - 'OwnerName' = (Get-PiiSafeString -PlainText $OwnerName) - 'RepositoryName' = (Get-PiiSafeString -PlainText $RepositoryName) - 'LabelCount' = $Name.Count + begin + { + $labelNames = @() } - $hashBody = @{ - 'labels' = $Name + process + { + foreach ($name in $Label) + { + $labelNames += $name + } } - $params = @{ - 'UriFragment' = "repos/$OwnerName/$RepositoryName/issues/$Issue/labels" - 'Body' = (ConvertTo-Json -InputObject $hashBody) - 'Method' = 'Post' - 'Description' = "Adding labels to issue $Issue in $RepositoryName" - 'AcceptHeader' = 'application/vnd.github.symmetra-preview+json' - 'AccessToken' = $AccessToken - 'TelemetryEventName' = $MyInvocation.MyCommand.Name - 'TelemetryProperties' = $telemetryProperties - 'NoStatus' = (Resolve-ParameterWithDefaultConfigurationValue -Name NoStatus -ConfigValueName DefaultNoStatus) - } + end + { + Write-InvocationLog + + $elements = Resolve-RepositoryElements + $OwnerName = $elements.ownerName + $RepositoryName = $elements.repositoryName + + $telemetryProperties = @{ + 'OwnerName' = (Get-PiiSafeString -PlainText $OwnerName) + 'RepositoryName' = (Get-PiiSafeString -PlainText $RepositoryName) + 'LabelCount' = $Label.Count + } + + $hashBody = @{ + 'labels' = $labelNames + } + + $params = @{ + 'UriFragment' = "repos/$OwnerName/$RepositoryName/issues/$Issue/labels" + 'Body' = (ConvertTo-Json -InputObject $hashBody) + 'Method' = 'Post' + 'Description' = "Adding labels to issue $Issue in $RepositoryName" + 'AcceptHeader' = $script:symmetraAcceptHeader + 'AccessToken' = $AccessToken + 'TelemetryEventName' = $MyInvocation.MyCommand.Name + 'TelemetryProperties' = $telemetryProperties + 'NoStatus' = (Resolve-ParameterWithDefaultConfigurationValue -Name NoStatus -ConfigValueName DefaultNoStatus) + } - return Invoke-GHRestMethod @params + return (Invoke-GHRestMethod @params | Add-GitHubLabelAdditionalProperties) + } } function Set-GitHubIssueLabel @@ -768,9 +964,12 @@ function Set-GitHubIssueLabel .PARAMETER Issue Issue number to replace the labels. - .PARAMETER LabelName + .PARAMETER Label Array of label names that will be set on the issue. + .PARAMETER Force + If this switch is specified, you will not be prompted for confirmation of command execution. + .PARAMETER AccessToken If provided, this will be used as the AccessToken for authentication with the REST Api. Otherwise, will attempt to use the configured value or will run unauthenticated. @@ -781,14 +980,65 @@ function Set-GitHubIssueLabel the background, enabling the command prompt to provide status information. If not supplied here, the DefaultNoStatus configuration property value will be used. + .INPUTS + GitHub.Branch + GitHub.Content + GitHub.Event + GitHub.Issue + GitHub.IssueComment + GitHub.Label + GitHub.Milestone + GitHub.PullRequest + GitHub.Project + GitHub.ProjectCard + GitHub.ProjectColumn + GitHub.Release + GitHub.Repository + + .OUTPUTS + GitHub.Label + .EXAMPLE - Set-GitHubIssueLabel -OwnerName Microsoft -RepositoryName PowerShellForGitHub -Issue 1 -LabelName $labels + Set-GitHubIssueLabel -OwnerName microsoft -RepositoryName PowerShellForGitHub -Issue 1 -Label $labels Replaces labels on an issue in the PowerShellForGitHub project. + + .EXAMPLE + ('help wanted', 'good first issue') | Set-GitHubIssueLabel -OwnerName microsoft -RepositoryName PowerShellForGitHub -Issue 1 + + Replaces labels on an issue in the PowerShellForGitHub project + with 'help wanted' and 'good first issue'. + + .EXAMPLE + Set-GitHubIssueLabel -OwnerName microsoft -RepositoryName PowerShellForGitHub -Issue 1 -Confirm:$false + + Removes all labels from issue 1 in the PowerShellForGitHub project. + Will not prompt for confirmation, as -Confirm:$false was specified. + + This is the same result as having called + Remove-GitHubIssueLabel -OwnerName microsoft -RepositoryName PowerShellForGitHub -Issue 1 -Confirm:$false + + .EXAMPLE + Set-GitHubIssueLabel -OwnerName microsoft -RepositoryName PowerShellForGitHub -Issue 1 -Force + + Removes all labels from issue 1 in the PowerShellForGitHub project. + Will not prompt for confirmation, as -Force was specified. + + This is the same result as having called + Remove-GitHubIssueLabel -OwnerName microsoft -RepositoryName PowerShellForGitHub -Issue 1 -Force + + .NOTES + This is implemented as a function rather than a filter because the ValueFromPipeline + parameter (Name) is itself an array which we want to ensure is processed only a single time. + This API endpoint doesn't add labels to a repository, it replaces the existing labels with + the new set provided, so we need to make sure that we have all the requested labels available + to us at the time that the API endpoint is called. #> [CmdletBinding( SupportsShouldProcess, - DefaultParameterSetName='Elements')] + DefaultParameterSetName='Elements', + ConfirmImpact='High')] + [OutputType({$script:GitHubLabelTypeName})] [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSShouldProcess", "", Justification="Methods called within here make use of PSShouldProcess, and the switch is passed on to them inherently.")] [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSReviewUnusedParameter", "", Justification="One or more parameters (like NoStatus) are only referenced by helper methods which get access to it from the stack via Get-Variable -Scope 1.")] param( @@ -800,53 +1050,86 @@ function Set-GitHubIssueLabel [Parameter( Mandatory, + ValueFromPipelineByPropertyName, ParameterSetName='Uri')] + [Alias('RepositoryUrl')] [string] $Uri, - [Parameter(Mandatory)] + [Parameter( + Mandatory, + ValueFromPipelineByPropertyName)] + [Alias('IssueNumber')] [int64] $Issue, - [Parameter(Mandatory)] + [Parameter(ValueFromPipelineByPropertyName)] [Alias('LabelName')] - [string[]] $Name, + [string[]] $Label, + + [switch] $Force, [string] $AccessToken, [switch] $NoStatus ) - Write-InvocationLog - - $elements = Resolve-RepositoryElements - $OwnerName = $elements.ownerName - $RepositoryName = $elements.repositoryName - - $telemetryProperties = @{ - 'OwnerName' = (Get-PiiSafeString -PlainText $OwnerName) - 'RepositoryName' = (Get-PiiSafeString -PlainText $RepositoryName) - 'LabelCount' = $Name.Count + begin + { + $labelNames = @() } - $hashBody = @{ - 'labels' = $Name + process + { + foreach ($name in $Label) + { + $labelNames += $name + } } - $params = @{ - 'UriFragment' = "repos/$OwnerName/$RepositoryName/issues/$Issue/labels" - 'Body' = (ConvertTo-Json -InputObject $hashBody) - 'Method' = 'Put' - 'Description' = "Replacing labels to issue $Issue in $RepositoryName" - 'AcceptHeader' = 'application/vnd.github.symmetra-preview+json' - 'AccessToken' = $AccessToken - 'TelemetryEventName' = $MyInvocation.MyCommand.Name - 'TelemetryProperties' = $telemetryProperties - 'NoStatus' = (Resolve-ParameterWithDefaultConfigurationValue -Name NoStatus -ConfigValueName DefaultNoStatus) - } + end + { + Write-InvocationLog + + $elements = Resolve-RepositoryElements + $OwnerName = $elements.ownerName + $RepositoryName = $elements.repositoryName + + $telemetryProperties = @{ + 'OwnerName' = (Get-PiiSafeString -PlainText $OwnerName) + 'RepositoryName' = (Get-PiiSafeString -PlainText $RepositoryName) + 'LabelCount' = $Label.Count + } - return Invoke-GHRestMethod @params + $hashBody = @{ + 'labels' = $labelNames + } + + if ($Force -and (-not $Confirm)) + { + $ConfirmPreference = 'None' + } + + if (($labelNames.Count -eq 0) -and (-not $PSCmdlet.ShouldProcess($Issue, "Remove all labels from issue"))) + { + return + } + + $params = @{ + 'UriFragment' = "repos/$OwnerName/$RepositoryName/issues/$Issue/labels" + 'Body' = (ConvertTo-Json -InputObject $hashBody) + 'Method' = 'Put' + 'Description' = "Replacing labels to issue $Issue in $RepositoryName" + 'AcceptHeader' = $script:symmetraAcceptHeader + 'AccessToken' = $AccessToken + 'TelemetryEventName' = $MyInvocation.MyCommand.Name + 'TelemetryProperties' = $telemetryProperties + 'NoStatus' = (Resolve-ParameterWithDefaultConfigurationValue -Name NoStatus -ConfigValueName DefaultNoStatus) + } + + return (Invoke-GHRestMethod @params | Add-GitHubLabelAdditionalProperties) + } } -function Remove-GitHubIssueLabel +filter Remove-GitHubIssueLabel { <# .DESCRIPTION @@ -870,9 +1153,10 @@ function Remove-GitHubIssueLabel .PARAMETER Issue Issue number to remove the label from. - .PARAMETER Name + .PARAMETER Label Name of the label to be deleted. If not provided, will delete all labels on the issue. - Emoji and codes are supported. For more information, see here: https://www.webpagefx.com/tools/emoji-cheat-sheet/ + Emoji and codes are supported. + For more information, see here: https://www.webpagefx.com/tools/emoji-cheat-sheet/ .PARAMETER Force If this switch is specified, you will not be prompted for confirmation of command execution. @@ -887,20 +1171,37 @@ function Remove-GitHubIssueLabel the background, enabling the command prompt to provide status information. If not supplied here, the DefaultNoStatus configuration property value will be used. + .INPUTS + GitHub.Branch + GitHub.Content + GitHub.Event + GitHub.Issue + GitHub.IssueComment + GitHub.Label + GitHub.Milestone + GitHub.PullRequest + GitHub.Project + GitHub.ProjectCard + GitHub.ProjectColumn + GitHub.Release + GitHub.Repository + .EXAMPLE - Remove-GitHubIssueLabel -OwnerName Microsoft -RepositoryName PowerShellForGitHub -Name TestLabel -Issue 1 + Remove-GitHubIssueLabel -OwnerName microsoft -RepositoryName PowerShellForGitHub -Label TestLabel -Issue 1 Removes the label called "TestLabel" from issue 1 in the PowerShellForGitHub project. .EXAMPLE - Remove-GitHubIssueLabel -OwnerName Microsoft -RepositoryName PowerShellForGitHub -Name TestLabel -Issue 1 -Confirm:$false + Remove-GitHubIssueLabel -OwnerName microsoft -RepositoryName PowerShellForGitHub -Label TestLabel -Issue 1 -Confirm:$false - Removes the label called "TestLabel" from issue 1 in the PowerShellForGitHub project. Will not prompt for confirmation, as -Confirm:$false was specified. + Removes the label called "TestLabel" from issue 1 in the PowerShellForGitHub project. + Will not prompt for confirmation, as -Confirm:$false was specified. .EXAMPLE - Remove-GitHubIssueLabel -OwnerName Microsoft -RepositoryName PowerShellForGitHub -Name TestLabel -Issue 1 -Force + Remove-GitHubIssueLabel -OwnerName microsoft -RepositoryName PowerShellForGitHub -Label TestLabel -Issue 1 -Force - Removes the label called "TestLabel" from issue 1 in the PowerShellForGitHub project. Will not prompt for confirmation, as -Force was specified. + Removes the label called "TestLabel" from issue 1 in the PowerShellForGitHub project. + Will not prompt for confirmation, as -Force was specified. #> [CmdletBinding( SupportsShouldProcess, @@ -908,21 +1209,33 @@ function Remove-GitHubIssueLabel ConfirmImpact="High")] [Alias('Delete-GitHubLabel')] param( - [Parameter(Mandatory, ParameterSetName='Elements')] + [Parameter( + Mandatory, + ParameterSetName='Elements')] [string] $OwnerName, - [Parameter(Mandatory, ParameterSetName='Elements')] + [Parameter( + Mandatory, + ParameterSetName='Elements')] [string] $RepositoryName, - [Parameter(Mandatory, ParameterSetName='Uri')] + [Parameter( + Mandatory, + ValueFromPipelineByPropertyName, + ParameterSetName='Uri')] + [Alias('RepositoryUrl')] [string] $Uri, - [Parameter(Mandatory)] + [Parameter( + Mandatory, + ValueFromPipelineByPropertyName)] + [Alias('IssueNumber')] [int64] $Issue, + [Parameter(ValueFromPipelineByPropertyName)] [ValidateNotNullOrEmpty()] [Alias('LabelName')] - [string] $Name, + [string] $Label, [switch] $Force, @@ -943,10 +1256,9 @@ function Remove-GitHubIssueLabel } $description = [String]::Empty - - if ($PSBoundParameters.ContainsKey('Name')) + if ($PSBoundParameters.ContainsKey('Label')) { - $description = "Deleting label $Name from issue $Issue in $RepositoryName" + $description = "Deleting label $Label from issue $Issue in $RepositoryName" } else { @@ -958,13 +1270,13 @@ function Remove-GitHubIssueLabel $ConfirmPreference = 'None' } - if ($PSCmdlet.ShouldProcess($Name, "Remove label")) + if ($PSCmdlet.ShouldProcess($Label, "Remove label")) { $params = @{ - 'UriFragment' = "/repos/$OwnerName/$RepositoryName/issues/$Issue/labels/$Name" + 'UriFragment' = "/repos/$OwnerName/$RepositoryName/issues/$Issue/labels/$Label" 'Method' = 'Delete' 'Description' = $description - 'AcceptHeader' = 'application/vnd.github.symmetra-preview+json' + 'AcceptHeader' = $script:symmetraAcceptHeader 'AccessToken' = $AccessToken 'TelemetryEventName' = $MyInvocation.MyCommand.Name 'TelemetryProperties' = $telemetryProperties @@ -975,6 +1287,60 @@ function Remove-GitHubIssueLabel } } +filter Add-GitHubLabelAdditionalProperties +{ +<# + .SYNOPSIS + Adds type name and additional properties to ease pipelining to GitHub Label objects. + + .PARAMETER InputObject + The GitHub object to add additional properties to. + + .PARAMETER TypeName + The type that should be assigned to the object. + + .INPUTS + [PSCustomObject] + + .OUTPUTS + GitHub.Label +#> + [CmdletBinding()] + [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSUseSingularNouns", "", Justification="Internal helper that is definitely adding more than one property.")] + param( + [Parameter( + Mandatory, + ValueFromPipeline)] + [AllowNull()] + [AllowEmptyCollection()] + [PSCustomObject[]] $InputObject, + + [ValidateNotNullOrEmpty()] + [string] $TypeName = $script:GitHubLabelTypeName + ) + + foreach ($item in $InputObject) + { + $item.PSObject.TypeNames.Insert(0, $TypeName) + + if (-not (Get-GitHubConfiguration -Name DisablePipelineSupport)) + { + $elements = Split-GitHubUri -Uri $item.url + $repositoryUrl = Join-GitHubUri @elements + Add-Member -InputObject $item -Name 'RepositoryUrl' -Value $repositoryUrl -MemberType NoteProperty -Force + + if ($null -ne $item.id) + { + Add-Member -InputObject $item -Name 'LabelId' -Value $item.id -MemberType NoteProperty -Force + } + + Add-Member -InputObject $item -Name 'LabelName' -Value $item.name -MemberType NoteProperty -Force + } + + Write-Output $item + } +} + # A set of labels that a project might want to initially populate their repository with # Used by Set-GitHubLabel when no Label list is provided by the user. # This list exists to support v0.1.0 users. diff --git a/GitHubMilestones.ps1 b/GitHubMilestones.ps1 index 96f5946e..70b5b003 100644 --- a/GitHubMilestones.ps1 +++ b/GitHubMilestones.ps1 @@ -1,15 +1,21 @@ # Copyright (c) Microsoft Corporation. All rights reserved. # Licensed under the MIT License. -# For more information refer to" +@{ + GitHubMilestoneTypeName = 'GitHub.Milestone' + }.GetEnumerator() | ForEach-Object { + Set-Variable -Scope Script -Option ReadOnly -Name $_.Key -Value $_.Value + } + +# For more information refer to: # https://github.community/t5/How-to-use-Git-and-GitHub/Milestone-quot-Due-On-quot-field-defaults-to-7-00-when-set-by-v3/m-p/6901 $script:minimumHoursToEnsureDesiredDateInPacificTime = 9 -function Get-GitHubMilestone +filter Get-GitHubMilestone { <# .DESCRIPTION - Get the milestones for a given Github repository. + Get the milestones for a given GitHub repository. The Git repo for this module can be found here: http://aka.ms/PowerShellForGitHub @@ -27,7 +33,8 @@ function Get-GitHubMilestone them individually. .PARAMETER Milestone - The number of a specific milestone to get. If not supplied, will return back all milestones for this repository. + The number of a specific milestone to get. If not supplied, will return back all milestones + for this repository. .PARAMETER Sort How to sort the results. @@ -48,33 +55,75 @@ function Get-GitHubMilestone the background, enabling the command prompt to provide status information. If not supplied here, the DefaultNoStatus configuration property value will be used. + .INPUTS + GitHub.Branch + GitHub.Content + GitHub.Event + GitHub.Issue + GitHub.IssueComment + GitHub.Label + GitHub.Milestone + GitHub.PullRequest + GitHub.Project + GitHub.ProjectCard + GitHub.ProjectColumn + GitHub.Release + GitHub.Repository + + .OUTPUTS + GitHub.Milestone + .EXAMPLE - Get-GitHubMilestone -OwnerName Microsoft -RepositoryName PowerShellForGitHub - Get the milestones for the Microsoft\PowerShellForGitHub project. + Get-GitHubMilestone -OwnerName microsoft -RepositoryName PowerShellForGitHub + Get the milestones for the microsoft\PowerShellForGitHub project. .EXAMPLE Get-GitHubMilestone -Uri 'https://github.com/PowerShell/PowerShellForGitHub' -Milestone 1 - Get milestone number 1 for the Microsoft\PowerShellForGitHub project. + Get milestone number 1 for the microsoft\PowerShellForGitHub project. #> [CmdletBinding( SupportsShouldProcess, DefaultParameterSetName='RepositoryElements')] [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSShouldProcess", "", Justification="Methods called within here make use of PSShouldProcess, and the switch is passed on to them inherently.")] param( - [Parameter(Mandatory, ParameterSetName='MilestoneElements')] - [Parameter(Mandatory, ParameterSetName='RepositoryElements')] + [Parameter( + Mandatory, + ParameterSetName='MilestoneElements')] + [Parameter( + Mandatory, + ParameterSetName='RepositoryElements')] [string] $OwnerName, - [Parameter(Mandatory, ParameterSetName='MilestoneElements')] - [Parameter(Mandatory, ParameterSetName='RepositoryElements')] + [Parameter( + Mandatory, + ParameterSetName='MilestoneElements')] + [Parameter( + Mandatory, + ParameterSetName='RepositoryElements')] [string] $RepositoryName, - [Parameter(Mandatory, ParameterSetName='MilestoneUri')] - [Parameter(Mandatory, ParameterSetName='RepositoryUri')] + [Parameter( + Mandatory, + ValueFromPipelineByPropertyName, + ParameterSetName='MilestoneUri')] + [Parameter( + Mandatory, + ValueFromPipelineByPropertyName, + ParameterSetName='RepositoryUri')] + [Alias('RepositoryUrl')] [string] $Uri, - [Parameter(Mandatory, ParameterSetName='MilestoneUri')] - [Parameter(Mandatory, ParameterSetName='MilestoneElements')] + [Parameter( + Mandatory, + ValueFromPipeline, + ValueFromPipelineByPropertyName, + ParameterSetName='MilestoneUri')] + [Parameter( + Mandatory, + ValueFromPipeline, + ValueFromPipelineByPropertyName, + ParameterSetName='MilestoneElements')] + [Alias('MilestoneNumber')] [int64] $Milestone, [Parameter(ParameterSetName='RepositoryUri')] @@ -128,16 +177,17 @@ function Get-GitHubMilestone } $getParams += "sort=$($sortConverter[$Sort])" - } - if ($PSBoundParameters.ContainsKey('Direction')) - { - $directionConverter = @{ - 'Ascending' = 'asc' - 'Descending' = 'desc' - } + # We only look at this parameter if the user provided Sort as well. + if ($PSBoundParameters.ContainsKey('Direction')) + { + $directionConverter = @{ + 'Ascending' = 'asc' + 'Descending' = 'desc' + } - $getParams += "direction=$($directionConverter[$Direction])" + $getParams += "direction=$($directionConverter[$Direction])" + } } if ($PSBoundParameters.ContainsKey('State')) @@ -159,14 +209,14 @@ function Get-GitHubMilestone 'NoStatus' = (Resolve-ParameterWithDefaultConfigurationValue -Name NoStatus -ConfigValueName DefaultNoStatus) } - return Invoke-GHRestMethodMultipleResult @params + return (Invoke-GHRestMethodMultipleResult @params | Add-GitHubMilestoneAdditionalProperties) } -function New-GitHubMilestone +filter New-GitHubMilestone { <# .DESCRIPTION - Creates a new Github milestone for the given repository + Creates a new GitHub milestone for the given repository The Git repo for this module can be found here: http://aka.ms/PowerShellForGitHub @@ -207,10 +257,28 @@ function New-GitHubMilestone the background, enabling the command prompt to provide status information. If not supplied here, the DefaultNoStatus configuration property value will be used. + .INPUTS + GitHub.Branch + GitHub.Content + GitHub.Event + GitHub.Issue + GitHub.IssueComment + GitHub.Label + GitHub.Milestone + GitHub.PullRequest + GitHub.Project + GitHub.ProjectCard + GitHub.ProjectColumn + GitHub.Release + GitHub.Repository + + .OUTPUTS + GitHub.Milestone + .EXAMPLE - New-GitHubMilestone -OwnerName Microsoft -RepositoryName PowerShellForGitHub -Title "Testing this API" + New-GitHubMilestone -OwnerName microsoft -RepositoryName PowerShellForGitHub -Title "Testing this API" - Creates a new Github milestone for the Microsoft\PowerShellForGitHub project. + Creates a new GitHub milestone for the microsoft\PowerShellForGitHub project. .NOTES For more information on how GitHub handles the dates specified in DueOn, please refer to @@ -229,17 +297,31 @@ function New-GitHubMilestone DefaultParameterSetName='Elements')] [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSShouldProcess", "", Justification="Methods called within here make use of PSShouldProcess, and the switch is passed on to them inherently.")] param( - [Parameter(Mandatory, ParameterSetName='Elements')] + [Parameter( + Mandatory, + ParameterSetName='Elements')] [string] $OwnerName, - [Parameter(Mandatory, ParameterSetName='Elements')] + [Parameter( + Mandatory, + ParameterSetName='Elements')] [string] $RepositoryName, - [Parameter(Mandatory, ParameterSetName='Uri')] + [Parameter( + Mandatory, + ValueFromPipelineByPropertyName, + ParameterSetName='Uri')] + [Alias('RepositoryUrl')] [string] $Uri, - [Parameter(Mandatory, ParameterSetName='Uri')] - [Parameter(Mandatory, ParameterSetName='Elements')] + [Parameter( + Mandatory, + ValueFromPipeline, + ParameterSetName='Uri')] + [Parameter( + Mandatory, + ValueFromPipeline, + ParameterSetName='Elements')] [string] $Title, [ValidateSet('Open', 'Closed')] @@ -305,10 +387,10 @@ function New-GitHubMilestone 'NoStatus' = (Resolve-ParameterWithDefaultConfigurationValue -Name NoStatus -ConfigValueName DefaultNoStatus) } - return Invoke-GHRestMethod @params + return (Invoke-GHRestMethod @params | Add-GitHubMilestoneAdditionalProperties) } -function Set-GitHubMilestone +filter Set-GitHubMilestone { <# .DESCRIPTION @@ -356,10 +438,28 @@ function Set-GitHubMilestone the background, enabling the command prompt to provide status information. If not supplied here, the DefaultNoStatus configuration property value will be used. + .INPUTS + GitHub.Branch + GitHub.Content + GitHub.Event + GitHub.Issue + GitHub.IssueComment + GitHub.Label + GitHub.Milestone + GitHub.PullRequest + GitHub.Project + GitHub.ProjectCard + GitHub.ProjectColumn + GitHub.Release + GitHub.Repository + + .OUTPUTS + GitHub.Milestone + .EXAMPLE - Set-GitHubMilestone -OwnerName Microsoft -RepositoryName PowerShellForGitHub -Milestone 1 -Title "Testing this API" + Set-GitHubMilestone -OwnerName microsoft -RepositoryName PowerShellForGitHub -Milestone 1 -Title "Testing this API" - Update an existing milestone for the Microsoft\PowerShellForGitHub project. + Update an existing milestone for the microsoft\PowerShellForGitHub project. .NOTES For more information on how GitHub handles the dates specified in DueOn, please refer to @@ -378,21 +478,40 @@ function Set-GitHubMilestone DefaultParameterSetName='Elements')] [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSShouldProcess", "", Justification="Methods called within here make use of PSShouldProcess, and the switch is passed on to them inherently.")] param( - [Parameter(Mandatory, ParameterSetName='Elements')] + [Parameter( + Mandatory, + ParameterSetName='Elements')] [string] $OwnerName, - [Parameter(Mandatory, ParameterSetName='Elements')] + [Parameter( + Mandatory, + ParameterSetName='Elements')] [string] $RepositoryName, - [Parameter(Mandatory, ParameterSetName='Uri')] + [Parameter( + Mandatory, + ValueFromPipelineByPropertyName, + ParameterSetName='Uri')] + [Alias('RepositoryUrl')] [string] $Uri, - [Parameter(Mandatory, ParameterSetName='Uri')] - [Parameter(Mandatory, ParameterSetName='Elements')] + [Parameter( + Mandatory, + ValueFromPipelineByPropertyName, + ParameterSetName='Uri')] + [Parameter( + Mandatory, + ValueFromPipelineByPropertyName, + ParameterSetName='Elements')] + [Alias('MilestoneNumber')] [int64] $Milestone, - [Parameter(Mandatory, ParameterSetName='Uri')] - [Parameter(Mandatory, ParameterSetName='Elements')] + [Parameter( + Mandatory, + ParameterSetName='Uri')] + [Parameter( + Mandatory, + ParameterSetName='Elements')] [string] $Title, [ValidateSet('Open', 'Closed')] @@ -459,14 +578,14 @@ function Set-GitHubMilestone 'NoStatus' = (Resolve-ParameterWithDefaultConfigurationValue -Name NoStatus -ConfigValueName DefaultNoStatus) } - return Invoke-GHRestMethod @params + return (Invoke-GHRestMethod @params | Add-GitHubMilestoneAdditionalProperties) } -function Remove-GitHubMilestone +filter Remove-GitHubMilestone { <# .DESCRIPTION - Deletes a Github milestone for the given repository + Deletes a GitHub milestone for the given repository The Git repo for this module can be found here: http://aka.ms/PowerShellForGitHub @@ -499,20 +618,37 @@ function Remove-GitHubMilestone the background, enabling the command prompt to provide status information. If not supplied here, the DefaultNoStatus configuration property value will be used. + .INPUTS + GitHub.Branch + GitHub.Content + GitHub.Event + GitHub.Issue + GitHub.IssueComment + GitHub.Label + GitHub.Milestone + GitHub.PullRequest + GitHub.Project + GitHub.ProjectCard + GitHub.ProjectColumn + GitHub.Release + GitHub.Repository + .EXAMPLE - Remove-GitHubMilestone -OwnerName Microsoft -RepositoryName PowerShellForGitHub -Milestone 1 + Remove-GitHubMilestone -OwnerName microsoft -RepositoryName PowerShellForGitHub -Milestone 1 - Deletes a Github milestone from the Microsoft\PowerShellForGitHub project. + Deletes a GitHub milestone from the microsoft\PowerShellForGitHub project. .EXAMPLE - Remove-GitHubMilestone -OwnerName Microsoft -RepositoryName PowerShellForGitHub -Milestone 1 -Confirm:$false + Remove-GitHubMilestone -OwnerName microsoft -RepositoryName PowerShellForGitHub -Milestone 1 -Confirm:$false - Deletes a Github milestone from the Microsoft\PowerShellForGitHub project. Will not prompt for confirmation, as -Confirm:$false was specified. + Deletes a Github milestone from the microsoft\PowerShellForGitHub project. Will not prompt + for confirmation, as -Confirm:$false was specified. .EXAMPLE - Remove-GitHubMilestone -OwnerName Microsoft -RepositoryName PowerShellForGitHub -Milestone 1 -Force + Remove-GitHubMilestone -OwnerName microsoft -RepositoryName PowerShellForGitHub -Milestone 1 -Force - Deletes a Github milestone from the Microsoft\PowerShellForGitHub project. Will not prompt for confirmation, as -Force was specified. + Deletes a Github milestone from the microsoft\PowerShellForGitHub project. Will not prompt + for confirmation, as -Force was specified. #> [CmdletBinding( SupportsShouldProcess, @@ -521,18 +657,33 @@ function Remove-GitHubMilestone [Alias('Delete-GitHubMilestone')] [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSReviewUnusedParameter", "", Justification="One or more parameters (like NoStatus) are only referenced by helper methods which get access to it from the stack via Get-Variable -Scope 1.")] param( - [Parameter(Mandatory, ParameterSetName='Elements')] + [Parameter( + Mandatory, + ParameterSetName='Elements')] [string] $OwnerName, - [Parameter(Mandatory, ParameterSetName='Elements')] + [Parameter( + Mandatory, + ParameterSetName='Elements')] [string] $RepositoryName, - [Parameter(Mandatory, ParameterSetName='Uri')] + [Parameter( + Mandatory, + ValueFromPipelineByPropertyName, + ParameterSetName='Uri')] + [Alias('RepositoryUrl')] [string] $Uri, - [Parameter(Mandatory, ParameterSetName='Uri')] - [Parameter(Mandatory, ParameterSetName='Elements')] - [string] $Milestone, + [Parameter( + Mandatory, + ValueFromPipelineByPropertyName, + ParameterSetName='Uri')] + [Parameter( + Mandatory, + ValueFromPipelineByPropertyName, + ParameterSetName='Elements')] + [Alias('MilestoneNumber')] + [int64] $Milestone, [switch] $Force, @@ -573,3 +724,57 @@ function Remove-GitHubMilestone return Invoke-GHRestMethod @params } } + +filter Add-GitHubMilestoneAdditionalProperties +{ +<# + .SYNOPSIS + Adds type name and additional properties to ease pipelining to GitHub Milestone objects. + + .PARAMETER InputObject + The GitHub object to add additional properties to. + + .PARAMETER TypeName + The type that should be assigned to the object. + + .INPUTS + [PSCustomObject] + + .OUTPUTS + GitHub.Milestone +#> + [CmdletBinding()] + [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSUseSingularNouns", "", Justification="Internal helper that is definitely adding more than one property.")] + param( + [Parameter( + Mandatory, + ValueFromPipeline)] + [AllowNull()] + [AllowEmptyCollection()] + [PSCustomObject[]] $InputObject, + + [ValidateNotNullOrEmpty()] + [string] $TypeName = $script:GitHubMilestoneTypeName + ) + + foreach ($item in $InputObject) + { + $item.PSObject.TypeNames.Insert(0, $TypeName) + + if (-not (Get-GitHubConfiguration -Name DisablePipelineSupport)) + { + $elements = Split-GitHubUri -Uri $item.html_url + $repositoryUrl = Join-GitHubUri @elements + Add-Member -InputObject $item -Name 'RepositoryUrl' -Value $repositoryUrl -MemberType NoteProperty -Force + Add-Member -InputObject $item -Name 'MilestoneId' -Value $item.id -MemberType NoteProperty -Force + Add-Member -InputObject $item -Name 'MilestoneNumber' -Value $item.number -MemberType NoteProperty -Force + + if ($null -ne $item.creator) + { + $null = Add-GitHubUserAdditionalProperties -InputObject $item.creator + } + } + + Write-Output $item + } +} diff --git a/GitHubMiscellaneous.ps1 b/GitHubMiscellaneous.ps1 index 28e9d08d..b137d6ea 100644 --- a/GitHubMiscellaneous.ps1 +++ b/GitHubMiscellaneous.ps1 @@ -1,6 +1,16 @@ # Copyright (c) Microsoft Corporation. All rights reserved. # Licensed under the MIT License. +@{ + GitHubRateLimitTypeName = 'GitHub.RateLimit' + GitHubLicenseTypeName = 'GitHub.License' + GitHubEmojiTypeName = 'GitHub.Emoji' + GitHubCodeOfConductTypeName = 'GitHub.CodeOfConduct' + GitHubGitignoreTypeName = 'GitHub.Gitignore' + }.GetEnumerator() | ForEach-Object { + Set-Variable -Scope Script -Option ReadOnly -Name $_.Key -Value $_.Value + } + function Get-GitHubRateLimit { <# @@ -27,7 +37,7 @@ function Get-GitHubRateLimit If not supplied here, the DefaultNoStatus configuration property value will be used. .OUTPUTS - [PSCustomObject] + GitHub.RateLimit Limits returned are _per hour_. The Search API has a custom rate limit, separate from the rate limit @@ -38,7 +48,8 @@ function Get-GitHubRateLimit For these reasons, the Rate Limit API response categorizes your rate limit. Under resources, you'll see three objects: - The core object provides your rate limit status for all non-search-related resources in the REST API. + The core object provides your rate limit status for all non-search-related resources + in the REST API. The search object provides your rate limit status for the Search API. The graphql object provides your rate limit status for the GraphQL API. @@ -52,6 +63,7 @@ function Get-GitHubRateLimit Get-GitHubRateLimit #> [CmdletBinding(SupportsShouldProcess)] + [OutputType({$script:GitHubRateLimitTypeName})] [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSShouldProcess", "", Justification="Methods called within here make use of PSShouldProcess, and the switch is passed on to them inherently.")] [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSReviewUnusedParameter", "", Justification="One or more parameters (like NoStatus) are only referenced by helper methods which get access to it from the stack via Get-Variable -Scope 1.")] param( @@ -71,7 +83,9 @@ function Get-GitHubRateLimit 'NoStatus' = (Resolve-ParameterWithDefaultConfigurationValue -Name NoStatus -ConfigValueName DefaultNoStatus) } - return Invoke-GHRestMethod @params + $result = Invoke-GHRestMethod @params + $result.PSObject.TypeNames.Insert(0, $script:GitHubRateLimitTypeName) + return $result } function ConvertFrom-GitHubMarkdown @@ -110,6 +124,9 @@ function ConvertFrom-GitHubMarkdown the background, enabling the command prompt to provide status information. If not supplied here, the DefaultNoStatus configuration property value will be used. + .INPUTS + [String] + .OUTPUTS [String] The HTML version of the Markdown content. @@ -119,6 +136,7 @@ function ConvertFrom-GitHubMarkdown Returns back '

Bolded Text

' #> [CmdletBinding(SupportsShouldProcess)] + [OutputType([String])] [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSShouldProcess", "", Justification="Methods called within here make use of PSShouldProcess, and the switch is passed on to them inherently.")] [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSReviewUnusedParameter", "", Justification="One or more parameters (like NoStatus) are only referenced by helper methods which get access to it from the stack via Get-Variable -Scope 1.")] param( @@ -126,7 +144,9 @@ function ConvertFrom-GitHubMarkdown Mandatory, ValueFromPipeline)] [ValidateNotNullOrEmpty()] - [ValidateScript({if ([System.Text.Encoding]::UTF8.GetBytes($_).Count -lt 400000) { $true } else { throw "Content must be less than 400 KB." }})] + [ValidateScript({ + if ([System.Text.Encoding]::UTF8.GetBytes($_).Count -lt 400000) { $true } + else { throw "Content must be less than 400 KB." }})] [string] $Content, [ValidateSet('Markdown', 'GitHubFlavoredMarkdown')] @@ -177,7 +197,7 @@ function ConvertFrom-GitHubMarkdown } } -function Get-GitHubLicense +filter Get-GitHubLicense { <# .SYNOPSIS @@ -201,8 +221,8 @@ function Get-GitHubLicense The OwnerName and RepositoryName will be extracted from here instead of needing to provide them individually. - .PARAMETER Name - The name of the license to retrieve the content for. If not specified, all licenses + .PARAMETER Key + The key of the license to retrieve the content for. If not specified, all licenses will be returned. .PARAMETER AccessToken @@ -215,27 +235,52 @@ function Get-GitHubLicense the background, enabling the command prompt to provide status information. If not supplied here, the DefaultNoStatus configuration property value will be used. + + .INPUTS + [String] + GitHub.Branch + GitHub.Content + GitHub.Event + GitHub.Issue + GitHub.IssueComment + GitHub.Label + GitHub.Milestone + GitHub.PullRequest + GitHub.Project + GitHub.ProjectCard + GitHub.ProjectColumn + GitHub.Release + GitHub.Repository + + + .OUTPUTS + GitHub.License + .EXAMPLE Get-GitHubLicense Returns metadata about popular open source licenses .EXAMPLE - Get-GitHubLicense -Name mit + Get-GitHubLicense -Key mit Gets the content of the mit license file .EXAMPLE - Get-GitHubLicense -OwnerName Microsoft -RepositoryName PowerShellForGitHub + Get-GitHubLicense -OwnerName microsoft -RepositoryName PowerShellForGitHub - Gets the content of the license file for the Microsoft\PowerShellForGitHub repository. + Gets the content of the license file for the microsoft\PowerShellForGitHub repository. It may be necessary to convert the content of the file. Check the 'encoding' property of the result to know how 'content' is encoded. As an example, to convert from Base64, do the following: [System.Text.Encoding]::UTF8.GetString([System.Convert]::FromBase64String($result.content)) #> - [CmdletBinding(SupportsShouldProcess)] + [CmdletBinding( + SupportsShouldProcess, + DefaultParameterSetName='All')] + [OutputType({$script:GitHubLicenseTypeName})] + [OutputType({$script:GitHubContentTypeName})] [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSShouldProcess", "", Justification="Methods called within here make use of PSShouldProcess, and the switch is passed on to them inherently.")] param( [Parameter(ParameterSetName='Elements')] @@ -246,13 +291,17 @@ function Get-GitHubLicense [Parameter( Mandatory, + ValueFromPipelineByPropertyName, ParameterSetName='Uri')] + [Alias('RepositoryUrl')] [string] $Uri, [Parameter( Mandatory, + ValueFromPipelineByPropertyName, ParameterSetName='Individual')] - [string] $Name, + [Alias('LicenseKey')] + [string] $Key, [string] $AccessToken, @@ -269,11 +318,11 @@ function Get-GitHubLicense $uriFragment = 'licenses' $description = 'Getting all licenses' - if ($PSBoundParameters.ContainsKey('Name')) + if ($PSBoundParameters.ContainsKey('Key')) { - $telemetryProperties['Name'] = $Name - $uriFragment = "licenses/$Name" - $description = "Getting the $Name license" + $telemetryProperties['Key'] = $Name + $uriFragment = "licenses/$Key" + $description = "Getting the $Key license" } elseif ((-not [String]::IsNullOrEmpty($OwnerName)) -and (-not [String]::IsNullOrEmpty($RepositoryName))) { @@ -293,7 +342,35 @@ function Get-GitHubLicense 'NoStatus' = (Resolve-ParameterWithDefaultConfigurationValue -Name NoStatus -ConfigValueName DefaultNoStatus) } - return Invoke-GHRestMethod @params + $result = Invoke-GHRestMethod @params + foreach ($item in $result) + { + if ($PSCmdlet.ParameterSetName -in ('Elements', 'Uri')) + { + $null = $item | Add-GitHubContentAdditionalProperties + + # Add the decoded Base64 content directly to the object as an additional String property + $decoded = [System.Text.Encoding]::UTF8.GetString([System.Convert]::FromBase64String($item.content)) + Add-Member -InputObject $item -NotePropertyName "contentAsString" -NotePropertyValue $decoded + + $item.license.PSObject.TypeNames.Insert(0, $script:GitHubLicenseTypeName) + + if (-not (Get-GitHubConfiguration -Name DisablePipelineSupport)) + { + Add-Member -InputObject $item -Name 'LicenseKey' -Value $item.license.key -MemberType NoteProperty -Force + } + } + else + { + $item.PSObject.TypeNames.Insert(0, $script:GitHubLicenseTypeName) + if (-not (Get-GitHubConfiguration -Name DisablePipelineSupport)) + { + Add-Member -InputObject $item -Name 'LicenseKey' -Value $item.key -MemberType NoteProperty -Force + } + } + } + + return $result } function Get-GitHubEmoji @@ -318,13 +395,13 @@ function Get-GitHubEmoji If not supplied here, the DefaultNoStatus configuration property value will be used. .OUTPUTS - [PSCustomObject] - The emoji name and a link to its image. + GitHub.Emoji .EXAMPLE Get-GitHubEmoji #> [CmdletBinding(SupportsShouldProcess)] + [OutputType({$script:GitHubEmojiTypeName})] [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSShouldProcess", "", Justification="Methods called within here make use of PSShouldProcess, and the switch is passed on to them inherently.")] [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSReviewUnusedParameter", "", Justification="One or more parameters (like NoStatus) are only referenced by helper methods which get access to it from the stack via Get-Variable -Scope 1.")] param( @@ -344,17 +421,19 @@ function Get-GitHubEmoji 'NoStatus' = (Resolve-ParameterWithDefaultConfigurationValue -Name NoStatus -ConfigValueName DefaultNoStatus) } - return Invoke-GHRestMethod @params + $result = Invoke-GHRestMethod @params + $result.PSObject.TypeNames.Insert(0, $script:GitHubEmojiTypeName) + return $result } -function Get-GitHubCodeOfConduct +filter Get-GitHubCodeOfConduct { <# .SYNOPSIS - Gets license or license content from GitHub. + Gets Codes of Conduct or a specific Code of Conduct from GitHub. .DESCRIPTION - Gets license or license content from GitHub. + Gets Codes of Conduct or a specific Code of Conduct from GitHub. The Git repo for this module can be found here: http://aka.ms/PowerShellForGitHub @@ -371,9 +450,9 @@ function Get-GitHubCodeOfConduct The OwnerName and RepositoryName will be extracted from here instead of needing to provide them individually. - .PARAMETER Name - The name of the license to retrieve the content for. If not specified, all licenses - will be returned. + .PARAMETER Key + The unique key of the Code of Conduct to retrieve the content for. If not specified, all + Codes of Conduct will be returned. .PARAMETER AccessToken If provided, this will be used as the AccessToken for authentication with the @@ -385,20 +464,40 @@ function Get-GitHubCodeOfConduct the background, enabling the command prompt to provide status information. If not supplied here, the DefaultNoStatus configuration property value will be used. + .INPUTS + [String] + GitHub.Branch + GitHub.Content + GitHub.Event + GitHub.Issue + GitHub.IssueComment + GitHub.Label + GitHub.Milestone + GitHub.PullRequest + GitHub.Project + GitHub.ProjectCard + GitHub.ProjectColumn + GitHub.Release + GitHub.Repository + + + .OUTPUTS + GitHub.CodeOfConduct + .EXAMPLE Get-GitHubCodeOfConduct Returns metadata about popular Codes of Conduct .EXAMPLE - Get-GitHubCodeOfConduct -Name citizen_code_of_conduct + Get-GitHubCodeOfConduct -Key citizen_code_of_conduct Gets the content of the 'Citizen Code of Conduct' .EXAMPLE - Get-GitHubCodeOfConduct -OwnerName Microsoft -RepositoryName PowerShellForGitHub + Get-GitHubCodeOfConduct -OwnerName microsoft -RepositoryName PowerShellForGitHub - Gets the content of the Code of Conduct file for the Microsoft\PowerShellForGitHub repository + Gets the content of the Code of Conduct file for the microsoft\PowerShellForGitHub repository if one is detected. It may be necessary to convert the content of the file. Check the 'encoding' property of @@ -408,6 +507,7 @@ function Get-GitHubCodeOfConduct [System.Text.Encoding]::UTF8.GetString([System.Convert]::FromBase64String($result.content)) #> [CmdletBinding(SupportsShouldProcess)] + [OutputType({$script:GitHubCodeOfConductTypeName})] [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSShouldProcess", "", Justification="Methods called within here make use of PSShouldProcess, and the switch is passed on to them inherently.")] param( [Parameter(ParameterSetName='Elements')] @@ -418,13 +518,17 @@ function Get-GitHubCodeOfConduct [Parameter( Mandatory, + ValueFromPipelineByPropertyName, ParameterSetName='Uri')] + [Alias('RepositoryUrl')] [string] $Uri, [Parameter( Mandatory, + ValueFromPipelineByPropertyName, ParameterSetName='Individual')] - [string] $Name, + [Alias('CodeOfConductKey')] + [string] $Key, [string] $AccessToken, @@ -441,11 +545,11 @@ function Get-GitHubCodeOfConduct $uriFragment = 'codes_of_conduct' $description = 'Getting all Codes of Conduct' - if ($PSBoundParameters.ContainsKey('Name')) + if ($PSBoundParameters.ContainsKey('Key')) { - $telemetryProperties['Name'] = $Name - $uriFragment = "codes_of_conduct/$Name" - $description = "Getting the $Name Code of Conduct" + $telemetryProperties['Key'] = $Name + $uriFragment = "codes_of_conduct/$Key" + $description = "Getting the $Key Code of Conduct" } elseif ((-not [String]::IsNullOrEmpty($OwnerName)) -and (-not [String]::IsNullOrEmpty($RepositoryName))) { @@ -458,7 +562,7 @@ function Get-GitHubCodeOfConduct $params = @{ 'UriFragment' = $uriFragment 'Method' = 'Get' - 'AcceptHeader' = 'application/vnd.github.scarlet-witch-preview+json' + 'AcceptHeader' = $script:scarletWitchAcceptHeader 'Description' = $description 'AccessToken' = $AccessToken 'TelemetryEventName' = $MyInvocation.MyCommand.Name @@ -466,10 +570,20 @@ function Get-GitHubCodeOfConduct 'NoStatus' = (Resolve-ParameterWithDefaultConfigurationValue -Name NoStatus -ConfigValueName DefaultNoStatus) } - return Invoke-GHRestMethod @params + $result = Invoke-GHRestMethod @params + foreach ($item in $result) + { + $item.PSObject.TypeNames.Insert(0, $script:GitHubCodeOfConductTypeName) + if (-not (Get-GitHubConfiguration -Name DisablePipelineSupport)) + { + Add-Member -InputObject $item -Name 'CodeOfConductKey' -Value $item.key -MemberType NoteProperty -Force + } + } + + return $result } -function Get-GitHubGitIgnore +filter Get-GitHubGitIgnore { <# .SYNOPSIS @@ -484,6 +598,9 @@ function Get-GitHubGitIgnore The name of the .gitignore template whose content should be fetched. Not providing this will cause a list of all available templates to be returned. + .PARAMETER RawContent + If specified, the raw content of the specified .gitignore file will be returned. + .PARAMETER AccessToken If provided, this will be used as the AccessToken for authentication with the REST Api. Otherwise, will attempt to use the configured value or will run unauthenticated. @@ -494,6 +611,12 @@ function Get-GitHubGitIgnore the background, enabling the command prompt to provide status information. If not supplied here, the DefaultNoStatus configuration property value will be used. + .INPUTS + [String] + + .OUTPUTS + GitHub.Gitignore + .EXAMPLE Get-GitHubGitIgnore @@ -505,10 +628,17 @@ function Get-GitHubGitIgnore Returns the content of the VisualStudio.gitignore template. #> [CmdletBinding(SupportsShouldProcess)] + [OutputType({$script:GitHubGitignoreTypeName})] [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSShouldProcess", "", Justification="Methods called within here make use of PSShouldProcess, and the switch is passed on to them inherently.")] param( + [Parameter( + ValueFromPipeline, + ParameterSetName='Individual')] [string] $Name, + [Parameter(ParameterSetName='Individual')] + [switch] $RawContent, + [string] $AccessToken, [switch] $NoStatus @@ -537,5 +667,21 @@ function Get-GitHubGitIgnore 'NoStatus' = (Resolve-ParameterWithDefaultConfigurationValue -Name NoStatus -ConfigValueName DefaultNoStatus) } - return Invoke-GHRestMethod @params + if ($RawContent) + { + $params['AcceptHeader'] = (Get-MediaAcceptHeader -MediaType 'Raw') + } + + $result = Invoke-GHRestMethod @params + if ($PSBoundParameters.ContainsKey('Name') -and (-not $RawContent)) + { + $result.PSObject.TypeNames.Insert(0, $script:GitHubGitignoreTypeName) + } + + if ($RawContent) + { + $result = [System.Text.Encoding]::UTF8.GetString($result) + } + + return $result } diff --git a/GitHubOrganizations.ps1 b/GitHubOrganizations.ps1 index db2073ba..021ba709 100644 --- a/GitHubOrganizations.ps1 +++ b/GitHubOrganizations.ps1 @@ -1,7 +1,13 @@ # Copyright (c) Microsoft Corporation. All rights reserved. # Licensed under the MIT License. -function Get-GitHubOrganizationMember +@{ + GitHubOrganizationTypeName = 'GitHub.Organization' + }.GetEnumerator() | ForEach-Object { + Set-Variable -Scope Script -Option ReadOnly -Name $_.Key -Value $_.Value + } + +filter Get-GitHubOrganizationMember { <# .SYNOPSIS @@ -25,18 +31,26 @@ function Get-GitHubOrganizationMember the background, enabling the command prompt to provide status information. If not supplied here, the DefaultNoStatus configuration property value will be used. + .INPUTS + [String] + .OUTPUTS - [PSCustomObject[]] List of members within the organization. + GitHub.User + List of members within the organization. .EXAMPLE Get-GitHubOrganizationMember -OrganizationName PowerShell #> + [CmdletBinding(SupportsShouldProcess)] + [OutputType({$script:GitHubUserTypeName})] [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSShouldProcess", "", Justification="Methods called within here make use of PSShouldProcess, and the switch is passed on to them inherently.")] [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSReviewUnusedParameter", "", Justification="One or more parameters (like NoStatus) are only referenced by helper methods which get access to it from the stack via Get-Variable -Scope 1.")] - [CmdletBinding(SupportsShouldProcess)] param ( - [Parameter(Mandatory)] + [Parameter( + Mandatory, + ValueFromPipeline, + ValueFromPipelineByPropertyName)] [ValidateNotNullOrEmpty()] [String] $OrganizationName, @@ -60,10 +74,10 @@ function Get-GitHubOrganizationMember 'NoStatus' = (Resolve-ParameterWithDefaultConfigurationValue -Name NoStatus -ConfigValueName DefaultNoStatus) } - return Invoke-GHRestMethodMultipleResult @params + return (Invoke-GHRestMethodMultipleResult @params | Add-GitHubUserAdditionalProperties) } -function Test-GitHubOrganizationMember +filter Test-GitHubOrganizationMember { <# .SYNOPSIS @@ -90,23 +104,30 @@ function Test-GitHubOrganizationMember the background, enabling the command prompt to provide status information. If not supplied here, the DefaultNoStatus configuration property value will be used. + .INPUTS + [String] + .OUTPUTS [Bool] .EXAMPLE Test-GitHubOrganizationMember -OrganizationName PowerShell -UserName Octocat #> - [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSShouldProcess", "", Justification="Methods called within here make use of PSShouldProcess, and the switch is passed on to them inherently.")] - [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSReviewUnusedParameter", "", Justification="One or more parameters (like NoStatus) are only referenced by helper methods which get access to it from the stack via Get-Variable -Scope 1.")] [CmdletBinding(SupportsShouldProcess)] [OutputType([bool])] + [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSShouldProcess", "", Justification="Methods called within here make use of PSShouldProcess, and the switch is passed on to them inherently.")] + [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSReviewUnusedParameter", "", Justification="One or more parameters (like NoStatus) are only referenced by helper methods which get access to it from the stack via Get-Variable -Scope 1.")] param ( - [Parameter(Mandatory)] + [Parameter( + Mandatory, + ValueFromPipelineByPropertyName)] [ValidateNotNullOrEmpty()] [String] $OrganizationName, - [Parameter(Mandatory)] + [Parameter( + Mandatory, + ValueFromPipelineByPropertyName)] [ValidateNotNullOrEmpty()] [String] $UserName, @@ -142,3 +163,49 @@ function Test-GitHubOrganizationMember return $false } } + +filter Add-GitHubOrganizationAdditionalProperties +{ +<# + .SYNOPSIS + Adds type name and additional properties to ease pipelining to GitHub Organization objects. + + .PARAMETER InputObject + The GitHub object to add additional properties to. + + .PARAMETER TypeName + The type that should be assigned to the object. + + .INPUTS + [PSCustomObject] + + .OUTPUTS + GitHub.Organization +#> + [CmdletBinding()] + [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSUseSingularNouns", "", Justification="Internal helper that is definitely adding more than one property.")] + param( + [Parameter( + Mandatory, + ValueFromPipeline)] + [AllowNull()] + [AllowEmptyCollection()] + [PSCustomObject[]] $InputObject, + + [ValidateNotNullOrEmpty()] + [string] $TypeName = $script:GitHubOrganizationTypeName + ) + + foreach ($item in $InputObject) + { + $item.PSObject.TypeNames.Insert(0, $TypeName) + + if (-not (Get-GitHubConfiguration -Name DisablePipelineSupport)) + { + Add-Member -InputObject $item -Name 'OrganizationName' -Value $item.login -MemberType NoteProperty -Force + Add-Member -InputObject $item -Name 'OrganizationId' -Value $item.id -MemberType NoteProperty -Force + } + + Write-Output $item + } +} diff --git a/GitHubProjectCards.ps1 b/GitHubProjectCards.ps1 index 10a4d46d..ed911fa5 100644 --- a/GitHubProjectCards.ps1 +++ b/GitHubProjectCards.ps1 @@ -1,19 +1,25 @@ # Copyright (c) Microsoft Corporation. All rights reserved. # Licensed under the MIT License. -function Get-GitHubProjectCard +@{ + GitHubProjectCardTypeName = 'GitHub.ProjectCard' + }.GetEnumerator() | ForEach-Object { + Set-Variable -Scope Script -Option ReadOnly -Name $_.Key -Value $_.Value + } + +filter Get-GitHubProjectCard { <# .DESCRIPTION - Get the cards for a given Github Project Column. + Get the cards for a given GitHub Project Column. The Git repo for this module can be found here: http://aka.ms/PowerShellForGitHub .PARAMETER Column ID of the column to retrieve cards for. - .PARAMETER ArchivedState - Only cards with this ArchivedState are returned. + .PARAMETER State + Only cards with this State are returned. Options are all, archived, or NotArchived (default). .PARAMETER AccessToken @@ -26,18 +32,25 @@ function Get-GitHubProjectCard the background, enabling the command prompt to provide status information. If not supplied here, the DefaultNoStatus configuration property value will be used. + .INPUTS + GitHub.ProjectCard + GitHub.ProjectColumn + + .OUTPUTS + GitHub.ProjectCard + .EXAMPLE Get-GitHubProjectCard -Column 999999 Get the the not_archived cards for column 999999. .EXAMPLE - Get-GitHubProjectCard -Column 999999 -ArchivedState All + Get-GitHubProjectCard -Column 999999 -State All - Gets all the cards for column 999999, no matter the ArchivedState. + Gets all the cards for column 999999, no matter the State. .EXAMPLE - Get-GitHubProjectCard -Column 999999 -ArchivedState Archived + Get-GitHubProjectCard -Column 999999 -State Archived Gets the archived cards for column 999999. @@ -48,17 +61,27 @@ function Get-GitHubProjectCard #> [CmdletBinding( SupportsShouldProcess, - DefaultParameterSetName = 'Column')] + DefaultParameterSetName = 'Card')] + [OutputType({$script:GitHubProjectCardTypeName})] [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSShouldProcess", "", Justification = "Methods called within here make use of PSShouldProcess, and the switch is passed on to them inherently.")] param( - [Parameter(Mandatory, ParameterSetName = 'Column')] + [Parameter( + Mandatory, + ValueFromPipelineByPropertyName, + ParameterSetName = 'Column')] + [Alias('ColumnId')] [int64] $Column, - [Parameter(Mandatory, ParameterSetName = 'Card')] + [Parameter( + Mandatory, + ValueFromPipelineByPropertyName, + ParameterSetName = 'Card')] + [Alias('CardId')] [int64] $Card, [ValidateSet('All', 'Archived', 'NotArchived')] - [string] $ArchivedState = 'NotArchived', + [Alias('ArchivedState')] + [string] $State = 'NotArchived', [string] $AccessToken, @@ -87,14 +110,14 @@ function Get-GitHubProjectCard $description = "Getting project card $Card" } - if ($PSBoundParameters.ContainsKey('ArchivedState')) + if ($PSBoundParameters.ContainsKey('State')) { $getParams = @() - $Archived = $ArchivedState.ToLower().Replace('notarchived','not_archived') + $Archived = $State.ToLower().Replace('notarchived','not_archived') $getParams += "archived_state=$Archived" $uriFragment = "$uriFragment`?" + ($getParams -join '&') - $description += " with ArchivedState '$Archived'" + $description += " with State '$Archived'" } $params = @{ @@ -104,17 +127,17 @@ function Get-GitHubProjectCard 'TelemetryEventName' = $MyInvocation.MyCommand.Name 'TelemetryProperties' = $telemetryProperties 'NoStatus' = (Resolve-ParameterWithDefaultConfigurationValue -Name NoStatus -ConfigValueName DefaultNoStatus) - 'AcceptHeader' = 'application/vnd.github.inertia-preview+json' + 'AcceptHeader' = $script:inertiaAcceptHeader } - return Invoke-GHRestMethodMultipleResult @params + return (Invoke-GHRestMethodMultipleResult @params | Add-GitHubProjectCardAdditionalProperties) } -function New-GitHubProjectCard +filter New-GitHubProjectCard { <# .DESCRIPTION - Creates a new card for a Github project. + Creates a new card for a GitHub project. The Git repo for this module can be found here: http://aka.ms/PowerShellForGitHub @@ -124,13 +147,13 @@ function New-GitHubProjectCard .PARAMETER Note The name of the column to create. - .PARAMETER ContentId - The issue or pull request ID you want to associate with this card. + .PARAMETER IssueId + The ID of the issue you want to associate with this card (not to be confused with + the Issue _number_ which you see in the URL and can refer to with a hashtag). - .PARAMETER ContentType - The type of content you want to associate with this card. - Required if you provide ContentId. - Use Issue when ContentId is an issue ID and use PullRequest when ContentId is a pull request id. + .PARAMETER PullRequestId + The ID of the pull request you want to associate with this card (not to be confused with + the Pull Request _number_ which you see in the URL and can refer to with a hashtag). .PARAMETER AccessToken If provided, this will be used as the AccessToken for authentication with the @@ -142,44 +165,61 @@ function New-GitHubProjectCard the background, enabling the command prompt to provide status information. If not supplied here, the DefaultNoStatus configuration property value will be used. - .EXAMPLE - New-GitHubProjectCard -Column 999999 -Note 'Note on card' + .INPUTS + GitHub.IssueComment + GitHub.Issue + GitHub.PullRequest + GitHub.ProjectCard + GitHub.ProjectColumn - Creates a card on column 999999 with the note 'Note on card'. + .OUTPUTS + GitHub.ProjectCard .EXAMPLE - New-GitHubProjectCard -Column 999999 -ContentId 888888 -ContentType Issue + New-GitHubProjectCard -Column 999999 -Note 'Note on card' - Creates a card on column 999999 for the issue with ID 888888. + Creates a card on column 999999 with the note 'Note on card'. .EXAMPLE - New-GitHubProjectCard -Column 999999 -ContentId 888888 -ContentType Issue + New-GitHubProjectCard -Column 999999 -IssueId 888888 Creates a card on column 999999 for the issue with ID 888888. .EXAMPLE - New-GitHubProjectCard -Column 999999 -ContentId 777777 -ContentType PullRequest + New-GitHubProjectCard -Column 999999 -PullRequestId 888888 - Creates a card on column 999999 for the pull request with ID 777777. + Creates a card on column 999999 for the pull request with ID 888888. #> [CmdletBinding( SupportsShouldProcess, DefaultParameterSetName = 'Note')] + [OutputType({$script:GitHubProjectCardTypeName})] [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSShouldProcess", "", Justification = "Methods called within here make use of PSShouldProcess, and the switch is passed on to them inherently.")] [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSReviewUnusedParameter", "", Justification="One or more parameters (like NoStatus) are only referenced by helper methods which get access to it from the stack via Get-Variable -Scope 1.")] param( - [Parameter(Mandatory)] + [Parameter( + Mandatory, + ValueFromPipelineByPropertyName)] + [Alias('ColumnId')] [int64] $Column, - [Parameter(Mandatory, ParameterSetName = 'Note')] + [Parameter( + Mandatory, + ParameterSetName = 'Note')] + [Alias('Content')] [string] $Note, - [Parameter(Mandatory, ParameterSetName = 'Content')] - [int64] $ContentId, + [Parameter( + Mandatory, + ValueFromPipelineByPropertyName, + ParameterSetName = 'Issue')] + [int64] $IssueId, - [Parameter(Mandatory, ParameterSetName = 'Content')] - [ValidateSet('Issue', 'PullRequest')] - [string] $ContentType, + [Parameter( + Mandatory, + ValueFromPipelineByPropertyName, + ParameterSetName = 'PullRequest')] + [int64] $PullRequestId, [string] $AccessToken, @@ -201,13 +241,22 @@ function New-GitHubProjectCard 'note' = $Note } } - elseif ($PSCmdlet.ParameterSetName -eq 'Content') + elseif ($PSCmdlet.ParameterSetName -in ('Issue', 'PullRequest')) { - $telemetryProperties['Content'] = $true + $contentType = $PSCmdlet.ParameterSetName + $telemetryProperties['ContentType'] = $contentType $hashBody = @{ - 'content_id' = $ContentId - 'content_type' = $ContentType + 'content_type' = $contentType + } + + if ($PSCmdlet.ParameterSetName -eq 'Issue') + { + $hashBody['content_id'] = $IssueId + } + else + { + $hashBody['content_id'] = $PullRequestId } } @@ -220,13 +269,13 @@ function New-GitHubProjectCard 'TelemetryEventName' = $MyInvocation.MyCommand.Name 'TelemetryProperties' = $telemetryProperties 'NoStatus' = (Resolve-ParameterWithDefaultConfigurationValue -Name NoStatus -ConfigValueName DefaultNoStatus) - 'AcceptHeader' = 'application/vnd.github.inertia-preview+json' + 'AcceptHeader' = $script:inertiaAcceptHeader } - return Invoke-GHRestMethod @params + return (Invoke-GHRestMethod @params | Add-GitHubProjectCardAdditionalProperties) } -function Set-GitHubProjectCard +filter Set-GitHubProjectCard { <# .DESCRIPTION @@ -257,6 +306,12 @@ function Set-GitHubProjectCard the background, enabling the command prompt to provide status information. If not supplied here, the DefaultNoStatus configuration property value will be used. + .INPUTS + GitHub.ProjectCard + + .OUTPUTS + GitHub.ProjectCard + .EXAMPLE Set-GitHubProjectCard -Card 999999 -Note UpdatedNote @@ -273,13 +328,18 @@ function Set-GitHubProjectCard Restores the card with ID 999999. #> [CmdletBinding( - SupportsShouldProcess, - DefaultParameterSetName = 'Note')] + SupportsShouldProcess, + DefaultParameterSetName = 'Note')] + [OutputType({$script:GitHubProjectCardTypeName})] [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSShouldProcess", "", Justification = "Methods called within here make use of PSShouldProcess, and the switch is passed on to them inherently.")] param( - [Parameter(Mandatory)] + [Parameter( + Mandatory, + ValueFromPipelineByPropertyName)] + [Alias('CardId')] [int64] $Card, + [Alias('Content')] [string] $Note, [Parameter(ParameterSetName = 'Archive')] @@ -329,13 +389,13 @@ function Set-GitHubProjectCard 'TelemetryEventName' = $MyInvocation.MyCommand.Name 'TelemetryProperties' = $telemetryProperties 'NoStatus' = (Resolve-ParameterWithDefaultConfigurationValue -Name NoStatus -ConfigValueName DefaultNoStatus) - 'AcceptHeader' = 'application/vnd.github.inertia-preview+json' + 'AcceptHeader' = $script:inertiaAcceptHeader } - return Invoke-GHRestMethod @params + return (Invoke-GHRestMethod @params | Add-GitHubProjectCardAdditionalProperties) } -function Remove-GitHubProjectCard +filter Remove-GitHubProjectCard { <# .DESCRIPTION @@ -359,6 +419,9 @@ function Remove-GitHubProjectCard the background, enabling the command prompt to provide status information. If not supplied here, the DefaultNoStatus configuration property value will be used. + .INPUTS + GitHub.ProjectCard + .EXAMPLE Remove-GitHubProjectCard -Card 999999 @@ -380,7 +443,10 @@ function Remove-GitHubProjectCard [Alias('Delete-GitHubProjectCard')] [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSReviewUnusedParameter", "", Justification="One or more parameters (like NoStatus) are only referenced by helper methods which get access to it from the stack via Get-Variable -Scope 1.")] param( - [Parameter(Mandatory)] + [Parameter( + Mandatory, + ValueFromPipelineByPropertyName)] + [Alias('CardId')] [int64] $Card, [switch] $Force, @@ -412,14 +478,14 @@ function Remove-GitHubProjectCard 'TelemetryEventName' = $MyInvocation.MyCommand.Name 'TelemetryProperties' = $telemetryProperties 'NoStatus' = (Resolve-ParameterWithDefaultConfigurationValue -Name NoStatus -ConfigValueName DefaultNoStatus) - 'AcceptHeader' = 'application/vnd.github.inertia-preview+json' + 'AcceptHeader' = $script:inertiaAcceptHeader } return Invoke-GHRestMethod @params } } -function Move-GitHubProjectCard +filter Move-GitHubProjectCard { <# .DESCRIPTION @@ -439,7 +505,7 @@ function Move-GitHubProjectCard .PARAMETER After Moves the card to the position after the card ID specified. - .PARAMETER ColumnId + .PARAMETER Column The ID of a column in the same project to move the card to. .PARAMETER AccessToken @@ -452,6 +518,10 @@ function Move-GitHubProjectCard the background, enabling the command prompt to provide status information. If not supplied here, the DefaultNoStatus configuration property value will be used. + .INPUTS + GitHub.ProjectCard + GitHub.ProjectColumn + .EXAMPLE Move-GitHubProjectCard -Card 999999 -Top @@ -469,16 +539,18 @@ function Move-GitHubProjectCard Within the same column. .EXAMPLE - Move-GitHubProjectCard -Card 999999 -After 888888 -ColumnId 123456 + Move-GitHubProjectCard -Card 999999 -After 888888 -Column 123456 Moves the project card with ID 999999 to the position after the card ID 888888, in the column with ID 123456. #> - [CmdletBinding( - SupportsShouldProcess)] + [CmdletBinding(SupportsShouldProcess)] [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSShouldProcess", "", Justification = "Methods called within here make use of PSShouldProcess, and the switch is passed on to them inherently.")] param( - [Parameter(Mandatory)] + [Parameter( + Mandatory, + ValueFromPipelineByPropertyName)] + [Alias('CardId')] [int64] $Card, [switch] $Top, @@ -487,7 +559,9 @@ function Move-GitHubProjectCard [int64] $After, - [int64] $ColumnId, + [Parameter(ValueFromPipelineByPropertyName)] + [Alias('ColumnId')] + [int64] $Column, [string] $AccessToken, @@ -524,10 +598,10 @@ function Move-GitHubProjectCard 'position' = $Position } - if ($PSBoundParameters.ContainsKey('ColumnId')) + if ($PSBoundParameters.ContainsKey('Column')) { - $telemetryProperties['ColumnId'] = $true - $hashBody.add('column_id', $ColumnId) + $telemetryProperties['Column'] = $true + $hashBody.add('column_id', $Column) } $params = @{ @@ -539,8 +613,89 @@ function Move-GitHubProjectCard 'TelemetryEventName' = $MyInvocation.MyCommand.Name 'TelemetryProperties' = $telemetryProperties 'NoStatus' = (Resolve-ParameterWithDefaultConfigurationValue -Name NoStatus -ConfigValueName DefaultNoStatus) - 'AcceptHeader' = 'application/vnd.github.inertia-preview+json' + 'AcceptHeader' = $script:inertiaAcceptHeader } return Invoke-GHRestMethod @params -} \ No newline at end of file +} + + +filter Add-GitHubProjectCardAdditionalProperties +{ +<# + .SYNOPSIS + Adds type name and additional properties to ease pipelining to GitHub Project Card objects. + + .PARAMETER InputObject + The GitHub object to add additional properties to. + + .PARAMETER TypeName + The type that should be assigned to the object. + + .INPUTS + [PSCustomObject] + + .OUTPUTS + GitHub.ProjectCard +#> + [CmdletBinding()] + [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSUseSingularNouns", "", Justification="Internal helper that is definitely adding more than one property.")] + param( + [Parameter( + Mandatory, + ValueFromPipeline)] + [AllowNull()] + [AllowEmptyCollection()] + [PSCustomObject[]] $InputObject, + + [ValidateNotNullOrEmpty()] + [string] $TypeName = $script:GitHubProjectCardTypeName + ) + + foreach ($item in $InputObject) + { + $item.PSObject.TypeNames.Insert(0, $TypeName) + + if (-not (Get-GitHubConfiguration -Name DisablePipelineSupport)) + { + Add-Member -InputObject $item -Name 'CardId' -Value $item.id -MemberType NoteProperty -Force + + if ($item.project_url -match '^.*/projects/(\d+)$') + { + $projectId = $Matches[1] + Add-Member -InputObject $item -Name 'ProjectId' -Value $projectId -MemberType NoteProperty -Force + } + + if ($item.column_url -match '^.*/columns/(\d+)$') + { + $columnId = $Matches[1] + Add-Member -InputObject $item -Name 'ColumnId' -Value $columnId -MemberType NoteProperty -Force + } + + if ($null -ne $item.content_url) + { + $elements = Split-GitHubUri -Uri $item.content_url + $repositoryUrl = Join-GitHubUri @elements + Add-Member -InputObject $item -Name 'RepositoryUrl' -Value $repositoryUrl -MemberType NoteProperty -Force + + if ($item.content_url -match '^.*/issues/(\d+)$') + { + $issueNumber = $Matches[1] + Add-Member -InputObject $item -Name 'IssueNumber' -Value $issueNumber -MemberType NoteProperty -Force + } + elseif ($item.content_url -match '^.*/pull/(\d+)$') + { + $pullRequestNumber = $Matches[1] + Add-Member -InputObject $item -Name 'PullRequestNumber' -Value $pullRequestNumber -MemberType NoteProperty -Force + } + } + + if ($null -ne $item.creator) + { + $null = Add-GitHubUserAdditionalProperties -InputObject $item.creator + } + } + + Write-Output $item + } +} diff --git a/GitHubProjectColumns.ps1 b/GitHubProjectColumns.ps1 index 4e4d0e82..f65cd309 100644 --- a/GitHubProjectColumns.ps1 +++ b/GitHubProjectColumns.ps1 @@ -1,11 +1,17 @@ # Copyright (c) Microsoft Corporation. All rights reserved. # Licensed under the MIT License. -function Get-GitHubProjectColumn +@{ + GitHubProjectColumnTypeName = 'GitHub.ProjectColumn' + }.GetEnumerator() | ForEach-Object { + Set-Variable -Scope Script -Option ReadOnly -Name $_.Key -Value $_.Value + } + +filter Get-GitHubProjectColumn { <# .DESCRIPTION - Get the columns for a given Github Project. + Get the columns for a given GitHub Project. The Git repo for this module can be found here: http://aka.ms/PowerShellForGitHub @@ -25,6 +31,14 @@ function Get-GitHubProjectColumn the background, enabling the command prompt to provide status information. If not supplied here, the DefaultNoStatus configuration property value will be used. + .INPUTS + GitHub.Project + GitHub.ProjectCard + GitHub.ProjectColumn + + .OUTPUTS + GitHub.ProjectColumn + .EXAMPLE Get-GitHubProjectColumn -Project 999999 @@ -37,14 +51,24 @@ function Get-GitHubProjectColumn #> [CmdletBinding( SupportsShouldProcess, - DefaultParameterSetName = 'Project')] + DefaultParameterSetName = 'Column')] + [OutputType({$script:GitHubProjectColumnTypeName})] [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSShouldProcess", "", Justification = "Methods called within here make use of PSShouldProcess, and the switch is passed on to them inherently.")] [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSReviewUnusedParameter", "", Justification="One or more parameters (like NoStatus) are only referenced by helper methods which get access to it from the stack via Get-Variable -Scope 1.")] param( - [Parameter(Mandatory, ParameterSetName = 'Project')] + [Parameter( + Mandatory, + ValueFromPipelineByPropertyName, + ParameterSetName = 'Project')] + [Alias('ProjectId')] [int64] $Project, - [Parameter(Mandatory, ParameterSetName = 'Column')] + [Parameter( + Mandatory, + ValueFromPipeline, + ValueFromPipelineByPropertyName, + ParameterSetName = 'Column')] + [Alias('ColumnId')] [int64] $Column, [string] $AccessToken, @@ -81,17 +105,17 @@ function Get-GitHubProjectColumn 'TelemetryEventName' = $MyInvocation.MyCommand.Name 'TelemetryProperties' = $telemetryProperties 'NoStatus' = (Resolve-ParameterWithDefaultConfigurationValue -Name NoStatus -ConfigValueName DefaultNoStatus) - 'AcceptHeader' = 'application/vnd.github.inertia-preview+json' + 'AcceptHeader' = $script:inertiaAcceptHeader } - return Invoke-GHRestMethodMultipleResult @params + return (Invoke-GHRestMethodMultipleResult @params | Add-GitHubProjectColumnAdditionalProperties) } -function New-GitHubProjectColumn +filter New-GitHubProjectColumn { <# .DESCRIPTION - Creates a new column for a Github project. + Creates a new column for a GitHub project. The Git repo for this module can be found here: http://aka.ms/PowerShellForGitHub @@ -111,22 +135,37 @@ function New-GitHubProjectColumn the background, enabling the command prompt to provide status information. If not supplied here, the DefaultNoStatus configuration property value will be used. + .INPUTS + [String] + GitHub.Project + GitHub.ProjectCard + GitHub.ProjectColumn + + .OUTPUTS + GitHub.ProjectColumn + .EXAMPLE - New-GitHubProjectColumn -Project 999999 -Name 'Done' + New-GitHubProjectColumn -Project 999999 -ColumnName 'Done' Creates a column called 'Done' for the project with ID 999999. #> - [CmdletBinding( - SupportsShouldProcess)] + [CmdletBinding(SupportsShouldProcess)] + [OutputType({$script:GitHubProjectColumnTypeName})] [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSShouldProcess", "", Justification = "Methods called within here make use of PSShouldProcess, and the switch is passed on to them inherently.")] [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSReviewUnusedParameter", "", Justification="One or more parameters (like NoStatus) are only referenced by helper methods which get access to it from the stack via Get-Variable -Scope 1.")] param( - [Parameter(Mandatory)] + [Parameter( + Mandatory, + ValueFromPipelineByPropertyName)] + [Alias('ProjectId')] [int64] $Project, - [Parameter(Mandatory)] - [string] $Name, + [Parameter( + Mandatory, + ValueFromPipeline)] + [Alias('Name')] + [string] $ColumnName, [string] $AccessToken, @@ -139,10 +178,10 @@ function New-GitHubProjectColumn $telemetryProperties['Project'] = Get-PiiSafeString -PlainText $Project $uriFragment = "/projects/$Project/columns" - $apiDescription = "Creating project column $Name" + $apiDescription = "Creating project column $ColumnName" $hashBody = @{ - 'name' = $Name + 'name' = $ColumnName } $params = @{ @@ -154,13 +193,13 @@ function New-GitHubProjectColumn 'TelemetryEventName' = $MyInvocation.MyCommand.Name 'TelemetryProperties' = $telemetryProperties 'NoStatus' = (Resolve-ParameterWithDefaultConfigurationValue -Name NoStatus -ConfigValueName DefaultNoStatus) - 'AcceptHeader' = 'application/vnd.github.inertia-preview+json' + 'AcceptHeader' = $script:inertiaAcceptHeader } - return Invoke-GHRestMethod @params + return (Invoke-GHRestMethod @params | Add-GitHubProjectColumnAdditionalProperties) } -function Set-GitHubProjectColumn +filter Set-GitHubProjectColumn { <# .DESCRIPTION @@ -184,21 +223,32 @@ function Set-GitHubProjectColumn the background, enabling the command prompt to provide status information. If not supplied here, the DefaultNoStatus configuration property value will be used. + .INPUTS + GitHub.ProjectCard + GitHub.ProjectColumn + + .OUTPUTS + GitHub.ProjectColumn + .EXAMPLE - Set-GitHubProjectColumn -Column 999999 -Name NewColumnName + Set-GitHubProjectColumn -Column 999999 -ColumnName NewColumnName Set the project column name to 'NewColumnName' with column with ID 999999. #> - [CmdletBinding( - SupportsShouldProcess)] + [CmdletBinding(SupportsShouldProcess)] + [OutputType({$script:GitHubProjectColumnTypeName})] [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSShouldProcess", "", Justification = "Methods called within here make use of PSShouldProcess, and the switch is passed on to them inherently.")] [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSReviewUnusedParameter", "", Justification="One or more parameters (like NoStatus) are only referenced by helper methods which get access to it from the stack via Get-Variable -Scope 1.")] param( - [Parameter(Mandatory)] + [Parameter( + Mandatory, + ValueFromPipelineByPropertyName)] + [Alias('ColumnId')] [int64] $Column, [Parameter(Mandatory)] - [string] $Name, + [Alias('Name')] + [string] $ColumnName, [string] $AccessToken, @@ -213,7 +263,7 @@ function Set-GitHubProjectColumn $apiDescription = "Updating column $Column" $hashBody = @{ - 'name' = $Name + 'name' = $ColumnName } $params = @{ @@ -225,13 +275,13 @@ function Set-GitHubProjectColumn 'TelemetryEventName' = $MyInvocation.MyCommand.Name 'TelemetryProperties' = $telemetryProperties 'NoStatus' = (Resolve-ParameterWithDefaultConfigurationValue -Name NoStatus -ConfigValueName DefaultNoStatus) - 'AcceptHeader' = 'application/vnd.github.inertia-preview+json' + 'AcceptHeader' = $script:inertiaAcceptHeader } - return Invoke-GHRestMethod @params + return (Invoke-GHRestMethod @params | Add-GitHubProjectColumnAdditionalProperties) } -function Remove-GitHubProjectColumn +filter Remove-GitHubProjectColumn { <# .DESCRIPTION @@ -255,6 +305,10 @@ function Remove-GitHubProjectColumn the background, enabling the command prompt to provide status information. If not supplied here, the DefaultNoStatus configuration property value will be used. + .INPUTS + GitHub.ProjectCard + GitHub.ProjectColumn + .EXAMPLE Remove-GitHubProjectColumn -Column 999999 @@ -276,7 +330,10 @@ function Remove-GitHubProjectColumn [Alias('Delete-GitHubProjectColumn')] [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSReviewUnusedParameter", "", Justification="One or more parameters (like NoStatus) are only referenced by helper methods which get access to it from the stack via Get-Variable -Scope 1.")] param( - [Parameter(Mandatory)] + [Parameter( + Mandatory, + ValueFromPipelineByPropertyName)] + [Alias('ColumnId')] [int64] $Column, [switch] $Force, @@ -308,14 +365,14 @@ function Remove-GitHubProjectColumn 'TelemetryEventName' = $MyInvocation.MyCommand.Name 'TelemetryProperties' = $telemetryProperties 'NoStatus' = (Resolve-ParameterWithDefaultConfigurationValue -Name NoStatus -ConfigValueName DefaultNoStatus) - 'AcceptHeader' = 'application/vnd.github.inertia-preview+json' + 'AcceptHeader' = $script:inertiaAcceptHeader } return Invoke-GHRestMethod @params } } -function Move-GitHubProjectColumn +filter Move-GitHubProjectColumn { <# .DESCRIPTION @@ -346,6 +403,10 @@ function Move-GitHubProjectColumn the background, enabling the command prompt to provide status information. If not supplied here, the DefaultNoStatus configuration property value will be used. + .INPUTS + GitHub.ProjectCard + GitHub.ProjectColumn + .EXAMPLE Move-GitHubProjectColumn -Column 999999 -First @@ -361,12 +422,14 @@ function Move-GitHubProjectColumn Moves the project column with ID 999999 to the position after column with ID 888888. #> - [CmdletBinding( - SupportsShouldProcess)] + [CmdletBinding(SupportsShouldProcess)] [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSShouldProcess", "", Justification = "Methods called within here make use of PSShouldProcess, and the switch is passed on to them inherently.")] [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSReviewUnusedParameter", "", Justification="One or more parameters (like NoStatus) are only referenced by helper methods which get access to it from the stack via Get-Variable -Scope 1.")] param( - [Parameter(Mandatory)] + [Parameter( + Mandatory, + ValueFromPipelineByPropertyName)] + [Alias('ColumnId')] [int64] $Column, [switch] $First, @@ -419,8 +482,60 @@ function Move-GitHubProjectColumn 'TelemetryEventName' = $MyInvocation.MyCommand.Name 'TelemetryProperties' = $telemetryProperties 'NoStatus' = (Resolve-ParameterWithDefaultConfigurationValue -Name NoStatus -ConfigValueName DefaultNoStatus) - 'AcceptHeader' = 'application/vnd.github.inertia-preview+json' + 'AcceptHeader' = $script:inertiaAcceptHeader } return Invoke-GHRestMethod @params -} \ No newline at end of file +} + +filter Add-GitHubProjectColumnAdditionalProperties +{ +<# + .SYNOPSIS + Adds type name and additional properties to ease pipelining to GitHub Project Column objects. + + .PARAMETER InputObject + The GitHub object to add additional properties to. + + .PARAMETER TypeName + The type that should be assigned to the object. + + .INPUTS + [PSCustomObject] + + .OUTPUTS + GitHub.ProjectColumn +#> + [CmdletBinding()] + [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSUseSingularNouns", "", Justification="Internal helper that is definitely adding more than one property.")] + param( + [Parameter( + Mandatory, + ValueFromPipeline)] + [AllowNull()] + [AllowEmptyCollection()] + [PSCustomObject[]] $InputObject, + + [ValidateNotNullOrEmpty()] + [string] $TypeName = $script:GitHubProjectColumnTypeName + ) + + foreach ($item in $InputObject) + { + $item.PSObject.TypeNames.Insert(0, $TypeName) + + if (-not (Get-GitHubConfiguration -Name DisablePipelineSupport)) + { + Add-Member -InputObject $item -Name 'ColumnId' -Value $item.id -MemberType NoteProperty -Force + Add-Member -InputObject $item -Name 'ColumnName' -Value $item.name -MemberType NoteProperty -Force + + if ($item.project_url -match '^.*/projects/(\d+)$') + { + $projectId = $Matches[1] + Add-Member -InputObject $item -Name 'ProjectId' -Value $projectId -MemberType NoteProperty -Force + } + } + + Write-Output $item + } +} diff --git a/GitHubProjects.ps1 b/GitHubProjects.ps1 index 6d026730..3476f263 100644 --- a/GitHubProjects.ps1 +++ b/GitHubProjects.ps1 @@ -1,11 +1,17 @@ # Copyright (c) Microsoft Corporation. All rights reserved. # Licensed under the MIT License. -function Get-GitHubProject +@{ + GitHubProjectTypeName = 'GitHub.Project' + }.GetEnumerator() | ForEach-Object { + Set-Variable -Scope Script -Option ReadOnly -Name $_.Key -Value $_.Value + } + +filter Get-GitHubProject { <# .DESCRIPTION - Get the projects for a given Github user, repository or organization. + Get the projects for a given GitHub user, repository or organization. The Git repo for this module can be found here: http://aka.ms/PowerShellForGitHub @@ -44,10 +50,28 @@ function Get-GitHubProject the background, enabling the command prompt to provide status information. If not supplied here, the DefaultNoStatus configuration property value will be used. + .INPUTS + GitHub.Branch + GitHub.Content + GitHub.Event + GitHub.Issue + GitHub.IssueComment + GitHub.Label + GitHub.Milestone + GitHub.PullRequest + GitHub.Project + GitHub.ProjectCard + GitHub.ProjectColumn + GitHub.Release + GitHub.Repository + + .OUTPUTS + GitHub.Project + .EXAMPLE - Get-GitHubProject -OwnerName Microsoft -RepositoryName PowerShellForGitHub + Get-GitHubProject -OwnerName microsoft -RepositoryName PowerShellForGitHub - Get the projects for the Microsoft\PowerShellForGitHub repository. + Get the projects for the microsoft\PowerShellForGitHub repository. .EXAMPLE Get-GitHubProject -OrganizationName Microsoft @@ -57,7 +81,7 @@ function Get-GitHubProject .EXAMPLE Get-GitHubProject -Uri https://github.com/Microsoft/PowerShellForGitHub - Get the projects for the Microsoft\PowerShellForGitHub repository using the Uri. + Get the projects for the microsoft\PowerShellForGitHub repository using the Uri. .EXAMPLE Get-GitHubProject -UserName GitHubUser @@ -65,9 +89,9 @@ function Get-GitHubProject Get the projects for the user GitHubUser. .EXAMPLE - Get-GitHubProject -OwnerName Microsoft -RepositoryName PowerShellForGitHub -State Closed + Get-GitHubProject -OwnerName microsoft -RepositoryName PowerShellForGitHub -State Closed - Get closed projects from the Microsoft\PowerShellForGitHub repo. + Get closed projects from the microsoft\PowerShellForGitHub repo. .EXAMPLE Get-GitHubProject -Project 4378613 @@ -77,24 +101,50 @@ function Get-GitHubProject [CmdletBinding( SupportsShouldProcess, DefaultParameterSetName = 'Elements')] + [OutputType({$script:GitHubPullRequestTypeName})] [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSShouldProcess", "", Justification = "Methods called within here make use of PSShouldProcess, and the switch is passed on to them inherently.")] param( - [Parameter(Mandatory, ParameterSetName = 'Elements')] + [Parameter( + Mandatory, + ParameterSetName = 'Elements')] [string] $OwnerName, - [Parameter(Mandatory, ParameterSetName = 'Elements')] + [Parameter( + Mandatory, + ParameterSetName = 'Elements')] [string] $RepositoryName, - [Parameter(Mandatory, ParameterSetName = 'Uri')] + [Parameter( + Mandatory, + ValueFromPipelineByPropertyName, + ParameterSetName='Uri')] + [Parameter( + Mandatory, + ValueFromPipelineByPropertyName, + ParameterSetName='ProjectObject')] + [Alias('RepositoryUrl')] [string] $Uri, - [Parameter(Mandatory, ParameterSetName = 'Organization')] + [Parameter( + Mandatory, + ParameterSetName = 'Organization')] [string] $OrganizationName, - [Parameter(Mandatory, ParameterSetName = 'User')] + [Parameter( + Mandatory, + ParameterSetName = 'User')] [string] $UserName, - [Parameter(Mandatory, ParameterSetName = 'Project')] + [Parameter( + Mandatory, + ValueFromPipeline, + ValueFromPipelineByPropertyName, + ParameterSetName = 'Project')] + [Parameter( + Mandatory, + ValueFromPipelineByPropertyName, + ParameterSetName='ProjectObject')] + [Alias('ProjectId')] [int64] $Project, [ValidateSet('Open', 'Closed', 'All')] @@ -111,7 +161,7 @@ function Get-GitHubProject $uriFragment = [String]::Empty $description = [String]::Empty - if ($PSCmdlet.ParameterSetName -eq 'Project') + if ($PSCmdlet.ParameterSetName -in @('Project', 'ProjectObject')) { $telemetryProperties['Project'] = Get-PiiSafeString -PlainText $Project @@ -162,18 +212,18 @@ function Get-GitHubProject 'TelemetryEventName' = $MyInvocation.MyCommand.Name 'TelemetryProperties' = $telemetryProperties 'NoStatus' = (Resolve-ParameterWithDefaultConfigurationValue -Name NoStatus -ConfigValueName DefaultNoStatus) - 'AcceptHeader' = 'application/vnd.github.inertia-preview+json' + 'AcceptHeader' = $script:inertiaAcceptHeader } - return Invoke-GHRestMethodMultipleResult @params + return (Invoke-GHRestMethodMultipleResult @params | Add-GitHubProjectAdditionalProperties) } -function New-GitHubProject +filter New-GitHubProject { <# .DESCRIPTION - Creates a new Github project for the given repository + Creates a new GitHub project for the given repository The Git repo for this module can be found here: http://aka.ms/PowerShellForGitHub @@ -212,48 +262,83 @@ function New-GitHubProject the background, enabling the command prompt to provide status information. If not supplied here, the DefaultNoStatus configuration property value will be used. + .INPUTS + GitHub.Branch + GitHub.Content + GitHub.Event + GitHub.Issue + GitHub.IssueComment + GitHub.Label + GitHub.Milestone + GitHub.PullRequest + GitHub.Project + GitHub.ProjectCard + GitHub.ProjectColumn + GitHub.Release + GitHub.Repository + + .OUTPUTS + GitHub.Project + .EXAMPLE - New-GitHubProject -OwnerName Microsoft -RepositoryName PowerShellForGitHub -Name TestProject + New-GitHubProject -OwnerName microsoft -RepositoryName PowerShellForGitHub -ProjectName TestProject - Creates a project called 'TestProject' for the Microsoft\PowerShellForGitHub repository. + Creates a project called 'TestProject' for the microsoft\PowerShellForGitHub repository. .EXAMPLE - New-GitHubProject -OrganizationName Microsoft -Name TestProject -Description 'This is just a test project' + New-GitHubProject -OrganizationName Microsoft -ProjectName TestProject -Description 'This is just a test project' Create a project for the Microsoft organization called 'TestProject' with a description. .EXAMPLE - New-GitHubProject -Uri https://github.com/Microsoft/PowerShellForGitHub -Name TestProject + New-GitHubProject -Uri https://github.com/Microsoft/PowerShellForGitHub -ProjectName TestProject - Create a project for the Microsoft\PowerShellForGitHub repository using the Uri called 'TestProject'. + Create a project for the microsoft\PowerShellForGitHub repository + using the Uri called 'TestProject'. .EXAMPLE - New-GitHubProject -UserProject -Name 'TestProject' + New-GitHubProject -UserProject -ProjectName 'TestProject' Creates a project for the signed in user called 'TestProject'. #> [CmdletBinding( SupportsShouldProcess, DefaultParameterSetName = 'Elements')] + [OutputType({$script:GitHubPullRequestTypeName})] [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSShouldProcess", "", Justification = "Methods called within here make use of PSShouldProcess, and the switch is passed on to them inherently.")] param( - [Parameter(Mandatory, ParameterSetName = 'Elements')] + [Parameter( + Mandatory, + ParameterSetName = 'Elements')] [string] $OwnerName, - [Parameter(Mandatory, ParameterSetName = 'Elements')] + [Parameter( + Mandatory, + ParameterSetName = 'Elements')] [string] $RepositoryName, - [Parameter(Mandatory, ParameterSetName = 'Uri')] + [Parameter( + Mandatory, + ValueFromPipelineByPropertyName, + ParameterSetName='Uri')] + [Alias('RepositoryUrl')] [string] $Uri, - [Parameter(Mandatory, ParameterSetName = 'Organization')] + [Parameter( + Mandatory, + ParameterSetName = 'Organization')] [string] $OrganizationName, - [Parameter(Mandatory, ParameterSetName = 'User')] + [Parameter( + Mandatory, + ParameterSetName = 'User')] [switch] $UserProject, - [Parameter(Mandatory)] - [string] $Name, + [Parameter( + Mandatory, + ValueFromPipeline)] + [Alias('Name')] + [string] $ProjectName, [string] $Description, @@ -265,7 +350,7 @@ function New-GitHubProject Write-InvocationLog $telemetryProperties = @{} - $telemetryProperties['Name'] = Get-PiiSafeString -PlainText $Name + $telemetryProperties['ProjectName'] = Get-PiiSafeString -PlainText $ProjectName $uriFragment = [String]::Empty $apiDescription = [String]::Empty @@ -297,7 +382,7 @@ function New-GitHubProject } $hashBody = @{ - 'name' = $Name + 'name' = $ProjectName } if ($PSBoundParameters.ContainsKey('Description')) @@ -314,13 +399,13 @@ function New-GitHubProject 'TelemetryEventName' = $MyInvocation.MyCommand.Name 'TelemetryProperties' = $telemetryProperties 'NoStatus' = (Resolve-ParameterWithDefaultConfigurationValue -Name NoStatus -ConfigValueName DefaultNoStatus) - 'AcceptHeader' = 'application/vnd.github.inertia-preview+json' + 'AcceptHeader' = $script:inertiaAcceptHeader } - return Invoke-GHRestMethod @params + return (Invoke-GHRestMethod @params | Add-GitHubProjectAdditionalProperties) } -function Set-GitHubProject +filter Set-GitHubProject { <# .DESCRIPTION @@ -357,23 +442,45 @@ function Set-GitHubProject the background, enabling the command prompt to provide status information. If not supplied here, the DefaultNoStatus configuration property value will be used. + .INPUTS + GitHub.Branch + GitHub.Content + GitHub.Event + GitHub.Issue + GitHub.IssueComment + GitHub.Label + GitHub.Milestone + GitHub.PullRequest + GitHub.Project + GitHub.ProjectCard + GitHub.ProjectColumn + GitHub.Release + GitHub.Repository + + .OUTPUTS + GitHub.Project + .EXAMPLE Set-GitHubProject -Project 999999 -State Closed Set the project with ID '999999' to closed. .EXAMPLE - $project = Get-GitHubProject -OwnerName Microsoft -RepositoryName PowerShellForGitHub | Where-Object Name -eq 'TestProject' + $project = Get-GitHubProject -OwnerName microsoft -RepositoryName PowerShellForGitHub | Where-Object Name -eq 'TestProject' Set-GitHubProject -Project $project.id -State Closed - Get the ID for the 'TestProject' project for the Microsoft\PowerShellForGitHub + Get the ID for the 'TestProject' project for the microsoft\PowerShellForGitHub repository and set state to closed. #> - [CmdletBinding( - SupportsShouldProcess)] + [CmdletBinding(SupportsShouldProcess)] + [OutputType({$script:GitHubPullRequestTypeName})] [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSShouldProcess", "", Justification = "Methods called within here make use of PSShouldProcess, and the switch is passed on to them inherently.")] param( - [Parameter(Mandatory)] + [Parameter( + Mandatory, + ValueFromPipeline, + ValueFromPipelineByPropertyName)] + [Alias('ProjectId')] [int64] $Project, [string] $Description, @@ -433,17 +540,17 @@ function Set-GitHubProject 'TelemetryEventName' = $MyInvocation.MyCommand.Name 'TelemetryProperties' = $telemetryProperties 'NoStatus' = (Resolve-ParameterWithDefaultConfigurationValue -Name NoStatus -ConfigValueName DefaultNoStatus) - 'AcceptHeader' = 'application/vnd.github.inertia-preview+json' + 'AcceptHeader' = $script:inertiaAcceptHeader } - return Invoke-GHRestMethod @params + return (Invoke-GHRestMethod @params | Add-GitHubProjectAdditionalProperties) } -function Remove-GitHubProject +filter Remove-GitHubProject { <# .DESCRIPTION - Removes the projects for a given Github repository. + Removes the projects for a given GitHub repository. The Git repo for this module can be found here: http://aka.ms/PowerShellForGitHub @@ -463,6 +570,21 @@ function Remove-GitHubProject the background, enabling the command prompt to provide status information. If not supplied here, the DefaultNoStatus configuration property value will be used. + .INPUTS + GitHub.Branch + GitHub.Content + GitHub.Event + GitHub.Issue + GitHub.IssueComment + GitHub.Label + GitHub.Milestone + GitHub.PullRequest + GitHub.Project + GitHub.ProjectCard + GitHub.ProjectColumn + GitHub.Release + GitHub.Repository + .EXAMPLE Remove-GitHubProject -Project 4387531 @@ -479,10 +601,10 @@ function Remove-GitHubProject Remove project with ID '4387531' without prompting for confirmation. .EXAMPLE - $project = Get-GitHubProject -OwnerName Microsoft -RepositoryName PowerShellForGitHub | Where-Object Name -eq 'TestProject' + $project = Get-GitHubProject -OwnerName microsoft -RepositoryName PowerShellForGitHub | Where-Object Name -eq 'TestProject' Remove-GitHubProject -Project $project.id - Get the ID for the 'TestProject' project for the Microsoft\PowerShellForGitHub + Get the ID for the 'TestProject' project for the microsoft\PowerShellForGitHub repository and then remove the project. #> [CmdletBinding( @@ -491,7 +613,10 @@ function Remove-GitHubProject [Alias('Delete-GitHubProject')] [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSReviewUnusedParameter", "", Justification="One or more parameters (like NoStatus) are only referenced by helper methods which get access to it from the stack via Get-Variable -Scope 1.")] param( - [Parameter(Mandatory)] + [Parameter( + Mandatory, + ValueFromPipelineByPropertyName)] + [Alias('ProjectId')] [int64] $Project, [switch] $Force, @@ -523,10 +648,70 @@ function Remove-GitHubProject 'TelemetryEventName' = $MyInvocation.MyCommand.Name 'TelemetryProperties' = $telemetryProperties 'NoStatus' = (Resolve-ParameterWithDefaultConfigurationValue -Name NoStatus -ConfigValueName DefaultNoStatus) - 'AcceptHeader' = 'application/vnd.github.inertia-preview+json' + 'AcceptHeader' = $script:inertiaAcceptHeader } return Invoke-GHRestMethod @params } } + +filter Add-GitHubProjectAdditionalProperties +{ +<# + .SYNOPSIS + Adds type name and additional properties to ease pipelining to GitHub Project objects. + + .PARAMETER InputObject + The GitHub object to add additional properties to. + + .PARAMETER TypeName + The type that should be assigned to the object. + + .INPUTS + [PSCustomObject] + + .OUTPUTS + GitHub.Project +#> + [CmdletBinding()] + [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSUseSingularNouns", "", Justification="Internal helper that is definitely adding more than one property.")] + param( + [Parameter( + Mandatory, + ValueFromPipeline)] + [AllowNull()] + [AllowEmptyCollection()] + [PSCustomObject[]] $InputObject, + + [ValidateNotNullOrEmpty()] + [string] $TypeName = $script:GitHubProjectTypeName + ) + + foreach ($item in $InputObject) + { + $item.PSObject.TypeNames.Insert(0, $TypeName) + + if (-not (Get-GitHubConfiguration -Name DisablePipelineSupport)) + { + $elements = Split-GitHubUri -Uri $item.html_url + $repositoryUrl = Join-GitHubUri @elements + + # A "user" project has no associated repository, and adding this in that scenario + # would cause API-level errors with piping further on, + if ($elements.OwnerName -ne 'users') + { + Add-Member -InputObject $item -Name 'RepositoryUrl' -Value $repositoryUrl -MemberType NoteProperty -Force + } + + Add-Member -InputObject $item -Name 'ProjectId' -Value $item.id -MemberType NoteProperty -Force + + if ($null -ne $item.creator) + { + $null = Add-GitHubUserAdditionalProperties -InputObject $item.creator + } + } + + Write-Output $item + } +} diff --git a/GitHubPullRequests.ps1 b/GitHubPullRequests.ps1 index d2d99337..c08c887a 100644 --- a/GitHubPullRequests.ps1 +++ b/GitHubPullRequests.ps1 @@ -1,7 +1,13 @@ # Copyright (c) Microsoft Corporation. All rights reserved. # Licensed under the MIT License. -function Get-GitHubPullRequest +@{ + GitHubPullRequestTypeName = 'GitHub.PullRequest' + }.GetEnumerator() | ForEach-Object { + Set-Variable -Scope Script -Option ReadOnly -Name $_.Key -Value $_.Value + } + +filter Get-GitHubPullRequest { <# .SYNOPSIS @@ -58,14 +64,29 @@ function Get-GitHubPullRequest the background, enabling the command prompt to provide status information. If not supplied here, the DefaultNoStatus configuration property value will be used. + .INPUTS + GitHub.Branch + GitHub.Content + GitHub.Event + GitHub.Issue + GitHub.IssueComment + GitHub.Label + GitHub.Milestone + GitHub.PullRequest + GitHub.Project + GitHub.ProjectCard + GitHub.ProjectColumn + GitHub.Release + GitHub.Repository + .OUTPUTS - [PSCustomObject[]] List of Pull Requests that match the specified criteria. + GitHub.PulLRequest .EXAMPLE $pullRequests = Get-GitHubPullRequest -Uri 'https://github.com/PowerShell/PowerShellForGitHub' .EXAMPLE - $pullRequests = Get-GitHubPullRequest -OwnerName Microsoft -RepositoryName PowerShellForGitHub -State Closed + $pullRequests = Get-GitHubPullRequest -OwnerName microsoft -RepositoryName PowerShellForGitHub -State Closed #> [CmdletBinding( SupportsShouldProcess, @@ -80,10 +101,16 @@ function Get-GitHubPullRequest [Parameter( Mandatory, + ValueFromPipelineByPropertyName, ParameterSetName='Uri')] + [Alias('RepositoryUrl')] [string] $Uri, - [string] $PullRequest, + [Parameter( + ValueFromPipeline, + ValueFromPipelineByPropertyName)] + [Alias('PullRequestNumber')] + [int64] $PullRequest, [ValidateSet('Open', 'Closed', 'All')] [string] $State = 'Open', @@ -117,7 +144,7 @@ function Get-GitHubPullRequest $uriFragment = "/repos/$OwnerName/$RepositoryName/pulls" $description = "Getting pull requests for $RepositoryName" - if (-not [String]::IsNullOrEmpty($PullRequest)) + if ($PSBoundParameters.ContainsKey('PullRequest')) { $uriFragment = $uriFragment + "/$PullRequest" $description = "Getting pull request $PullRequest for $RepositoryName" @@ -154,24 +181,25 @@ function Get-GitHubPullRequest $params = @{ 'UriFragment' = $uriFragment + '?' + ($getParams -join '&') 'Description' = $description - 'AcceptHeader' = 'application/vnd.github.symmetra-preview+json' + 'AcceptHeader' = $script:symmetraAcceptHeader 'AccessToken' = $AccessToken 'TelemetryEventName' = $MyInvocation.MyCommand.Name 'TelemetryProperties' = $telemetryProperties 'NoStatus' = (Resolve-ParameterWithDefaultConfigurationValue -Name NoStatus -ConfigValueName DefaultNoStatus) } - return Invoke-GHRestMethodMultipleResult @params + return (Invoke-GHRestMethodMultipleResult @params | Add-GitHubPullRequestAdditionalProperties) } -function New-GitHubPullRequest +filter New-GitHubPullRequest { <# .SYNOPSIS Create a new pull request in the specified repository. .DESCRIPTION - Opens a new pull request from the given branch into the given branch in the specified repository. + Opens a new pull request from the given branch into the given branch + in the specified repository. The Git repo for this module can be found here: http://aka.ms/PowerShellForGitHub @@ -231,8 +259,23 @@ function New-GitHubPullRequest the background, enabling the command prompt to provide status information. If not supplied here, the DefaultNoStatus configuration property value will be used. + .INPUTS + GitHub.Branch + GitHub.Content + GitHub.Event + GitHub.Issue + GitHub.IssueComment + GitHub.Label + GitHub.Milestone + GitHub.PullRequest + GitHub.Project + GitHub.ProjectCard + GitHub.ProjectColumn + GitHub.Release + GitHub.Repository + .OUTPUTS - [PSCustomObject] An object describing the created pull request. + GitHub.PullRequest .EXAMPLE $prParams = @{ @@ -254,7 +297,9 @@ function New-GitHubPullRequest #> [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSShouldProcess", "", Justification="Methods called within here make use of PSShouldProcess, and the switch is passed on to them inherently.")] - [CmdletBinding(SupportsShouldProcess, DefaultParameterSetName='Elements_Title')] + [CmdletBinding( + SupportsShouldProcess, + DefaultParameterSetName='Elements_Title')] param( [Parameter(ParameterSetName='Elements_Title')] [Parameter(ParameterSetName='Elements_Issue')] @@ -266,10 +311,13 @@ function New-GitHubPullRequest [Parameter( Mandatory, + ValueFromPipelineByPropertyName, ParameterSetName='Uri_Title')] [Parameter( Mandatory, + ValueFromPipelineByPropertyName, ParameterSetName='Uri_Issue')] + [Alias('RepositoryUrl')] [string] $Uri, [Parameter( @@ -287,10 +335,13 @@ function New-GitHubPullRequest [Parameter( Mandatory, + ValueFromPipelineByPropertyName, ParameterSetName='Elements_Issue')] [Parameter( Mandatory, + ValueFromPipelineByPropertyName, ParameterSetName='Uri_Issue')] + [Alias('IssueNumber')] [int] $Issue, [Parameter(Mandatory)] @@ -387,5 +438,73 @@ function New-GitHubPullRequest $restParams['AcceptHeader'] = $acceptHeader } - return Invoke-GHRestMethod @restParams + return (Invoke-GHRestMethod @restParams | Add-GitHubPullRequestAdditionalProperties) +} + +filter Add-GitHubPullRequestAdditionalProperties +{ +<# + .SYNOPSIS + Adds type name and additional properties to ease pipelining to GitHub Repository objects. + + .PARAMETER InputObject + The GitHub object to add additional properties to. + + .PARAMETER TypeName + The type that should be assigned to the object. +#> + [CmdletBinding()] + [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSUseSingularNouns", "", Justification="Internal helper that is definitely adding more than one property.")] + param( + [Parameter( + Mandatory, + ValueFromPipeline)] + [AllowNull()] + [AllowEmptyCollection()] + [PSCustomObject[]] $InputObject, + + [ValidateNotNullOrEmpty()] + [string] $TypeName = $script:GitHubPullRequestTypeName + ) + + foreach ($item in $InputObject) + { + $item.PSObject.TypeNames.Insert(0, $TypeName) + + if (-not (Get-GitHubConfiguration -Name DisablePipelineSupport)) + { + $elements = Split-GitHubUri -Uri $item.html_url + $repositoryUrl = Join-GitHubUri @elements + Add-Member -InputObject $item -Name 'RepositoryUrl' -Value $repositoryUrl -MemberType NoteProperty -Force + Add-Member -InputObject $item -Name 'PullRequestId' -Value $item.id -MemberType NoteProperty -Force + Add-Member -InputObject $item -Name 'PullRequestNumber' -Value $item.number -MemberType NoteProperty -Force + + @('assignee', 'assignees', 'requested_reviewers', 'merged_by', 'user') | + ForEach-Object { + if ($null -ne $item.$_) + { + $null = Add-GitHubUserAdditionalProperties -InputObject $item.$_ + } + } + + if ($null -ne $item.labels) + { + $null = Add-GitHubLabelAdditionalProperties -InputObject $item.labels + } + + if ($null -ne $item.milestone) + { + $null = Add-GitHubMilestoneAdditionalProperties -InputObject $item.milestone + } + + if ($null -ne $item.requested_teams) + { + $null = Add-GitHubTeamAdditionalProperties -InputObject $item.requested_teams + } + + # TODO: What type are item.head and item.base? + } + + Write-Output $item + } } diff --git a/GitHubReleases.ps1 b/GitHubReleases.ps1 index e7eb49a2..6083f669 100644 --- a/GitHubReleases.ps1 +++ b/GitHubReleases.ps1 @@ -1,7 +1,13 @@ # Copyright (c) Microsoft Corporation. All rights reserved. # Licensed under the MIT License. -function Get-GitHubRelease +@{ + GitHubReleaseTypeName = 'GitHub.Release' + }.GetEnumerator() | ForEach-Object { + Set-Variable -Scope Script -Option ReadOnly -Name $_.Key -Value $_.Value + } + +filter Get-GitHubRelease { <# .SYNOPSIS @@ -25,8 +31,8 @@ function Get-GitHubRelease The OwnerName and RepositoryName will be extracted from here instead of needing to provide them individually. - .PARAMETER ReleaseId - Specific releaseId of a release. + .PARAMETER Release + The ID of a specific release. This is an optional parameter which can limit the results to a single release. .PARAMETER Latest @@ -47,13 +53,31 @@ function Get-GitHubRelease the background, enabling the command prompt to provide status information. If not supplied here, the DefaultNoStatus configuration property value will be used. + .INPUTS + GitHub.Branch + GitHub.Content + GitHub.Event + GitHub.Issue + GitHub.IssueComment + GitHub.Label + GitHub.Milestone + GitHub.PullRequest + GitHub.Project + GitHub.ProjectCard + GitHub.ProjectColumn + GitHub.Release + GitHub.Repository + + .OUTPUTS + GitHub.Release + .EXAMPLE Get-GitHubRelease Gets all releases for the default configured owner/repository. .EXAMPLE - Get-GitHubRelease -ReleaseId 12345 + Get-GitHubRelease -Release 12345 Get a specific release for the default configured owner/repository @@ -84,49 +108,50 @@ function Get-GitHubRelease [CmdletBinding( SupportsShouldProcess, DefaultParameterSetName='Elements')] + [OutputType({$script:GitHubReleaseTypeName})] [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSShouldProcess", "", Justification="Methods called within here make use of PSShouldProcess, and the switch is passed on to them inherently.")] param( - [Parameter( - ParameterSetName='Elements')] - [Parameter( - ParameterSetName="Elements-ReleaseId")] - [Parameter( - ParameterSetName="Elements-Latest")] - [Parameter( - ParameterSetName="Elements-Tag")] + [Parameter(ParameterSetName='Elements')] + [Parameter(ParameterSetName="Elements-ReleaseId")] + [Parameter(ParameterSetName="Elements-Latest")] + [Parameter(ParameterSetName="Elements-Tag")] [string] $OwnerName, - [Parameter( - ParameterSetName='Elements')] - [Parameter( - ParameterSetName="Elements-ReleaseId")] - [Parameter( - ParameterSetName="Elements-Latest")] - [Parameter( - ParameterSetName="Elements-Tag")] + [Parameter(ParameterSetName='Elements')] + [Parameter(ParameterSetName="Elements-ReleaseId")] + [Parameter(ParameterSetName="Elements-Latest")] + [Parameter(ParameterSetName="Elements-Tag")] [string] $RepositoryName, [Parameter( Mandatory, + ValueFromPipelineByPropertyName, ParameterSetName='Uri')] [Parameter( Mandatory, + ValueFromPipelineByPropertyName, ParameterSetName="Uri-ReleaseId")] [Parameter( Mandatory, + ValueFromPipelineByPropertyName, ParameterSetName="Uri-Latest")] [Parameter( Mandatory, + ValueFromPipelineByPropertyName, ParameterSetName="Uri-Tag")] + [Alias('RepositoryUrl')] [string] $Uri, [Parameter( Mandatory, + ValueFromPipelineByPropertyName, ParameterSetName="Elements-ReleaseId")] [Parameter( Mandatory, + ValueFromPipelineByPropertyName, ParameterSetName="Uri-ReleaseId")] - [string] $ReleaseId, + [Alias('ReleaseId')] + [int64] $Release, [Parameter( Mandatory, @@ -163,12 +188,12 @@ function Get-GitHubRelease $uriFragment = "repos/$OwnerName/$RepositoryName/releases" $description = "Getting releases for $OwnerName/$RepositoryName" - if(-not [String]::IsNullOrEmpty($ReleaseId)) + if ($PSBoundParameters.ContainsKey('Release')) { - $telemetryProperties['ProvidedReleaseId'] = $true + $telemetryProperties['ProvidedRelease'] = $true - $uriFragment += "/$ReleaseId" - $description = "Getting release information for $ReleaseId from $OwnerName/$RepositoryName" + $uriFragment += "/$Release" + $description = "Getting release information for $Release from $OwnerName/$RepositoryName" } if($Latest) @@ -193,8 +218,66 @@ function Get-GitHubRelease 'AccessToken' = $AccessToken 'TelemetryEventName' = $MyInvocation.MyCommand.Name 'TelemetryProperties' = $telemetryProperties - 'NoStatus' = (Resolve-ParameterWithDefaultConfigurationValue -BoundParameters $PSBoundParameters -Name NoStatus -ConfigValueName DefaultNoStatus) + 'NoStatus' = (Resolve-ParameterWithDefaultConfigurationValue -Name NoStatus -ConfigValueName DefaultNoStatus) } - return Invoke-GHRestMethodMultipleResult @params + return (Invoke-GHRestMethodMultipleResult @params | Add-GitHubReleaseAdditionalProperties) +} + + +filter Add-GitHubReleaseAdditionalProperties +{ +<# + .SYNOPSIS + Adds type name and additional properties to ease pipelining to GitHub Release objects. + + .PARAMETER InputObject + The GitHub object to add additional properties to. + + .PARAMETER TypeName + The type that should be assigned to the object. + + .INPUTS + [PSCustomObject] + + .OUTPUTS + GitHub.Release +#> + [CmdletBinding()] + [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSUseSingularNouns", "", Justification="Internal helper that is definitely adding more than one property.")] + param( + [Parameter( + Mandatory, + ValueFromPipeline)] + [AllowNull()] + [AllowEmptyCollection()] + [PSCustomObject[]] $InputObject, + + [ValidateNotNullOrEmpty()] + [string] $TypeName = $script:GitHubReleaseTypeName + ) + + foreach ($item in $InputObject) + { + $item.PSObject.TypeNames.Insert(0, $TypeName) + + if (-not (Get-GitHubConfiguration -Name DisablePipelineSupport)) + { + if (-not [String]::IsNullOrEmpty($item.html_url)) + { + $elements = Split-GitHubUri -Uri $item.html_url + $repositoryUrl = Join-GitHubUri @elements + Add-Member -InputObject $item -Name 'RepositoryUrl' -Value $repositoryUrl -MemberType NoteProperty -Force + } + + Add-Member -InputObject $item -Name 'ReleaseId' -Value $item.id -MemberType NoteProperty -Force + + if ($null -ne $item.author) + { + $null = Add-GitHubUserAdditionalProperties -InputObject $item.author + } + } + + Write-Output $item + } } diff --git a/GitHubRepositories.ps1 b/GitHubRepositories.ps1 index f962d1d4..c59e33e5 100644 --- a/GitHubRepositories.ps1 +++ b/GitHubRepositories.ps1 @@ -1,7 +1,17 @@ # Copyright (c) Microsoft Corporation. All rights reserved. # Licensed under the MIT License. -function New-GitHubRepository +@{ + GitHubRepositoryTypeName = 'GitHub.Repository' + GitHubRepositoryTopicTypeName = 'GitHub.RepositoryTopic' + GitHubRepositoryContributorStatisticsTypeName = 'GitHub.RepositoryContributorStatistics' + GitHubRepositoryLanguageTypeName = 'GitHub.RepositoryLanguage' + GitHubRepositoryTagTypeName = 'GitHub.RepositoryTag' + }.GetEnumerator() | ForEach-Object { + Set-Variable -Scope Script -Option ReadOnly -Name $_.Key -Value $_.Value + } + +filter New-GitHubRepository { <# .SYNOPSIS @@ -85,19 +95,45 @@ function New-GitHubRepository the background, enabling the command prompt to provide status information. If not supplied here, the DefaultNoStatus configuration property value will be used. + .INPUTS + GitHub.Branch + GitHub.Content + GitHub.Event + GitHub.Issue + GitHub.IssueComment + GitHub.Label + GitHub.Milestone + GitHub.PullRequest + GitHub.Project + GitHub.ProjectCard + GitHub.ProjectColumn + GitHub.Release + GitHub.Repository + + .OUTPUTS + GitHub.Repository + .EXAMPLE New-GitHubRepository -RepositoryName MyNewRepo -AutoInit + .EXAMPLE + 'MyNewRepo' | New-GitHubRepository -AutoInit + .EXAMPLE New-GitHubRepository -RepositoryName MyNewRepo -Organization MyOrg -DisallowRebaseMerge #> [CmdletBinding(SupportsShouldProcess)] + [OutputType({$script:GitHubRepositoryTypeName})] [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSShouldProcess", "", Justification="Methods called within here make use of PSShouldProcess, and the switch is passed on to them inherently.")] param( - [Parameter(Mandatory)] + [Parameter( + Mandatory, + ValueFromPipeline)] [ValidateNotNullOrEmpty()] + [Alias('Name')] [string] $RepositoryName, + [Parameter(ValueFromPipelineByPropertyName)] [string] $OrganizationName, [string] $Description, @@ -185,13 +221,13 @@ function New-GitHubRepository 'AccessToken' = $AccessToken 'TelemetryEventName' = $MyInvocation.MyCommand.Name 'TelemetryProperties' = $telemetryProperties - 'NoStatus' = (Resolve-ParameterWithDefaultConfigurationValue -BoundParameters $PSBoundParameters -Name NoStatus -ConfigValueName DefaultNoStatus) + 'NoStatus' = (Resolve-ParameterWithDefaultConfigurationValue -Name NoStatus -ConfigValueName DefaultNoStatus) } - return Invoke-GHRestMethod @params + return (Invoke-GHRestMethod @params | Add-GitHubRepositoryAdditionalProperties) } -function Remove-GitHubRepository +filter Remove-GitHubRepository { <# .SYNOPSIS @@ -228,6 +264,21 @@ function Remove-GitHubRepository the background, enabling the command prompt to provide status information. If not supplied here, the DefaultNoStatus configuration property value will be used. + .INPUTS + GitHub.Branch + GitHub.Content + GitHub.Event + GitHub.Issue + GitHub.IssueComment + GitHub.Label + GitHub.Milestone + GitHub.PullRequest + GitHub.Project + GitHub.ProjectCard + GitHub.ProjectColumn + GitHub.Release + GitHub.Repository + .EXAMPLE Remove-GitHubRepository -OwnerName You -RepositoryName YourRepoToDelete @@ -243,6 +294,12 @@ function Remove-GitHubRepository Remove-GitHubRepository -Uri https://github.com/You/YourRepoToDelete -Force Remove repository with the given URI, without prompting for confirmation. + + .EXAMPLE + $repo = Get-GitHubRepository -Uri https://github.com/You/YourRepoToDelete + $repo | Remove-GitHubRepository -Force + + You can also pipe in a repo that was returned from a previous command. #> [CmdletBinding( SupportsShouldProcess, @@ -258,7 +315,9 @@ function Remove-GitHubRepository [Parameter( Mandatory, + ValueFromPipelineByPropertyName, ParameterSetName='Uri')] + [Alias('RepositoryUrl')] [string] $Uri, [switch] $Force, @@ -293,14 +352,14 @@ function Remove-GitHubRepository 'AccessToken' = $AccessToken 'TelemetryEventName' = $MyInvocation.MyCommand.Name 'TelemetryProperties' = $telemetryProperties - 'NoStatus' = (Resolve-ParameterWithDefaultConfigurationValue -BoundParameters $PSBoundParameters -Name NoStatus -ConfigValueName DefaultNoStatus) + 'NoStatus' = (Resolve-ParameterWithDefaultConfigurationValue -Name NoStatus -ConfigValueName DefaultNoStatus) } return Invoke-GHRestMethod @params } } -function Get-GitHubRepository +filter Get-GitHubRepository { <# .SYNOPSIS @@ -370,6 +429,24 @@ function Get-GitHubRepository the background, enabling the command prompt to provide status information. If not supplied here, the DefaultNoStatus configuration property value will be used. + .INPUTS + GitHub.Branch + GitHub.Content + GitHub.Event + GitHub.Issue + GitHub.IssueComment + GitHub.Label + GitHub.Milestone + GitHub.PullRequest + GitHub.Project + GitHub.ProjectCard + GitHub.ProjectColumn + GitHub.Release + GitHub.Repository + + .OUTPUTS + GitHub.Repository + .EXAMPLE Get-GitHubRepository @@ -385,11 +462,21 @@ function Get-GitHubRepository Gets all of the repositories for the user octocat + .EXAMPLE + Get-GitHubUser -UserName octocat | Get-GitHubRepository + + Gets all of the repositories for the user octocat + .EXAMPLE Get-GitHubRepository -Uri https://github.com/microsoft/PowerShellForGitHub Gets information about the microsoft/PowerShellForGitHub repository. + .EXAMPLE + $repo | Get-GitHubRepository + + You can pipe in a previous repository to get its refreshed information. + .EXAMPLE Get-GitHubRepository -OrganizationName PowerShell @@ -398,10 +485,14 @@ function Get-GitHubRepository [CmdletBinding( SupportsShouldProcess, DefaultParameterSetName='AuthenticatedUser')] + [OutputType({$script:GitHubRepositoryTypeName})] [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSShouldProcess", "", Justification="Methods called within here make use of PSShouldProcess, and the switch is passed on to them inherently.")] [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSReviewUnusedParameter", "", Justification="One or more parameters (like NoStatus) are only referenced by helper methods which get access to it from the stack via Get-Variable -Scope 1.")] param( - [Parameter(ParameterSetName='ElementsOrUser')] + [Parameter( + ValueFromPipelineByPropertyName, + ParameterSetName='ElementsOrUser')] + [Alias('UserName')] [string] $OwnerName, [Parameter(ParameterSetName='ElementsOrUser')] @@ -409,14 +500,18 @@ function Get-GitHubRepository [Parameter( Mandatory, + ValueFromPipelineByPropertyName, ParameterSetName='Uri')] + [Alias('RepositoryUrl')] [string] $Uri, - [Parameter(ParameterSetName='Organization')] + [Parameter( + ValueFromPipelineByPropertyName, + ParameterSetName='Organization')] [string] $OrganizationName, - [ValidateSet('All', 'Public', 'Private')] [Parameter(ParameterSetName='AuthenticatedUser')] + [ValidateSet('All', 'Public', 'Private')] [string] $Visibility, [Parameter(ParameterSetName='AuthenticatedUser')] @@ -521,7 +616,6 @@ function Get-GitHubRepository } 'Organization' { - $telemetryProperties['OrganizationName'] = Get-PiiSafeString -PlainText $OrganizationName $uriFragment = "orgs/$OrganizationName/repos" @@ -598,13 +692,13 @@ function Get-GitHubRepository 'AccessToken' = $AccessToken 'TelemetryEventName' = $MyInvocation.MyCommand.Name 'TelemetryProperties' = $telemetryProperties - 'NoStatus' = (Resolve-ParameterWithDefaultConfigurationValue -BoundParameters $PSBoundParameters -Name NoStatus -ConfigValueName DefaultNoStatus) + 'NoStatus' = (Resolve-ParameterWithDefaultConfigurationValue -Name NoStatus -ConfigValueName DefaultNoStatus) } - return Invoke-GHRestMethodMultipleResult @params + return (Invoke-GHRestMethodMultipleResult @params | Add-GitHubRepositoryAdditionalProperties) } -function Rename-GitHubRepository +filter Rename-GitHubRepository { <# .SYNOPSIS @@ -625,7 +719,8 @@ function Rename-GitHubRepository .PARAMETER Uri Uri for the repository to rename. You can supply this directly, or more easily by - using Get-GitHubRepository to get the repository as you please, and then piping the result to this cmdlet + using Get-GitHubRepository to get the repository as you please, + and then piping the result to this cmdlet. .PARAMETER NewName The new name to set for the given GitHub repository @@ -643,56 +738,87 @@ function Rename-GitHubRepository the background, enabling the command prompt to provide status information. If not supplied here, the DefaultNoStatus configuration property value will be used. + .INPUTS + GitHub.Branch + GitHub.Content + GitHub.Event + GitHub.Issue + GitHub.IssueComment + GitHub.Label + GitHub.Milestone + GitHub.PullRequest + GitHub.Project + GitHub.ProjectCard + GitHub.ProjectColumn + GitHub.Release + GitHub.Repository + + .OUTPUTS + GitHub.Repository + .EXAMPLE Get-GitHubRepository -Owner octocat -RepositoryName hello-world | Rename-GitHubRepository -NewName hello-again-world - Get the given 'hello-world' repo from the user 'octocat' and rename it to be https://github.com/octocat/hello-again-world. + Get the given 'hello-world' repo from the user 'octocat' and then + rename it to be https://github.com/octocat/hello-again-world. .EXAMPLE Get-GitHubRepository -Uri https://github.com/octocat/hello-world | Rename-GitHubRepository -NewName hello-again-world -Confirm:$false - Get the repository at https://github.com/octocat/hello-world and then rename it https://github.com/octocat/hello-again-world. Will not prompt for confirmation, as -Confirm:$false was specified. + Get the repository at https://github.com/octocat/hello-world and then + rename it https://github.com/octocat/hello-again-world. + Will not prompt for confirmation, as -Confirm:$false was specified. .EXAMPLE Rename-GitHubRepository -Uri https://github.com/octocat/hello-world -NewName hello-again-world - Rename the repository at https://github.com/octocat/hello-world to https://github.com/octocat/hello-again-world. + Rename the repository at https://github.com/octocat/hello-world to + https://github.com/octocat/hello-again-world. .EXAMPLE New-GitHubRepositoryFork -Uri https://github.com/octocat/hello-world | Foreach-Object {$_ | Rename-GitHubRepository -NewName "$($_.name)_fork"} - Fork the `hello-world` repository from the user 'octocat', and then rename the newly forked repository by appending '_fork'. + Fork the `hello-world` repository from the user 'octocat', and then + rename the newly forked repository by appending '_fork'. .EXAMPLE Rename-GitHubRepository -Uri https://github.com/octocat/hello-world -NewName hello-again-world -Confirm:$false - Rename the repository at https://github.com/octocat/hello-world to https://github.com/octocat/hello-again-world without prompting for confirmation. + Rename the repository at https://github.com/octocat/hello-world to + https://github.com/octocat/hello-again-world without prompting for confirmation. .EXAMPLE Rename-GitHubRepository -Uri https://github.com/octocat/hello-world -NewName hello-again-world -Force - Rename the repository at https://github.com/octocat/hello-world to https://github.com/octocat/hello-again-world without prompting for confirmation. + Rename the repository at https://github.com/octocat/hello-world to + https://github.com/octocat/hello-again-world without prompting for confirmation. #> [CmdletBinding( SupportsShouldProcess, DefaultParameterSetName='Uri', ConfirmImpact="High")] + [OutputType({$script:GitHubRepositoryTypeName})] [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSShouldProcess", "", Justification="Methods called within here make use of PSShouldProcess, and the switch is passed on to them inherently.")] param( - [Parameter(Mandatory=$true, ParameterSetName='Elements')] + [Parameter( + Mandatory, + ParameterSetName='Elements')] [string] $OwnerName, - [Parameter(Mandatory=$true, ParameterSetName='Elements')] + [Parameter( + Mandatory, + ParameterSetName='Elements')] [string] $RepositoryName, [Parameter( Mandatory, ValueFromPipelineByPropertyName, ParameterSetName='Uri')] - [Alias("html_url")] + [Alias("RepositoryUrl")] [string] $Uri, - [parameter(Mandatory)][String]$NewName, + [parameter(Mandatory)] + [String] $NewName, [switch] $Force, @@ -701,44 +827,12 @@ function Rename-GitHubRepository [switch] $NoStatus ) - process - { - $repositoryInfoForDisplayMessage = if ($PSCmdlet.ParameterSetName -eq "Uri") { $Uri } else { $OwnerName, $RepositoryName -join "/" } - - if ($Force -and (-not $Confirm)) - { - $ConfirmPreference = 'None' - } - - if ($PSCmdlet.ShouldProcess($repositoryInfoForDisplayMessage, "Rename repository to '$NewName'")) - { - Write-InvocationLog -Invocation $MyInvocation - $elements = Resolve-RepositoryElements -BoundParameters $PSBoundParameters - $OwnerName = $elements.ownerName - $RepositoryName = $elements.repositoryName - - $telemetryProperties = @{ - 'OwnerName' = (Get-PiiSafeString -PlainText $OwnerName) - 'RepositoryName' = (Get-PiiSafeString -PlainText $RepositoryName) - } - - $params = @{ - 'UriFragment' = "repos/$OwnerName/$RepositoryName" - 'Method' = 'Patch' - Body = ConvertTo-Json -InputObject @{name = $NewName} - 'Description' = "Renaming repository at '$repositoryInfoForDisplayMessage' to '$NewName'" - 'AccessToken' = $AccessToken - 'TelemetryEventName' = $MyInvocation.MyCommand.Name - 'TelemetryProperties' = $telemetryProperties - 'NoStatus' = (Resolve-ParameterWithDefaultConfigurationValue -BoundParameters $PSBoundParameters -Name NoStatus -ConfigValueName DefaultNoStatus) - } - - return Invoke-GHRestMethod @params - } - } + # This method was created by mistake and is now retained to avoid a breaking change. + # Update-GitHubRepository is able to handle this scenario just fine. + return Update-GitHubRepository @PSBoundParameters } -function Update-GitHubRepository +filter Update-GitHubRepository { <# .SYNOPSIS @@ -762,6 +856,9 @@ function Update-GitHubRepository The OwnerName and RepositoryName will be extracted from here instead of needing to provide them individually. + .PARAMETER NewName + Rename the repository to this new name. + .PARAMETER Description A short description of the repository. @@ -808,6 +905,10 @@ function Update-GitHubRepository Specify this to archive this repository. NOTE: You cannot unarchive repositories through the API / this module. + .PARAMETER Force + If this switch is specified, you will not be prompted for confirmation of command execution + when renaming the repository. + .PARAMETER AccessToken If provided, this will be used as the AccessToken for authentication with the REST Api. Otherwise, will attempt to use the configured value or will run unauthenticated. @@ -818,8 +919,26 @@ function Update-GitHubRepository the background, enabling the command prompt to provide status information. If not supplied here, the DefaultNoStatus configuration property value will be used. + .INPUTS + GitHub.Branch + GitHub.Content + GitHub.Event + GitHub.Issue + GitHub.IssueComment + GitHub.Label + GitHub.Milestone + GitHub.PullRequest + GitHub.Project + GitHub.ProjectCard + GitHub.ProjectColumn + GitHub.Release + GitHub.Repository + + .OUTPUTS + GitHub.Repository + .EXAMPLE - Update-GitHubRepository -OwnerName Microsoft -RepositoryName PowerShellForGitHub -Description 'The best way to automate your GitHub interactions' + Update-GitHubRepository -OwnerName microsoft -RepositoryName PowerShellForGitHub -Description 'The best way to automate your GitHub interactions' Changes the description of the specified repository. @@ -827,10 +946,19 @@ function Update-GitHubRepository Update-GitHubRepository -Uri https://github.com/PowerShell/PowerShellForGitHub -Private:$false Changes the visibility of the specified repository to be public. + + .EXAMPLE + Get-GitHubRepository -Uri https://github.com/PowerShell/PowerShellForGitHub | + Update-GitHubRepository -NewName 'PoShForGitHub' -Force + + Renames the repository without any user confirmation prompting. This is identical to using + Rename-GitHubRepository -Uri https://github.com/PowerShell/PowerShellForGitHub -NewName 'PoShForGitHub' -Confirm:$false #> [CmdletBinding( SupportsShouldProcess, - DefaultParameterSetName='Elements')] + DefaultParameterSetName='Elements', + ConfirmImpact='High')] + [OutputType({$script:GitHubRepositoryTypeName})] [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSShouldProcess", "", Justification="Methods called within here make use of PSShouldProcess, and the switch is passed on to them inherently.")] param( [Parameter(ParameterSetName='Elements')] @@ -841,9 +969,14 @@ function Update-GitHubRepository [Parameter( Mandatory, + ValueFromPipelineByPropertyName, ParameterSetName='Uri')] + [Alias('RepositoryUrl')] [string] $Uri, + [ValidateNotNullOrEmpty()] + [string] $NewName, + [string] $Description, [string] $Homepage, @@ -870,6 +1003,8 @@ function Update-GitHubRepository [switch] $Archived, + [switch] $Force, + [string] $AccessToken, [switch] $NoStatus @@ -886,8 +1021,22 @@ function Update-GitHubRepository 'RepositoryName' = (Get-PiiSafeString -PlainText $RepositoryName) } - $hashBody = @{ - 'name' = $RepositoryName + if ($Force -and (-not $Confirm)) + { + $ConfirmPreference = 'None' + } + + $hashBody = @{} + + if ($PSBoundParameters.ContainsKey('NewName')) + { + $existingName = if ($PSCmdlet.ParameterSetName -eq 'Uri') { $Uri } else { $OwnerName, $RepositoryName -join '/' } + if (-not $PSCmdlet.ShouldProcess($existingName, "Rename repository to '$NewName'")) + { + return + } + + $hashBody['name'] = $NewName } if ($PSBoundParameters.ContainsKey('Description')) { $hashBody['description'] = $Description } @@ -913,13 +1062,13 @@ function Update-GitHubRepository 'AccessToken' = $AccessToken 'TelemetryEventName' = $MyInvocation.MyCommand.Name 'TelemetryProperties' = $telemetryProperties - 'NoStatus' = (Resolve-ParameterWithDefaultConfigurationValue -BoundParameters $PSBoundParameters -Name NoStatus -ConfigValueName DefaultNoStatus) + 'NoStatus' = (Resolve-ParameterWithDefaultConfigurationValue -Name NoStatus -ConfigValueName DefaultNoStatus) } - return Invoke-GHRestMethod @params + return (Invoke-GHRestMethod @params | Add-GitHubRepositoryAdditionalProperties) } -function Get-GitHubRepositoryTopic +filter Get-GitHubRepositoryTopic { <# .SYNOPSIS @@ -953,8 +1102,26 @@ function Get-GitHubRepositoryTopic the background, enabling the command prompt to provide status information. If not supplied here, the DefaultNoStatus configuration property value will be used. + .INPUTS + GitHub.Branch + GitHub.Content + GitHub.Event + GitHub.Issue + GitHub.IssueComment + GitHub.Label + GitHub.Milestone + GitHub.PullRequest + GitHub.Project + GitHub.ProjectCard + GitHub.ProjectColumn + GitHub.Release + GitHub.Repository + + .OUTPUTS + GitHub.RepositoryTopic + .EXAMPLE - Get-GitHubRepositoryTopic -OwnerName Microsoft -RepositoryName PowerShellForGitHub + Get-GitHubRepositoryTopic -OwnerName microsoft -RepositoryName PowerShellForGitHub .EXAMPLE Get-GitHubRepositoryTopic -Uri https://github.com/PowerShell/PowerShellForGitHub @@ -962,6 +1129,7 @@ function Get-GitHubRepositoryTopic [CmdletBinding( SupportsShouldProcess, DefaultParameterSetName='Elements')] + [OutputType({$script:GitHubRepositoryTopicTypeName})] [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSShouldProcess", "", Justification="Methods called within here make use of PSShouldProcess, and the switch is passed on to them inherently.")] param( [Parameter(ParameterSetName='Elements')] @@ -972,7 +1140,9 @@ function Get-GitHubRepositoryTopic [Parameter( Mandatory, + ValueFromPipelineByPropertyName, ParameterSetName='Uri')] + [Alias('RepositoryUrl')] [string] $Uri, [string] $AccessToken, @@ -999,10 +1169,11 @@ function Get-GitHubRepositoryTopic 'AccessToken' = $AccessToken 'TelemetryEventName' = $MyInvocation.MyCommand.Name 'TelemetryProperties' = $telemetryProperties - 'NoStatus' = (Resolve-ParameterWithDefaultConfigurationValue -BoundParameters $PSBoundParameters -Name NoStatus -ConfigValueName DefaultNoStatus) + 'NoStatus' = (Resolve-ParameterWithDefaultConfigurationValue -Name NoStatus -ConfigValueName DefaultNoStatus) } - return Invoke-GHRestMethod @params + return (Invoke-GHRestMethod @params | + Add-GitHubRepositoryAdditionalProperties -TypeName $script:GitHubRepositoryTopicTypeName -OwnerName $OwnerName -RepositoryName $RepositoryName) } function Set-GitHubRepositoryTopic @@ -1029,7 +1200,7 @@ function Set-GitHubRepositoryTopic The OwnerName and RepositoryName will be extracted from here instead of needing to provide them individually. - .PARAMETER Name + .PARAMETER Topic Array of topics to add to the repository. .PARAMETER Clear @@ -1045,15 +1216,44 @@ function Set-GitHubRepositoryTopic the background, enabling the command prompt to provide status information. If not supplied here, the DefaultNoStatus configuration property value will be used. + .INPUTS + GitHub.Branch + GitHub.Content + GitHub.Event + GitHub.Issue + GitHub.IssueComment + GitHub.Label + GitHub.Milestone + GitHub.PullRequest + GitHub.Project + GitHub.ProjectCard + GitHub.ProjectColumn + GitHub.Release + GitHub.Repository + + .OUTPUTS + GitHub.RepositoryTopic + .EXAMPLE - Set-GitHubRepositoryTopic -OwnerName Microsoft -RepositoryName PowerShellForGitHub -Clear + Set-GitHubRepositoryTopic -OwnerName microsoft -RepositoryName PowerShellForGitHub -Clear .EXAMPLE - Set-GitHubRepositoryTopic -Uri https://github.com/PowerShell/PowerShellForGitHub -Name ('octocat', 'powershell', 'github') + Set-GitHubRepositoryTopic -Uri https://github.com/PowerShell/PowerShellForGitHub -Topic ('octocat', 'powershell', 'github') + + .EXAMPLE + ('octocat', 'powershell', 'github') | Set-GitHubRepositoryTopic -Uri https://github.com/PowerShell/PowerShellForGitHub + + .NOTES + This is implemented as a function rather than a filter because the ValueFromPipeline + parameter (Topic) is itself an array which we want to ensure is processed only a single time. + This API endpoint doesn't add topics to a repository, it replaces the existing topics with + the new set provided, so we need to make sure that we have all the requested topics available + to us at the time that the API endpoint is called. #> [CmdletBinding( SupportsShouldProcess, DefaultParameterSetName='ElementsName')] + [OutputType({$script:GitHubRepositoryTopicTypeName})] [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSShouldProcess", "", Justification="Methods called within here make use of PSShouldProcess, and the switch is passed on to them inherently.")] param( [Parameter(ParameterSetName='ElementsName')] @@ -1066,19 +1266,25 @@ function Set-GitHubRepositoryTopic [Parameter( Mandatory, + ValueFromPipelineByPropertyName, ParameterSetName='UriName')] [Parameter( Mandatory, + ValueFromPipelineByPropertyName, ParameterSetName='UriClear')] + [Alias('RepositoryUrl')] [string] $Uri, [Parameter( Mandatory, + ValueFromPipeline, ParameterSetName='ElementsName')] [Parameter( Mandatory, + ValueFromPipeline, ParameterSetName='UriName')] - [string[]] $Name, + [Alias('Name')] + [string[]] $Topic, [Parameter( Mandatory, @@ -1093,48 +1299,64 @@ function Set-GitHubRepositoryTopic [switch] $NoStatus ) - Write-InvocationLog -Invocation $MyInvocation - - $elements = Resolve-RepositoryElements -BoundParameters $PSBoundParameters - $OwnerName = $elements.ownerName - $RepositoryName = $elements.repositoryName - - $telemetryProperties = @{ - 'OwnerName' = (Get-PiiSafeString -PlainText $OwnerName) - 'RepositoryName' = (Get-PiiSafeString -PlainText $RepositoryName) - 'Clear' = $PSBoundParameters.ContainsKey('Clear') + begin + { + $topics = @() } - if ($Clear) + process { - $description = "Clearing topics in $RepositoryName" - $Name = @() + foreach ($value in $Topic) + { + $topics += $value + } } - else + + end { - $description = "Replacing topics in $RepositoryName" - } + Write-InvocationLog -Invocation $MyInvocation - $hashBody = @{ - 'names' = $Name - } + $elements = Resolve-RepositoryElements -BoundParameters $PSBoundParameters + $OwnerName = $elements.ownerName + $RepositoryName = $elements.repositoryName - $params = @{ - 'UriFragment' = "repos/$OwnerName/$RepositoryName/topics" - 'Body' = (ConvertTo-Json -InputObject $hashBody) - 'Method' = 'Put' - 'Description' = $description - 'AcceptHeader' = $script:mercyAcceptHeader - 'AccessToken' = $AccessToken - 'TelemetryEventName' = $MyInvocation.MyCommand.Name - 'TelemetryProperties' = $telemetryProperties - 'NoStatus' = (Resolve-ParameterWithDefaultConfigurationValue -BoundParameters $PSBoundParameters -Name NoStatus -ConfigValueName DefaultNoStatus) - } + $telemetryProperties = @{ + 'OwnerName' = (Get-PiiSafeString -PlainText $OwnerName) + 'RepositoryName' = (Get-PiiSafeString -PlainText $RepositoryName) + 'Clear' = $PSBoundParameters.ContainsKey('Clear') + } - return Invoke-GHRestMethod @params + if ($Clear) + { + $description = "Clearing topics in $RepositoryName" + } + else + { + $description = "Replacing topics in $RepositoryName" + } + + $hashBody = @{ + 'names' = $topics + } + + $params = @{ + 'UriFragment' = "repos/$OwnerName/$RepositoryName/topics" + 'Body' = (ConvertTo-Json -InputObject $hashBody) + 'Method' = 'Put' + 'Description' = $description + 'AcceptHeader' = $script:mercyAcceptHeader + 'AccessToken' = $AccessToken + 'TelemetryEventName' = $MyInvocation.MyCommand.Name + 'TelemetryProperties' = $telemetryProperties + 'NoStatus' = (Resolve-ParameterWithDefaultConfigurationValue -Name NoStatus -ConfigValueName DefaultNoStatus) + } + + return (Invoke-GHRestMethod @params | + Add-GitHubRepositoryAdditionalProperties -TypeName $script:GitHubRepositoryTopicTypeName -OwnerName $OwnerName -RepositoryName $RepositoryName) + } } -function Get-GitHubRepositoryContributor +filter Get-GitHubRepositoryContributor { <# .SYNOPSIS @@ -1181,11 +1403,27 @@ function Get-GitHubRepositoryContributor the background, enabling the command prompt to provide status information. If not supplied here, the DefaultNoStatus configuration property value will be used. + .INPUTS + GitHub.Branch + GitHub.Content + GitHub.Event + GitHub.Issue + GitHub.IssueComment + GitHub.Label + GitHub.Milestone + GitHub.PullRequest + GitHub.Project + GitHub.ProjectCard + GitHub.ProjectColumn + GitHub.Release + GitHub.Repository + .OUTPUTS - [PSCustomObject[]] List of contributors for the repository. + GitHub.User + GitHub.RepositoryContributorStatistics .EXAMPLE - Get-GitHubRepositoryContributor -OwnerName Microsoft -RepositoryName PowerShellForGitHub + Get-GitHubRepositoryContributor -OwnerName microsoft -RepositoryName PowerShellForGitHub .EXAMPLE Get-GitHubRepositoryContributor -Uri 'https://github.com/PowerShell/PowerShellForGitHub' -IncludeStatistics @@ -1193,6 +1431,8 @@ function Get-GitHubRepositoryContributor [CmdletBinding( SupportsShouldProcess, DefaultParameterSetName='Elements')] + [OutputType({$script:GitHubUserTypeName})] + [OutputType({$script:GitHubRepositoryContributorStatisticsTypeName})] [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSShouldProcess", "", Justification="Methods called within here make use of PSShouldProcess, and the switch is passed on to them inherently.")] param( [Parameter(ParameterSetName='Elements')] @@ -1203,7 +1443,9 @@ function Get-GitHubRepositoryContributor [Parameter( Mandatory, + ValueFromPipelineByPropertyName, ParameterSetName='Uri')] + [Alias('RepositoryUrl')] [string] $Uri, [switch] $IncludeAnonymousContributors, @@ -1240,13 +1482,34 @@ function Get-GitHubRepositoryContributor 'AccessToken' = $AccessToken 'TelemetryEventName' = $MyInvocation.MyCommand.Name 'TelemetryProperties' = $telemetryProperties - 'NoStatus' = (Resolve-ParameterWithDefaultConfigurationValue -BoundParameters $PSBoundParameters -Name NoStatus -ConfigValueName DefaultNoStatus) + 'NoStatus' = (Resolve-ParameterWithDefaultConfigurationValue -Name NoStatus -ConfigValueName DefaultNoStatus) + } + + $results = Invoke-GHRestMethodMultipleResult @params + + if ($IncludeStatistics) + { + foreach ($item in $results) + { + $item.PSObject.TypeNames.Insert(0, $script:GitHubRepositoryContributorStatisticsTypeName) + + if (-not (Get-GitHubConfiguration -Name DisablePipelineSupport)) + { + $repositoryUrl = (Join-GitHubUri -OwnerName $OwnerName -RepositoryName $RepositoryName) + Add-Member -InputObject $item -Name 'RepositoryUrl' -Value $repositoryUrl -MemberType NoteProperty -Force + $null = Add-GitHubUserAdditionalProperties -InputObject $item.author + } + } + } + else + { + $results = $results | Add-GitHubUserAdditionalProperties } - return Invoke-GHRestMethodMultipleResult @params + return $results } -function Get-GitHubRepositoryCollaborator +filter Get-GitHubRepositoryCollaborator { <# .SYNOPSIS @@ -1274,17 +1537,39 @@ function Get-GitHubRepositoryCollaborator If provided, this will be used as the AccessToken for authentication with the REST Api. Otherwise, will attempt to use the configured value or will run unauthenticated. + .PARAMETER Affiliation + Filter collaborators returned by their affiliation. Can be one of: + All: All collaborators the authenticated user can see. + Direct: All collaborators with permissions to an organization-owned repository, + regardless of organization membership status. + Outside: All outside collaborators of an organization-owned repository. + .PARAMETER NoStatus If this switch is specified, long-running commands will run on the main thread with no commandline status update. When not specified, those commands run in the background, enabling the command prompt to provide status information. If not supplied here, the DefaultNoStatus configuration property value will be used. + .INPUTS + GitHub.Branch + GitHub.Content + GitHub.Event + GitHub.Issue + GitHub.IssueComment + GitHub.Label + GitHub.Milestone + GitHub.PullRequest + GitHub.Project + GitHub.ProjectCard + GitHub.ProjectColumn + GitHub.Release + GitHub.Repository + .OUTPUTS - [PSCustomObject[]] List of collaborators for the repository. + GitHub.User .EXAMPLE - Get-GitHubRepositoryCollaborator -OwnerName Microsoft -RepositoryName PowerShellForGitHub + Get-GitHubRepositoryCollaborator -OwnerName microsoft -RepositoryName PowerShellForGitHub .EXAMPLE Get-GitHubRepositoryCollaborator -Uri 'https://github.com/PowerShell/PowerShellForGitHub' @@ -1292,7 +1577,8 @@ function Get-GitHubRepositoryCollaborator [CmdletBinding( SupportsShouldProcess, DefaultParameterSetName='Elements')] - [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSShouldProcess", "", Justification="Methods called within here make use of PSShouldProcess, and the switch is passed on to them inherently.")] + [OutputType({$script:GitHubUserTypeName})] + [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSShouldProcess", "", Justification="Methods called within here make use of PSShouldProcess, and the switch is passed on to them inherently.")] param( [Parameter(ParameterSetName='Elements')] [string] $OwnerName, @@ -1302,9 +1588,14 @@ function Get-GitHubRepositoryCollaborator [Parameter( Mandatory, + ValueFromPipelineByPropertyName, ParameterSetName='Uri')] + [Alias('RepositoryUrl')] [string] $Uri, + [ValidateSet('All', 'Direct', 'Outside')] + [string] $Affiliation = 'All', + [string] $AccessToken, [switch] $NoStatus @@ -1321,19 +1612,23 @@ function Get-GitHubRepositoryCollaborator 'RepositoryName' = (Get-PiiSafeString -PlainText $RepositoryName) } + $getParams = @( + "affiliation=$($Affiliation.ToLower())" + ) + $params = @{ - 'UriFragment' = "repos/$OwnerName/$RepositoryName/collaborators" + 'UriFragment' = "repos/$OwnerName/$RepositoryName/collaborators?" + ($getParams -join '&') 'Description' = "Getting collaborators for $RepositoryName" 'AccessToken' = $AccessToken 'TelemetryEventName' = $MyInvocation.MyCommand.Name 'TelemetryProperties' = $telemetryProperties - 'NoStatus' = (Resolve-ParameterWithDefaultConfigurationValue -BoundParameters $PSBoundParameters -Name NoStatus -ConfigValueName DefaultNoStatus) + 'NoStatus' = (Resolve-ParameterWithDefaultConfigurationValue -Name NoStatus -ConfigValueName DefaultNoStatus) } - return Invoke-GHRestMethodMultipleResult @params + return (Invoke-GHRestMethodMultipleResult @params | Add-GitHubUserAdditionalProperties) } -function Get-GitHubRepositoryLanguage +filter Get-GitHubRepositoryLanguage { <# .SYNOPSIS @@ -1367,12 +1662,27 @@ function Get-GitHubRepositoryLanguage the background, enabling the command prompt to provide status information. If not supplied here, the DefaultNoStatus configuration property value will be used. + .INPUTS + GitHub.Branch + GitHub.Content + GitHub.Event + GitHub.Issue + GitHub.IssueComment + GitHub.Label + GitHub.Milestone + GitHub.PullRequest + GitHub.Project + GitHub.ProjectCard + GitHub.ProjectColumn + GitHub.Release + GitHub.Repository + .OUTPUTS - [PSCustomObject[]] List of languages for the specified repository. The value shown - for each language is the number of bytes of code written in that language. + GitHub.RepositoryLanguage - The value shown for each language is the number + of bytes of code written in that language. .EXAMPLE - Get-GitHubRepositoryLanguage -OwnerName Microsoft -RepositoryName PowerShellForGitHub + Get-GitHubRepositoryLanguage -OwnerName microsoft -RepositoryName PowerShellForGitHub .EXAMPLE Get-GitHubRepositoryLanguage -Uri https://github.com/PowerShell/PowerShellForGitHub @@ -1380,6 +1690,7 @@ function Get-GitHubRepositoryLanguage [CmdletBinding( SupportsShouldProcess, DefaultParameterSetName='Elements')] + [OutputType({$script:GitHubRepositoryLanguageTypeName})] [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSShouldProcess", "", Justification="Methods called within here make use of PSShouldProcess, and the switch is passed on to them inherently.")] param( [Parameter(ParameterSetName='Elements')] @@ -1390,7 +1701,9 @@ function Get-GitHubRepositoryLanguage [Parameter( Mandatory, + ValueFromPipelineByPropertyName, ParameterSetName='Uri')] + [Alias('RepositoryUrl')] [string] $Uri, [string] $AccessToken, @@ -1415,13 +1728,14 @@ function Get-GitHubRepositoryLanguage 'AccessToken' = $AccessToken 'TelemetryEventName' = $MyInvocation.MyCommand.Name 'TelemetryProperties' = $telemetryProperties - 'NoStatus' = (Resolve-ParameterWithDefaultConfigurationValue -BoundParameters $PSBoundParameters -Name NoStatus -ConfigValueName DefaultNoStatus) + 'NoStatus' = (Resolve-ParameterWithDefaultConfigurationValue -Name NoStatus -ConfigValueName DefaultNoStatus) } - return Invoke-GHRestMethodMultipleResult @params + return (Invoke-GHRestMethodMultipleResult @params | + Add-GitHubRepositoryAdditionalProperties -TypeName $script:GitHubRepositoryLanguageTypeName) } -function Get-GitHubRepositoryTag +filter Get-GitHubRepositoryTag { <# .SYNOPSIS @@ -1455,8 +1769,26 @@ function Get-GitHubRepositoryTag the background, enabling the command prompt to provide status information. If not supplied here, the DefaultNoStatus configuration property value will be used. + .INPUTS + GitHub.Branch + GitHub.Content + GitHub.Event + GitHub.Issue + GitHub.IssueComment + GitHub.Label + GitHub.Milestone + GitHub.PullRequest + GitHub.Project + GitHub.ProjectCard + GitHub.ProjectColumn + GitHub.Release + GitHub.Repository + + .OUTPUTS + GitHub.RepositoryTag + .EXAMPLE - Get-GitHubRepositoryTag -OwnerName Microsoft -RepositoryName PowerShellForGitHub + Get-GitHubRepositoryTag -OwnerName microsoft -RepositoryName PowerShellForGitHub .EXAMPLE Get-GitHubRepositoryTag -Uri https://github.com/PowerShell/PowerShellForGitHub @@ -1464,6 +1796,7 @@ function Get-GitHubRepositoryTag [CmdletBinding( SupportsShouldProcess, DefaultParameterSetName='Elements')] + [OutputType({$script:GitHubRepositoryTagTypeName})] [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSShouldProcess", "", Justification="Methods called within here make use of PSShouldProcess, and the switch is passed on to them inherently.")] param( [Parameter(ParameterSetName='Elements')] @@ -1474,7 +1807,9 @@ function Get-GitHubRepositoryTag [Parameter( Mandatory, + ValueFromPipelineByPropertyName, ParameterSetName='Uri')] + [Alias('RepositoryUrl')] [string] $Uri, [string] $AccessToken, @@ -1499,13 +1834,14 @@ function Get-GitHubRepositoryTag 'AccessToken' = $AccessToken 'TelemetryEventName' = $MyInvocation.MyCommand.Name 'TelemetryProperties' = $telemetryProperties - 'NoStatus' = (Resolve-ParameterWithDefaultConfigurationValue -BoundParameters $PSBoundParameters -Name NoStatus -ConfigValueName DefaultNoStatus) + 'NoStatus' = (Resolve-ParameterWithDefaultConfigurationValue -Name NoStatus -ConfigValueName DefaultNoStatus) } - return Invoke-GHRestMethodMultipleResult @params + return (Invoke-GHRestMethodMultipleResult @params | + Add-GitHubRepositoryAdditionalProperties -TypeName $script:GitHubRepositoryTagTypeName -OwnerName $OwnerName -RepositoryName $RepositoryName) } -function Move-GitHubRepositoryOwnership +filter Move-GitHubRepositoryOwnership { <# .SYNOPSIS @@ -1546,12 +1882,31 @@ function Move-GitHubRepositoryOwnership the background, enabling the command prompt to provide status information. If not supplied here, the DefaultNoStatus configuration property value will be used. + .INPUTS + GitHub.Branch + GitHub.Content + GitHub.Event + GitHub.Issue + GitHub.IssueComment + GitHub.Label + GitHub.Milestone + GitHub.PullRequest + GitHub.Project + GitHub.ProjectCard + GitHub.ProjectColumn + GitHub.Release + GitHub.Repository + + .OUTPUTS + GitHub.Repository + .EXAMPLE - Move-GitHubRepositoryOwnership -OwnerName Microsoft -RepositoryName PowerShellForGitHub -NewOwnerName OctoCat + Move-GitHubRepositoryOwnership -OwnerName microsoft -RepositoryName PowerShellForGitHub -NewOwnerName OctoCat #> [CmdletBinding( SupportsShouldProcess, DefaultParameterSetName='Elements')] + [OutputType({$script:GitHubRepositoryTypeName})] [Alias('Transfer-GitHubRepositoryOwnership')] [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSShouldProcess", "", Justification="Methods called within here make use of PSShouldProcess, and the switch is passed on to them inherently.")] param( @@ -1563,7 +1918,9 @@ function Move-GitHubRepositoryOwnership [Parameter( Mandatory, + ValueFromPipelineByPropertyName, ParameterSetName='Uri')] + [Alias('RepositoryUrl')] [string] $Uri, [Parameter(Mandatory)] @@ -1602,8 +1959,98 @@ function Move-GitHubRepositoryOwnership 'AccessToken' = $AccessToken 'TelemetryEventName' = $MyInvocation.MyCommand.Name 'TelemetryProperties' = $telemetryProperties - 'NoStatus' = (Resolve-ParameterWithDefaultConfigurationValue -BoundParameters $PSBoundParameters -Name NoStatus -ConfigValueName DefaultNoStatus) + 'NoStatus' = (Resolve-ParameterWithDefaultConfigurationValue -Name NoStatus -ConfigValueName DefaultNoStatus) } - return Invoke-GHRestMethod @params + return (Invoke-GHRestMethod @params | Add-GitHubRepositoryAdditionalProperties) +} + +filter Add-GitHubRepositoryAdditionalProperties +{ +<# + .SYNOPSIS + Adds type name and additional properties to ease pipelining to GitHub Repository objects. + + .PARAMETER InputObject + The GitHub object to add additional properties to. + + .PARAMETER TypeName + The type that should be assigned to the object. + + .PARAMETER OwnerName + Owner of the repository. This information might be obtainable from InputObject, so this + is optional based on what InputObject contains. + + .PARAMETER RepositoryName + Name of the repository. This information might be obtainable from InputObject, so this + is optional based on what InputObject contains. + + .INPUTS + [PSCustomObject] + + .OUTPUTS + GitHub.Repository +#> + [CmdletBinding()] + [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSUseSingularNouns", "", Justification="Internal helper that is definitely adding more than one property.")] + param( + [Parameter( + Mandatory, + ValueFromPipeline)] + [AllowNull()] + [AllowEmptyCollection()] + [PSCustomObject[]] $InputObject, + + [ValidateNotNullOrEmpty()] + [string] $TypeName = $script:GitHubRepositoryTypeName, + + [string] $OwnerName, + + [string] $RepositoryName + ) + + foreach ($item in $InputObject) + { + $item.PSObject.TypeNames.Insert(0, $TypeName) + + if (-not (Get-GitHubConfiguration -Name DisablePipelineSupport)) + { + $repositoryUrl = [String]::Empty + if ([String]::IsNullOrEmpty($item.html_url)) + { + if ($PSBoundParameters.ContainsKey('OwnerName') -and + $PSBoundParameters.ContainsKey('RepositoryName')) + { + $repositoryUrl = (Join-GitHubUri -OwnerName $OwnerName -RepositoryName $RepositoryName) + } + } + else + { + $elements = Split-GitHubUri -Uri $item.html_url + $repositoryUrl = Join-GitHubUri @elements + } + + if (-not [String]::IsNullOrEmpty($repositoryUrl)) + { + Add-Member -InputObject $item -Name 'RepositoryUrl' -Value $repositoryUrl -MemberType NoteProperty -Force + } + + if ($item.id -gt 0) + { + Add-Member -InputObject $item -Name 'RepositoryId' -Value $item.id -MemberType NoteProperty -Force + } + + if ($null -ne $item.owner) + { + $null = Add-GitHubUserAdditionalProperties -InputObject $item.owner + } + + if ($null -ne $item.organization) + { + $null = Add-GitHubOrganizationAdditionalProperties -InputObject $item.organization + } + } + + Write-Output $item + } } diff --git a/GitHubRepositoryForks.ps1 b/GitHubRepositoryForks.ps1 index f50b2af9..3322c726 100644 --- a/GitHubRepositoryForks.ps1 +++ b/GitHubRepositoryForks.ps1 @@ -1,7 +1,7 @@ # Copyright (c) Microsoft Corporation. All rights reserved. # Licensed under the MIT License. -function Get-GitHubRepositoryFork +filter Get-GitHubRepositoryFork { <# .SYNOPSIS @@ -38,14 +38,33 @@ function Get-GitHubRepositoryFork the background, enabling the command prompt to provide status information. If not supplied here, the DefaultNoStatus configuration property value will be used. + .INPUTS + GitHub.Branch + GitHub.Content + GitHub.Event + GitHub.Issue + GitHub.IssueComment + GitHub.Label + GitHub.Milestone + GitHub.PullRequest + GitHub.Project + GitHub.ProjectCard + GitHub.ProjectColumn + GitHub.Release + GitHub.Repository + + .OUTPUTS + GitHub.Repository + .EXAMPLE - Get-GitHubRepositoryFork -OwnerName Microsoft -RepositoryName PowerShellForGitHub + Get-GitHubRepositoryFork -OwnerName microsoft -RepositoryName PowerShellForGitHub - Gets all of the forks for the Microsoft\PowerShellForGitHub repository. + Gets all of the forks for the microsoft\PowerShellForGitHub repository. #> [CmdletBinding( SupportsShouldProcess, DefaultParameterSetName='Elements')] + [OutputType({$script:GitHubRepositoryTypeName})] [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSShouldProcess", "", Justification="Methods called within here make use of PSShouldProcess, and the switch is passed on to them inherently.")] [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSReviewUnusedParameter", "", Justification="One or more parameters (like NoStatus) are only referenced by helper methods which get access to it from the stack via Get-Variable -Scope 1.")] param( @@ -57,7 +76,9 @@ function Get-GitHubRepositoryFork [Parameter( Mandatory, + ValueFromPipelineByPropertyName, ParameterSetName='Uri')] + [Alias('RepositoryUrl')] [string] $Uri, [ValidateSet('Newest', 'Oldest', 'Stargazers')] @@ -93,10 +114,10 @@ function Get-GitHubRepositoryFork 'NoStatus' = (Resolve-ParameterWithDefaultConfigurationValue -Name NoStatus -ConfigValueName DefaultNoStatus) } - return Invoke-GHRestMethodMultipleResult @params + return (Invoke-GHRestMethodMultipleResult @params | Add-GitHubRepositoryAdditionalProperties) } -function New-GitHubRepositoryFork +filter New-GitHubRepositoryFork { <# .SYNOPSIS @@ -134,19 +155,38 @@ function New-GitHubRepositoryFork the background, enabling the command prompt to provide status information. If not supplied here, the DefaultNoStatus configuration property value will be used. + .INPUTS + GitHub.Branch + GitHub.Content + GitHub.Event + GitHub.Issue + GitHub.IssueComment + GitHub.Label + GitHub.Milestone + GitHub.PullRequest + GitHub.Project + GitHub.ProjectCard + GitHub.ProjectColumn + GitHub.Release + GitHub.Repository + + .OUTPUTS + GitHub.Repository + .EXAMPLE - New-GitHubRepositoryFork -OwnerName Microsoft -RepositoryName PowerShellForGitHub + New-GitHubRepositoryFork -OwnerName microsoft -RepositoryName PowerShellForGitHub Creates a fork of this repository under the current authenticated user's account. .EXAMPLE - New-GitHubRepositoryFork -OwnerName Microsoft -RepositoryName PowerShellForGitHub -OrganizationName OctoLabs + New-GitHubRepositoryFork -OwnerName microsoft -RepositoryName PowerShellForGitHub -OrganizationName OctoLabs Creates a fork of this repository under the OctoLabs organization. #> [CmdletBinding( SupportsShouldProcess, DefaultParameterSetName='Elements')] + [OutputType({$script:GitHubRepositoryTypeName})] [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSShouldProcess", "", Justification="Methods called within here make use of PSShouldProcess, and the switch is passed on to them inherently.")] param( [Parameter(ParameterSetName='Elements')] @@ -157,7 +197,9 @@ function New-GitHubRepositoryFork [Parameter( Mandatory, + ValueFromPipelineByPropertyName, ParameterSetName='Uri')] + [Alias('RepositoryUrl')] [string] $Uri, [string] $OrganizationName, @@ -196,7 +238,7 @@ function New-GitHubRepositoryFork 'NoStatus' = (Resolve-ParameterWithDefaultConfigurationValue -Name NoStatus -ConfigValueName DefaultNoStatus) } - $result = Invoke-GHRestMethod @params + $result = (Invoke-GHRestMethod @params | Add-GitHubRepositoryAdditionalProperties) Write-Log -Message 'Forking a repository happens asynchronously. You may have to wait a short period of time (up to 5 minutes) before you can access the git objects.' -Level Warning return $result diff --git a/GitHubRepositoryTraffic.ps1 b/GitHubRepositoryTraffic.ps1 index c27ab1be..bcdcda13 100644 --- a/GitHubRepositoryTraffic.ps1 +++ b/GitHubRepositoryTraffic.ps1 @@ -1,7 +1,16 @@ # Copyright (c) Microsoft Corporation. All rights reserved. # Licensed under the MIT License. -function Get-GitHubReferrerTraffic +@{ + GitHubReferrerTrafficTypeName = 'GitHub.ReferrerTraffic' + GitHubPathTrafficTypeName = 'GitHub.PathTraffic' + GitHubViewTrafficTypeName = 'GitHub.ViewTraffic' + GitHubCloneTrafficTypeName = 'GitHub.CloneTraffic' + }.GetEnumerator() | ForEach-Object { + Set-Variable -Scope Script -Option ReadOnly -Name $_.Key -Value $_.Value + } + +filter Get-GitHubReferrerTraffic { <# .SYNOPSIS @@ -35,14 +44,33 @@ function Get-GitHubReferrerTraffic the background, enabling the command prompt to provide status information. If not supplied here, the DefaultNoStatus configuration property value will be used. + .INPUTS + GitHub.Branch + GitHub.Content + GitHub.Event + GitHub.Issue + GitHub.IssueComment + GitHub.Label + GitHub.Milestone + GitHub.PullRequest + GitHub.Project + GitHub.ProjectCard + GitHub.ProjectColumn + GitHub.Release + GitHub.Repository + + .OUTPUTS + GitHub.ReferrerTraffic + .EXAMPLE - Get-GitHubReferrerTraffic -OwnerName Microsoft -RepositoryName PowerShellForGitHub + Get-GitHubReferrerTraffic -OwnerName microsoft -RepositoryName PowerShellForGitHub - Get the top 10 referrers over the last 14 days from the Microsoft\PowerShellForGitHub project. + Get the top 10 referrers over the last 14 days from the microsoft\PowerShellForGitHub project. #> [CmdletBinding( SupportsShouldProcess, DefaultParameterSetName='Elements')] + [OutputType({$script:GitHubReferrerTrafficTypeName})] [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSShouldProcess", "", Justification="Methods called within here make use of PSShouldProcess, and the switch is passed on to them inherently.")] [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSReviewUnusedParameter", "", Justification="One or more parameters (like NoStatus) are only referenced by helper methods which get access to it from the stack via Get-Variable -Scope 1.")] param( @@ -54,7 +82,9 @@ function Get-GitHubReferrerTraffic [Parameter( Mandatory, + ValueFromPipelineByPropertyName, ParameterSetName='Uri')] + [Alias('RepositoryUrl')] [string] $Uri, [string] $AccessToken, @@ -83,14 +113,21 @@ function Get-GitHubReferrerTraffic 'NoStatus' = (Resolve-ParameterWithDefaultConfigurationValue -Name NoStatus -ConfigValueName DefaultNoStatus) } - return Invoke-GHRestMethod @params + $result = Invoke-GHRestMethod @params + + if ($null -ne $result) + { + $result.PSObject.TypeNames.Insert(0, $script:GitHubReferrerTrafficTypeName) + } + + return $result } -function Get-GitHubPathTraffic +filter Get-GitHubPathTraffic { <# .SYNOPSIS - Get the top 10 popular contents over the last 14 days for a given Github repository. + Get the top 10 popular contents over the last 14 days for a given GitHub repository. .DESCRIPTION Get the top 10 popular contents over the last 14 days for a given GitHub repository. @@ -120,14 +157,34 @@ function Get-GitHubPathTraffic the background, enabling the command prompt to provide status information. If not supplied here, the DefaultNoStatus configuration property value will be used. + .INPUTS + GitHub.Branch + GitHub.Content + GitHub.Event + GitHub.Issue + GitHub.IssueComment + GitHub.Label + GitHub.Milestone + GitHub.PullRequest + GitHub.Project + GitHub.ProjectCard + GitHub.ProjectColumn + GitHub.Release + GitHub.Repository + + .OUTPUTS + GitHub.PathTraffic + .EXAMPLE - Get-GitHubPathTraffic -OwnerName Microsoft -RepositoryName PowerShellForGitHub + Get-GitHubPathTraffic -OwnerName microsoft -RepositoryName PowerShellForGitHub - Get the top 10 popular contents over the last 14 days from the Microsoft\PowerShellForGitHub project. + Get the top 10 popular contents over the last 14 days + from the microsoft\PowerShellForGitHub project. #> [CmdletBinding( SupportsShouldProcess, DefaultParameterSetName='Elements')] + [OutputType({$script:GitHubPathTrafficTypeName})] [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSShouldProcess", "", Justification="Methods called within here make use of PSShouldProcess, and the switch is passed on to them inherently.")] [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSReviewUnusedParameter", "", Justification="One or more parameters (like NoStatus) are only referenced by helper methods which get access to it from the stack via Get-Variable -Scope 1.")] param( @@ -139,7 +196,9 @@ function Get-GitHubPathTraffic [Parameter( Mandatory, + ValueFromPipelineByPropertyName, ParameterSetName='Uri')] + [Alias('RepositoryUrl')] [string] $Uri, [string] $AccessToken, @@ -168,18 +227,27 @@ function Get-GitHubPathTraffic 'NoStatus' = (Resolve-ParameterWithDefaultConfigurationValue -Name NoStatus -ConfigValueName DefaultNoStatus) } - return Invoke-GHRestMethod @params + $result = Invoke-GHRestMethod @params + + if ($null -ne $result) + { + $result.PSObject.TypeNames.Insert(0, $script:GitHubPathTrafficTypeName) + } + + return $result } -function Get-GitHubViewTraffic +filter Get-GitHubViewTraffic { <# .SYNOPSIS - Get the total number of views and breakdown per day or week for the last 14 days for the given Github Repository. + Get the total number of views and breakdown per day or week for the last 14 days for the + given GitHub Repository. .DESCRIPTION Get the total number of views and breakdown per day or week for the last 14 days. - Timestamps are aligned to UTC midnight of the beginning of the day or week. Week begins on Monday. + Timestamps are aligned to UTC midnight of the beginning of the day or week. + Week begins on Monday. The Git repo for this module can be found here: http://aka.ms/PowerShellForGitHub @@ -209,14 +277,34 @@ function Get-GitHubViewTraffic the background, enabling the command prompt to provide status information. If not supplied here, the DefaultNoStatus configuration property value will be used. + .INPUTS + GitHub.Branch + GitHub.Content + GitHub.Event + GitHub.Issue + GitHub.IssueComment + GitHub.Label + GitHub.Milestone + GitHub.PullRequest + GitHub.Project + GitHub.ProjectCard + GitHub.ProjectColumn + GitHub.Release + GitHub.Repository + + .OUTPUTS + GitHub.ViewTraffic + .EXAMPLE - Get-GitHubViewTraffic -OwnerName Microsoft -RepositoryName PowerShellForGitHub + Get-GitHubViewTraffic -OwnerName microsoft -RepositoryName PowerShellForGitHub - Get the total number of views and breakdown per day or week for the last 14 days from the Microsoft\PowerShellForGitHub project. + Get the total number of views and breakdown per day or week for the last 14 days from + the microsoft\PowerShellForGitHub project. #> [CmdletBinding( SupportsShouldProcess, DefaultParameterSetName='Elements')] + [OutputType({$script:GitHubViewTrafficTypeName})] [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSShouldProcess", "", Justification="Methods called within here make use of PSShouldProcess, and the switch is passed on to them inherently.")] [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSReviewUnusedParameter", "", Justification="One or more parameters (like NoStatus) are only referenced by helper methods which get access to it from the stack via Get-Variable -Scope 1.")] param( @@ -228,7 +316,9 @@ function Get-GitHubViewTraffic [Parameter( Mandatory, + ValueFromPipelineByPropertyName, ParameterSetName='Uri')] + [Alias('RepositoryUrl')] [string] $Uri, [ValidateSet('Day', 'Week')] @@ -261,18 +351,27 @@ function Get-GitHubViewTraffic 'NoStatus' = (Resolve-ParameterWithDefaultConfigurationValue -Name NoStatus -ConfigValueName DefaultNoStatus) } - return Invoke-GHRestMethod @params + $result = Invoke-GHRestMethod @params + + if ($null -ne $result) + { + $result.PSObject.TypeNames.Insert(0, $script:GitHubViewTrafficTypeName) + } + + return $result } -function Get-GitHubCloneTraffic +filter Get-GitHubCloneTraffic { <# .SYNOPSIS - Get the total number of clones and breakdown per day or week for the last 14 days for the given Github Repository. + Get the total number of clones and breakdown per day or week for the last 14 days for the + given GitHub Repository. .DESCRIPTION Get the total number of clones and breakdown per day or week for the last 14 days. - Timestamps are aligned to UTC midnight of the beginning of the day or week. Week begins on Monday. + Timestamps are aligned to UTC midnight of the beginning of the day or week. + Week begins on Monday. The Git repo for this module can be found here: http://aka.ms/PowerShellForGitHub @@ -302,14 +401,34 @@ function Get-GitHubCloneTraffic the background, enabling the command prompt to provide status information. If not supplied here, the DefaultNoStatus configuration property value will be used. + .INPUTS + GitHub.Branch + GitHub.Content + GitHub.Event + GitHub.Issue + GitHub.IssueComment + GitHub.Label + GitHub.Milestone + GitHub.PullRequest + GitHub.Project + GitHub.ProjectCard + GitHub.ProjectColumn + GitHub.Release + GitHub.Repository + + .OUTPUTS + GitHub.CloneTraffic + .EXAMPLE - Get-GitHubCloneTraffic -OwnerName Microsoft -RepositoryName PowerShellForGitHub + Get-GitHubCloneTraffic -OwnerName microsoft -RepositoryName PowerShellForGitHub - Get the total number of clones and breakdown per day or week for the last 14 days from the Microsoft\PowerShellForGitHub project. + Get the total number of clones and breakdown per day or week for the last 14 days + from the microsoft\PowerShellForGitHub project. #> [CmdletBinding( SupportsShouldProcess, DefaultParameterSetName='Elements')] + [OutputType({$script:GitHubCloneTrafficTypeName})] [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSShouldProcess", "", Justification="Methods called within here make use of PSShouldProcess, and the switch is passed on to them inherently.")] [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSReviewUnusedParameter", "", Justification="One or more parameters (like NoStatus) are only referenced by helper methods which get access to it from the stack via Get-Variable -Scope 1.")] param( @@ -321,7 +440,9 @@ function Get-GitHubCloneTraffic [Parameter( Mandatory, + ValueFromPipelineByPropertyName, ParameterSetName='Uri')] + [Alias('RepositoryUrl')] [string] $Uri, [ValidateSet('Day', 'Week')] @@ -354,5 +475,12 @@ function Get-GitHubCloneTraffic 'NoStatus' = (Resolve-ParameterWithDefaultConfigurationValue -Name NoStatus -ConfigValueName DefaultNoStatus) } - return Invoke-GHRestMethod @params + $result = Invoke-GHRestMethod @params + + if ($null -ne $result) + { + $result.PSObject.TypeNames.Insert(0, $script:GitHubCloneTrafficTypeName) + } + + return $result } diff --git a/GitHubTeams.ps1 b/GitHubTeams.ps1 index 6c27c294..fa6da2cc 100644 --- a/GitHubTeams.ps1 +++ b/GitHubTeams.ps1 @@ -1,7 +1,13 @@ # Copyright (c) Microsoft Corporation. All rights reserved. # Licensed under the MIT License. -function Get-GitHubTeam +@{ + GitHubTeamTypeName = 'GitHub.Team' + }.GetEnumerator() | ForEach-Object { + Set-Variable -Scope Script -Option ReadOnly -Name $_.Key -Value $_.Value + } + +filter Get-GitHubTeam { <# .SYNOPSIS @@ -41,17 +47,35 @@ function Get-GitHubTeam the background, enabling the command prompt to provide status information. If not supplied here, the DefaultNoStatus configuration property value will be used. + .INPUTS + GitHub.Branch + GitHub.Content + GitHub.Event + GitHub.Issue + GitHub.IssueComment + GitHub.Label + GitHub.Milestone + GitHub.Organization + GitHub.PullRequest + GitHub.Project + GitHub.ProjectCard + GitHub.ProjectColumn + GitHub.Release + GitHub.Repository + GitHub.Team + .OUTPUTS - [PSCustomObject[]] The team(s) that match the user's request. + GitHub.Team .EXAMPLE Get-GitHubTeam -OrganizationName PowerShell #> - [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSShouldProcess", "", Justification="Methods called within here make use of PSShouldProcess, and the switch is passed on to them inherently.")] - [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSReviewUnusedParameter", "", Justification="One or more parameters (like NoStatus) are only referenced by helper methods which get access to it from the stack via Get-Variable -Scope 1.")] [CmdletBinding( SupportsShouldProcess, DefaultParameterSetName='Elements')] + [OutputType({$script:GitHubTeamTypeName})] + [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSShouldProcess", "", Justification="Methods called within here make use of PSShouldProcess, and the switch is passed on to them inherently.")] + [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSReviewUnusedParameter", "", Justification="One or more parameters (like NoStatus) are only referenced by helper methods which get access to it from the stack via Get-Variable -Scope 1.")] param ( [Parameter(ParameterSetName='Elements')] @@ -62,17 +86,21 @@ function Get-GitHubTeam [Parameter( Mandatory, + ValueFromPipelineByPropertyName, ParameterSetName='Uri')] + [Alias('RepositoryUrl')] [string] $Uri, [Parameter( Mandatory, + ValueFromPipelineByPropertyName, ParameterSetName='Organization')] [ValidateNotNullOrEmpty()] [string] $OrganizationName, [Parameter( Mandatory, + ValueFromPipelineByPropertyName, ParameterSetName='Single')] [ValidateNotNullOrEmpty()] [string] $TeamId, @@ -117,7 +145,7 @@ function Get-GitHubTeam $params = @{ 'UriFragment' = $uriFragment - 'AcceptHeader' = 'application/vnd.github.hellcat-preview+json' + 'AcceptHeader' = $script:hellcatAcceptHeader 'Description' = $description 'AccessToken' = $AccessToken 'TelemetryEventName' = $MyInvocation.MyCommand.Name @@ -125,10 +153,11 @@ function Get-GitHubTeam 'NoStatus' = (Resolve-ParameterWithDefaultConfigurationValue -Name NoStatus -ConfigValueName DefaultNoStatus) } - return Invoke-GHRestMethodMultipleResult @params + return (Invoke-GHRestMethodMultipleResult @params | + Add-GitHubTeamAdditionalProperties) } -function Get-GitHubTeamMember +filter Get-GitHubTeamMember { <# .SYNOPSIS @@ -158,30 +187,51 @@ function Get-GitHubTeamMember the background, enabling the command prompt to provide status information. If not supplied here, the DefaultNoStatus configuration property value will be used. + .INPUTS + GitHub.Branch + GitHub.Content + GitHub.Event + GitHub.Issue + GitHub.IssueComment + GitHub.Label + GitHub.Milestone + GitHub.PullRequest + GitHub.Project + GitHub.ProjectCard + GitHub.ProjectColumn + GitHub.Release + GitHub.Repository + GitHub.Team + .OUTPUTS - [PSCustomObject[]] List of members on the team within the organization. + GitHub.User .EXAMPLE $members = Get-GitHubTeamMember -Organization PowerShell -TeamName Everybody #> - [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSShouldProcess", "", Justification="Methods called within here make use of PSShouldProcess, and the switch is passed on to them inherently.")] [CmdletBinding( SupportsShouldProcess, DefaultParameterSetName='ID')] + [OutputType({$script:GitHubUserTypeName})] + [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSShouldProcess", "", Justification="Methods called within here make use of PSShouldProcess, and the switch is passed on to them inherently.")] param ( - [Parameter(Mandatory)] + [Parameter( + Mandatory, + ValueFromPipelineByPropertyName)] [ValidateNotNullOrEmpty()] [String] $OrganizationName, [Parameter( Mandatory, + ValueFromPipelineByPropertyName, ParameterSetName='Name')] [ValidateNotNullOrEmpty()] [String] $TeamName, [Parameter( Mandatory, + ValueFromPipelineByPropertyName, ParameterSetName='ID')] [int64] $TeamId, @@ -223,5 +273,57 @@ function Get-GitHubTeamMember 'NoStatus' = $NoStatus } - return Invoke-GHRestMethodMultipleResult @params + return (Invoke-GHRestMethodMultipleResult @params | Add-GitHubUserAdditionalProperties) +} + +filter Add-GitHubTeamAdditionalProperties +{ +<# + .SYNOPSIS + Adds type name and additional properties to ease pipelining to GitHub Team objects. + + .PARAMETER InputObject + The GitHub object to add additional properties to. + + .PARAMETER TypeName + The type that should be assigned to the object. + + .INPUTS + [PSCustomObject] + + .OUTPUTS + GitHub.Team +#> + [CmdletBinding()] + [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSUseSingularNouns", "", Justification="Internal helper that is definitely adding more than one property.")] + param( + [Parameter( + Mandatory, + ValueFromPipeline)] + [AllowNull()] + [AllowEmptyCollection()] + [PSCustomObject[]] $InputObject, + + [ValidateNotNullOrEmpty()] + [string] $TypeName = $script:GitHubTeamTypeName + ) + + foreach ($item in $InputObject) + { + $item.PSObject.TypeNames.Insert(0, $TypeName) + + if (-not (Get-GitHubConfiguration -Name DisablePipelineSupport)) + { + Add-Member -InputObject $item -Name 'TeamName' -Value $item.name -MemberType NoteProperty -Force + Add-Member -InputObject $item -Name 'TeamId' -Value $item.id -MemberType NoteProperty -Force + + # Apply these properties to any embedded parent teams as well. + if ($null -ne $item.parent) + { + $null = Add-GitHubTeamAdditionalProperties -InputObject $item.parent + } + } + + Write-Output $item + } } diff --git a/GitHubUsers.ps1 b/GitHubUsers.ps1 index 3a77bca4..ed2b2cb9 100644 --- a/GitHubUsers.ps1 +++ b/GitHubUsers.ps1 @@ -1,7 +1,14 @@ # Copyright (c) Microsoft Corporation. All rights reserved. # Licensed under the MIT License. -function Get-GitHubUser +@{ + GitHubUserTypeName = 'GitHub.User' + GitHubUserContextualInformationTypeName = 'GitHub.UserContextualInformation' + }.GetEnumerator() | ForEach-Object { + Set-Variable -Scope Script -Option ReadOnly -Name $_.Key -Value $_.Value + } + +filter Get-GitHubUser { <# .SYNOPSIS @@ -14,7 +21,8 @@ function Get-GitHubUser .PARAMETER User The GitHub user to retrieve information for. - If not specified, will retrieve information on all GitHub users (and may take a while to complete). + If not specified, will retrieve information on all GitHub users + (and may take a while to complete). .PARAMETER Current If specified, gets information on the current user. @@ -38,11 +46,22 @@ function Get-GitHubUser which provides an email entry for this endpoint. If the user does not set a public email address for email, then it will have a value of null. + .INPUTS + GitHub.User + + .OUTPUTS + GitHub.User + .EXAMPLE - Get-GitHubUser -User octocat + Get-GitHubUser -UserName octocat Gets information on just the user named 'octocat' + .EXAMPLE + 'octocat', 'PowerShellForGitHubTeam' | Get-GitHubUser + + Gets information on the users named 'octocat' and 'PowerShellForGitHubTeam' + .EXAMPLE Get-GitHubUser @@ -56,11 +75,17 @@ function Get-GitHubUser [CmdletBinding( SupportsShouldProcess, DefaultParameterSetName='ListAndSearch')] + [OutputType({$script:GitHubUserTypeName})] [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSShouldProcess", "", Justification="Methods called within here make use of PSShouldProcess, and the switch is passed on to them inherently.")] [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSReviewUnusedParameter", "", Justification="One or more parameters (like NoStatus) are only referenced by helper methods which get access to it from the stack via Get-Variable -Scope 1.")] param( - [Parameter(ParameterSetName='ListAndSearch')] - [string] $User, + [Parameter( + ValueFromPipeline, + ValueFromPipelineByPropertyName, + ParameterSetName='ListAndSearch')] + [Alias('Name')] + [Alias('User')] + [string] $UserName, [Parameter(ParameterSetName='Current')] [switch] $Current, @@ -80,19 +105,22 @@ function Get-GitHubUser if ($Current) { - return Invoke-GHRestMethod -UriFragment "user" -Description "Getting current authenticated user" -Method 'Get' @params + return (Invoke-GHRestMethod -UriFragment "user" -Description "Getting current authenticated user" -Method 'Get' @params | + Add-GitHubUserAdditionalProperties) } - elseif ([String]::IsNullOrEmpty($User)) + elseif ([String]::IsNullOrEmpty($UserName)) { - return Invoke-GHRestMethodMultipleResult -UriFragment 'users' -Description 'Getting all users' @params + return (Invoke-GHRestMethodMultipleResult -UriFragment 'users' -Description 'Getting all users' @params | + Add-GitHubUserAdditionalProperties) } else { - return Invoke-GHRestMethod -UriFragment "users/$User" -Description "Getting user $User" -Method 'Get' @params + return (Invoke-GHRestMethod -UriFragment "users/$UserName" -Description "Getting user $UserName" -Method 'Get' @params | + Add-GitHubUserAdditionalProperties) } } -function Get-GitHubUserContextualInformation +filter Get-GitHubUserContextualInformation { <# .SYNOPSIS @@ -106,11 +134,23 @@ function Get-GitHubUserContextualInformation .PARAMETER User The GitHub user to retrieve information for. - .PARAMETER Subject - Identifies which additional information to receive about the user's hovercard. + .PARAMETER OrganizationId + The ID of an Organization. When provided, this returns back the context for the user + in relation to this Organization. + + .PARAMETER RepositoryId + The ID for a Repository. When provided, this returns back the context for the user + in relation to this Repository. - .PARAMETER SubjectId - The ID for the Subject. Required when Subject has been specified. + .PARAMETER IssueId + The ID for a Issue. When provided, this returns back the context for the user + in relation to this Issue. + NOTE: This is the *id* of the issue and not the issue *number*. + + .PARAMETER PullRequestId + The ID for a PullRequest. When provided, this returns back the context for the user + in relation to this Pull Request. + NOTE: This is the *id* of the pull request and not the pull request *number*. .PARAMETER AccessToken If provided, this will be used as the AccessToken for authentication with the @@ -122,23 +162,68 @@ function Get-GitHubUserContextualInformation the background, enabling the command prompt to provide status information. If not supplied here, the DefaultNoStatus configuration property value will be used. + .INPUTS + GitHub.Issue + GitHub.Organization + GitHub.PullRequest + GitHub.Repository + GitHub.User + + .OUTPUTS + GitHub.UserContextualInformation + .EXAMPLE Get-GitHubUserContextualInformation -User octocat .EXAMPLE - Get-GitHubUserContextualInformation -User octocat -Subject Repository -SubjectId 1300192 + Get-GitHubUserContextualInformation -User octocat -RepositoryId 1300192 + + .EXAMPLE + $repo = Get-GitHubRepository -OwnerName microsoft -RepositoryName 'PowerShellForGitHub' + $repo | Get-GitHubUserContextualInformation -User octocat + + .EXAMPLE + Get-GitHubIssue -OwnerName microsoft -RepositoryName PowerShellForGitHub -Issue 70 | + Get-GitHubUserContextualInformation -User octocat #> - [CmdletBinding(SupportsShouldProcess)] + [CmdletBinding( + SupportsShouldProcess, + DefaultParameterSetName='NoContext')] + [OutputType({$script:GitHubUserContextualInformationTypeName})] [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSShouldProcess", "", Justification="Methods called within here make use of PSShouldProcess, and the switch is passed on to them inherently.")] [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSReviewUnusedParameter", "", Justification="One or more parameters (like NoStatus) are only referenced by helper methods which get access to it from the stack via Get-Variable -Scope 1.")] param( - [Parameter(Mandatory)] - [string] $User, - - [ValidateSet('Organization', 'Repository', 'Issue', 'PullRequest')] - [string] $Subject, - - [string] $SubjectId, + [Parameter( + Mandatory, + ValueFromPipeline, + ValueFromPipelineByPropertyName)] + [Alias('Name')] + [Alias('User')] + [string] $UserName, + + [Parameter( + Mandatory, + ValueFromPipelineByPropertyName, + ParameterSetName='Organization')] + [int64] $OrganizationId, + + [Parameter( + Mandatory, + ValueFromPipelineByPropertyName, + ParameterSetName='Repository')] + [int64] $RepositoryId, + + [Parameter( + Mandatory, + ValueFromPipelineByPropertyName, + ParameterSetName='Issue')] + [int64] $IssueId, + + [Parameter( + Mandatory, + ValueFromPipelineByPropertyName, + ParameterSetName='PullRequest')] + [int64] $PullRequestId, [string] $AccessToken, @@ -149,38 +234,70 @@ function Get-GitHubUserContextualInformation $getParams = @() - # Intentionally not using -xor here because we need to know if we're setting the GET parameters as well. - if ((-not [String]::IsNullOrEmpty($Subject)) -or (-not [String]::IsNullOrEmpty($SubjectId))) + $contextType = [String]::Empty + $contextId = 0 + if ($PSCmdlet.ParameterSetName -ne 'NoContext') { - if ([String]::IsNullOrEmpty($Subject) -or [String]::IsNullOrEmpty($SubjectId)) + if ($PSCmdlet.ParameterSetName -eq 'Organization') { - $message = 'If either Subject or SubjectId has been provided, then BOTH must be provided.' - Write-Log -Message $message -Level Error - throw $message + $getParams += 'subject_type=organization' + $getParams += "subject_id=$OrganizationId" + + $contextType = 'OrganizationId' + $contextId = $OrganizationId + } + elseif ($PSCmdlet.ParameterSetName -eq 'Repository') + { + $getParams += 'subject_type=repository' + $getParams += "subject_id=$RepositoryId" + + $contextType = 'RepositoryId' + $contextId = $RepositoryId } + elseif ($PSCmdlet.ParameterSetName -eq 'Issue') + { + $getParams += 'subject_type=issue' + $getParams += "subject_id=$IssueId" - $subjectConverter = @{ - 'Organization' = 'organization' - 'Repository' = 'repository' - 'Issue' = 'issue' - 'PullRequest' = 'pull_request' + $contextType = 'IssueId' + $contextId = $IssueId } + elseif ($PSCmdlet.ParameterSetName -eq 'PullRequest') + { + $getParams += 'subject_type=pull_request' + $getParams += "subject_id=$PullRequestId" - $getParams += "subject_type=$($subjectConverter[$Subject])" - $getParams += "subject_id=$SubjectId" + $contextType = 'PullRequestId' + $contextId = $PullRequestId + } } $params = @{ - 'UriFragment' = "users/$User/hovercard`?" + ($getParams -join '&') + 'UriFragment' = "users/$UserName/hovercard`?" + ($getParams -join '&') 'Method' = 'Get' - 'Description' = "Getting hovercard information for $User" - 'AcceptHeader' = 'application/vnd.github.hagar-preview+json' + 'Description' = "Getting hovercard information for $UserName" + 'AcceptHeader' = $script:hagarAcceptHeader 'AccessToken' = $AccessToken 'TelemetryEventName' = $MyInvocation.MyCommand.Name 'NoStatus' = (Resolve-ParameterWithDefaultConfigurationValue -Name NoStatus -ConfigValueName DefaultNoStatus) } - Invoke-GHRestMethod @params + $result = Invoke-GHRestMethod @params + foreach ($item in $result.contexts) + { + $item.PSObject.TypeNames.Insert(0, $script:GitHubUserContextualInformationTypeName) + + if (-not (Get-GitHubConfiguration -Name DisablePipelineSupport)) + { + Add-Member -InputObject $item -Name 'UserName' -Value $UserName -MemberType NoteProperty -Force + if ($PSCmdlet.ParameterSetName -ne 'NoContext') + { + Add-Member -InputObject $item -Name $contextType -Value $contextId -MemberType NoteProperty -Force + } + } + } + + return $result } function Update-GitHubCurrentUser @@ -226,6 +343,9 @@ function Update-GitHubCurrentUser the background, enabling the command prompt to provide status information. If not supplied here, the DefaultNoStatus configuration property value will be used. + .OUTPUTS + GitHub.User + .EXAMPLE Update-GitHubCurrentUser -Location 'Seattle, WA' -Hireable:$false @@ -233,6 +353,7 @@ function Update-GitHubCurrentUser are not currently hireable. #> [CmdletBinding(SupportsShouldProcess)] + [OutputType({$script:GitHubUserTypeName})] [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSShouldProcess", "", Justification="Methods called within here make use of PSShouldProcess, and the switch is passed on to them inherently.")] param( [string] $Name, @@ -275,5 +396,82 @@ function Update-GitHubCurrentUser 'NoStatus' = (Resolve-ParameterWithDefaultConfigurationValue -Name NoStatus -ConfigValueName DefaultNoStatus) } - return Invoke-GHRestMethod @params + return (Invoke-GHRestMethod @params | Add-GitHubUserAdditionalProperties) +} + +filter Add-GitHubUserAdditionalProperties +{ +<# + .SYNOPSIS + Adds type name and additional properties to ease pipelining to GitHub User objects. + + .PARAMETER InputObject + The GitHub object to add additional properties to. + + .PARAMETER TypeName + The type that should be assigned to the object. + + .PARAMETER Name + The name of the user. This information might be obtainable from InputObject, so this + is optional based on what InputObject contains. + + .PARAMETER Id + The ID of the user. This information might be obtainable from InputObject, so this + is optional based on what InputObject contains. + + .INPUTS + [PSCustomObject] + + .OUTPUTS + GitHub.User +#> + [CmdletBinding()] + [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSUseSingularNouns", "", Justification="Internal helper that is definitely adding more than one property.")] + param( + [Parameter( + Mandatory, + ValueFromPipeline)] + [AllowNull()] + [AllowEmptyCollection()] + [PSCustomObject[]] $InputObject, + + [ValidateNotNullOrEmpty()] + [string] $TypeName = $script:GitHubUserTypeName, + + [string] $Name, + + [int64] $Id + ) + + foreach ($item in $InputObject) + { + $item.PSObject.TypeNames.Insert(0, $TypeName) + + if (-not (Get-GitHubConfiguration -Name DisablePipelineSupport)) + { + $userName = $item.login + if ([String]::IsNullOrEmpty($userName) -and $PSBoundParameters.ContainsKey('Name')) + { + $userName = $Name + } + + if (-not [String]::IsNullOrEmpty($userName)) + { + Add-Member -InputObject $item -Name 'UserName' -Value $userName -MemberType NoteProperty -Force + } + + $userId = $item.id + if (($userId -eq 0) -and $PSBoundParameters.ContainsKey('Id')) + { + $userId = $Id + } + + if ($userId -ne 0) + { + Add-Member -InputObject $item -Name 'UserId' -Value $userId -MemberType NoteProperty -Force + } + } + + Write-Output $item + } } diff --git a/Helpers.ps1 b/Helpers.ps1 index 1813d168..17fa80e2 100644 --- a/Helpers.ps1 +++ b/Helpers.ps1 @@ -281,13 +281,13 @@ function Write-Log [System.Management.Automation.ErrorRecord] $Exception ) - Begin + begin { # Accumulate the list of Messages, whether by pipeline or parameter. $messages = @() } - Process + process { foreach ($m in $Message) { @@ -295,7 +295,7 @@ function Write-Log } } - End + end { if ($null -ne $Exception) { @@ -499,6 +499,7 @@ function Write-InvocationLog } function DeepCopy-Object +{ <# .SYNOPSIS Creates a deep copy of a serializable object. @@ -524,7 +525,6 @@ function DeepCopy-Object .RETURNS An exact copy of the PSObject that was just deep copied. #> -{ [CmdletBinding()] [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSUseApprovedVerbs", "", Justification="Intentional. This isn't exported, and needed to be explicit relative to Copy-Object.")] param( @@ -682,9 +682,7 @@ function Resolve-UnverifiedPath #> [CmdletBinding()] param( - [Parameter( - Position=0, - ValueFromPipeline)] + [Parameter(ValueFromPipeline)] [string] $Path ) diff --git a/PowerShellForGitHub.psd1 b/PowerShellForGitHub.psd1 index 5ef27773..c7f51c82 100644 --- a/PowerShellForGitHub.psd1 +++ b/PowerShellForGitHub.psd1 @@ -24,9 +24,9 @@ 'GitHubAssignees.ps1', 'GitHubBranches.ps1', 'GitHubCore.ps1', - 'GitHubComments.ps1', 'GitHubContents.ps1', 'GitHubEvents.ps1', + 'GitHubIssueComments.ps1', 'GitHubIssues.ps1', 'GitHubLabels.ps1', 'GitHubMilestones.ps1', @@ -57,13 +57,13 @@ 'Get-GitHubAssignee', 'Get-GitHubCloneTraffic', 'Get-GitHubCodeOfConduct', - 'Get-GitHubComment', 'Get-GitHubConfiguration', 'Get-GitHubContent', 'Get-GitHubEmoji', 'Get-GitHubEvent', 'Get-GitHubGitIgnore', 'Get-GitHubIssue', + 'Get-GitHubIssueComment', 'Get-GitHubIssueTimeline', 'Get-GitHubLabel', 'Get-GitHubLicense', @@ -95,13 +95,14 @@ 'Group-GitHubPullRequest', 'Invoke-GHRestMethod', 'Invoke-GHRestMethodMultipleResult', + 'Join-GitHubUri', 'Lock-GitHubIssue', 'Move-GitHubProjectCard', 'Move-GitHubProjectColumn', 'Move-GitHubRepositoryOwnership', - 'New-GithubAssignee', - 'New-GitHubComment', + 'New-GitHubAssignee', 'New-GitHubIssue', + 'New-GitHubIssueComment', 'New-GitHubLabel', 'New-GitHubMilestone', 'New-GitHubProject', @@ -110,8 +111,8 @@ 'New-GitHubPullRequest', 'New-GitHubRepository', 'New-GitHubRepositoryFork', - 'Remove-GithubAssignee', - 'Remove-GitHubComment', + 'Remove-GitHubAssignee', + 'Remove-GitHubIssueComment', 'Remove-GitHubIssueLabel', 'Remove-GitHubLabel', 'Remove-GitHubMilestone', @@ -123,8 +124,8 @@ 'Reset-GitHubConfiguration', 'Restore-GitHubConfiguration', 'Set-GitHubAuthentication', - 'Set-GitHubComment', 'Set-GitHubConfiguration', + 'Set-GitHubIssueComment', 'Set-GitHubIssueLabel', 'Set-GitHubLabel', 'Set-GitHubMilestone', @@ -145,6 +146,7 @@ AliasesToExport = @( 'Delete-GitHubComment', + 'Delete-GitHubIssueComment', 'Delete-GitHubLabel', 'Delete-GitHubMilestone', 'Delete-GitHubProject', @@ -152,6 +154,10 @@ 'Delete-GitHubProjectColumn' 'Delete-GitHubRepository', 'Get-GitHubBranch', + 'Get-GitHubComment', + 'New-GitHubComment', + 'Remove-GitHubComment', + 'Set-GitHubComment', 'Transfer-GitHubRepositoryOwnership' ) diff --git a/README.md b/README.md index 0332ee91..0488dc54 100644 --- a/README.md +++ b/README.md @@ -8,9 +8,9 @@ [![CII Best Practices](https://bestpractices.coreinfrastructure.org/projects/3990/badge)](https://bestpractices.coreinfrastructure.org/projects/3990) [![tweet](https://img.shields.io/twitter/url?url=https%3A%2F%2Ftwitter.com%2FQuackFu)](https://twitter.com/intent/tweet?text=%23PowerShellForGitHub%20%40QuackFu%20&original_referer=https://github.com/microsoft/PowerShellForGitHub)
-[![Build status](https://dev.azure.com/ms/PowerShellForGitHub/_apis/build/status/PowerShellForGitHub-CI?branchName=master)](https://dev.azure.com/ms/PowerShellForGitHub/_build?definitionId=109&_a=summary&repositoryFilter=63&branchFilter=2197) -[![Azure DevOps tests](https://img.shields.io/azure-devops/tests/ms/PowerShellForGitHub/109/master)](https://dev.azure.com/ms/PowerShellForGitHub/_build?definitionId=109&_a=summary&repositoryFilter=63&branchFilter=2197) -[![Azure DevOps coverage](https://img.shields.io/azure-devops/coverage/ms/PowerShellForGitHub/109/master)](https://dev.azure.com/ms/PowerShellForGitHub/_build?definitionId=109&_a=summary&repositoryFilter=63&branchFilter=2197) +[![Build status](https://dev.azure.com/ms/PowerShellForGitHub/_apis/build/status/PowerShellForGitHub-CI?branchName=master)](https://dev.azure.com/ms/PowerShellForGitHub/_build/latest?definitionId=109&branchName=master) +[![Azure DevOps tests](https://img.shields.io/azure-devops/tests/ms/PowerShellForGitHub/109/master)](https://dev.azure.com/ms/PowerShellForGitHub/_build/latest?definitionId=109&branchName=master) +[![Azure DevOps coverage](https://img.shields.io/azure-devops/coverage/ms/PowerShellForGitHub/109/master)](https://dev.azure.com/ms/PowerShellForGitHub/_build/latest?definitionId=109&branchName=master)
[![Help Wanted Issues](https://img.shields.io/github/issues/microsoft/PowerShellForGitHub/help%20wanted)](https://github.com/microsoft/PowerShellForGitHub/issues?q=is%3Aissue+is%3Aopen+label%3A%22help+wanted%22) [![GitHub last commit](https://img.shields.io/github/last-commit/microsoft/PowerShellForGitHub)](https://github.com/HowardWolosky/PowerShellForGitHub/commits/master) @@ -35,28 +35,58 @@ ## Overview This is a [PowerShell](https://microsoft.com/powershell) [module](https://technet.microsoft.com/en-us/library/dd901839.aspx) -that provides command-line interaction and automation for the [GitHub v3 API](https://developer.github.com/v3/). +that provides stateless command-line interaction and automation for the +[GitHub v3 API](https://developer.github.com/v3/). + +**Embracing the benefits of PowerShell, it has +[full support for pipelining](./USAGE.md#embracing-the-pipeline), allowing you pipe the output of +virtually any command into any other command within the module.** ---------- ## Current API Support At present, this module can: - * Query issues - * Query [pull requests](https://developer.github.com/v3/pulls/) + * Query, create, update and remove [Repositories](https://developer.github.com/v3/repos/) including + * Query [Branches](https://developer.github.com/v3/repos/branches/) + * Query and create new [Forks](https://developer.github.com/v3/repos/forks/) + * Query/retrieve [Content](https://developer.github.com/v3/repos/contents/) from a repo. + * Query the languages and tags in a repository, and and query/update its topics. + * Change repository ownership. + * Query various [traffic reports](https://developer.github.com/v3/repos/traffic/) including + referral sources and paths, page views and clones. + * Query, create, edit, lock/unlock [Issues](https://developer.github.com/v3/issues/) and + all of their related properties: + * Query, check, add and remove [Assignees](https://developer.github.com/v3/issues/assignees/) + * Query, create, edit and remove [Issue Comments](https://developer.github.com/v3/issues/comments/) + * Query, create, edit and remove [Labels](https://developer.github.com/v3/issues/labels/) + * Query [Events](https://developer.github.com/v3/issues/events/) and the + [timeline](https://developer.github.com/v3/issues/timeline/) + * Query, create, edit and remove [Milestones](https://developer.github.com/v3/issues/milestones/) + * Query and create [Pull Requests](https://developer.github.com/v3/pulls/) * Query [collaborators](https://developer.github.com/v3/repos/collaborators/) * Query [contributors](https://developer.github.com/v3/repos/statistics/) - * Query [organizations](https://developer.github.com/v3/orgs/) - * Query, create, update and remove [Issues](https://developer.github.com/v3/issues/) and - all of their related properties (assignees, comments, events, labels, milestones, timeline) - * Query, create, update and remove [Labels](https://developer.github.com/v3/issues/labels/) - * Query, check, add and remove [Assignees](https://developer.github.com/v3/issues/assignees/) - * Query, create, update and remove [Repositories](https://developer.github.com/v3/repos/) + * Query [organizations](https://developer.github.com/v3/orgs/) and their members. * Query and update [Users](https://developer.github.com/v3/users/) + * Query [Teams](https://developer.github.com/v3/teams/) and their members. + * Query, create, edit and remove [Projects](https://developer.github.com/v3/projects/), along with + [Project Columns](https://developer.github.com/v3/projects/columns/) and + [Project Cards](https://developer.github.com/v3/projects/cards/) + * Query [Releases](https://developer.github.com/v3/repos/releases/) + * Miscellaneous functionality: + * Get all [Codes of Conduct](https://developer.github.com/v3/codes_of_conduct/) as well as that + of a specific repo. + * Get all [GitHub emojis](https://developer.github.com/v3/emojis/) + * Get [gitignore templates](https://developer.github.com/v3/gitignore/) + * Get [commonly used licenses](https://developer.github.com/v3/licenses/) as well as that for + a specific repository. + * [Convert markdown](https://developer.github.com/v3/markdown/) to the equivalent HTML + * Get your current [rate limit](https://developer.github.com/v3/rate_limit/) for API usage. Development is ongoing, with the goal to add broad support for the entire API set. -For a comprehensive look at what work is remaining to be API Complete, refer to [Issue #70](https://github.com/PowerShell/PowerShellForGitHub/issues/70). +For a comprehensive look at what work is remaining to be API Complete, refer to +[Issue #70](https://github.com/microsoft/PowerShellForGitHub/issues/70). Review [examples](USAGE.md#examples) to see how the module can be used to accomplish some of these tasks. @@ -135,7 +165,7 @@ Set-GitHubConfiguration -ApiHostName "github.contoso.com" Example command: ```powershell -$issues = Get-GitHubIssue -Uri 'https://github.com/PowerShell/PowerShellForGitHub' +$issues = Get-GitHubIssue -Uri 'https://github.com/microsoft/PowerShellForGitHub' ``` For more example commands, please refer to [USAGE](USAGE.md#examples). @@ -147,7 +177,7 @@ For more example commands, please refer to [USAGE](USAGE.md#examples). Please see the [Contribution Guide](CONTRIBUTING.md) for information on how to develop and contribute. -If you have any problems, please consult [GitHub Issues](https://github.com/PowerShell/PowerShellForGitHub/issues) +If you have any problems, please consult [GitHub Issues](https://github.com/microsoft/PowerShellForGitHub/issues) to see if has already been discussed. If you do not see your problem captured, please file [feedback](CONTRIBUTING.md#feedback). diff --git a/Tests/Common.ps1 b/Tests/Common.ps1 index f28dc534..2e5d5512 100644 --- a/Tests/Common.ps1 +++ b/Tests/Common.ps1 @@ -1,6 +1,15 @@ # Copyright (c) Microsoft Corporation. All rights reserved. # Licensed under the MIT License. +# PSScriptAnalyzer incorrectly flags a number of variables as PSUseDeclaredVarsMoreThanAssignments +# since it doesn't work well with variables defined in BeforeAll{} but only referenced in a later Context. +# We are suppressing that rule in Test files, which means that we are then losing out on catching +# scenarios where we might be assigning to a variable and then referencing it with a typo. +# By setting StrictMode, the test file will immediately fail if there are any variables that are +# being referenced before they were assigned. It won't catch variables that are assigned to but +# never referenced, but that's not as big of a deal for tests. +Set-StrictMode -Version 1.0 + # Caches if the tests are actively configured with an access token. $script:accessTokenConfigured = $false diff --git a/Tests/GitHubAnalytics.tests.ps1 b/Tests/GitHubAnalytics.tests.ps1 index df62e4f5..1be29162 100644 --- a/Tests/GitHubAnalytics.tests.ps1 +++ b/Tests/GitHubAnalytics.tests.ps1 @@ -6,307 +6,18 @@ Tests for GitHubAnalytics.ps1 module #> +[CmdletBinding()] +[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseDeclaredVarsMoreThanAssignments', '', + Justification='Suppress false positives in Pester code blocks')] +param() + # This is common test code setup logic for all Pester test files $moduleRootPath = Split-Path -Path $PSScriptRoot -Parent . (Join-Path -Path $moduleRootPath -ChildPath 'Tests\Common.ps1') try { - Describe 'Obtaining issues for repository' { - $repo = New-GitHubRepository -RepositoryName ([Guid]::NewGuid().Guid) -AutoInit - - Context 'When initially created, there are no issues' { - $issues = @(Get-GitHubIssue -Uri $repo.svn_url) - - It 'Should return expected number of issues' { - $issues.Count | Should be 0 - } - } - - Context 'When there are issues present' { - $newIssues = @() - for ($i = 0; $i -lt 4; $i++) - { - $newIssues += New-GitHubIssue -OwnerName $script:ownerName -RepositoryName $repo.name -Title ([guid]::NewGuid().Guid) - } - - $newIssues[0] = Update-GitHubIssue -OwnerName $script:ownerName -RepositoryName $repo.name -Issue $newIssues[0].number -State Closed - $newIssues[-1] = Update-GitHubIssue -OwnerName $script:ownerName -RepositoryName $repo.name -Issue $newIssues[-1].number -State Closed - - $issues = @(Get-GitHubIssue -Uri $repo.svn_url) - It 'Should return only open issues' { - $issues.Count | Should be 2 - } - - $issues = @(Get-GitHubIssue -Uri $repo.svn_url -State All) - It 'Should return all issues' { - $issues.Count | Should be 4 - } - - $createdOnOrAfterDate = Get-Date -Date $newIssues[0].created_at - $createdOnOrBeforeDate = Get-Date -Date $newIssues[2].created_at - $issues = @((Get-GitHubIssue -Uri $repo.svn_url) | Where-Object { ($_.created_at -ge $createdOnOrAfterDate) -and ($_.created_at -le $createdOnOrBeforeDate) }) - - It 'Smart object date conversion works for comparing dates' { - $issues.Count | Should be 2 - } - - $createdDate = Get-Date -Date $newIssues[1].created_at - $issues = @(Get-GitHubIssue -Uri $repo.svn_url -State All | Where-Object { ($_.created_at -ge $createdDate) -and ($_.state -eq 'closed') }) - - It 'Able to filter based on date and state' { - $issues.Count | Should be 1 - } - } - - Context 'When issues are retrieved with a specific MediaTypes' { - $newIssue = New-GitHubIssue -OwnerName $script:ownerName -RepositoryName $repo.name -Title ([guid]::NewGuid()) -Body ([guid]::NewGuid()) - - $issues = @(Get-GitHubIssue -Uri $repo.svn_url -Issue $newIssue.number -MediaType 'Html') - It 'Should return an issue with body_html' { - $issues[0].body_html | Should not be $null - } - } - - $null = Remove-GitHubRepository -Uri ($repo.svn_url) -Confirm:$false - } - - Describe 'Obtaining repository with biggest number of issues' { - $repo1 = New-GitHubRepository -RepositoryName ([Guid]::NewGuid().Guid) -AutoInit - $repo2 = New-GitHubRepository -RepositoryName ([Guid]::NewGuid().Guid) -AutoInit - - Context 'When no additional conditions specified' { - for ($i = 0; $i -lt 3; $i++) - { - $null = New-GitHubIssue -OwnerName $script:ownerName -RepositoryName $repo1.name -Title ([guid]::NewGuid().Guid) - } - - $repos = @(($repo1.svn_url), ($repo2.svn_url)) - $issueCounts = @() - $repos | ForEach-Object { $issueCounts = $issueCounts + ([PSCustomObject]@{ 'Uri' = $_; 'Count' = (Get-GitHubIssue -Uri $_).Count }) } - $issueCounts = $issueCounts | Sort-Object -Property Count -Descending - - It 'Should return expected number of issues for each repository' { - $issueCounts[0].Count | Should be 3 - $issueCounts[1].Count | Should be 0 - } - - It 'Should return expected repository names' { - $issueCounts[0].Uri | Should be $repo1.svn_url - $issueCounts[1].Uri | Should be $repo2.svn_url - } - } - - $null = Remove-GitHubRepository -Uri ($repo1.svn_url) -Confirm:$false - $null = Remove-GitHubRepository -Uri ($repo2.svn_url) -Confirm:$false - } - - - # TODO: Re-enable these tests once the module has sufficient support getting the repository into the - # required state for testing, and to recover back to the original state at the conclusion of the test. - - # Describe 'Obtaining pull requests for repository' { - # Context 'When no additional conditions specified' { - # $pullRequests = Get-GitHubPullRequest -Uri $script:repositoryUrl - - # It 'Should return expected number of PRs' { - # @($pullRequests).Count | Should be 2 - # } - # } - - # Context 'When state and time range specified' { - # $mergedStartDate = Get-Date -Date '2016-04-10' - # $mergedEndDate = Get-Date -Date '2016-05-07' - # $pullRequests = Get-GitHubPullRequest -Uri $script:repositoryUrl -State Closed | - # Where-Object { ($_.merged_at -ge $mergedStartDate) -and ($_.merged_at -le $mergedEndDate) } - - # It 'Should return expected number of PRs' { - # @($pullRequests).Count | Should be 3 - # } - # } - # } - - # Describe 'Obtaining repository with biggest number of pull requests' { - # Context 'When no additional conditions specified' { - # @($script:repositoryUrl, $script:repositoryUrl2) | - # ForEach-Object { - # $pullRequestCounts += ([PSCustomObject]@{ - # 'Uri' = $_; - # 'Count' = (Get-GitHubPullRequest -Uri $_).Count }) } - # $pullRequestCounts = $pullRequestCounts | Sort-Object -Property Count -Descending - - # It 'Should return expected number of pull requests for each repository' { - # @($pullRequestCounts[0].Count) | Should be 2 - # @($pullRequestCounts[1].Count) | Should be 0 - # } - - # It 'Should return expected repository names' { - # @($pullRequestCounts[0].Uri) | Should be $script:repositoryUrl - # @($pullRequestCounts[1].Uri) | Should be $script:repositoryUrl2 - # } - # } - - # Context 'When state and time range specified' { - # $mergedDate = Get-Date -Date '2015-04-20' - # $repos = @($script:repositoryUrl, $script:repositoryUrl2) - # $pullRequestCounts = @() - # $pullRequestSearchParams = @{ - # 'State' = 'closed' - # } - # $repos | - # ForEach-Object { - # $pullRequestCounts += ([PSCustomObject]@{ - # 'Uri' = $_; - # 'Count' = ( - # (Get-GitHubPullRequest -Uri $_ @pullRequestSearchParams) | - # Where-Object { $_.merged_at -ge $mergedDate } - # ).Count - # }) } - - # $pullRequestCounts = $pullRequestCounts | Sort-Object -Property Count -Descending - # $pullRequests = Get-GitHubTopPullRequestRepository -Uri @($script:repositoryUrl, $script:repositoryUrl2) -State Closed -MergedOnOrAfter - - # It 'Should return expected number of pull requests for each repository' { - # @($pullRequests[0].Count) | Should be 3 - # @($pullRequests[1].Count) | Should be 0 - # } - - # It 'Should return expected repository names' { - # @($pullRequests[0].Uri) | Should be $script:repositoryUrl - # @($pullRequests[1].Uri) | Should be $script:repositoryUrl2 - # } - # } - # } - - if ($script:accessTokenConfigured) - { - Describe 'Obtaining collaborators for repository' { - $repositoryName = [guid]::NewGuid().Guid - $null = New-GitHubRepository -RepositoryName $repositoryName -AutoInit - $repositoryUrl = "https://github.com/$script:ownerName/$repositoryName" - - $collaborators = @(Get-GitHubRepositoryCollaborator -Uri $repositoryUrl) - - It 'Should return expected number of collaborators' { - $collaborators.Count | Should be 1 - } - - $null = Remove-GitHubRepository -OwnerName $script:ownerName -RepositoryName $repositoryName -Confirm:$false - } - } - - Describe 'Obtaining contributors for repository' { - $repositoryName = [guid]::NewGuid().Guid - $null = New-GitHubRepository -RepositoryName $repositoryName -AutoInit - $repositoryUrl = "https://github.com/$script:ownerName/$repositoryName" - - $contributors = @(Get-GitHubRepositoryContributor -Uri $repositoryUrl -IncludeStatistics) - - It 'Should return expected number of contributors' { - $contributors.Count | Should be 1 - } - - $null = Remove-GitHubRepository -OwnerName $script:ownerName -RepositoryName $repositoryName -Confirm:$false - } - - if ($script:accessTokenConfigured) - { - # TODO: Re-enable these tests once the module has sufficient support getting the Organization - # and repository into the required state for testing, and to recover back to the original state - # at the conclusion of the test. - - # Describe 'Obtaining organization members' { - # $members = Get-GitHubOrganizationMember -OrganizationName $script:organizationName - - # It 'Should return expected number of organization members' { - # @($members).Count | Should be 1 - # } - # } - - # Describe 'Obtaining organization teams' { - # $teams = Get-GitHubTeam -OrganizationName $script:organizationName - - # It 'Should return expected number of organization teams' { - # @($teams).Count | Should be 2 - # } - # } - - # Describe 'Obtaining organization team members' { - # $members = Get-GitHubTeamMember -OrganizationName $script:organizationName -TeamName $script:organizationTeamName - - # It 'Should return expected number of organization team members' { - # @($members).Count | Should be 1 - # } - # } - } - - Describe 'Getting repositories from organization' { - $original = @(Get-GitHubRepository -OrganizationName $script:organizationName) - - $repo = New-GitHubRepository -RepositoryName ([guid]::NewGuid().Guid) -OrganizationName $script:organizationName - $current = @(Get-GitHubRepository -OrganizationName $script:organizationName) - - It 'Should return expected number of organization repositories' { - ($current.Count - $original.Count) | Should be 1 - } - - $null = Remove-GitHubRepository -Uri $repo.svn_url -Confirm:$false - } - - Describe 'Getting unique contributors from contributors array' { - $repositoryName = [guid]::NewGuid().Guid - $null = New-GitHubRepository -RepositoryName $repositoryName -AutoInit - - $contributors = @(Get-GitHubRepositoryContributor -OwnerName $script:ownerName -RepositoryName $repositoryName -IncludeStatistics) - - $uniqueContributors = $contributors | - Select-Object -ExpandProperty author | - Select-Object -ExpandProperty login -Unique - Sort-Object - - It 'Should return expected number of unique contributors' { - $uniqueContributors.Count | Should be 1 - } - - $null = Remove-GitHubRepository -OwnerName $script:ownerName -RepositoryName $repositoryName -Confirm:$false - } - - Describe 'Getting repository name from url' { - $repositoryName = [guid]::NewGuid().Guid - $url = "https://github.com/$script:ownerName/$repositoryName" - $name = Split-GitHubUri -Uri $url -RepositoryName - - It 'Should return expected repository name' { - $name | Should be $repositoryName - } - } - - Describe 'Getting repository owner from url' { - $repositoryName = [guid]::NewGuid().Guid - $url = "https://github.com/$script:ownerName/$repositoryName" - $owner = Split-GitHubUri -Uri $url -OwnerName - - It 'Should return expected repository owner' { - $owner | Should be $script:ownerName - } - } - - Describe 'Getting branches for repository' { - $repositoryName = [guid]::NewGuid().Guid - $null = New-GitHubRepository -RepositoryName $repositoryName -AutoInit - - $branches = @(Get-GitHubRepositoryBranch -OwnerName $script:ownerName -RepositoryName $repositoryName) - - It 'Should return expected number of repository branches' { - $branches.Count | Should be 1 - } - - It 'Should return the name of the branches' { - $branches[0].name | Should be 'master' - } - - $null = Remove-GitHubRepository -OwnerName $script:ownerName -RepositoryName $repositoryName -Confirm:$false - } + # TODO } finally { diff --git a/Tests/GitHubAssignees.tests.ps1 b/Tests/GitHubAssignees.tests.ps1 index 5e4738d5..d9eeb61c 100644 --- a/Tests/GitHubAssignees.tests.ps1 +++ b/Tests/GitHubAssignees.tests.ps1 @@ -6,62 +6,253 @@ Tests for GitHubAssignees.ps1 module #> +[CmdletBinding()] +[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseDeclaredVarsMoreThanAssignments', '', + Justification='Suppress false positives in Pester code blocks')] +param() + # This is common test code setup logic for all Pester test files $moduleRootPath = Split-Path -Path $PSScriptRoot -Parent . (Join-Path -Path $moduleRootPath -ChildPath 'Tests\Common.ps1') try { - $repo = New-GitHubRepository -RepositoryName ([Guid]::NewGuid().Guid) -AutoInit - $issue = New-GitHubIssue -Uri $repo.svn_url -Title "Test issue" + Describe 'Getting an Assignee' { + BeforeAll { + $repo = New-GitHubRepository -RepositoryName ([Guid]::NewGuid().Guid) -AutoInit + } - Describe 'Getting a valid assignee' { + AfterAll { + $repo | Remove-GitHubRepository -Confirm:$false + } - Context 'For getting a valid assignee' { - $assigneeList = @(Get-GitHubAssignee -Uri $repo.svn_url) + Context 'For getting assignees in a repository via parameters' { + $assigneeList = @(Get-GitHubAssignee -OwnerName $script:ownerName -RepositoryName $repo.name) It 'Should have returned the one assignee' { - $assigneeList.Count | Should be 1 + $assigneeList.Count | Should -Be 1 } - $assigneeUserName = $assigneeList[0].login - - It 'Should have returned an assignee with a login'{ - $assigneeUserName | Should not be $null + It 'Should have the expected type' { + $assigneeList[0].PSObject.TypeNames[0] | Should -Be 'GitHub.User' } + } - $hasPermission = Test-GitHubAssignee -Uri $repo.svn_url -Assignee $assigneeUserName + Context 'For getting assignees in a repository with the repo on the pipeline' { + $assigneeList = @($repo | Get-GitHubAssignee) - It 'Should have returned an assignee with permission to be assigned to an issue'{ - $hasPermission | Should be $true + It 'Should have returned the one assignee' { + $assigneeList.Count | Should -Be 1 } + It 'Should have the expected type' { + $assigneeList[0].PSObject.TypeNames[0] | Should -Be 'GitHub.User' + } } } - Describe 'Adding and removing an assignee to an issue'{ + Describe 'Testing for a valid Assignee' { + BeforeAll { + $repo = New-GitHubRepository -RepositoryName ([Guid]::NewGuid().Guid) -AutoInit + $octocat = Get-GitHubUser -UserName 'octocat' + $owner = Get-GitHubUser -UserName $script:ownerName + } - Context 'For adding an assignee to an issue'{ - $assigneeList = @(Get-GitHubAssignee -Uri $repo.svn_url) - $assigneeUserName = $assigneeList[0].login - $assignees = $assigneeUserName - New-GithubAssignee -Uri $repo.svn_url -Issue $issue.number -Assignee $assignees - $issue = Get-GitHubIssue -Uri $repo.svn_url -Issue $issue.number + AfterAll { + $repo | Remove-GitHubRepository -Confirm:$false + } - It 'Should have assigned the user to the issue' { - $issue.assignee.login | Should be $assigneeUserName + Context 'For testing valid owner with parameters' { + $hasPermission = Test-GitHubAssignee -OwnerName $script:ownerName -RepositoryName $repo.name -Assignee $script:ownerName + + It 'Should consider the owner of the repo to be a valid assignee' { + $hasPermission | Should -BeTrue } + } - Remove-GithubAssignee -Uri $repo.svn_url -Issue $issue.number -Assignee $assignees -Confirm:$false - $issue = Get-GitHubIssue -Uri $repo.svn_url -Issue $issue.number + Context 'For testing valid owner with the repo on the pipeline' { + $hasPermission = $repo | Test-GitHubAssignee -Assignee $script:ownerName - It 'Should have removed the user from issue' { - $issue.assignees.Count | Should be 0 + It 'Should consider the owner of the repo to be a valid assignee' { + $hasPermission | Should -BeTrue + } + } + + Context 'For testing valid owner with a user object on the pipeline' { + $hasPermission = $owner | Test-GitHubAssignee -OwnerName $script:ownerName -RepositoryName $repo.name + + It 'Should consider the owner of the repo to be a valid assignee' { + $hasPermission | Should -BeTrue + } + } + + Context 'For testing invalid owner with a user object on the pipeline' { + $hasPermission = $octocat | Test-GitHubAssignee -OwnerName $script:ownerName -RepositoryName $repo.name + + It 'Should consider the owner of the repo to be a valid assignee' { + $hasPermission | Should -BeFalse } } } - Remove-GitHubRepository -Uri $repo.svn_url -Confirm:$false + Describe 'Adding and Removing Assignees from an Issue' { + BeforeAll { + $repo = New-GitHubRepository -RepositoryName ([Guid]::NewGuid().Guid) -AutoInit + $owner = Get-GitHubUser -UserName $script:ownerName + } + + AfterAll { + $repo | Remove-GitHubRepository -Confirm:$false + } + + Context 'Adding and removing an assignee via parameters' { + $issue = $repo | New-GitHubIssue -Title "Test issue" + It 'Should have no assignees when created' { + $issue.assignee.login | Should -BeNullOrEmpty + $issue.assignees | Should -BeNullOrEmpty + } + + $updatedIssue = New-GitHubAssignee -OwnerName $script:ownerName -RepositoryName $repo.name -Issue $issue.number -Assignee $owner.login + It 'Should have returned the same issue' { + $updatedIssue.number | Should -Be $issue.number + } + + It 'Should have added the requested Assignee to the issue' { + $updatedIssue.assignees.Count | Should -Be 1 + $updatedIssue.assignee.login | Should -Be $owner.login + $updatedIssue.assignees[0].login | Should -Be $owner.login + } + + It 'Should be of the expected type' { + $updatedIssue.PSObject.TypeNames[0] | Should -Be 'GitHub.Issue' + } + + $updatedIssue = Remove-GitHubAssignee -OwnerName $script:ownerName -RepositoryName $repo.name -Issue $issue.number -Assignee $owner.login -Confirm:$false + It 'Should have returned the same issue' { + $updatedIssue.number | Should -Be $issue.number + } + + It 'Should have added the requested Assignee to the issue' { + $updatedIssue.assignee.login | Should -BeNullOrEmpty + $updatedIssue.assignees | Should -BeNullOrEmpty + } + + It 'Should be of the expected type' { + $updatedIssue.PSObject.TypeNames[0] | Should -Be 'GitHub.Issue' + } + } + + Context 'Adding an assignee with the repo on the pipeline' { + $issue = $repo | New-GitHubIssue -Title "Test issue" + It 'Should have no assignees when created' { + $issue.assignee.login | Should -BeNullOrEmpty + $issue.assignees | Should -BeNullOrEmpty + } + + $updatedIssue = $repo | New-GitHubAssignee -Issue $issue.number -Assignee $owner.login + It 'Should have returned the same issue' { + $updatedIssue.number | Should -Be $issue.number + } + + It 'Should have added the requested Assignee to the issue' { + $updatedIssue.assignees.Count | Should -Be 1 + $updatedIssue.assignee.login | Should -Be $owner.login + $updatedIssue.assignees[0].login | Should -Be $owner.login + } + + It 'Should be of the expected type' { + $updatedIssue.PSObject.TypeNames[0] | Should -Be 'GitHub.Issue' + } + + $updatedIssue = $repo | Remove-GitHubAssignee -Issue $issue.number -Assignee $owner.login -Force -Confirm:$false + It 'Should have returned the same issue' { + $updatedIssue.number | Should -Be $issue.number + } + + It 'Should have added the requested Assignee to the issue' { + $updatedIssue.assignee.login | Should -BeNullOrEmpty + $updatedIssue.assignees | Should -BeNullOrEmpty + } + + It 'Should be of the expected type' { + $updatedIssue.PSObject.TypeNames[0] | Should -Be 'GitHub.Issue' + } + } + + Context 'Adding an assignee with the issue on the pipeline' { + $issue = $repo | New-GitHubIssue -Title "Test issue" + It 'Should have no assignees when created' { + $issue.assignee.login | Should -BeNullOrEmpty + $issue.assignees | Should -BeNullOrEmpty + } + + $updatedIssue = $issue | New-GitHubAssignee -Assignee $owner.login + It 'Should have returned the same issue' { + $updatedIssue.number | Should -Be $issue.number + } + + It 'Should have added the requested Assignee to the issue' { + $updatedIssue.assignees.Count | Should -Be 1 + $updatedIssue.assignee.login | Should -Be $owner.login + $updatedIssue.assignees[0].login | Should -Be $owner.login + } + + It 'Should be of the expected type' { + $updatedIssue.PSObject.TypeNames[0] | Should -Be 'GitHub.Issue' + } + + $updatedIssue = $issue | Remove-GitHubAssignee -Assignee $owner.login -Force + It 'Should have returned the same issue' { + $updatedIssue.number | Should -Be $issue.number + } + + It 'Should have added the requested Assignee to the issue' { + $updatedIssue.assignee.login | Should -BeNullOrEmpty + $updatedIssue.assignees | Should -BeNullOrEmpty + } + + It 'Should be of the expected type' { + $updatedIssue.PSObject.TypeNames[0] | Should -Be 'GitHub.Issue' + } + } + + Context 'Adding an assignee with the assignee user object on the pipeline' { + $issue = $repo | New-GitHubIssue -Title "Test issue" + It 'Should have no assignees when created' { + $issue.assignee.login | Should -BeNullOrEmpty + $issue.assignees | Should -BeNullOrEmpty + } + + $updatedIssue = $owner | New-GitHubAssignee -OwnerName $script:ownerName -RepositoryName $repo.name -Issue $issue.number + It 'Should have returned the same issue' { + $updatedIssue.number | Should -Be $issue.number + } + + It 'Should have added the requested Assignee to the issue' { + $updatedIssue.assignees.Count | Should -Be 1 + $updatedIssue.assignee.login | Should -Be $owner.login + $updatedIssue.assignees[0].login | Should -Be $owner.login + } + + It 'Should be of the expected type' { + $updatedIssue.PSObject.TypeNames[0] | Should -Be 'GitHub.Issue' + } + + $updatedIssue = $owner | Remove-GitHubAssignee -OwnerName $script:ownerName -RepositoryName $repo.name -Issue $issue.number -Force + It 'Should have returned the same issue' { + $updatedIssue.number | Should -Be $issue.number + } + + It 'Should have added the requested Assignee to the issue' { + $updatedIssue.assignee.login | Should -BeNullOrEmpty + $updatedIssue.assignees | Should -BeNullOrEmpty + } + + It 'Should be of the expected type' { + $updatedIssue.PSObject.TypeNames[0] | Should -Be 'GitHub.Issue' + } + } + } } finally { diff --git a/Tests/GitHubBranches.tests.ps1 b/Tests/GitHubBranches.tests.ps1 new file mode 100644 index 00000000..f0df1ca9 --- /dev/null +++ b/Tests/GitHubBranches.tests.ps1 @@ -0,0 +1,119 @@ +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. + +<# +.Synopsis + Tests for GitHubBranches.ps1 module +#> + +[CmdletBinding()] +[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseDeclaredVarsMoreThanAssignments', '', + Justification='Suppress false positives in Pester code blocks')] +param() + +# This is common test code setup logic for all Pester test files +$moduleRootPath = Split-Path -Path $PSScriptRoot -Parent +. (Join-Path -Path $moduleRootPath -ChildPath 'Tests\Common.ps1') + +try +{ + Describe 'Getting branches for repository' { + BeforeAll { + $repositoryName = [guid]::NewGuid().Guid + $repo = New-GitHubRepository -RepositoryName $repositoryName -AutoInit + $branchName = 'master' + } + + AfterAll { + $repo | Remove-GitHubRepository -Confirm:$false + } + + Context 'Getting all branches for a repository with parameters' { + $branches = @(Get-GitHubRepositoryBranch -OwnerName $script:ownerName -RepositoryName $repositoryName) + + It 'Should return expected number of repository branches' { + $branches.Count | Should -Be 1 + } + + It 'Should return the name of the expected branch' { + $branches.name | Should -Contain $branchName + } + + It 'Should have the exected type and addititional properties' { + $branches[0].PSObject.TypeNames[0] | Should -Be 'GitHub.Branch' + $branches[0].RepositoryUrl | Should -Be $repo.RepositoryUrl + $branches[0].BranchName | Should -Be $branches[0].name + } + } + + Context 'Getting all branches for a repository with the repo on the pipeline' { + $branches = @($repo | Get-GitHubRepositoryBranch) + + It 'Should return expected number of repository branches' { + $branches.Count | Should -Be 1 + } + + It 'Should return the name of the expected branch' { + $branches.name | Should -Contain $branchName + } + + It 'Should have the exected type and addititional properties' { + $branches[0].PSObject.TypeNames[0] | Should -Be 'GitHub.Branch' + $branches[0].RepositoryUrl | Should -Be $repo.RepositoryUrl + $branches[0].BranchName | Should -Be $branches[0].name + } + } + + Context 'Getting a specific branch for a repository with parameters' { + $branch = Get-GitHubRepositoryBranch -OwnerName $script:ownerName -RepositoryName $repositoryName -BranchName $branchName + + It 'Should return the expected branch name' { + $branch.name | Should -Be $branchName + } + + It 'Should have the exected type and addititional properties' { + $branch.PSObject.TypeNames[0] | Should -Be 'GitHub.Branch' + $branch.RepositoryUrl | Should -Be $repo.RepositoryUrl + $branch.BranchName | Should -Be $branch.name + } + } + + Context 'Getting a specific branch for a repository with the repo on the pipeline' { + $branch = $repo | Get-GitHubRepositoryBranch -BranchName $branchName + + It 'Should return the expected branch name' { + $branch.name | Should -Be $branchName + } + + It 'Should have the exected type and addititional properties' { + $branch.PSObject.TypeNames[0] | Should -Be 'GitHub.Branch' + $branch.RepositoryUrl | Should -Be $repo.RepositoryUrl + $branch.BranchName | Should -Be $branch.name + } + } + + Context 'Getting a specific branch for a repository with the branch object on the pipeline' { + $branch = Get-GitHubRepositoryBranch -OwnerName $script:ownerName -RepositoryName $repositoryName -BranchName $branchName + $branchAgain = $branch | Get-GitHubRepositoryBranch + + It 'Should return the expected branch name' { + $branchAgain.name | Should -Be $branchName + } + + It 'Should have the exected type and addititional properties' { + $branchAgain.PSObject.TypeNames[0] | Should -Be 'GitHub.Branch' + $branchAgain.RepositoryUrl | Should -Be $repo.RepositoryUrl + $branchAgain.BranchName | Should -Be $branchAgain.name + } + } + } +} +finally +{ + if (Test-Path -Path $script:originalConfigFile -PathType Leaf) + { + # Restore the user's configuration to its pre-test state + Restore-GitHubConfiguration -Path $script:originalConfigFile + $script:originalConfigFile = $null + } +} diff --git a/Tests/GitHubComments.tests.ps1 b/Tests/GitHubComments.tests.ps1 deleted file mode 100644 index 66c9242b..00000000 --- a/Tests/GitHubComments.tests.ps1 +++ /dev/null @@ -1,100 +0,0 @@ -# Copyright (c) Microsoft Corporation. All rights reserved. -# Licensed under the MIT License. - -<# -.Synopsis - Tests for GitHubComments.ps1 module -#> - -# This is common test code setup logic for all Pester test files -$moduleRootPath = Split-Path -Path $PSScriptRoot -Parent -. (Join-Path -Path $moduleRootPath -ChildPath 'Tests\Common.ps1') - -try -{ - # Define Script-scoped, readonly, hidden variables. - @{ - defaultIssueTitle = "Test Title" - defaultCommentBody = "This is a test body." - defaultEditedCommentBody = "This is an edited test body." - }.GetEnumerator() | ForEach-Object { - Set-Variable -Force -Scope Script -Option ReadOnly -Visibility Private -Name $_.Key -Value $_.Value - } - - Describe 'Creating, modifying and deleting comments' { - $repo = New-GitHubRepository -RepositoryName ([Guid]::NewGuid().Guid) -AutoInit - - $issue = New-GitHubIssue -Uri $repo.svn_url -Title $defaultIssueTitle - - Context 'For creating a new comment' { - $newComment = New-GitHubComment -Uri $repo.svn_url -Issue $issue.number -Body $defaultCommentBody - $existingComment = Get-GitHubComment -Uri $repo.svn_url -CommentID $newComment.id - - It "Should have the expected body text" { - $existingComment.body | Should be $defaultCommentBody - } - } - - Context 'For getting comments from an issue' { - $existingComments = @(Get-GitHubComment -Uri $repo.svn_url -Issue $issue.number) - - It 'Should have the expected number of comments' { - $existingComments.Count | Should be 1 - } - - It 'Should have the expected body text on the first comment' { - $existingComments[0].body | Should be $defaultCommentBody - } - } - - Context 'For getting comments from an issue with a specific MediaType' { - $existingComments = @(Get-GitHubComment -Uri $repo.svn_url -Issue $issue.number -MediaType 'Html') - - It 'Should have the expected body_html on the first comment' { - $existingComments[0].body_html | Should not be $null - } - } - - Context 'For editing a comment' { - $newComment = New-GitHubComment -Uri $repo.svn_url -Issue $issue.number -Body $defaultCommentBody - $editedComment = Set-GitHubComment -Uri $repo.svn_url -CommentID $newComment.id -Body $defaultEditedCommentBody - - It 'Should have a body that is not equal to the original body' { - $editedComment.body | Should not be $newComment.Body - } - - It 'Should have the edited content' { - $editedComment.body | Should be $defaultEditedCommentBody - } - } - - Context 'For getting comments from a repository and deleting them' { - $existingComments = @(Get-GitHubComment -Uri $repo.svn_url) - - It 'Should have the expected number of comments' { - $existingComments.Count | Should be 2 - } - - foreach($comment in $existingComments) { - Remove-GitHubComment -Uri $repo.svn_url -CommentID $comment.id -Confirm:$false - } - - $existingComments = @(Get-GitHubComment -Uri $repo.svn_url) - - It 'Should have no comments' { - $existingComments.Count | Should be 0 - } - } - - Remove-GitHubRepository -Uri $repo.svn_url -Confirm:$false - } -} -finally -{ - if (Test-Path -Path $script:originalConfigFile -PathType Leaf) - { - # Restore the user's configuration to its pre-test state - Restore-GitHubConfiguration -Path $script:originalConfigFile - $script:originalConfigFile = $null - } -} diff --git a/Tests/GitHubContents.tests.ps1 b/Tests/GitHubContents.tests.ps1 index 7dbf568e..806d373c 100644 --- a/Tests/GitHubContents.tests.ps1 +++ b/Tests/GitHubContents.tests.ps1 @@ -6,6 +6,11 @@ Tests for GitHubContents.ps1 module #> +[CmdletBinding()] +[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseDeclaredVarsMoreThanAssignments', '', + Justification='Suppress false positives in Pester code blocks')] +param() + # This is common test code setup logic for all Pester test files $moduleRootPath = Split-Path -Path $PSScriptRoot -Parent . (Join-Path -Path $moduleRootPath -ChildPath 'Tests\Common.ps1') @@ -29,74 +34,133 @@ try } Describe 'Getting file and folder content' { - # AutoInit will create a readme with the GUID of the repo name - $repo = New-GitHubRepository -RepositoryName ($repoGuid) -AutoInit + BeforeAll { + # AutoInit will create a readme with the GUID of the repo name + $repo = New-GitHubRepository -RepositoryName ($repoGuid) -AutoInit + } - Context 'For getting folder contents' { + AfterAll { + Remove-GitHubRepository -Uri $repo.svn_url -Confirm:$false + } + Context 'For getting folder contents with parameters' { $folderOutput = Get-GitHubContent -OwnerName $script:ownerName -RepositoryName $repo.name It "Should have the expected name" { - $folderOutput.name | Should be "" + $folderOutput.name | Should -BeNullOrEmpty } + It "Should have the expected path" { - $folderOutput.path | Should be "" + $folderOutput.path | Should -BeNullOrEmpty } + It "Should have the expected type" { - $folderOutput.type | Should be "dir" + $folderOutput.type | Should -Be "dir" } + It "Should have the expected entries" { - $folderOutput.entries.length | Should be 1 + $folderOutput.entries.length | Should -Be 1 } + It "Should have the expected entry data" { - $folderOutput.entries[0].name | Should be $readmeFileName - $folderOutput.entries[0].path | Should be $readmeFileName + $folderOutput.entries[0].name | Should -Be $readmeFileName + $folderOutput.entries[0].path | Should -Be $readmeFileName + } + + It "Should have the expected type and additional properties" { + $folderOutput.PSObject.TypeNames[0] | Should -Be 'GitHub.Content' + $folderOutput.RepositoryUrl | Should -Be $repo.RepositoryUrl } } Context 'For getting folder contents via URL' { - $folderOutput = Get-GitHubContent -Uri "https://github.com/$($script:ownerName)/$($repo.name)" It "Should have the expected name" { - $folderOutput.name | Should be "" + $folderOutput.name | Should -BeNullOrEmpty } + It "Should have the expected path" { - $folderOutput.path | Should be "" + $folderOutput.path | Should -BeNullOrEmpty } + It "Should have the expected type" { - $folderOutput.type | Should be "dir" + $folderOutput.type | Should -Be "dir" } + It "Should have the expected entries" { - $folderOutput.entries.length | Should be 1 + $folderOutput.entries.length | Should -Be 1 } + It "Should have the expected entry data" { - $folderOutput.entries[0].name | Should be $readmeFileName - $folderOutput.entries[0].path | Should be $readmeFileName + $folderOutput.entries[0].name | Should -Be $readmeFileName + $folderOutput.entries[0].path | Should -Be $readmeFileName + } + + It "Should have the expected type" { + $folderOutput.PSObject.TypeNames[0] | Should -Be 'GitHub.Content' + $folderOutput.RepositoryUrl | Should -Be $repo.RepositoryUrl } } - Context 'For getting raw (byte) file contents' { + Context 'For getting folder contents with the repo on the pipeline' { + $folderOutput = $repo | Get-GitHubContent + + It "Should have the expected name" { + $folderOutput.name | Should -BeNullOrEmpty + } + + It "Should have the expected path" { + $folderOutput.path | Should -BeNullOrEmpty + } + + It "Should have the expected type" { + $folderOutput.type | Should -Be "dir" + } + + It "Should have the expected entries" { + $folderOutput.entries.length | Should -Be 1 + } + + It "Should have the expected entry data" { + $folderOutput.entries[0].name | Should -Be $readmeFileName + $folderOutput.entries[0].path | Should -Be $readmeFileName + } + It "Should have the expected type" { + $folderOutput.PSObject.TypeNames[0] | Should -Be 'GitHub.Content' + $folderOutput.RepositoryUrl | Should -Be $repo.RepositoryUrl + } + } + + Context 'For getting raw (byte) file contents' { $readmeFileBytes = Get-GitHubContent -OwnerName $script:ownerName -RepositoryName $repo.name -Path $readmeFileName -MediaType Raw $readmeFileString = [System.Text.Encoding]::UTF8.GetString($readmeFileBytes) It "Should have the expected content" { - $readmeFileString | Should be $rawOutput + $readmeFileString | Should -Be $rawOutput + } + + It "Should have the expected type" { + $readmeFileString.PSObject.TypeNames[0] | Should -Not -Be 'GitHub.Content' + $readmeFileString.RepositoryUrl | Should -BeNullOrEmpty } } Context 'For getting raw (string) file contents' { - $readmeFileString = Get-GitHubContent -OwnerName $script:ownerName -RepositoryName $repo.name -Path $readmeFileName -MediaType Raw -ResultAsString It "Should have the expected content" { - $readmeFileString | Should be $rawOutput + $readmeFileString | Should -Be $rawOutput + } + + It "Should have the expected type" { + $readmeFileString.PSObject.TypeNames[0] | Should -Not -Be 'GitHub.Content' + $readmeFileString.RepositoryUrl | Should -BeNullOrEmpty } } Context 'For getting html (byte) file contents' { - $readmeFileBytes = Get-GitHubContent -OwnerName $script:ownerName -RepositoryName $repo.name -Path $readmeFileName -MediaType Html $readmeFileString = [System.Text.Encoding]::UTF8.GetString($readmeFileBytes) @@ -108,10 +172,14 @@ try $readmeNoBreaks.StartsWith($htmlOutputStart) | Should -BeTrue $readmeNoBreaks.IndexOf($repoGuid) | Should -BeGreaterOrEqual 0 } + + It "Should have the expected type" { + $readmeNoBreaks.PSObject.TypeNames[0] | Should -Not -Be 'GitHub.Content' + $readmeNoBreaks.RepositoryUrl | Should -BeNullOrEmpty + } } Context 'For getting html (string) file contents' { - $readmeFileString = Get-GitHubContent -OwnerName $script:ownerName -RepositoryName $repo.name -Path $readmeFileName -MediaType Html -ResultAsString # Replace newlines with empty for comparison purposes @@ -122,55 +190,69 @@ try $readmeNoBreaks.StartsWith($htmlOutputStart) | Should -BeTrue $readmeNoBreaks.IndexOf($repoGuid) | Should -BeGreaterOrEqual 0 } + + It "Should have the expected type" { + $readmeFileString.PSObject.TypeNames[0] | Should -Not -Be 'GitHub.Content' + $readmeFileString.RepositoryUrl | Should -BeNullOrEmpty + } } Context 'For getting object (default) file result' { - $readmeFileObject = Get-GitHubContent -OwnerName $script:ownerName -RepositoryName $repo.name -Path $readmeFileName It "Should have the expected name" { - $readmeFileObject.name | Should be $readmeFileName + $readmeFileObject.name | Should -Be $readmeFileName } + It "Should have the expected path" { - $readmeFileObject.path | Should be $readmeFileName + $readmeFileObject.path | Should -Be $readmeFileName } + It "Should have the expected type" { - $readmeFileObject.type | Should be "file" + $readmeFileObject.type | Should -Be "file" } + It "Should have the expected encoding" { - $readmeFileObject.encoding | Should be "base64" + $readmeFileObject.encoding | Should -Be "base64" } It "Should have the expected content" { # Convert from base64 $readmeFileString = [System.Text.Encoding]::UTF8.GetString([System.Convert]::FromBase64String($readmeFileObject.content)) - $readmeFileString | Should be $rawOutput + $readmeFileString | Should -Be $rawOutput + } + + It "Should have the expected type" { + $readmeFileObject.PSObject.TypeNames[0] | Should -Be 'GitHub.Content' + $readmeFileObject.RepositoryUrl | Should -Be $repo.RepositoryUrl } } Context 'For getting object file result as string' { - $readmeFileObject = Get-GitHubContent -OwnerName $script:ownerName -RepositoryName $repo.name -Path $readmeFileName -MediaType Object -ResultAsString It "Should have the expected name" { - $readmeFileObject.name | Should be $readmeFileName + $readmeFileObject.name | Should -Be $readmeFileName } It "Should have the expected path" { - $readmeFileObject.path | Should be $readmeFileName + $readmeFileObject.path | Should -Be $readmeFileName } It "Should have the expected type" { - $readmeFileObject.type | Should be "file" + $readmeFileObject.type | Should -Be "file" } It "Should have the expected encoding" { - $readmeFileObject.encoding | Should be "base64" + $readmeFileObject.encoding | Should -Be "base64" } It "Should have the expected content" { - $readmeFileObject.contentAsString | Should be $rawOutput + $readmeFileObject.contentAsString | Should -Be $rawOutput } - } - Remove-GitHubRepository -Uri $repo.svn_url -Confirm:$false + It "Should have the expected type" { + $readmeFileObject.PSObject.TypeNames[0] | Should -Be 'GitHub.Content' + $readmeFileObject.RepositoryUrl | Should -Be $repo.RepositoryUrl + } + } } } finally diff --git a/Tests/GitHubCore.Tests.ps1 b/Tests/GitHubCore.Tests.ps1 index 0fab5a3a..cb6c3533 100644 --- a/Tests/GitHubCore.Tests.ps1 +++ b/Tests/GitHubCore.Tests.ps1 @@ -6,6 +6,11 @@ Tests for GitHubCore.ps1 module #> +[CmdletBinding()] +[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseDeclaredVarsMoreThanAssignments', '', + Justification='Suppress false positives in Pester code blocks')] +param() + # This is common test code setup logic for all Pester test files $moduleRootPath = Split-Path -Path $PSScriptRoot -Parent . (Join-Path -Path $moduleRootPath -ChildPath 'Tests\Common.ps1') @@ -28,7 +33,7 @@ try It 'Should return the same values' { $originalJson = (ConvertTo-Json -InputObject $original -Depth $jsonConversionDepth) $convertedJson = (ConvertTo-Json -InputObject $converted -Depth $jsonConversionDepth) - $originalJson -eq $convertedJson | Should be $true + $originalJson -eq $convertedJson | Should -Be $true } } @@ -48,7 +53,7 @@ try It 'Should return the correct values' { $originalJson = (ConvertTo-Json -InputObject $original -Depth $jsonConversionDepth) $convertedJson = (ConvertTo-Json -InputObject $converted -Depth $jsonConversionDepth) - $originalJson -eq $convertedJson | Should be $true + $originalJson -eq $convertedJson | Should -Be $true } } @@ -78,26 +83,26 @@ try $converted = ConvertTo-SmarterObject -InputObject $original It 'Should convert the value to a [DateTime]' { - $converted.closed_at -is [DateTime] | Should be $true - $converted.committed_at -is [DateTime] | Should be $true - $converted.completed_at -is [DateTime] | Should be $true - $converted.created_at -is [DateTime] | Should be $true - $converted.date -is [DateTime] | Should be $true - $converted.due_on -is [DateTime] | Should be $true - $converted.last_edited_at -is [DateTime] | Should be $true - $converted.last_read_at -is [DateTime] | Should be $true - $converted.merged_at -is [DateTime] | Should be $true - $converted.published_at -is [DateTime] | Should be $true - $converted.pushed_at -is [DateTime] | Should be $true - $converted.starred_at -is [DateTime] | Should be $true - $converted.started_at -is [DateTime] | Should be $true - $converted.submitted_at -is [DateTime] | Should be $true - $converted.timestamp -is [DateTime] | Should be $true - $converted.updated_at -is [DateTime] | Should be $true + $converted.closed_at -is [DateTime] | Should -Be $true + $converted.committed_at -is [DateTime] | Should -Be $true + $converted.completed_at -is [DateTime] | Should -Be $true + $converted.created_at -is [DateTime] | Should -Be $true + $converted.date -is [DateTime] | Should -Be $true + $converted.due_on -is [DateTime] | Should -Be $true + $converted.last_edited_at -is [DateTime] | Should -Be $true + $converted.last_read_at -is [DateTime] | Should -Be $true + $converted.merged_at -is [DateTime] | Should -Be $true + $converted.published_at -is [DateTime] | Should -Be $true + $converted.pushed_at -is [DateTime] | Should -Be $true + $converted.starred_at -is [DateTime] | Should -Be $true + $converted.started_at -is [DateTime] | Should -Be $true + $converted.submitted_at -is [DateTime] | Should -Be $true + $converted.timestamp -is [DateTime] | Should -Be $true + $converted.updated_at -is [DateTime] | Should -Be $true } It 'Should NOT convert the value to a [DateTime] if it''s not a known property' { - $converted.prop1 -is [DateTime] | Should be $false + $converted.prop1 -is [DateTime] | Should -Be $false } } @@ -124,22 +129,22 @@ try $converted = ConvertTo-SmarterObject -InputObject $original It 'Should keep the existing value' { - $original.closed_at -eq $converted.closed_at | Should be $true - $original.committed_at -eq $converted.committed_at | Should be $true - $original.completed_at -eq $converted.completed_at | Should be $true - $original.created_at -eq $converted.created_at | Should be $true - $original.date -eq $converted.date | Should be $true - $original.due_on -eq $converted.due_on | Should be $true - $original.last_edited_at -eq $converted.last_edited_at | Should be $true - $original.last_read_at -eq $converted.last_read_at | Should be $true - $original.merged_at -eq $converted.merged_at | Should be $true - $original.published_at -eq $converted.published_at | Should be $true - $original.pushed_at -eq $converted.pushed_at | Should be $true - $original.starred_at -eq $converted.starred_at | Should be $true - $original.started_at -eq $converted.started_at | Should be $true - $original.submitted_at -eq $converted.submitted_at | Should be $true - $original.timestamp -eq $converted.timestamp | Should be $true - $original.updated_at -eq $converted.updated_at | Should be $true + $original.closed_at -eq $converted.closed_at | Should -Be $true + $original.committed_at -eq $converted.committed_at | Should -Be $true + $original.completed_at -eq $converted.completed_at | Should -Be $true + $original.created_at -eq $converted.created_at | Should -Be $true + $original.date -eq $converted.date | Should -Be $true + $original.due_on -eq $converted.due_on | Should -Be $true + $original.last_edited_at -eq $converted.last_edited_at | Should -Be $true + $original.last_read_at -eq $converted.last_read_at | Should -Be $true + $original.merged_at -eq $converted.merged_at | Should -Be $true + $original.published_at -eq $converted.published_at | Should -Be $true + $original.pushed_at -eq $converted.pushed_at | Should -Be $true + $original.starred_at -eq $converted.starred_at | Should -Be $true + $original.started_at -eq $converted.started_at | Should -Be $true + $original.submitted_at -eq $converted.submitted_at | Should -Be $true + $original.timestamp -eq $converted.timestamp | Should -Be $true + $original.updated_at -eq $converted.updated_at | Should -Be $true } } @@ -156,7 +161,7 @@ try It 'Should still be an empty array after conversion' { $originalJson = (ConvertTo-Json -InputObject $original -Depth $jsonConversionDepth) $convertedJson = (ConvertTo-Json -InputObject $converted -Depth $jsonConversionDepth) - $originalJson -eq $convertedJson | Should be $true + $originalJson -eq $convertedJson | Should -Be $true } } @@ -173,7 +178,7 @@ try It 'Should still be a single item array after conversion' { $originalJson = (ConvertTo-Json -InputObject $original -Depth $jsonConversionDepth) $convertedJson = (ConvertTo-Json -InputObject $converted -Depth $jsonConversionDepth) - $originalJson -eq $convertedJson | Should be $true + $originalJson -eq $convertedJson | Should -Be $true } } @@ -190,11 +195,56 @@ try It 'Should still be a multi item array after conversion' { $originalJson = (ConvertTo-Json -InputObject $original -Depth $jsonConversionDepth) $convertedJson = (ConvertTo-Json -InputObject $converted -Depth $jsonConversionDepth) - $originalJson -eq $convertedJson | Should be $true + $originalJson -eq $convertedJson | Should -Be $true } } } } + + Describe 'Testing Split-GitHubUri' { + BeforeAll { + $repositoryName = [guid]::NewGuid().Guid + $url = "https://github.com/$script:ownerName/$repositoryName" + } + + Context 'For getting the OwnerName' { + It 'Should return expected repository name' { + $name = Split-GitHubUri -Uri $url -RepositoryName + $name | Should -Be $repositoryName + } + + It 'Should return expected repository name with the pipeline' { + $name = $url | Split-GitHubUri -RepositoryName + $name | Should -Be $repositoryName + } + } + + Context 'For getting the RepositoryName' { + It 'Should return expected owner name' { + $name = Split-GitHubUri -Uri $url -OwnerName + $name | Should -Be $script:ownerName + } + + It 'Should return expected owner name with the pipeline' { + $owner = $url | Split-GitHubUri -OwnerName + $owner | Should -Be $script:ownerName + } + } + + Context 'For getting both the OwnerName and the RepositoryName' { + It 'Should return both OwnerName and RepositoryName' { + $elements = Split-GitHubUri -Uri $url + $elements.ownerName | Should -Be $script:ownerName + $elements.repositoryName | Should -Be $repositoryName + } + + It 'Should return both OwnerName and RepositoryName with the pipeline' { + $elements = $url | Split-GitHubUri + $elements.ownerName | Should -Be $script:ownerName + $elements.repositoryName | Should -Be $repositoryName + } + } + } } finally { diff --git a/Tests/GitHubEvents.tests.ps1 b/Tests/GitHubEvents.tests.ps1 index 4411d640..43390e7a 100644 --- a/Tests/GitHubEvents.tests.ps1 +++ b/Tests/GitHubEvents.tests.ps1 @@ -6,83 +6,132 @@ Tests for GitHubEvents.ps1 module #> +[CmdletBinding()] +[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseDeclaredVarsMoreThanAssignments', '', + Justification='Suppress false positives in Pester code blocks')] +param() + # This is common test code setup logic for all Pester test files $moduleRootPath = Split-Path -Path $PSScriptRoot -Parent . (Join-Path -Path $moduleRootPath -ChildPath 'Tests\Common.ps1') try { - if ($accessTokenConfigured) - { - Describe 'Getting events from repository' { + Describe 'Getting events from repository' { + BeforeAll { $repositoryName = [Guid]::NewGuid() - $null = New-GitHubRepository -RepositoryName $repositoryName + $repo = New-GitHubRepository -RepositoryName $repositoryName + } + + AfterAll { + $null = $repo | Remove-GitHubRepository -Force + } - Context 'For getting events from a new repository' { - $events = @(Get-GitHubEvent -OwnerName $ownerName -RepositoryName $repositoryName) + Context 'For getting events from a new repository (via parameter)' { + $events = @(Get-GitHubEvent -OwnerName $ownerName -RepositoryName $repositoryName) - It 'Should have no events' { - $events.Count | Should be 0 - } + It 'Should have no events (via parameter)' { + $events.Count | Should -Be 0 } + } + + Context 'For getting events from a new repository (via pipeline)' { + $events = @($repo | Get-GitHubEvent) - $issue = New-GithubIssue -OwnerName $ownerName -RepositoryName $repositoryName -Title "New Issue" - Update-GitHubIssue -OwnerName $ownerName -RepositoryName $repositoryName -Issue $issue.number -State Closed + It 'Should have no events (via parameter)' { + $events.Count | Should -Be 0 + } + } - Context 'For getting events from a repository' { - $events = @(Get-GitHubEvent -OwnerName $ownerName -RepositoryName $repositoryName) + Context 'For getting Issue events from a repository' { + $issue = $repo | New-GitHubIssue -Title 'New Issue' + $issue = $issue | Update-GitHubIssue -State Closed + $events = @($repo | Get-GitHubEvent) - It 'Should have an event from closing an issue' { - $events.Count | Should be 1 - } + It 'Should have an event from closing an issue' { + $events.Count | Should -Be 1 } - $null = Remove-GitHubRepository -OwnerName $ownerName -RepositoryName $repositoryName -Confirm:$false + It 'Should have the expected type and additional properties' { + $events[0].PSObject.TypeNames[0] | Should -Be 'GitHub.Event' + $events[0].issue.PSObject.TypeNames[0] | Should -Be 'GitHub.Issue' + $events[0].IssueId | Should -Be $events[0].issue.id + $events[0].IssueNumber | Should -Be $events[0].issue.number + $events[0].actor.PSObject.TypeNames[0] | Should -Be 'GitHub.User' + } } + } - Describe 'Getting events from an issue' { + Describe 'Getting events from an issue' { + BeforeAll { $repositoryName = [Guid]::NewGuid() - $null = New-GitHubRepository -RepositoryName $repositoryName - $issue = New-GithubIssue -OwnerName $ownerName -RepositoryName $repositoryName -Title "New Issue" + $repo = New-GitHubRepository -RepositoryName $repositoryName + $issue = New-GitHubIssue -OwnerName $ownerName -RepositoryName $repositoryName -Title "New Issue" + } + + AfterAll { + $repo | Remove-GitHubRepository -Confirm:$false + } - Context 'For getting events from a new issue' { - $events = @(Get-GitHubEvent -OwnerName $ownerName -RepositoryName $repositoryName -Issue $issue.number) + Context 'For getting events from a new issue' { + $events = @(Get-GitHubEvent -OwnerName $ownerName -RepositoryName $repositoryName -Issue $issue.number) - It 'Should have no events' { - $events.Count | Should be 0 - } + It 'Should have no events' { + $events.Count | Should -Be 0 } + } - Context 'For getting events from an issue' { - Update-GitHubIssue -OwnerName $ownerName -RepositoryName $repositoryName -Issue $issue.number -State Closed - Update-GitHubIssue -OwnerName $ownerName -RepositoryName $repositoryName -Issue $issue.number -State Open - $events = @(Get-GitHubEvent -OwnerName $ownerName -RepositoryName $repositoryName) + Context 'For getting events from an issue' { + $issue = $issue | Update-GitHubIssue -State Closed + $issue = $issue | Update-GitHubIssue -State Open + $events = @(Get-GitHubEvent -OwnerName $ownerName -RepositoryName $repositoryName) - It 'Should have two events from closing and opening the issue' { - $events.Count | Should be 2 - } + It 'Should have two events from closing and opening the issue' { + $events.Count | Should -Be 2 } - - $null = Remove-GitHubRepository -OwnerName $ownerName -RepositoryName $repositoryName -Confirm:$false } - Describe 'Getting an event directly' { + } + + Describe 'Getting an event directly' { + BeforeAll { $repositoryName = [Guid]::NewGuid() - $null = New-GitHubRepository -RepositoryName $repositoryName - $issue = New-GithubIssue -OwnerName $ownerName -RepositoryName $repositoryName -Title "New Issue" - Update-GitHubIssue -OwnerName $ownerName -RepositoryName $repositoryName -Issue $issue.number -State Closed - Update-GitHubIssue -OwnerName $ownerName -RepositoryName $repositoryName -Issue $issue.number -State Open - $events = @(Get-GitHubEvent -OwnerName $ownerName -RepositoryName $repositoryName) + $repo = New-GitHubRepository -RepositoryName $repositoryName + $issue = $repo | New-GitHubIssue -Title 'New Issue' + $issue = $issue | Update-GitHubIssue -State Closed + $issue = $issue | Update-GitHubIssue -State Open + $events = @($repo | Get-GitHubEvent) + } + + AfterAll { + $repo | Remove-GitHubRepository -Confirm:$false + } - Context 'For getting an event directly'{ - $singleEvent = Get-GitHubEvent -OwnerName $ownerName -RepositoryName $repositoryName -EventID $events[0].id + Context 'For getting a single event directly by parameter'{ + $singleEvent = Get-GitHubEvent -OwnerName $ownerName -RepositoryName $repositoryName -EventID $events[0].id - It 'Should have the correct event type'{ - $singleEvent.event | Should be 'reopened' - } + It 'Should have the correct event type' { + $singleEvent.event | Should -Be 'reopened' + } + } + + Context 'For getting a single event directly by pipeline'{ + $singleEvent = $events[0] | Get-GitHubEvent + + It 'Should have the expected event type' { + $singleEvent.event | Should -Be $events[0].event } - $null = Remove-GitHubRepository -OwnerName $ownerName -RepositoryName $repositoryName -Confirm:$false + It 'Should have the same id' { + $singleEvent.id | Should -Be $events[0].id + } + + It 'Should have the expected type and additional properties' { + $singleEvent.PSObject.TypeNames[0] | Should -Be 'GitHub.Event' + $singleEvent.RepositoryUrl | Should -Be $repo.RepositoryUrl + $singleEvent.EventId | Should -Be $singleEvent.id + $singleEvent.actor.PSObject.TypeNames[0] | Should -Be 'GitHub.User' + } } } } diff --git a/Tests/GitHubIssueComments.tests.ps1 b/Tests/GitHubIssueComments.tests.ps1 new file mode 100644 index 00000000..d5a82873 --- /dev/null +++ b/Tests/GitHubIssueComments.tests.ps1 @@ -0,0 +1,251 @@ +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. + +<# +.Synopsis + Tests for GitHubComments.ps1 module +#> + +[CmdletBinding()] +[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseDeclaredVarsMoreThanAssignments', '', + Justification='Suppress false positives in Pester code blocks')] +param() + +# This is common test code setup logic for all Pester test files +$moduleRootPath = Split-Path -Path $PSScriptRoot -Parent +. (Join-Path -Path $moduleRootPath -ChildPath 'Tests\Common.ps1') + +try +{ + # Define Script-scoped, readonly, hidden variables. + @{ + defaultIssueTitle = "Test Title" + defaultCommentBody = "This is a test body." + defaultEditedCommentBody = "This is an edited test body." + }.GetEnumerator() | ForEach-Object { + Set-Variable -Force -Scope Script -Option ReadOnly -Visibility Private -Name $_.Key -Value $_.Value + } + + Describe 'Creating, modifying and deleting comments' { + BeforeAll { + $repo = New-GitHubRepository -RepositoryName ([Guid]::NewGuid().Guid) -AutoInit + $issue = $repo | New-GitHubIssue -Title $defaultIssueTitle + } + + AfterAll { + $repo | Remove-GitHubRepository -Confirm:$false + } + + Context 'With parameters' { + $comment = New-GitHubIssueComment -OwnerName $script:ownerName -RepositoryName $repo.name -Issue $issue.number -Body $defaultCommentBody + It 'Should have the expected body text' { + $comment.body | Should -Be $defaultCommentBody + } + + It 'Should have the expected type and additional properties' { + $comment.PSObject.TypeNames[0] | Should -Be 'GitHub.IssueComment' + $comment.PSObject.TypeNames[1] | Should -Be 'GitHub.Comment' + $comment.RepositoryUrl | Should -Be $repo.RepositoryUrl + $comment.CommentId | Should -Be $comment.id + $comment.user.PSObject.TypeNames[0] | Should -Be 'GitHub.User' + } + + $result = Get-GitHubIssueComment -OwnerName $script:ownerName -RepositoryName $repo.name -Comment $comment.id + It 'Should be the expected comment' { + $result.id | Should -Be $comment.id + } + + It 'Should have the expected type and additional properties' { + $result.PSObject.TypeNames[0] | Should -Be 'GitHub.IssueComment' + $result.PSObject.TypeNames[1] | Should -Be 'GitHub.Comment' + $result.RepositoryUrl | Should -Be $repo.RepositoryUrl + $result.CommentId | Should -Be $result.id + $result.user.PSObject.TypeNames[0] | Should -Be 'GitHub.User' + } + + $commentId = $result.id + $updated = Set-GitHubIssueComment -OwnerName $script:ownerName -RepositoryName $repo.name -Comment $commentId -Body $defaultEditedCommentBody + It 'Should have modified the expected comment' { + $updated.id | Should -Be $commentId + } + + It 'Should have the expected body text' { + $updated.body | Should -Be $defaultEditedCommentBody + } + + It 'Should have the expected type and additional properties' { + $updated.PSObject.TypeNames[0] | Should -Be 'GitHub.IssueComment' + $updated.PSObject.TypeNames[1] | Should -Be 'GitHub.Comment' + $updated.RepositoryUrl | Should -Be $repo.RepositoryUrl + $updated.CommentId | Should -Be $updated.id + $updated.user.PSObject.TypeNames[0] | Should -Be 'GitHub.User' + } + + Remove-GitHubIssueComment -OwnerName $script:ownerName -RepositoryName $repo.name -Comment $commentId -Force + It 'Should have been removed' { + { Get-GitHubIssueComment -OwnerName $script:ownerName -RepositoryName $repo.name -Comment $commentId } | Should -Throw + } + } + + Context 'With the repo on the pipeline' { + $comment = $repo | New-GitHubIssueComment -Issue $issue.number -Body $defaultCommentBody + It 'Should have the expected body text' { + $comment.body | Should -Be $defaultCommentBody + } + + It 'Should have the expected type and additional properties' { + $comment.PSObject.TypeNames[0] | Should -Be 'GitHub.IssueComment' + $comment.PSObject.TypeNames[1] | Should -Be 'GitHub.Comment' + $comment.RepositoryUrl | Should -Be $repo.RepositoryUrl + $comment.CommentId | Should -Be $comment.id + $comment.user.PSObject.TypeNames[0] | Should -Be 'GitHub.User' + } + + $result = $repo | Get-GitHubIssueComment -Comment $comment.id + It 'Should be the expected comment' { + $result.id | Should -Be $comment.id + } + + It 'Should have the expected type and additional properties' { + $result.PSObject.TypeNames[0] | Should -Be 'GitHub.IssueComment' + $result.PSObject.TypeNames[1] | Should -Be 'GitHub.Comment' + $result.RepositoryUrl | Should -Be $repo.RepositoryUrl + $result.CommentId | Should -Be $result.id + $result.user.PSObject.TypeNames[0] | Should -Be 'GitHub.User' + } + + $commentId = $result.id + $updated = $repo | Set-GitHubIssueComment -Comment $commentId -Body $defaultEditedCommentBody + It 'Should have modified the expected comment' { + $updated.id | Should -Be $commentId + } + + It 'Should have the expected body text' { + $updated.body | Should -Be $defaultEditedCommentBody + } + + It 'Should have the expected type and additional properties' { + $updated.PSObject.TypeNames[0] | Should -Be 'GitHub.IssueComment' + $updated.PSObject.TypeNames[1] | Should -Be 'GitHub.Comment' + $updated.RepositoryUrl | Should -Be $repo.RepositoryUrl + $updated.CommentId | Should -Be $updated.id + $updated.user.PSObject.TypeNames[0] | Should -Be 'GitHub.User' + } + + $repo | Remove-GitHubIssueComment -Comment $commentId -Force + It 'Should have been removed' { + { $repo | Get-GitHubIssueComment -Comment $commentId } | Should -Throw + } + } + + Context 'With the issue on the pipeline' { + $comment = $issue | New-GitHubIssueComment -Body $defaultCommentBody + It 'Should have the expected body text' { + $comment.body | Should -Be $defaultCommentBody + } + + It 'Should have the expected type and additional properties' { + $comment.PSObject.TypeNames[0] | Should -Be 'GitHub.IssueComment' + $comment.PSObject.TypeNames[1] | Should -Be 'GitHub.Comment' + $comment.RepositoryUrl | Should -Be $repo.RepositoryUrl + $comment.CommentId | Should -Be $comment.id + $comment.user.PSObject.TypeNames[0] | Should -Be 'GitHub.User' + } + + $result = Get-GitHubIssueComment -OwnerName $script:ownerName -RepositoryName $repo.name -Comment $comment.id + It 'Should be the expected comment' { + $result.id | Should -Be $comment.id + } + + It 'Should have the expected type and additional properties' { + $result.PSObject.TypeNames[0] | Should -Be 'GitHub.IssueComment' + $result.PSObject.TypeNames[1] | Should -Be 'GitHub.Comment' + $result.RepositoryUrl | Should -Be $repo.RepositoryUrl + $result.CommentId | Should -Be $result.id + $result.user.PSObject.TypeNames[0] | Should -Be 'GitHub.User' + } + + $commentId = $result.id + $updated = Set-GitHubIssueComment -OwnerName $script:ownerName -RepositoryName $repo.name -Comment $commentId -Body $defaultEditedCommentBody + It 'Should have modified the expected comment' { + $updated.id | Should -Be $commentId + } + + It 'Should have the expected body text' { + $updated.body | Should -Be $defaultEditedCommentBody + } + + It 'Should have the expected type and additional properties' { + $updated.PSObject.TypeNames[0] | Should -Be 'GitHub.IssueComment' + $updated.PSObject.TypeNames[1] | Should -Be 'GitHub.Comment' + $updated.RepositoryUrl | Should -Be $repo.RepositoryUrl + $updated.CommentId | Should -Be $updated.id + $updated.user.PSObject.TypeNames[0] | Should -Be 'GitHub.User' + } + + Remove-GitHubIssueComment -OwnerName $script:ownerName -RepositoryName $repo.name -Comment $commentId -Force + It 'Should have been removed' { + { Get-GitHubIssueComment -OwnerName $script:ownerName -RepositoryName $repo.name -Comment $commentId } | Should -Throw + } + } + + Context 'With the comment object on the pipeline' { + $comment = New-GitHubIssueComment -OwnerName $script:ownerName -RepositoryName $repo.name -Issue $issue.number -Body $defaultCommentBody + It 'Should have the expected body text' { + $comment.body | Should -Be $defaultCommentBody + } + + It 'Should have the expected type and additional properties' { + $comment.PSObject.TypeNames[0] | Should -Be 'GitHub.IssueComment' + $comment.PSObject.TypeNames[1] | Should -Be 'GitHub.Comment' + $comment.RepositoryUrl | Should -Be $repo.RepositoryUrl + $comment.CommentId | Should -Be $comment.id + $comment.user.PSObject.TypeNames[0] | Should -Be 'GitHub.User' + } + + $result = $comment | Get-GitHubIssueComment + It 'Should be the expected comment' { + $result.id | Should -Be $comment.id + } + + It 'Should have the expected type and additional properties' { + $result.PSObject.TypeNames[0] | Should -Be 'GitHub.IssueComment' + $result.PSObject.TypeNames[1] | Should -Be 'GitHub.Comment' + $result.RepositoryUrl | Should -Be $repo.RepositoryUrl + $result.CommentId | Should -Be $result.id + $result.user.PSObject.TypeNames[0] | Should -Be 'GitHub.User' + } + + $updated = $comment | Set-GitHubIssueComment -Body $defaultEditedCommentBody + It 'Should have modified the expected comment' { + $updated.id | Should -Be $comment.id + } + + It 'Should have the expected body text' { + $updated.body | Should -Be $defaultEditedCommentBody + } + + It 'Should have the expected type and additional properties' { + $updated.PSObject.TypeNames[0] | Should -Be 'GitHub.IssueComment' + $updated.PSObject.TypeNames[1] | Should -Be 'GitHub.Comment' + $updated.RepositoryUrl | Should -Be $repo.RepositoryUrl + $updated.CommentId | Should -Be $updated.id + $updated.user.PSObject.TypeNames[0] | Should -Be 'GitHub.User' + } + + $comment | Remove-GitHubIssueComment -Force + It 'Should have been removed' { + { $comment | Get-GitHubIssueComment } | Should -Throw + } + } + } +} +finally +{ + if (Test-Path -Path $script:originalConfigFile -PathType Leaf) + { + # Restore the user's configuration to its pre-test state + Restore-GitHubConfiguration -Path $script:originalConfigFile + $script:originalConfigFile = $null + } +} diff --git a/Tests/GitHubIssues.tests.ps1 b/Tests/GitHubIssues.tests.ps1 new file mode 100644 index 00000000..21b4dd30 --- /dev/null +++ b/Tests/GitHubIssues.tests.ps1 @@ -0,0 +1,617 @@ +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. + +<# +.Synopsis + Tests for GitHubIssues.ps1 module +#> + +[CmdletBinding()] +[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseDeclaredVarsMoreThanAssignments', '', + Justification='Suppress false positives in Pester code blocks')] +param() + +# This is common test code setup logic for all Pester test files +$moduleRootPath = Split-Path -Path $PSScriptRoot -Parent +. (Join-Path -Path $moduleRootPath -ChildPath 'Tests\Common.ps1') + +try +{ + Describe 'Getting issues for a repository' { + BeforeAll { + $repo = New-GitHubRepository -RepositoryName ([Guid]::NewGuid().Guid) -AutoInit + } + + AfterAll { + Remove-GitHubRepository -Uri $repo.RepositoryUrl -Confirm:$false + } + + Context 'Getting all issues for a repository with parameters' { + $currentIssues = @(Get-GitHubIssue -OwnerName $script:ownerName -RepositoryName $repo.name) + + $numIssues = 2 + 1..$numIssues | + ForEach-Object { New-GitHubIssue -OwnerName $script:ownerName -RepositoryName $repo.name -Title ([Guid]::NewGuid().Guid) } | + Out-Null + + $issues = @(Get-GitHubIssue -OwnerName $script:ownerName -RepositoryName $repo.name) + It 'Should return expected number of issues' { + $issues.Count | Should -Be ($numIssues + $currentIssues.Count) + } + } + + Context 'Getting all issues for a repository with the repo on the pipeline' { + $currentIssues = @($repo | Get-GitHubIssue) + + $numIssues = 2 + 1..$numIssues | + ForEach-Object { $repo | New-GitHubIssue -Title ([Guid]::NewGuid().Guid) } | + Out-Null + + $issues = @($repo | Get-GitHubIssue) + It 'Should return expected number of issues' { + $issues.Count | Should -Be ($numIssues + $currentIssues.Count) + } + } + + Context 'Getting a specific issue with parameters' { + $issue = New-GitHubIssue -OwnerName $script:ownerName -RepositoryName $repo.name -Title ([Guid]::NewGuid().Guid) + + $result = Get-GitHubIssue -OwnerName $script:ownerName -RepositoryName $repo.name -Issue $issue.number + It 'Should be the expected Issue' { + $result.id | Should -Be $issue.id + } + + It 'Should have the expected property values' { + $result.user.login | Should -Be $script:ownerName + $result.labels | Should -BeNullOrEmpty + $result.milestone | Should -BeNullOrEmpty + $result.assignee | Should -BeNullOrEmpty + $result.assignees | Should -BeNullOrEmpty + $result.closed_by | Should -BeNullOrEmpty + $result.repository | Should -BeNullOrEmpty + } + + It 'Should have the expected type and additional properties' { + $result.PSObject.TypeNames[0] | Should -Be 'GitHub.Issue' + $result.RepositoryUrl | Should -Be $repo.RepositoryUrl + $result.IssueId | Should -Be $result.id + $result.IssueNumber | Should -Be $result.number + $result.user.PSObject.TypeNames[0] | Should -Be 'GitHub.User' + } + } + + Context 'Getting a specific issue with the repo on the pipeline' { + $issue = $repo | New-GitHubIssue -Title ([Guid]::NewGuid().Guid) + + $result = $repo | Get-GitHubIssue -Issue $issue.number + It 'Should be the expected Issue' { + $result.id | Should -Be $issue.id + } + + It 'Should have the expected property values' { + $result.user.login | Should -Be $script:ownerName + $result.labels | Should -BeNullOrEmpty + $result.milestone | Should -BeNullOrEmpty + $result.assignee | Should -BeNullOrEmpty + $result.assignees | Should -BeNullOrEmpty + $result.closed_by | Should -BeNullOrEmpty + $result.repository | Should -BeNullOrEmpty + } + + It 'Should have the expected type and additional properties' { + $result.PSObject.TypeNames[0] | Should -Be 'GitHub.Issue' + $result.RepositoryUrl | Should -Be $repo.RepositoryUrl + $result.IssueId | Should -Be $result.id + $result.IssueNumber | Should -Be $result.number + $result.user.PSObject.TypeNames[0] | Should -Be 'GitHub.User' + } + } + + Context 'Getting a specific issue with the issue on the pipeline' { + $issue = $repo | New-GitHubIssue -Title ([Guid]::NewGuid().Guid) + + $result = $issue | Get-GitHubIssue -Issue $issue.number + It 'Should be the expected Issue' { + $result.id | Should -Be $issue.id + } + + It 'Should have the expected property values' { + $result.user.login | Should -Be $script:ownerName + $result.labels | Should -BeNullOrEmpty + $result.milestone | Should -BeNullOrEmpty + $result.assignee | Should -BeNullOrEmpty + $result.assignees | Should -BeNullOrEmpty + $result.closed_by | Should -BeNullOrEmpty + $result.repository | Should -BeNullOrEmpty + } + + It 'Should have the expected type and additional properties' { + $result.PSObject.TypeNames[0] | Should -Be 'GitHub.Issue' + $result.RepositoryUrl | Should -Be $repo.RepositoryUrl + $result.IssueId | Should -Be $result.id + $result.IssueNumber | Should -Be $result.number + $result.user.PSObject.TypeNames[0] | Should -Be 'GitHub.User' + } + } + + Context 'When issues are retrieved with a specific MediaTypes' { + $newIssue = New-GitHubIssue -OwnerName $script:ownerName -RepositoryName $repo.name -Title ([guid]::NewGuid()) -Body ([Guid]::NewGuid()) + + $issues = @(Get-GitHubIssue -Uri $repo.svn_url -Issue $newIssue.number -MediaType 'Html') + It 'Should return an issue with body_html' { + $issues[0].body_html | Should -Not -Be $null + } + } + } + + Describe 'Date-specific Issue tests' { + BeforeAll { + $repo = New-GitHubRepository -RepositoryName ([Guid]::NewGuid().Guid) -AutoInit + } + + AfterAll { + Remove-GitHubRepository -Uri $repo.RepositoryUrl -Confirm:$false + } + + Context 'Date specific scenarios' { + $existingIssues = @($repo | Get-GitHubIssue -State All) + + $newIssues = @() + for ($i = 0; $i -lt 4; $i++) + { + $newIssues += New-GitHubIssue -OwnerName $script:ownerName -RepositoryName $repo.name -Title ([Guid]::NewGuid().Guid) + Start-Sleep -Seconds 1 # Needed to ensure that there is a unique creation timestamp between issues + } + + $newIssues[0] = Update-GitHubIssue -OwnerName $script:ownerName -RepositoryName $repo.name -Issue $newIssues[0].number -State Closed + $newIssues[-1] = Update-GitHubIssue -OwnerName $script:ownerName -RepositoryName $repo.name -Issue $newIssues[-1].number -State Closed + + $existingOpenIssues = @($existingIssues | Where-Object { $_.state -eq 'open' }) + $newOpenIssues = @($newIssues | Where-Object { $_.state -eq 'open' }) + $issues = @($repo | Get-GitHubIssue) + It 'Should return only open issues' { + $issues.Count | Should -Be ($newOpenIssues.Count + $existingOpenIssues.Count) + } + + $issues = @($repo | Get-GitHubIssue -State All) + It 'Should return all issues' { + $issues.Count | Should -Be ($newIssues.Count + $existingIssues.Count) + } + + $createdOnOrAfterDate = Get-Date -Date $newIssues[0].created_at + $createdOnOrBeforeDate = Get-Date -Date $newIssues[2].created_at + $issues = @(($repo | Get-GitHubIssue) | + Where-Object { ($_.created_at -ge $createdOnOrAfterDate) -and ($_.created_at -le $createdOnOrBeforeDate) }) + + It 'Smart object date conversion works for comparing dates' { + $issues.Count | Should -Be 2 + } + + $createdDate = Get-Date -Date $newIssues[1].created_at + $issues = @(Get-GitHubIssue -Uri $repo.svn_url -State All | + Where-Object { ($_.created_at -ge $createdDate) -and ($_.state -eq 'closed') }) + + It 'Able to filter based on date and state' { + $issues.Count | Should -Be 1 + } + } + } + Describe 'Creating issues' { + BeforeAll { + $repo = New-GitHubRepository -RepositoryName ([Guid]::NewGuid().Guid) -AutoInit + $milestone = $repo | New-GitHubMilestone -Title ([Guid]::NewGuid().Guid) + } + + AfterAll { + Remove-GitHubRepository -Uri $repo.RepositoryUrl -Confirm:$false + } + + Context 'Creating an Issue with parameters' { + $params = @{ + 'OwnerName' = $script:ownerName + 'RepositoryName' = $repo.name + 'Title' = '-issue title-' + 'Body' = '-issue body-' + 'Assignee' = $script:ownerName + 'Milestone' = $milestone.number + 'Label' = 'bug' + 'MediaType' = 'Raw' + } + + $issue = New-GitHubIssue @params + + It 'Should have the expected property values' { + $issue.title | Should -Be $params.Title + $issue.body | Should -Be $params.Body + $issue.assignee.login | Should -Be $params.Assignee + $issue.assignees.Count | Should -Be 1 + $issue.assignees[0].login | Should -Be $params.Assignee + $issue.milestone.number | Should -Be $params.Milestone + $issue.labels.Count | Should -Be 1 + $issue.labels[0].name | Should -Contain $params.Label + } + + It 'Should have the expected type and additional properties' { + $issue.PSObject.TypeNames[0] | Should -Be 'GitHub.Issue' + $issue.RepositoryUrl | Should -Be $repo.RepositoryUrl + $issue.IssueId | Should -Be $issue.id + $issue.IssueNumber | Should -Be $issue.number + $issue.user.PSObject.TypeNames[0] | Should -Be 'GitHub.User' + $issue.assignee.PSObject.TypeNames[0] | Should -Be 'GitHub.User' + $issue.assignees[0].PSObject.TypeNames[0] | Should -Be 'GitHub.User' + $issue.milestone.PSObject.TypeNames[0] | Should -Be 'GitHub.Milestone' + $issue.labels[0].PSObject.TypeNames[0] | Should -Be 'GitHub.Label' + } + } + + Context 'Creating an Issue with the repo on the pipeline' { + $params = @{ + 'Title' = '-issue title-' + 'Body' = '-issue body-' + 'Assignee' = $script:ownerName + 'Milestone' = $milestone.number + 'Label' = 'bug' + 'MediaType' = 'Raw' + } + + $issue = $repo | New-GitHubIssue @params + + It 'Should have the expected property values' { + $issue.title | Should -Be $params.Title + $issue.body | Should -Be $params.Body + $issue.assignee.login | Should -Be $params.Assignee + $issue.assignees.Count | Should -Be 1 + $issue.assignees[0].login | Should -Be $params.Assignee + $issue.milestone.number | Should -Be $params.Milestone + $issue.labels.Count | Should -Be 1 + $issue.labels[0].name | Should -Contain $params.Label + } + + It 'Should have the expected type and additional properties' { + $issue.PSObject.TypeNames[0] | Should -Be 'GitHub.Issue' + $issue.RepositoryUrl | Should -Be $repo.RepositoryUrl + $issue.IssueId | Should -Be $issue.id + $issue.IssueNumber | Should -Be $issue.number + $issue.user.PSObject.TypeNames[0] | Should -Be 'GitHub.User' + $issue.assignee.PSObject.TypeNames[0] | Should -Be 'GitHub.User' + $issue.assignees[0].PSObject.TypeNames[0] | Should -Be 'GitHub.User' + $issue.milestone.PSObject.TypeNames[0] | Should -Be 'GitHub.Milestone' + $issue.labels[0].PSObject.TypeNames[0] | Should -Be 'GitHub.Label' + } + } + } + + Describe 'Updating issues' { + BeforeAll { + $repo = New-GitHubRepository -RepositoryName ([Guid]::NewGuid().Guid) -AutoInit + $milestone = $repo | New-GitHubMilestone -Title ([Guid]::NewGuid().Guid) + $title = 'issue title' + } + + AfterAll { + Remove-GitHubRepository -Uri $repo.RepositoryUrl -Confirm:$false + } + + Context 'Updating an Issue with parameters' { + $issue = New-GitHubIssue -OwnerName $script:OwnerName -RepositoryName $repo.name -Title $title + It 'Should have the expected property values' { + $issue.title | Should -Be $title + $issue.body | Should -BeNullOrEmpty + $issue.assignee.login | Should -BeNullOrEmpty + $issue.assignees | Should -BeNullOrEmpty + $issue.milestone | Should -BeNullOrEmpty + $issue.labels | Should -BeNullOrEmpty + } + + $params = @{ + 'OwnerName' = $script:ownerName + 'RepositoryName' = $repo.name + 'Issue' = $issue.number + 'Title' = '-new title-' + 'Body' = '-new body-' + 'Assignee' = $script:ownerName + 'Milestone' = $milestone.number + 'Label' = 'bug' + 'MediaType' = 'Raw' + } + + $updated = Update-GitHubIssue @params + It 'Should have the expected property values' { + $updated.id | Should -Be $issue.id + $updated.number | Should -Be $issue.number + $updated.title | Should -Be $params.Title + $updated.body | Should -Be $params.Body + $updated.assignee.login | Should -Be $params.Assignee + $updated.assignees.Count | Should -Be 1 + $updated.assignees[0].login | Should -Be $params.Assignee + $updated.milestone.number | Should -Be $params.Milestone + $updated.labels.Count | Should -Be 1 + $updated.labels[0].name | Should -Contain $params.Label + } + + It 'Should have the expected type and additional properties' { + $updated.PSObject.TypeNames[0] | Should -Be 'GitHub.Issue' + $updated.RepositoryUrl | Should -Be $repo.RepositoryUrl + $updated.IssueId | Should -Be $updated.id + $updated.IssueNumber | Should -Be $updated.number + $updated.user.PSObject.TypeNames[0] | Should -Be 'GitHub.User' + $updated.assignee.PSObject.TypeNames[0] | Should -Be 'GitHub.User' + $updated.assignees[0].PSObject.TypeNames[0] | Should -Be 'GitHub.User' + $updated.milestone.PSObject.TypeNames[0] | Should -Be 'GitHub.Milestone' + $updated.labels[0].PSObject.TypeNames[0] | Should -Be 'GitHub.Label' + } + } + + Context 'Updating an Issue with the repo on the pipeline' { + $issue = New-GitHubIssue -OwnerName $script:OwnerName -RepositoryName $repo.name -Title $title + It 'Should have the expected property values' { + $issue.title | Should -Be $title + $issue.body | Should -BeNullOrEmpty + $issue.assignee.login | Should -BeNullOrEmpty + $issue.assignees | Should -BeNullOrEmpty + $issue.milestone | Should -BeNullOrEmpty + $issue.labels | Should -BeNullOrEmpty + } + + $params = @{ + 'Issue' = $issue.number + 'Title' = '-new title-' + 'Body' = '-new body-' + 'Assignee' = $script:ownerName + 'Milestone' = $milestone.number + 'Label' = 'bug' + 'MediaType' = 'Raw' + } + + $updated = $repo | Update-GitHubIssue @params + It 'Should have the expected property values' { + $updated.id | Should -Be $issue.id + $updated.number | Should -Be $issue.number + $updated.title | Should -Be $params.Title + $updated.body | Should -Be $params.Body + $updated.assignee.login | Should -Be $params.Assignee + $updated.assignees.Count | Should -Be 1 + $updated.assignees[0].login | Should -Be $params.Assignee + $updated.milestone.number | Should -Be $params.Milestone + $updated.labels.Count | Should -Be 1 + $updated.labels[0].name | Should -Contain $params.Label + } + + It 'Should have the expected type and additional properties' { + $updated.PSObject.TypeNames[0] | Should -Be 'GitHub.Issue' + $updated.RepositoryUrl | Should -Be $repo.RepositoryUrl + $updated.IssueId | Should -Be $updated.id + $updated.IssueNumber | Should -Be $updated.number + $updated.user.PSObject.TypeNames[0] | Should -Be 'GitHub.User' + $updated.assignee.PSObject.TypeNames[0] | Should -Be 'GitHub.User' + $updated.assignees[0].PSObject.TypeNames[0] | Should -Be 'GitHub.User' + $updated.milestone.PSObject.TypeNames[0] | Should -Be 'GitHub.Milestone' + $updated.labels[0].PSObject.TypeNames[0] | Should -Be 'GitHub.Label' + } + } + + Context 'Updating an Issue with the issue on the pipeline' { + $issue = New-GitHubIssue -OwnerName $script:OwnerName -RepositoryName $repo.name -Title $title + It 'Should have the expected property values' { + $issue.title | Should -Be $title + $issue.body | Should -BeNullOrEmpty + $issue.assignee.login | Should -BeNullOrEmpty + $issue.assignees | Should -BeNullOrEmpty + $issue.milestone | Should -BeNullOrEmpty + $issue.labels | Should -BeNullOrEmpty + } + + $params = @{ + 'Title' = '-new title-' + 'Body' = '-new body-' + 'Assignee' = $script:ownerName + 'Milestone' = $milestone.number + 'Label' = 'bug' + 'MediaType' = 'Raw' + } + + $updated = $issue | Update-GitHubIssue @params + It 'Should have the expected property values' { + $updated.id | Should -Be $issue.id + $updated.number | Should -Be $issue.number + $updated.title | Should -Be $params.Title + $updated.body | Should -Be $params.Body + $updated.assignee.login | Should -Be $params.Assignee + $updated.assignees.Count | Should -Be 1 + $updated.assignees[0].login | Should -Be $params.Assignee + $updated.milestone.number | Should -Be $params.Milestone + $updated.labels.Count | Should -Be 1 + $updated.labels[0].name | Should -Contain $params.Label + } + + It 'Should have the expected type and additional properties' { + $updated.PSObject.TypeNames[0] | Should -Be 'GitHub.Issue' + $updated.RepositoryUrl | Should -Be $repo.RepositoryUrl + $updated.IssueId | Should -Be $updated.id + $updated.IssueNumber | Should -Be $updated.number + $updated.user.PSObject.TypeNames[0] | Should -Be 'GitHub.User' + $updated.assignee.PSObject.TypeNames[0] | Should -Be 'GitHub.User' + $updated.assignees[0].PSObject.TypeNames[0] | Should -Be 'GitHub.User' + $updated.milestone.PSObject.TypeNames[0] | Should -Be 'GitHub.Milestone' + $updated.labels[0].PSObject.TypeNames[0] | Should -Be 'GitHub.Label' + } + } + } + + Describe 'Locking and unlocking issues' { + BeforeAll { + $repo = New-GitHubRepository -RepositoryName ([Guid]::NewGuid().Guid) -AutoInit + } + + AfterAll { + Remove-GitHubRepository -Uri $repo.RepositoryUrl -Confirm:$false + } + + Context 'Locking and unlocking an Issue with parameters' { + $issue = New-GitHubIssue -OwnerName $script:OwnerName -RepositoryName $repo.name -Title ([Guid]::NewGuid().Guid) + It 'Should be unlocked' { + $issue.locked | Should -BeFalse + $issue.active_lock_reason | Should -BeNullOrEmpty + } + + $reason = 'Resolved' + Lock-GitHubIssue -OwnerName $script:OwnerName -RepositoryName $repo.name -Issue $issue.number -Reason $reason + $updated = Get-GitHubIssue -OwnerName $script:OwnerName -RepositoryName $repo.name -Issue $issue.number + It 'Should be locked' { + $updated.id | Should -Be $issue.id + $updated.number | Should -Be $issue.number + $updated.locked | Should -BeTrue + $updated.active_lock_reason | Should -Be $reason + } + + Unlock-GitHubIssue -OwnerName $script:OwnerName -RepositoryName $repo.name -Issue $issue.number + $updated = Get-GitHubIssue -OwnerName $script:OwnerName -RepositoryName $repo.name -Issue $issue.number + It 'Should be unlocked again' { + $updated.id | Should -Be $issue.id + $updated.number | Should -Be $issue.number + $updated.locked | Should -BeFalse + $updated.active_lock_reason | Should -BeNullOrEmpty + } + } + + Context 'Locking and unlocking an Issue with the repo on the pipeline' { + $issue = $repo | New-GitHubIssue -Title ([Guid]::NewGuid().Guid) + It 'Should be unlocked' { + $issue.locked | Should -BeFalse + $issue.active_lock_reason | Should -BeNullOrEmpty + } + + $reason = 'Resolved' + $repo | Lock-GitHubIssue -Issue $issue.number -Reason $reason + $updated = $repo | Get-GitHubIssue -Issue $issue.number + It 'Should be locked' { + $updated.id | Should -Be $issue.id + $updated.number | Should -Be $issue.number + $updated.locked | Should -BeTrue + $updated.active_lock_reason | Should -Be $reason + } + + $repo | Unlock-GitHubIssue -Issue $issue.number + $updated = $repo | Get-GitHubIssue -Issue $issue.number + It 'Should be unlocked again' { + $updated.id | Should -Be $issue.id + $updated.number | Should -Be $issue.number + $updated.locked | Should -BeFalse + $updated.active_lock_reason | Should -BeNullOrEmpty + } + } + + Context 'Locking and unlocking an Issue with the issue on the pipeline' { + $issue = New-GitHubIssue -OwnerName $script:OwnerName -RepositoryName $repo.name -Title ([Guid]::NewGuid().Guid) + It 'Should be unlocked' { + $issue.locked | Should -BeFalse + $issue.active_lock_reason | Should -BeNullOrEmpty + } + + $reason = 'Resolved' + $issue | Lock-GitHubIssue -Reason $reason + $updated = $issue | Get-GitHubIssue + It 'Should be locked' { + $updated.id | Should -Be $issue.id + $updated.number | Should -Be $issue.number + $updated.locked | Should -BeTrue + $updated.active_lock_reason | Should -Be $reason + } + + $issue | Unlock-GitHubIssue + $updated = $issue | Get-GitHubIssue + It 'Should be unlocked again' { + $updated.id | Should -Be $issue.id + $updated.number | Should -Be $issue.number + $updated.locked | Should -BeFalse + $updated.active_lock_reason | Should -BeNullOrEmpty + } + } + } + + Describe 'Issue Timeline' { + BeforeAll { + $repo = New-GitHubRepository -RepositoryName ([Guid]::NewGuid().Guid) -AutoInit + } + + AfterAll { + Remove-GitHubRepository -Uri $repo.RepositoryUrl -Confirm:$false + } + + Context 'Getting the Issue timeline with parameters' { + $issue = New-GitHubIssue -OwnerName $script:OwnerName -RepositoryName $repo.name -Title ([Guid]::NewGuid().Guid) + $timeline = @(Get-GitHubIssueTimeline -OwnerName $script:OwnerName -RepositoryName $repo.name -Issue $issue.number) + It 'Should have no events so far' { + $timeline.Count | Should -Be 0 + } + + Lock-GitHubIssue -OwnerName $script:OwnerName -RepositoryName $repo.name -Issue $issue.number + $timeline = @(Get-GitHubIssueTimeline -OwnerName $script:OwnerName -RepositoryName $repo.name -Issue $issue.number) + It 'Should have an event now' { + $timeline.Count | Should -Be 1 + $timeline[0].event | Should -Be 'locked' + } + + It 'Should have the expected type and additional properties' { + $timeline[0].PSObject.TypeNames[0] | Should -Be 'GitHub.Event' + $timeline[0].RepositoryUrl | Should -Be $repo.RepositoryUrl + $timeline[0].EventId | Should -Be $timeline[0].id + $timeline[0].actor.PSObject.TypeNames[0] | Should -Be 'GitHub.User' + } + } + + Context 'Getting the Issue timeline with the repo on the pipeline' { + $issue = $repo | New-GitHubIssue -Title ([Guid]::NewGuid().Guid) + $timeline = @($repo | Get-GitHubIssueTimeline -Issue $issue.number) + It 'Should have no events so far' { + $timeline.Count | Should -Be 0 + } + + $repo | Lock-GitHubIssue -Issue $issue.number + $timeline = @($repo | Get-GitHubIssueTimeline -Issue $issue.number) + It 'Should have an event now' { + $timeline.Count | Should -Be 1 + $timeline[0].event | Should -Be 'locked' + } + + It 'Should have the expected type and additional properties' { + $timeline[0].PSObject.TypeNames[0] | Should -Be 'GitHub.Event' + $timeline[0].RepositoryUrl | Should -Be $repo.RepositoryUrl + $timeline[0].EventId | Should -Be $timeline[0].id + $timeline[0].actor.PSObject.TypeNames[0] | Should -Be 'GitHub.User' + } + } + + Context 'Getting the Issue timeline with the issue on the pipeline' { + $issue = $repo | New-GitHubIssue -Title ([Guid]::NewGuid().Guid) + $timeline = @($issue | Get-GitHubIssueTimeline) + It 'Should have no events so far' { + $timeline.Count | Should -Be 0 + } + + $issue | Lock-GitHubIssue + $timeline = @($issue | Get-GitHubIssueTimeline) + It 'Should have an event now' { + $timeline.Count | Should -Be 1 + $timeline[0].event | Should -Be 'locked' + } + + It 'Should have the expected type and additional properties' { + $timeline[0].PSObject.TypeNames[0] | Should -Be 'GitHub.Event' + $timeline[0].RepositoryUrl | Should -Be $repo.RepositoryUrl + $timeline[0].EventId | Should -Be $timeline[0].id + $timeline[0].actor.PSObject.TypeNames[0] | Should -Be 'GitHub.User' + } + } + } +} +finally +{ + if (Test-Path -Path $script:originalConfigFile -PathType Leaf) + { + # Restore the user's configuration to its pre-test state + Restore-GitHubConfiguration -Path $script:originalConfigFile + $script:originalConfigFile = $null + } +} diff --git a/Tests/GitHubLabels.tests.ps1 b/Tests/GitHubLabels.tests.ps1 index 1de571dc..e2807442 100644 --- a/Tests/GitHubLabels.tests.ps1 +++ b/Tests/GitHubLabels.tests.ps1 @@ -6,13 +6,18 @@ Tests for GitHubLabels.ps1 module #> +[CmdletBinding()] +[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseDeclaredVarsMoreThanAssignments', '', + Justification='Suppress false positives in Pester code blocks')] +param() + # This is common test code setup logic for all Pester test files $moduleRootPath = Split-Path -Path $PSScriptRoot -Parent . (Join-Path -Path $moduleRootPath -ChildPath 'Tests\Common.ps1') try { - $defaultLabels = @( + $defaultLabels = @( @{ 'name' = 'pri:lowest' 'color' = '4285F4' @@ -71,255 +76,1271 @@ try } ) - if ($accessTokenConfigured) - { - Describe 'Getting labels from repository' { + Describe 'Getting labels from a repository' { + BeforeAll { $repositoryName = [Guid]::NewGuid().Guid - $null = New-GitHubRepository -RepositoryName $repositoryName - Set-GitHubLabel -OwnerName $ownerName -RepositoryName $repositoryName -Label $defaultLabels + $repo = New-GitHubRepository -RepositoryName $repositoryName - Context 'When querying for all labels' { - $labels = @(Get-GitHubLabel -OwnerName $ownerName -RepositoryName $repositoryName) + Set-GitHubLabel -OwnerName $script:ownerName -RepositoryName $repositoryName -Label $defaultLabels + } - It 'Should return expected number of labels' { - $labels.Count | Should be $:defaultLabels.Count + AfterAll { + $repo | Remove-GitHubRepository -Force + } + + Context 'When querying for all labels' { + $labels = @(Get-GitHubLabel -OwnerName $script:ownerName -RepositoryName $repositoryName) + + It 'Should return expected number of labels' { + $labels.Count | Should -Be $defaultLabels.Count + } + + It 'Should have the expected type and additional properties' { + foreach ($label in $labels) + { + $label.PSObject.TypeNames[0] | Should -Be 'GitHub.Label' + $label.RepositoryUrl | Should -Be $repo.RepositoryUrl + $label.LabelId | Should -Be $label.id + $label.LabelName | Should -Be $label.name } } + } - Context 'When querying for specific label' { - $label = Get-GitHubLabel -OwnerName $ownerName -RepositoryName $repositoryName -Name bug + Context 'When querying for all labels (via repo on pipeline)' { + $labels = @($repo | Get-GitHubLabel) + + It 'Should return expected number of labels' { + $labels.Count | Should -Be $defaultLabels.Count + } - It 'Should return expected label' { - $label.name | Should be "bug" + It 'Should have the expected type and additional properties' { + foreach ($label in $labels) + { + $label.PSObject.TypeNames[0] | Should -Be 'GitHub.Label' + $label.RepositoryUrl | Should -Be $repo.RepositoryUrl + $label.LabelId | Should -Be $label.id + $label.LabelName | Should -Be $label.name } } + } + + Context 'When pipeline properties are disabled' { + BeforeAll { + Set-GitHubConfiguration -DisablePipelineSupport + $labels = @($repo | Get-GitHubLabel) + } + + AfterAll { + Set-GitHubConfiguration -DisablePipelineSupport:$false + } + + It 'Should return expected number of labels' { + $labels.Count | Should -Be $defaultLabels.Count + } - $null = Remove-GitHubRepository -OwnerName $ownerName -RepositoryName $repositoryName -Confirm:$false + It 'Should have the expected type and additional properties' { + foreach ($label in $labels) + { + $label.PSObject.TypeNames[0] | Should -Be 'GitHub.Label' + $label.RepositoryUrl | Should -BeNullOrEmpty + $label.LabelId | Should -BeNullOrEmpty + $label.LabelName | Should -BeNullOrEmpty + } + } } - Describe 'Creating new label' { + Context 'When querying for a specific label' { + $labelName = 'bug' + $label = Get-GitHubLabel -OwnerName $script:ownerName -RepositoryName $repositoryName -Label $labelName + + It 'Should return expected label' { + $label.name | Should -Be $labelName + } + + It 'Should have the expected type and additional properties' { + $label.PSObject.TypeNames[0] | Should -Be 'GitHub.Label' + $label.RepositoryUrl | Should -Be $repo.RepositoryUrl + $label.LabelId | Should -Be $label.id + $label.LabelName | Should -Be $label.name + } + } + + Context 'When querying for a specific label (via repo on pipeline)' { + $labelName = 'bug' + $label = $repo | Get-GitHubLabel -Label $labelName + + It 'Should return expected label' { + $label.name | Should -Be $labelName + } + + It 'Should have the expected type and additional properties' { + $label.PSObject.TypeNames[0] | Should -Be 'GitHub.Label' + $label.RepositoryUrl | Should -Be $repo.RepositoryUrl + $label.LabelId | Should -Be $label.id + $label.LabelName | Should -Be $label.name + } + } + + # TODO: This test has been disabled until we can figure out how to fix the parameter sets + # for Get-GitHubLabel pipelining to still support Label this way. + # + # Context 'When querying for a specific label (via Label on pipeline)' { + # $labelName = 'bug' + # $label = $labelName | Get-GitHubLabel -OwnerName $script:ownerName -RepositoryName $repositoryName + + # It 'Should return expected label' { + # $label.name | Should -Be $labelName + # } + + # It 'Should have the expected type and additional properties' { + # $label.PSObject.TypeNames[0] | Should -Be 'GitHub.Label' + # $label.RepositoryUrl | Should -Be $repo.RepositoryUrl + # $label.LabelId | Should -Be $label.id + # $label.LabelName | Should -Be $label.name + # } + # } + } + + Describe 'Creating a new label' { + BeforeAll { $repositoryName = [Guid]::NewGuid().Guid - $null = New-GitHubRepository -RepositoryName $repositoryName + $repo = New-GitHubRepository -RepositoryName $repositoryName + } + + AfterAll { + $repo | Remove-GitHubRepository -Force + } + Context 'On a repo with parameters' { $labelName = [Guid]::NewGuid().Guid - New-GitHubLabel -OwnerName $ownerName -RepositoryName $repositoryName -Name $labelName -Color BBBBBB - $label = Get-GitHubLabel -OwnerName $ownerName -RepositoryName $repositoryName -Name $labelName + $color = 'AAAAAA' + $label = New-GitHubLabel -OwnerName $script:ownerName -RepositoryName $repositoryName -Label $labelName -Color $color It 'New label should be created' { - $label.name | Should be $labelName + $label.name | Should -Be $labelName + $label.color | Should -Be $color } - AfterEach { - Remove-GitHubLabel -OwnerName $ownerName -RepositoryName $repositoryName -Name $labelName -Confirm:$false + It 'Should have the expected type and additional properties' { + $label.PSObject.TypeNames[0] | Should -Be 'GitHub.Label' + $label.RepositoryUrl | Should -Be $repo.RepositoryUrl + $label.LabelId | Should -Be $label.id + $label.LabelName | Should -Be $label.name + } + } + + Context 'On a repo with and color starts with a #' { + $labelName = [Guid]::NewGuid().Guid + $color = '#AAAAAA' + $label = New-GitHubLabel -OwnerName $script:ownerName -RepositoryName $repositoryName -Label $labelName -Color $color + + It 'New label should be created' { + $label.name | Should -Be $labelName + $label.color | Should -Be $color.Substring(1) + $label.description | Should -BeNullOrEmpty } - $null = Remove-GitHubRepository -OwnerName $ownerName -RepositoryName $repositoryName -Confirm:$false + It 'Should have the expected type and additional properties' { + $label.PSObject.TypeNames[0] | Should -Be 'GitHub.Label' + $label.RepositoryUrl | Should -Be $repo.RepositoryUrl + $label.LabelId | Should -Be $label.id + $label.LabelName | Should -Be $label.name + } } - Describe 'Removing label' { - $repositoryName = [Guid]::NewGuid().Guid - $null = New-GitHubRepository -RepositoryName $repositoryName - Set-GitHubLabel -OwnerName $ownerName -RepositoryName $repositoryName -Label $defaultLabels + Context 'On a repo with the repo on the pipeline' { + $labelName = [Guid]::NewGuid().Guid + $color = 'BBBBBB' + $description = 'test description' + $label = $repo | New-GitHubLabel -Label $labelName -Color $color -Description $description + It 'New label should be created' { + $label.name | Should -Be $labelName + $label.color | Should -Be $color + $label.description | Should -Be $description + } + + It 'Should have the expected type and additional properties' { + $label.PSObject.TypeNames[0] | Should -Be 'GitHub.Label' + $label.RepositoryUrl | Should -Be $repo.RepositoryUrl + $label.LabelId | Should -Be $label.id + $label.LabelName | Should -Be $label.name + } + } + + Context 'On a repo with the name on the pipeline' { $labelName = [Guid]::NewGuid().Guid - New-GitHubLabel -OwnerName $ownerName -RepositoryName $repositoryName -Name $labelName -Color BBBBBB - $labels = @(Get-GitHubLabel -OwnerName $ownerName -RepositoryName $repositoryName) + $color = 'CCCCCC' + $label = $labelName | New-GitHubLabel -OwnerName $script:ownerName -RepositoryName $repositoryName -Color $color - It 'Should return increased number of labels' { - $labels.Count | Should be ($defaultLabels.Count + 1) + It 'New label should be created' { + $label.name | Should -Be $labelName + $label.color | Should -Be $color } - Remove-GitHubLabel -OwnerName $ownerName -RepositoryName $repositoryName -Name $labelName -Confirm:$false - $labels = @(Get-GitHubLabel -OwnerName $ownerName -RepositoryName $repositoryName) + It 'Should have the expected type and additional properties' { + $label.PSObject.TypeNames[0] | Should -Be 'GitHub.Label' + $label.RepositoryUrl | Should -Be $repo.RepositoryUrl + $label.LabelId | Should -Be $label.id + $label.LabelName | Should -Be $label.name + } + } - It 'Should return expected number of labels' { - $labels.Count | Should be $defaultLabels.Count + Context 'On a repo with three names on the pipeline' { + $labelNames = @(([Guid]::NewGuid().Guid), ([Guid]::NewGuid().Guid), ([Guid]::NewGuid().Guid)) + $color = 'CCCCCC' + $labels = @($labelNames | New-GitHubLabel -OwnerName $script:ownerName -RepositoryName $repositoryName -Color $color) + + It 'Has the right count of labels' { + $labels.Count | Should -Be $labelNames.Count } - $null = Remove-GitHubRepository -OwnerName $ownerName -RepositoryName $repositoryName -Confirm:$false + It 'Has the right label details' { + foreach ($label in $labels) + { + $labelNames | Should -Contain $label.name + $label.color | Should -Be $color + } + } + + It 'Should have the expected type and additional properties' { + foreach ($label in $labels) + { + $label.PSObject.TypeNames[0] | Should -Be 'GitHub.Label' + $label.RepositoryUrl | Should -Be $repo.RepositoryUrl + $label.LabelId | Should -Be $label.id + $label.LabelName | Should -Be $label.name + } + } } + } - Describe 'Updating label' { + Describe 'Removing a label' { + BeforeAll { $repositoryName = [Guid]::NewGuid().Guid - $null = New-GitHubRepository -RepositoryName $repositoryName + $repo = New-GitHubRepository -RepositoryName $repositoryName + } - $labelName = [Guid]::NewGuid().Guid + AfterAll { + $repo | Remove-GitHubRepository -Force + } + + Context 'Removing a label with parameters' { + $label = $repo | New-GitHubLabel -Label 'test' -Color 'CCCCCC' + Remove-GitHubLabel -OwnerName $script:ownerName -RepositoryName $repositoryName -Label $label.name -Force + + It 'Should be gone after being removed by parameter' { + { $label | Get-GitHubLabel } | Should -Throw + } + } + + Context 'Removing a label with the repo on the pipeline' { + $label = $repo | New-GitHubLabel -Label 'test' -Color 'CCCCCC' + $repo | Remove-GitHubLabel -Label $label.name -Confirm:$false + + It 'Should be gone after being removed by parameter' { + { $label | Get-GitHubLabel } | Should -Throw + } + } + + Context 'Removing a label with the name on the pipeline' { + $label = $repo | New-GitHubLabel -Label 'test' -Color 'CCCCCC' + $label.name | Remove-GitHubLabel -OwnerName $script:ownerName -RepositoryName $repositoryName -Force + + It 'Should be gone after being removed by parameter' { + { $label | Get-GitHubLabel } | Should -Throw + } + } + + Context 'Removing a label with the label object on the pipeline' { + $label = $repo | New-GitHubLabel -Label 'test' -Color 'CCCCCC' + $label | Remove-GitHubLabel -Force + + It 'Should be gone after being removed by parameter' { + { $label | Get-GitHubLabel } | Should -Throw + } + } + } + + Describe 'Updating a label' { + BeforeAll { + $repositoryName = [Guid]::NewGuid().Guid + $repo = New-GitHubRepository -RepositoryName $repositoryName + } + + AfterAll { + $repo | Remove-GitHubRepository -Force + } + + Context 'Updating label color with parameters' { + $label = $repo | New-GitHubLabel -Label ([Guid]::NewGuid().Guid) -Color 'BBBBBB' + + $newColor = 'AAAAAA' + $result = Update-GitHubLabel -OwnerName $script:ownerName -RepositoryName $repositoryName -Label $label.name -Color $newColor + + It 'Label should have different color' { + $result.name | Should -Be $label.name + $result.color | Should -Be $newColor + $result.description | Should -Be $label.description + } + + It 'Should have the expected type and additional properties' { + $result.PSObject.TypeNames[0] | Should -Be 'GitHub.Label' + $result.RepositoryUrl | Should -Be $repo.RepositoryUrl + $result.LabelId | Should -Be $result.id + $result.LabelName | Should -Be $result.name + } + } - Context 'Updating label color' { - New-GitHubLabel -OwnerName $ownerName -RepositoryName $repositoryName -Name $labelName -Color BBBBBB - Update-GitHubLabel -OwnerName $ownerName -RepositoryName $repositoryName -Name $labelName -NewName $labelName -Color AAAAAA - $label = Get-GitHubLabel -OwnerName $ownerName -RepositoryName $repositoryName -Name $labelName + Context 'Updating label color (with #) with parameters' { + $label = $repo | New-GitHubLabel -Label ([Guid]::NewGuid().Guid) -Color 'BBBBBB' - AfterEach { - Remove-GitHubLabel -OwnerName $ownerName -RepositoryName $repositoryName -Name $labelName -Confirm:$false + $newColor = '#AAAAAA' + $result = Update-GitHubLabel -OwnerName $script:ownerName -RepositoryName $repositoryName -Label $label.name -Color $newColor + + It 'Label should have different color' { + $result.name | Should -Be $label.name + $result.color | Should -Be $newColor.Substring(1) + $result.description | Should -Be $label.description + } + + It 'Should have the expected type and additional properties' { + $result.PSObject.TypeNames[0] | Should -Be 'GitHub.Label' + $result.RepositoryUrl | Should -Be $repo.RepositoryUrl + $result.LabelId | Should -Be $result.id + $result.LabelName | Should -Be $result.name + } + } + + Context 'Updating label name with parameters' { + $label = $repo | New-GitHubLabel -Label ([Guid]::NewGuid().Guid) -Color 'BBBBBB' + + $newName = [Guid]::NewGuid().Guid + $result = Update-GitHubLabel -OwnerName $script:ownerName -RepositoryName $repositoryName -Label $label.name -NewName $newName + + It 'Label should have different name' { + $result.name | Should -Be $newName + $result.color | Should -Be $label.color + $result.description | Should -Be $label.description + } + + It 'Should have the expected type and additional properties' { + $result.PSObject.TypeNames[0] | Should -Be 'GitHub.Label' + $result.RepositoryUrl | Should -Be $repo.RepositoryUrl + $result.LabelId | Should -Be $result.id + $result.LabelName | Should -Be $result.name + } + } + + Context 'Updating label description with parameters' { + $label = $repo | New-GitHubLabel -Label ([Guid]::NewGuid().Guid) -Color 'BBBBBB' -Description 'test description' + + $newDescription = [Guid]::NewGuid().Guid + $result = Update-GitHubLabel -OwnerName $script:ownerName -RepositoryName $repositoryName -Label $label.name -Description $newDescription + + It 'Label should have different name' { + $result.name | Should -Be $label.name + $result.color | Should -Be $label.color + $result.description | Should -Be $newDescription + } + + It 'Should have the expected type and additional properties' { + $result.PSObject.TypeNames[0] | Should -Be 'GitHub.Label' + $result.RepositoryUrl | Should -Be $repo.RepositoryUrl + $result.LabelId | Should -Be $result.id + $result.LabelName | Should -Be $result.name + } + } + + Context 'Updating label name, color and description with parameters' { + $label = $repo | New-GitHubLabel -Label ([Guid]::NewGuid().Guid) -Color 'BBBBBB' -Description 'test description' + + $newName = [Guid]::NewGuid().Guid + $newColor = 'AAAAAA' + $newDescription = [Guid]::NewGuid().Guid + $result = Update-GitHubLabel -OwnerName $script:ownerName -RepositoryName $repositoryName -Label $label.name -NewName $newName -Color $newColor -Description $newDescription + + It 'Label should have different everything' { + $result.name | Should -Be $newName + $result.color | Should -Be $newColor + $result.description | Should -Be $newDescription + } + + It 'Should have the expected type and additional properties' { + $result.PSObject.TypeNames[0] | Should -Be 'GitHub.Label' + $result.RepositoryUrl | Should -Be $repo.RepositoryUrl + $result.LabelId | Should -Be $result.id + $result.LabelName | Should -Be $result.name + } + + + } + + Context 'Updating label color with repo on the pipeline' { + $label = $repo | New-GitHubLabel -Label ([Guid]::NewGuid().Guid) -Color 'BBBBBB' + + $newColor = 'AAAAAA' + $result = $repo | Update-GitHubLabel -Label $label.name -Color $newColor + + It 'Label should have different color' { + $result.name | Should -Be $label.name + $result.color | Should -Be $newColor + $result.description | Should -Be $label.description + } + + It 'Should have the expected type and additional properties' { + $result.PSObject.TypeNames[0] | Should -Be 'GitHub.Label' + $result.RepositoryUrl | Should -Be $repo.RepositoryUrl + $result.LabelId | Should -Be $result.id + $result.LabelName | Should -Be $result.name + } + } + + Context 'Updating label name with the label on the pipeline' { + $label = $repo | New-GitHubLabel -Label ([Guid]::NewGuid().Guid) -Color 'BBBBBB' + + $newName = [Guid]::NewGuid().Guid + $result = $label | Update-GitHubLabel -NewName $newName + + It 'Label should have different name' { + $result.name | Should -Be $newName + $result.color | Should -Be $label.color + $result.description | Should -Be $label.description + } + + It 'Should have the expected type and additional properties' { + $result.PSObject.TypeNames[0] | Should -Be 'GitHub.Label' + $result.RepositoryUrl | Should -Be $repo.RepositoryUrl + $result.LabelId | Should -Be $result.id + $result.LabelName | Should -Be $result.name + } + } + + Context 'Updating label name, color and description with the label on the pipeline' { + $label = $repo | New-GitHubLabel -Label ([Guid]::NewGuid().Guid) -Color 'BBBBBB' -Description 'test description' + + $newName = [Guid]::NewGuid().Guid + $newColor = 'AAAAAA' + $newDescription = [Guid]::NewGuid().Guid + $result = $label | Update-GitHubLabel -NewName $newName -Color $newColor -Description $newDescription + + It 'Label should have different everything' { + $result.name | Should -Be $newName + $result.color | Should -Be $newColor + $result.description | Should -Be $newDescription + } + + It 'Should have the expected type and additional properties' { + $result.PSObject.TypeNames[0] | Should -Be 'GitHub.Label' + $result.RepositoryUrl | Should -Be $repo.RepositoryUrl + $result.LabelId | Should -Be $result.id + $result.LabelName | Should -Be $result.name + } + } + } + + Describe 'Initializing the labels on a repository' { + BeforeAll { + $repositoryName = [Guid]::NewGuid().Guid + $repo = New-GitHubRepository -RepositoryName $repositoryName + } + + AfterAll { + $repo | Remove-GitHubRepository -Force + } + + Context 'Applying a default set of labels' { + Set-GitHubLabel -OwnerName $script:ownerName -RepositoryName $repositoryName -Label $defaultLabels + + $labels = @($repo | Get-GitHubLabel) + + It 'Should return the expected number of labels' { + $labels.Count | Should -Be $defaultLabels.Count + } + + It 'Should have the right set of labels' { + foreach ($item in $defaultLabels) + { + $label = $labels | Where-Object { $_.name -eq $item.name } + $item.name | Should -Be $label.name + $item.color | Should -Be $label.color } + } + } - It 'Label should have different color' { - $label.color | Should be AAAAAA + Context 'Applying an overlapping set of labels' { + $newLabels = @( + @{ 'name' = $defaultLabels[0].name; 'color' = 'aaaaaa' }, + @{ 'name' = $defaultLabels[1].name; 'color' = 'bbbbbb' } + @{ 'name' = $defaultLabels[2].name; 'color' = $defaultLabels[2].color } + @{ 'name' = ([Guid]::NewGuid().Guid); 'color' = 'cccccc' } + @{ 'name' = ([Guid]::NewGuid().Guid); 'color' = 'dddddd' } + ) + + $originalLabels = @($repo | Get-GitHubLabel) + $null = $repo | Set-GitHubLabel -Label $newLabels + $labels = @($repo | Get-GitHubLabel) + + It 'Should return the expected number of labels' { + $labels.Count | Should -Be $newLabels.Count + } + + It 'Should have the right set of labels' { + foreach ($item in $newLabels) + { + $label = $labels | Where-Object { $_.name -eq $item.name } + $item.name | Should -Be $label.name + $item.color | Should -Be $label.color } } - Context 'Updating label name' { - $newLabelName = $labelName + "2" - New-GitHubLabel -OwnerName $ownerName -RepositoryName $repositoryName -Name $labelName -Color BBBBBB - Update-GitHubLabel -OwnerName $ownerName -RepositoryName $repositoryName -Name $labelName -NewName $newLabelName -Color BBBBBB - $label = Get-GitHubLabel -OwnerName $ownerName -RepositoryName $repositoryName -Name $newLabelName + It 'Should have retained the ID''s of the pre-existing labels' { + $originalLabel = $originalLabels | Where-Object { $_.name -eq $newLabels[0].name } + $label = $labels | Where-Object { $_.name -eq $newLabels[0].name } + $label.id | Should -Be $originalLabel.id + + $originalLabel = $originalLabels | Where-Object { $_.name -eq $newLabels[1].name } + $label = $labels | Where-Object { $_.name -eq $newLabels[1].name } + $label.id | Should -Be $originalLabel.id + + $originalLabel = $originalLabels | Where-Object { $_.name -eq $newLabels[2].name } + $label = $labels | Where-Object { $_.name -eq $newLabels[2].name } + $label.id | Should -Be $originalLabel.id + + $originalLabel = $originalLabels | Where-Object { $_.name -eq $newLabels[3].name } + $label = $labels | Where-Object { $_.name -eq $newLabels[3].name } + $originalLabel | Should -BeNullOrEmpty + $label | Should -Not -BeNullOrEmpty + + $originalLabel = $originalLabels | Where-Object { $_.name -eq $newLabels[4].name } + $label = $labels | Where-Object { $_.name -eq $newLabels[4].name } + $originalLabel | Should -BeNullOrEmpty + $label | Should -Not -BeNullOrEmpty + } + } + + } - AfterEach { - Remove-GitHubLabel -OwnerName $ownerName -RepositoryName $repositoryName -Name $newLabelName -Confirm:$false + Describe 'Adding labels to an issue' { + BeforeAll { + $repositoryName = [Guid]::NewGuid().Guid + $repo = New-GitHubRepository -RepositoryName $repositoryName + $repo | Set-GitHubLabel -Label $defaultLabels + } + + AfterAll { + $repo | Remove-GitHubRepository -Force + } + + Context 'Adding labels to an issue' { + $expectedLabels = @($defaultLabels[0].name, $defaultLabels[1].name, $defaultLabels[3].name) + $issue = $repo | New-GitHubIssue -Title 'test issue' + $result = @(Add-GitHubIssueLabel -OwnerName $script:ownerName -RepositoryName $repositoryName -Issue $issue.number -LabelName $expectedLabels) + + It 'Should return the number of labels that were just added' { + $result.Count | Should -Be $expectedLabels.Count + } + + It 'Should be the right set of labels' { + foreach ($label in $expectedLabels) + { + $result.name | Should -Contain $label } + } - It 'Label should have different color' { - $label | Should not be $null - $label.color | Should be BBBBBB + It 'Should have the expected type and additional properties' { + foreach ($label in $result) + { + $label.PSObject.TypeNames[0] | Should -Be 'GitHub.Label' + $label.RepositoryUrl | Should -Be $repo.RepositoryUrl + $label.LabelId | Should -Be $label.id + $label.LabelName | Should -Be $label.name } } - $null = Remove-GitHubRepository -OwnerName $ownerName -RepositoryName $repositoryName -Confirm:$false + $issueLabels = Get-GitHubLabel -OwnerName $ownerName -RepositoryName $repositoryName -Issue $issue.number + + It 'Should return the number of labels that were just added from querying the issue again' { + $issueLabels.Count | Should -Be $expectedLabels.Count + } + + It 'Should have the expected type and additional properties' { + foreach ($label in $issueLabels) + { + $label.PSObject.TypeNames[0] | Should -Be 'GitHub.Label' + $label.RepositoryUrl | Should -Be $repo.RepositoryUrl + $label.LabelId | Should -Be $label.id + $label.LabelName | Should -Be $label.name + } + } } - Describe 'Applying set of labels on repository' { - $repositoryName = [Guid]::NewGuid().Guid - $null = New-GitHubRepository -RepositoryName $repositoryName + Context 'Adding labels to an issue with the repo on the pipeline' { + $expectedLabels = @($defaultLabels[0].name, $defaultLabels[1].name, $defaultLabels[3].name) + $issue = $repo | New-GitHubIssue -Title 'test issue' + $result = @($repo | Add-GitHubIssueLabel -Issue $issue.number -LabelName $expectedLabels) - $labelName = [Guid]::NewGuid().Guid - Set-GitHubLabel -OwnerName $ownerName -RepositoryName $repositoryName -Label $defaultLabels + It 'Should return the number of labels that were just added' { + $result.Count | Should -Be $expectedLabels.Count + } - # Add new label - New-GitHubLabel -OwnerName $ownerName -RepositoryName $repositoryName -Name $labelName -Color BBBBBB - $labels = @(Get-GitHubLabel -OwnerName $ownerName -RepositoryName $repositoryName) + It 'Should be the right set of labels' { + foreach ($label in $expectedLabels) + { + $result.name | Should -Contain $label + } + } - # Change color of existing label - Update-GitHubLabel -OwnerName $ownerName -RepositoryName $repositoryName -Name "bug" -NewName "bug" -Color BBBBBB + It 'Should have the expected type and additional properties' { + foreach ($label in $result) + { + $label.PSObject.TypeNames[0] | Should -Be 'GitHub.Label' + $label.RepositoryUrl | Should -Be $repo.RepositoryUrl + $label.LabelId | Should -Be $label.id + $label.LabelName | Should -Be $label.name + } + } - # Remove one of approved labels" - Remove-GitHubLabel -OwnerName $ownerName -RepositoryName $repositoryName -Name "discussion" -Confirm:$false + $issueLabels = $repo | Get-GitHubLabel -Issue $issue.number - It 'Should return increased number of labels' { - $($labels).Count | Should be ($defaultLabels.Count + 1) + It 'Should return the number of labels that were just added from querying the issue again' { + $issueLabels.Count | Should -Be $expectedLabels.Count } - Set-GitHubLabel -OwnerName $ownerName -RepositoryName $repositoryName -Label $defaultLabels - $labels = @(Get-GitHubLabel -OwnerName $ownerName -RepositoryName $repositoryName) + It 'Should have the expected type and additional properties' { + foreach ($label in $issueLabels) + { + $label.PSObject.TypeNames[0] | Should -Be 'GitHub.Label' + $label.RepositoryUrl | Should -Be $repo.RepositoryUrl + $label.LabelId | Should -Be $label.id + $label.LabelName | Should -Be $label.name + } + } + } - It 'Should return expected number of labels' { - $labels.Count | Should be $defaultLabels.Count - $bugLabel = $labels | Where-Object {$_.name -eq "bug"} - $bugLabel.color | Should be "fc2929" + Context 'Adding labels to an issue with the issue on the pipeline' { + $expectedLabels = @($defaultLabels[0].name, $defaultLabels[1].name, $defaultLabels[3].name) + $issue = $repo | New-GitHubIssue -Title 'test issue' + $result = @($issue | Add-GitHubIssueLabel -LabelName $expectedLabels) + + It 'Should return the number of labels that were just added' { + $result.Count | Should -Be $expectedLabels.Count } - $null = Remove-GitHubRepository -OwnerName $ownerName -RepositoryName $repositoryName -Confirm:$false + It 'Should be the right set of labels' { + foreach ($label in $expectedLabels) + { + $result.name | Should -Contain $label + } + } + + It 'Should have the expected type and additional properties' { + foreach ($label in $result) + { + $label.PSObject.TypeNames[0] | Should -Be 'GitHub.Label' + $label.RepositoryUrl | Should -Be $repo.RepositoryUrl + $label.LabelId | Should -Be $label.id + $label.LabelName | Should -Be $label.name + } + } + + $issueLabels = $issue | Get-GitHubLabel + + It 'Should return the number of labels that were just added from querying the issue again' { + $issueLabels.Count | Should -Be $expectedLabels.Count + } + + It 'Should have the expected type and additional properties' { + foreach ($label in $issueLabels) + { + $label.PSObject.TypeNames[0] | Should -Be 'GitHub.Label' + $label.RepositoryUrl | Should -Be $repo.RepositoryUrl + $label.LabelId | Should -Be $label.id + $label.LabelName | Should -Be $label.name + } + } } - Describe 'Adding labels to an issue'{ - $repositoryName = [Guid]::NewGuid().Guid - $null = New-GitHubRepository -RepositoryName $repositoryName - Set-GitHubLabel -OwnerName $ownerName -RepositoryName $repositoryName -Label $defaultLabels + Context 'Adding labels to an issue with the label names on the pipeline' { + $expectedLabels = @($defaultLabels[0].name, $defaultLabels[1].name, $defaultLabels[3].name) + $issue = $repo | New-GitHubIssue -Title 'test issue' + $result = @($expectedLabels | Add-GitHubIssueLabel -OwnerName $script:ownerName -RepositoryName $repositoryName -Issue $issue.number) - $issueName = [Guid]::NewGuid().Guid - $issue = New-GitHubIssue -OwnerName $ownerName -RepositoryName $repositoryName -Title $issueName + It 'Should return the number of labels that were just added' { + $result.Count | Should -Be $expectedLabels.Count + } - Context 'Adding labels to an issue' { - $labelsToAdd = @('pri:lowest', 'pri:low', 'pri:medium', 'pri:high', 'pri:highest', 'bug', 'duplicate', - 'enhancement', 'up for grabs', 'question', 'discussion', 'wontfix', 'in progress', 'ready') - $addedLabels = @(Add-GitHubIssueLabel -OwnerName $ownerName -RepositoryName $repositoryName -Issue $issue.number -LabelName $labelsToAdd) + It 'Should be the right set of labels' { + foreach ($label in $expectedLabels) + { + $result.name | Should -Contain $label + } + } - It 'Should return the number of labels that were just added' { - $addedLabels.Count | Should be $defaultLabels.Count + It 'Should have the expected type and additional properties' { + foreach ($label in $result) + { + $label.PSObject.TypeNames[0] | Should -Be 'GitHub.Label' + $label.RepositoryUrl | Should -Be $repo.RepositoryUrl + $label.LabelId | Should -Be $label.id + $label.LabelName | Should -Be $label.name } + } + + $issueLabels = $issue | Get-GitHubLabel - $labelIssues = Get-GitHubLabel -OwnerName $ownerName -RepositoryName $repositoryName -Issue $issue.number + It 'Should return the number of labels that were just added from querying the issue again' { + $issueLabels.Count | Should -Be $expectedLabels.Count + } - It 'Should return the number of labels that were just added from querying the issue again' { - $labelIssues.Count | Should be $defaultLabels.Count + It 'Should have the expected type and additional properties' { + foreach ($label in $issueLabels) + { + $label.PSObject.TypeNames[0] | Should -Be 'GitHub.Label' + $label.RepositoryUrl | Should -Be $repo.RepositoryUrl + $label.LabelId | Should -Be $label.id + $label.LabelName | Should -Be $label.name } } + } + + Context 'Adding labels to an issue with the label object on the pipeline' { + $expectedLabels = @($defaultLabels[0].name, $defaultLabels[1].name, $defaultLabels[3].name) + $issue = $repo | New-GitHubIssue -Title 'test issue' + $labels = @($expectedLabels | ForEach-Object { Get-GitHubLabel -OwnerName $script:ownerName -RepositoryName $repositoryName -Label $_ } ) + $result = @($labels | Add-GitHubIssueLabel -OwnerName $script:ownerName -RepositoryName $repositoryName -Issue $issue.number) - $null = Remove-GitHubRepository -OwnerName $ownerName -RepositoryName $repositoryName -Confirm:$false + It 'Should return the number of labels that were just added' { + $result.Count | Should -Be $expectedLabels.Count + } + + It 'Should be the right set of labels' { + foreach ($label in $expectedLabels) + { + $result.name | Should -Contain $label + } + } + + It 'Should have the expected type and additional properties' { + foreach ($label in $result) + { + $label.PSObject.TypeNames[0] | Should -Be 'GitHub.Label' + $label.RepositoryUrl | Should -Be $repo.RepositoryUrl + $label.LabelId | Should -Be $label.id + $label.LabelName | Should -Be $label.name + } + } + + $issueLabels = $issue | Get-GitHubLabel + + It 'Should return the number of labels that were just added from querying the issue again' { + $issueLabels.Count | Should -Be $expectedLabels.Count + } + + It 'Should have the expected type and additional properties' { + foreach ($label in $issueLabels) + { + $label.PSObject.TypeNames[0] | Should -Be 'GitHub.Label' + $label.RepositoryUrl | Should -Be $repo.RepositoryUrl + $label.LabelId | Should -Be $label.id + $label.LabelName | Should -Be $label.name + } + } } + } - Describe 'Creating a new Issue with labels' { + Describe 'Creating a new Issue with labels' { + BeforeAll { $repositoryName = [Guid]::NewGuid().Guid - $null = New-GitHubRepository -RepositoryName $repositoryName - Set-GitHubLabel -OwnerName $ownerName -RepositoryName $repositoryName -Label $defaultLabels + $repo = New-GitHubRepository -RepositoryName $repositoryName + $repo | Set-GitHubLabel -Label $defaultLabels + } + + AfterAll { + $repo | Remove-GitHubRepository -Force + } + + Context 'When creating a new issue using parameters' { + $issueName = [Guid]::NewGuid().Guid + $issueLabels = @($defaultLabels[0].name, $defaultLabels[1].name) + $issue = New-GitHubIssue -OwnerName $script:ownerName -RepositoryName $repositoryName -Title $issueName -Label $issueLabels + + It 'Should return the number of labels that were just added' { + $issue.labels.Count | Should -Be $issueLabels.Count + } + It 'Should be the right set of labels' { + foreach ($label in $issueLabels) + { + $issue.labels.name | Should -Contain $label + } + } + + It 'Should have the expected type and additional properties' { + foreach ($label in $issue.labels) + { + $label.PSObject.TypeNames[0] | Should -Be 'GitHub.Label' + $label.RepositoryUrl | Should -Be $repo.RepositoryUrl + $label.LabelId | Should -Be $label.id + $label.LabelName | Should -Be $label.name + } + } + } + + Context 'When creating a new issue using the repo on the pipeline' { $issueName = [Guid]::NewGuid().Guid $issueLabels = @($defaultLabels[0].name, $defaultLabels[1].name) - $issue = New-GitHubIssue -OwnerName $ownerName -RepositoryName $repositoryName -Title $issueName -Label $issueLabels + $issue = $repo | New-GitHubIssue -Title $issueName -Label $issueLabels It 'Should return the number of labels that were just added' { - $issue.labels.Count | Should be $issueLabels.Count + $issue.labels.Count | Should -Be $issueLabels.Count } - $null = Remove-GitHubRepository -OwnerName $ownerName -RepositoryName $repositoryName -Confirm:$false + It 'Should be the right set of labels' { + foreach ($label in $issueLabels) + { + $issue.labels.name | Should -Contain $label + } + } + + It 'Should have the expected type and additional properties' { + foreach ($label in $issue.labels) + { + $label.PSObject.TypeNames[0] | Should -Be 'GitHub.Label' + $label.RepositoryUrl | Should -Be $repo.RepositoryUrl + $label.LabelId | Should -Be $label.id + $label.LabelName | Should -Be $label.name + } + } } + } - Describe 'Removing labels on an issue'{ + Describe 'Removing labels on an issue' { + BeforeAll { $repositoryName = [Guid]::NewGuid().Guid - $null = New-GitHubRepository -RepositoryName $repositoryName + $repo = New-GitHubRepository -RepositoryName $repositoryName + $repo | Set-GitHubLabel -Label $defaultLabels + } + + AfterAll { + $repo | Remove-GitHubRepository -Force + } + + Context 'For removing an individual issue with parameters' { + $issueName = [Guid]::NewGuid().Guid + $issue = $repo | New-GitHubIssue -Title $issueName + + $labelsToAdd = @($defaultLabels[0].name, $defaultLabels[1].name, $defaultLabels[2].name) + $issue | Add-GitHubIssueLabel -LabelName $labelsToAdd + + $issueLabels = @($issue | Get-GitHubLabel) + It 'Should have the expected number of labels' { + $issueLabels.Count | Should -Be $labelsToAdd.Count + } + + # Doing this manually instead of in a loop to try out different combinations of -Confirm:$false and -Force + Remove-GitHubIssueLabel -OwnerName $script:ownerName -RepositoryName $repositoryName -Label $labelsToAdd[0] -Issue $issue.number -Confirm:$false + Remove-GitHubIssueLabel -OwnerName $script:ownerName -RepositoryName $repositoryName -Label $labelsToAdd[1] -Issue $issue.number -Force + Remove-GitHubIssueLabel -OwnerName $script:ownerName -RepositoryName $repositoryName -Label $labelsToAdd[2] -Issue $issue.number -Confirm:$false -Force + + $issueLabels = @($issue | Get-GitHubLabel) + It 'Should have removed all labels from the issue' { + $issueLabels.Count | Should -Be 0 + } + } + Context 'For removing an individual issue using the repo on the pipeline' { $issueName = [Guid]::NewGuid().Guid - $issue = New-GitHubIssue -OwnerName $ownerName -RepositoryName $repositoryName -Title $issueName + $issue = $repo | New-GitHubIssue -Title $issueName - $labelsToAdd = @('pri:lowest', 'pri:low', 'pri:medium', 'pri:high', 'pri:highest', 'bug', 'duplicate', - 'enhancement', 'up for grabs', 'question', 'discussion', 'wontfix', 'in progress', 'ready') - Add-GitHubIssueLabel -OwnerName $ownerName -RepositoryName $repositoryName -Issue $issue.number -LabelName $labelsToAdd + $labelsToAdd = @($defaultLabels[0].name, $defaultLabels[1].name, $defaultLabels[2].name, $defaultLabels[3].name) + $issue | Add-GitHubIssueLabel -LabelName $labelsToAdd - Context 'For removing individual issues'{ - Remove-GitHubIssueLabel -OwnerName $ownerName -RepositoryName $repositoryName -Name "discussion" -Issue $issue.number -Confirm:$false - Remove-GitHubIssueLabel -OwnerName $ownerName -RepositoryName $repositoryName -Name "question" -Issue $issue.number -Force - Remove-GitHubIssueLabel -OwnerName $ownerName -RepositoryName $repositoryName -Name "bug" -Issue $issue.number -Confirm:$false -Force - $labelIssues = @(Get-GitHubLabel -OwnerName $ownerName -RepositoryName $repositoryName -Issue $issue.number) + $issueLabels = @($issue | Get-GitHubLabel) + It 'Should have the expected number of labels' { + $issueLabels.Count | Should -Be $labelsToAdd.Count + } + + $labelToRemove = $labelsToAdd[0] + $repo | Remove-GitHubIssueLabel -Label $labelToRemove -Issue $issue.number -Confirm:$false + + $issueLabels = @($issue | Get-GitHubLabel) + It 'Should have removed the expected label from the issue' { + $issueLabels.Count | Should -Be ($labelsToAdd.Count - 1) + $issueLabels.name | Should -Not -Contain $labelToRemove + } + } + + Context 'For removing an individual issue using the issue on the pipeline' { + $issueName = [Guid]::NewGuid().Guid + $issue = $repo | New-GitHubIssue -Title $issueName + + $labelsToAdd = @($defaultLabels[0].name, $defaultLabels[1].name, $defaultLabels[2].name, $defaultLabels[3].name) + $issue | Add-GitHubIssueLabel -LabelName $labelsToAdd + + $issueLabels = @($issue | Get-GitHubLabel) + It 'Should have the expected number of labels' { + $issueLabels.Count | Should -Be $labelsToAdd.Count + } + + $labelToRemove = $labelsToAdd[1] + $issue | Remove-GitHubIssueLabel -Label $labelToRemove -Confirm:$false + + $issueLabels = @($issue | Get-GitHubLabel) + It 'Should have removed the expected label from the issue' { + $issueLabels.Count | Should -Be ($labelsToAdd.Count - 1) + $issueLabels.name | Should -Not -Contain $labelToRemove + } + } + + # TODO: This has been disabled for now, as ValueFromPipeline has been disabled until we + # sort out some complication issues with the ParameterSets + # + # Context 'For removing an individual issue using the label name on the pipeline' { + # $issueName = [Guid]::NewGuid().Guid + # $issue = $repo | New-GitHubIssue -Title $issueName + + # $labelsToAdd = @($defaultLabels[0].name, $defaultLabels[1].name, $defaultLabels[2].name, $defaultLabels[3].name) + # $issue | Add-GitHubIssueLabel -LabelName $labelsToAdd + + # $issueLabels = @($issue | Get-GitHubLabel) + # It 'Should have the expected number of labels' { + # $issueLabels.Count | Should -Be $labelsToAdd.Count + # } + + # $labelToRemove = $labelsToAdd[2] + # $labelToRemove | Remove-GitHubIssueLabel -OwnerName $script:ownerName -RepositoryName $repositoryName -Issue $issue.number -Confirm:$false + + # $issueLabels = @($issue | Get-GitHubLabel) + # It 'Should have removed the expected label from the issue' { + # $issueLabels.Count | Should -Be ($labelsToAdd.Count - 1) + # $issueLabels.name | Should -Not -Contain $labelToRemove + # } + # } + + Context 'For removing an individual issue using the label object on the pipeline' { + $issueName = [Guid]::NewGuid().Guid + $issue = $repo | New-GitHubIssue -Title $issueName + + $labelsToAdd = @($defaultLabels[0].name, $defaultLabels[1].name, $defaultLabels[2].name, $defaultLabels[3].name) + $issue | Add-GitHubIssueLabel -LabelName $labelsToAdd + + $issueLabels = @($issue | Get-GitHubLabel) + It 'Should have the expected number of labels' { + $issueLabels.Count | Should -Be $labelsToAdd.Count + } - It 'Should have removed three labels from the issue' { - $labelIssues.Count | Should be ($defaultLabels.Count - 3) + $labelToRemove = $labelsToAdd[0] + $label = $repo | Get-GitHubLabel -Label $labelToRemove + $label | Remove-GitHubIssueLabel -Issue $issue.number -Confirm:$false + + $issueLabels = @($issue | Get-GitHubLabel) + It 'Should have removed the expected label from the issue' { + $issueLabels.Count | Should -Be ($labelsToAdd.Count - 1) + $issueLabels.name | Should -Not -Contain $labelToRemove + } + } + + Context 'For removing all issues' { + $issueName = [Guid]::NewGuid().Guid + $issue = $repo | New-GitHubIssue -Title $issueName + + $labelsToAdd = @($defaultLabels[0].name, $defaultLabels[1].name, $defaultLabels[2].name, $defaultLabels[3].name) + $issue | Add-GitHubIssueLabel -LabelName $labelsToAdd + + $issueLabels = @($issue | Get-GitHubLabel) + It 'Should have the expected number of labels' { + $issueLabels.Count | Should -Be $labelsToAdd.Count + } + + $issue | Remove-GitHubIssueLabel -Confirm:$false + + $issueLabels = @($issue | Get-GitHubLabel) + It 'Should have removed all labels from the issue' { + $issueLabels.Count | Should -Be 0 + } + } + + Context 'For removing all issues using Set-GitHubIssueLabel with the Issue on the pipeline' { + $issueName = [Guid]::NewGuid().Guid + $issue = $repo | New-GitHubIssue -Title $issueName + + $labelsToAdd = @($defaultLabels[0].name, $defaultLabels[1].name, $defaultLabels[2].name, $defaultLabels[3].name) + $issue | Add-GitHubIssueLabel -LabelName $labelsToAdd + + $issueLabels = @($issue | Get-GitHubLabel) + It 'Should have the expected number of labels' { + $issueLabels.Count | Should -Be $labelsToAdd.Count + } + + $issue | Set-GitHubIssueLabel -Confirm:$false + + $issueLabels = @($issue | Get-GitHubLabel) + It 'Should have removed all labels from the issue' { + $issueLabels.Count | Should -Be 0 + } + } + } + + Describe 'Replacing labels on an issue' { + BeforeAll { + $repositoryName = [Guid]::NewGuid().Guid + $repo = New-GitHubRepository -RepositoryName $repositoryName + $repo | Set-GitHubLabel -Label $defaultLabels + } + + AfterAll { + $repo | Remove-GitHubRepository -Force + } + + Context 'Change the set of labels with parameters' { + $labelsToAdd = @($defaultLabels[0].name, $defaultLabels[1].name) + $issue = $repo | New-GitHubIssue -Title ([Guid]::NewGuid().Guid) -Label $labelsToAdd + + It 'Should have assigned the expected labels' { + $issue.labels.Count | Should -Be $labelsToAdd.Count + foreach ($label in $labelsToAdd) + { + $issue.labels.name | Should -Contain $label + } + } + + $newIssueLabels = @($defaultLabels[0].name, $defaultLabels[5].name) + $result = @(Set-GitHubIssueLabel -OwnerName $script:ownerName -RepositoryName $repositoryName -Issue $issue.number -Label $newIssueLabels) + + It 'Should have the expected labels' { + $result.labels.Count | Should -Be $newIssueLabels.Count + foreach ($label in $newIssueLabels) + { + $result.name | Should -Contain $label + } + } + + It 'Should have the expected type and additional properties' { + foreach ($label in $result) + { + $label.PSObject.TypeNames[0] | Should -Be 'GitHub.Label' + $label.RepositoryUrl | Should -Be $repo.RepositoryUrl + $label.LabelId | Should -Be $label.id + $label.LabelName | Should -Be $label.name + } + } + } + + Context 'Change the set of labels with the repo on the pipeline' { + $labelsToAdd = @($defaultLabels[0].name, $defaultLabels[1].name) + $issue = $repo | New-GitHubIssue -Title ([Guid]::NewGuid().Guid) -Label $labelsToAdd + + It 'Should have assigned the expected labels' { + $issue.labels.Count | Should -Be $labelsToAdd.Count + foreach ($label in $labelsToAdd) + { + $issue.labels.name | Should -Contain $label } } - Context 'For removing all issues'{ - Remove-GitHubIssueLabel -OwnerName $ownerName -RepositoryName $repositoryName -Issue $issue.number -Confirm:$false - $labelIssues = @(Get-GitHubLabel -OwnerName $ownerName -RepositoryName $repositoryName -Issue $issue.number) + $newIssueLabels = @($defaultLabels[0].name, $defaultLabels[5].name) + $result = @($repo | Set-GitHubIssueLabel -Issue $issue.number -Label $newIssueLabels) - It 'Should have removed all labels from the issue' { - $labelIssues.Count | Should be 0 + It 'Should have the expected labels' { + $result.labels.Count | Should -Be $newIssueLabels.Count + foreach ($label in $newIssueLabels) + { + $result.name | Should -Contain $label } } - $null = Remove-GitHubRepository -OwnerName $ownerName -RepositoryName $repositoryName -Confirm:$false + It 'Should have the expected type and additional properties' { + foreach ($label in $result) + { + $label.PSObject.TypeNames[0] | Should -Be 'GitHub.Label' + $label.RepositoryUrl | Should -Be $repo.RepositoryUrl + $label.LabelId | Should -Be $label.id + $label.LabelName | Should -Be $label.name + } + } } - Describe 'Replacing labels on an issue'{ + Context 'Change the set of labels with the issue on the pipeline' { + $labelsToAdd = @($defaultLabels[0].name, $defaultLabels[1].name) + $issue = $repo | New-GitHubIssue -Title ([Guid]::NewGuid().Guid) -Label $labelsToAdd + + It 'Should have assigned the expected labels' { + $issue.labels.Count | Should -Be $labelsToAdd.Count + foreach ($label in $labelsToAdd) + { + $issue.labels.name | Should -Contain $label + } + } + + $newIssueLabels = @($defaultLabels[0].name, $defaultLabels[5].name) + $result = @($issue | Set-GitHubIssueLabel -Label $newIssueLabels) + + It 'Should have the expected labels' { + $result.labels.Count | Should -Be $newIssueLabels.Count + foreach ($label in $newIssueLabels) + { + $result.name | Should -Contain $label + } + } + + It 'Should have the expected type and additional properties' { + foreach ($label in $result) + { + $label.PSObject.TypeNames[0] | Should -Be 'GitHub.Label' + $label.RepositoryUrl | Should -Be $repo.RepositoryUrl + $label.LabelId | Should -Be $label.id + $label.LabelName | Should -Be $label.name + } + } + } + + Context 'Change the set of labels with parameters with the labels on the pipeline' { + $labelsToAdd = @($defaultLabels[0].name, $defaultLabels[1].name) + $issue = $repo | New-GitHubIssue -Title ([Guid]::NewGuid().Guid) -Label $labelsToAdd + + It 'Should have assigned the expected labels' { + $issue.labels.Count | Should -Be $labelsToAdd.Count + foreach ($label in $labelsToAdd) + { + $issue.labels.name | Should -Contain $label + } + } + + $newIssueLabelNames = @($defaultLabels[0].name, $defaultLabels[5].name) + $issueLabels = @($newIssueLabelNames | ForEach-Object { $repo | Get-GitHubLabel -Label $_ }) + $result = @($issueLabels | Set-GitHubIssueLabel -OwnerName $script:ownerName -RepositoryName $repositoryName -Issue $issue.number) + + It 'Should have the expected labels' { + $result.labels.Count | Should -Be $newIssueLabelNames.Count + foreach ($label in $newIssueLabelNames) + { + $result.name | Should -Contain $label + } + } + + It 'Should have the expected type and additional properties' { + foreach ($label in $result) + { + $label.PSObject.TypeNames[0] | Should -Be 'GitHub.Label' + $label.RepositoryUrl | Should -Be $repo.RepositoryUrl + $label.LabelId | Should -Be $label.id + $label.LabelName | Should -Be $label.name + } + } + } + } + + Describe 'Labels and Milestones' { + BeforeAll { $repositoryName = [Guid]::NewGuid().Guid - $null = New-GitHubRepository -RepositoryName $repositoryName + $repo = New-GitHubRepository -RepositoryName $repositoryName + $repo | Set-GitHubLabel -Label $defaultLabels - $issueName = [Guid]::NewGuid().Guid - $issue = New-GitHubIssue -OwnerName $ownerName -RepositoryName $repositoryName -Title $issueName + $milestone = $repo | New-GitHubMilestone -Title 'test milestone' + + $issueLabels = @($defaultLabels[0].name, $defaultLabels[1].name, $defaultLabels[3].name) + $issue = $milestone | New-GitHubIssue -Title 'test issue' -Label $issueLabels + + $issueLabels2 = @($defaultLabels[4].name, $defaultLabels[5].name) + $issue2 = $milestone | New-GitHubIssue -Title 'test issue' -Label $issueLabels2 + } + + AfterAll { + $repo | Remove-GitHubRepository -Force + } + + Context 'Getting labels for issues in a milestone with parameters' { + It 'Should return the number of labels that were just added to the issue' { + $issue.labels.Count | Should -Be $issueLabels.Count + $issue2.labels.Count | Should -Be $issueLabels2.Count + } + + $milestoneLabels = Get-GitHubLabel -OwnerName $script:ownerName -RepositoryName $repositoryName -Milestone $milestone.number + + It 'Should return the same number of labels in the issue that were assigned to the milestone' { + $milestoneLabels.Count | Should -Be ($issue.labels.Count + $issue2.labels.Count) + } + + It 'Should be the right set of labels' { + $allLabels = $issue.labels.name + $issue2.labels.name + foreach ($label in $allLabels) + { + $milestoneLabels.name | Should -Contain $label + } + } + + It 'Should have the expected type and additional properties' { + foreach ($label in $milestoneLabels) + { + $label.PSObject.TypeNames[0] | Should -Be 'GitHub.Label' + $label.RepositoryUrl | Should -Be $repo.RepositoryUrl + $label.LabelId | Should -Be $label.id + $label.LabelName | Should -Be $label.name + } + } + } - $labelsToAdd = @('pri:lowest', 'pri:low', 'pri:medium', 'pri:high', 'pri:highest', 'bug', 'duplicate', - 'enhancement', 'up for grabs', 'question', 'discussion', 'wontfix', 'in progress', 'ready') + Context 'Getting labels for issues in a milestone with the repo on the pipeline' { + It 'Should return the number of labels that were just added to the issue' { + $issue.labels.Count | Should -Be $issueLabels.Count + $issue2.labels.Count | Should -Be $issueLabels2.Count + } - Add-GitHubIssueLabel -OwnerName $ownerName -RepositoryName $repositoryName -Issue $issue.number -LabelName 'pri:medium' + $milestoneLabels = $repo | Get-GitHubLabel -Milestone $milestone.number - $addedLabels = @(Set-GitHubIssueLabel -OwnerName $ownerName -RepositoryName $repositoryName -Issue $issue.number -LabelName $labelsToAdd) + It 'Should return the same number of labels in the issues that were assigned to the milestone' { + $milestoneLabels.Count | Should -Be ($issue.labels.Count + $issue2.labels.Count) + } - It 'Should return the issue with 14 labels' { - $addedLabels.Count | Should be $labelsToAdd.Count + It 'Should be the right set of labels' { + $allLabels = $issue.labels.name + $issue2.labels.name + foreach ($label in $allLabels) + { + $milestoneLabels.name | Should -Contain $label + } } - $labelIssues = Get-GitHubLabel -OwnerName $ownerName -RepositoryName $repositoryName -Issue $issue.number + It 'Should have the expected type and additional properties' { + foreach ($label in $milestoneLabels) + { + $label.PSObject.TypeNames[0] | Should -Be 'GitHub.Label' + $label.RepositoryUrl | Should -Be $repo.RepositoryUrl + $label.LabelId | Should -Be $label.id + $label.LabelName | Should -Be $label.name + } + } + } - It 'Should have 14 labels after querying the issue' { - $labelIssues.Count | Should be $defaultLabels.Count + Context 'Getting labels for issues in a milestone on the pipeline' { + It 'Should return the number of labels that were just added to the issue' { + $issue.labels.Count | Should -Be $issueLabels.Count + $issue2.labels.Count | Should -Be $issueLabels2.Count } - $updatedIssueLabels = $labelsToAdd[0] - $updatedIssue = Update-GitHubIssue -OwnerName $ownerName -RepositoryName $repositoryName -Issue $issue.number -Label $updatedIssueLabels + $milestoneLabels = $milestone | Get-GitHubLabel + + It 'Should return the same number of labels in the issue that is assigned to the milestone' { + $milestoneLabels.Count | Should -Be ($issue.labels.Count + $issue2.labels.Count) + } - It 'Should have 1 label after updating the issue' { - $updatedIssue.labels.Count | Should be $updatedIssueLabels.Count + It 'Should be the right set of labels' { + $allLabels = $issue.labels.name + $issue2.labels.name + foreach ($label in $allLabels) + { + $milestoneLabels.name | Should -Contain $label + } } - $null = Remove-GitHubRepository -OwnerName $ownerName -RepositoryName $repositoryName -Confirm:$false + It 'Should have the expected type and additional properties' { + foreach ($label in $milestoneLabels) + { + $label.PSObject.TypeNames[0] | Should -Be 'GitHub.Label' + $label.RepositoryUrl | Should -Be $repo.RepositoryUrl + $label.LabelId | Should -Be $label.id + $label.LabelName | Should -Be $label.name + } + } } } } diff --git a/Tests/GitHubMilestones.tests.ps1 b/Tests/GitHubMilestones.tests.ps1 index db9ab8cf..048e5dac 100644 --- a/Tests/GitHubMilestones.tests.ps1 +++ b/Tests/GitHubMilestones.tests.ps1 @@ -6,6 +6,11 @@ Tests for GitHubMilestones.ps1 module #> +[CmdletBinding()] +[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseDeclaredVarsMoreThanAssignments', '', + Justification='Suppress false positives in Pester code blocks')] +param() + # This is common test code setup logic for all Pester test files $moduleRootPath = Split-Path -Path $PSScriptRoot -Parent . (Join-Path -Path $moduleRootPath -ChildPath 'Tests\Common.ps1') @@ -14,116 +19,400 @@ try { # Define Script-scoped, readonly, hidden variables. @{ - defaultIssueTitle = "This is a test issue." - defaultMilestoneTitle1 = "This is a test milestone title #1." - defaultMilestoneTitle2 = "This is a test milestone title #2." - defaultMilestoneTitle3 = "This is a test milestone title #3." - defaultMilestoneTitle4 = "This is a test milestone title #4." - defaultEditedMilestoneTitle = "This is an edited milestone title." - defaultMilestoneDescription = "This is a test milestone description." - defaultEditedMilestoneDescription = "This is an edited milestone description." defaultMilestoneDueOn = (Get-Date).AddYears(1).ToUniversalTime() }.GetEnumerator() | ForEach-Object { Set-Variable -Force -Scope Script -Option ReadOnly -Visibility Private -Name $_.Key -Value $_.Value } - Describe 'Creating, modifying and deleting milestones' { - $repo = New-GitHubRepository -RepositoryName ([Guid]::NewGuid().Guid) -AutoInit - $issue = New-GitHubIssue -Uri $repo.svn_url -Title $defaultIssueTitle + Describe 'Creating a milestone' { + BeforeAll { + $repo = New-GitHubRepository -RepositoryName ([Guid]::NewGuid().Guid) -AutoInit - Context 'For creating a new milestone' { - $newMilestone = New-GitHubMilestone -Uri $repo.svn_url -Title $defaultMilestoneTitle1 -State "Closed" -DueOn $defaultMilestoneDueOn - $existingMilestone = Get-GitHubMilestone -Uri $repo.svn_url -Milestone $newMilestone.number + $commonParams = @{ + 'State' = 'Closed' + 'DueOn' = $script:defaultMilestoneDueOn + 'Description' = 'Milestone description' + } - # We'll be testing to make sure that regardless of the time in the timestamp, we'll get the desired date. - $newMilestoneDueOnEarlyMorning = New-GitHubMilestone -Uri $repo.svn_url -Title $defaultMilestoneTitle2 -State "Closed" -DueOn $defaultMilestoneDueOn.date.AddHours(1) - $newMilestoneDueOnLateEvening = New-GitHubMilestone -Uri $repo.svn_url -Title $defaultMilestoneTitle3 -State "Closed" -DueOn $defaultMilestoneDueOn.date.AddHours(23) + $title = 'Milestone title' + } + + AfterAll { + $repo | Remove-GitHubRepository -Force + } + + Context 'Using the parameter' { + BeforeAll { + $milestone = New-GitHubMilestone -OwnerName $repo.owner.login -RepositoryName $repo.name -Title $title @commonParams + } + + AfterAll { + $milestone | Remove-GitHubMilestone -Force + } + + $returned = Get-GitHubMilestone -OwnerName $repo.owner.login -RepositoryName $repo.name -Milestone $milestone.MilestoneNumber + + It 'Should exist' { + $returned.id | Should -Be $milestone.id + } + + It 'Should have the correct creation properties' { + $milestone.title | Should -Be $title + $milestone.state | Should -Be $commonParams['State'] + $milestone.description | Should -Be $commonParams['Description'] + + # GitHub drops the time that is attached to 'due_on', so it's only relevant + # to compare the dates against each other. + (Get-Date -Date $milestone.due_on).Date | Should -Be $commonParams['DueOn'].Date + } + + It 'Should have the expected type and additional properties' { + $milestone.PSObject.TypeNames[0] | Should -Be 'GitHub.Milestone' + $milestone.RepositoryUrl | Should -Be $repo.RepositoryUrl + $milestone.MilestoneId | Should -Be $milestone.id + $milestone.MilestoneNumber | Should -Be $milestone.number + $milestone.creator.PSObject.TypeNames[0] | Should -Be 'GitHub.User' + } + } + + Context 'Using the pipeline for the repo' { + BeforeAll { + $milestone = $repo | New-GitHubMilestone -Title $title @commonParams + } + + AfterAll { + $milestone | Remove-GitHubMilestone -Force + } + + $returned = $milestone | Get-GitHubMilestone + + It 'Should exist' { + $returned.id | Should -Be $milestone.id + } + + It 'Should have the correct creation properties' { + $milestone.title | Should -Be $title + $milestone.state | Should -Be $commonParams['State'] + $milestone.description | Should -Be $commonParams['Description'] + + # GitHub drops the time that is attached to 'due_on', so it's only relevant + # to compare the dates against each other. + (Get-Date -Date $milestone.due_on).Date | Should -Be $commonParams['DueOn'].Date + } + + It 'Should have the expected type and additional properties' { + $milestone.PSObject.TypeNames[0] | Should -Be 'GitHub.Milestone' + $milestone.RepositoryUrl | Should -Be $repo.RepositoryUrl + $milestone.MilestoneId | Should -Be $milestone.id + $milestone.MilestoneNumber | Should -Be $milestone.number + $milestone.creator.PSObject.TypeNames[0] | Should -Be 'GitHub.User' + } + } - It "Should have the expected title text" { - $existingMilestone.title | Should be $defaultMilestoneTitle1 + Context 'Using the pipeline for the title' { + BeforeAll { + $milestone = $title | New-GitHubMilestone -OwnerName $repo.owner.login -RepositoryName $repo.name @commonParams } - It "Should have the expected state" { - $existingMilestone.state | Should be "closed" + AfterAll { + $milestone | Remove-GitHubMilestone -Force } - It "Should have the expected due_on date" { + $returned = $repo | Get-GitHubMilestone -Milestone $milestone.MilestoneNumber + + It 'Should exist' { + $returned.id | Should -Be $milestone.id + } + + It 'Should have the correct creation properties' { + $milestone.title | Should -Be $title + $milestone.state | Should -Be $commonParams['State'] + $milestone.description | Should -Be $commonParams['Description'] + # GitHub drops the time that is attached to 'due_on', so it's only relevant # to compare the dates against each other. - (Get-Date -Date $existingMilestone.due_on).Date | Should be $defaultMilestoneDueOn.Date + (Get-Date -Date $milestone.due_on).Date | Should -Be $commonParams['DueOn'].Date + } + + It 'Should have the expected type and additional properties' { + $milestone.PSObject.TypeNames[0] | Should -Be 'GitHub.Milestone' + $milestone.RepositoryUrl | Should -Be $repo.RepositoryUrl + $milestone.MilestoneId | Should -Be $milestone.id + $milestone.MilestoneNumber | Should -Be $milestone.number + $milestone.creator.PSObject.TypeNames[0] | Should -Be 'GitHub.User' } + } + + Context 'That is due at different times of the day' { + # We'll be testing to make sure that regardless of the time in the timestamp, we'll get the desired date. + $title = 'Milestone title' It "Should have the expected due_on date even if early morning" { + $milestone = $repo | New-GitHubMilestone -Title 'Due early in the morning' -State "Closed" -DueOn $defaultMilestoneDueOn.date.AddHours(1) + # GitHub drops the time that is attached to 'due_on', so it's only relevant # to compare the dates against each other. - (Get-Date -Date $newMilestoneDueOnEarlyMorning.due_on).Date | Should be $defaultMilestoneDueOn.Date + (Get-Date -Date $milestone.due_on).Date | Should -Be $defaultMilestoneDueOn.Date } It "Should have the expected due_on date even if late evening" { + $milestone = $repo | New-GitHubMilestone -Title 'Due late in the evening' -State "Closed" -DueOn $defaultMilestoneDueOn.date.AddHours(23) + # GitHub drops the time that is attached to 'due_on', so it's only relevant # to compare the dates against each other. - (Get-Date -Date $newMilestoneDueOnLateEvening.due_on).Date | Should be $defaultMilestoneDueOn.Date + (Get-Date -Date $milestone.due_on).Date | Should -Be $defaultMilestoneDueOn.Date } + } + } - It "Should allow the addition of an existing issue" { - Update-GitHubIssue -Uri $repo.svn_url -Issue $issue.number -Milestone $existingMilestone.number - } + Describe 'Associating milestones with issues' { + BeforeAll { + $repo = New-GitHubRepository -RepositoryName ([Guid]::NewGuid().Guid) -AutoInit + $milestone = $repo | New-GitHubMilestone -Title 'Milestone Title' + $issue = $repo | New-GitHubIssue -Title 'Issue Title' + } + + AfterAll { + $repo | Remove-GitHubRepository -Force } - Context 'For getting milestones from a repo' { - $existingMilestones =@(Get-GitHubMilestone -Uri $repo.svn_url -State Closed) + Context 'Adding milestone to an issue' { + It 'Should not have any open issues associated with it' { + $issue.milestone | Should -BeNullOrEmpty + $milestone.open_issues | Should -Be 0 + } + + $issue = $issue | Update-GitHubIssue -Milestone $milestone.MilestoneNumber + $milestone = $milestone | Get-GitHubMilestone + It "Should be associated to the milestone now" { + $issue.milestone.number | Should -Be $milestone.MilestoneNumber + $milestone.open_issues | Should -Be 1 + } + + $issue = $issue | Update-GitHubIssue -Milestone 0 + $milestone = $milestone | Get-GitHubMilestone + It 'Should no longer be associated to the milestone' { + $issue.milestone | Should -BeNullOrEmpty + $milestone.open_issues | Should -Be 0 + } + + $issue = $issue | Update-GitHubIssue -Milestone $milestone.MilestoneNumber + $milestone = $milestone | Get-GitHubMilestone + It "Should be associated to the milestone again" { + $issue.milestone.number | Should -Be $milestone.MilestoneNumber + $milestone.open_issues | Should -Be 1 + } + + $milestone | Remove-GitHubMilestone -Force $issue = Get-GitHubIssue -Uri $repo.svn_url -Issue $issue.number + It 'Should have removed the association when the milestone was deleted' { + $issue.milestone | Should -BeNullOrEmpty + } + } + } + + Describe 'Getting milestones' { + BeforeAll { + $repo = New-GitHubRepository -RepositoryName ([Guid]::NewGuid().Guid) -AutoInit + $title = 'Milestone title' + } + + AfterAll { + $repo | Remove-GitHubRepository -Force + } + + Context 'Getting a specific milestone' { + BeforeAll { + $closedMilestone = 'C' | New-GitHubMilestone -Uri $repo.RepositoryUrl -State 'Closed' + $openMilestone = 'O' | New-GitHubMilestone -Uri $repo.RepositoryUrl -State 'Open' + } + + AfterAll { + $closedMilestone | Remove-GitHubMilestone -Force + $openMilestone | Remove-GitHubMilestone -Force + } + + $milestone = $closedMilestone + $returned = Get-GitHubMilestone -Uri $repo.RepositoryUrl -Milestone $milestone.MilestoneNumber + It 'Should get the right milestone as a parameter' { + $returned.MilestoneId | Should -Be $milestone.MilestoneId + } + + It 'Should have the expected type and additional properties' { + $returned.PSObject.TypeNames[0] | Should -Be 'GitHub.Milestone' + $returned.RepositoryUrl | Should -Be $repo.RepositoryUrl + $returned.MilestoneId | Should -Be $returned.id + $returned.MilestoneNumber | Should -Be $returned.number + $returned.creator.PSObject.TypeNames[0] | Should -Be 'GitHub.User' + } + + $milestone = $openMilestone + $returned = $openMilestone | Get-GitHubMilestone + It 'Should get the right milestone via the pipeline' { + $returned.MilestoneId | Should -Be $milestone.MilestoneId + } + + It 'Should have the expected type and additional properties' { + $returned.PSObject.TypeNames[0] | Should -Be 'GitHub.Milestone' + $returned.RepositoryUrl | Should -Be $repo.RepositoryUrl + $returned.MilestoneId | Should -Be $returned.id + $returned.MilestoneNumber | Should -Be $returned.number + $returned.creator.PSObject.TypeNames[0] | Should -Be 'GitHub.User' + } + } + + Context 'Getting multiple milestones' { + BeforeAll { + $today = (Get-Date).ToUniversalTime() + $nextWeek = (Get-Date).AddDays(7).ToUniversalTime() + $numClosedMilestones = 3 + $numOpenMilestones = 4 + $closed = 1..$numClosedMilestones | ForEach-Object { $repo | New-GitHubMilestone -Title "Closed $_" -State 'Closed' -DueOn $today } + $open = 1..$numOpenMilestones | ForEach-Object { $repo | New-GitHubMilestone -Title "Open $_" -State 'Open' -DueOn $nextWeek } + } + + AfterAll { + $closed | Remove-GitHubMilestone -Force + $open | Remove-GitHubMilestone -Force + } It 'Should have the expected number of milestones' { - $existingMilestones.Count | Should be 3 + $milestones = @(Get-GitHubMilestone -Uri $repo.RepositoryUrl -State 'All') + $milestones.Count | Should -Be ($numClosedMilestones + $numOpenMilestones) + } + + It 'Should have the expected number of open milestones' { + $milestones = @($repo | Get-GitHubMilestone -State 'Open') + $milestones.Count | Should -Be $numOpenMilestones + } + + It 'Should have the expected number of closed milestones' { + $milestones = @(Get-GitHubMilestone -Uri $repo.RepositoryUrl -State 'Closed') + $milestones.Count | Should -Be $numClosedMilestones + } + + It 'Should sort them the right way | DueOn, Descending' { + $milestones = @(Get-GitHubMilestone -Uri $repo.RepositoryUrl -State 'All' -Sort 'DueOn' -Direction 'Descending') + $milestones[0].state | Should -Be 'Open' } - It 'Should have the expected title text on the first milestone' { - $existingMilestones[0].title | Should be $defaultMilestoneTitle1 + It 'Should sort them the right way | DueOn, Ascending' { + $milestones = @(Get-GitHubMilestone -Uri $repo.RepositoryUrl -State 'All' -Sort 'DueOn' -Direction 'Ascending') + $milestones[0].state | Should -Be 'Closed' + } + } + } + + Describe 'Editing a milestone' { + BeforeAll { + $repo = New-GitHubRepository -RepositoryName ([Guid]::NewGuid().Guid) -AutoInit + + $createParams = @{ + 'Title' = 'Created Title' + 'State' = 'Open' + 'Description' = 'Created Description' + 'DueOn' = (Get-Date).ToUniversalTime() } - It 'Should have the expected issue in the first milestone' { - $existingMilestones[0].open_issues | should be 1 - $issue.milestone.number | Should be 1 + $editParams = @{ + 'Title' = 'Edited Title' + 'State' = 'Closed' + 'Description' = 'Edited Description' + 'DueOn' = (Get-Date).AddDays(7).ToUniversalTime() } } - Context 'For editing a milestone' { - $newMilestone = New-GitHubMilestone -Uri $repo.svn_url -Title $defaultMilestoneTitle4 -Description $defaultMilestoneDescription - $editedMilestone = Set-GitHubMilestone -Uri $repo.svn_url -Milestone $newMilestone.number -Title $defaultEditedMilestoneTitle -Description $defaultEditedMilestoneDescription + AfterAll { + $repo | Remove-GitHubRepository -Force + } - It 'Should have a title/description that is not equal to the original title/description' { - $editedMilestone.title | Should not be $newMilestone.title - $editedMilestone.description | Should not be $newMilestone.description + Context 'Using the parameter' { + BeforeAll { + $milestone = New-GitHubMilestone -OwnerName $repo.owner.login -RepositoryName $repo.name @createParams + $edited = Set-GitHubMilestone -Uri $milestone.RepositoryUrl -Milestone $milestone.MilestoneNumber @editParams } - It 'Should have the edited content' { - $editedMilestone.title | Should be $defaultEditedMilestoneTitle - $editedMilestone.description | Should be $defaultEditedMilestoneDescription + AfterAll { + $milestone | Remove-GitHubMilestone -Force + } + + It 'Should be editable via the parameter' { + $edited.id | Should -Be $milestone.id + $edited.title | Should -Be $editParams['Title'] + $edited.state | Should -Be $editParams['State'] + $edited.description | Should -Be $editParams['Description'] + + # GitHub drops the time that is attached to 'due_on', so it's only relevant + # to compare the dates against each other. + (Get-Date -Date $edited.due_on).Date | Should -Be $editParams['DueOn'].Date + } + + It 'Should have the expected type and additional properties' { + $edited.PSObject.TypeNames[0] | Should -Be 'GitHub.Milestone' + $edited.RepositoryUrl | Should -Be $repo.RepositoryUrl + $edited.MilestoneId | Should -Be $milestone.id + $edited.MilestoneNumber | Should -Be $milestone.number + $edited.creator.PSObject.TypeNames[0] | Should -Be 'GitHub.User' } } - Context 'For getting milestones from a repository and deleting them' { - $existingMilestones = @(Get-GitHubMilestone -Uri $repo.svn_url -State All -Sort Completeness -Direction Descending) + Context 'Using the pipeline' { + BeforeAll { + $milestone = New-GitHubMilestone -OwnerName $repo.owner.login -RepositoryName $repo.name @createParams + $edited = $milestone | Set-GitHubMilestone @editParams + } - It 'Should have the expected number of milestones' { - $existingMilestones.Count | Should be 4 + AfterAll { + $milestone | Remove-GitHubMilestone -Force } - foreach($milestone in $existingMilestones) { - Remove-GitHubMilestone -Uri $repo.svn_url -Milestone $milestone.number -Confirm:$false + It 'Should be editable via the pipeline' { + $edited.id | Should -Be $milestone.id + $edited.title | Should -Be $editParams['Title'] + $edited.state | Should -Be $editParams['State'] + $edited.description | Should -Be $editParams['Description'] + + # GitHub drops the time that is attached to 'due_on', so it's only relevant + # to compare the dates against each other. + (Get-Date -Date $edited.due_on).Date | Should -Be $editParams['DueOn'].Date } - $existingMilestones = @(Get-GitHubMilestone -Uri $repo.svn_url -State All) - $issue = Get-GitHubIssue -Uri $repo.svn_url -Issue $issue.number + It 'Should have the expected type and additional properties' { + $edited.PSObject.TypeNames[0] | Should -Be 'GitHub.Milestone' + $edited.RepositoryUrl | Should -Be $repo.RepositoryUrl + $edited.MilestoneId | Should -Be $milestone.id + $edited.MilestoneNumber | Should -Be $milestone.number + $edited.creator.PSObject.TypeNames[0] | Should -Be 'GitHub.User' + } + } + } - It 'Should have no milestones' { - $existingMilestones.Count | Should be 0 - $issue.milestone | Should be $null + Describe 'Deleting a milestone' { + BeforeAll { + $repo = New-GitHubRepository -RepositoryName ([Guid]::NewGuid().Guid) -AutoInit + } + + AfterAll { + $repo | Remove-GitHubRepository -Force + } + + Context 'Using the parameter' { + $milestone = $repo | New-GitHubMilestone -Title 'Milestone title' -State "Closed" -DueOn $defaultMilestoneDueOn + Remove-GitHubMilestone -OwnerName $repo.owner.login -RepositoryName $repo.name -Milestone $milestone.MilestoneNumber -Force + + It 'Should be deleted' { + { Get-GitHubMilestone -OwnerName $repo.owner.login -RepositoryName $repo.name -Milestone $milestone.MilestoneNumber } | Should -Throw } } - Remove-GitHubRepository -Uri $repo.svn_url -Confirm:$false + Context 'Using the pipeline' { + $milestone = $repo | New-GitHubMilestone -Title 'Milestone title' -State "Closed" -DueOn $defaultMilestoneDueOn + $milestone | Remove-GitHubMilestone -Force + + It 'Should be deleted' { + { $milestone | Get-GitHubMilestone } | Should -Throw + } + } } } finally diff --git a/Tests/GitHubMiscellaneous.tests.ps1 b/Tests/GitHubMiscellaneous.tests.ps1 new file mode 100644 index 00000000..7be6eb96 --- /dev/null +++ b/Tests/GitHubMiscellaneous.tests.ps1 @@ -0,0 +1,352 @@ +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. + +<# +.Synopsis + Tests for GitHubMiscellaneous.ps1 module +#> + +[CmdletBinding()] +[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseDeclaredVarsMoreThanAssignments', '', + Justification='Suppress false positives in Pester code blocks')] +param() + +Describe 'Get-GitHubRateLimit' { + BeforeAll { + # This is common test code setup logic for all Pester test files + $moduleRootPath = Split-Path -Path $PSScriptRoot -Parent + . (Join-Path -Path $moduleRootPath -ChildPath 'Tests\Common.ps1') + } + + AfterAll { + # Restore the user's configuration to its pre-test state + Restore-GitHubConfiguration -Path $script:originalConfigFile + $script:originalConfigFile = $null + } + + Context 'Is working' { + BeforeAll { + $result = Get-GitHubRateLimit + } + + It 'Has the expected type' { + $result.PSObject.TypeNames[0] | Should -Be 'GitHub.RateLimit' + } + } +} + +Describe 'ConvertFrom-GitHubMarkdown' { + BeforeAll { + # This is common test code setup logic for all Pester test files + $moduleRootPath = Split-Path -Path $PSScriptRoot -Parent + . (Join-Path -Path $moduleRootPath -ChildPath 'Tests\Common.ps1') + } + + AfterAll { + # Restore the user's configuration to its pre-test state + Restore-GitHubConfiguration -Path $script:originalConfigFile + $script:originalConfigFile = $null + } + + Context 'Works with the parameter' { + BeforeAll { + $markdown = '**PowerShellForGitHub**' + $expectedHtml = '

PowerShellForGitHub

' + } + + It 'Has the expected result as a parameter' { + $result = ConvertFrom-GitHubMarkdown -Content $markdown + + # Replace newlines with empty for comparison purposes + $result.Replace("`n", "").Replace("`r", "") | Should -Be $expectedHtml + } + + It 'Has the expected result with the pipeline' { + $result = $markdown | ConvertFrom-GitHubMarkdown + + # Replace newlines with empty for comparison purposes + $result.Replace("`n", "").Replace("`r", "") | Should -Be $expectedHtml + } + } +} + +Describe 'Get-GitHubLicense' { + BeforeAll { + # This is common test code setup logic for all Pester test files + $moduleRootPath = Split-Path -Path $PSScriptRoot -Parent + . (Join-Path -Path $moduleRootPath -ChildPath 'Tests\Common.ps1') + } + + AfterAll { + # Restore the user's configuration to its pre-test state + Restore-GitHubConfiguration -Path $script:originalConfigFile + $script:originalConfigFile = $null + } + + Context 'Can get the license for a repo with parameters' { + BeforeAll { + $result = Get-GitHubLicense -OwnerName 'PowerShell' -RepositoryName 'PowerShell' + } + + It 'Has the expected result' { + $result.license.key | Should -Be 'mit' + } + + It 'Has the expected type and additional properties' { + $result.PSObject.TypeNames[0] | Should -Be 'GitHub.Content' + $result.LicenseKey | Should -Be $result.license.key + $result.license.PSObject.TypeNames[0] | Should -Be 'GitHub.License' + } + } + + Context 'Can get the license for a repo with the repo on the pipeline' { + BeforeAll { + $result = Get-GitHubRepository -OwnerName 'PowerShell' -RepositoryName 'PowerShell' | Get-GitHubLicense + } + + It 'Has the expected result' { + $result.license.key | Should -Be 'mit' + } + + It 'Has the expected type and additional properties' { + $result.PSObject.TypeNames[0] | Should -Be 'GitHub.Content' + $result.LicenseKey | Should -Be $result.license.key + $result.license.PSObject.TypeNames[0] | Should -Be 'GitHub.License' + } + } + + Context 'Can get all of the licenses' { + BeforeAll { + $results = @(Get-GitHubLicense) + } + + It 'Has the expected result' { + # The number of licenses on GitHub is unlikely to remain static. + # Let's just make sure that we have a few results + $results.Count | Should -BeGreaterThan 3 + } + + It 'Has the expected type and additional properties' { + foreach ($license in $results) + { + $license.PSObject.TypeNames[0] | Should -Be 'GitHub.License' + $license.LicenseKey | Should -Be $license.key + } + } + } + + Context 'Can get a specific license' { + BeforeAll { + $result = Get-GitHubLicense -Key 'mit' + $again = $result | Get-GitHubLicense + } + + It 'Has the expected result' { + $result.key | Should -Be 'mit' + } + + It 'Has the expected type and additional properties' { + $result.PSObject.TypeNames[0] | Should -Be 'GitHub.License' + $result.LicenseKey | Should -Be $result.key + } + + It 'Has the expected result' { + $again.key | Should -Be 'mit' + } + + It 'Has the expected type and additional properties' { + $again.PSObject.TypeNames[0] | Should -Be 'GitHub.License' + $again.LicenseKey | Should -Be $again.key + } + } +} + +Describe 'Get-GitHubEmoji' { + BeforeAll { + # This is common test code setup logic for all Pester test files + $moduleRootPath = Split-Path -Path $PSScriptRoot -Parent + . (Join-Path -Path $moduleRootPath -ChildPath 'Tests\Common.ps1') + } + + AfterAll { + # Restore the user's configuration to its pre-test state + Restore-GitHubConfiguration -Path $script:originalConfigFile + $script:originalConfigFile = $null + } + + Context 'Is working' { + BeforeAll { + $result = Get-GitHubEmoji + } + + It 'Has the expected type' { + $result.PSObject.TypeNames[0] | Should -Be 'GitHub.Emoji' + } + } +} + +Describe 'Get-GitHubCodeOfConduct' { + BeforeAll { + # This is common test code setup logic for all Pester test files + $moduleRootPath = Split-Path -Path $PSScriptRoot -Parent + . (Join-Path -Path $moduleRootPath -ChildPath 'Tests\Common.ps1') + } + + AfterAll { + # Restore the user's configuration to its pre-test state + Restore-GitHubConfiguration -Path $script:originalConfigFile + $script:originalConfigFile = $null + } + + Context 'Can get the code of conduct for a repo with parameters' { + BeforeAll { + $result = Get-GitHubCodeOfConduct -OwnerName 'PowerShell' -RepositoryName 'PowerShell' + } + + It 'Has the expected result' { + $result.key | Should -Be 'other' + } + + It 'Has the expected type and additional properties' { + $result.PSObject.TypeNames[0] | Should -Be 'GitHub.CodeOfConduct' + $result.CodeOfConductKey | Should -Be $result.key + } + } + + Context 'Can get the code of conduct for a repo with the repo on the pipeline' { + BeforeAll { + $result = Get-GitHubRepository -OwnerName 'PowerShell' -RepositoryName 'PowerShell' | Get-GitHubCodeOfConduct + } + + It 'Has the expected result' { + $result.key | Should -Be 'other' + } + + It 'Has the expected type and additional properties' { + $result.PSObject.TypeNames[0] | Should -Be 'GitHub.CodeOfConduct' + $result.CodeOfConductKey | Should -Be $result.key + } + } + + Context 'Can get all of the codes of conduct' { + BeforeAll { + $results = @(Get-GitHubCodeOfConduct) + } + + It 'Has the expected results' { + # The number of codes of conduct on GitHub is unlikely to remain static. + # Let's just make sure that we have a couple results + $results.Count | Should -BeGreaterOrEqual 2 + } + + It 'Has the expected type and additional properties' { + foreach ($item in $results) + { + $item.PSObject.TypeNames[0] | Should -Be 'GitHub.CodeOfConduct' + $item.CodeOfConductKey | Should -Be $item.key + } + } + } + + Context 'Can get a specific code of conduct' { + BeforeAll { + $key = 'contributor_covenant' + $result = Get-GitHubCodeOfConduct -Key $key + $again = $result | Get-GitHubCodeOfConduct + } + + It 'Has the expected result' { + $result.key | Should -Be $key + } + + It 'Has the expected type and additional properties' { + $result.PSObject.TypeNames[0] | Should -Be 'GitHub.CodeOfConduct' + $result.CodeOfConductKey | Should -Be $result.key + } + + It 'Has the expected result' { + $again.key | Should -Be $key + } + + It 'Has the expected type and additional properties' { + $again.PSObject.TypeNames[0] | Should -Be 'GitHub.CodeOfConduct' + $again.CodeOfConductKey | Should -Be $again.key + } + } +} + +Describe 'Get-GitHubGitIgnore' { + BeforeAll { + # This is common test code setup logic for all Pester test files + $moduleRootPath = Split-Path -Path $PSScriptRoot -Parent + . (Join-Path -Path $moduleRootPath -ChildPath 'Tests\Common.ps1') + } + + AfterAll { + # Restore the user's configuration to its pre-test state + Restore-GitHubConfiguration -Path $script:originalConfigFile + $script:originalConfigFile = $null + } + + Context 'Gets all the known .gitignore files' { + BeforeAll { + $result = Get-GitHubGitIgnore + } + + It 'Has the expected values' { + # The number of .gitignore files on GitHub is unlikely to remain static. + # Let's just make sure that we have a bunch of results + $result.Count | Should -BeGreaterOrEqual 5 + } + It 'Has the expected type' { + $result.PSObject.TypeNames[0] | Should -Not -Be 'GitHub.Gitignore' + } + } + + Context 'Gets a specific one via parameter' { + BeforeAll { + $name = 'C' + $result = Get-GitHubGitIgnore -Name $name + } + + It 'Has the expected value' { + $result.name | Should -Be $name + $result.source | Should -Not -BeNullOrEmpty + } + + It 'Has the expected type' { + $result.PSObject.TypeNames[0] | Should -Be 'GitHub.Gitignore' + } + } + + Context 'Gets a specific one via the pipeline' { + BeforeAll { + $name = 'C' + $result = $name | Get-GitHubGitIgnore + } + + It 'Has the expected value' { + $result.name | Should -Be $name + $result.source | Should -Not -BeNullOrEmpty + } + + It 'Has the expected type' { + $result.PSObject.TypeNames[0] | Should -Be 'GitHub.Gitignore' + } + } + + Context 'Gets a specific one as raw content via the pipeline' { + BeforeAll { + $name = 'C' + $result = $name | Get-GitHubGitIgnore -RawContent + } + + It 'Has the expected value' { + $result | Should -Not -BeNullOrEmpty + } + + It 'Has the expected type' { + $result.PSObject.TypeNames[0] | Should -Not -Be 'GitHub.Gitignore' + } + } +} diff --git a/Tests/GitHubOrganizations.tests.ps1 b/Tests/GitHubOrganizations.tests.ps1 new file mode 100644 index 00000000..42bc547b --- /dev/null +++ b/Tests/GitHubOrganizations.tests.ps1 @@ -0,0 +1,58 @@ +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. + +<# +.Synopsis + Tests for GitHubOrganizations.ps1 module +#> + +[CmdletBinding()] +[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseDeclaredVarsMoreThanAssignments', '', + Justification='Suppress false positives in Pester code blocks')] +param() + +# This is common test code setup logic for all Pester test files +$moduleRootPath = Split-Path -Path $PSScriptRoot -Parent +. (Join-Path -Path $moduleRootPath -ChildPath 'Tests\Common.ps1') + +try +{ + # TODO once more capabilities exist in the module's API set + + # TODO: Re-enable these tests once the module has sufficient support getting the Organization + # and repository into the required state for testing, and to recover back to the original state + # at the conclusion of the test. + + # Describe 'Obtaining organization members' { + # $members = Get-GitHubOrganizationMember -OrganizationName $script:organizationName + + # It 'Should return expected number of organization members' { + # @($members).Count | Should -Be 1 + # } + # } + + # Describe 'Obtaining organization teams' { + # $teams = Get-GitHubTeam -OrganizationName $script:organizationName + + # It 'Should return expected number of organization teams' { + # @($teams).Count | Should -Be 2 + # } + # } + + # Describe 'Obtaining organization team members' { + # $members = Get-GitHubTeamMember -OrganizationName $script:organizationName -TeamName $script:organizationTeamName + + # It 'Should return expected number of organization team members' { + # @($members).Count | Should -Be 1 + # } + # } +} +finally +{ + if (Test-Path -Path $script:originalConfigFile -PathType Leaf) + { + # Restore the user's configuration to its pre-test state + Restore-GitHubConfiguration -Path $script:originalConfigFile + $script:originalConfigFile = $null + } +} diff --git a/Tests/GitHubProjectCards.tests.ps1 b/Tests/GitHubProjectCards.tests.ps1 index 7875f4e7..54d80ec3 100644 --- a/Tests/GitHubProjectCards.tests.ps1 +++ b/Tests/GitHubProjectCards.tests.ps1 @@ -6,6 +6,11 @@ Tests for GitHubProjectCards.ps1 module #> +[CmdletBinding()] +[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseDeclaredVarsMoreThanAssignments', '', + Justification='Suppress false positives in Pester code blocks')] +param() + # This is common test code setup logic for all Pester test files $moduleRootPath = Split-Path -Path $PSScriptRoot -Parent . (Join-Path -Path $moduleRootPath -ChildPath 'Tests\Common.ps1') @@ -29,10 +34,10 @@ try } $repo = New-GitHubRepository -RepositoryName ([Guid]::NewGuid().Guid) -AutoInit - $project = New-GitHubProject -Owner $script:ownerName -Repository $repo.name -Name $defaultProject + $project = New-GitHubProject -OwnerName $script:ownerName -RepositoryName $repo.name -ProjectName $defaultProject - $column = New-GitHubProjectColumn -Project $project.id -Name $defaultColumn - $columntwo = New-GitHubProjectColumn -Project $project.id -Name $defaultColumnTwo + $column = New-GitHubProjectColumn -Project $project.id -ColumnName $defaultColumn + $columntwo = New-GitHubProjectColumn -Project $project.id -ColumnName $defaultColumnTwo $issue = New-GitHubIssue -Owner $script:ownerName -RepositoryName $repo.name -Title $defaultIssue @@ -41,10 +46,6 @@ try $card = New-GitHubProjectCard -Column $column.id -Note $defaultCard $cardArchived = New-GitHubProjectCard -Column $column.id -Note $defaultArchivedCard $null = Set-GitHubProjectCard -Card $cardArchived.id -Archive - - # Avoid PSScriptAnalyzer PSUseDeclaredVarsMoreThanAssignments - $card = $card - $cardArchived = $cardArchived } AfterAll { @@ -53,34 +54,103 @@ try Context 'Get cards for a column' { $results = @(Get-GitHubProjectCard -Column $column.id) + It 'Should get cards' { - $results | Should Not BeNullOrEmpty + $results | Should -Not -BeNullOrEmpty + } + + It 'Should only have one card (since it defaults to not archived)' { + $results.Count | Should -Be 1 } It 'Note is correct' { - $results.note | Should be $defaultCard + $results[0].note | Should -Be $defaultCard + } + + It 'Has the expected type and additional properties' { + $results[0].PSObject.TypeNames[0] | Should -Be 'GitHub.ProjectCard' + $results[0].CardId | Should -Be $results[0].id + $results[0].ProjectId | Should -Be $project.id + $results[0].ColumnId | Should -Be $column.id + $results[0].IssueNumber | Should -BeNullOrEmpty + $results[0].RepositoryUrl | Should -BeNullOrEmpty + $results[0].PullRequestNumber | Should -BeNullOrEmpty + $results[0].creator.PSObject.TypeNames[0] | Should -Be 'GitHub.User' } } Context 'Get all cards for a column' { - $results = @(Get-GitHubProjectCard -Column $column.id -ArchivedState All) + $results = @(Get-GitHubProjectCard -Column $column.id -State All) + It 'Should get all cards' { - $results.Count | Should Be 2 + $results.Count | Should -Be 2 + } + + It 'Has the expected type and additional properties' { + foreach ($item in $results) + { + $item.PSObject.TypeNames[0] | Should -Be 'GitHub.ProjectCard' + $item.CardId | Should -Be $item.id + $item.ProjectId | Should -Be $project.id + $item.ColumnId | Should -Be $column.id + $item.IssueNumber | Should -BeNullOrEmpty + $item.RepositoryUrl | Should -BeNullOrEmpty + $item.PullRequestNumber | Should -BeNullOrEmpty + $item.creator.PSObject.TypeNames[0] | Should -Be 'GitHub.User' + } } } Context 'Get archived cards for a column' { - $result = Get-GitHubProjectCard -Column $column.id -ArchivedState Archived + $result = Get-GitHubProjectCard -Column $column.id -State Archived It 'Should get archived card' { - $result | Should Not BeNullOrEmpty + $result | Should -Not -BeNullOrEmpty } It 'Note is correct' { - $result.note | Should be $defaultArchivedCard + $result.note | Should -Be $defaultArchivedCard } It 'Should be archived' { - $result.Archived | Should be $true + $result.Archived | Should -Be $true + } + + It 'Has the expected type and additional properties' { + $result.PSObject.TypeNames[0] | Should -Be 'GitHub.ProjectCard' + $result.CardId | Should -Be $result.id + $result.ProjectId | Should -Be $project.id + $result.ColumnId | Should -Be $column.id + $result.IssueNumber | Should -BeNullOrEmpty + $result.RepositoryUrl | Should -BeNullOrEmpty + $result.PullRequestNumber | Should -BeNullOrEmpty + $result.creator.PSObject.TypeNames[0] | Should -Be 'GitHub.User' + } + } + + Context 'Get non-archived cards for a column (with column on pipeline)' { + $result = $column | Get-GitHubProjectCard -State NotArchived + + It 'Should get non-archived card' { + $result | Should -Not -BeNullOrEmpty + } + + It 'Should have the right ID' { + $result.id | Should -Be $card.id + } + + It 'Should not be archived' { + $result.Archived | Should -Be $false + } + + It 'Has the expected type and additional properties' { + $result.PSObject.TypeNames[0] | Should -Be 'GitHub.ProjectCard' + $result.CardId | Should -Be $result.id + $result.ProjectId | Should -Be $project.id + $result.ColumnId | Should -Be $column.id + $result.IssueNumber | Should -BeNullOrEmpty + $result.RepositoryUrl | Should -BeNullOrEmpty + $result.PullRequestNumber | Should -BeNullOrEmpty + $result.creator.PSObject.TypeNames[0] | Should -Be 'GitHub.User' } } } @@ -90,11 +160,6 @@ try $card = New-GitHubProjectCard -Column $column.id -Note $defaultCard $cardTwo = New-GitHubProjectCard -Column $column.id -Note $defaultCardTwo $cardArchived = New-GitHubProjectCard -Column $column.id -Note $defaultArchivedCard - - # Avoid PSScriptAnalyzer PSUseDeclaredVarsMoreThanAssignments - $card = $card - $cardTwo = $cardTwo - $cardArchived = $cardArchived } AfterAll { @@ -106,11 +171,48 @@ try $result = Get-GitHubProjectCard -Card $card.id It 'Should get card' { - $result | Should Not BeNullOrEmpty + $result | Should -Not -BeNullOrEmpty } It 'Note has been updated' { - $result.note | Should be $defaultCardUpdated + $result.note | Should -Be $defaultCardUpdated + } + + It 'Has the expected type and additional properties' { + $result.PSObject.TypeNames[0] | Should -Be 'GitHub.ProjectCard' + $result.CardId | Should -Be $result.id + $result.ProjectId | Should -Be $project.id + $result.ColumnId | Should -Be $column.id + $result.IssueNumber | Should -BeNullOrEmpty + $result.RepositoryUrl | Should -BeNullOrEmpty + $result.PullRequestNumber | Should -BeNullOrEmpty + $result.creator.PSObject.TypeNames[0] | Should -Be 'GitHub.User' + } + } + + Context 'Modify card note (via card on pipeline)' { + $result = $card | Get-GitHubProjectCard + + It 'Should have the expected Note value' { + $result.note | Should -Be $defaultCardUpdated + } + + $null = $card | Set-GitHubProjectCard -Note $defaultCard + $result = $card | Get-GitHubProjectCard + + It 'Should have the updated Note' { + $result.note | Should -Be $defaultCard + } + + It 'Has the expected type and additional properties' { + $result.PSObject.TypeNames[0] | Should -Be 'GitHub.ProjectCard' + $result.CardId | Should -Be $result.id + $result.ProjectId | Should -Be $project.id + $result.ColumnId | Should -Be $column.id + $result.IssueNumber | Should -BeNullOrEmpty + $result.RepositoryUrl | Should -BeNullOrEmpty + $result.PullRequestNumber | Should -BeNullOrEmpty + $result.creator.PSObject.TypeNames[0] | Should -Be 'GitHub.User' } } @@ -119,24 +221,24 @@ try $result = Get-GitHubProjectCard -Card $cardArchived.id It 'Should get card' { - $result | Should Not BeNullOrEmpty + $result | Should -Not -BeNullOrEmpty } It 'Card is archived' { - $result.Archived | Should be $true + $result.Archived | Should -Be $true } } Context 'Restore a card' { - $null = Set-GitHubProjectCard -Card $cardArchived.id -Restore + $null = $cardArchived | Set-GitHubProjectCard -Restore $result = Get-GitHubProjectCard -Card $cardArchived.id It 'Should get card' { - $result | Should Not BeNullOrEmpty + $result | Should -Not -BeNullOrEmpty } It 'Card is not archived' { - $result.Archived | Should be $false + $result.Archived | Should -Be $false } } @@ -145,7 +247,18 @@ try $results = @(Get-GitHubProjectCard -Column $column.id) It 'Card is now top' { - $results[0].note | Should be $defaultCardTwo + $results[0].note | Should -Be $defaultCardTwo + } + + It 'Has the expected type and additional properties' { + $results[0].PSObject.TypeNames[0] | Should -Be 'GitHub.ProjectCard' + $results[0].CardId | Should -Be $results[0].id + $results[0].ProjectId | Should -Be $project.id + $results[0].ColumnId | Should -Be $column.id + $results[0].IssueNumber | Should -BeNullOrEmpty + $results[0].RepositoryUrl | Should -BeNullOrEmpty + $results[0].PullRequestNumber | Should -BeNullOrEmpty + $results[0].creator.PSObject.TypeNames[0] | Should -Be 'GitHub.User' } } @@ -154,7 +267,38 @@ try $results = @(Get-GitHubProjectCard -Column $column.id) It 'Card now exists in new column' { - $results[1].note | Should be $defaultCardTwo + $results[1].note | Should -Be $defaultCardTwo + } + + It 'Has the expected type and additional properties' { + $results[1].PSObject.TypeNames[0] | Should -Be 'GitHub.ProjectCard' + $results[1].CardId | Should -Be $results[1].id + $results[1].ProjectId | Should -Be $project.id + $results[1].ColumnId | Should -Be $column.id + $results[1].IssueNumber | Should -BeNullOrEmpty + $results[1].RepositoryUrl | Should -BeNullOrEmpty + $results[1].PullRequestNumber | Should -BeNullOrEmpty + $results[1].creator.PSObject.TypeNames[0] | Should -Be 'GitHub.User' + } + } + + Context 'Move card using before parameter (card on pipeline)' { + $null = $cardTwo | Move-GitHubProjectCard -After $card.id + $results = @($column | Get-GitHubProjectCard) + + It 'Card now exists in new column' { + $results[1].note | Should -Be $defaultCardTwo + } + + It 'Has the expected type and additional properties' { + $results[1].PSObject.TypeNames[0] | Should -Be 'GitHub.ProjectCard' + $results[1].CardId | Should -Be $results[1].id + $results[1].ProjectId | Should -Be $project.id + $results[1].ColumnId | Should -Be $column.id + $results[1].IssueNumber | Should -BeNullOrEmpty + $results[1].RepositoryUrl | Should -BeNullOrEmpty + $results[1].PullRequestNumber | Should -BeNullOrEmpty + $results[1].creator.PSObject.TypeNames[0] | Should -Be 'GitHub.User' } } @@ -163,24 +307,52 @@ try $results = @(Get-GitHubProjectCard -Column $columnTwo.id) It 'Card now exists in new column' { - $results[0].note | Should be $defaultCardTwo + $results[0].note | Should -Be $defaultCardTwo + } + + It 'Has the expected type and additional properties' { + $results[0].PSObject.TypeNames[0] | Should -Be 'GitHub.ProjectCard' + $results[0].CardId | Should -Be $results[0].id + $results[0].ProjectId | Should -Be $project.id + $results[0].ColumnId | Should -Be $columnTwo.id + $results[0].IssueNumber | Should -BeNullOrEmpty + $results[0].RepositoryUrl | Should -BeNullOrEmpty + $results[0].PullRequestNumber | Should -BeNullOrEmpty + $results[0].creator.PSObject.TypeNames[0] | Should -Be 'GitHub.User' + } + } + + Context 'Move card to another column (with column on pipeline)' { + $null = ($column | Move-GitHubProjectCard -Card $cardTwo.id -Top) + $result = $cardTwo | Get-GitHubProjectCard + + It 'Card now exists in new column' { + $result.ColumnId | Should -Be $column.ColumnId + } + + It 'Has the expected type and additional properties' { + $result.PSObject.TypeNames[0] | Should -Be 'GitHub.ProjectCard' + $result.CardId | Should -Be $result.id + $result.ProjectId | Should -Be $project.id + $result.ColumnId | Should -Be $column.id + $result.IssueNumber | Should -BeNullOrEmpty + $result.RepositoryUrl | Should -BeNullOrEmpty + $result.PullRequestNumber | Should -BeNullOrEmpty + $result.creator.PSObject.TypeNames[0] | Should -Be 'GitHub.User' } } Context 'Move command throws appropriate error' { It 'Appropriate error is thrown' { - { Move-GitHubProjectCard -Card $cardTwo.id -Top -Bottom } | Should Throw 'You must use one (and only one) of the parameters Top, Bottom or After.' + { Move-GitHubProjectCard -Card $cardTwo.id -Top -Bottom } | Should -Throw 'You must use one (and only one) of the parameters Top, Bottom or After.' } } } - Describe 'Create Project Cards' -tag new { + Describe 'Create Project Cards' { Context 'Create project card with note' { BeforeAll { $card = @{id = 0} - - # Avoid PSScriptAnalyzer PSUseDeclaredVarsMoreThanAssignments - $card = $card } AfterAll { @@ -192,20 +364,62 @@ try $result = Get-GitHubProjectCard -Card $card.id It 'Card exists' { - $result | Should Not BeNullOrEmpty + $result | Should -Not -BeNullOrEmpty } It 'Note is correct' { - $result.note | Should be $defaultCard + $result.note | Should -Be $defaultCard + } + + It 'Has the expected type and additional properties' { + $result.PSObject.TypeNames[0] | Should -Be 'GitHub.ProjectCard' + $result.CardId | Should -Be $result.id + $result.ProjectId | Should -Be $project.id + $result.ColumnId | Should -Be $column.id + $result.IssueNumber | Should -BeNullOrEmpty + $result.RepositoryUrl | Should -BeNullOrEmpty + $result.PullRequestNumber | Should -BeNullOrEmpty + $result.creator.PSObject.TypeNames[0] | Should -Be 'GitHub.User' } } - Context 'Create project card from issue' { + Context 'Create project card with note (with column object via pipeline)' { BeforeAll { $card = @{id = 0} + } + + AfterAll { + $null = Remove-GitHubProjectCard -Card $card.id -Confirm:$false + Remove-Variable -Name card + } + + $newCard = $column | New-GitHubProjectCard -Note $defaultCard + $card.id = $newCard.id + $result = $newCard | Get-GitHubProjectCard - # Avoid PSScriptAnalyzer PSUseDeclaredVarsMoreThanAssignments - $card = $card + It 'Card exists' { + $result | Should -Not -BeNullOrEmpty + } + + It 'Note is correct' { + $result.note | Should -Be $defaultCard + } + + It 'Has the expected type and additional properties' { + $result.PSObject.TypeNames[0] | Should -Be 'GitHub.ProjectCard' + $result.CardId | Should -Be $result.id + $result.ProjectId | Should -Be $project.id + $result.ColumnId | Should -Be $column.id + $result.IssueNumber | Should -BeNullOrEmpty + $result.RepositoryUrl | Should -BeNullOrEmpty + $result.PullRequestNumber | Should -BeNullOrEmpty + $result.creator.PSObject.TypeNames[0] | Should -Be 'GitHub.User' + } + } + + Context 'Create project card from issue' { + BeforeAll { + $card = @{id = 0} } AfterAll { @@ -213,31 +427,86 @@ try Remove-Variable -Name card } - $card.id = (New-GitHubProjectCard -Column $column.id -ContentId $issue.id -ContentType 'Issue').id + $card.id = (New-GitHubProjectCard -Column $column.id -IssueId $issue.id).id $result = Get-GitHubProjectCard -Card $card.id It 'Card exists' { - $result | Should Not BeNullOrEmpty + $result | Should -Not -BeNullOrEmpty } It 'Content url is for an issue' { - $result.content_url | Should match 'issues' + $result.content_url | Should -Match 'issues' + } + + It 'Has the expected type and additional properties' { + $result.PSObject.TypeNames[0] | Should -Be 'GitHub.ProjectCard' + $result.CardId | Should -Be $result.id + $result.ProjectId | Should -Be $project.id + $result.ColumnId | Should -Be $column.id + $result.IssueNumber | Should -Be $issue.number + $result.RepositoryUrl | Should -Be $issue.RepositoryUrl + $result.PullRequestNumber | Should -BeNullOrEmpty + $result.creator.PSObject.TypeNames[0] | Should -Be 'GitHub.User' } } + + Context 'Create project card from issue (with issue object on pipeline)' { + BeforeAll { + $card = @{id = 0} + } + + AfterAll { + $null = Remove-GitHubProjectCard -Card $card.id -Force + Remove-Variable -Name card + } + + $newCard = $issue | New-GitHubProjectCard -Column $column.id + $card.id = $newCard.id + $result = $newCard | Get-GitHubProjectCard + + It 'Card exists' { + $result | Should -Not -BeNullOrEmpty + } + + It 'Content url is for an issue' { + $result.content_url | Should -Match 'issues' + } + + It 'Has the expected type and additional properties' { + $result.PSObject.TypeNames[0] | Should -Be 'GitHub.ProjectCard' + $result.CardId | Should -Be $result.id + $result.ProjectId | Should -Be $project.id + $result.ColumnId | Should -Be $column.id + $result.IssueNumber | Should -Be $issue.number + $result.RepositoryUrl | Should -Be $issue.RepositoryUrl + $result.PullRequestNumber | Should -BeNullOrEmpty + $result.creator.PSObject.TypeNames[0] | Should -Be 'GitHub.User' + } + } + + # TODO: Create a test that verifies cards created based on a pull request } Describe 'Remove card' { Context 'Remove card' { BeforeAll { $card = New-GitHubProjectCard -Column $column.id -Note $defaultCard - - # Avoid PSScriptAnalyzer PSUseDeclaredVarsMoreThanAssignments - $card = $card } $null = Remove-GitHubProjectCard -Card $card.id -Confirm:$false It 'Project card should be removed' { - {Get-GitHubProjectCard -Card $card.id} | Should Throw + {Get-GitHubProjectCard -Card $card.id} | Should -Throw + } + } + + Context 'Remove card (via pipeline)' { + BeforeAll { + $card = $column | New-GitHubProjectCard -Note $defaultCard + } + + $null = $card | Remove-GitHubProjectCard -Force + It 'Project card should be removed' { + {$card | Get-GitHubProjectCard} | Should -Throw } } } diff --git a/Tests/GitHubProjectColumns.tests.ps1 b/Tests/GitHubProjectColumns.tests.ps1 index 6eb3f047..a2fcbb09 100644 --- a/Tests/GitHubProjectColumns.tests.ps1 +++ b/Tests/GitHubProjectColumns.tests.ps1 @@ -6,6 +6,11 @@ Tests for GitHubProjectColumns.ps1 module #> +[CmdletBinding()] +[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseDeclaredVarsMoreThanAssignments', '', + Justification='Suppress false positives in Pester code blocks')] +param() + # This is common test code setup logic for all Pester test files $moduleRootPath = Split-Path -Path $PSScriptRoot -Parent . (Join-Path -Path $moduleRootPath -ChildPath 'Tests\Common.ps1') @@ -22,14 +27,11 @@ try Set-Variable -Force -Scope Script -Option ReadOnly -Visibility Private -Name $_.Key -Value $_.Value } - $project = New-GitHubProject -UserProject -Name $defaultProject + $project = New-GitHubProject -UserProject -ProjectName $defaultProject Describe 'Getting Project Columns' { BeforeAll { - $column = New-GitHubProjectColumn -Project $project.id -Name $defaultColumn - - # Avoid PSScriptAnalyzer PSUseDeclaredVarsMoreThanAssignments - $column = $column + $column = New-GitHubProjectColumn -Project $project.id -ColumnName $defaultColumn } AfterAll { @@ -39,27 +41,82 @@ try Context 'Get columns for a project' { $results = @(Get-GitHubProjectColumn -Project $project.id) It 'Should get column' { - $results | Should Not BeNullOrEmpty + $results | Should -Not -BeNullOrEmpty + } + + It 'Should only have one column' { + $results.Count | Should -Be 1 + } + + It 'Name is correct' { + $results[0].name | Should -Be $defaultColumn + } + + It 'Should have the expected type and additional properties' { + $results[0].PSObject.TypeNames[0] | Should -Be 'GitHub.ProjectColumn' + $results[0].ColumnId | Should -Be $results[0].id + $results[0].ColumnName | Should -Be $results[0].name + $results[0].ProjectId | Should -Be $project.id + } + } + + Context 'Get columns for a project (via pipeline)' { + $results = @($project | Get-GitHubProjectColumn) + It 'Should get column' { + $results | Should -Not -BeNullOrEmpty } It 'Should only have one column' { - $results.Count | Should Be 1 + $results.Count | Should -Be 1 } It 'Name is correct' { - $results[0].name | Should Be $defaultColumn + $results[0].name | Should -Be $defaultColumn + } + + It 'Should have the expected type and additional properties' { + $results[0].PSObject.TypeNames[0] | Should -Be 'GitHub.ProjectColumn' + $results[0].ColumnId | Should -Be $results[0].id + $results[0].ColumnName | Should -Be $results[0].name + $results[0].ProjectId | Should -Be $project.id + } + } + + Context 'Get specific column' { + $result = Get-GitHubProjectColumn -Column $column.id + + It 'Should be the right column' { + $result.id | Should -Be $column.id + } + + It 'Should have the expected type and additional properties' { + $result.PSObject.TypeNames[0] | Should -Be 'GitHub.ProjectColumn' + $result.ColumnId | Should -Be $result.id + $result.ColumnName | Should -Be $result.name + $result.ProjectId | Should -Be $project.id + } + } + + Context 'Get specific column (via pipeline)' { + $result = $column | Get-GitHubProjectColumn + + It 'Should be the right column' { + $result.id | Should -Be $column.id + } + + It 'Should have the expected type and additional properties' { + $result.PSObject.TypeNames[0] | Should -Be 'GitHub.ProjectColumn' + $result.ColumnId | Should -Be $result.id + $result.ColumnName | Should -Be $result.name + $result.ProjectId | Should -Be $project.id } } } Describe 'Modify Project Column' { BeforeAll { - $column = New-GitHubProjectColumn -Project $project.id -Name $defaultColumn - $columntwo = New-GitHubProjectColumn -Project $project.id -Name $defaultColumnTwo - - # Avoid PSScriptAnalyzer PSUseDeclaredVarsMoreThanAssignments - $column = $column - $columnTwo = $columnTwo + $column = New-GitHubProjectColumn -Project $project.id -ColumnName $defaultColumn + $columntwo = New-GitHubProjectColumn -Project $project.id -ColumnName $defaultColumnTwo } AfterAll { @@ -68,15 +125,22 @@ try } Context 'Modify column name' { - $null = Set-GitHubProjectColumn -Column $column.id -Name $defaultColumnUpdate + $null = Set-GitHubProjectColumn -Column $column.id -ColumnName $defaultColumnUpdate $result = Get-GitHubProjectColumn -Column $column.id It 'Should get column' { - $result | Should Not BeNullOrEmpty + $result | Should -Not -BeNullOrEmpty } It 'Name has been updated' { - $result.name | Should Be $defaultColumnUpdate + $result.name | Should -Be $defaultColumnUpdate + } + + It 'Should have the expected type and additional properties' { + $result.PSObject.TypeNames[0] | Should -Be 'GitHub.ProjectColumn' + $result.ColumnId | Should -Be $result.id + $result.ColumnName | Should -Be $result.name + $result.ProjectId | Should -Be $project.id } } @@ -85,11 +149,18 @@ try $results = @(Get-GitHubProjectColumn -Project $project.id) It 'Should still have more than one column in the project' { - $results.Count | Should Be 2 + $results.Count | Should -Be 2 } It 'Column is now in the first position' { - $results[0].name | Should Be $defaultColumnTwo + $results[0].name | Should -Be $defaultColumnTwo + } + + It 'Should have the expected type and additional properties' { + $results[0].PSObject.TypeNames[0] | Should -Be 'GitHub.ProjectColumn' + $results[0].ColumnId | Should -Be $results[0].id + $results[0].ColumnName | Should -Be $results[0].name + $results[0].ProjectId | Should -Be $project.id } } @@ -98,13 +169,20 @@ try $results = @(Get-GitHubProjectColumn -Project $project.id) It 'Column is now not in the first position' { - $results[1].name | Should Be $defaultColumnTwo + $results[1].name | Should -Be $defaultColumnTwo + } + + It 'Should have the expected type and additional properties' { + $results[1].PSObject.TypeNames[0] | Should -Be 'GitHub.ProjectColumn' + $results[1].ColumnId | Should -Be $columntwo.ColumnId + $results[1].ColumnName | Should -Be $columntwo.ColumnName + $results[1].ProjectId | Should -Be $project.id } } Context 'Move command throws appropriate error' { It 'Expected error returned' { - { Move-GitHubProjectColumn -Column $column.id -First -Last } | Should Throw 'You must use one (and only one) of the parameters First, Last or After.' + { Move-GitHubProjectColumn -Column $column.id -First -Last } | Should -Throw 'You must use one (and only one) of the parameters First, Last or After.' } } } @@ -113,9 +191,64 @@ try Context 'Create project column' { BeforeAll { $column = @{id = 0} + } - # Avoid PSScriptAnalyzer PSUseDeclaredVarsMoreThanAssignments - $column = $column + AfterAll { + $null = Remove-GitHubProjectColumn -Column $column.id -Force + Remove-Variable -Name column + } + + $column.id = (New-GitHubProjectColumn -Project $project.id -ColumnName $defaultColumn).id + $result = Get-GitHubProjectColumn -Column $column.id + + It 'Column exists' { + $result | Should -Not -BeNullOrEmpty + } + + It 'Name is correct' { + $result.name | Should -Be $defaultColumn + } + + It 'Should have the expected type and additional properties' { + $result.PSObject.TypeNames[0] | Should -Be 'GitHub.ProjectColumn' + $result.ColumnId | Should -Be $result.id + $result.ColumnName | Should -Be $result.name + $result.ProjectId | Should -Be $project.id + } + } + + Context 'Create project column (object via pipeline)' { + BeforeAll { + $column = @{id = 0} + } + + AfterAll { + $null = Remove-GitHubProjectColumn -Column $column.id -Force + Remove-Variable -Name column + } + + $column.id = ($project | New-GitHubProjectColumn -ColumnName $defaultColumn).id + $result = Get-GitHubProjectColumn -Column $column.id + + It 'Column exists' { + $result | Should -Not -BeNullOrEmpty + } + + It 'Name is correct' { + $result.name | Should -Be $defaultColumn + } + + It 'Should have the expected type and additional properties' { + $result.PSObject.TypeNames[0] | Should -Be 'GitHub.ProjectColumn' + $result.ColumnId | Should -Be $result.id + $result.ColumnName | Should -Be $result.name + $result.ProjectId | Should -Be $project.id + } + } + + Context 'Create project column (name via pipeline)' { + BeforeAll { + $column = @{id = 0} } AfterAll { @@ -123,15 +256,22 @@ try Remove-Variable -Name column } - $column.id = (New-GitHubProjectColumn -Project $project.id -Name $defaultColumn).id + $column.id = ($defaultColumn | New-GitHubProjectColumn -Project $project.id).id $result = Get-GitHubProjectColumn -Column $column.id It 'Column exists' { - $result | Should Not BeNullOrEmpty + $result | Should -Not -BeNullOrEmpty } It 'Name is correct' { - $result.name | Should Be $defaultColumn + $result.name | Should -Be $defaultColumn + } + + It 'Should have the expected type and additional properties' { + $result.PSObject.TypeNames[0] | Should -Be 'GitHub.ProjectColumn' + $result.ColumnId | Should -Be $result.id + $result.ColumnName | Should -Be $result.name + $result.ProjectId | Should -Be $project.id } } } @@ -139,15 +279,23 @@ try Describe 'Remove project column' { Context 'Remove project column' { BeforeAll { - $column = New-GitHubProjectColumn -Project $project.id -Name $defaultColumn - - # Avoid PSScriptAnalyzer PSUseDeclaredVarsMoreThanAssignments - $column = $column + $column = New-GitHubProjectColumn -Project $project.id -ColumnName $defaultColumn } $null = Remove-GitHubProjectColumn -Column $column.id -Confirm:$false It 'Project column should be removed' { - {Get-GitHubProjectColumn -Column $column.id} | Should Throw + {Get-GitHubProjectColumn -Column $column.id} | Should -Throw + } + } + + Context 'Remove project column (via pipeline)' { + BeforeAll { + $column = New-GitHubProjectColumn -Project $project.id -ColumnName $defaultColumn + } + + $column | Remove-GitHubProjectColumn -Force + It 'Project column should be removed' { + {$column | Get-GitHubProjectColumn} | Should -Throw } } } diff --git a/Tests/GitHubProjects.tests.ps1 b/Tests/GitHubProjects.tests.ps1 index dec75e4a..ccde85d6 100644 --- a/Tests/GitHubProjects.tests.ps1 +++ b/Tests/GitHubProjects.tests.ps1 @@ -6,6 +6,11 @@ Tests for GitHubProjects.ps1 module #> +[CmdletBinding()] +[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseDeclaredVarsMoreThanAssignments', '', + Justification='Suppress false positives in Pester code blocks')] +param() + # This is common test code setup logic for all Pester test files $moduleRootPath = Split-Path -Path $PSScriptRoot -Parent . (Join-Path -Path $moduleRootPath -ChildPath 'Tests\Common.ps1') @@ -37,10 +42,7 @@ try Describe 'Getting Project' { Context 'Get User projects' { BeforeAll { - $project = New-GitHubProject -UserProject -Name $defaultUserProject -Description $defaultUserProjectDesc - - # Avoid PSScriptAnalyzer PSUseDeclaredVarsMoreThanAssignments - $project = $project + $project = New-GitHubProject -UserProject -ProjectName $defaultUserProject -Description $defaultUserProjectDesc } AfterAll { @@ -49,28 +51,31 @@ try $results = @(Get-GitHubProject -UserName $script:ownerName | Where-Object Name -eq $defaultUserProject) It 'Should get project' { - $results | Should Not BeNullOrEmpty + $results | Should -Not -BeNullOrEmpty } It 'Should only get a single project' { - $results.Count | Should Be 1 + $results.Count | Should -Be 1 } It 'Name is correct' { - $results[0].name | Should be $defaultUserProject + $results[0].name | Should -Be $defaultUserProject } It 'Description is correct' { - $results[0].body | Should be $defaultUserProjectDesc + $results[0].body | Should -Be $defaultUserProjectDesc + } + + It 'Should have the expected type and additional properties' { + $results[0].PSObject.TypeNames[0] | Should -Be 'GitHub.Project' + $results[0].RepositoryUrl | Should -BeNullOrEmpty # no RepositoryUrl for user projects + $results[0].ProjectId | Should -Be $results[0].id } } Context 'Get Organization projects' { BeforeAll { - $project = New-GitHubProject -OrganizationName $script:organizationName -Name $defaultOrgProject -Description $defaultOrgProjectDesc - - # Avoid PSScriptAnalyzer PSUseDeclaredVarsMoreThanAssignments - $project = $project + $project = New-GitHubProject -OrganizationName $script:organizationName -ProjectName $defaultOrgProject -Description $defaultOrgProjectDesc } AfterAll { @@ -79,28 +84,34 @@ try $results = @(Get-GitHubProject -OrganizationName $script:organizationName | Where-Object Name -eq $defaultOrgProject) It 'Should get project' { - $results | Should Not BeNullOrEmpty + $results | Should -Not -BeNullOrEmpty } It 'Should only get a single project' { - $results.Count | Should Be 1 + $results.Count | Should -Be 1 } It 'Name is correct' { - $results[0].name | Should be $defaultOrgProject + $results[0].name | Should -Be $defaultOrgProject } It 'Description is correct' { - $results[0].body | Should be $defaultOrgProjectDesc + $results[0].body | Should -Be $defaultOrgProjectDesc + } + + It 'Should have the expected type and additional properties' { + $elements = Split-GitHubUri -Uri $results[0].html_url + $repositoryUrl = Join-GitHubUri @elements + + $results[0].PSObject.TypeNames[0] | Should -Be 'GitHub.Project' + $results[0].RepositoryUrl | Should -Be $repositoryUrl + $results[0].ProjectId | Should -Be $results[0].id } } Context 'Get Repo projects' { BeforeAll { - $project = New-GitHubProject -OwnerName $script:ownerName -RepositoryName $repo.name -Name $defaultRepoProject -Description $defaultRepoProjectDesc - - # Avoid PSScriptAnalyzer PSUseDeclaredVarsMoreThanAssignments - $project = $project + $project = New-GitHubProject -OwnerName $script:ownerName -RepositoryName $repo.name -ProjectName $defaultRepoProject -Description $defaultRepoProjectDesc } AfterAll { @@ -109,54 +120,139 @@ try $results = @(Get-GitHubProject -OwnerName $script:ownerName -RepositoryName $repo.name | Where-Object Name -eq $defaultRepoProject) It 'Should get project' { - $results | Should Not BeNullOrEmpty + $results | Should -Not -BeNullOrEmpty } It 'Should only get a single project' { - $results.Count | Should Be 1 + $results.Count | Should -Be 1 } It 'Name is correct' { - $results[0].name | Should be $defaultRepoProject + $results[0].name | Should -Be $defaultRepoProject } It 'Description is correct' { - $results[0].body | Should be $defaultRepoProjectDesc + $results[0].body | Should -Be $defaultRepoProjectDesc + } + + It 'Should have the expected type and additional properties' { + $results[0].PSObject.TypeNames[0] | Should -Be 'GitHub.Project' + $results[0].RepositoryUrl | Should -Be $repo.RepositoryUrl + $results[0].ProjectId | Should -Be $results[0].id + $results[0].creator.PSObject.TypeNames[0] | Should -Be 'GitHub.User' } } - Context 'Get a closed Repo project' { + Context 'Get a closed Repo project (via pipeline)' { BeforeAll { - $project = New-GitHubProject -OwnerName $script:ownerName -RepositoryName $repo.name -Name $defaultProjectClosed -Description $defaultProjectClosedDesc + $project = $repo | New-GitHubProject -ProjectName $defaultProjectClosed -Description $defaultProjectClosedDesc $null = Set-GitHubProject -Project $project.id -State Closed - - # Avoid PSScriptAnalyzer PSUseDeclaredVarsMoreThanAssignments - $project = $project } AfterAll { $null = Remove-GitHubProject -Project $project.id -Confirm:$false } - $results = @(Get-GitHubProject -OwnerName $script:ownerName -RepositoryName $repo.name -State 'Closed' | Where-Object Name -eq $defaultProjectClosed) + $results = @($repo | Get-GitHubProject -State 'Closed') It 'Should get project' { - $results | Should Not BeNullOrEmpty + $results | Should -Not -BeNullOrEmpty } It 'Should only get a single project' { - $results.Count | Should Be 1 + $results.Count | Should -Be 1 } It 'Name is correct' { - $results[0].name | Should be $defaultProjectClosed + $results[0].name | Should -Be $defaultProjectClosed } It 'Description is correct' { - $results[0].body | Should be $defaultProjectClosedDesc + $results[0].body | Should -Be $defaultProjectClosedDesc } It 'State is correct' { - $results[0].state | Should be "Closed" + $results[0].state | Should -Be "Closed" + } + + It 'Should have the expected type and additional properties' { + $results[0].PSObject.TypeNames[0] | Should -Be 'GitHub.Project' + $results[0].RepositoryUrl | Should -Be $repo.RepositoryUrl + $results[0].ProjectId | Should -Be $results[0].id + $results[0].creator.PSObject.TypeNames[0] | Should -Be 'GitHub.User' + } + } + + Context 'Get a specific project (by parameter)' { + BeforeAll { + $project = New-GitHubProject -OwnerName $script:ownerName -RepositoryName $repo.name -ProjectName $defaultRepoProject -Description $defaultRepoProjectDesc + } + + AfterAll { + $null = Remove-GitHubProject -Project $project.id -Confirm:$false + } + + $result = Get-GitHubProject -Project $project.id + It 'Should get project' { + $result | Should -Not -BeNullOrEmpty + } + + It 'Name is correct' { + $result.name | Should -Be $defaultRepoProject + } + + It 'Description is correct' { + $result.body | Should -Be $defaultRepoProjectDesc + } + + It 'Should have the expected type and additional properties' { + $result.PSObject.TypeNames[0] | Should -Be 'GitHub.Project' + $result.RepositoryUrl | Should -Be $repo.RepositoryUrl + $result.ProjectId | Should -Be $project.id + $result.creator.PSObject.TypeNames[0] | Should -Be 'GitHub.User' + } + } + + Context 'Get a specific project (by pipeline object)' { + BeforeAll { + $project = $repo | New-GitHubProject -ProjectName $defaultRepoProject -Description $defaultRepoProjectDesc + } + + AfterAll { + $project | Remove-GitHubProject -Force + } + + $result = $project | Get-GitHubProject + It 'Should get the right project' { + $result.id | Should -Be $project.id + } + + It 'Should have the expected type and additional properties' { + $result.PSObject.TypeNames[0] | Should -Be 'GitHub.Project' + $result.RepositoryUrl | Should -Be $repo.RepositoryUrl + $result.ProjectId | Should -Be $project.id + $result.creator.PSObject.TypeNames[0] | Should -Be 'GitHub.User' + } + } + + Context 'Get a specific project (with ID via pipeline)' { + BeforeAll { + $project = $repo | New-GitHubProject -ProjectName $defaultRepoProject -Description $defaultRepoProjectDesc + } + + AfterAll { + $project | Remove-GitHubProject -Force + } + + $result = $project.id | Get-GitHubProject + It 'Should get the right project' { + $result.id | Should -Be $project.id + } + + It 'Should have the expected type and additional properties' { + $result.PSObject.TypeNames[0] | Should -Be 'GitHub.Project' + $result.RepositoryUrl | Should -Be $repo.RepositoryUrl + $result.ProjectId | Should -Be $project.id + $result.creator.PSObject.TypeNames[0] | Should -Be 'GitHub.User' } } } @@ -164,10 +260,7 @@ try Describe 'Modify Project' { Context 'Modify User projects' { BeforeAll { - $project = New-GitHubProject -UserProject -Name $defaultUserProject -Description $defaultUserProjectDesc - - # Avoid PSScriptAnalyzer PSUseDeclaredVarsMoreThanAssignments - $project = $project + $project = New-GitHubProject -UserProject -ProjectName $defaultUserProject -Description $defaultUserProjectDesc } AfterAll { @@ -177,24 +270,90 @@ try $null = Set-GitHubProject -Project $project.id -Description $modifiedUserProjectDesc $result = Get-GitHubProject -Project $project.id It 'Should get project' { - $result | Should Not BeNullOrEmpty + $result | Should -Not -BeNullOrEmpty } It 'Name is correct' { - $result.name | Should be $defaultUserProject + $result.name | Should -Be $defaultUserProject } It 'Description should be updated' { - $result.body | Should be $modifiedUserProjectDesc + $result.body | Should -Be $modifiedUserProjectDesc + } + + It 'Should have the expected type and additional properties' { + $result.PSObject.TypeNames[0] | Should -Be 'GitHub.Project' + $result.RepositoryUrl | Should -BeNullOrEmpty # no RepositoryUrl for user projects + $result.ProjectId | Should -Be $result.id + $result.creator.PSObject.TypeNames[0] | Should -Be 'GitHub.User' } } - Context 'Modify Organization projects' { + Context 'Modify User projects (via ID in pipeline)' { BeforeAll { - $project = New-GitHubProject -OrganizationName $script:organizationName -Name $defaultOrgProject -Description $defaultOrgProjectDesc + $project = New-GitHubProject -UserProject -ProjectName $defaultUserProject -Description $defaultUserProjectDesc + } + + AfterAll { + $null = Remove-GitHubProject -Project $project.id -Confirm:$false + } + + $null = $project.id | Set-GitHubProject -Description $modifiedUserProjectDesc + $result = Get-GitHubProject -Project $project.id + It 'Should get project' { + $result | Should -Not -BeNullOrEmpty + } - # Avoid PSScriptAnalyzer PSUseDeclaredVarsMoreThanAssignments - $project = $project + It 'Name is correct' { + $result.name | Should -Be $defaultUserProject + } + + It 'Description should be updated' { + $result.body | Should -Be $modifiedUserProjectDesc + } + + It 'Should have the expected type and additional properties' { + $result.PSObject.TypeNames[0] | Should -Be 'GitHub.Project' + $result.RepositoryUrl | Should -BeNullOrEmpty # no RepositoryUrl for user projects + $result.ProjectId | Should -Be $result.id + $result.creator.PSObject.TypeNames[0] | Should -Be 'GitHub.User' + } + } + + Context 'Modify User projects (via object in pipeline)' { + BeforeAll { + $project = New-GitHubProject -UserProject -ProjectName $defaultUserProject -Description $defaultUserProjectDesc + } + + AfterAll { + $null = Remove-GitHubProject -Project $project.id -Confirm:$false + } + + $null = $project | Set-GitHubProject -Description $modifiedUserProjectDesc + $result = Get-GitHubProject -Project $project.id + It 'Should get project' { + $result | Should -Not -BeNullOrEmpty + } + + It 'Name is correct' { + $result.name | Should -Be $defaultUserProject + } + + It 'Description should be updated' { + $result.body | Should -Be $modifiedUserProjectDesc + } + + It 'Should have the expected type and additional properties' { + $result.PSObject.TypeNames[0] | Should -Be 'GitHub.Project' + $result.RepositoryUrl | Should -BeNullOrEmpty # no RepositoryUrl for user projects + $result.ProjectId | Should -Be $result.id + $result.creator.PSObject.TypeNames[0] | Should -Be 'GitHub.User' + } + } + + Context 'Modify Organization projects' { + BeforeAll { + $project = New-GitHubProject -OrganizationName $script:organizationName -ProjectName $defaultOrgProject -Description $defaultOrgProjectDesc } AfterAll { @@ -204,33 +363,39 @@ try $null = Set-GitHubProject -Project $project.id -Description $modifiedOrgProjectDesc -Private:$false -OrganizationPermission Admin $result = Get-GitHubProject -Project $project.id It 'Should get project' { - $result | Should Not BeNullOrEmpty + $result | Should -Not -BeNullOrEmpty } It 'Name is correct' { - $result.name | Should be $defaultOrgProject + $result.name | Should -Be $defaultOrgProject } It 'Description should be updated' { - $result.body | Should be $modifiedOrgProjectDesc + $result.body | Should -Be $modifiedOrgProjectDesc } It 'Visibility should be updated to public' { - $result.private | Should be $false + $result.private | Should -Be $false } It 'Organization permission should be updated to admin' { - $result.organization_permission | Should be 'admin' + $result.organization_permission | Should -Be 'admin' } + It 'Should have the expected type and additional properties' { + $elements = Split-GitHubUri -Uri $result.html_url + $repositoryUrl = Join-GitHubUri @elements + + $result.PSObject.TypeNames[0] | Should -Be 'GitHub.Project' + $result.RepositoryUrl | Should -Be $repositoryUrl + $result.ProjectId | Should -Be $result.id + $result.creator.PSObject.TypeNames[0] | Should -Be 'GitHub.User' + } } Context 'Modify Repo projects' { BeforeAll { - $project = New-GitHubProject -OwnerName $script:ownerName -RepositoryName $repo.name -Name $defaultRepoProject -Description $defaultRepoProjectDesc - - # Avoid PSScriptAnalyzer PSUseDeclaredVarsMoreThanAssignments - $project = $project + $project = New-GitHubProject -OwnerName $script:ownerName -RepositoryName $repo.name -ProjectName $defaultRepoProject -Description $defaultRepoProjectDesc } AfterAll { @@ -240,15 +405,22 @@ try $null = Set-GitHubProject -Project $project.id -Description $modifiedRepoProjectDesc $result = Get-GitHubProject -Project $project.id It 'Should get project' { - $result | Should Not BeNullOrEmpty + $result | Should -Not -BeNullOrEmpty } It 'Name is correct' { - $result.name | Should be $defaultRepoProject + $result.name | Should -Be $defaultRepoProject } It 'Description should be updated' { - $result.body | Should be $modifiedRepoProjectDesc + $result.body | Should -Be $modifiedRepoProjectDesc + } + + It 'Should have the expected type and additional properties' { + $result.PSObject.TypeNames[0] | Should -Be 'GitHub.Project' + $result.RepositoryUrl | Should -Be $repo.RepositoryUrl + $result.ProjectId | Should -Be $result.id + $result.creator.PSObject.TypeNames[0] | Should -Be 'GitHub.User' } } } @@ -257,9 +429,38 @@ try Context 'Create User projects' { BeforeAll { $project = @{id = 0} + } - # Avoid PSScriptAnalyzer PSUseDeclaredVarsMoreThanAssignments - $project = $project + AfterAll { + $null = Remove-GitHubProject -Project $project.id -Confirm:$false + Remove-Variable project + } + + $project.id = (New-GitHubProject -UserProject -ProjectName $defaultUserProject -Description $defaultUserProjectDesc).id + $result = Get-GitHubProject -Project $project.id + It 'Project exists' { + $result | Should -Not -BeNullOrEmpty + } + + It 'Name is correct' { + $result.name | Should -Be $defaultUserProject + } + + It 'Description should be updated' { + $result.body | Should -Be $defaultUserProjectDesc + } + + It 'Should have the expected type and additional properties' { + $result.PSObject.TypeNames[0] | Should -Be 'GitHub.Project' + $result.RepositoryUrl | Should -BeNullOrEmpty # no RepositoryUrl for user projects + $result.ProjectId | Should -Be $result.id + $result.creator.PSObject.TypeNames[0] | Should -Be 'GitHub.User' + } + } + + Context 'Create User project (title on pipeline)' { + BeforeAll { + $project = @{id = 0} } AfterAll { @@ -267,27 +468,31 @@ try Remove-Variable project } - $project.id = (New-GitHubProject -UserProject -Name $defaultUserProject -Description $defaultUserProjectDesc).id + $project.id = ($defaultUserProject | New-GitHubProject -UserProject -Description $defaultUserProjectDesc).id $result = Get-GitHubProject -Project $project.id It 'Project exists' { - $result | Should Not BeNullOrEmpty + $result | Should -Not -BeNullOrEmpty } It 'Name is correct' { - $result.name | Should be $defaultUserProject + $result.name | Should -Be $defaultUserProject } It 'Description should be updated' { - $result.body | Should be $defaultUserProjectDesc + $result.body | Should -Be $defaultUserProjectDesc + } + + It 'Should have the expected type and additional properties' { + $result.PSObject.TypeNames[0] | Should -Be 'GitHub.Project' + $result.RepositoryUrl | Should -BeNullOrEmpty # no RepositoryUrl for user projects + $result.ProjectId | Should -Be $result.id + $result.creator.PSObject.TypeNames[0] | Should -Be 'GitHub.User' } } Context 'Create Organization projects' { BeforeAll { $project = @{id = 0} - - # Avoid PSScriptAnalyzer PSUseDeclaredVarsMoreThanAssignments - $project = $project } AfterAll { @@ -295,27 +500,34 @@ try Remove-Variable project } - $project.id = (New-GitHubProject -OrganizationName $script:organizationName -Name $defaultOrgProject -Description $defaultOrgProjectDesc).id + $project.id = (New-GitHubProject -OrganizationName $script:organizationName -ProjectName $defaultOrgProject -Description $defaultOrgProjectDesc).id $result = Get-GitHubProject -Project $project.id It 'Project exists' { - $result | Should Not BeNullOrEmpty + $result | Should -Not -BeNullOrEmpty } It 'Name is correct' { - $result.name | Should be $defaultOrgProject + $result.name | Should -Be $defaultOrgProject } It 'Description should be updated' { - $result.body | Should be $defaultOrgProjectDesc + $result.body | Should -Be $defaultOrgProjectDesc + } + + It 'Should have the expected type and additional properties' { + $elements = Split-GitHubUri -Uri $result.html_url + $repositoryUrl = Join-GitHubUri @elements + + $result.PSObject.TypeNames[0] | Should -Be 'GitHub.Project' + $result.RepositoryUrl | Should -Be $repositoryUrl + $result.ProjectId | Should -Be $result.id + $result.creator.PSObject.TypeNames[0] | Should -Be 'GitHub.User' } } Context 'Create Repo projects' { BeforeAll { $project = @{id = 0} - - # Avoid PSScriptAnalyzer PSUseDeclaredVarsMoreThanAssignments - $project = $project } AfterAll { @@ -323,62 +535,91 @@ try Remove-Variable project } - $project.id = (New-GitHubProject -OwnerName $script:ownerName -RepositoryName $repo.name -Name $defaultRepoProject -Description $defaultRepoProjectDesc).id + $project.id = (New-GitHubProject -OwnerName $script:ownerName -RepositoryName $repo.name -ProjectName $defaultRepoProject -Description $defaultRepoProjectDesc).id $result = Get-GitHubProject -Project $project.id It 'Project Exists' { - $result | Should Not BeNullOrEmpty + $result | Should -Not -BeNullOrEmpty } It 'Name is correct' { - $result.name | Should be $defaultRepoProject + $result.name | Should -Be $defaultRepoProject } It 'Description should be updated' { - $result.body | Should be $defaultRepoProjectDesc + $result.body | Should -Be $defaultRepoProjectDesc + } + + It 'Should have the expected type and additional properties' { + $result.PSObject.TypeNames[0] | Should -Be 'GitHub.Project' + $result.RepositoryUrl | Should -Be $repo.RepositoryUrl + $result.ProjectId | Should -Be $result.id + $result.creator.PSObject.TypeNames[0] | Should -Be 'GitHub.User' } } - } - Describe 'Remove Project' { - Context 'Remove User projects' { + Context 'Create Repo project (via pipeline)' { BeforeAll { - $project = New-GitHubProject -UserProject -Name $defaultUserProject -Description $defaultUserProjectDesc + $project = @{id = 0} + } + + AfterAll { + $null = Remove-GitHubProject -Project $project.id -Confirm:$false + Remove-Variable project + } + + $project.id = ($repo | New-GitHubProject -ProjectName $defaultRepoProject -Description $defaultRepoProjectDesc).id + $result = Get-GitHubProject -Project $project.id + It 'Project Exists' { + $result | Should -Not -BeNullOrEmpty + } + + It 'Name is correct' { + $result.name | Should -Be $defaultRepoProject + } - # Avoid PSScriptAnalyzer PSUseDeclaredVarsMoreThanAssignments - $project = $project + It 'Description should be updated' { + $result.body | Should -Be $defaultRepoProjectDesc } + It 'Should have the expected type and additional properties' { + $result.PSObject.TypeNames[0] | Should -Be 'GitHub.Project' + $result.RepositoryUrl | Should -Be $repo.RepositoryUrl + $result.ProjectId | Should -Be $result.id + $result.creator.PSObject.TypeNames[0] | Should -Be 'GitHub.User' + } + } + } + + Describe 'Remove Project' { + Context 'Remove User projects' { + $project = New-GitHubProject -UserProject -ProjectName $defaultUserProject -Description $defaultUserProjectDesc $null = Remove-GitHubProject -Project $project.id -Force It 'Project should be removed' { - {Get-GitHubProject -Project $project.id} | Should Throw + {Get-GitHubProject -Project $project.id} | Should -Throw } } Context 'Remove Organization projects' { - BeforeAll { - $project = New-GitHubProject -OrganizationName $script:organizationName -Name $defaultOrgProject -Description $defaultOrgProjectDesc - - # Avoid PSScriptAnalyzer PSUseDeclaredVarsMoreThanAssignments - $project = $project - } - + $project = New-GitHubProject -OrganizationName $script:organizationName -ProjectName $defaultOrgProject -Description $defaultOrgProjectDesc $null = Remove-GitHubProject -Project $project.id -Force It 'Project should be removed' { - {Get-GitHubProject -Project $project.id} | Should Throw + {Get-GitHubProject -Project $project.id} | Should -Throw } } Context 'Remove Repo projects' { - BeforeAll { - $project = New-GitHubProject -OwnerName $script:ownerName -RepositoryName $repo.name -Name $defaultRepoProject -Description $defaultRepoProjectDesc - - # Avoid PSScriptAnalyzer PSUseDeclaredVarsMoreThanAssignments - $project = $project + $project = New-GitHubProject -OwnerName $script:ownerName -RepositoryName $repo.name -ProjectName $defaultRepoProject -Description $defaultRepoProjectDesc + $null = Remove-GitHubProject -Project $project.id -Confirm:$false + It 'Project should be removed' { + {Get-GitHubProject -Project $project.id} | Should -Throw } + } - $null = Remove-GitHubProject -Project $project.id -Confirm:$false + Context 'Remove Repo project via pipeline' { + $project = $repo | New-GitHubProject -ProjectName $defaultRepoProject -Description $defaultRepoProjectDesc + $project | Remove-GitHubProject -Force It 'Project should be removed' { - {Get-GitHubProject -Project $project.id} | Should Throw + {$project | Get-GitHubProject} | Should -Throw } } } diff --git a/Tests/GitHubPullRequests.tests.ps1 b/Tests/GitHubPullRequests.tests.ps1 new file mode 100644 index 00000000..8df52250 --- /dev/null +++ b/Tests/GitHubPullRequests.tests.ps1 @@ -0,0 +1,88 @@ +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. + +<# +.Synopsis + Tests for GitHubPullRequests.ps1 module +#> + +[CmdletBinding()] +[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseDeclaredVarsMoreThanAssignments', '', + Justification='Suppress false positives in Pester code blocks')] +param() + +# This is common test code setup logic for all Pester test files +$moduleRootPath = Split-Path -Path $PSScriptRoot -Parent +. (Join-Path -Path $moduleRootPath -ChildPath 'Tests\Common.ps1') + +try +{ + Describe 'Getting pull request from repository' { + BeforeAll { + $repo = Get-GitHubRepository -OwnerName 'microsoft' -RepositoryName 'PowerShellForGitHub' + } + + Context 'When getting a pull request' { + $pullRequestNumber = 39 + $pullRequest = Get-GitHubPullRequest -OwnerName 'microsoft' -RepositoryName 'PowerShellForGitHub' -PullRequest $pullRequestNumber + + It 'Should be the expected pull request' { + $pullRequest.number | Should -Be $pullRequestNumber + } + + It 'Should have the expected type and additional properties' { + $elements = Split-GitHubUri -Uri $pullRequest.html_url + $repositoryUrl = Join-GitHubUri @elements + + $pullRequest.PSObject.TypeNames[0] | Should -Be 'GitHub.PullRequest' + $pullRequest.RepositoryUrl | Should -Be $repo.RepositoryUrl + $pullRequest.PullRequestId | Should -Be $pullRequest.id + $pullRequest.PullRequestNumber | Should -Be $pullRequest.number + $pullRequest.user.PSObject.TypeNames[0] | Should -Be 'GitHub.User' + $pullRequest.labels[0].PSObject.TypeNames[0] | Should -Be 'GitHub.Label' + $pullRequest.assignee.PSObject.TypeNames[0] | Should -Be 'GitHub.User' + $pullRequest.assignees[0].PSObject.TypeNames[0] | Should -Be 'GitHub.User' + $pullRequest.requested_teams[0].PSObject.TypeNames[0] | Should -Be 'GitHub.Team' + $pullRequest.merged_by.PSObject.TypeNames[0] | Should -Be 'GitHub.User' + } + + It 'Should be refreshable via the pipeline' { + $refresh = $pullRequest | Get-GitHubPullRequest + $refresh.PullRequestNumber | Should -Be $pullRequest.PullRequestNumber + } + + It 'Should be retrievable by passing the repo on the pipeline' { + $pullRequest = $repo | Get-GitHubPullRequest -PullRequest $pullRequestNumber + $pullRequest.number | Should -Be $pullRequestNumber + } + + It 'Should fail when it the pull request does not exist' { + { $repo | Get-GitHubPullRequest -PullRequest 1 } | Should -Throw + } + } + } + + Describe 'Getting multiple pull requests from repository' { + BeforeAll { + $ownerName = 'microsoft' + $repositoryName = 'PowerShellForGitHub' + } + + Context 'All closed' { + $pullRequests = @(Get-GitHubPullRequest -OwnerName $ownerName -RepositoryName $repositoryName -State 'Closed') + + It 'Should return expected number of PRs' { + $pullRequests.Count | Should -BeGreaterOrEqual 140 + } + } + } +} +finally +{ + if (Test-Path -Path $script:originalConfigFile -PathType Leaf) + { + # Restore the user's configuration to its pre-test state + Restore-GitHubConfiguration -Path $script:originalConfigFile + $script:originalConfigFile = $null + } +} diff --git a/Tests/GitHubReleases.tests.ps1 b/Tests/GitHubReleases.tests.ps1 index 53d5ed31..10c1b42a 100644 --- a/Tests/GitHubReleases.tests.ps1 +++ b/Tests/GitHubReleases.tests.ps1 @@ -6,83 +6,134 @@ Tests for GitHubReleases.ps1 module #> +[CmdletBinding()] +[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseDeclaredVarsMoreThanAssignments', '', + Justification='Suppress false positives in Pester code blocks')] +param() + # This is common test code setup logic for all Pester test files $moduleRootPath = Split-Path -Path $PSScriptRoot -Parent . (Join-Path -Path $moduleRootPath -ChildPath 'Tests\Common.ps1') try { - if ($accessTokenConfigured) - { - Describe 'Getting releases from repository' { - $ownerName = "dotnet" - $repositoryName = "core" - $releases = @(Get-GitHubRelease -OwnerName $ownerName -RepositoryName $repositoryName) + Describe 'Getting releases from repository' { + $ownerName = "dotnet" + $repositoryName = "core" + $releases = @(Get-GitHubRelease -OwnerName $ownerName -RepositoryName $repositoryName) + + Context 'When getting all releases' { + It 'Should return multiple releases' { + $releases.Count | Should -BeGreaterThan 1 + } - Context 'When getting all releases' { - It 'Should return multiple releases' { - $releases.Count | Should BeGreaterThan 1 - } + It 'Should have expected type and additional properties' { + $releases[0].PSObject.TypeNames[0] | Should -Be 'GitHub.Release' + $releases[0].html_url.StartsWith($releases[0].RepositoryUrl) | Should -BeTrue + $releases[0].id | Should -Be $releases[0].ReleaseId } + } - Context 'When getting the latest releases' { - $latest = @(Get-GitHubRelease -OwnerName $ownerName -RepositoryName $repositoryName -Latest) + Context 'When getting the latest releases' { + $latest = @(Get-GitHubRelease -OwnerName $ownerName -RepositoryName $repositoryName -Latest) - It 'Should return one value' { - $latest.Count | Should Be 1 - } + It 'Should return one value' { + $latest.Count | Should -Be 1 + } - It 'Should return the first release from the full releases list' { - $latest[0].url | Should Be $releases[0].url - $latest[0].name | Should Be $releases[0].name - } + It 'Should return the first release from the full releases list' { + $latest[0].url | Should -Be $releases[0].url + $latest[0].name | Should -Be $releases[0].name } - Context 'When getting a specific release' { - $specificIndex = 5 - $specific = @(Get-GitHubRelease -OwnerName $ownerName -RepositoryName $repositoryName -ReleaseId $releases[$specificIndex].id) + It 'Should have expected type and additional properties' { + $latest[0].PSObject.TypeNames[0] | Should -Be 'GitHub.Release' + $latest[0].html_url.StartsWith($latest[0].RepositoryUrl) | Should -BeTrue + $latest[0].id | Should -Be $latest[0].ReleaseId + } + } - It 'Should return one value' { - $specific.Count | Should Be 1 - } + Context 'When getting the latest releases via the pipeline' { + $latest = @(Get-GitHubRepository -OwnerName $ownerName -RepositoryName $repositoryName | + Get-GitHubRelease -Latest) - It 'Should return the correct release' { - $specific.name | Should Be $releases[$specificIndex].name - } + It 'Should return one value' { + $latest.Count | Should -Be 1 } - Context 'When getting a tagged release' { - $taggedIndex = 8 - $tagged = @(Get-GitHubRelease -OwnerName $ownerName -RepositoryName $repositoryName -Tag $releases[$taggedIndex].tag_name) + It 'Should return the first release from the full releases list' { + $latest[0].url | Should -Be $releases[0].url + $latest[0].name | Should -Be $releases[0].name + } - It 'Should return one value' { - $tagged.Count | Should Be 1 - } + It 'Should have expected type and additional properties' { + $latest[0].PSObject.TypeNames[0] | Should -Be 'GitHub.Release' + $latest[0].html_url.StartsWith($latest[0].RepositoryUrl) | Should -BeTrue + $latest[0].id | Should -Be $latest[0].ReleaseId + } - It 'Should return the correct release' { - $tagged.name | Should Be $releases[$taggedIndex].name - } + $latestAgain = @($latest | Get-GitHubRelease) + It 'Should be the same release' { + $latest[0].ReleaseId | Should -Be $latestAgain[0].ReleaseId } } - Describe 'Getting releases from default owner/repository' { - $originalOwnerName = Get-GitHubConfiguration -Name DefaultOwnerName - $originalRepositoryName = Get-GitHubConfiguration -Name DefaultRepositoryName + Context 'When getting a specific release' { + $specificIndex = 5 + $specific = @(Get-GitHubRelease -OwnerName $ownerName -RepositoryName $repositoryName -ReleaseId $releases[$specificIndex].id) - try { - Set-GitHubConfiguration -DefaultOwnerName "dotnet" - Set-GitHubConfiguration -DefaultRepositoryName "core" - $releases = @(Get-GitHubRelease) + It 'Should return one value' { + $specific.Count | Should -Be 1 + } - Context 'When getting all releases' { - It 'Should return multiple releases' { - $releases.Count | Should BeGreaterThan 1 - } + It 'Should return the correct release' { + $specific.name | Should -Be $releases[$specificIndex].name + } + + It 'Should have expected type and additional properties' { + $specific[0].PSObject.TypeNames[0] | Should -Be 'GitHub.Release' + $specific[0].html_url.StartsWith($specific[0].RepositoryUrl) | Should -BeTrue + $specific[0].id | Should -Be $specific[0].ReleaseId + } + } + + Context 'When getting a tagged release' { + $taggedIndex = 8 + $tagged = @(Get-GitHubRelease -OwnerName $ownerName -RepositoryName $repositoryName -Tag $releases[$taggedIndex].tag_name) + + It 'Should return one value' { + $tagged.Count | Should -Be 1 + } + + It 'Should return the correct release' { + $tagged.name | Should -Be $releases[$taggedIndex].name + } + + It 'Should have expected type and additional properties' { + $tagged[0].PSObject.TypeNames[0] | Should -Be 'GitHub.Release' + $tagged[0].html_url.StartsWith($tagged[0].RepositoryUrl) | Should -BeTrue + $tagged[0].id | Should -Be $tagged[0].ReleaseId + } + } + } + + Describe 'Getting releases from default owner/repository' { + $originalOwnerName = Get-GitHubConfiguration -Name DefaultOwnerName + $originalRepositoryName = Get-GitHubConfiguration -Name DefaultRepositoryName + + try { + Set-GitHubConfiguration -DefaultOwnerName "dotnet" + Set-GitHubConfiguration -DefaultRepositoryName "core" + $releases = @(Get-GitHubRelease) + + Context 'When getting all releases' { + It 'Should return multiple releases' { + $releases.Count | Should -BeGreaterThan 1 } - } finally { - Set-GitHubConfiguration -DefaultOwnerName $originalOwnerName - Set-GitHubConfiguration -DefaultRepositoryName $originalRepositoryName } + } finally { + Set-GitHubConfiguration -DefaultOwnerName $originalOwnerName + Set-GitHubConfiguration -DefaultRepositoryName $originalRepositoryName } } } diff --git a/Tests/GitHubRepositories.tests.ps1 b/Tests/GitHubRepositories.tests.ps1 index 598361f7..3dc1bf81 100644 --- a/Tests/GitHubRepositories.tests.ps1 +++ b/Tests/GitHubRepositories.tests.ps1 @@ -409,7 +409,7 @@ try } It "Should have the expected new repository name - by URI" { - $renamedRepo = $repo | Rename-GitHubRepository -NewName $newRepoName -Force + $renamedRepo = Rename-GitHubRepository -Uri ($repo.RepositoryUrl) -NewName $newRepoName -Force $renamedRepo.name | Should -Be $newRepoName } @@ -418,6 +418,18 @@ try $renamedRepo.name | Should -Be $newRepoName } + It "Should work via the pipeline" { + $renamedRepo = $repo | Rename-GitHubRepository -NewName $newRepoName -Confirm:$false + $renamedRepo.name | Should -Be $newRepoName + $renamedRepo.PSObject.TypeNames[0] | Should -Be 'GitHub.Repository' + } + + It "Should be possible to rename with Update-GitHubRepository too" { + $renamedRepo = $repo | Update-GitHubRepository -NewName $newRepoName -Confirm:$false + $renamedRepo.name | Should -Be $newRepoName + $renamedRepo.PSObject.TypeNames[0] | Should -Be 'GitHub.Repository' + } + AfterEach -Scriptblock { Remove-GitHubRepository -Uri "$($repo.svn_url)$suffixToAddToRepo" -Confirm:$false } @@ -555,6 +567,74 @@ try } } + Describe 'Common user repository pipeline scenarios' { + Context 'For authenticated user' { + BeforeAll -Scriptblock { + $repo = ([Guid]::NewGuid().Guid) | New-GitHubRepository -AutoInit + } + + It "Should have expected additional properties and type after creation" { + $repo.PSObject.TypeNames[0] | Should -Be 'GitHub.Repository' + $repo.RepositoryUrl | Should -Be (Join-GitHubUri -OwnerName $script:ownerName -RepositoryName $repo.name) + $repo.RepositoryId | Should -Be $repo.id + $repo.owner.PSObject.TypeNames[0] | Should -Be 'GitHub.User' + } + + It "Should have expected additional properties and type after creation" { + $returned = ($repo | Get-GitHubRepository) + $returned.PSObject.TypeNames[0] | Should -Be 'GitHub.Repository' + $returned.RepositoryUrl | Should -Be (Join-GitHubUri -OwnerName $script:ownerName -RepositoryName $returned.name) + $returned.RepositoryId | Should -Be $returned.id + $returned.owner.PSObject.TypeNames[0] | Should -Be 'GitHub.User' + } + + It "Should get the repository by user" { + $repos = @($script:ownerName | Get-GitHubUser | Get-GitHubRepository) + $repos.name | Should -Contain $repo.name + } + + It 'Should be removable by the pipeline' { + ($repo | Remove-GitHubRepository -Confirm:$false) | Should -BeNullOrEmpty + { $repo | Get-GitHubRepository } | Should -Throw + } + } + } + + Describe 'Common organization repository pipeline scenarios' { + Context 'For organization' { + BeforeAll -Scriptblock { + $org = [PSCustomObject]@{'OrganizationName' = $script:organizationName} + $repo = $org | New-GitHubRepository -RepositoryName ([Guid]::NewGuid().Guid) -AutoInit + } + + It "Should have expected additional properties and type after creation" { + $repo.PSObject.TypeNames[0] | Should -Be 'GitHub.Repository' + $repo.RepositoryUrl | Should -Be (Join-GitHubUri -OwnerName $script:organizationName -RepositoryName $repo.name) + $repo.RepositoryId | Should -Be $repo.id + $repo.owner.PSObject.TypeNames[0] | Should -Be 'GitHub.User' + $repo.organization.PSObject.TypeNames[0] | Should -Be 'GitHub.Organization' + $repo.organization.OrganizationName | Should -Be $repo.organization.login + $repo.organization.OrganizationId | Should -Be $repo.organization.id + } + + It "Should have expected additional properties and type after creation" { + $returned = ($repo | Get-GitHubRepository) + $returned.PSObject.TypeNames[0] | Should -Be 'GitHub.Repository' + $returned.RepositoryUrl | Should -Be (Join-GitHubUri -OwnerName $script:organizationName -RepositoryName $returned.name) + $returned.RepositoryId | Should -Be $returned.id + $returned.owner.PSObject.TypeNames[0] | Should -Be 'GitHub.User' + $returned.organization.PSObject.TypeNames[0] | Should -Be 'GitHub.Organization' + $returned.organization.OrganizationName | Should -Be $returned.organization.login + $returned.organization.OrganizationId | Should -Be $returned.organization.id + } + + It 'Should be removable by the pipeline' { + ($repo | Remove-GitHubRepository -Confirm:$false) | Should -BeNullOrEmpty + { $repo | Get-GitHubRepository } | Should -Throw + } + } + } + Describe 'Get/set repository topic' { Context -Name 'For creating and getting a repository topic' -Fixture { @@ -563,17 +643,51 @@ try } It 'Should have the expected topic' { - Set-GitHubRepositoryTopic -OwnerName $repo.owner.login -RepositoryName $repo.name -Name $defaultRepoTopic + $null = Set-GitHubRepositoryTopic -OwnerName $repo.owner.login -RepositoryName $repo.name -Topic $defaultRepoTopic $topic = Get-GitHubRepositoryTopic -OwnerName $repo.owner.login -RepositoryName $repo.name + $topic.names | Should -Be $defaultRepoTopic } It 'Should have no topics' { - Set-GitHubRepositoryTopic -OwnerName $repo.owner.login -RepositoryName $repo.name -Clear + $null = Set-GitHubRepositoryTopic -OwnerName $repo.owner.login -RepositoryName $repo.name -Clear $topic = Get-GitHubRepositoryTopic -OwnerName $repo.owner.login -RepositoryName $repo.name + $topic.names | Should -BeNullOrEmpty } + It 'Should have the expected topic (using repo via pipeline)' { + $null = $repo | Set-GitHubRepositoryTopic -Topic $defaultRepoTopic + $topic = $repo | Get-GitHubRepositoryTopic + + $topic.names | Should -Be $defaultRepoTopic + $topic.PSObject.TypeNames[0] | Should -Be 'GitHub.RepositoryTopic' + $topic.RepositoryUrl | Should -Be $repo.RepositoryUrl + } + + It 'Should have the expected topic (using topic via pipeline)' { + $null = $defaultRepoTopic | Set-GitHubRepositoryTopic -OwnerName $repo.owner.login -RepositoryName $repo.name + $topic = $repo | Get-GitHubRepositoryTopic + + $topic.names | Should -Be $defaultRepoTopic + $topic.PSObject.TypeNames[0] | Should -Be 'GitHub.RepositoryTopic' + $topic.RepositoryUrl | Should -Be $repo.RepositoryUrl + } + + It 'Should have the expected multi-topic (using topic via pipeline)' { + $topics = @('one', 'two') + $null = $topics | Set-GitHubRepositoryTopic -OwnerName $repo.owner.login -RepositoryName $repo.name + $result = $repo | Get-GitHubRepositoryTopic + + $result.PSObject.TypeNames[0] | Should -Be 'GitHub.RepositoryTopic' + $result.RepositoryUrl | Should -Be $repo.RepositoryUrl + $result.names.count | Should -Be $topics.Count + foreach ($topic in $topics) + { + $result.names | Should -Contain $topic + } + } + AfterAll -ScriptBlock { Remove-GitHubRepository -Uri $repo.svn_url -Confirm:$false } @@ -595,6 +709,14 @@ try It 'Should contain PowerShell' { $languages = Get-GitHubRepositoryLanguage -OwnerName "microsoft" -RepositoryName "PowerShellForGitHub" $languages.PowerShell | Should -Not -BeNullOrEmpty + $languages.PSObject.TypeNames[0] | Should -Be 'GitHub.RepositoryLanguage' + } + + It 'Should contain PowerShell (via pipeline)' { + $psfg = Get-GitHubRepository -OwnerName "microsoft" -RepositoryName "PowerShellForGitHub" + $languages = $psfg | Get-GitHubRepositoryLanguage + $languages.PowerShell | Should -Not -BeNullOrEmpty + $languages.PSObject.TypeNames[0] | Should -Be 'GitHub.RepositoryLanguage' } AfterAll -ScriptBlock { @@ -615,11 +737,82 @@ try $tags | Should -BeNullOrEmpty } + It 'Should be empty (via pipeline)' { + $tags = $repo | Get-GitHubRepositoryTag + $tags | Should -BeNullOrEmpty + } + AfterAll -ScriptBlock { Remove-GitHubRepository -Uri $repo.svn_url -Confirm:$false } } } + + Describe 'Contributors for a repository' { + BeforeAll { + $repo = New-GitHubRepository -RepositoryName ([guid]::NewGuid().Guid) -AutoInit + } + + AfterAll { + $null = Remove-GitHubRepository -Uri $repo.RepositoryUrl -Confirm:$false + } + + Context -Name 'Obtaining contributors for repository' -Fixture { + $contributors = @(Get-GitHubRepositoryContributor -Uri $repo.RepositoryUrl) + + It 'Should return expected number of contributors' { + $contributors.Count | Should -Be 1 + $contributors[0].PSObject.TypeNames[0] = 'GitHub.User' + } + } + + Context -Name 'Obtaining contributors for repository (via pipeline)' -Fixture { + $contributors = @($repo | Get-GitHubRepositoryContributor -IncludeStatistics) + + It 'Should return expected number of contributors' { + $contributors.Count | Should -Be 1 + $contributors[0].PSObject.TypeNames[0] = 'GitHub.User' + } + } + + Context -Name 'Obtaining contributor statistics for repository' -Fixture { + $stats = @(Get-GitHubRepositoryContributor -Uri $repo.RepositoryUrl -IncludeStatistics) + + It 'Should return expected number of contributors' { + $stats.Count | Should -Be 1 + $stats[0].PSObject.TypeNames[0] = 'GitHub.RepositoryContributorStatistics' + $stats[0].author.PSObject.TypeNames[0] = 'GitHub.User' + } + } + } + + Describe 'Collaborators for a repository' { + BeforeAll { + $repo = New-GitHubRepository -RepositoryName ([guid]::NewGuid().Guid) -AutoInit + } + + AfterAll { + $null = Remove-GitHubRepository -Uri $repo.RepositoryUrl -Confirm:$false + } + + Context -Name 'Obtaining collaborators for repository' -Fixture { + $collaborators = @(Get-GitHubRepositoryCollaborator -Uri $repo.RepositoryUrl) + + It 'Should return expected number of collaborators' { + $collaborators.Count | Should -Be 1 + $collaborators[0].PSObject.TypeNames[0] = 'GitHub.User' + } + } + + Context -Name 'Obtaining collaborators for repository (via pipeline)' -Fixture { + $collaborators = @($repo | Get-GitHubRepositoryCollaborator) + + It 'Should return expected number of collaborators' { + $collaborators.Count | Should -Be 1 + $collaborators[0].PSObject.TypeNames[0] = 'GitHub.User' + } + } + } } finally { diff --git a/Tests/GitHubRepositoryForks.tests.ps1 b/Tests/GitHubRepositoryForks.tests.ps1 index c5267a20..a7f6d37f 100644 --- a/Tests/GitHubRepositoryForks.tests.ps1 +++ b/Tests/GitHubRepositoryForks.tests.ps1 @@ -32,7 +32,7 @@ try } AfterAll { - Remove-GitHubRepository -Uri $repo.svn_url -Confirm:$false + $repo | Remove-GitHubRepository -Force } $newForks = @(Get-GitHubRepositoryFork -OwnerName $script:upstreamOwnerName -RepositoryName $script:upstreamRepositoryName -Sort Newest) @@ -43,6 +43,36 @@ try # think that there's an existing clone out there and so may name this one "...-1" $ourFork.full_name.StartsWith("$($script:ownerName)/$script:upstreamRepositoryName") | Should -BeTrue } + + It 'Should have the expected additional type and properties' { + $ourFork.PSObject.TypeNames[0] | Should -Be 'GitHub.Repository' + $ourFork.RepositoryId | Should -Be $ourFork.id + } + } + + Context 'When a new fork is created (with the pipeline)' { + BeforeAll { + $upstream = Get-GitHubRepository -OwnerName $script:upstreamOwnerName -RepositoryName $script:upstreamRepositoryName + $repo = $upstream | New-GitHubRepositoryFork + } + + AfterAll { + $repo | Remove-GitHubRepository -Force + } + + $newForks = @(Get-GitHubRepositoryFork -OwnerName $script:upstreamOwnerName -RepositoryName $script:upstreamRepositoryName -Sort Newest) + $ourFork = $newForks | Where-Object { $_.owner.login -eq $script:ownerName } + + It 'Should be in the list' { + # Doing this syntax, because due to odd timing with GitHub, it's possible it may + # think that there's an existing clone out there and so may name this one "...-1" + $ourFork.full_name.StartsWith("$($script:ownerName)/$script:upstreamRepositoryName") | Should -BeTrue + } + + It 'Should have the expected additional type and properties' { + $ourFork.PSObject.TypeNames[0] | Should -Be 'GitHub.Repository' + $ourFork.RepositoryId | Should -Be $ourFork.id + } } } @@ -53,7 +83,8 @@ try } AfterAll { - Remove-GitHubRepository -Uri $repo.svn_url -Confirm:$false + Start-Sleep -Seconds 3 # Trying to avoid an issue with deleting the repo if it's still being created by GitHub + $repo | Remove-GitHubRepository -Force } $newForks = @(Get-GitHubRepositoryFork -OwnerName $script:upstreamOwnerName -RepositoryName $script:upstreamRepositoryName -Sort Newest) diff --git a/Tests/GitHubRepositoryTraffic.tests.ps1 b/Tests/GitHubRepositoryTraffic.tests.ps1 index ea542e19..7136ee0a 100644 --- a/Tests/GitHubRepositoryTraffic.tests.ps1 +++ b/Tests/GitHubRepositoryTraffic.tests.ps1 @@ -6,65 +6,102 @@ Tests for GitHubRepositoryTraffic.ps1 module #> +[CmdletBinding()] +[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseDeclaredVarsMoreThanAssignments', '', + Justification='Suppress false positives in Pester code blocks')] +param() + # This is common test code setup logic for all Pester test files $moduleRootPath = Split-Path -Path $PSScriptRoot -Parent . (Join-Path -Path $moduleRootPath -ChildPath 'Tests\Common.ps1') try { - Describe 'Getting the referrer list' { - $repo = New-GitHubRepository -RepositoryName ([Guid]::NewGuid().Guid) -AutoInit + Describe 'Testing the referrer traffic on a repository' { + BeforeAll { + $repo = New-GitHubRepository -RepositoryName ([Guid]::NewGuid().Guid) -AutoInit + } - Context 'When initially created, there are no referrers' { - $referrerList = Get-GitHubReferrerTraffic -Uri $repo.svn_url + AfterAll { + Remove-GitHubRepository -Uri $repo.RepositoryUrl -Confirm:$false + } + Context 'When initially created, there are no referrers' { It 'Should return expected number of referrers' { - $referrerList.Count | Should be 0 + $traffic = Get-GitHubReferrerTraffic -Uri $repo.svn_url + $traffic | Should -BeNullOrEmpty } - Remove-GitHubRepository -Uri $repo.svn_url -Confirm:$false + It 'Should have the expected type (via pipeline)' { + $traffic = $repo | Get-GitHubReferrerTraffic + $traffic | Should -BeNullOrEmpty + } } } - Describe 'Getting the popular content over the last 14 days' { - $repo = New-GitHubRepository -RepositoryName ([Guid]::NewGuid().Guid) -AutoInit + Describe 'Testing the path traffic on a repository' { + BeforeAll { + $repo = New-GitHubRepository -RepositoryName ([Guid]::NewGuid().Guid) -AutoInit + } - Context 'When initially created, there are is no popular content' { - $pathList = Get-GitHubPathTraffic -Uri $repo.svn_url + AfterAll { + Remove-GitHubRepository -Uri $repo.RepositoryUrl -Confirm:$false + } - It 'Should return expected number of popular content' { - $pathList.Count | Should be 0 + Context 'Getting the popular content over the last 14 days' { + It 'Should have no traffic since it was just created' { + $traffic = Get-GitHubPathTraffic -Uri $repo.svn_url + $traffic | Should -BeNullOrEmpty } - Remove-GitHubRepository -Uri $repo.svn_url -Confirm:$false + It 'Should have the expected type (via pipeline)' { + $traffic = $repo | Get-GitHubPathTraffic + $traffic | Should -BeNullOrEmpty + } } } - Describe 'Getting the views over the last 14 days' { - $repo = New-GitHubRepository -RepositoryName ([Guid]::NewGuid().Guid) -AutoInit + Describe 'Testing the view traffic on a repository' { + BeforeAll { + $repo = New-GitHubRepository -RepositoryName ([Guid]::NewGuid().Guid) -AutoInit + } - Context 'When initially created, there are no views' { - $viewList = Get-GitHubViewTraffic -Uri $repo.svn_url + AfterAll { + Remove-GitHubRepository -Uri $repo.RepositoryUrl -Confirm:$false + } - It 'Should return 0 in the count property' { - $viewList.Count | Should be 0 + Context 'Getting the views over the last 14 days' { + It 'Should have no traffic since it was just created' { + $traffic = Get-GitHubViewTraffic -Uri $repo.svn_url + $traffic.Count | Should -Be 0 } - Remove-GitHubRepository -Uri $repo.svn_url -Confirm:$false + It 'Should have the expected type (via pipeline)' { + $traffic = $repo | Get-GitHubViewTraffic + $traffic.PSObject.TypeNames[0] | Should -Be 'GitHub.ViewTraffic' + } } } - Describe 'Getting the clones over the last 14 days' { - $repo = New-GitHubRepository -RepositoryName ([Guid]::NewGuid().Guid) -AutoInit + Describe 'Testing the clone traffic on a repository' { + BeforeAll { + $repo = New-GitHubRepository -RepositoryName ([Guid]::NewGuid().Guid) -AutoInit + } - Context 'When initially created, there is 0 clones' { - $cloneList = Get-GitHubCloneTraffic -Uri $repo.svn_url + AfterAll { + Remove-GitHubRepository -Uri $repo.RepositoryUrl -Confirm:$false + } - It 'Should return expected number of clones' { - $cloneList.Count | Should be 0 + Context 'Getting the clones over the last 14 days' { + It 'Should have no clones since it was just created' { + $traffic = Get-GitHubCloneTraffic -Uri $repo.svn_url + $traffic.Count | Should -Be 0 } - Remove-GitHubRepository -Uri $repo.svn_url -Confirm:$false + It 'Should have no clones since it was just created (via pipeline)' { + $traffic = $repo | Get-GitHubCloneTraffic + $traffic.PSObject.TypeNames[0] | Should -Be 'GitHub.CloneTraffic' + } } } } diff --git a/Tests/GitHubTeams.tests.ps1 b/Tests/GitHubTeams.tests.ps1 new file mode 100644 index 00000000..21658992 --- /dev/null +++ b/Tests/GitHubTeams.tests.ps1 @@ -0,0 +1,30 @@ +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. + +<# +.Synopsis + Tests for GitHubTeams.ps1 module +#> + +[CmdletBinding()] +[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseDeclaredVarsMoreThanAssignments', '', + Justification='Suppress false positives in Pester code blocks')] +param() + +# This is common test code setup logic for all Pester test files +$moduleRootPath = Split-Path -Path $PSScriptRoot -Parent +. (Join-Path -Path $moduleRootPath -ChildPath 'Tests\Common.ps1') + +try +{ + # TODO once more capabilities exist in the module's API set +} +finally +{ + if (Test-Path -Path $script:originalConfigFile -PathType Leaf) + { + # Restore the user's configuration to its pre-test state + Restore-GitHubConfiguration -Path $script:originalConfigFile + $script:originalConfigFile = $null + } +} diff --git a/Tests/GitHubUsers.tests.ps1 b/Tests/GitHubUsers.tests.ps1 new file mode 100644 index 00000000..837455b6 --- /dev/null +++ b/Tests/GitHubUsers.tests.ps1 @@ -0,0 +1,134 @@ +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. + +<# +.Synopsis + Tests for GitHubIssues.ps1 module +#> + +[CmdletBinding()] +[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseDeclaredVarsMoreThanAssignments', '', + Justification='Suppress false positives in Pester code blocks')] +param() + +# This is common test code setup logic for all Pester test files +$moduleRootPath = Split-Path -Path $PSScriptRoot -Parent +. (Join-Path -Path $moduleRootPath -ChildPath 'Tests\Common.ps1') + +try +{ + Describe 'Getting a user' { + Context 'Current user when additional properties are enabled' { + BeforeAll { + $currentUser = Get-GitHubUser -Current + } + + It 'Should have the expected type and additional properties' { + $currentUser.UserName | Should -Be $currentUser.login + $currentUser.UserId | Should -Be $currentUser.id + $currentUser.PSObject.TypeNames[0] | Should -Be 'GitHub.User' + } + } + + Context 'Current user when additional properties are disabled' { + BeforeAll { + Set-GitHubConfiguration -DisablePipelineSupport + $currentUser = Get-GitHubUser -Current + } + + AfterAll { + Set-GitHubConfiguration -DisablePipelineSupport:$false + } + + It 'Should only have the expected type' { + $currentUser.UserName | Should -BeNullOrEmpty + $currentUser.UserId | Should -BeNullOrEmpty + $currentUser.PSObject.TypeNames[0] | Should -Be 'GitHub.User' + } + } + + Context 'Specific user as a parameter' { + BeforeAll { + $user = Get-GitHubUser -UserName $script:ownerName + } + + It 'Should have the expected type and additional properties' { + $user.UserName | Should -Be $user.login + $user.UserId | Should -Be $user.id + $user.PSObject.TypeNames[0] | Should -Be 'GitHub.User' + } + } + + Context 'Specific user with the pipeline' { + BeforeAll { + $user = $script:ownerName | Get-GitHubUser + } + + It 'Should have the expected type and additional properties' { + $user.UserName | Should -Be $user.login + $user.UserId | Should -Be $user.id + $user.PSObject.TypeNames[0] | Should -Be 'GitHub.User' + } + } + } + + Describe 'Getting user context' { + BeforeAll { + $repo = New-GitHubRepository -RepositoryName ([Guid]::NewGuid().Guid) -AutoInit + } + + AfterAll { + Remove-GitHubRepository -Uri $repo.RepositoryUrl -Confirm:$false + } + + Context 'Checking context on a repo' { + It 'Should indicate ownership as a parameter' { + $context = Get-GitHubUserContextualInformation -UserName $script:ownerName -RepositoryId $repo.id + 'Owns this repository' | Should -BeIn $context.contexts.message + } + + It 'Should indicate ownership with the repo on the pipeline' { + $context = $repo | Get-GitHubUserContextualInformation -UserName $script:ownerName + 'Owns this repository' | Should -BeIn $context.contexts.message + } + + It 'Should indicate ownership with the username on the pipeline' { + $context = $script:ownerName | Get-GitHubUserContextualInformation -RepositoryId $repo.id + 'Owns this repository' | Should -BeIn $context.contexts.message + $context.contexts[0].PSObject.TypeNames[0] | Should -Be 'GitHub.UserContextualInformation' + } + + It 'Should indicate ownership with the user on the pipeline' { + $user = Get-GitHubUser -UserName $script:ownerName + $context = $user | Get-GitHubUserContextualInformation -RepositoryId $repo.id + 'Owns this repository' | Should -BeIn $context.contexts.message + $context.contexts[0].PSObject.TypeNames[0] | Should -Be 'GitHub.UserContextualInformation' + } + } + + Context 'Checking context on an issue with the pipeline' { + $issue = New-GitHubIssue -Uri $repo.RepositoryUrl -Title ([guid]::NewGuid().Guid) + $context = $issue | Get-GitHubUserContextualInformation -UserName $script:ownerName + + It 'Should indicate the user created the issue' { + $context.contexts[0].octicon | Should -Be 'issue-opened' + $context.contexts[0].IssueId | Should -Be $issue.IssueId + $context.contexts[0].PSObject.TypeNames[0] | Should -Be 'GitHub.UserContextualInformation' + } + + It 'Should indicate the user owns the repository' { + $context.contexts[1].message | Should -Be 'Owns this repository' + $context.contexts[1].PSObject.TypeNames[0] | Should -Be 'GitHub.UserContextualInformation' + } + } + } +} +finally +{ + if (Test-Path -Path $script:originalConfigFile -PathType Leaf) + { + # Restore the user's configuration to its pre-test state + Restore-GitHubConfiguration -Path $script:originalConfigFile + $script:originalConfigFile = $null + } +} diff --git a/USAGE.md b/USAGE.md index 844d1186..59cc3954 100644 --- a/USAGE.md +++ b/USAGE.md @@ -5,6 +5,9 @@ * [Logging](#logging) * [Telemetry](#telemetry) * [Examples](#examples) + * [Overview](#overview) + * [Embracing the pipeline](#embracing-the-pipeline) + * [Pipeline Example](#pipeline-example) * [Analytics](#analytics) * [Querying Issues](#querying-issues) * [Querying Pull Requests](#querying-pull-requests) @@ -40,12 +43,12 @@ * [Add assignee to an issue](#add-assignee-to-an-issue) * [Remove assignee from an issue](#remove-assignee-from-an-issue) * [Comments](#comments) - * [Get comments from an issue](#get-comments-from-an-issue) - * [Get comments from a repository](#get-comments-from-a-repository) - * [Get a single comment](#get-a-single-comment) - * [Adding a new comment to an issue](#adding-a-new-comment-to-an-issue) - * [Editing an existing comment](#editing-an-existing-comment) - * [Removing a comment](#removing-a-comment) + * [Get comments from an Issue](#get-comments-from-an-issue) + * [Get Issue comments from a repository](#get-issue-comments-from-a-repository) + * [Get a single Issue comment](#get-a-single-issue-comment) + * [Adding a new comment to an Issue](#adding-a-new-comment-to-an-issue) + * [Editing an existing Issue comment](#editing-an-existing-issue-comment) + * [Removing an Issue comment](#removing-an-issue-comment) * [Milestones](#milestones) * [Get milestones from a repository](#get-milestones-from-a-repository) * [Get a single milestone](#get-a-single-milestone) @@ -104,12 +107,6 @@ In order to track usage, gauge performance and identify areas for improvement, t employed during execution of commands within this module (via Application Insights). For more information, refer to the [Privacy Policy](README.md#privacy-policy). -> You may notice some needed assemblies for communicating with Application Insights being -> downloaded on first run of a command within each PowerShell session. The -> [automatic dependency downloads](#automatic-dependency-downloads) section of the setup -> documentation describes how you can avoid having to always re-download the telemetry assemblies -> in the future. - We recommend that you always leave the telemetry feature enabled, but a situation may arise where it must be disabled for some reason. In this scenario, you can disable telemetry by calling: @@ -161,6 +158,58 @@ us for analysis. We expose it here for complete transparency. ## Examples +### Overview + +#### Embracing the Pipeline + +One of the major benefits of PowerShell is its pipeline -- allowing you to "pipe" a saved value or +the output of a previous command directly into the next command. There is absolutely no requirement +to make use of it in order to use the module, but you will find that the module becomes increasingly +easier to use and more powerful if you do. + +Some of the examples that you find below will show how you might be able to use it to your advantage. + +#### Pipeline Example + +Most commands require you to pass in either a `Uri` for the repository or its elements (the +`OwnerName` and `RepositoryName`). If you keep around the repo that you're interacting with in +a local var (like `$repo`), then you can pipe that into any command to avoid having to specify that +information. Further, piping in a more specific object (like an `Issue`) allows you to avoid even +specifying the relevant Issue number. + +Without the pipeline, an interaction log might look like this: + +```powershell +# Find all of the issues that have the label "repro steps needed" and add a new comment to those +# issues asking for an update. +$issues = @(Get-GitHubIssue -OwnerName microsoft -RepositoryName PowerShellForGitHub -Label 'repro steps needed') +foreach ($issue in $issues) +{ + $params = @{ + 'OwnerName' = 'microsoft' + 'RepositoryName' = 'PowerShellForGitHub' + 'Issue' = $issue.number + 'Body' = 'Any update on those repro steps?' + } + + New-GitHubIssueComment @params +} + +``` + +With the pipeline, a similar interaction log might look like this: + +```powershell +# Find all of the issues that have the label "repro steps needed" and add a new comment to those +# issues asking for an update. +Get-GitHubRepository -OwnerName microsoft -RepositoryName PowerShellForGitHub | + Get-GitHubIssue -Label 'repro steps needed' | + New-GitHubIssueComment -Body 'Any update on those repro steps?' +``` + +We encourage you to explore how embracing the pipeline may simplify your code and interaction +with GitHub using this module! + ### Analytics #### Querying Issues @@ -204,8 +253,8 @@ $issueCounts | Sort-Object -Property Count -Descending #### Querying Pull Requests ```powershell -# Getting all of the pull requests from the Microsoft\PowerShellForGitHub repository -$issues = Get-GitHubIssue -OwnerName Microsoft -RepositoryName 'PowerShellForGitHub' +# Getting all of the pull requests from the microsoft\PowerShellForGitHub repository +$issues = Get-GitHubIssue -OwnerName microsoft -RepositoryName 'PowerShellForGitHub' ``` ```powershell @@ -324,12 +373,12 @@ Add-GitHubIssueLabel -OwnerName $script:ownerName -RepositoryName $repositoryNam #### Removing a Label From an Issue ```powershell -Remove-GitHubIssueLabel -OwnerName Microsoft -RepositoryName DesiredStateConfiguration -Name TestLabel -Issue 1 +Remove-GitHubIssueLabel -OwnerName microsoft -RepositoryName DesiredStateConfiguration -Name TestLabel -Issue 1 ``` #### Updating a Label With a New Name and Color ```powershell -Update-GitHubLabel -OwnerName Microsoft -RepositoryName DesiredStateConfiguration -Name TestLabel -NewName NewTestLabel -Color BBBB00 +Update-GitHubLabel -OwnerName microsoft -RepositoryName DesiredStateConfiguration -Name TestLabel -NewName NewTestLabel -Color BBBB00 ``` #### Bulk Updating Labels in a Repository @@ -354,7 +403,7 @@ Update-GitHubCurrentUser -Location 'Seattle, WA' -Hireable:$false #### Getting any user ```powershell -Get-GitHubUser -Name octocat +Get-GitHubUser -UserName octocat ``` #### Getting all users @@ -369,12 +418,12 @@ Get-GitHubUser #### Get all the forks for a repository ```powershell -Get-GitHubRepositoryFork -OwnerName Microsoft -RepositoryName PowerShellForGitHub +Get-GitHubRepositoryFork -OwnerName microsoft -RepositoryName PowerShellForGitHub ``` #### Create a new fork ```powershell -New-GitHubRepositoryForm -OwnerName Microsoft -RepositoryName PowerShellForGitHub +New-GitHubRepositoryForm -OwnerName microsoft -RepositoryName PowerShellForGitHub ``` ---------- @@ -383,22 +432,22 @@ New-GitHubRepositoryForm -OwnerName Microsoft -RepositoryName PowerShellForGitHu #### Get the referrer traffic for a repository ```powershell -Get-GitHubReferrerTraffic -OwnerName Microsoft -RepositoryName PowerShellForGitHub +Get-GitHubReferrerTraffic -OwnerName microsoft -RepositoryName PowerShellForGitHub ``` #### Get the popular content for a repository ```powershell -Get-GitHubPathTraffic -OwnerName Microsoft -RepositoryName PowerShellForGitHub +Get-GitHubPathTraffic -OwnerName microsoft -RepositoryName PowerShellForGitHub ``` #### Get the number of views for a repository ```powershell -Get-GitHubViewTraffic -OwnerName Microsoft -RepositoryName PowerShellForGitHub -Per Week +Get-GitHubViewTraffic -OwnerName microsoft -RepositoryName PowerShellForGitHub -Per Week ``` #### Get the number of clones for a repository ```powershell -Get-GitHubCloneTraffic -OwnerName Microsoft -RepositoryName PowerShellForGitHub -Per Day +Get-GitHubCloneTraffic -OwnerName microsoft -RepositoryName PowerShellForGitHub -Per Day ``` ---------- @@ -407,56 +456,56 @@ Get-GitHubCloneTraffic -OwnerName Microsoft -RepositoryName PowerShellForGitHub #### Get assignees ```powershell -Get-GitHubAssignee -OwnerName Microsoft -RepositoryName PowerShellForGitHub +Get-GitHubAssignee -OwnerName microsoft -RepositoryName PowerShellForGitHub ``` #### Check assignee permission ```powershell -$HasPermission = Test-GitHubAssignee -OwnerName Microsoft -RepositoryName PowerShellForGitHub -Assignee "LoginID123" +$HasPermission = Test-GitHubAssignee -OwnerName microsoft -RepositoryName PowerShellForGitHub -Assignee "LoginID123" ``` #### Add assignee to an issue ```powershell -New-GithubAssignee -OwnerName Microsoft -RepositoryName PowerShellForGitHub -Assignees $assignees -Issue 1 +New-GitHubAssignee -OwnerName microsoft -RepositoryName PowerShellForGitHub -Assignees $assignees -Issue 1 ``` #### Remove assignee from an issue ```powershell -Remove-GithubAssignee -OwnerName Microsoft -RepositoryName PowerShellForGitHub -Assignees $assignees -Issue 1 +Remove-GitHubAssignee -OwnerName microsoft -RepositoryName PowerShellForGitHub -Assignees $assignees -Issue 1 ``` ---------- ### Comments -#### Get comments from an issue +#### Get comments from an Issue ```powershell -Get-GitHubIssueComment -OwnerName Microsoft -RepositoryName PowerShellForGitHub -Issue 1 +Get-GitHubIssueComment -OwnerName microsoft -RepositoryName PowerShellForGitHub -Issue 1 ``` -#### Get comments from a repository +#### Get Issue comments from a repository ```powershell -Get-GitHubRepositoryComment -OwnerName Microsoft -RepositoryName PowerShellForGitHub -Sort Created -Direction Ascending -Since '2011-04-14T16:00:49Z' +Get-GitHubRepositoryComment -OwnerName microsoft -RepositoryName PowerShellForGitHub -Sort Created -Direction Ascending -Since '2011-04-14T16:00:49Z' ``` -#### Get a single comment +#### Get a single Issue comment ```powershell -Get-GitHubComment -OwnerName Microsoft -RepositoryName PowerShellForGitHub -CommentID 1 +Get-GitHubIssueComment -OwnerName microsoft -RepositoryName PowerShellForGitHub -CommentID 1 ``` -#### Adding a new comment to an issue +#### Adding a new comment to an Issue ```powershell -New-GitHubComment -OwnerName Microsoft -RepositoryName PowerShellForGitHub -Issue 1 -Body "Testing this API" +New-GitHubIssueComment -OwnerName microsoft -RepositoryName PowerShellForGitHub -Issue 1 -Body "Testing this API" ``` -#### Editing an existing comment +#### Editing an existing Issue comment ```powershell -Set-GitHubComment -OwnerName Microsoft -RepositoryName PowerShellForGitHub -CommentID 1 -Body "Testing this API" +Set-GitHubIssueComment -OwnerName microsoft -RepositoryName PowerShellForGitHub -CommentID 1 -Body "Testing this API" ``` -#### Removing a comment +#### Removing an Issue comment ```powershell -Remove-GitHubComment -OwnerName Microsoft -RepositoryName PowerShellForGitHub -CommentID 1 +Remove-GitHubIssueComment -OwnerName microsoft -RepositoryName PowerShellForGitHub -CommentID 1 ``` ---------- @@ -465,28 +514,28 @@ Remove-GitHubComment -OwnerName Microsoft -RepositoryName PowerShellForGitHub -C #### Get milestones from a repository ```powershell -Get-GitHubMilestone -OwnerName Microsoft -RepositoryName PowerShellForGitHub -Sort DueOn -Direction Ascending -DueOn '2011-04-14T16:00:49Z' +Get-GitHubMilestone -OwnerName microsoft -RepositoryName PowerShellForGitHub -Sort DueOn -Direction Ascending -DueOn '2011-04-14T16:00:49Z' ``` #### Get a single milestone ```powershell -Get-GitHubMilestone -OwnerName Microsoft -RepositoryName PowerShellForGitHub -Milestone 1 +Get-GitHubMilestone -OwnerName microsoft -RepositoryName PowerShellForGitHub -Milestone 1 ``` #### Assign an existing issue to a new milestone ```powershell -New-GitHubMilestone -OwnerName Microsoft -RepositoryName PowerShellForGitHub -Title "Testing this API" -Update-GitHubIssue -OwnerName Microsoft -RepositoryName PowerShellForGitHub -Issue 2 -Milestone 1 +New-GitHubMilestone -OwnerName microsoft -RepositoryName PowerShellForGitHub -Title "Testing this API" +Update-GitHubIssue -OwnerName microsoft -RepositoryName PowerShellForGitHub -Issue 2 -Milestone 1 ``` #### Editing an existing milestone ```powershell -Set-GitHubMilestone -OwnerName Microsoft -RepositoryName PowerShellForGitHub -Milestone 1 -Title "Testing this API edited" +Set-GitHubMilestone -OwnerName microsoft -RepositoryName PowerShellForGitHub -Milestone 1 -Title "Testing this API edited" ``` #### Removing a milestone ```powershell -Remove-GitHubMilestone -OwnerName Microsoft -RepositoryName PowerShellForGitHub -Milestone 1 +Remove-GitHubMilestone -OwnerName microsoft -RepositoryName PowerShellForGitHub -Milestone 1 ``` ---------- @@ -495,17 +544,17 @@ Remove-GitHubMilestone -OwnerName Microsoft -RepositoryName PowerShellForGitHub #### Get events from a repository ```powershell -Get-GitHubEvent -OwnerName Microsoft -RepositoryName PowerShellForGitHub +Get-GitHubEvent -OwnerName microsoft -RepositoryName PowerShellForGitHub ``` #### Get events from an issue ```powershell -Get-GitHubEvent -OwnerName Microsoft -RepositoryName PowerShellForGitHub -Issue 1 +Get-GitHubEvent -OwnerName microsoft -RepositoryName PowerShellForGitHub -Issue 1 ``` #### Get a single event ```powershell -Get-GitHubEvent -OwnerName Microsoft -RepositoryName PowerShellForGitHub -EventID 1 +Get-GitHubEvent -OwnerName microsoft -RepositoryName PowerShellForGitHub -EventID 1 ``` ---------- @@ -514,7 +563,7 @@ Get-GitHubEvent -OwnerName Microsoft -RepositoryName PowerShellForGitHub -EventI #### Get projects for a repository ```powershell -Get-GitHubProject -OwnerName Microsoft -RepositoryName PowerShellForGitHub +Get-GitHubProject -OwnerName microsoft -RepositoryName PowerShellForGitHub ``` #### Get projects for a user @@ -524,12 +573,12 @@ Get-GitHubProject -UserName octocat #### Create a project ```powershell -New-GitHubProject -OwnerName octocat -RepositoryName PowerShellForGitHub -Name TestProject +New-GitHubProject -OwnerName octocat -RepositoryName PowerShellForGitHub -ProjectName TestProject ``` #### Add a column to a project ```powershell -New-GitHubProjectColumn -Project 1 -Name 'To Do' +New-GitHubProjectColumn -Project 1 -ColumnName 'To Do' ``` #### Add a card to a column @@ -560,12 +609,15 @@ Move-GitHubProjectCard -Card 4 -ColumnId 6 -Bottom @LazyWinAdmin used this module to migrate his blog comments from Disqus to GitHub Issues. [See blog post](https://lazywinadmin.com/2019/04/moving_blog_comments.html) for full details. ```powershell +# Get your repo +$repo = Get-GitHubRepository -OwnerName -RepositoryName RepoName + # Create an issue -$IssueObject = New-GitHubIssue @githubsplat -Title $IssueTitle -Body $body -Label 'blog comments' +$issue = $repo | New-GitHubIssue -Title $IssueTitle -Body $body -Label 'blog comments' # Create Comment -New-GitHubComment @githubsplat -Issue $IssueObject.number -Body $CommentBody +$issue | New-GitHubIssueComment -Body $CommentBody # Close issue -Update-GitHubIssue @githubsplat -Issue $IssueObject.number -State Closed +$issue | Update-GitHubIssue -State Closed ``` diff --git a/UpdateCheck.ps1 b/UpdateCheck.ps1 index c76bb253..0773f60c 100644 --- a/UpdateCheck.ps1 +++ b/UpdateCheck.ps1 @@ -58,7 +58,8 @@ function Invoke-UpdateCheck if ($state -eq 'Failed') { # We'll just assume we're up-to-date if we failed to check today. - Write-Log -Message '[$moduleName] update check failed for today (web request failed). Assuming up-to-date.' -Level Verbose + $message = '[$moduleName] update check failed for today (web request failed). Assuming up-to-date.' + Write-Log -Message $message -Level Verbose $script:HasLatestVersion = $true # Clear out the job info (even though we don't need the result) @@ -81,22 +82,26 @@ function Invoke-UpdateCheck $script:HasLatestVersion = $latestVersion -eq $moduleVersion if ($script:HasLatestVersion) { - Write-Log "[$moduleName] update check complete. Running latest version: $latestVersion" -Level Verbose + $message = "[$moduleName] update check complete. Running latest version: $latestVersion" + Write-Log =Message $message -Level Verbose } elseif ($moduleVersion -gt $latestVersion) { - Write-Log "[$moduleName] update check complete. This version ($moduleVersion) is newer than the latest published version ($latestVersion)." -Level Verbose + $message = "[$moduleName] update check complete. This version ($moduleVersion) is newer than the latest published version ($latestVersion)." + Write-Log -Message $message -Level Verbose } else { - Write-Log "A newer version of $moduleName is available ($latestVersion). Your current version is $moduleVersion. Run 'Update-Module $moduleName' to get up-to-date." -Level Warning + $message = "A newer version of $moduleName is available ($latestVersion). Your current version is $moduleVersion. Run 'Update-Module $moduleName' to get up-to-date." + Write-Log -Message $message -Level Warning } } catch { # This could happen if the server returned back an invalid (non-XML) response for the request # for some reason. - Write-Log -Message "[$moduleName] update check failed for today (invalid XML response). Assuming up-to-date." -Level Verbose + $message = "[$moduleName] update check failed for today (invalid XML response). Assuming up-to-date." + Write-Log -Message $message -Level Verbose $script:HasLatestVersion = $true } From 618398eedd4571a42e000a4ce4527b56244f7720 Mon Sep 17 00:00:00 2001 From: Giuseppe Campanelli Date: Thu, 18 Jun 2020 15:44:16 -0400 Subject: [PATCH 34/60] Save & restore the state of [Net.ServicePointManager]::SecurityProtocol (#240) [Net.ServicePointManager]::SecurityProtocol is a global configuration that was being modified during the execution of `Invoke-GHRestMethod`. With this change, the global state will be cached before the change and restored immediately afterwards. Fixes #230 --- GitHubCore.ps1 | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/GitHubCore.ps1 b/GitHubCore.ps1 index 542ae5a8..09068429 100644 --- a/GitHubCore.ps1 +++ b/GitHubCore.ps1 @@ -201,6 +201,7 @@ function Invoke-GHRestMethod } $NoStatus = Resolve-ParameterWithDefaultConfigurationValue -Name NoStatus -ConfigValueName DefaultNoStatus + $originalSecurityProtocol = [Net.ServicePointManager]::SecurityProtocol try { @@ -560,6 +561,10 @@ function Invoke-GHRestMethod Set-TelemetryException -Exception $ex -ErrorBucket $errorBucket -Properties $localTelemetryProperties -NoStatus:$NoStatus throw $newLineOutput } + finally + { + [Net.ServicePointManager]::SecurityProtocol = $originalSecurityProtocol + } } function Invoke-GHRestMethodMultipleResult From 06f596e8dfc53abafa5eb15dec380d609b510588 Mon Sep 17 00:00:00 2001 From: Tyler James Leonhardt Date: Fri, 19 Jun 2020 19:01:48 -0700 Subject: [PATCH 35/60] PSScriptAnalyzer and VS Code tooling (#224) This adds the following files: * `launch.json` - used by vscode in order to debug PowerShellForGitHub with ease * `PSScriptAnalyzerSettings.psd1` - used by PSSA to check syntax and also by VS Code to show problems for all rules PSSA Rules: https://github.com/PowerShell/PSScriptAnalyzer/tree/master/RuleDocumentation Specifically: https://github.com/PowerShell/PSScriptAnalyzer/blob/master/RuleDocumentation/UseCompatibleSyntax.md --- .vscode/launch.json | 17 +++++++++++++++++ CONTRIBUTING.md | 2 +- PSScriptAnalyzerSettings.psd1 | 19 +++++++++++++++++++ .../templates/run-staticAnalysis.yaml | 2 +- build/scripts/ConvertTo-NUnitXml.ps1 | 2 +- 5 files changed, 39 insertions(+), 3 deletions(-) create mode 100644 .vscode/launch.json create mode 100644 PSScriptAnalyzerSettings.psd1 diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 00000000..7820daf8 --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,17 @@ +{ + // Use IntelliSense to learn about possible attributes. + // Hover to view descriptions of existing attributes. + // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "configurations": [ + { + // Run this launch config and then set breakpoints in your module. + // Then you can `Import-Module -Force ./PowerShellForGitHub.psd1` + // and run a function that will hit the breakpoint. + "name": "PowerShell: Interactive Session", + "type": "PowerShell", + "request": "launch", + "cwd": "" + } + ] +} diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 1f690f71..d561cd59 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -153,7 +153,7 @@ Update-Module -Name PSScriptAnalyzer Once it's installed (or updated), from the root of your enlistment simply call ```powershell -Invoke-ScriptAnalyzer -Path .\ -Recurse +Invoke-ScriptAnalyzer -Settings ./PSScriptAnalyzerSettings.psd1 -Path ./ -Recurse ``` That should return with no output. If you see any output when calling that command, diff --git a/PSScriptAnalyzerSettings.psd1 b/PSScriptAnalyzerSettings.psd1 new file mode 100644 index 00000000..c7fa4bf3 --- /dev/null +++ b/PSScriptAnalyzerSettings.psd1 @@ -0,0 +1,19 @@ +# PSScriptAnalyzerSettings.psd1 +# Settings for PSScriptAnalyzer invocation. +# All default rules are also enabled. +@{ + Rules = @{ + PSUseCompatibleSyntax = @{ + # This turns the rule on (setting it to false will turn it off) + Enable = $true + + # Simply list the targeted versions of PowerShell here + TargetVersions = @( + '4.0', + '5.1', + '6.2', + '7.0' + ) + } + } +} diff --git a/build/pipelines/templates/run-staticAnalysis.yaml b/build/pipelines/templates/run-staticAnalysis.yaml index 86a673fc..5a3a14fa 100644 --- a/build/pipelines/templates/run-staticAnalysis.yaml +++ b/build/pipelines/templates/run-staticAnalysis.yaml @@ -9,7 +9,7 @@ steps: displayName: 'Install PSScriptAnalyzer' - powershell: | - $results = try { Invoke-ScriptAnalyzer -Path ./ –Recurse -ErrorAction Stop } catch { $_.Exception.StackTrace; throw } + $results = try { Invoke-ScriptAnalyzer -Settings ./PSScriptAnalyzerSettings.psd1 -Path ./ –Recurse -ErrorAction Stop } catch { $_.Exception.StackTrace; throw } $results | ForEach-Object { Write-Host "##vso[task.logissue type=$($_.Severity);sourcepath=$($_.ScriptPath);linenumber=$($_.Line);columnnumber=$($_.Column);]$($_.Message)" } $null = New-Item -Path ..\ -Name ScriptAnalyzer -ItemType Directory -Force diff --git a/build/scripts/ConvertTo-NUnitXml.ps1 b/build/scripts/ConvertTo-NUnitXml.ps1 index fc65224b..b4321152 100644 --- a/build/scripts/ConvertTo-NUnitXml.ps1 +++ b/build/scripts/ConvertTo-NUnitXml.ps1 @@ -16,7 +16,7 @@ Overwrite the file at Path if it exists. .EXAMPLE - $results = Invoke-ScriptAnalyzer -Path ./ -Recurse + $results = Invoke-ScriptAnalyzer -Settings ./PSScriptAnalyzerSettings.psd1 -Path ./ -Recurse .\ConverTo-NUnitXml.ps1 -ScriptAnalyzerResult $results -Path ./PSScriptAnalyzerFailures.xml #> [CmdletBinding()] From 2385b5cf5d959a7581bf968f15f346d9a0ff816b Mon Sep 17 00:00:00 2001 From: Howard Wolosky Date: Sat, 20 Jun 2020 19:03:49 -0700 Subject: [PATCH 36/60] Fix usage of Resolve-RepositoryElements -DisableValidation (#243) There are only four legitimate instances in the module as of today where we should be disabling the validation that Resolve-RepositoryElements provides. All other instances were likely introduced due to mistaken copy/paste issues. The only reason we'd want to disable the validation is if there are multiple parametersets that need to be considered for the function, where at least one _would_ require `OwnerName`/`RepositoryName` while at least one other one _would not_. This would have meant that we would have attempted to call those GitHub endpoints with incomplete path information which would have most likely resulted in an error that we could have caught sooner without needing to invoke a web request. I've fixed up all of these incorrect instances, and added a comment to the two remaining ones to remind others later on why that switch is being used, and help prevent incorrect usage in the future. I've also cleaned up unnecessary passing of `BoundParameters` since it already does the correct thing by default. --- GitHubContents.ps1 | 2 +- GitHubIssues.ps1 | 4 ++++ GitHubMiscellaneous.ps1 | 24 ++++++++++++++++++-- GitHubReleases.ps1 | 2 +- GitHubRepositories.ps1 | 35 +++++++++++++++++++---------- GitHubRepositoryForks.ps1 | 4 ++-- Tests/GitHubMiscellaneous.tests.ps1 | 20 +++++++++++++++++ 7 files changed, 73 insertions(+), 18 deletions(-) diff --git a/GitHubContents.ps1 b/GitHubContents.ps1 index 1514382e..780a538d 100644 --- a/GitHubContents.ps1 +++ b/GitHubContents.ps1 @@ -138,7 +138,7 @@ Write-InvocationLog - $elements = Resolve-RepositoryElements -DisableValidation + $elements = Resolve-RepositoryElements $OwnerName = $elements.ownerName $RepositoryName = $elements.repositoryName diff --git a/GitHubIssues.ps1 b/GitHubIssues.ps1 index 8e903c00..fa043e3d 100644 --- a/GitHubIssues.ps1 +++ b/GitHubIssues.ps1 @@ -219,6 +219,9 @@ filter Get-GitHubIssue Write-InvocationLog + # Intentionally disabling validation here because parameter sets exist that do not require + # an OwnerName and RepositoryName. Therefore, we will do futher parameter validation further + # into the function. $elements = Resolve-RepositoryElements -DisableValidation $OwnerName = $elements.ownerName $RepositoryName = $elements.repositoryName @@ -226,6 +229,7 @@ filter Get-GitHubIssue $telemetryProperties = @{ 'OwnerName' = (Get-PiiSafeString -PlainText $OwnerName) 'RepositoryName' = (Get-PiiSafeString -PlainText $RepositoryName) + 'OrganizationName' = (Get-PiiSafeString -PlainText $OrganizationName) 'ProvidedIssue' = $PSBoundParameters.ContainsKey('Issue') } diff --git a/GitHubMiscellaneous.ps1 b/GitHubMiscellaneous.ps1 index b137d6ea..10f98fb5 100644 --- a/GitHubMiscellaneous.ps1 +++ b/GitHubMiscellaneous.ps1 @@ -310,14 +310,18 @@ filter Get-GitHubLicense Write-InvocationLog + $telemetryProperties = @{} + + # Intentionally disabling validation because parameter sets exist that both require + # OwnerName/RepositoryName (to get that repo's License) as well as don't (to get + # all known Licenses). We'll do additional parameter validation within the function. $elements = Resolve-RepositoryElements -DisableValidation $OwnerName = $elements.ownerName $RepositoryName = $elements.repositoryName - $telemetryProperties = @{} - $uriFragment = 'licenses' $description = 'Getting all licenses' + if ($PSBoundParameters.ContainsKey('Key')) { $telemetryProperties['Key'] = $Name @@ -331,6 +335,12 @@ filter Get-GitHubLicense $uriFragment = "repos/$OwnerName/$RepositoryName/license" $description = "Getting the license for $RepositoryName" } + elseif ([String]::IsNullOrEmpty($OwnerName) -xor [String]::IsNullOrEmpty($RepositoryName)) + { + $message = 'When specifying OwnerName and/or RepositorName, BOTH must be specified.' + Write-Log -Message $message -Level Error + throw $message + } $params = @{ 'UriFragment' = $uriFragment @@ -537,6 +547,9 @@ filter Get-GitHubCodeOfConduct Write-InvocationLog + # Intentionally disabling validation because parameter sets exist that both require + # OwnerName/RepositoryName (to get that repo's Code of Conduct) as well as don't (to get + # all known Codes of Conduct). We'll do additional parameter validation within the function. $elements = Resolve-RepositoryElements -DisableValidation $OwnerName = $elements.ownerName $RepositoryName = $elements.repositoryName @@ -545,6 +558,7 @@ filter Get-GitHubCodeOfConduct $uriFragment = 'codes_of_conduct' $description = 'Getting all Codes of Conduct' + if ($PSBoundParameters.ContainsKey('Key')) { $telemetryProperties['Key'] = $Name @@ -558,6 +572,12 @@ filter Get-GitHubCodeOfConduct $uriFragment = "repos/$OwnerName/$RepositoryName/community/code_of_conduct" $description = "Getting the Code of Conduct for $RepositoryName" } + elseif ([String]::IsNullOrEmpty($OwnerName) -xor [String]::IsNullOrEmpty($RepositoryName)) + { + $message = 'When specifying OwnerName and/or RepositorName, BOTH must be specified.' + Write-Log -Message $message -Level Error + throw $message + } $params = @{ 'UriFragment' = $uriFragment diff --git a/GitHubReleases.ps1 b/GitHubReleases.ps1 index 6083f669..b4024828 100644 --- a/GitHubReleases.ps1 +++ b/GitHubReleases.ps1 @@ -176,7 +176,7 @@ filter Get-GitHubRelease Write-InvocationLog -Invocation $MyInvocation - $elements = Resolve-RepositoryElements -BoundParameters $PSBoundParameters -DisableValidation + $elements = Resolve-RepositoryElements $OwnerName = $elements.ownerName $RepositoryName = $elements.repositoryName diff --git a/GitHubRepositories.ps1 b/GitHubRepositories.ps1 index c59e33e5..e61bc1d5 100644 --- a/GitHubRepositories.ps1 +++ b/GitHubRepositories.ps1 @@ -306,6 +306,7 @@ filter Remove-GitHubRepository DefaultParameterSetName='Elements', ConfirmImpact="High")] [Alias('Delete-GitHubRepository')] + [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSReviewUnusedParameter", "", Justification="One or more parameters (like NoStatus) are only referenced by helper methods which get access to it from the stack via Get-Variable -Scope 1.")] param( [Parameter(ParameterSetName='Elements')] [string] $OwnerName, @@ -329,7 +330,7 @@ filter Remove-GitHubRepository Write-InvocationLog -Invocation $MyInvocation - $elements = Resolve-RepositoryElements -BoundParameters $PSBoundParameters + $elements = Resolve-RepositoryElements $OwnerName = $elements.ownerName $RepositoryName = $elements.repositoryName @@ -548,7 +549,11 @@ filter Get-GitHubRepository Write-InvocationLog -Invocation $MyInvocation - $elements = Resolve-RepositoryElements -BoundParameters $PSBoundParameters -DisableValidation + # We are explicitly disabling validation here because a valid parameter set for this function + # allows the OwnerName to be passed in, but not the RepositoryName. That would allow the caller + # to get all of the repositories owned by a specific username. Therefore, we don't want to fail + # if both have not been supplied...we'll do the extra validation within the function. + $elements = Resolve-RepositoryElements -DisableValidation $OwnerName = $elements.ownerName $RepositoryName = $elements.repositoryName @@ -1012,7 +1017,7 @@ filter Update-GitHubRepository Write-InvocationLog -Invocation $MyInvocation - $elements = Resolve-RepositoryElements -BoundParameters $PSBoundParameters + $elements = Resolve-RepositoryElements $OwnerName = $elements.ownerName $RepositoryName = $elements.repositoryName @@ -1131,6 +1136,7 @@ filter Get-GitHubRepositoryTopic DefaultParameterSetName='Elements')] [OutputType({$script:GitHubRepositoryTopicTypeName})] [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSShouldProcess", "", Justification="Methods called within here make use of PSShouldProcess, and the switch is passed on to them inherently.")] + [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSReviewUnusedParameter", "", Justification="One or more parameters (like NoStatus) are only referenced by helper methods which get access to it from the stack via Get-Variable -Scope 1.")] param( [Parameter(ParameterSetName='Elements')] [string] $OwnerName, @@ -1152,7 +1158,7 @@ filter Get-GitHubRepositoryTopic Write-InvocationLog -Invocation $MyInvocation - $elements = Resolve-RepositoryElements -BoundParameters $PSBoundParameters + $elements = Resolve-RepositoryElements $OwnerName = $elements.ownerName $RepositoryName = $elements.repositoryName @@ -1316,7 +1322,7 @@ function Set-GitHubRepositoryTopic { Write-InvocationLog -Invocation $MyInvocation - $elements = Resolve-RepositoryElements -BoundParameters $PSBoundParameters + $elements = Resolve-RepositoryElements $OwnerName = $elements.ownerName $RepositoryName = $elements.repositoryName @@ -1434,6 +1440,7 @@ filter Get-GitHubRepositoryContributor [OutputType({$script:GitHubUserTypeName})] [OutputType({$script:GitHubRepositoryContributorStatisticsTypeName})] [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSShouldProcess", "", Justification="Methods called within here make use of PSShouldProcess, and the switch is passed on to them inherently.")] + [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSReviewUnusedParameter", "", Justification="One or more parameters (like NoStatus) are only referenced by helper methods which get access to it from the stack via Get-Variable -Scope 1.")] param( [Parameter(ParameterSetName='Elements')] [string] $OwnerName, @@ -1459,7 +1466,7 @@ filter Get-GitHubRepositoryContributor Write-InvocationLog -Invocation $MyInvocation - $elements = Resolve-RepositoryElements -BoundParameters $PSBoundParameters + $elements = Resolve-RepositoryElements $OwnerName = $elements.ownerName $RepositoryName = $elements.repositoryName @@ -1577,8 +1584,9 @@ filter Get-GitHubRepositoryCollaborator [CmdletBinding( SupportsShouldProcess, DefaultParameterSetName='Elements')] - [OutputType({$script:GitHubUserTypeName})] - [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSShouldProcess", "", Justification="Methods called within here make use of PSShouldProcess, and the switch is passed on to them inherently.")] + [OutputType({$script:GitHubUserTypeName})] + [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSShouldProcess", "", Justification="Methods called within here make use of PSShouldProcess, and the switch is passed on to them inherently.")] + [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSReviewUnusedParameter", "", Justification="One or more parameters (like NoStatus) are only referenced by helper methods which get access to it from the stack via Get-Variable -Scope 1.")] param( [Parameter(ParameterSetName='Elements')] [string] $OwnerName, @@ -1603,7 +1611,7 @@ filter Get-GitHubRepositoryCollaborator Write-InvocationLog -Invocation $MyInvocation - $elements = Resolve-RepositoryElements -BoundParameters $PSBoundParameters + $elements = Resolve-RepositoryElements $OwnerName = $elements.ownerName $RepositoryName = $elements.repositoryName @@ -1692,6 +1700,7 @@ filter Get-GitHubRepositoryLanguage DefaultParameterSetName='Elements')] [OutputType({$script:GitHubRepositoryLanguageTypeName})] [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSShouldProcess", "", Justification="Methods called within here make use of PSShouldProcess, and the switch is passed on to them inherently.")] + [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSReviewUnusedParameter", "", Justification="One or more parameters (like NoStatus) are only referenced by helper methods which get access to it from the stack via Get-Variable -Scope 1.")] param( [Parameter(ParameterSetName='Elements')] [string] $OwnerName, @@ -1713,7 +1722,7 @@ filter Get-GitHubRepositoryLanguage Write-InvocationLog -Invocation $MyInvocation - $elements = Resolve-RepositoryElements -BoundParameters $PSBoundParameters + $elements = Resolve-RepositoryElements $OwnerName = $elements.ownerName $RepositoryName = $elements.repositoryName @@ -1798,6 +1807,7 @@ filter Get-GitHubRepositoryTag DefaultParameterSetName='Elements')] [OutputType({$script:GitHubRepositoryTagTypeName})] [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSShouldProcess", "", Justification="Methods called within here make use of PSShouldProcess, and the switch is passed on to them inherently.")] + [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSReviewUnusedParameter", "", Justification="One or more parameters (like NoStatus) are only referenced by helper methods which get access to it from the stack via Get-Variable -Scope 1.")] param( [Parameter(ParameterSetName='Elements')] [string] $OwnerName, @@ -1819,7 +1829,7 @@ filter Get-GitHubRepositoryTag Write-InvocationLog -Invocation $MyInvocation - $elements = Resolve-RepositoryElements -BoundParameters $PSBoundParameters + $elements = Resolve-RepositoryElements $OwnerName = $elements.ownerName $RepositoryName = $elements.repositoryName @@ -1909,6 +1919,7 @@ filter Move-GitHubRepositoryOwnership [OutputType({$script:GitHubRepositoryTypeName})] [Alias('Transfer-GitHubRepositoryOwnership')] [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSShouldProcess", "", Justification="Methods called within here make use of PSShouldProcess, and the switch is passed on to them inherently.")] + [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSReviewUnusedParameter", "", Justification="One or more parameters (like NoStatus) are only referenced by helper methods which get access to it from the stack via Get-Variable -Scope 1.")] param( [Parameter(ParameterSetName='Elements')] [string] $OwnerName, @@ -1936,7 +1947,7 @@ filter Move-GitHubRepositoryOwnership Write-InvocationLog -Invocation $MyInvocation - $elements = Resolve-RepositoryElements -BoundParameters $PSBoundParameters + $elements = Resolve-RepositoryElements $OwnerName = $elements.ownerName $RepositoryName = $elements.repositoryName diff --git a/GitHubRepositoryForks.ps1 b/GitHubRepositoryForks.ps1 index 3322c726..457ee90d 100644 --- a/GitHubRepositoryForks.ps1 +++ b/GitHubRepositoryForks.ps1 @@ -91,7 +91,7 @@ filter Get-GitHubRepositoryFork Write-InvocationLog - $elements = Resolve-RepositoryElements -DisableValidation + $elements = Resolve-RepositoryElements $OwnerName = $elements.ownerName $RepositoryName = $elements.repositoryName @@ -211,7 +211,7 @@ filter New-GitHubRepositoryFork Write-InvocationLog - $elements = Resolve-RepositoryElements -DisableValidation + $elements = Resolve-RepositoryElements $OwnerName = $elements.ownerName $RepositoryName = $elements.repositoryName diff --git a/Tests/GitHubMiscellaneous.tests.ps1 b/Tests/GitHubMiscellaneous.tests.ps1 index 7be6eb96..9d9e64dd 100644 --- a/Tests/GitHubMiscellaneous.tests.ps1 +++ b/Tests/GitHubMiscellaneous.tests.ps1 @@ -99,6 +99,16 @@ Describe 'Get-GitHubLicense' { } } + Context 'Will fail if not provided both OwnerName and RepositoryName' { + It 'Should fail if only OwnerName is specified' { + { Get-GitHubLicense -OwnerName 'PowerShell' } | Should -Throw + } + + It 'Should fail if only RepositoryName is specified' { + { Get-GitHubLicense -RepositoryName 'PowerShell' } | Should -Throw + } + } + Context 'Can get the license for a repo with the repo on the pipeline' { BeforeAll { $result = Get-GitHubRepository -OwnerName 'PowerShell' -RepositoryName 'PowerShell' | Get-GitHubLicense @@ -213,6 +223,16 @@ Describe 'Get-GitHubCodeOfConduct' { } } + Context 'Will fail if not provided both OwnerName and RepositoryName' { + It 'Should fail if only OwnerName is specified' { + { Get-GitHubCodeOfConduct -OwnerName 'PowerShell' } | Should -Throw + } + + It 'Should fail if only RepositoryName is specified' { + { Get-GitHubCodeOfConduct -RepositoryName 'PowerShell' } | Should -Throw + } + } + Context 'Can get the code of conduct for a repo with the repo on the pipeline' { BeforeAll { $result = Get-GitHubRepository -OwnerName 'PowerShell' -RepositoryName 'PowerShell' | Get-GitHubCodeOfConduct From b70c7d433721bcbe82e6272e32979cf2e5c5e1d8 Mon Sep 17 00:00:00 2001 From: Simon Heather <32168619+X-Guardian@users.noreply.github.com> Date: Wed, 24 Jun 2020 20:24:43 +0100 Subject: [PATCH 37/60] GitHubRepositories: Add Dependabot Service Functions (#235) This adds the following functions to the `GitHubRepositories` module to manage the GitHub Dependabot services. - `Test-GitHubRepositoryVulnerabilityAlert` - `Enable-GitHubRepositoryVulnerabilityAlert` - `Disable-GitHubRepositoryVulnerabilityAlert` - `Enable-GitHubRepositorySecurityFix` - `Disable-GitHubRepositorySecurityFix` There is no `Test-GitHubRepositorySecurityFix` function is there is currently no API for this. [GitHub Repositories Rest API v3](https://developer.github.com/v3/repos/) --- GitHubRepositories.ps1 | 616 +++++++++++++++++++++++++++++ PowerShellForGitHub.psd1 | 5 + Tests/GitHubRepositories.tests.ps1 | 123 +++++- USAGE.md | 40 ++ 4 files changed, 783 insertions(+), 1 deletion(-) diff --git a/GitHubRepositories.ps1 b/GitHubRepositories.ps1 index e61bc1d5..de205538 100644 --- a/GitHubRepositories.ps1 +++ b/GitHubRepositories.ps1 @@ -1976,6 +1976,622 @@ filter Move-GitHubRepositoryOwnership return (Invoke-GHRestMethod @params | Add-GitHubRepositoryAdditionalProperties) } +filter Test-GitHubRepositoryVulnerabilityAlert +{ + <# + .SYNOPSIS + Retrieves the status of vulnerability alerts for a repository on GitHub. + + .DESCRIPTION + Retrieves the status of vulnerability alerts for a repository on GitHub. + + The Git repo for this module can be found here: http://aka.ms/PowerShellForGitHub + + .PARAMETER OwnerName + Owner of the repository. + If not supplied here, the DefaultOwnerName configuration property value will be used. + + .PARAMETER RepositoryName + Name of the repository. + If not supplied here, the DefaultRepositoryName configuration property value will be used. + + .PARAMETER Uri + Uri for the repository. + The OwnerName and RepositoryName will be extracted from here instead of needing to provide + them individually. + + .PARAMETER AccessToken + If provided, this will be used as the AccessToken for authentication with the + REST Api. Otherwise, will attempt to use the configured value or will run unauthenticated. + + .PARAMETER NoStatus + If this switch is specified, long-running commands will run on the main thread + with no commandline status update. When not specified, those commands run in + the background, enabling the command prompt to provide status information. + If not supplied here, the DefaultNoStatus configuration property value will be used. + + .INPUTS + GitHub.Branch + GitHub.Content + GitHub.Event + GitHub.Issue + GitHub.IssueComment + GitHub.Label + GitHub.Milestone + GitHub.PullRequest + GitHub.Project + GitHub.ProjectCard + GitHub.ProjectColumn + GitHub.Release + GitHub.Repository + + .OUTPUTS + System.Boolean + + .NOTES + The authenticated user must have admin access to the repository. + + .EXAMPLE + Test-GitHubRepositoryVulnerabilityAlert -OwnerName Microsoft -RepositoryName PowerShellForGitHub + + Retrieves the status of vulnerability alerts for the PowerShellForGithub repository. + + .EXAMPLE + Test-GitHubRepositoryVulnerabilityAlert -Uri https://github.com/PowerShell/PowerShellForGitHub + + Retrieves the status of vulnerability alerts for the PowerShellForGithub repository. +#> + [CmdletBinding( + PositionalBinding = $false, + DefaultParameterSetName='Elements')] + param( + [Parameter(ParameterSetName='Elements')] + [string] $OwnerName, + + [Parameter(ParameterSetName='Elements')] + [string] $RepositoryName, + + [Parameter( + Mandatory, + Position = 1, + ValueFromPipelineByPropertyName, + ParameterSetName='Uri')] + [Alias('RepositoryUrl')] + [string] $Uri, + + [string] $AccessToken, + + [switch] $NoStatus + ) + + Write-InvocationLog + + $elements = Resolve-RepositoryElements -BoundParameters $PSBoundParameters + $OwnerName = $elements.ownerName + $RepositoryName = $elements.repositoryName + + $telemetryProperties = @{ + 'OwnerName' = (Get-PiiSafeString -PlainText $OwnerName) + 'RepositoryName' = (Get-PiiSafeString -PlainText $RepositoryName) + } + + $params = @{ + UriFragment = "repos/$OwnerName/$RepositoryName/vulnerability-alerts" + Description = "Getting Vulnerability Alerts status for $RepositoryName" + AcceptHeader = $script:dorianAcceptHeader + Method = 'Get' + AccessToken = $AccessToken + TelemetryEventName = $MyInvocation.MyCommand.Name + TelemetryProperties = $telemetryProperties + NoStatus = (Resolve-ParameterWithDefaultConfigurationValue -BoundParameters $PSBoundParameters ` + -Name NoStatus -ConfigValueName DefaultNoStatus) + } + + try + { + Invoke-GHRestMethod @params | Out-Null + $result = $true + } + catch + { + # Temporary code to handle current differences in exception object between PS5 and PS7 + if ($PSVersionTable.PSedition -eq 'Core') + { + if ($_.Exception -is [Microsoft.PowerShell.Commands.HttpResponseException] -and + ($_.ErrorDetails.Message | ConvertFrom-Json).message -eq 'Vulnerability alerts are disabled.') + { + $result = $false + } + else + { + throw $_ + } + } + else + { + if ($_.Exception.Message -like '*Vulnerability alerts are disabled.*') + { + $result = $false + } + else + { + throw $_ + } + } + } + + return $result +} + +filter Enable-GitHubRepositoryVulnerabilityAlert +{ + <# + .SYNOPSIS + Enables vulnerability alerts for a repository on GitHub. + + .DESCRIPTION + Enables vulnerability alerts for a repository on GitHub. + + The Git repo for this module can be found here: http://aka.ms/PowerShellForGitHub + + .PARAMETER OwnerName + Owner of the repository. + If not supplied here, the DefaultOwnerName configuration property value will be used. + + .PARAMETER RepositoryName + Name of the repository. + If not supplied here, the DefaultRepositoryName configuration property value will be used. + + .PARAMETER Uri + Uri for the repository. + The OwnerName and RepositoryName will be extracted from here instead of needing to provide + them individually. + + .PARAMETER AccessToken + If provided, this will be used as the AccessToken for authentication with the + REST Api. Otherwise, will attempt to use the configured value or will run unauthenticated. + + .PARAMETER NoStatus + If this switch is specified, long-running commands will run on the main thread + with no commandline status update. When not specified, those commands run in + the background, enabling the command prompt to provide status information. + If not supplied here, the DefaultNoStatus configuration property value will be used. + + .INPUTS + GitHub.Branch + GitHub.Content + GitHub.Event + GitHub.Issue + GitHub.IssueComment + GitHub.Label + GitHub.Milestone + GitHub.PullRequest + GitHub.Project + GitHub.ProjectCard + GitHub.ProjectColumn + GitHub.Release + GitHub.Repository + + .OUTPUTS + None + + .NOTES + The authenticated user must have admin access to the repository. + + .EXAMPLE + Enable-GitHubRepositoryVulnerabilityAlert -OwnerName Microsoft -RepositoryName PowerShellForGitHub + + Enables vulnerability alerts for the PowerShellForGithub repository. + + .EXAMPLE + Enable-GitHubRepositoryVulnerabilityAlert -Uri https://github.com/PowerShell/PowerShellForGitHub + + Enables vulnerability alerts for the PowerShellForGithub repository. +#> + [CmdletBinding( + PositionalBinding = $false, + SupportsShouldProcess, + DefaultParameterSetName='Elements')] + param( + [Parameter( + ParameterSetName='Elements')] + [string] $OwnerName, + + [Parameter(ParameterSetName='Elements')] + [string] $RepositoryName, + + [Parameter( + Mandatory, + Position = 1, + ValueFromPipelineByPropertyName, + ParameterSetName='Uri')] + [Alias('RepositoryUrl')] + [string] $Uri, + + [string] $AccessToken, + + [switch] $NoStatus + ) + + $elements = Resolve-RepositoryElements -BoundParameters $PSBoundParameters + $OwnerName = $elements.ownerName + $RepositoryName = $elements.repositoryName + + $telemetryProperties = @{ + 'OwnerName' = (Get-PiiSafeString -PlainText $OwnerName) + 'RepositoryName' = (Get-PiiSafeString -PlainText $RepositoryName) + } + + if ($PSCmdlet.ShouldProcess($RepositoryName, 'Enable Vulnerability Alerts')) + { + Write-InvocationLog + + $params = @{ + UriFragment = "repos/$OwnerName/$RepositoryName/vulnerability-alerts" + Description = "Enabling Vulnerability Alerts for $RepositoryName" + AcceptHeader = $script:dorianAcceptHeader + Method = 'Put' + AccessToken = $AccessToken + TelemetryEventName = $MyInvocation.MyCommand.Name + TelemetryProperties = $telemetryProperties + NoStatus = (Resolve-ParameterWithDefaultConfigurationValue -BoundParameters $PSBoundParameters ` + -Name NoStatus -ConfigValueName DefaultNoStatus) + } + + Invoke-GHRestMethod @params | Out-Null + } +} + +filter Disable-GitHubRepositoryVulnerabilityAlert +{ + <# + .SYNOPSIS + Disables vulnerability alerts for a repository on GitHub. + + .DESCRIPTION + Disables vulnerability alerts for a repository on GitHub. + + The Git repo for this module can be found here: http://aka.ms/PowerShellForGitHub + + .PARAMETER OwnerName + Owner of the repository. + If not supplied here, the DefaultOwnerName configuration property value will be used. + + .PARAMETER RepositoryName + Name of the repository. + If not supplied here, the DefaultRepositoryName configuration property value will be used. + + .PARAMETER Uri + Uri for the repository. + The OwnerName and RepositoryName will be extracted from here instead of needing to provide + them individually. + + .PARAMETER AccessToken + If provided, this will be used as the AccessToken for authentication with the + REST Api. Otherwise, will attempt to use the configured value or will run unauthenticated. + + .PARAMETER NoStatus + If this switch is specified, long-running commands will run on the main thread + with no commandline status update. When not specified, those commands run in + the background, enabling the command prompt to provide status information. + If not supplied here, the DefaultNoStatus configuration property value will be used. + + .INPUTS + GitHub.Branch + GitHub.Content + GitHub.Event + GitHub.Issue + GitHub.IssueComment + GitHub.Label + GitHub.Milestone + GitHub.PullRequest + GitHub.Project + GitHub.ProjectCard + GitHub.ProjectColumn + GitHub.Release + GitHub.Repository + + .OUTPUTS + None + + .NOTES + The authenticated user must have admin access to the repository. + + .EXAMPLE + Disable-GitHubRepositoryVulnerabilityAlert -OwnerName Microsoft -RepositoryName PowerShellForGitHub + + Disables vulnerability alerts for the PowerShellForGithub repository. + + .EXAMPLE + Disable-GitHubRepositoryVulnerabilityAlert -Uri https://github.com/PowerShell/PowerShellForGitHub + + Disables vulnerability alerts for the PowerShellForGithub repository. +#> + [CmdletBinding( + PositionalBinding = $false, + SupportsShouldProcess, + DefaultParameterSetName='Elements')] + param( + [Parameter(ParameterSetName='Elements')] + [string] $OwnerName, + + [Parameter(ParameterSetName='Elements')] + [string] $RepositoryName, + + [Parameter( + Mandatory, + Position = 1, + ValueFromPipelineByPropertyName, + ParameterSetName='Uri')] + [string] $Uri, + + [string] $AccessToken, + + [switch] $NoStatus + ) + + $elements = Resolve-RepositoryElements -BoundParameters $PSBoundParameters + $OwnerName = $elements.ownerName + $RepositoryName = $elements.repositoryName + + $telemetryProperties = @{ + 'OwnerName' = (Get-PiiSafeString -PlainText $OwnerName) + 'RepositoryName' = (Get-PiiSafeString -PlainText $RepositoryName) + } + + if ($PSCmdlet.ShouldProcess($RepositoryName, 'Disable Vulnerability Alerts')) + { + Write-InvocationLog + + $params = @{ + UriFragment = "repos/$OwnerName/$RepositoryName/vulnerability-alerts" + Description = "Disabling Vulnerability Alerts for $RepositoryName" + AcceptHeader = $script:dorianAcceptHeader + Method = 'Delete' + AccessToken = $AccessToken + TelemetryEventName = $MyInvocation.MyCommand.Name + TelemetryProperties = $telemetryProperties + NoStatus = (Resolve-ParameterWithDefaultConfigurationValue -BoundParameters $PSBoundParameters ` + -Name NoStatus -ConfigValueName DefaultNoStatus) + } + + Invoke-GHRestMethod @params | Out-Null + } +} + +filter Enable-GitHubRepositorySecurityFix +{ + <# + .SYNOPSIS + Enables automated security fixes for a repository on GitHub. + + .DESCRIPTION + Enables automated security fixes for a repository on GitHub. + + The Git repo for this module can be found here: http://aka.ms/PowerShellForGitHub + + .PARAMETER OwnerName + Owner of the repository. + If not supplied here, the DefaultOwnerName configuration property value will be used. + + .PARAMETER RepositoryName + Name of the repository. + If not supplied here, the DefaultRepositoryName configuration property value will be used. + + .PARAMETER Uri + Uri for the repository. + The OwnerName and RepositoryName will be extracted from here instead of needing to provide + them individually. + + .PARAMETER AccessToken + If provided, this will be used as the AccessToken for authentication with the + REST Api. Otherwise, will attempt to use the configured value or will run unauthenticated. + + .PARAMETER NoStatus + If this switch is specified, long-running commands will run on the main thread + with no commandline status update. When not specified, those commands run in + the background, enabling the command prompt to provide status information. + If not supplied here, the DefaultNoStatus configuration property value will be used. + + .INPUTS + GitHub.Branch + GitHub.Content + GitHub.Event + GitHub.Issue + GitHub.IssueComment + GitHub.Label + GitHub.Milestone + GitHub.PullRequest + GitHub.Project + GitHub.ProjectCard + GitHub.ProjectColumn + GitHub.Release + GitHub.Repository + + .OUTPUTS + None + + .NOTES + The authenticated user must have admin access to the repository. + + .EXAMPLE + Enable-GitHubRepositorySecurityFix -OwnerName Microsoft -RepositoryName PowerShellForGitHub + + Enables automated security fixes for the PowerShellForGitHub repository. + .EXAMPLE + Enable-GitHubRepositorySecurityFix -Uri https://github.com/PowerShell/PowerShellForGitHub + + Enables automated security fixes for the PowerShellForGitHub repository. +#> + [CmdletBinding( + PositionalBinding = $false, + SupportsShouldProcess, + DefaultParameterSetName='Elements')] + param( + [Parameter( + ParameterSetName='Elements')] + [string] $OwnerName, + + [Parameter(ParameterSetName='Elements')] + [string] $RepositoryName, + + [Parameter( + Mandatory, + Position = 1, + ValueFromPipelineByPropertyName, + ParameterSetName='Uri')] + [string] $Uri, + + [string] $AccessToken, + + [switch] $NoStatus + ) + + $elements = Resolve-RepositoryElements -BoundParameters $PSBoundParameters + $OwnerName = $elements.ownerName + $RepositoryName = $elements.repositoryName + + $telemetryProperties = @{ + 'OwnerName' = (Get-PiiSafeString -PlainText $OwnerName) + 'RepositoryName' = (Get-PiiSafeString -PlainText $RepositoryName) + } + + if ($PSCmdlet.ShouldProcess($RepositoryName, 'Enable Automated Security Fixes')) + { + Write-InvocationLog + + $params = @{ + UriFragment = "repos/$OwnerName/$RepositoryName/automated-security-fixes" + Description = "Enabling Automated Security Fixes for $RepositoryName" + AcceptHeader = $script:londonAcceptHeader + Method = 'Put' + AccessToken = $AccessToken + TelemetryEventName = $MyInvocation.MyCommand.Name + TelemetryProperties = $telemetryProperties + NoStatus = (Resolve-ParameterWithDefaultConfigurationValue -BoundParameters $PSBoundParameters ` + -Name NoStatus -ConfigValueName DefaultNoStatus) + } + + Invoke-GHRestMethod @params + } +} + +filter Disable-GitHubRepositorySecurityFix +{ + <# + .SYNOPSIS + Disables automated security fixes for a repository on GitHub. + + .DESCRIPTION + Disables automated security fixes for a repository on GitHub. + + The Git repo for this module can be found here: http://aka.ms/PowerShellForGitHub + + .PARAMETER OwnerName + Owner of the repository. + If not supplied here, the DefaultOwnerName configuration property value will be used. + + .PARAMETER RepositoryName + Name of the repository. + If not supplied here, the DefaultRepositoryName configuration property value will be used. + + .PARAMETER Uri + Uri for the repository. + The OwnerName and RepositoryName will be extracted from here instead of needing to provide + them individually. + + .PARAMETER AccessToken + If provided, this will be used as the AccessToken for authentication with the + REST Api. Otherwise, will attempt to use the configured value or will run unauthenticated. + + .PARAMETER NoStatus + If this switch is specified, long-running commands will run on the main thread + with no commandline status update. When not specified, those commands run in + the background, enabling the command prompt to provide status information. + If not supplied here, the DefaultNoStatus configuration property value will be used. + + .INPUTS + GitHub.Branch + GitHub.Content + GitHub.Event + GitHub.Issue + GitHub.IssueComment + GitHub.Label + GitHub.Milestone + GitHub.PullRequest + GitHub.Project + GitHub.ProjectCard + GitHub.ProjectColumn + GitHub.Release + GitHub.Repository + + .OUTPUTS + None + + .NOTES + The authenticated user must have admin access to the repository. + + .EXAMPLE + Disable-GitHubRepositorySecurityFix -OwnerName Microsoft -RepositoryName PowerShellForGitHub + + Disables automated security fixes for the PowerShellForGithub repository. + .EXAMPLE + Disable-GitHubRepositorySecurityFix -Uri https://github.com/PowerShell/PowerShellForGitHub + + Disables automated security fixes for the PowerShellForGithub repository. +#> + [CmdletBinding( + PositionalBinding = $false, + SupportsShouldProcess, + DefaultParameterSetName='Elements')] + param( + [Parameter(ParameterSetName='Elements')] + [string] $OwnerName, + + [Parameter(ParameterSetName='Elements')] + [string] $RepositoryName, + + [Parameter( + Mandatory, + Position = 1, + ValueFromPipelineByPropertyName, + ParameterSetName='Uri')] + [string] $Uri, + + [string] $AccessToken, + + [switch] $NoStatus + ) + + $elements = Resolve-RepositoryElements -BoundParameters $PSBoundParameters + $OwnerName = $elements.ownerName + $RepositoryName = $elements.repositoryName + + $telemetryProperties = @{ + 'OwnerName' = (Get-PiiSafeString -PlainText $OwnerName) + 'RepositoryName' = (Get-PiiSafeString -PlainText $RepositoryName) + } + + if ($PSCmdlet.ShouldProcess($RepositoryName, 'Disable Automated Security Fixes')) + { + Write-InvocationLog + + $params = @{ + UriFragment = "repos/$OwnerName/$RepositoryName/automated-security-fixes" + Description = "Disabling Automated Security Fixes for $RepositoryName" + AcceptHeader = $script:londonAcceptHeader + Method = 'Delete' + AccessToken = $AccessToken + TelemetryEventName = $MyInvocation.MyCommand.Name + TelemetryProperties = $telemetryProperties + NoStatus = (Resolve-ParameterWithDefaultConfigurationValue -BoundParameters $PSBoundParameters ` + -Name NoStatus -ConfigValueName DefaultNoStatus) + } + + Invoke-GHRestMethod @params | Out-Null + } +} + filter Add-GitHubRepositoryAdditionalProperties { <# diff --git a/PowerShellForGitHub.psd1 b/PowerShellForGitHub.psd1 index c7f51c82..36467c7e 100644 --- a/PowerShellForGitHub.psd1 +++ b/PowerShellForGitHub.psd1 @@ -54,6 +54,10 @@ 'Backup-GitHubConfiguration', 'Clear-GitHubAuthentication', 'ConvertFrom-GitHubMarkdown', + 'Disable-GitHubRepositorySecurityFix', + 'Disable-GitHubRepositoryVulnerabilityAlert', + 'Enable-GitHubRepositorySecurityFix', + 'Enable-GitHubRepositoryVulnerabilityAlert', 'Get-GitHubAssignee', 'Get-GitHubCloneTraffic', 'Get-GitHubCodeOfConduct', @@ -137,6 +141,7 @@ 'Test-GitHubAssignee', 'Test-GitHubAuthenticationConfigured', 'Test-GitHubOrganizationMember', + 'Test-GitHubRepositoryVulnerabilityAlert', 'Unlock-GitHubIssue', 'Update-GitHubCurrentUser', 'Update-GitHubIssue', diff --git a/Tests/GitHubRepositories.tests.ps1 b/Tests/GitHubRepositories.tests.ps1 index 3dc1bf81..04aa8e01 100644 --- a/Tests/GitHubRepositories.tests.ps1 +++ b/Tests/GitHubRepositories.tests.ps1 @@ -813,6 +813,127 @@ try } } } + + Describe 'GitHubRepositories\Test-GitHubRepositoryVulnerabilityAlert' { + BeforeAll -ScriptBlock { + $repo = New-GitHubRepository -RepositoryName ([Guid]::NewGuid().Guid) + } + + Context 'When the Git Hub Repository Vulnerability Alert Status is Enabled' { + BeforeAll -ScriptBlock { + Enable-GitHubRepositoryVulnerabilityAlert -Uri $repo.svn_url + $result = Test-GitHubRepositoryVulnerabilityAlert -Uri $repo.svn_url + } + + It 'Should return an object of the correct type' { + $result | Should -BeOfType System.Boolean + } + + It 'Should return the correct value' { + $result | Should -Be $true + } + } + + Context 'When the Git Hub Repository Vulnerability Alert Status is Disabled' { + BeforeAll -ScriptBlock { + Disable-GitHubRepositoryVulnerabilityAlert -Uri $repo.svn_url + $status = Test-GitHubRepositoryVulnerabilityAlert -Uri $repo.svn_url + } + + It 'Should return an object of the correct type' { + $status | Should -BeOfType System.Boolean + } + + It 'Should return the correct value' { + $status | Should -BeFalse + } + } + + Context 'When Invoke-GHRestMethod returns an unexpected error' { + It 'Should throw' { + $getGitHubRepositoryVulnerabilityAlertParms = @{ + OwnerName = 'octocat' + RepositoryName = 'IncorrectRepostioryName' + } + { Test-GitHubRepositoryVulnerabilityAlert @getGitHubRepositoryVulnerabilityAlertParms } | + Should -Throw + } + } + + AfterAll -ScriptBlock { + Remove-GitHubRepository -Uri $repo.svn_url -Force + } + } + + Describe 'GitHubRepositories\Enable-GitHubRepositoryVulnerabilityAlert' { + BeforeAll { + $repo = New-GitHubRepository -RepositoryName ([Guid]::NewGuid().Guid) + } + + Context 'When Enabling GitHub Repository Vulnerability Alerts' { + It 'Should not throw' { + { Enable-GitHubRepositoryVulnerabilityAlert -Uri $repo.svn_url } | + Should -Not -Throw + } + } + + AfterAll -ScriptBlock { + Remove-GitHubRepository -Uri $repo.svn_url -Force + } + } + + Describe 'GitHubRepositories\Disable-GitHubRepositoryVulnerabilityAlert' { + BeforeAll { + $repo = New-GitHubRepository -RepositoryName ([Guid]::NewGuid().Guid) + Enable-GitHubRepositoryVulnerabilityAlert -Uri $repo.svn_url + } + + Context 'When Disabling GitHub Repository Vulnerability Alerts' { + It 'Should not throw' { + { Disable-GitHubRepositoryVulnerabilityAlert -Uri $repo.svn_url } | + Should -Not -Throw + } + } + + AfterAll -ScriptBlock { + Remove-GitHubRepository -Uri $repo.svn_url -Force + } + } + + Describe 'GitHubRepositories\Enable-GitHubRepositorySecurityFix' { + BeforeAll { + $repo = New-GitHubRepository -RepositoryName ([Guid]::NewGuid().Guid) + } + + Context 'When Enabling GitHub Repository Security Fixes' { + It 'Should not throw' { + { Enable-GitHubRepositorySecurityFix -Uri $repo.svn_url } | + Should -Not -Throw + } + } + + AfterAll -ScriptBlock { + Remove-GitHubRepository -Uri $repo.svn_url -Force + } + } + + Describe 'GitHubRepositories\Disable-GitHubRepositorySecurityFix' { + BeforeAll { + $repo = New-GitHubRepository -RepositoryName ([Guid]::NewGuid().Guid) + Enable-GitHubRepositorySecurityFix -Uri $repo.svn_url + } + + Context 'When Disabling GitHub Repository Security Fixes' { + It 'Should not throw' { + { Disable-GitHubRepositorySecurityFix -Uri $repo.svn_url } | + Should -Not -Throw + } + } + + AfterAll -ScriptBlock { + Remove-GitHubRepository -Uri $repo.svn_url -Force + } + } } finally { @@ -822,4 +943,4 @@ finally Restore-GitHubConfiguration -Path $script:originalConfigFile $script:originalConfigFile = $null } -} \ No newline at end of file +} diff --git a/USAGE.md b/USAGE.md index 59cc3954..fd96d10d 100644 --- a/USAGE.md +++ b/USAGE.md @@ -29,6 +29,12 @@ * [Updating the current authenticated user](#updating-the-current-authenticated-user) * [Getting any user](#getting-any-user) * [Getting all users](#getting-all-users) + * [Repositories](#repositories) + * [Get repository vulnerability alert status](#get-repository-vulnerability-alert-status) + * [Enable repository vulnerability alerts](#enable-repository-vulnerability-alerts) + * [Disable repository vulnerability alerts](#disable-repository-vulnerability-alerts) + * [Enable repository automatic security fixes](#enable-repository-automatic-security-fixes) + * [Disable repository automatic security fixes](#disable-repository-automatic-security-fixes) * [Forks](#forks) * [Get all the forks for a repository](#get-all-the-forks-for-a-repository) * [Create a new fork](#create-a-new-fork) @@ -414,6 +420,40 @@ Get-GitHubUser ---------- +### Repositories + +#### Get repository vulnerability alert status + +```powershell +Test-GitHubRepositoryVulnerabilityAlert -OwnerName microsoft -RepositoryName PowerShellForGitHub +``` + +#### Enable repository vulnerability alerts + +```powershell +Enable-GitHubRepositoryVulnerabilityAlert -OwnerName microsoft -RepositoryName PowerShellForGitHub +``` + +#### Disable repository vulnerability alert + +```powershell +Disable-GitHubRepositoryVulnerabilityAlert -OwnerName microsoft -RepositoryName PowerShellForGitHub +``` + +#### Enable repository automatic security fixes + +```powershell +Enable-GitHubRepositorySecurityFix -OwnerName microsoft -RepositoryName PowerShellForGitHub +``` + +#### Disable repository automatic security fixes + +```powershell +Disable-GitHubRepositorySecurityFix -OwnerName microsoft -RepositoryName PowerShellForGitHub +``` + +---------- + ### Forks #### Get all the forks for a repository From 0bc09d28714eb5be522471dde25790946286bcdd Mon Sep 17 00:00:00 2001 From: Howard Wolosky Date: Wed, 24 Jun 2020 15:24:28 -0700 Subject: [PATCH 38/60] Fix double-space added after 'Description' throughout module (#244) Somehow two spaces got added after `'Description' =` in a function and then got copy/paste repeated throughout the module. This fixes that. It also adds a ruler line at the 100 character mark in VS Code to help developers see when they should start to wrap their code/comments. --- .vscode/settings.json | 3 +++ CONTRIBUTING.md | 3 ++- GitHubAssignees.ps1 | 8 ++++---- GitHubBranches.ps1 | 2 +- GitHubIssueComments.ps1 | 6 +++--- GitHubIssues.ps1 | 12 ++++++------ GitHubLabels.ps1 | 14 +++++++------- GitHubMilestones.ps1 | 6 +++--- GitHubMiscellaneous.ps1 | 12 ++++++------ GitHubOrganizations.ps1 | 4 ++-- GitHubPullRequests.ps1 | 2 +- GitHubReleases.ps1 | 2 +- GitHubRepositories.ps1 | 20 ++++++++++---------- GitHubRepositoryForks.ps1 | 4 ++-- GitHubRepositoryTraffic.ps1 | 8 ++++---- GitHubTeams.ps1 | 4 ++-- GitHubUsers.ps1 | 4 ++-- 17 files changed, 59 insertions(+), 55 deletions(-) diff --git a/.vscode/settings.json b/.vscode/settings.json index e9600a4a..a24892b4 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,4 +1,7 @@ { + "editor.rulers": [ + 100 + ], "[powershell]": { "files.trimTrailingWhitespace": true }, diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index d561cd59..f3e2d01f 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -243,7 +243,8 @@ Here are some general guidelines reviewing/maintaining code. There are of course exceptions, but this is generally an enforced preference. The [Visual Studio Productivity Power Tools](https://visualstudiogallery.msdn.microsoft.com/34ebc6a2-2777-421d-8914-e29c1dfa7f5d) extension has a "Column Guides" feature that makes it easy to add a Guideline in column 100 - to make it really obvious when coding. + to make it really obvious when coding. If you use VS Code, this module's `.vscode/settings.json` + configures that for you automatically. ---------- diff --git a/GitHubAssignees.ps1 b/GitHubAssignees.ps1 index eccc9810..2c439da1 100644 --- a/GitHubAssignees.ps1 +++ b/GitHubAssignees.ps1 @@ -100,7 +100,7 @@ filter Get-GitHubAssignee $params = @{ 'UriFragment' = "repos/$OwnerName/$RepositoryName/assignees" - 'Description' = "Getting assignee list for $RepositoryName" + 'Description' = "Getting assignee list for $RepositoryName" 'AccessToken' = $AccessToken 'TelemetryEventName' = $MyInvocation.MyCommand.Name 'TelemetryProperties' = $telemetryProperties @@ -228,7 +228,7 @@ filter Test-GitHubAssignee $params = @{ 'UriFragment' = "repos/$OwnerName/$RepositoryName/assignees/$Assignee" 'Method' = 'Get' - 'Description' = "Checking permission for $Assignee for $RepositoryName" + 'Description' = "Checking permission for $Assignee for $RepositoryName" 'AccessToken' = $AccessToken 'TelemetryEventName' = $MyInvocation.MyCommand.Name 'TelemetryProperties' = $telemetryProperties @@ -411,7 +411,7 @@ function New-GitHubAssignee 'UriFragment' = "repos/$OwnerName/$RepositoryName/issues/$Issue/assignees" 'Body' = (ConvertTo-Json -InputObject $hashBody) 'Method' = 'Post' - 'Description' = "Add assignees to issue $Issue for $RepositoryName" + 'Description' = "Add assignees to issue $Issue for $RepositoryName" 'AccessToken' = $AccessToken 'AcceptHeader' = $script:symmetraAcceptHeader 'TelemetryEventName' = $MyInvocation.MyCommand.Name @@ -603,7 +603,7 @@ function Remove-GitHubAssignee 'UriFragment' = "repos/$OwnerName/$RepositoryName/issues/$Issue/assignees" 'Body' = (ConvertTo-Json -InputObject $hashBody) 'Method' = 'Delete' - 'Description' = "Removing assignees from issue $Issue for $RepositoryName" + 'Description' = "Removing assignees from issue $Issue for $RepositoryName" 'AccessToken' = $AccessToken 'AcceptHeader' = $script:symmetraAcceptHeader 'TelemetryEventName' = $MyInvocation.MyCommand.Name diff --git a/GitHubBranches.ps1 b/GitHubBranches.ps1 index 357d5792..f595ace7 100644 --- a/GitHubBranches.ps1 +++ b/GitHubBranches.ps1 @@ -144,7 +144,7 @@ filter Get-GitHubRepositoryBranch $params = @{ 'UriFragment' = $uriFragment + '?' + ($getParams -join '&') - 'Description' = "Getting branches for $RepositoryName" + 'Description' = "Getting branches for $RepositoryName" 'AccessToken' = $AccessToken 'TelemetryEventName' = $MyInvocation.MyCommand.Name 'TelemetryProperties' = $telemetryProperties diff --git a/GitHubIssueComments.ps1 b/GitHubIssueComments.ps1 index a388db93..1eacf806 100644 --- a/GitHubIssueComments.ps1 +++ b/GitHubIssueComments.ps1 @@ -413,7 +413,7 @@ filter New-GitHubIssueComment 'UriFragment' = "repos/$OwnerName/$RepositoryName/issues/$Issue/comments" 'Body' = (ConvertTo-Json -InputObject $hashBody) 'Method' = 'Post' - 'Description' = "Creating comment under issue $Issue for $RepositoryName" + 'Description' = "Creating comment under issue $Issue for $RepositoryName" 'AccessToken' = $AccessToken 'AcceptHeader' = (Get-MediaAcceptHeader -MediaType $MediaType -AsJson -AcceptHeader $squirrelGirlAcceptHeader) 'TelemetryEventName' = $MyInvocation.MyCommand.Name @@ -556,7 +556,7 @@ filter Set-GitHubIssueComment 'UriFragment' = "repos/$OwnerName/$RepositoryName/issues/comments/$Comment" 'Body' = (ConvertTo-Json -InputObject $hashBody) 'Method' = 'Patch' - 'Description' = "Update comment $Comment for $RepositoryName" + 'Description' = "Update comment $Comment for $RepositoryName" 'AccessToken' = $AccessToken 'AcceptHeader' = (Get-MediaAcceptHeader -MediaType $MediaType -AsJson -AcceptHeader $squirrelGirlAcceptHeader) 'TelemetryEventName' = $MyInvocation.MyCommand.Name @@ -693,7 +693,7 @@ filter Remove-GitHubIssueComment $params = @{ 'UriFragment' = "repos/$OwnerName/$RepositoryName/issues/comments/$Comment" 'Method' = 'Delete' - 'Description' = "Removing comment $Comment for $RepositoryName" + 'Description' = "Removing comment $Comment for $RepositoryName" 'AccessToken' = $AccessToken 'TelemetryEventName' = $MyInvocation.MyCommand.Name 'TelemetryProperties' = $telemetryProperties diff --git a/GitHubIssues.ps1 b/GitHubIssues.ps1 index fa043e3d..46931a43 100644 --- a/GitHubIssues.ps1 +++ b/GitHubIssues.ps1 @@ -357,7 +357,7 @@ filter Get-GitHubIssue $params = @{ 'UriFragment' = $uriFragment + '?' + ($getParams -join '&') - 'Description' = $description + 'Description' = $description 'AcceptHeader' = (Get-MediaAcceptHeader -MediaType $MediaType -AsJson -AcceptHeader $symmetraAcceptHeader) 'AccessToken' = $AccessToken 'TelemetryEventName' = $MyInvocation.MyCommand.Name @@ -484,7 +484,7 @@ filter Get-GitHubIssueTimeline $params = @{ 'UriFragment' = "repos/$OwnerName/$RepositoryName/issues/$Issue/timeline" - 'Description' = "Getting timeline for Issue #$Issue in $RepositoryName" + 'Description' = "Getting timeline for Issue #$Issue in $RepositoryName" 'AcceptHeader' = $script:mockingbirdAcceptHeader 'AccessToken' = $AccessToken 'TelemetryEventName' = $MyInvocation.MyCommand.Name @@ -643,7 +643,7 @@ filter New-GitHubIssue 'UriFragment' = "/repos/$OwnerName/$RepositoryName/issues" 'Body' = (ConvertTo-Json -InputObject $hashBody) 'Method' = 'Post' - 'Description' = "Creating new Issue ""$Title"" on $RepositoryName" + 'Description' = "Creating new Issue ""$Title"" on $RepositoryName" 'AcceptHeader' = (Get-MediaAcceptHeader -MediaType $MediaType -AsJson -AcceptHeader $symmetraAcceptHeader) 'AccessToken' = $AccessToken 'TelemetryEventName' = $MyInvocation.MyCommand.Name @@ -823,7 +823,7 @@ filter Update-GitHubIssue 'UriFragment' = "/repos/$OwnerName/$RepositoryName/issues/$Issue" 'Body' = (ConvertTo-Json -InputObject $hashBody) 'Method' = 'Patch' - 'Description' = "Updating Issue #$Issue on $RepositoryName" + 'Description' = "Updating Issue #$Issue on $RepositoryName" 'AcceptHeader' = (Get-MediaAcceptHeader -MediaType $MediaType -AsJson -AcceptHeader $symmetraAcceptHeader) 'AccessToken' = $AccessToken 'TelemetryEventName' = $MyInvocation.MyCommand.Name @@ -956,7 +956,7 @@ filter Lock-GitHubIssue 'UriFragment' = "/repos/$OwnerName/$RepositoryName/issues/$Issue/lock" 'Body' = (ConvertTo-Json -InputObject $hashBody) 'Method' = 'Put' - 'Description' = "Locking Issue #$Issue on $RepositoryName" + 'Description' = "Locking Issue #$Issue on $RepositoryName" 'AcceptHeader' = $script:sailorVAcceptHeader 'AccessToken' = $AccessToken 'TelemetryEventName' = $MyInvocation.MyCommand.Name @@ -1066,7 +1066,7 @@ filter Unlock-GitHubIssue $params = @{ 'UriFragment' = "/repos/$OwnerName/$RepositoryName/issues/$Issue/lock" 'Method' = 'Delete' - 'Description' = "Unlocking Issue #$Issue on $RepositoryName" + 'Description' = "Unlocking Issue #$Issue on $RepositoryName" 'AcceptHeader' = $script:sailorVAcceptHeader 'AccessToken' = $AccessToken 'TelemetryEventName' = $MyInvocation.MyCommand.Name diff --git a/GitHubLabels.ps1 b/GitHubLabels.ps1 index b020e54c..41d306f5 100644 --- a/GitHubLabels.ps1 +++ b/GitHubLabels.ps1 @@ -182,7 +182,7 @@ filter Get-GitHubLabel $params = @{ 'UriFragment' = $uriFragment - 'Description' = $description + 'Description' = $description 'AcceptHeader' = $script:symmetraAcceptHeader 'AccessToken' = $AccessToken 'TelemetryEventName' = $MyInvocation.MyCommand.Name @@ -327,7 +327,7 @@ filter New-GitHubLabel 'UriFragment' = "repos/$OwnerName/$RepositoryName/labels" 'Body' = (ConvertTo-Json -InputObject $hashBody) 'Method' = 'Post' - 'Description' = "Creating label $Label in $RepositoryName" + 'Description' = "Creating label $Label in $RepositoryName" 'AcceptHeader' = $script:symmetraAcceptHeader 'AccessToken' = $AccessToken 'TelemetryEventName' = $MyInvocation.MyCommand.Name @@ -474,7 +474,7 @@ filter Remove-GitHubLabel $params = @{ 'UriFragment' = "repos/$OwnerName/$RepositoryName/labels/$Label" 'Method' = 'Delete' - 'Description' = "Deleting label $Label from $RepositoryName" + 'Description' = "Deleting label $Label from $RepositoryName" 'AcceptHeader' = $script:symmetraAcceptHeader 'AccessToken' = $AccessToken 'TelemetryEventName' = $MyInvocation.MyCommand.Name @@ -626,7 +626,7 @@ filter Update-GitHubLabel 'UriFragment' = "repos/$OwnerName/$RepositoryName/labels/$Label" 'Body' = (ConvertTo-Json -InputObject $hashBody) 'Method' = 'Patch' - 'Description' = "Updating label $Label" + 'Description' = "Updating label $Label" 'AcceptHeader' = $script:symmetraAcceptHeader 'AccessToken' = $AccessToken 'TelemetryEventName' = $MyInvocation.MyCommand.Name @@ -928,7 +928,7 @@ function Add-GitHubIssueLabel 'UriFragment' = "repos/$OwnerName/$RepositoryName/issues/$Issue/labels" 'Body' = (ConvertTo-Json -InputObject $hashBody) 'Method' = 'Post' - 'Description' = "Adding labels to issue $Issue in $RepositoryName" + 'Description' = "Adding labels to issue $Issue in $RepositoryName" 'AcceptHeader' = $script:symmetraAcceptHeader 'AccessToken' = $AccessToken 'TelemetryEventName' = $MyInvocation.MyCommand.Name @@ -1117,7 +1117,7 @@ function Set-GitHubIssueLabel 'UriFragment' = "repos/$OwnerName/$RepositoryName/issues/$Issue/labels" 'Body' = (ConvertTo-Json -InputObject $hashBody) 'Method' = 'Put' - 'Description' = "Replacing labels to issue $Issue in $RepositoryName" + 'Description' = "Replacing labels to issue $Issue in $RepositoryName" 'AcceptHeader' = $script:symmetraAcceptHeader 'AccessToken' = $AccessToken 'TelemetryEventName' = $MyInvocation.MyCommand.Name @@ -1275,7 +1275,7 @@ filter Remove-GitHubIssueLabel $params = @{ 'UriFragment' = "/repos/$OwnerName/$RepositoryName/issues/$Issue/labels/$Label" 'Method' = 'Delete' - 'Description' = $description + 'Description' = $description 'AcceptHeader' = $script:symmetraAcceptHeader 'AccessToken' = $AccessToken 'TelemetryEventName' = $MyInvocation.MyCommand.Name diff --git a/GitHubMilestones.ps1 b/GitHubMilestones.ps1 index 70b5b003..ddbbd165 100644 --- a/GitHubMilestones.ps1 +++ b/GitHubMilestones.ps1 @@ -380,7 +380,7 @@ filter New-GitHubMilestone 'UriFragment' = "repos/$OwnerName/$RepositoryName/milestones" 'Body' = (ConvertTo-Json -InputObject $hashBody) 'Method' = 'Post' - 'Description' = "Creating milestone for $RepositoryName" + 'Description' = "Creating milestone for $RepositoryName" 'AccessToken' = $AccessToken 'TelemetryEventName' = $MyInvocation.MyCommand.Name 'TelemetryProperties' = $telemetryProperties @@ -571,7 +571,7 @@ filter Set-GitHubMilestone 'UriFragment' = "repos/$OwnerName/$RepositoryName/milestones/$Milestone" 'Body' = (ConvertTo-Json -InputObject $hashBody) 'Method' = 'Patch' - 'Description' = "Setting milestone $Milestone for $RepositoryName" + 'Description' = "Setting milestone $Milestone for $RepositoryName" 'AccessToken' = $AccessToken 'TelemetryEventName' = $MyInvocation.MyCommand.Name 'TelemetryProperties' = $telemetryProperties @@ -714,7 +714,7 @@ filter Remove-GitHubMilestone $params = @{ 'UriFragment' = "repos/$OwnerName/$RepositoryName/milestones/$Milestone" 'Method' = 'Delete' - 'Description' = "Removing milestone $Milestone for $RepositoryName" + 'Description' = "Removing milestone $Milestone for $RepositoryName" 'AccessToken' = $AccessToken 'TelemetryEventName' = $MyInvocation.MyCommand.Name 'TelemetryProperties' = $telemetryProperties diff --git a/GitHubMiscellaneous.ps1 b/GitHubMiscellaneous.ps1 index 10f98fb5..a2498de5 100644 --- a/GitHubMiscellaneous.ps1 +++ b/GitHubMiscellaneous.ps1 @@ -77,7 +77,7 @@ function Get-GitHubRateLimit $params = @{ 'UriFragment' = 'rate_limit' 'Method' = 'Get' - 'Description' = "Getting your API rate limit" + 'Description' = "Getting your API rate limit" 'AccessToken' = $AccessToken 'TelemetryEventName' = $MyInvocation.MyCommand.Name 'NoStatus' = (Resolve-ParameterWithDefaultConfigurationValue -Name NoStatus -ConfigValueName DefaultNoStatus) @@ -186,7 +186,7 @@ function ConvertFrom-GitHubMarkdown 'UriFragment' = 'markdown' 'Body' = (ConvertTo-Json -InputObject $hashBody) 'Method' = 'Post' - 'Description' = "Converting Markdown to HTML" + 'Description' = "Converting Markdown to HTML" 'AccessToken' = $AccessToken 'TelemetryEventName' = $MyInvocation.MyCommand.Name 'TelemetryProperties' = $telemetryProperties @@ -345,7 +345,7 @@ filter Get-GitHubLicense $params = @{ 'UriFragment' = $uriFragment 'Method' = 'Get' - 'Description' = $description + 'Description' = $description 'AccessToken' = $AccessToken 'TelemetryEventName' = $MyInvocation.MyCommand.Name 'TelemetryProperties' = $telemetryProperties @@ -425,7 +425,7 @@ function Get-GitHubEmoji $params = @{ 'UriFragment' = 'emojis' 'Method' = 'Get' - 'Description' = "Getting all GitHub emojis" + 'Description' = "Getting all GitHub emojis" 'AccessToken' = $AccessToken 'TelemetryEventName' = $MyInvocation.MyCommand.Name 'NoStatus' = (Resolve-ParameterWithDefaultConfigurationValue -Name NoStatus -ConfigValueName DefaultNoStatus) @@ -583,7 +583,7 @@ filter Get-GitHubCodeOfConduct 'UriFragment' = $uriFragment 'Method' = 'Get' 'AcceptHeader' = $script:scarletWitchAcceptHeader - 'Description' = $description + 'Description' = $description 'AccessToken' = $AccessToken 'TelemetryEventName' = $MyInvocation.MyCommand.Name 'TelemetryProperties' = $telemetryProperties @@ -680,7 +680,7 @@ filter Get-GitHubGitIgnore $params = @{ 'UriFragment' = $uriFragment 'Method' = 'Get' - 'Description' = $description + 'Description' = $description 'AccessToken' = $AccessToken 'TelemetryEventName' = $MyInvocation.MyCommand.Name 'TelemetryProperties' = $telemetryProperties diff --git a/GitHubOrganizations.ps1 b/GitHubOrganizations.ps1 index 021ba709..95e81e88 100644 --- a/GitHubOrganizations.ps1 +++ b/GitHubOrganizations.ps1 @@ -67,7 +67,7 @@ filter Get-GitHubOrganizationMember $params = @{ 'UriFragment' = "orgs/$OrganizationName/members" - 'Description' = "Getting members for $OrganizationName" + 'Description' = "Getting members for $OrganizationName" 'AccessToken' = $AccessToken 'TelemetryEventName' = $MyInvocation.MyCommand.Name 'TelemetryProperties' = $telemetryProperties @@ -144,7 +144,7 @@ filter Test-GitHubOrganizationMember $params = @{ 'UriFragment' = "orgs/$OrganizationName/members/$UserName" - 'Description' = "Checking if $UserName is a member of $OrganizationName" + 'Description' = "Checking if $UserName is a member of $OrganizationName" 'Method' = 'Get' 'ExtendedResult' = $true 'AccessToken' = $AccessToken diff --git a/GitHubPullRequests.ps1 b/GitHubPullRequests.ps1 index c08c887a..09860e02 100644 --- a/GitHubPullRequests.ps1 +++ b/GitHubPullRequests.ps1 @@ -180,7 +180,7 @@ filter Get-GitHubPullRequest $params = @{ 'UriFragment' = $uriFragment + '?' + ($getParams -join '&') - 'Description' = $description + 'Description' = $description 'AcceptHeader' = $script:symmetraAcceptHeader 'AccessToken' = $AccessToken 'TelemetryEventName' = $MyInvocation.MyCommand.Name diff --git a/GitHubReleases.ps1 b/GitHubReleases.ps1 index b4024828..997faff6 100644 --- a/GitHubReleases.ps1 +++ b/GitHubReleases.ps1 @@ -214,7 +214,7 @@ filter Get-GitHubRelease $params = @{ 'UriFragment' = $uriFragment - 'Description' = $description + 'Description' = $description 'AccessToken' = $AccessToken 'TelemetryEventName' = $MyInvocation.MyCommand.Name 'TelemetryProperties' = $telemetryProperties diff --git a/GitHubRepositories.ps1 b/GitHubRepositories.ps1 index de205538..dcb2fc92 100644 --- a/GitHubRepositories.ps1 +++ b/GitHubRepositories.ps1 @@ -349,7 +349,7 @@ filter Remove-GitHubRepository $params = @{ 'UriFragment' = "repos/$OwnerName/$RepositoryName" 'Method' = 'Delete' - 'Description' = "Deleting $RepositoryName" + 'Description' = "Deleting $RepositoryName" 'AccessToken' = $AccessToken 'TelemetryEventName' = $MyInvocation.MyCommand.Name 'TelemetryProperties' = $telemetryProperties @@ -692,7 +692,7 @@ filter Get-GitHubRepository $params = @{ 'UriFragment' = $uriFragment + '?' + ($getParams -join '&') - 'Description' = $description + 'Description' = $description 'AcceptHeader' = "$script:nebulaAcceptHeader,$script:baptisteAcceptHeader,$script:mercyAcceptHeader" 'AccessToken' = $AccessToken 'TelemetryEventName' = $MyInvocation.MyCommand.Name @@ -1063,7 +1063,7 @@ filter Update-GitHubRepository 'Body' = (ConvertTo-Json -InputObject $hashBody) 'Method' = 'Patch' 'AcceptHeader' = $script:baptisteAcceptHeader - 'Description' = "Updating $RepositoryName" + 'Description' = "Updating $RepositoryName" 'AccessToken' = $AccessToken 'TelemetryEventName' = $MyInvocation.MyCommand.Name 'TelemetryProperties' = $telemetryProperties @@ -1170,7 +1170,7 @@ filter Get-GitHubRepositoryTopic $params = @{ 'UriFragment' = "repos/$OwnerName/$RepositoryName/topics" 'Method' = 'Get' - 'Description' = "Getting topics for $RepositoryName" + 'Description' = "Getting topics for $RepositoryName" 'AcceptHeader' = $script:mercyAcceptHeader 'AccessToken' = $AccessToken 'TelemetryEventName' = $MyInvocation.MyCommand.Name @@ -1349,7 +1349,7 @@ function Set-GitHubRepositoryTopic 'UriFragment' = "repos/$OwnerName/$RepositoryName/topics" 'Body' = (ConvertTo-Json -InputObject $hashBody) 'Method' = 'Put' - 'Description' = $description + 'Description' = $description 'AcceptHeader' = $script:mercyAcceptHeader 'AccessToken' = $AccessToken 'TelemetryEventName' = $MyInvocation.MyCommand.Name @@ -1485,7 +1485,7 @@ filter Get-GitHubRepositoryContributor $params = @{ 'UriFragment' = $uriFragment + '?' + ($getParams -join '&') - 'Description' = "Getting contributors for $RepositoryName" + 'Description' = "Getting contributors for $RepositoryName" 'AccessToken' = $AccessToken 'TelemetryEventName' = $MyInvocation.MyCommand.Name 'TelemetryProperties' = $telemetryProperties @@ -1626,7 +1626,7 @@ filter Get-GitHubRepositoryCollaborator $params = @{ 'UriFragment' = "repos/$OwnerName/$RepositoryName/collaborators?" + ($getParams -join '&') - 'Description' = "Getting collaborators for $RepositoryName" + 'Description' = "Getting collaborators for $RepositoryName" 'AccessToken' = $AccessToken 'TelemetryEventName' = $MyInvocation.MyCommand.Name 'TelemetryProperties' = $telemetryProperties @@ -1733,7 +1733,7 @@ filter Get-GitHubRepositoryLanguage $params = @{ 'UriFragment' = "repos/$OwnerName/$RepositoryName/languages" - 'Description' = "Getting languages for $RepositoryName" + 'Description' = "Getting languages for $RepositoryName" 'AccessToken' = $AccessToken 'TelemetryEventName' = $MyInvocation.MyCommand.Name 'TelemetryProperties' = $telemetryProperties @@ -1840,7 +1840,7 @@ filter Get-GitHubRepositoryTag $params = @{ 'UriFragment' = "repos/$OwnerName/$RepositoryName/tags" - 'Description' = "Getting tags for $RepositoryName" + 'Description' = "Getting tags for $RepositoryName" 'AccessToken' = $AccessToken 'TelemetryEventName' = $MyInvocation.MyCommand.Name 'TelemetryProperties' = $telemetryProperties @@ -1966,7 +1966,7 @@ filter Move-GitHubRepositoryOwnership 'UriFragment' = "repos/$OwnerName/$RepositoryName/transfer" 'Body' = (ConvertTo-Json -InputObject $hashBody) 'Method' = 'Post' - 'Description' = "Transferring ownership of $RepositoryName to $NewOwnerName" + 'Description' = "Transferring ownership of $RepositoryName to $NewOwnerName" 'AccessToken' = $AccessToken 'TelemetryEventName' = $MyInvocation.MyCommand.Name 'TelemetryProperties' = $telemetryProperties diff --git a/GitHubRepositoryForks.ps1 b/GitHubRepositoryForks.ps1 index 457ee90d..98e311d2 100644 --- a/GitHubRepositoryForks.ps1 +++ b/GitHubRepositoryForks.ps1 @@ -107,7 +107,7 @@ filter Get-GitHubRepositoryFork $params = @{ 'UriFragment' = "repos/$OwnerName/$RepositoryName/forks`?" + ($getParams -join '&') - 'Description' = "Getting all forks of $RepositoryName" + 'Description' = "Getting all forks of $RepositoryName" 'AccessToken' = $AccessToken 'TelemetryEventName' = $MyInvocation.MyCommand.Name 'TelemetryProperties' = $telemetryProperties @@ -231,7 +231,7 @@ filter New-GitHubRepositoryFork $params = @{ 'UriFragment' = "repos/$OwnerName/$RepositoryName/forks`?" + ($getParams -join '&') 'Method' = 'Post' - 'Description' = "Creating fork of $RepositoryName" + 'Description' = "Creating fork of $RepositoryName" 'AccessToken' = $AccessToken 'TelemetryEventName' = $MyInvocation.MyCommand.Name 'TelemetryProperties' = $telemetryProperties diff --git a/GitHubRepositoryTraffic.ps1 b/GitHubRepositoryTraffic.ps1 index bcdcda13..d3c26ddd 100644 --- a/GitHubRepositoryTraffic.ps1 +++ b/GitHubRepositoryTraffic.ps1 @@ -106,7 +106,7 @@ filter Get-GitHubReferrerTraffic $params = @{ 'UriFragment' = "repos/$OwnerName/$RepositoryName/traffic/popular/referrers" 'Method' = 'Get' - 'Description' = "Getting referrers for $RepositoryName" + 'Description' = "Getting referrers for $RepositoryName" 'AccessToken' = $AccessToken 'TelemetryEventName' = $MyInvocation.MyCommand.Name 'TelemetryProperties' = $telemetryProperties @@ -220,7 +220,7 @@ filter Get-GitHubPathTraffic $params = @{ 'UriFragment' = "repos/$OwnerName/$RepositoryName/traffic/popular/paths" 'Method' = 'Get' - 'Description' = "Getting popular contents for $RepositoryName" + 'Description' = "Getting popular contents for $RepositoryName" 'AccessToken' = $AccessToken 'TelemetryEventName' = $MyInvocation.MyCommand.Name 'TelemetryProperties' = $telemetryProperties @@ -344,7 +344,7 @@ filter Get-GitHubViewTraffic $params = @{ 'UriFragment' = "repos/$OwnerName/$RepositoryName/traffic/views`?per=$($Per.ToLower())" 'Method' = 'Get' - 'Description' = "Getting views for $RepositoryName" + 'Description' = "Getting views for $RepositoryName" 'AccessToken' = $AccessToken 'TelemetryEventName' = $MyInvocation.MyCommand.Name 'TelemetryProperties' = $telemetryProperties @@ -468,7 +468,7 @@ filter Get-GitHubCloneTraffic $params = @{ 'UriFragment' = "repos/$OwnerName/$RepositoryName/traffic/clones`?per=$($Per.ToLower())" 'Method' = 'Get' - 'Description' = "Getting number of clones for $RepositoryName" + 'Description' = "Getting number of clones for $RepositoryName" 'AccessToken' = $AccessToken 'TelemetryEventName' = $MyInvocation.MyCommand.Name 'TelemetryProperties' = $telemetryProperties diff --git a/GitHubTeams.ps1 b/GitHubTeams.ps1 index fa6da2cc..54b1f809 100644 --- a/GitHubTeams.ps1 +++ b/GitHubTeams.ps1 @@ -146,7 +146,7 @@ filter Get-GitHubTeam $params = @{ 'UriFragment' = $uriFragment 'AcceptHeader' = $script:hellcatAcceptHeader - 'Description' = $description + 'Description' = $description 'AccessToken' = $AccessToken 'TelemetryEventName' = $MyInvocation.MyCommand.Name 'TelemetryProperties' = $telemetryProperties @@ -266,7 +266,7 @@ filter Get-GitHubTeamMember $params = @{ 'UriFragment' = "teams/$TeamId/members" - 'Description' = "Getting members of team $TeamId" + 'Description' = "Getting members of team $TeamId" 'AccessToken' = $AccessToken 'TelemetryEventName' = $MyInvocation.MyCommand.Name 'TelemetryProperties' = $telemetryProperties diff --git a/GitHubUsers.ps1 b/GitHubUsers.ps1 index ed2b2cb9..1ed37ccc 100644 --- a/GitHubUsers.ps1 +++ b/GitHubUsers.ps1 @@ -275,7 +275,7 @@ filter Get-GitHubUserContextualInformation $params = @{ 'UriFragment' = "users/$UserName/hovercard`?" + ($getParams -join '&') 'Method' = 'Get' - 'Description' = "Getting hovercard information for $UserName" + 'Description' = "Getting hovercard information for $UserName" 'AcceptHeader' = $script:hagarAcceptHeader 'AccessToken' = $AccessToken 'TelemetryEventName' = $MyInvocation.MyCommand.Name @@ -390,7 +390,7 @@ function Update-GitHubCurrentUser 'UriFragment' = 'user' 'Method' = 'Patch' 'Body' = (ConvertTo-Json -InputObject $hashBody) - 'Description' = "Updating current authenticated user" + 'Description' = "Updating current authenticated user" 'AccessToken' = $AccessToken 'TelemetryEventName' = $MyInvocation.MyCommand.Name 'NoStatus' = (Resolve-ParameterWithDefaultConfigurationValue -Name NoStatus -ConfigValueName DefaultNoStatus) From d855e80ae3fa78a5f183cfb2f319616239175669 Mon Sep 17 00:00:00 2001 From: "Christoph Bergmeister [MVP]" Date: Fri, 26 Jun 2020 17:01:34 +0100 Subject: [PATCH 39/60] Add more logging into catch block when PSScriptAnalyzer encounters a fatal error (#246) Enhancing #236 --- build/pipelines/templates/run-staticAnalysis.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build/pipelines/templates/run-staticAnalysis.yaml b/build/pipelines/templates/run-staticAnalysis.yaml index 5a3a14fa..5325a767 100644 --- a/build/pipelines/templates/run-staticAnalysis.yaml +++ b/build/pipelines/templates/run-staticAnalysis.yaml @@ -9,7 +9,7 @@ steps: displayName: 'Install PSScriptAnalyzer' - powershell: | - $results = try { Invoke-ScriptAnalyzer -Settings ./PSScriptAnalyzerSettings.psd1 -Path ./ –Recurse -ErrorAction Stop } catch { $_.Exception.StackTrace; throw } + $results = try { Invoke-ScriptAnalyzer -Settings ./PSScriptAnalyzerSettings.psd1 -Path ./ –Recurse -ErrorAction Stop } catch { 'Unexpected Error'; $_.Exception.StackTrace; if ($IsCoreCLR) { Get-Error }; throw } $results | ForEach-Object { Write-Host "##vso[task.logissue type=$($_.Severity);sourcepath=$($_.ScriptPath);linenumber=$($_.Line);columnnumber=$($_.Column);]$($_.Message)" } $null = New-Item -Path ..\ -Name ScriptAnalyzer -ItemType Directory -Force From d96541ee5e16a3b9e11a52994a26540b203fb22c Mon Sep 17 00:00:00 2001 From: Simon Heather <32168619+X-Guardian@users.noreply.github.com> Date: Fri, 26 Jun 2020 21:51:49 +0100 Subject: [PATCH 40/60] GitHubRepositories: Add New-GitHubRepositoryFromTemplate Function (#221) Adds a new function `New-GitHubRepositoryFromTemplate`. This function creates a new GitHub repository from a specified template repository. Fixes #220 --- CONTRIBUTING.md | 1 + GitHubRepositories.ps1 | 169 +++++++++++++++++++++++++++++ PowerShellForGitHub.psd1 | 1 + Tests/GitHubRepositories.tests.ps1 | 78 +++++++++++++ USAGE.md | 34 +++++- 5 files changed, 281 insertions(+), 2 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index f3e2d01f..3195ab63 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -554,6 +554,7 @@ Thank you to all of our contributors, no matter how big or small the contributio - **[Jess Pomfret (@jpomfret)](https://github.com/jpomfret)** - **[Giuseppe Campanelli (@themilanfan)](https://github.com/themilanfan)** - **[Christoph Bergmeister (@bergmeister)](https://github.com/bergmeister)** +- **[Simon Heather (@X-Guardian)](https://github.com/X-Guardian)** ---------- diff --git a/GitHubRepositories.ps1 b/GitHubRepositories.ps1 index dcb2fc92..50849b7a 100644 --- a/GitHubRepositories.ps1 +++ b/GitHubRepositories.ps1 @@ -227,6 +227,175 @@ filter New-GitHubRepository return (Invoke-GHRestMethod @params | Add-GitHubRepositoryAdditionalProperties) } +filter New-GitHubRepositoryFromTemplate +{ +<# + .SYNOPSIS + Creates a new repository on GitHub from a template repository. + + .DESCRIPTION + Creates a new repository on GitHub from a template repository. + + The Git repo for this module can be found here: http://aka.ms/PowerShellForGitHub + + .PARAMETER OwnerName + Owner of the template repository. + If no value is specified, the DefaultOwnerName configuration property value will be used, + and if there is no configuration value defined, the current authenticated user will be used. + + .PARAMETER RepositoryName + Name of the template repository. + + .PARAMETER Uri + Uri for the repository. + The OwnerName and RepositoryName will be extracted from here instead of needing to provide + them individually. + + .PARAMETER TargetOwnerName + The organization or person who will own the new repository. + To create a new repository in an organization, the authenticated user must be a member + of the specified organization. + + .PARAMETER TargetRepositoryName + Name of the repository to be created. + + .PARAMETER Description + A short description of the repository. + + .PARAMETER Private + By default, this repository will created Public. Specify this to create a private + repository. + + .PARAMETER AccessToken + If provided, this will be used as the AccessToken for authentication with the + REST Api. Otherwise, will attempt to use the configured value or will run unauthenticated. + + .PARAMETER NoStatus + If this switch is specified, long-running commands will run on the main thread + with no commandline status update. When not specified, those commands run in + the background, enabling the command prompt to provide status information. + If not supplied here, the DefaultNoStatus configuration property value will be used. + + .INPUTS + GitHub.Branch + GitHub.Content + GitHub.Event + GitHub.Issue + GitHub.IssueComment + GitHub.Label + GitHub.Milestone + GitHub.PullRequest + GitHub.Project + GitHub.ProjectCard + GitHub.ProjectColumn + GitHub.Release + GitHub.Repository + + .OUTPUTS + GitHub.Repository + + .NOTES + The authenticated user must own or be a member of an organization that owns the repository. + + To check if a repository is available to use as a template, call `Get-GitHubRepository` on the + repository in question and check that the is_template property is $true. + + .EXAMPLE + New-GitHubRepositoryFromTemplate -OwnerName MyOrg -RepositoryName MyTemplateRepo -TargetRepositoryName MyNewRepo -TargetOwnerName Me + + Creates a new GitHub repository from the specified template repository. + + .EXAMPLE + $repo = Get-GitHubRepository -OwnerName MyOrg -RepositoryName MyTemplateRepo + $repo | New-GitHubRepositoryFromTemplate -TargetRepositoryName MyNewRepo -TargetOwnerName Me + + You can also pipe in a repo that was returned from a previous command. +#> + [CmdletBinding( + SupportsShouldProcess, + PositionalBinding = $false)] + [OutputType({$script:GitHubRepositoryTypeName})] + [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSShouldProcess", "", + Justification="Methods called within here make use of PSShouldProcess, and the switch is + passed on to them inherently.")] + param( + [Parameter(ParameterSetName = 'Elements')] + [string] $OwnerName, + + [Parameter( + Mandatory, + Position = 1, + ParameterSetName = 'Elements')] + [ValidateNotNullOrEmpty()] + [string] $RepositoryName, + + [Parameter( + Mandatory, + Position = 2, + ValueFromPipelineByPropertyName, + ParameterSetName = 'Uri')] + [Alias('RepositoryUrl')] + [string] $Uri, + + [Parameter( + Mandatory, + Position = 3)] + [ValidateNotNullOrEmpty()] + [string] $TargetOwnerName, + + [Parameter( + Mandatory, + Position = 4)] + [ValidateNotNullOrEmpty()] + [string] $TargetRepositoryName, + + [string] $Description, + + [switch] $Private, + + [string] $AccessToken, + + [switch] $NoStatus + ) + + Write-InvocationLog + + $elements = Resolve-RepositoryElements -BoundParameters $PSBoundParameters + $OwnerName = $elements.ownerName + + $telemetryProperties = @{ + RepositoryName = (Get-PiiSafeString -PlainText $RepositoryName) + OwnerName = (Get-PiiSafeString -PlainText $OwnerName) + TargetRepositoryName = (Get-PiiSafeString -PlainText $TargetRepositoryName) + TargetOwnerName = (Get-PiiSafeString -PlainText $TargetOwnerName) + } + + $uriFragment = "repos/$OwnerName/$RepositoryName/generate" + + $hashBody = @{ + owner = $TargetOwnerName + name = $TargetRepositoryName + } + + if ($PSBoundParameters.ContainsKey('Description')) { $hashBody['description'] = $Description } + if ($PSBoundParameters.ContainsKey('Private')) { $hashBody['private'] = $Private.ToBool() } + + $params = @{ + 'UriFragment' = $uriFragment + 'Body' = (ConvertTo-Json -InputObject $hashBody) + 'Method' = 'Post' + 'Description' = "Creating $TargetRepositoryName from Template" + 'AcceptHeader' = $script:baptisteAcceptHeader + 'AccessToken' = $AccessToken + 'TelemetryEventName' = $MyInvocation.MyCommand.Name + 'TelemetryProperties' = $telemetryProperties + 'NoStatus' = (Resolve-ParameterWithDefaultConfigurationValue ` + -BoundParameters $PSBoundParameters -Name NoStatus -ConfigValueName DefaultNoStatus) + } + + return (Invoke-GHRestMethod @params | Add-GitHubRepositoryAdditionalProperties) +} + filter Remove-GitHubRepository { <# diff --git a/PowerShellForGitHub.psd1 b/PowerShellForGitHub.psd1 index 36467c7e..58712d2f 100644 --- a/PowerShellForGitHub.psd1 +++ b/PowerShellForGitHub.psd1 @@ -114,6 +114,7 @@ 'New-GitHubProjectColumn', 'New-GitHubPullRequest', 'New-GitHubRepository', + 'New-GitHubRepositoryFromTemplate', 'New-GitHubRepositoryFork', 'Remove-GitHubAssignee', 'Remove-GitHubIssueComment', diff --git a/Tests/GitHubRepositories.tests.ps1 b/Tests/GitHubRepositories.tests.ps1 index 04aa8e01..6575f249 100644 --- a/Tests/GitHubRepositories.tests.ps1 +++ b/Tests/GitHubRepositories.tests.ps1 @@ -290,6 +290,84 @@ try } } + Describe 'GitHubRepositories\New-GitHubRepositoryFromTemplate' { + BeforeAll { + $templateRepoName = ([Guid]::NewGuid().Guid) + $ownerName = $script:ownerName + $testGitIgnoreTemplate = (Get-GitHubGitIgnore)[0] + $testLicenseTemplate = (Get-GitHubLicense)[0].key + + $newGitHubRepositoryParms = @{ + RepositoryName = $templateRepoName + Description = $defaultRepoDesc + GitIgnoreTemplate = $testGitIgnoreTemplate + LicenseTemplate = $testLicenseTemplate + IsTemplate = $true + } + + $templateRepo = New-GitHubRepository @newGitHubRepositoryParms + } + + Context 'When creating a public repository from a template' { + BeforeAll { + $repoName = ([Guid]::NewGuid().Guid) + $newRepoDesc = 'New Repo Description' + $newGitHubRepositoryFromTemplateParms = @{ + RepositoryName = $templateRepoName + OwnerName = $templateRepo.owner.login + TargetOwnerName = $ownerName + TargetRepositoryName = $repoName + Description = $newRepoDesc + } + + $repo = New-GitHubRepositoryFromTemplate @newGitHubRepositoryFromTemplateParms + } + + It 'Should support pipeline input for the uri parameter' { + $newGitHubRepositoryFromTemplateParms = @{ + TargetOwnerName = $ownerName + TargetRepositoryName = $repoName + } + + { $templateRepo | New-GitHubRepositoryFromTemplate @newGitHubRepositoryFromTemplateParms -WhatIf } | + Should -Not -Throw + } + + It 'Should have the expected type and addititional properties' { + $repo.PSObject.TypeNames[0] | Should -Be 'GitHub.Repository' + $repo.name | Should -Be $repoName + $repo.private | Should -BeFalse + $repo.owner.login | Should -Be $script:ownerName + $repo.description | Should -Be $newRepoDesc + $repo.is_template | Should -BeFalse + $repo.RepositoryId | Should -Be $repo.id + $repo.RepositoryUrl | Should -Be $repo.html_url + } + + It 'Should have created a .gitignore file' { + { Get-GitHubContent -Uri $repo.svn_url -Path '.gitignore' } | Should -Not -Throw + } + + It 'Should have created a LICENSE file' { + { Get-GitHubContent -Uri $repo.svn_url -Path 'LICENSE' } | Should -Not -Throw + } + + AfterAll { + if (Get-Variable -Name repo -ErrorAction SilentlyContinue) + { + Remove-GitHubRepository -Uri $repo.svn_url -Confirm:$false + } + } + } + + AfterAll { + if (Get-Variable -Name templateRepo -ErrorAction SilentlyContinue) + { + Remove-GitHubRepository -Uri $templateRepo.svn_url -Confirm:$false + } + } + } + Describe 'Getting repositories' { Context 'For authenticated user' { BeforeAll -Scriptblock { diff --git a/USAGE.md b/USAGE.md index fd96d10d..08a1086d 100644 --- a/USAGE.md +++ b/USAGE.md @@ -29,7 +29,11 @@ * [Updating the current authenticated user](#updating-the-current-authenticated-user) * [Getting any user](#getting-any-user) * [Getting all users](#getting-all-users) - * [Repositories](#repositories) + * [Repositories](#repositories]) + * [Create a repository](#Create-a-repository) + * [Create a repository in an organization](#Create-a-repository-in-an-organization) + * [Create a repository in an organization and grant access to a team](#Create-a-repository-in-an-organization-and-grant-access-to-a-team) + * [Create a repository from a template repository](#Create-a-repository-from-a-template-repository) * [Get repository vulnerability alert status](#get-repository-vulnerability-alert-status) * [Enable repository vulnerability alerts](#enable-repository-vulnerability-alerts) * [Disable repository vulnerability alerts](#disable-repository-vulnerability-alerts) @@ -103,7 +107,9 @@ The logging is affected by configuration properties (which can be checked with different log file for each PowerShell process. An easy way to view the filtered entries for a session is (replacing `PID` with the PID that you are interested in): - Get-Content -Path -Encoding UTF8 | Where { $_ -like '*[[]PID[]]*' } +```powershell +Get-Content -Path -Encoding UTF8 | Where { $_ -like '*[[]PID[]]*' } +``` ---------- @@ -422,6 +428,30 @@ Get-GitHubUser ### Repositories +#### Create a repository + +```powershell +New-GitHubRepository -RepositoryName TestRepo +``` + +#### Create a repository in an organization + +```powershell +New-GitHubRepository -RepositoryName TestRepo -OrganizationName MyOrg +``` + +#### Create a repository in an organization and grant access to a team + +```powershell +$myTeam = Get-GitHubTeam -OrganizationName MyOrg | Where-Object -Property name -eq MyTeam +New-GitHubRepository -RepositoryName TestRepo -OrganizationName MyOrg -TeamId $myTeam.id +``` + +#### Create a repository from a template repository + +```powershell +New-GitHubRepositoryFromTemplate -OwnerName MyOrg -RepositoryName MyNewRepo-TemplateOwnerName MyOrg -TemplateRepositoryName MyTemplateRepo +======= #### Get repository vulnerability alert status ```powershell From a1f5e935165b2c606f81089524e89da9bb8b851d Mon Sep 17 00:00:00 2001 From: Simon Heather <32168619+X-Guardian@users.noreply.github.com> Date: Sat, 27 Jun 2020 22:40:16 +0100 Subject: [PATCH 41/60] GitHubContents: New Function Set-GitHubContent (#241) Adds `Set-GitHubContent` An additional parameter `BranchName` has also been added to the `Get-GitHubContent` function which is required by `Set-GitHubContent`. References: [GitHub Rest API v3 Contents](https://developer.github.com/v3/repos/contents/#create-or-update-file-contents) --- GitHubContents.ps1 | 350 ++++++++++++++++++++++++++++++++- PowerShellForGitHub.psd1 | 1 + Tests/GitHubContents.tests.ps1 | 188 ++++++++++++++++++ USAGE.md | 33 ++++ 4 files changed, 570 insertions(+), 2 deletions(-) diff --git a/GitHubContents.ps1 b/GitHubContents.ps1 index 780a538d..7ce6472a 100644 --- a/GitHubContents.ps1 +++ b/GitHubContents.ps1 @@ -30,6 +30,9 @@ .PARAMETER Path The file path for which to retrieve contents + .PARAMETER BranchName + The branch, or defaults to the default branch of not specified. + .PARAMETER MediaType The format in which the API will return the body of the issue. @@ -126,6 +129,9 @@ [string] $Path, + [ValidateNotNullOrEmpty()] + [string] $BranchName, + [ValidateSet('Raw', 'Html', 'Object')] [string] $MediaType = 'Object', @@ -162,6 +168,11 @@ $description = "Getting all content for in $RepositoryName" } + if ($PSBoundParameters.ContainsKey('BranchName')) + { + $uriFragment += "?ref=$BranchName" + } + $params = @{ 'UriFragment' = $uriFragment 'Description' = $description @@ -197,6 +208,315 @@ return $result } +filter Set-GitHubContent +{ + <# + .SYNOPSIS + Sets the contents of a file or directory in a repository on GitHub. + + .DESCRIPTION + Sets the contents of a file or directory in a repository on GitHub. + + The Git repo for this module can be found here: http://aka.ms/PowerShellForGitHub + + .PARAMETER OwnerName + Owner of the repository. + If not supplied here, the DefaultOwnerName configuration property value will be used. + + .PARAMETER RepositoryName + Name of the repository. + If not supplied here, the DefaultRepositoryName configuration property value will be used. + + .PARAMETER Uri + Uri for the repository. + The OwnerName and RepositoryName will be extracted from here instead of needing to provide + them individually. + + .PARAMETER Path + The file path for which to set contents. + + .PARAMETER CommitMessage + The Git commit message. + + .PARAMETER Content + The new file content. + + .PARAMETER Sha + The SHA value of the current file if present. If this parameter is not provided, and the + file currently exists in the specified branch of the repo, it will be read to obtain this + value. + + .PARAMETER BranchName + The branch, or defaults to the default branch if not specified. + + .PARAMETER CommitterName + The name of the committer of the commit. Defaults to the name of the authenticated user if + not specified. If specified, CommiterEmail must also be specified. + + .PARAMETER CommitterEmail + The email of the committer of the commit. Defaults to the email of the authenticated user + if not specified. If specified, CommitterName must also be specified. + + .PARAMETER AuthorName + The name of the author of the commit. Defaults to the name of the authenticated user if + not specified. If specified, AuthorEmail must also be specified. + + .PARAMETER AuthorEmail + The email of the author of the commit. Defaults to the email of the authenticated user if + not specified. If specified, AuthorName must also be specified. + + .PARAMETER AccessToken + If provided, this will be used as the AccessToken for authentication with the + REST Api. Otherwise, will attempt to use the configured value or will run unauthenticated. + + .PARAMETER NoStatus + If this switch is specified, long-running commands will run on the main thread + with no commandline status update. When not specified, those commands run in + the background, enabling the command prompt to provide status information. + If not supplied here, the DefaultNoStatus configuration property value will be used. + + .INPUTS + GitHub.Branch + GitHub.Content + GitHub.Event + GitHub.Issue + GitHub.IssueComment + GitHub.Label + GitHub.Milestone + GitHub.PullRequest + GitHub.Project + GitHub.ProjectCard + GitHub.ProjectColumn + GitHub.Release + GitHub.Repository + + .OUTPUTS + GitHub.Content + + .EXAMPLE + Set-GitHubContent -OwnerName microsoft -RepositoryName PowerShellForGitHub -Path README.md -CommitMessage 'Adding README.md' -Content '# README' -BranchName master + + Sets the contents of the README.md file on the master branch of the PowerShellForGithub repository. +#> + [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSReviewUnusedParameter', '', + Justification = 'One or more parameters (like NoStatus) are only referenced by helper + methods which get access to it from the stack via Get-Variable -Scope 1.')] + [CmdletBinding( + SupportsShouldProcess, + PositionalBinding = $false)] + [OutputType({$script:GitHubContentTypeName})] + param( + [Parameter( + Mandatory, + ParameterSetName = 'Elements')] + [string] $OwnerName, + + [Parameter( + Mandatory, + ParameterSetName = 'Elements')] + [string] $RepositoryName, + + [Parameter( + Mandatory, + ValueFromPipelineByPropertyName, + Position = 1, + ParameterSetName='Uri')] + [Alias('RepositoryUrl')] + [string] $Uri, + + [Parameter( + Mandatory, + ValueFromPipelineByPropertyName, + Position = 2)] + [string] $Path, + + [Parameter( + Mandatory, + Position = 3)] + [string] $CommitMessage, + + [Parameter( + Mandatory, + Position = 4)] + [string] $Content, + + [Parameter(ValueFromPipelineByPropertyName)] + [string] $Sha, + + [Parameter(ValueFromPipelineByPropertyName)] + [string] $BranchName, + + [string] $CommitterName, + + [string] $CommitterEmail, + + [string] $AuthorName, + + [string] $AuthorEmail, + + [string] $AccessToken, + + [switch] $NoStatus + ) + + $elements = Resolve-RepositoryElements -DisableValidation + $OwnerName = $elements.ownerName + $RepositoryName = $elements.repositoryName + + $telemetryProperties = @{ + 'OwnerName' = (Get-PiiSafeString -PlainText $OwnerName) + 'RepositoryName' = (Get-PiiSafeString -PlainText $RepositoryName) + } + + $uriFragment = "/repos/$OwnerName/$RepositoryName/contents/$Path" + + $encodedContent = [Convert]::ToBase64String([System.Text.Encoding]::Unicode.GetBytes($Content)) + + $hashBody = @{ + message = $CommitMessage + content = $encodedContent + } + + if ($PSBoundParameters.ContainsKey('BranchName')) + { + $hashBody['branch'] = $BranchName + } + + if ($PSBoundParameters.ContainsKey('CommitterName') -or + $PSBoundParameters.ContainsKey('CommitterEmail')) + { + if (![System.String]::IsNullOrEmpty($CommitterName) -and + ![System.String]::IsNullOrEmpty($CommitterEmail)) + { + $hashBody['committer'] = @{ + name = $CommitterName + email = $CommitterEmail + } + } + else + { + $message = 'Both CommiterName and CommitterEmail need to be specified.' + Write-Log -Message $message -Level Error + throw $message + } + } + + if ($PSBoundParameters.ContainsKey('AuthorName') -or + $PSBoundParameters.ContainsKey('AuthorEmail')) + { + if (![System.String]::IsNullOrEmpty($CommitterName) -and + ![System.String]::IsNullOrEmpty($CommitterEmail)) + { + $hashBody['author'] = @{ + name = $AuthorName + email = $AuthorEmail + } + } + else + { + $message = 'Both AuthorName and AuthorEmail need to be specified.' + Write-Log -Message $message -Level Error + throw $message + } + } + + if ($PSBoundParameters.ContainsKey('Sha')) + { + $hashBody['sha'] = $Sha + } + + if ($PSCmdlet.ShouldProcess( + "$BranchName branch of $RepositoryName", + "Set GitHub Contents on $Path")) + { + Write-InvocationLog + + $params = @{ + UriFragment = $uriFragment + Description = "Writing content for $Path in the $BranchName branch of $RepositoryName" + Body = (ConvertTo-Json -InputObject $hashBody) + Method = 'Put' + AccessToken = $AccessToken + TelemetryEventName = $MyInvocation.MyCommand.Name + TelemetryProperties = $telemetryProperties + NoStatus = (Resolve-ParameterWithDefaultConfigurationValue -Name NoStatus ` + -ConfigValueName DefaultNoStatus) + } + + try + { + return (Invoke-GHRestMethod @params | Add-GitHubContentAdditionalProperties) + } + catch + { + $overwriteShaRequired = $false + + # Temporary code to handle current differences in exception object between PS5 and PS7 + if ($PSVersionTable.PSedition -eq 'Core') + { + $errorMessage = ($_.ErrorDetails.Message | ConvertFrom-Json).message -replace '\n',' ' -replace '\"','"' + if (($_.Exception -is [Microsoft.PowerShell.Commands.HttpResponseException]) -and + ($errorMessage -eq 'Invalid request. "sha" wasn''t supplied.')) + { + $overwriteShaRequired = $true + } + else + { + throw $_ + } + } + else + { + $errorMessage = $_.Exception.Message -replace '\n',' ' -replace '\"','"' + if ($errorMessage -like '*Invalid request. "sha" wasn''t supplied.*') + { + $overwriteShaRequired = $true + } + else + { + throw $_ + } + } + + if ($overwriteShaRequired) + { + # Get SHA from current file + $getGitHubContentParms = @{ + Path = $Path + OwnerName = $OwnerName + RepositoryName = $RepositoryName + } + + if ($PSBoundParameters.ContainsKey('BranchName')) + { + $getGitHubContentParms['BranchName'] = $BranchName + } + + if ($PSBoundParameters.ContainsKey('AccessToken')) + { + $getGitHubContentParms['AccessToken'] = $AccessToken + } + + if ($PSBoundParameters.ContainsKey('NoStatus')) + { + $getGitHubContentParms['NoStatus'] = $NoStatus + } + + $object = Get-GitHubContent @getGitHubContentParms + + $hashBody['sha'] = $object.sha + $params['body'] = ConvertTo-Json -InputObject $hashBody + + $message = 'Replacing the content of an existing file requires the current SHA ' + + 'of that file. Retrieving the SHA now.' + Write-Log -Level Verbose -Message $message + + return (Invoke-GHRestMethod @params | Add-GitHubContentAdditionalProperties) + } + } + } +} + filter Add-GitHubContentAdditionalProperties { <# @@ -235,11 +555,37 @@ filter Add-GitHubContentAdditionalProperties if (-not (Get-GitHubConfiguration -Name DisablePipelineSupport)) { - $elements = Split-GitHubUri -Uri $item.url + if ($item.html_url) + { + $uri = $item.html_url + } + else + { + $uri = $item.content.html_url + } + + $elements = Split-GitHubUri -Uri $uri $repositoryUrl = Join-GitHubUri @elements + Add-Member -InputObject $item -Name 'RepositoryUrl' -Value $repositoryUrl -MemberType NoteProperty -Force + + $hostName = $(Get-GitHubConfiguration -Name 'ApiHostName') + + if ($uri -match "^https?://(?:www\.|api\.|)$hostName/(?:[^/]+)/(?:[^/]+)/(?:blob|tree)/([^/]+)/([^#]*)?$") + { + $branchName = $Matches[1] + $path = $Matches[2] + } + else + { + $branchName = [String]::Empty + $path = [String]::Empty + } + + Add-Member -InputObject $item -Name 'BranchName' -Value $branchName -MemberType NoteProperty -Force + Add-Member -InputObject $item -Name 'Path' -Value $path -MemberType NoteProperty -Force } Write-Output $item } -} \ No newline at end of file +} diff --git a/PowerShellForGitHub.psd1 b/PowerShellForGitHub.psd1 index 58712d2f..3504bf7c 100644 --- a/PowerShellForGitHub.psd1 +++ b/PowerShellForGitHub.psd1 @@ -130,6 +130,7 @@ 'Restore-GitHubConfiguration', 'Set-GitHubAuthentication', 'Set-GitHubConfiguration', + 'Set-GitHubContent', 'Set-GitHubIssueComment', 'Set-GitHubIssueLabel', 'Set-GitHubLabel', diff --git a/Tests/GitHubContents.tests.ps1 b/Tests/GitHubContents.tests.ps1 index 806d373c..ba1ef8d4 100644 --- a/Tests/GitHubContents.tests.ps1 +++ b/Tests/GitHubContents.tests.ps1 @@ -254,6 +254,194 @@ try } } } + + Describe 'GitHubContents/Set-GitHubContent' { + BeforeAll { + $repoName = [Guid]::NewGuid().Guid + + $repo = New-GitHubRepository -RepositoryName $repoName -AutoInit + } + + Context 'When setting new file content' { + BeforeAll { + $filePath = 'notes' + $fileName = 'hello.txt' + $commitMessage = 'Commit Message' + $content = 'This is the content for test.txt' + $branchName = 'master' + $committerName = 'John Doe' + $committerEmail = 'john.doe@testdomain.com' + $authorName = 'Jane Doe' + $authorEmail = 'jane.doe@testdomain.com' + + $setGitHubContentParms = @{ + Path = "$filePath/$fileName" + CommitMessage = $commitMessage + Branch = $branchName + Content = $content + Uri = $repo.svn_url + CommitterName = $committerName + CommitterEmail = $committerEmail + authorName = $authorName + authorEmail = $authorEmail + } + + $result = Set-GitHubContent @setGitHubContentParms + } + + It 'Should have the expected type and additional properties' { + $result.PSObject.TypeNames[0] | Should -Be 'GitHub.Content' + $result.content.name | Should -Be $fileName + $result.content.path | Should -Be "$filePath/$fileName" + $result.content.url | Should -Be ("https://api.github.com/repos/$($script:ownerName)" + + "/$repoName/contents/$filePath/$($fileName)?ref=$BranchName") + $result.commit.author.name | Should -Be $authorName + $result.commit.author.email | Should -Be $authorEmail + $result.commit.committer.name | Should -Be $committerName + $result.commit.committer.email | Should -Be $committerEmail + $result.commit.message | Should -Be $commitMessage + } + + It 'Should have written the correct content' { + $getGitHubContentParms = @{ + Path = "$filePath/$fileName" + Uri = $repo.svn_url + MediaType = 'Raw' + ResultAsString = $true + } + + $writtenContent = Get-GitHubContent @getGitHubContentParms + + $content | Should -Be $writtenContent + } + + It 'Should support pipeline input' { + $getGitHubContentParms = @{ + Path = "$filePath/$fileName" + Uri = $repo.svn_url + } + + $writtenContent = Get-GitHubContent @getGitHubContentParms + + $setGitHubContentParms = @{ + CommitMessage = $commitMessage + Content = $content + CommitterName = $committerName + CommitterEmail = $committerEmail + authorName = $authorName + authorEmail = $authorEmail + } + + { $writtenContent | Set-GitHubContent @setGitHubContentParms -WhatIf } | Should -Not -Throw + } + } + + Context 'When overwriting file content' { + BeforeAll { + $filePath = 'notes' + $fileName = 'hello.txt' + $commitMessage = 'Commit Message 2' + $content = 'This is the new content for test.txt' + $branchName = 'master' + $committerName = 'John Doe' + $committerEmail = 'john.doe@testdomain.com' + $authorName = 'Jane Doe' + $authorEmail = 'jane.doe@testdomain.com' + + $setGitHubContentParms = @{ + Path = "$filePath/$fileName" + CommitMessage = $commitMessage + BranchName = $branchName + Content = $content + Uri = $repo.svn_url + CommitterName = $committerName + CommitterEmail = $committerEmail + authorName = $authorName + authorEmail = $authorEmail + NoStatus = $true + } + + $result = Set-GitHubContent @setGitHubContentParms + } + + It 'Should have the expected type and additional properties' { + $result.PSObject.TypeNames[0] | Should -Be 'GitHub.Content' + $result.content.name | Should -Be $fileName + $result.content.path | Should -Be "$filePath/$fileName" + $result.content.url | Should -Be ("https://api.github.com/repos/$($script:ownerName)" + + "/$repoName/contents/$filePath/$($fileName)?ref=$BranchName") + $result.commit.author.name | Should -Be $authorName + $result.commit.author.email | Should -Be $authorEmail + $result.commit.committer.name | Should -Be $committerName + $result.commit.committer.email | Should -Be $committerEmail + $result.commit.message | Should -Be $commitMessage + } + + It 'Should have written the correct content' { + $getGitHubContentParms = @{ + Path = "$filePath/$fileName" + Uri = $repo.svn_url + MediaType = 'Raw' + ResultAsString = $true + } + + $writtenContent = Get-GitHubContent @getGitHubContentParms + + $content | Should -Be $writtenContent + } + } + + Context 'When Specifying only one Committer parameter' { + $setGitHubContentParms = @{ + Path = "$filePath/$fileName" + CommitMessage = $commitMessage + BranchName = $branchName + Content = $content + Uri = $repo.svn_url + CommitterName = $committerName + } + + It 'Shoud throw the correct exception' { + $errorMessage = 'Both CommiterName and CommitterEmail need to be specified.' + { Set-GitHubContent @setGitHubContentParms } | Should -Throw $errorMessage + } + } + + Context 'When Specifying only one Author parameter' { + $setGitHubContentParms = @{ + Path = "$filePath/$fileName" + Uri = $repo.svn_url + CommitMessage = $commitMessage + BranchName = $branchName + Content = $content + AuthorName = $authorName + } + + It 'Shoud throw the correct exception' { + $errorMessage = 'Both AuthorName and AuthorEmail need to be specified.' + { Set-GitHubContent @setGitHubContentParms } | Should -Throw $errorMessage + } + } + + Context 'When Invoke-GHRestMethod returns an unexpected error' { + It 'Should throw' { + $setGitHubContentParms = @{ + Path = "$filePath/$fileName" + OwnerName = $script:ownerName + RepositoryName = 'IncorrectRepositoryName' + BranchName = $branchName + CommitMessage = $commitMessage + Content = $content + } + + { Set-GitHubContent @setGitHubContentParms } | Should -Throw + } + } + + AfterAll { + Remove-GitHubRepository -Uri $repo.svn_url -Force + } + } } finally { diff --git a/USAGE.md b/USAGE.md index 08a1086d..2b275c61 100644 --- a/USAGE.md +++ b/USAGE.md @@ -42,6 +42,11 @@ * [Forks](#forks) * [Get all the forks for a repository](#get-all-the-forks-for-a-repository) * [Create a new fork](#create-a-new-fork) + * [Content](#content) + * [Get html output for a file](#get-html-output-for-a-file) + * [Get raw output for a file](#get-raw-output-for-a-file) + * [Get a list of files](#get-a-list-of-files) + * [Write a file to a branch of a repository](#write-a-file-to-a-branch-of-a-repository) * [Traffic](#traffic) * [Get the referrer traffic for a repository](#get-the-referrer-traffic-for-a-repository) * [Get the popular content for a repository](#get-the-popular-content-for-a-repository) @@ -498,6 +503,34 @@ New-GitHubRepositoryForm -OwnerName microsoft -RepositoryName PowerShellForGitHu ---------- +### Content + +#### Get html output for a file + +```powershell +Get-GitHubContent -OwnerName microsoft -RepositoryName PowerShellForGitHub -Path README.md -MediaType Html +``` + +#### Get raw output for a file + +```powershell +Get-GitHubContent -OwnerName microsoft -RepositoryName PowerShellForGitHub -Path LICENSE +``` + +#### Get a list of files + +```powershell +Get-GitHubContent -OwnerName microsoft -RepositoryName PowerShellForGitHub -Path Tests +``` + +#### Write a file to a branch of a repository + +```powershell +Set-GitHubContent -OwnerName microsoft -RepositoryName PowerShellForGitHub -Path README.md -CommitMessage 'Adding README.md' -Content '# README' -BranchName master +``` + +---------- + ### Traffic #### Get the referrer traffic for a repository From 41de3adb29ed583f775ce30e52c3d6ed8ade35ff Mon Sep 17 00:00:00 2001 From: Simon Heather <32168619+X-Guardian@users.noreply.github.com> Date: Sun, 28 Jun 2020 00:23:00 +0100 Subject: [PATCH 42/60] GitHubRepositories: Add Output Type Views (#205) * Adds formatting for all of the types exposed in GitHubRepositories.ps1. * Fixes the `OutputType` for RepositoryCollaborator and RepositoryContributor. --- Formatters/GitHubRepositories.Format.ps1xml | 181 ++++++++++++++++++ GitHubRepositories.ps1 | 197 +++++++++++++++++++- PowerShellForGitHub.psd1 | 5 + Tests/GitHubRepositories.tests.ps1 | 8 +- 4 files changed, 383 insertions(+), 8 deletions(-) create mode 100644 Formatters/GitHubRepositories.Format.ps1xml diff --git a/Formatters/GitHubRepositories.Format.ps1xml b/Formatters/GitHubRepositories.Format.ps1xml new file mode 100644 index 00000000..5325fd19 --- /dev/null +++ b/Formatters/GitHubRepositories.Format.ps1xml @@ -0,0 +1,181 @@ + + + + + + GitHub.Repository + + GitHub.Repository + + + + + + + full_name + + + visibility + + + description + + + + + + + + + GitHub.RepositoryTopic + + GitHub.RepositoryTopic + + + + + + + names + + + RepositoryUrl + + + + + + + + + GitHub.RepositoryContributor + + GitHub.RepositoryContributor + + + + + + + UserName + + + type + + + contributions + + + + + + + + + GitHub.RepositoryContributorStatistics + + GitHub.RepositoryContributorStatistics + + + + + + + + + + + + + + + $_.author.UserName + + + + total + + + weeks + + + + + + + + + GitHub.RepositoryCollaborator + + GitHub.RepositoryCollaborator + + + + + + + + + + + + + + + + + + + UserName + + + + $_.permissions.admin + + + + + $_.permissions.push + + + + + $_.permissions.pull + + + + + + + + + + GitHub.RepositoryTag + + GitHub.RepositoryTag + + + + + + + + + + + + + name + + + + $_.commit.sha + + + + + + + + + diff --git a/GitHubRepositories.ps1 b/GitHubRepositories.ps1 index 50849b7a..f5c0d8f4 100644 --- a/GitHubRepositories.ps1 +++ b/GitHubRepositories.ps1 @@ -4,6 +4,8 @@ @{ GitHubRepositoryTypeName = 'GitHub.Repository' GitHubRepositoryTopicTypeName = 'GitHub.RepositoryTopic' + GitHubRepositoryContributorTypeName = 'GitHub.RepositoryContributor' + GitHubRepositoryCollaboratorTypeName = 'GitHub.RepositoryCollaborator' GitHubRepositoryContributorStatisticsTypeName = 'GitHub.RepositoryContributorStatistics' GitHubRepositoryLanguageTypeName = 'GitHub.RepositoryLanguage' GitHubRepositoryTagTypeName = 'GitHub.RepositoryTag' @@ -1606,7 +1608,7 @@ filter Get-GitHubRepositoryContributor [CmdletBinding( SupportsShouldProcess, DefaultParameterSetName='Elements')] - [OutputType({$script:GitHubUserTypeName})] + [OutputType({$script:GitHubRepositoryContributorTypeName})] [OutputType({$script:GitHubRepositoryContributorStatisticsTypeName})] [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSShouldProcess", "", Justification="Methods called within here make use of PSShouldProcess, and the switch is passed on to them inherently.")] [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSReviewUnusedParameter", "", Justification="One or more parameters (like NoStatus) are only referenced by helper methods which get access to it from the stack via Get-Variable -Scope 1.")] @@ -1679,7 +1681,7 @@ filter Get-GitHubRepositoryContributor } else { - $results = $results | Add-GitHubUserAdditionalProperties + $results = $results | Add-GitHubRepositoryContributorAdditionalProperties } return $results @@ -1753,7 +1755,7 @@ filter Get-GitHubRepositoryCollaborator [CmdletBinding( SupportsShouldProcess, DefaultParameterSetName='Elements')] - [OutputType({$script:GitHubUserTypeName})] + [OutputType({$script:GitHubRepositoryCollaboratorTypeName})] [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSShouldProcess", "", Justification="Methods called within here make use of PSShouldProcess, and the switch is passed on to them inherently.")] [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSReviewUnusedParameter", "", Justification="One or more parameters (like NoStatus) are only referenced by helper methods which get access to it from the stack via Get-Variable -Scope 1.")] param( @@ -1802,7 +1804,8 @@ filter Get-GitHubRepositoryCollaborator 'NoStatus' = (Resolve-ParameterWithDefaultConfigurationValue -Name NoStatus -ConfigValueName DefaultNoStatus) } - return (Invoke-GHRestMethodMultipleResult @params | Add-GitHubUserAdditionalProperties) + return (Invoke-GHRestMethodMultipleResult @params | + Add-GitHubRepositoryCollaboratorAdditionalProperties) } filter Get-GitHubRepositoryLanguage @@ -2850,3 +2853,189 @@ filter Add-GitHubRepositoryAdditionalProperties Write-Output $item } } + +filter Add-GitHubRepositoryContributorAdditionalProperties +{ +<# + .SYNOPSIS + Adds type name and additional properties to ease pipelining to GitHub Contributor objects. + + .PARAMETER InputObject + The GitHub object to add additional properties to. + + .PARAMETER TypeName + The type that should be assigned to the object. + + .PARAMETER Name + The name of the Contributor. This information might be obtainable from InputObject, so this + is optional based on what InputObject contains. + + .PARAMETER Id + The ID of the Contributor. This information might be obtainable from InputObject, so this + is optional based on what InputObject contains. + + .INPUTS + PSCustomObject + + .OUTPUTS + GitHub.RepositoryContributor +#> + [CmdletBinding()] + [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseSingularNouns', '', + Justification='Internal helper that is definitely adding more than one property.')] + param( + [Parameter( + Mandatory, + ValueFromPipeline)] + [AllowNull()] + [AllowEmptyCollection()] + [PSCustomObject[]] $InputObject, + + [ValidateNotNullOrEmpty()] + [string] $TypeName = $script:GitHubRepositoryContributorTypeName, + + [string] $Name, + + [int64] $Id + ) + + foreach ($item in $InputObject) + { + $item.PSObject.TypeNames.Insert(0, $TypeName) + if (-not (Get-GitHubConfiguration -Name DisablePipelineSupport)) + { + $UserName = $item.login + if ([String]::IsNullOrEmpty($UserName) -and $PSBoundParameters.ContainsKey('Name')) + { + $UserName = $Name + } + + if (-not [String]::IsNullOrEmpty($UserName)) + { + $addMemberParms = @{ + InputObject = $item + Name = 'UserName' + Value = $UserName + MemberType = 'NoteProperty' + Force = $true + } + Add-Member @addMemberParms + } + + $UserId = $item.id + if (($UserId -eq 0) -and $PSBoundParameters.ContainsKey('Id')) + { + $UserId = $Id + } + + if ($UserId -ne 0) + { + $addMemberParms = @{ + InputObject = $item + Name = 'UserId' + Value = $UserId + MemberType = 'NoteProperty' + Force = $true + } + + Add-Member @addMemberParms + } + } + + Write-Output $item + } +} + +filter Add-GitHubRepositoryCollaboratorAdditionalProperties +{ +<# + .SYNOPSIS + Adds type name and additional properties to ease pipelining to GitHub Collaborator objects. + + .PARAMETER InputObject + The GitHub object to add additional properties to. + + .PARAMETER TypeName + The type that should be assigned to the object. + + .PARAMETER Name + The name of the Collaborator. This information might be obtainable from InputObject, so this + is optional based on what InputObject contains. + + .PARAMETER Id + The ID of the Collaborator. This information might be obtainable from InputObject, so this + is optional based on what InputObject contains. + + .INPUTS + PSCustomObject + + .OUTPUTS + GitHub.RepositoryCollaborator +#> + [CmdletBinding()] + [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseSingularNouns', '', + Justification='Internal helper that is definitely adding more than one property.')] + param( + [Parameter( + Mandatory, + ValueFromPipeline)] + [AllowNull()] + [AllowEmptyCollection()] + [PSCustomObject[]] $InputObject, + + [ValidateNotNullOrEmpty()] + [string] $TypeName = $script:GitHubRepositoryCollaboratorTypeName, + + [string] $Name, + + [int64] $Id + ) + + foreach ($item in $InputObject) + { + $item.PSObject.TypeNames.Insert(0, $TypeName) + + if (-not (Get-GitHubConfiguration -Name DisablePipelineSupport)) + { + $userName = $item.login + if ([String]::IsNullOrEmpty($userName) -and $PSBoundParameters.ContainsKey('Name')) + { + $userName = $Name + } + + if (-not [String]::IsNullOrEmpty($userName)) + { + $addMemberParms = @{ + InputObject = $item + Name = 'UserName' + Value = $userName + MemberType = 'NoteProperty' + Force = $true + } + + Add-Member @addMemberParms + } + + $userId = $item.id + if (($userId -eq 0) -and $PSBoundParameters.ContainsKey('Id')) + { + $userId = $Id + } + + if ($userId -ne 0) + { + $addMemberParms = @{ + InputObject = $item + Name = 'UserId' + Value = $userId + MemberType = 'NoteProperty' + Force = $true + } + + Add-Member @addMemberParms + } + } + + Write-Output $item + } +} diff --git a/PowerShellForGitHub.psd1 b/PowerShellForGitHub.psd1 index 3504bf7c..b49ff1f0 100644 --- a/PowerShellForGitHub.psd1 +++ b/PowerShellForGitHub.psd1 @@ -13,6 +13,11 @@ # Script module or binary module file associated with this manifest. RootModule = 'PowerShellForGitHub.psm1' + # Format files (.ps1xml) to be loaded when importing this module + FormatsToProcess = @( + 'Formatters/GitHubRepositories.Format.ps1xml' + ) + # Modules to import as nested modules of the module specified in RootModule/ModuleToProcess NestedModules = @( # Ideally this list would be kept completely alphabetical, but other scripts (like diff --git a/Tests/GitHubRepositories.tests.ps1 b/Tests/GitHubRepositories.tests.ps1 index 6575f249..96e90cff 100644 --- a/Tests/GitHubRepositories.tests.ps1 +++ b/Tests/GitHubRepositories.tests.ps1 @@ -840,7 +840,7 @@ try It 'Should return expected number of contributors' { $contributors.Count | Should -Be 1 - $contributors[0].PSObject.TypeNames[0] = 'GitHub.User' + $contributors[0].PSObject.TypeNames[0] = 'GitHub.RepositoryContributor' } } @@ -849,7 +849,7 @@ try It 'Should return expected number of contributors' { $contributors.Count | Should -Be 1 - $contributors[0].PSObject.TypeNames[0] = 'GitHub.User' + $contributors[0].PSObject.TypeNames[0] = 'GitHub.RepositoryContributor' } } @@ -878,7 +878,7 @@ try It 'Should return expected number of collaborators' { $collaborators.Count | Should -Be 1 - $collaborators[0].PSObject.TypeNames[0] = 'GitHub.User' + $collaborators[0].PSObject.TypeNames[0] = 'GitHub.RepositoryCollaborator' } } @@ -887,7 +887,7 @@ try It 'Should return expected number of collaborators' { $collaborators.Count | Should -Be 1 - $collaborators[0].PSObject.TypeNames[0] = 'GitHub.User' + $collaborators[0].PSObject.TypeNames[0] = 'GitHub.RepositoryCollaborator' } } } From 19c84174299e4f7b9f5c77d4b9c9bad54aaf5aa8 Mon Sep 17 00:00:00 2001 From: Simon Heather <32168619+X-Guardian@users.noreply.github.com> Date: Sun, 28 Jun 2020 16:31:10 +0100 Subject: [PATCH 43/60] Update repo name in GitHubRepositoryForks tests (#251) This PR updates the upstream Owner and Repo name used in the GitHubRepositoryForks tests to cause less destruction. --- Tests/GitHubRepositoryForks.tests.ps1 | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Tests/GitHubRepositoryForks.tests.ps1 b/Tests/GitHubRepositoryForks.tests.ps1 index a7f6d37f..a44b1466 100644 --- a/Tests/GitHubRepositoryForks.tests.ps1 +++ b/Tests/GitHubRepositoryForks.tests.ps1 @@ -19,8 +19,8 @@ try { # Define Script-scoped, readonly, hidden variables. @{ - upstreamOwnerName = 'microsoft' - upstreamRepositoryName = 'PowerShellForGitHub' + upstreamOwnerName = 'octocat' + upstreamRepositoryName = 'Hello-World' }.GetEnumerator() | ForEach-Object { Set-Variable -Force -Scope Script -Option ReadOnly -Visibility Private -Name $_.Key -Value $_.Value } From eedfaa3740ac5330128fea27038f213c8abf1d4b Mon Sep 17 00:00:00 2001 From: Simon Heather <32168619+X-Guardian@users.noreply.github.com> Date: Sun, 28 Jun 2020 16:34:40 +0100 Subject: [PATCH 44/60] Update GitHubRepositories Tests and Refactor Get-GitHubRepository Function (#233) Updates the Pester tests for the `GitHubRepositories` module to increase the code coverage. This also: * Refactors `Get-GithubRepository` to remove a code path that could never be taken * Adds a `ValidateSet` to the `Affiliation` parameter of the `Get-GitHubRepository` function * Fixes the Comment Based Help descriptions for the `Get-GitHubRepositoryCollaborator` and `Move-GitHubRepositoryOwnership` --- GitHubRepositories.ps1 | 101 +++--- Tests/GitHubRepositories.tests.ps1 | 521 ++++++++++++++++++++++------- 2 files changed, 456 insertions(+), 166 deletions(-) diff --git a/GitHubRepositories.ps1 b/GitHubRepositories.ps1 index f5c0d8f4..7a2c2e04 100644 --- a/GitHubRepositories.ps1 +++ b/GitHubRepositories.ps1 @@ -658,8 +658,10 @@ filter Get-GitHubRepository SupportsShouldProcess, DefaultParameterSetName='AuthenticatedUser')] [OutputType({$script:GitHubRepositoryTypeName})] - [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSShouldProcess", "", Justification="Methods called within here make use of PSShouldProcess, and the switch is passed on to them inherently.")] - [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSReviewUnusedParameter", "", Justification="One or more parameters (like NoStatus) are only referenced by helper methods which get access to it from the stack via Get-Variable -Scope 1.")] + [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSShouldProcess", "", + Justification="Methods called within here make use of PSShouldProcess, and the switch is passed on to them inherently.")] + [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSReviewUnusedParameter", "", + Justification="One or more parameters (like NoStatus) are only referenced by helper methods which get access to it from the stack via Get-Variable -Scope 1.")] param( [Parameter( ValueFromPipelineByPropertyName, @@ -687,6 +689,7 @@ filter Get-GitHubRepository [string] $Visibility, [Parameter(ParameterSetName='AuthenticatedUser')] + [ValidateSet('Owner', 'Collaborator', 'OrganizationMember')] [string[]] $Affiliation, [Parameter(ParameterSetName='AuthenticatedUser')] @@ -736,11 +739,11 @@ filter Get-GitHubRepository $description = [String]::Empty switch ($PSCmdlet.ParameterSetName) { - { ('ElementsOrUser', 'Uri') -contains $_ } { + 'ElementsOrUser' { # This is a little tricky. Ideally we'd have two separate ParameterSets (Elements, User), # however PowerShell would be unable to disambiguate between the two, so unfortunately # we need to do some additional work here. And because fallthru doesn't appear to be - # working right, we're combining both of those, along with Uri. + # working right, we're combining both of those. if ([String]::IsNullOrWhiteSpace($OwnerName)) { @@ -750,37 +753,24 @@ filter Get-GitHubRepository } elseif ([String]::IsNullOrWhiteSpace($RepositoryName)) { - if ($PSCmdlet.ParameterSetName -eq 'ElementsOrUser') - { - $telemetryProperties['UsageType'] = 'User' - $telemetryProperties['OwnerName'] = Get-PiiSafeString -PlainText $OwnerName + $telemetryProperties['UsageType'] = 'User' + $telemetryProperties['OwnerName'] = Get-PiiSafeString -PlainText $OwnerName - $uriFragment = "users/$OwnerName/repos" - $description = "Getting repos for $OwnerName" - } - else - { - $message = 'RepositoryName could not be determined.' - Write-Log -Message $message -Level Error - throw $message - } + $uriFragment = "users/$OwnerName/repos" + $description = "Getting repos for $OwnerName" } else { - if ($PSCmdlet.ParameterSetName -eq 'ElementsOrUser') + if ($PSBoundParameters.ContainsKey('Type') -or + $PSBoundParameters.ContainsKey('Sort') -or + $PSBoundParameters.ContainsKey('Direction')) { - $telemetryProperties['UsageType'] = 'Elements' - - if ($PSBoundParameters.ContainsKey('Type') -or - $PSBoundParameters.ContainsKey('Sort') -or - $PSBoundParameters.ContainsKey('Direction')) - { - $message = 'Unable to specify -Type, -Sort and/or -Direction when retrieving a specific repository.' - Write-Log -Message $message -Level Error - throw $message - } + $message = 'Unable to specify -Type, -Sort and/or -Direction when retrieving a specific repository.' + Write-Log -Message $message -Level Error + throw $message } + $telemetryProperties['UsageType'] = 'Elements' $telemetryProperties['OwnerName'] = Get-PiiSafeString -PlainText $OwnerName $telemetryProperties['RepositoryName'] = Get-PiiSafeString -PlainText $RepositoryName @@ -791,20 +781,30 @@ filter Get-GitHubRepository break } - 'Organization' { - $telemetryProperties['OrganizationName'] = Get-PiiSafeString -PlainText $OrganizationName + 'Uri' { + if ($PSBoundParameters.ContainsKey('Type') -or + $PSBoundParameters.ContainsKey('Sort') -or + $PSBoundParameters.ContainsKey('Direction')) + { + $message = 'Unable to specify -Type, -Sort and/or -Direction when retrieving a specific repository.' + Write-Log -Message $message -Level Error + throw $message + } - $uriFragment = "orgs/$OrganizationName/repos" - $description = "Getting repos for $OrganizationName" + $telemetryProperties['OwnerName'] = Get-PiiSafeString -PlainText $OwnerName + $telemetryProperties['RepositoryName'] = Get-PiiSafeString -PlainText $RepositoryName + + $uriFragment = "repos/$OwnerName/$RepositoryName" + $description = "Getting $OwnerName/$RepositoryName" break } - 'User' { - $telemetryProperties['OwnerName'] = Get-PiiSafeString -PlainText $OwnerName + 'Organization' { + $telemetryProperties['OrganizationName'] = Get-PiiSafeString -PlainText $OrganizationName - $uriFragment = "users/$OwnerName/repos" - $description = "Getting repos for $OwnerName" + $uriFragment = "orgs/$OrganizationName/repos" + $description = "Getting repos for $OrganizationName" break } @@ -857,7 +857,18 @@ filter Get-GitHubRepository if ($PSBoundParameters.ContainsKey('Direction')) { $getParams += "direction=$($directionConverter[$Direction])" } if ($PSBoundParameters.ContainsKey('Affiliation') -and $Affiliation.Count -gt 0) { - $getParams += "affiliation=$($Affiliation -join ',')" + $affiliationMap = @{ + Owner = 'owner' + Collaborator = 'collaborator' + OrganizationMember = 'organization_member' + } + $affiliationParam = @() + + foreach ($member in $Affiliation) + { + $affiliationParam += $affiliationMap[$member] + } + $getParams += "affiliation=$($affiliationParam -join ',')" } if ($PSBoundParameters.ContainsKey('Since')) { $getParams += "since=$Since" } @@ -1602,8 +1613,12 @@ filter Get-GitHubRepositoryContributor .EXAMPLE Get-GitHubRepositoryContributor -OwnerName microsoft -RepositoryName PowerShellForGitHub + Gets a list of contributors for the PowerShellForGithub repository. + .EXAMPLE Get-GitHubRepositoryContributor -Uri 'https://github.com/PowerShell/PowerShellForGitHub' -IncludeStatistics + + Gets a list of contributors for the PowerShellForGithub repository including statistics. #> [CmdletBinding( SupportsShouldProcess, @@ -1691,10 +1706,10 @@ filter Get-GitHubRepositoryCollaborator { <# .SYNOPSIS - Retrieve list of contributors for a given repository. + Retrieve list of collaborators for a given repository. .DESCRIPTION - Retrieve list of contributors for a given repository. + Retrieve list of collaborators for a given repository. The Git repo for this module can be found here: http://aka.ms/PowerShellForGitHub @@ -1749,8 +1764,12 @@ filter Get-GitHubRepositoryCollaborator .EXAMPLE Get-GitHubRepositoryCollaborator -OwnerName microsoft -RepositoryName PowerShellForGitHub + Gets a list of collaborators for the PowerShellForGithub repository. + .EXAMPLE Get-GitHubRepositoryCollaborator -Uri 'https://github.com/PowerShell/PowerShellForGitHub' + + Gets a list of collaborators for the PowerShellForGithub repository. #> [CmdletBinding( SupportsShouldProcess, @@ -2027,10 +2046,10 @@ filter Move-GitHubRepositoryOwnership { <# .SYNOPSIS - Creates a new repository on GitHub. + Changes the ownership of a repository on GitHub. .DESCRIPTION - Creates a new repository on GitHub. + Changes the ownership of a repository on GitHub. The Git repo for this module can be found here: http://aka.ms/PowerShellForGitHub diff --git a/Tests/GitHubRepositories.tests.ps1 b/Tests/GitHubRepositories.tests.ps1 index 96e90cff..37bd6983 100644 --- a/Tests/GitHubRepositories.tests.ps1 +++ b/Tests/GitHubRepositories.tests.ps1 @@ -368,99 +368,259 @@ try } } - Describe 'Getting repositories' { - Context 'For authenticated user' { - BeforeAll -Scriptblock { - $publicRepo = New-GitHubRepository -RepositoryName ([Guid]::NewGuid().Guid) -AutoInit - $privateRepo = New-GitHubRepository -RepositoryName ([Guid]::NewGuid().Guid) -AutoInit -Private + Describe 'GitHubRepositories\Get-GitHubRepository' { + Context 'When getting a repository for the authenticated user' { + BeforeAll { + $publicRepo = New-GitHubRepository -RepositoryName ([Guid]::NewGuid().Guid) + $privateRepo = New-GitHubRepository -RepositoryName ([Guid]::NewGuid().Guid) -Private + } + + Context 'When specify the visibility parameter' { + BeforeAll { + $publicRepos = Get-GitHubRepository -Visibility Public + $privateRepos = Get-GitHubRepository -Visibility Private + } + + It 'Should return objects of the correct type' { + $publicRepos[0].PSObject.TypeNames[0] | Should -Be 'GitHub.Repository' + $privateRepos[0].PSObject.TypeNames[0] | Should -Be 'GitHub.Repository' + } + + It "Should return the correct membership" { + $publicRepo.name | Should -BeIn $publicRepos.name + $publicRepo.name | Should -Not -BeIn $privateRepos.name + $privateRepo.name | Should -BeIn $privateRepos.name + $privateRepo.name | Should -Not -BeIn $publicRepos.name + } + } + + Context 'When specifying the Type parameter' { + BeforeAll { + $publicRepos = Get-GitHubRepository -Type Public + $privateRepos = Get-GitHubRepository -Type Private + $ownerRepos = Get-GitHubRepository -Type Owner + $allRepos = Get-GitHubRepository -Type All + } + + It 'Should return objects of the correct type' { + $publicRepos[0].PSObject.TypeNames[0] | Should -Be 'GitHub.Repository' + $publicRepos[0].PSObject.TypeNames[0] | Should -Be 'GitHub.Repository' + $ownerRepos[0].PSObject.TypeNames[0] | Should -Be 'GitHub.Repository' + } + + It "Should return the correct membership" { + $publicRepo.name | Should -BeIn $publicRepos.name + $publicRepo.name | Should -Not -BeIn $privateRepos.name + $privateRepo.name | Should -BeIn $privateRepos.name + $privateRepo.name | Should -Not -BeIn $publicRepos.name + $publicRepo.name | Should -BeIn $ownerRepos.name + $privateRepo.name | Should -BeIn $ownerRepos.name + $publicRepo.name | Should -BeIn $allRepos.name + $privateRepo.name | Should -BeIn $allRepos.name + } } - It "Should have the public repo" { - $publicRepos = @(Get-GitHubRepository -Visibility Public) - $privateRepos = @(Get-GitHubRepository -Visibility Private) - $publicRepo.name | Should -BeIn $publicRepos.name - $publicRepo.name | Should -Not -BeIn $privateRepos.name + Context 'When specifying the Affiliation parameter' { + BeforeAll { + $ownerRepos = Get-GitHubRepository -Affiliation Owner, Collaborator + } + + It 'Should return objects of the correct type' { + $ownerRepos[0].PSObject.TypeNames[0] | Should -Be 'GitHub.Repository' + } + + It "Should return the correct membership" { + $publicRepo.name | Should -BeIn $ownerRepos.name + $privateRepo.name | Should -BeIn $ownerRepos.name + } } - It "Should have the private repo" { - $publicRepos = @(Get-GitHubRepository -Visibility Public) - $privateRepos = @(Get-GitHubRepository -Visibility Private) - $privateRepo.name | Should -BeIn $privateRepos.name - $privateRepo.name | Should -Not -BeIn $publicRepos.name + Context 'When specifying the Sort and Direction parameters' { + BeforeAll { + $sortedRepos = Get-GitHubRepository -Sort 'FullName' + $sortedDescendingRepos = Get-GitHubRepository -Sort FullName -Direction Descending + + $sortedRepoFullNames = [System.Collections.ArrayList]$sortedRepos.full_Name + $sortedRepoFullNames.Sort([System.StringComparer]::OrdinalIgnoreCase) + $sortedDescendingRepoFullNames = [System.Collections.ArrayList]$sortedDescendingRepos.full_Name + $sortedDescendingRepoFullNames.Sort([System.StringComparer]::OrdinalIgnoreCase) + $sortedDescendingRepoFullNames.Reverse() + } + + It 'Should return objects of the correct type' { + $sortedRepos[0].PSObject.TypeNames[0] | Should -Be 'GitHub.Repository' + $sortedDescendingRepos[0].PSObject.TypeNames[0] | Should -Be 'GitHub.Repository' + } + + It "Should return the correct membership order" { + for ($i = 1; $i -le $sortedRepos.count; $i++) { + $sortedRepos[$i].full_name | Should -Be $sortedRepoFullNames[$i] + $sortedDescendingRepos[$i].full_name | Should -Be $sortedDescendingRepoFullNames[$i] + } + } } - It 'Should not permit bad combination of parameters' { - { Get-GitHubRepository -Type All -Visibility All } | Should -Throw - { Get-GitHubRepository -Type All -Affiliation Owner } | Should -Throw + Context 'When Specifying an invalid Visibility parameter set' { + It 'Should throw the correct exception' { + $errorMessage = 'Unable to specify -Type when using -Visibility and/or -Affiliation.' + { Get-GitHubRepository -Type All -Visibility All } | Should -Throw $errorMessage + } } - AfterAll -ScriptBlock { - Remove-GitHubRepository -Uri $publicRepo.svn_url -Confirm:$false - Remove-GitHubRepository -Uri $privateRepo.svn_url -Confirm:$false + Context 'When Specifying an invalid Affiliation parameter set' { + It 'Should throw the correct exception' { + $errorMessage = 'Unable to specify -Type when using -Visibility and/or -Affiliation.' + { Get-GitHubRepository -Type All -Visibility All } | Should -Throw $errorMessage + } + } + + AfterAll { + Remove-GitHubRepository -Uri $publicRepo.svn_url -Force + Remove-GitHubRepository -Uri $privateRepo.svn_url -Force } } - Context 'For any user' { - It "Should have results for The Octocat" { - $repos = @(Get-GitHubRepository -OwnerName 'octocat' -Type Public) - $repos.Count | Should -BeGreaterThan 0 - $repos[0].owner.login | Should -Be 'octocat' + Context 'When getting a repository for a specified owner' { + BeforeAll { + $ownerName = 'octocat' + $repos = Get-GitHubRepository -OwnerName $ownerName + } + + It 'Should return objects of the correct type' { + $repos | Should -BeOfType PSCustomObject + } + + It "Should return one or more results" { + $repos.Count | Should -BeGreaterOrEqual 1 + } + + It 'Should return the correct properties' { + foreach ($repo in $repos) { + $repo.owner.login | Should -Be $ownerName + } } } - Context 'For organizations' { - BeforeAll -Scriptblock { - $repo = New-GitHubRepository -OrganizationName $script:organizationName -RepositoryName ([Guid]::NewGuid().Guid) -AutoInit + Context 'When getting a repository for a specified organization' { + BeforeAll { + $repo = New-GitHubRepository -OrganizationName $script:organizationName -RepositoryName ([Guid]::NewGuid().Guid) } It "Should have results for the organization" { - $repos = @(Get-GitHubRepository -OrganizationName $script:organizationName -Type All) + $repos = Get-GitHubRepository -OrganizationName $script:organizationName -Type All $repo.name | Should -BeIn $repos.name } - AfterAll -ScriptBlock { + AfterAll { Remove-GitHubRepository -Uri $repo.svn_url -Confirm:$false } } - Context 'For public repos' { - # Skipping these tests for now, as it would run for a _very_ long time. - # No obviously good way to verify this. - } + Context 'When getting all public repositories' { + BeforeAll { + $repo1 = New-GitHubRepository -RepositoryName ([Guid]::NewGuid().Guid) + $repo2 = New-GitHubRepository -RepositoryName ([Guid]::NewGuid().Guid) - Context 'For a specific repo' { - BeforeAll -ScriptBlock { - $repo = New-GitHubRepository -RepositoryName ([Guid]::NewGuid().Guid) -AutoInit + $repos = Get-GitHubRepository -GetAllPublicRepositories -Since $repo1.id } - It "Should be a single result using Uri ParameterSet" { - $result = Get-GitHubRepository -Uri $repo.svn_url - $result | Should -BeOfType PSCustomObject + It 'Should return an object of the correct type' { + $repos[0].PSObject.TypeNames[0] | Should -Be 'GitHub.Repository' } - It "Should be a single result using Elements ParameterSet" { - $result = Get-GitHubRepository -OwnerName $repo.owner.login -RepositoryName $repo.name - $result | Should -BeOfType PSCustomObject + It 'Should return at least one result' { + $repos.count | Should -BeGreaterOrEqual 1 } - It 'Should not permit additional parameters' { - { Get-GitHubRepository -OwnerName $repo.owner.login -RepositoryName $repo.name -Type All } | Should -Throw + It "Should return the correct membership" { + $repo2.name | Should -BeIn $repos.name } - It 'Should require both OwnerName and RepositoryName' { - { Get-GitHubRepository -RepositoryName $repo.name } | Should -Throw - { Get-GitHubRepository -Uri "https://github.com/$script:ownerName" } | Should -Throw + AfterAll { + Remove-GitHubRepository -Uri $repo1.svn_url -Force + Remove-GitHubRepository -Uri $repo2.svn_url -Force } + } - AfterAll -ScriptBlock { - Remove-GitHubRepository -Uri $repo.svn_url -Confirm:$false + Context 'When getting a specific repository' { + BeforeAll { + $repoName = [Guid]::NewGuid().Guid + $newGitHubRepositoryParms = @{ + RepositoryName = $repoName + Description = $defaultRepoDesc + HomePage = $defaultRepoHomePage + } + + $repo = New-GitHubRepository @newGitHubRepositoryParms + } + + Context 'When specifiying the Uri parameter' { + BeforeAll { + $uriRepo = Get-GitHubRepository -Uri $repo.svn_url + } + + It 'Should return an object of the correct type' { + $uriRepo.PSObject.TypeNames[0] | Should -Be 'GitHub.Repository' + } + + It 'Should return a single result' { + $uriRepo | Should -HaveCount 1 + } + + It 'Should return the correct properties' { + $uriRepo.name | Should -Be $repoName + $uriRepo.description | Should -Be $defaultRepoDesc + $uriRepo.homepage | Should -Be $defaultRepoHomePage + } + } + + Context 'When specifying the Owner and RepositoryName parameters' { + BeforeAll { + $elementsRepo = Get-GitHubRepository -OwnerName $repo.owner.login -RepositoryName $repo.name + } + + It 'Should return an object of the correct type' { + $uriRepo.PSObject.TypeNames[0] | Should -Be 'GitHub.Repository' + } + + It 'Should return a single result' { + $uriRepo | Should -HaveCount 1 + } + + It 'Should return the correct properties' { + $uriRepo.name | Should -Be $repoName + $uriRepo.description | Should -Be $defaultRepoDesc + $uriRepo.homepage | Should -Be $defaultRepoHomePage + } + + Context 'When specifying additional invalid parameters' { + It 'Should throw the correct exception' { + $errorMessage = 'Unable to specify -Type, -Sort and/or -Direction when retrieving a specific repository.' + { Get-GitHubRepository -OwnerName $repo.owner.login -RepositoryName $repo.name -Type All } | + Should -Throw $errorMessage + } + } + } + + Context 'When specifying only the Repository parameter' { + It 'Should throw the correct exception' { + $errorMessage = 'OwnerName could not be determined.' + { Get-GitHubRepository -RepositoryName $repo.name } | Should -Throw $errorMessage + } + } + + AfterAll { + if (Get-Variable -Name repo -ErrorAction SilentlyContinue) + { + Remove-GitHubRepository -Uri $repo.svn_url -Confirm:$false + } } } } - Describe 'Deleting repositories' { + Describe 'GitHubRepositories\Delete-GitHubRepository' { - Context -Name 'For deleting a repository' -Fixture { + Context -Name 'When deleting a repository' -Fixture { BeforeEach -ScriptBlock { $repo = New-GitHubRepository -RepositoryName ([Guid]::NewGuid().Guid) -Description $defaultRepoDesc -AutoInit } @@ -477,9 +637,9 @@ try } } - Describe 'Renaming repositories' { + Describe 'GitHubRepositories\Rename-GitHubRepository' { - Context -Name 'For renaming a repository' -Fixture { + Context -Name 'When renaming a repository' -Fixture { BeforeEach -Scriptblock { $repo = New-GitHubRepository -RepositoryName ([Guid]::NewGuid().Guid) -AutoInit $suffixToAddToRepo = "_renamed" @@ -607,7 +767,7 @@ try } AfterAll -ScriptBlock { - if ($repo) + if (Get-Variable -Name repo -ErrorAction SilentlyContinue) { Remove-GitHubRepository -Uri $repo.svn_url -Confirm:$false } @@ -713,11 +873,14 @@ try } } - Describe 'Get/set repository topic' { + Describe 'GitHubRepositories\Get-GitHubRepositoryTopic' { - Context -Name 'For creating and getting a repository topic' -Fixture { - BeforeAll -ScriptBlock { - $repo = New-GitHubRepository -RepositoryName ([Guid]::NewGuid().Guid) -AutoInit + Context -Name 'When getting a repository topic' { + BeforeAll { + $repo = New-GitHubRepository -RepositoryName ([Guid]::NewGuid().Guid) + Set-GitHubRepositoryTopic -OwnerName $repo.owner.login -RepositoryName $repo.name -Name $defaultRepoTopic | + Out-Null + $topic = Get-GitHubRepositoryTopic -OwnerName $repo.owner.login -RepositoryName $repo.name } It 'Should have the expected topic' { @@ -766,128 +929,236 @@ try } } - AfterAll -ScriptBlock { + AfterAll { Remove-GitHubRepository -Uri $repo.svn_url -Confirm:$false } } } - Describe 'Get repository languages' { + Describe 'GitHubRepositories\Set-GitHubRepositoryTopic' { + BeforeAll { + $repo = New-GitHubRepository -RepositoryName ([Guid]::NewGuid().Guid) + $topic = Set-GitHubRepositoryTopic -OwnerName $repo.owner.login -RepositoryName $repo.name -Name $defaultRepoTopic + } - Context -Name 'For getting repository languages' -Fixture { - BeforeAll -ScriptBlock { - $repo = New-GitHubRepository -RepositoryName ([Guid]::NewGuid().Guid) -AutoInit + Context -Name 'When setting a repository topic' { + It 'Should return an object of the correct type' { + $topic | Should -BeOfType PSCustomObject } - It 'Should be empty' { - $languages = Get-GitHubRepositoryLanguage -OwnerName $repo.owner.login -RepositoryName $repo.name - $languages | Should -BeNullOrEmpty + It 'Should return the correct properties' { + $defaultRepoTopic | Should -BeIn $topic.names } + } - It 'Should contain PowerShell' { - $languages = Get-GitHubRepositoryLanguage -OwnerName "microsoft" -RepositoryName "PowerShellForGitHub" - $languages.PowerShell | Should -Not -BeNullOrEmpty - $languages.PSObject.TypeNames[0] | Should -Be 'GitHub.RepositoryLanguage' + Context -Name 'When clearing all repository topics' { + BeforeAll { + $topic = Set-GitHubRepositoryTopic -OwnerName $repo.owner.login -RepositoryName $repo.name -Clear } - It 'Should contain PowerShell (via pipeline)' { - $psfg = Get-GitHubRepository -OwnerName "microsoft" -RepositoryName "PowerShellForGitHub" - $languages = $psfg | Get-GitHubRepositoryLanguage - $languages.PowerShell | Should -Not -BeNullOrEmpty - $languages.PSObject.TypeNames[0] | Should -Be 'GitHub.RepositoryLanguage' + It 'Should return an object of the correct type' { + $topic.PSObject.TypeNames[0] | Should -Be 'GitHub.RepositoryTopic' } - AfterAll -ScriptBlock { - Remove-GitHubRepository -Uri $repo.svn_url -Force + It 'Should return the correct properties' { + $topic.names | Should -BeNullOrEmpty } } + + AfterAll { + Remove-GitHubRepository -Uri $repo.svn_url -Confirm:$false + } } - Describe 'Get repository tags' { + Describe 'GitHubRepositories\Get-GitHubRepositoryContributor' { + BeforeAll { + $repoName = [Guid]::NewGuid().Guid + $repo = New-GitHubRepository -RepositoryName $repoName -AutoInit + } - Context -Name 'For getting repository tags' -Fixture { - BeforeAll -ScriptBlock { - $repo = New-GitHubRepository -RepositoryName ([Guid]::NewGuid().Guid) -AutoInit + Context 'When getting GitHub Repository Contributors' { + BeforeAll { + $getGitHubRepositoryContributorParms = @{ + OwnerName = $repo.owner.login + RepositoryName = $repoName + } + + $contributors = @(Get-GitHubRepositoryContributor @getGitHubRepositoryContributorParms) } - It 'Should be empty' { - $tags = Get-GitHubRepositoryTag -OwnerName $repo.owner.login -RepositoryName $repo.name - $tags | Should -BeNullOrEmpty + It 'Should return objects of the correct type' { + $contributors[0].PSObject.TypeNames[0] | Should -Be 'GitHub.RepositoryContributor' } - It 'Should be empty (via pipeline)' { - $tags = $repo | Get-GitHubRepositoryTag - $tags | Should -BeNullOrEmpty + It 'Should return expected number of contributors' { + $contributors.Count | Should -Be 1 } - AfterAll -ScriptBlock { - Remove-GitHubRepository -Uri $repo.svn_url -Confirm:$false + It "Should return the correct membership" { + $repo.owner.login | Should -BeIn $contributors.login } } - } - Describe 'Contributors for a repository' { - BeforeAll { - $repo = New-GitHubRepository -RepositoryName ([guid]::NewGuid().Guid) -AutoInit - } + Context 'When getting Github Repository Contributors with Statistics' { + BeforeAll { + $getGitHubRepositoryContributorParms = @{ + OwnerName = $repo.owner.login + RepositoryName = $repoName + IncludeStatistics = $true + } - AfterAll { - $null = Remove-GitHubRepository -Uri $repo.RepositoryUrl -Confirm:$false - } + $contributors = @(Get-GitHubRepositoryContributor @getGitHubRepositoryContributorParms) + } - Context -Name 'Obtaining contributors for repository' -Fixture { - $contributors = @(Get-GitHubRepositoryContributor -Uri $repo.RepositoryUrl) + It 'Should return objects of the correct type' { + $contributors[0].PSObject.TypeNames[0] | Should -Be 'GitHub.RepositoryContributorStatistics' + $contributors[0].author.PSObject.TypeNames[0] | Should -Be 'GitHub.User' + } It 'Should return expected number of contributors' { $contributors.Count | Should -Be 1 - $contributors[0].PSObject.TypeNames[0] = 'GitHub.RepositoryContributor' } - } - Context -Name 'Obtaining contributors for repository (via pipeline)' -Fixture { - $contributors = @($repo | Get-GitHubRepositoryContributor -IncludeStatistics) + It 'Should return the correct membership' { + $repo.owner.login | Should -BeIn $contributors.author.login + } - It 'Should return expected number of contributors' { - $contributors.Count | Should -Be 1 - $contributors[0].PSObject.TypeNames[0] = 'GitHub.RepositoryContributor' + It 'Should return the correct properties' { + $contributors.weeks | Should -Not -BeNullOrEmpty } } - Context -Name 'Obtaining contributor statistics for repository' -Fixture { - $stats = @(Get-GitHubRepositoryContributor -Uri $repo.RepositoryUrl -IncludeStatistics) + Context 'When getting Github Repository Contributors including Anonymous' { + BeforeAll { + $getGitHubRepositoryContributorParms = @{ + OwnerName = $repo.owner.login + RepositoryName = $repoName + IncludeAnonymousContributors = $true + } + + $contributors = @(Get-GitHubRepositoryContributor @getGitHubRepositoryContributorParms) + } - It 'Should return expected number of contributors' { - $stats.Count | Should -Be 1 - $stats[0].PSObject.TypeNames[0] = 'GitHub.RepositoryContributorStatistics' - $stats[0].author.PSObject.TypeNames[0] = 'GitHub.User' + It 'Should return objects of the correct type' { + $contributors[0].PSObject.TypeNames[0] | Should -Be 'GitHub.RepositoryContributor' + } + + It 'Should return at least one result' { + $contributors.count | Should -BeGreaterOrEqual 1 + } + + It 'Should return the correct membership' { + $repo.owner.login | Should -BeIn $contributors.login } } + + AfterAll { + Remove-GitHubRepository -Uri $repo.svn_url -Confirm:$false + } } - Describe 'Collaborators for a repository' { + Describe 'GitHubRepositories\Get-GitHubRepositoryCollaborator' { BeforeAll { - $repo = New-GitHubRepository -RepositoryName ([guid]::NewGuid().Guid) -AutoInit + $repoName = [Guid]::NewGuid().Guid + $repo = New-GitHubRepository -RepositoryName $repoName -AutoInit } - AfterAll { - $null = Remove-GitHubRepository -Uri $repo.RepositoryUrl -Confirm:$false - } + Context 'When getting GitHub Repository Collaborators' { + BeforeAll { + $getGitHubRepositoryCollaboratorParms = @{ + OwnerName = $repo.owner.login + RepositoryName = $repoName + } - Context -Name 'Obtaining collaborators for repository' -Fixture { - $collaborators = @(Get-GitHubRepositoryCollaborator -Uri $repo.RepositoryUrl) + $collaborators = @(Get-GitHubRepositoryCollaborator @getGitHubRepositoryCollaboratorParms) + } + + It 'Should return objects of the correct type' { + $collaborators[0].PSObject.TypeNames[0] | Should -Be 'GitHub.RepositoryCollaborator' + } It 'Should return expected number of collaborators' { $collaborators.Count | Should -Be 1 - $collaborators[0].PSObject.TypeNames[0] = 'GitHub.RepositoryCollaborator' + } + + It "Should return the correct membership" { + $repo.owner.login | Should -BeIn $collaborators.login } } - Context -Name 'Obtaining collaborators for repository (via pipeline)' -Fixture { - $collaborators = @($repo | Get-GitHubRepositoryCollaborator) + Context 'When getting GitHub Repository Collaborators (via pipeline)' { + BeforeAll { + $collaborators = @($repo | Get-GitHubRepositoryCollaborator) + } + + It 'Should return objects of the correct type' { + $collaborators[0].PSObject.TypeNames[0] | Should -Be 'GitHub.RepositoryCollaborator' + } It 'Should return expected number of collaborators' { $collaborators.Count | Should -Be 1 - $collaborators[0].PSObject.TypeNames[0] = 'GitHub.RepositoryCollaborator' + } + + It "Should return the correct membership" { + $repo.owner.login | Should -BeIn $collaborators.login + } + } + + AfterAll { + Remove-GitHubRepository -Uri $repo.svn_url -Confirm:$false + } + } + + Describe 'GitHubRepositories\Get-GitHubRepositoryLanguage' { + + Context -Name 'When getting repository languages' { + BeforeAll { + $repo = New-GitHubRepository -RepositoryName ([Guid]::NewGuid().Guid) -AutoInit + } + + It 'Should be empty' { + $languages = Get-GitHubRepositoryLanguage -OwnerName $repo.owner.login -RepositoryName $repo.name + $languages | Should -BeNullOrEmpty + } + + It 'Should contain PowerShell' { + $languages = Get-GitHubRepositoryLanguage -OwnerName "microsoft" -RepositoryName "PowerShellForGitHub" + $languages.PowerShell | Should -Not -BeNullOrEmpty + $languages.PSObject.TypeNames[0] | Should -Be 'GitHub.RepositoryLanguage' + } + + It 'Should contain PowerShell (via pipeline)' { + $psfg = Get-GitHubRepository -OwnerName "microsoft" -RepositoryName "PowerShellForGitHub" + $languages = $psfg | Get-GitHubRepositoryLanguage + $languages.PowerShell | Should -Not -BeNullOrEmpty + $languages.PSObject.TypeNames[0] | Should -Be 'GitHub.RepositoryLanguage' + } + + AfterAll { + Remove-GitHubRepository -Uri $repo.svn_url -Force + } + } + } + + Describe 'GitHubRepositories\Get-GitHubRepositoryTag' { + + Context -Name 'When getting repository tags' { + BeforeAll { + $repo = New-GitHubRepository -RepositoryName ([Guid]::NewGuid().Guid) -AutoInit + } + + It 'Should be empty' { + $tags = Get-GitHubRepositoryTag -OwnerName $repo.owner.login -RepositoryName $repo.name + $tags | Should -BeNullOrEmpty + } + + It 'Should be empty (via pipeline)' { + $tags = $repo | Get-GitHubRepositoryTag + $tags | Should -BeNullOrEmpty + } + + AfterAll { + Remove-GitHubRepository -Uri $repo.svn_url -Confirm:$false } } } From d32bd11d971c8b5c4a56b6ff6f997aca61fba2ca Mon Sep 17 00:00:00 2001 From: Howard Wolosky Date: Sun, 28 Jun 2020 10:41:17 -0700 Subject: [PATCH 45/60] Invoke-UpdateCheck fixes (#252) * Fixes a regression [introduced by #242 unfortunately](https://github.com/microsoft/PowerShellForGitHub/commit/17f6122d7812ee4001ce4bdf630429e711e45f7b#diff-34a614678760cc83c5a91ad95ae240e5R86) when I was reducing the lines in the module that exceeded 100 chars. Specifically (note the `=Message` instead of the `-Message`): https://github.com/microsoft/PowerShellForGitHub/blob/17f6122d7812ee4001ce4bdf630429e711e45f7b/UpdateCheck.ps1#L86 * Updates the web request to suppress the progress bar using `$ProgressPreference = 'SilentlyContinue'` * Adds a `-Force` switch to force the update check to happen again to make future debugging scenarios easier. Fixes #249 Fixes #250 --- UpdateCheck.ps1 | 24 ++++++++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) diff --git a/UpdateCheck.ps1 b/UpdateCheck.ps1 index 0773f60c..635ae714 100644 --- a/UpdateCheck.ps1 +++ b/UpdateCheck.ps1 @@ -26,13 +26,18 @@ function Invoke-UpdateCheck The Git repo for this module can be found here: http://aka.ms/PowerShellForGitHub + .PARAMETER Force + For debugging purposes, using this switch will allow the check to occur more than the limit + of once per day. This _will not_ bypass the DisableUpdateCheck configuration value however. + .EXAMPLE Invoke-UpdateCheck .NOTES Internal-only helper method. #> - param() + [cmdletbinding()] + param([switch] $Force) if (Get-GitHubConfiguration -Name DisableUpdateCheck) { @@ -44,6 +49,18 @@ function Invoke-UpdateCheck $jobNameToday = "Invoke-UpdateCheck-" + (Get-Date -format 'yyyyMMdd') + if ($Force) + { + if ($null -ne $script:UpdateCheckJobName) + { + # We're going to clear out the existing job and try running it again. + $null = Receive-Job -Name $script:UpdateCheckJobName -AutoRemoveJob -Wait -ErrorAction SilentlyContinue -ErrorVariable errorInfo + } + + $script:UpdateCheckJobName = $null + $script:HasLatestVersion = $null + } + # We only check once per day if ($jobNameToday -eq $script:UpdateCheckJobName) { @@ -83,7 +100,7 @@ function Invoke-UpdateCheck if ($script:HasLatestVersion) { $message = "[$moduleName] update check complete. Running latest version: $latestVersion" - Write-Log =Message $message -Level Verbose + Write-Log -Message $message -Level Verbose } elseif ($moduleVersion -gt $latestVersion) { @@ -132,6 +149,9 @@ function Invoke-UpdateCheck try { + # Disable Progress Bar in function scope during Invoke-WebRequest + $ProgressPreference = 'SilentlyContinue' + Invoke-WebRequest @params } catch From e57a9563ef68f3a897c2b523e5ea0cbf23011d4c Mon Sep 17 00:00:00 2001 From: Howard Wolosky Date: Sun, 28 Jun 2020 22:35:12 -0700 Subject: [PATCH 46/60] Standardize verb usage within the module (#228) This attempts to rectify some improper verb usage in the module based on the explanations of intended verb usage from [previous PowerShell documentation](https://web.archive.org/web/20171222220053/https://msdn.microsoft.com/en-us/library/windows/desktop/ms714428(v=vs.85).aspx). * We're standardizing on the following pattern for most object actions: `Get` / `Set` / `New` / `Remove` * We will continue to alias `Remove-*` as `Delete-*`. * When possible, this change attempts to avoid breaking changes by re-aliasing the newly named functions with their previous names. This was not possible in one instance, hence this is still a breaking change (although based on telemetry, it should be minimally impacting). Result: * `Update-GitHubCurrentUser` -> `Set-GitHubProfile` `[Alias('Update-GitHubCurrentUser')]` * `Update-GitHubIssue` -> `Set-GitHubIssue` `[Alias('Update-GitHubIssue')]` * `Update-GitHubRepository` -> `Set-GitHubRepository` `[Alias('Update-GitHubRepository')]` * `New-GitHubAssignee` -> `Add-GitHubAssignee` `[Alias('New-GitHubAssignee')]` * [breaking] `Update-GitHubLabel` -> `Set-GitHubLabel` `[Alias('Update-GitHubLabel')]` * [breaking] `Set-GitHubLabel` -> `Initialize-GitHubLabel` `` Changing an existing label has much more regular usage than replacing all of the labels in a repository, hence allowing the _new_ `Set-GitHubLabel` to keep the alias of `Update-GitHubLabel`. Our usage of the `Set-*` verb in general is a bit arguable based on the documentation ... in theory `Edit-*` might be a better fit since we're _editing_ aspects of an object as opposed to _replacing_ the entire content of an object. However, I think `Set-*` _feels_ ok in this module. We're _setting_ the state of these objects. Again...arguable, but this is a much smaller breaking change to get to a consistent terminology state. --- GitHubAssignees.ps1 | 11 ++--- GitHubIssues.ps1 | 9 ++-- GitHubLabels.ps1 | 15 +++---- GitHubRepositories.ps1 | 13 +++--- GitHubUsers.ps1 | 9 ++-- PowerShellForGitHub.psd1 | 17 +++++--- Tests/GitHubAssignees.tests.ps1 | 8 ++-- Tests/GitHubEvents.tests.ps1 | 10 ++--- Tests/GitHubIssues.tests.ps1 | 10 ++--- Tests/GitHubLabels.tests.ps1 | 66 +++++++++++++----------------- Tests/GitHubMilestones.tests.ps1 | 6 +-- Tests/GitHubRepositories.tests.ps1 | 14 +++---- USAGE.md | 17 ++++---- 13 files changed, 105 insertions(+), 100 deletions(-) diff --git a/GitHubAssignees.ps1 b/GitHubAssignees.ps1 index 2c439da1..0964573a 100644 --- a/GitHubAssignees.ps1 +++ b/GitHubAssignees.ps1 @@ -247,7 +247,7 @@ filter Test-GitHubAssignee } } -function New-GitHubAssignee +function Add-GitHubAssignee { <# .DESCRIPTION @@ -308,7 +308,7 @@ function New-GitHubAssignee .EXAMPLE $assignees = @('octocat') - New-GitHubAssignee -OwnerName microsoft -RepositoryName PowerShellForGitHub -Issue 1 -Assignee $assignee + Add-GitHubAssignee -OwnerName microsoft -RepositoryName PowerShellForGitHub -Issue 1 -Assignee $assignee Additionally assigns the usernames in $assignee to Issue #1 from the microsoft\PowerShellForGitHub project. @@ -316,7 +316,7 @@ function New-GitHubAssignee .EXAMPLE $assignees = @('octocat') $repo = Get-GitHubRepository -OwnerName microsoft -RepositoryName PowerShellForGitHub - $repo | New-GitHubAssignee -Issue 1 -Assignee $assignee + $repo | Add-GitHubAssignee -Issue 1 -Assignee $assignee Additionally assigns the usernames in $assignee to Issue #1 from the microsoft\PowerShellForGitHub project. @@ -325,14 +325,14 @@ function New-GitHubAssignee $assignees = @('octocat') Get-GitHubRepository -OwnerName microsoft -RepositoryName PowerShellForGitHub | Get-GitHubIssue -Issue 1 | - New-GitHubAssignee -Assignee $assignee + Add-GitHubAssignee -Assignee $assignee Additionally assigns the usernames in $assignee to Issue #1 from the microsoft\PowerShellForGitHub project. .EXAMPLE $octocat = Get-GitHubUser -UserName 'octocat' - $octocat | New-GitHubAssignee -OwnerName microsoft -RepositoryName PowerShellForGitHub -Issue 1 + $octocat | Add-GitHubAssignee -OwnerName microsoft -RepositoryName PowerShellForGitHub -Issue 1 Additionally assigns the user 'octocat' to Issue #1 from the microsoft\PowerShellForGitHub project. @@ -341,6 +341,7 @@ function New-GitHubAssignee SupportsShouldProcess, DefaultParameterSetName='Elements')] [OutputType({$script:GitHubIssueTypeName})] + [Alias('New-GitHubAssignee')] # Non-standard usage of the New verb, but done to avoid a breaking change post 0.14.0 [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSShouldProcess", "", Justification="Methods called within here make use of PSShouldProcess, and the switch is passed on to them inherently.")] [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSReviewUnusedParameter", "", Justification="One or more parameters (like NoStatus) are only referenced by helper methods which get access to it from the stack via Get-Variable -Scope 1.")] param( diff --git a/GitHubIssues.ps1 b/GitHubIssues.ps1 index 46931a43..1010b19a 100644 --- a/GitHubIssues.ps1 +++ b/GitHubIssues.ps1 @@ -654,14 +654,14 @@ filter New-GitHubIssue return (Invoke-GHRestMethod @params | Add-GitHubIssueAdditionalProperties) } -filter Update-GitHubIssue +filter Set-GitHubIssue { <# .SYNOPSIS - Create a new Issue on GitHub. + Updates an Issue on GitHub. .DESCRIPTION - Create a new Issue on GitHub. + Updates an Issue on GitHub. The Git repo for this module can be found here: http://aka.ms/PowerShellForGitHub @@ -744,12 +744,13 @@ filter Update-GitHubIssue GitHub.Issue .EXAMPLE - Update-GitHubIssue -OwnerName microsoft -RepositoryName PowerShellForGitHub -Issue 4 -Title 'Test Issue' -State Closed + Set-GitHubIssue -OwnerName microsoft -RepositoryName PowerShellForGitHub -Issue 4 -Title 'Test Issue' -State Closed #> [CmdletBinding( SupportsShouldProcess, DefaultParameterSetName='Elements')] [OutputType({$script:GitHubIssueTypeName})] + [Alias('Update-GitHubIssue')] # Non-standard usage of the Update verb, but done to avoid a breaking change post 0.14.0 [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSShouldProcess", "", Justification="Methods called within here make use of PSShouldProcess, and the switch is passed on to them inherently.")] param( [Parameter(ParameterSetName='Elements')] diff --git a/GitHubLabels.ps1 b/GitHubLabels.ps1 index 41d306f5..80f55480 100644 --- a/GitHubLabels.ps1 +++ b/GitHubLabels.ps1 @@ -486,7 +486,7 @@ filter Remove-GitHubLabel } } -filter Update-GitHubLabel +filter Set-GitHubLabel { <# .SYNOPSIS @@ -555,7 +555,7 @@ filter Update-GitHubLabel GitHub.Label .EXAMPLE - Update-GitHubLabel -OwnerName microsoft -RepositoryName PowerShellForGitHub -Label TestLabel -NewName NewTestLabel -Color BBBB00 + Set-GitHubLabel -OwnerName microsoft -RepositoryName PowerShellForGitHub -Label TestLabel -NewName NewTestLabel -Color BBBB00 Updates the existing label called TestLabel in the PowerShellForGitHub project to be called 'NewTestLabel' and be colored yellow. @@ -564,6 +564,7 @@ filter Update-GitHubLabel SupportsShouldProcess, DefaultParameterSetName='Elements')] [OutputType({$script:GitHubLabelTypeName})] + [Alias('Update-GitHubLabel')] # Non-standard usage of the Update verb, but done to avoid a breaking change post 0.14.0 [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSShouldProcess", "", Justification="Methods called within here make use of PSShouldProcess, and the switch is passed on to them inherently.")] param( [Parameter(ParameterSetName='Elements')] @@ -637,15 +638,15 @@ filter Update-GitHubLabel return (Invoke-GHRestMethod @params | Add-GitHubLabelAdditionalProperties) } -filter Set-GitHubLabel +filter Initialize-GitHubLabel { <# .SYNOPSIS - Sets the entire set of Labels on the given GitHub repository to match the provided list + Replaces the entire set of Labels on the given GitHub repository to match the provided list of Labels. .DESCRIPTION - Sets the entire set of Labels on the given GitHub repository to match the provided list + Replaces the entire set of Labels on the given GitHub repository to match the provided list of Labels. Will update the color/description for any Labels already in the repository that match the @@ -697,7 +698,7 @@ filter Set-GitHubLabel GitHub.Repository .EXAMPLE - Set-GitHubLabel -OwnerName microsoft -RepositoryName PowerShellForGitHub -Label @(@{'name' = 'TestLabel'; 'color' = 'EEEEEE'}, @{'name' = 'critical'; 'color' = 'FF000000'; 'description' = 'Needs immediate attention'}) + Initialize-GitHubLabel -OwnerName microsoft -RepositoryName PowerShellForGitHub -Label @(@{'name' = 'TestLabel'; 'color' = 'EEEEEE'}, @{'name' = 'critical'; 'color' = 'FF000000'; 'description' = 'Needs immediate attention'}) Removes any labels not in this Label array, ensure the current assigned color and descriptions match what's in the array for the labels that do already exist, and then creates new labels @@ -769,7 +770,7 @@ filter Set-GitHubLabel else { # Update label's color if it already exists - $null = Update-GitHubLabel -Label $labelToConfigure.name -NewName $labelToConfigure.name -Color $labelToConfigure.color @commonParams + $null = Set-GitHubLabel -Label $labelToConfigure.name -NewName $labelToConfigure.name -Color $labelToConfigure.color @commonParams } } diff --git a/GitHubRepositories.ps1 b/GitHubRepositories.ps1 index 7a2c2e04..8bc58389 100644 --- a/GitHubRepositories.ps1 +++ b/GitHubRepositories.ps1 @@ -1015,11 +1015,11 @@ filter Rename-GitHubRepository ) # This method was created by mistake and is now retained to avoid a breaking change. - # Update-GitHubRepository is able to handle this scenario just fine. - return Update-GitHubRepository @PSBoundParameters + # Set-GitHubRepository is able to handle this scenario just fine. + return Set-GitHubRepository @PSBoundParameters } -filter Update-GitHubRepository +filter Set-GitHubRepository { <# .SYNOPSIS @@ -1125,18 +1125,18 @@ filter Update-GitHubRepository GitHub.Repository .EXAMPLE - Update-GitHubRepository -OwnerName microsoft -RepositoryName PowerShellForGitHub -Description 'The best way to automate your GitHub interactions' + Set-GitHubRepository -OwnerName microsoft -RepositoryName PowerShellForGitHub -Description 'The best way to automate your GitHub interactions' Changes the description of the specified repository. .EXAMPLE - Update-GitHubRepository -Uri https://github.com/PowerShell/PowerShellForGitHub -Private:$false + Set-GitHubRepository -Uri https://github.com/PowerShell/PowerShellForGitHub -Private:$false Changes the visibility of the specified repository to be public. .EXAMPLE Get-GitHubRepository -Uri https://github.com/PowerShell/PowerShellForGitHub | - Update-GitHubRepository -NewName 'PoShForGitHub' -Force + Set-GitHubRepository -NewName 'PoShForGitHub' -Force Renames the repository without any user confirmation prompting. This is identical to using Rename-GitHubRepository -Uri https://github.com/PowerShell/PowerShellForGitHub -NewName 'PoShForGitHub' -Confirm:$false @@ -1146,6 +1146,7 @@ filter Update-GitHubRepository DefaultParameterSetName='Elements', ConfirmImpact='High')] [OutputType({$script:GitHubRepositoryTypeName})] + [Alias('Update-GitHubRepository')] # Non-standard usage of the Update verb, but done to avoid a breaking change post 0.14.0 [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSShouldProcess", "", Justification="Methods called within here make use of PSShouldProcess, and the switch is passed on to them inherently.")] param( [Parameter(ParameterSetName='Elements')] diff --git a/GitHubUsers.ps1 b/GitHubUsers.ps1 index 1ed37ccc..16019b45 100644 --- a/GitHubUsers.ps1 +++ b/GitHubUsers.ps1 @@ -300,14 +300,14 @@ filter Get-GitHubUserContextualInformation return $result } -function Update-GitHubCurrentUser +function Set-GitHubProfile { <# .SYNOPSIS - Updates information about the current authenticated user on GitHub. + Updates profile information for the current authenticated user on GitHub. .DESCRIPTION - Updates information about the current authenticated user on GitHub. + Updates profile information for the current authenticated user on GitHub. The Git repo for this module can be found here: http://aka.ms/PowerShellForGitHub @@ -347,13 +347,14 @@ function Update-GitHubCurrentUser GitHub.User .EXAMPLE - Update-GitHubCurrentUser -Location 'Seattle, WA' -Hireable:$false + Set-GitHubProfile -Location 'Seattle, WA' -Hireable:$false Updates the current user to indicate that their location is "Seattle, WA" and that they are not currently hireable. #> [CmdletBinding(SupportsShouldProcess)] [OutputType({$script:GitHubUserTypeName})] + [Alias('Update-GitHubCurrentUser')] # Non-standard usage of the Update verb, but done to avoid a breaking change post 0.14.0 [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSShouldProcess", "", Justification="Methods called within here make use of PSShouldProcess, and the switch is passed on to them inherently.")] param( [string] $Name, diff --git a/PowerShellForGitHub.psd1 b/PowerShellForGitHub.psd1 index b49ff1f0..cb7fcf06 100644 --- a/PowerShellForGitHub.psd1 +++ b/PowerShellForGitHub.psd1 @@ -55,6 +55,7 @@ # Functions to export from this module FunctionsToExport = @( + 'Add-GitHubAssignee', 'Add-GitHubIssueLabel', 'Backup-GitHubConfiguration', 'Clear-GitHubAuthentication', @@ -102,6 +103,7 @@ 'Get-GitHubViewTraffic', 'Group-GitHubIssue', 'Group-GitHubPullRequest', + 'Initialize-GitHubLabel', 'Invoke-GHRestMethod', 'Invoke-GHRestMethodMultipleResult', 'Join-GitHubUri', @@ -109,7 +111,6 @@ 'Move-GitHubProjectCard', 'Move-GitHubProjectColumn', 'Move-GitHubRepositoryOwnership', - 'New-GitHubAssignee', 'New-GitHubIssue', 'New-GitHubIssueComment', 'New-GitHubLabel', @@ -136,24 +137,23 @@ 'Set-GitHubAuthentication', 'Set-GitHubConfiguration', 'Set-GitHubContent', + 'Set-GitHubIssue', 'Set-GitHubIssueComment', 'Set-GitHubIssueLabel', 'Set-GitHubLabel', 'Set-GitHubMilestone', + 'Set-GitHubProfile', 'Set-GitHubProject', 'Set-GitHubProjectCard', 'Set-GitHubProjectColumn', + 'Set-GitHubRepository' 'Set-GitHubRepositoryTopic', 'Split-GitHubUri', 'Test-GitHubAssignee', 'Test-GitHubAuthenticationConfigured', 'Test-GitHubOrganizationMember', 'Test-GitHubRepositoryVulnerabilityAlert', - 'Unlock-GitHubIssue', - 'Update-GitHubCurrentUser', - 'Update-GitHubIssue', - 'Update-GitHubLabel', - 'Update-GitHubRepository' + 'Unlock-GitHubIssue' ) AliasesToExport = @( @@ -167,10 +167,15 @@ 'Delete-GitHubRepository', 'Get-GitHubBranch', 'Get-GitHubComment', + 'New-GitHubAssignee', 'New-GitHubComment', 'Remove-GitHubComment', 'Set-GitHubComment', 'Transfer-GitHubRepositoryOwnership' + 'Update-GitHubIssue', + 'Update-GitHubLabel', + 'Update-GitHubCurrentUser', + 'Update-GitHubRepository' ) # Cmdlets to export from this module diff --git a/Tests/GitHubAssignees.tests.ps1 b/Tests/GitHubAssignees.tests.ps1 index d9eeb61c..64a694b5 100644 --- a/Tests/GitHubAssignees.tests.ps1 +++ b/Tests/GitHubAssignees.tests.ps1 @@ -112,7 +112,7 @@ try $issue.assignees | Should -BeNullOrEmpty } - $updatedIssue = New-GitHubAssignee -OwnerName $script:ownerName -RepositoryName $repo.name -Issue $issue.number -Assignee $owner.login + $updatedIssue = Add-GitHubAssignee -OwnerName $script:ownerName -RepositoryName $repo.name -Issue $issue.number -Assignee $owner.login It 'Should have returned the same issue' { $updatedIssue.number | Should -Be $issue.number } @@ -149,7 +149,7 @@ try $issue.assignees | Should -BeNullOrEmpty } - $updatedIssue = $repo | New-GitHubAssignee -Issue $issue.number -Assignee $owner.login + $updatedIssue = $repo | Add-GitHubAssignee -Issue $issue.number -Assignee $owner.login It 'Should have returned the same issue' { $updatedIssue.number | Should -Be $issue.number } @@ -186,7 +186,7 @@ try $issue.assignees | Should -BeNullOrEmpty } - $updatedIssue = $issue | New-GitHubAssignee -Assignee $owner.login + $updatedIssue = $issue | Add-GitHubAssignee -Assignee $owner.login It 'Should have returned the same issue' { $updatedIssue.number | Should -Be $issue.number } @@ -223,7 +223,7 @@ try $issue.assignees | Should -BeNullOrEmpty } - $updatedIssue = $owner | New-GitHubAssignee -OwnerName $script:ownerName -RepositoryName $repo.name -Issue $issue.number + $updatedIssue = $owner | Add-GitHubAssignee -OwnerName $script:ownerName -RepositoryName $repo.name -Issue $issue.number It 'Should have returned the same issue' { $updatedIssue.number | Should -Be $issue.number } diff --git a/Tests/GitHubEvents.tests.ps1 b/Tests/GitHubEvents.tests.ps1 index 43390e7a..963cd9b1 100644 --- a/Tests/GitHubEvents.tests.ps1 +++ b/Tests/GitHubEvents.tests.ps1 @@ -45,7 +45,7 @@ try Context 'For getting Issue events from a repository' { $issue = $repo | New-GitHubIssue -Title 'New Issue' - $issue = $issue | Update-GitHubIssue -State Closed + $issue = $issue | Set-GitHubIssue -State Closed $events = @($repo | Get-GitHubEvent) It 'Should have an event from closing an issue' { @@ -82,8 +82,8 @@ try } Context 'For getting events from an issue' { - $issue = $issue | Update-GitHubIssue -State Closed - $issue = $issue | Update-GitHubIssue -State Open + $issue = $issue | Set-GitHubIssue -State Closed + $issue = $issue | Set-GitHubIssue -State Open $events = @(Get-GitHubEvent -OwnerName $ownerName -RepositoryName $repositoryName) It 'Should have two events from closing and opening the issue' { @@ -98,8 +98,8 @@ try $repositoryName = [Guid]::NewGuid() $repo = New-GitHubRepository -RepositoryName $repositoryName $issue = $repo | New-GitHubIssue -Title 'New Issue' - $issue = $issue | Update-GitHubIssue -State Closed - $issue = $issue | Update-GitHubIssue -State Open + $issue = $issue | Set-GitHubIssue -State Closed + $issue = $issue | Set-GitHubIssue -State Open $events = @($repo | Get-GitHubEvent) } diff --git a/Tests/GitHubIssues.tests.ps1 b/Tests/GitHubIssues.tests.ps1 index 21b4dd30..ed5c4d88 100644 --- a/Tests/GitHubIssues.tests.ps1 +++ b/Tests/GitHubIssues.tests.ps1 @@ -164,8 +164,8 @@ try Start-Sleep -Seconds 1 # Needed to ensure that there is a unique creation timestamp between issues } - $newIssues[0] = Update-GitHubIssue -OwnerName $script:ownerName -RepositoryName $repo.name -Issue $newIssues[0].number -State Closed - $newIssues[-1] = Update-GitHubIssue -OwnerName $script:ownerName -RepositoryName $repo.name -Issue $newIssues[-1].number -State Closed + $newIssues[0] = Set-GitHubIssue -OwnerName $script:ownerName -RepositoryName $repo.name -Issue $newIssues[0].number -State Closed + $newIssues[-1] = Set-GitHubIssue -OwnerName $script:ownerName -RepositoryName $repo.name -Issue $newIssues[-1].number -State Closed $existingOpenIssues = @($existingIssues | Where-Object { $_.state -eq 'open' }) $newOpenIssues = @($newIssues | Where-Object { $_.state -eq 'open' }) @@ -316,7 +316,7 @@ try 'MediaType' = 'Raw' } - $updated = Update-GitHubIssue @params + $updated = Set-GitHubIssue @params It 'Should have the expected property values' { $updated.id | Should -Be $issue.id $updated.number | Should -Be $issue.number @@ -364,7 +364,7 @@ try 'MediaType' = 'Raw' } - $updated = $repo | Update-GitHubIssue @params + $updated = $repo | Set-GitHubIssue @params It 'Should have the expected property values' { $updated.id | Should -Be $issue.id $updated.number | Should -Be $issue.number @@ -411,7 +411,7 @@ try 'MediaType' = 'Raw' } - $updated = $issue | Update-GitHubIssue @params + $updated = $issue | Set-GitHubIssue @params It 'Should have the expected property values' { $updated.id | Should -Be $issue.id $updated.number | Should -Be $issue.number diff --git a/Tests/GitHubLabels.tests.ps1 b/Tests/GitHubLabels.tests.ps1 index e2807442..5c3a1e30 100644 --- a/Tests/GitHubLabels.tests.ps1 +++ b/Tests/GitHubLabels.tests.ps1 @@ -81,7 +81,7 @@ try $repositoryName = [Guid]::NewGuid().Guid $repo = New-GitHubRepository -RepositoryName $repositoryName - Set-GitHubLabel -OwnerName $script:ownerName -RepositoryName $repositoryName -Label $defaultLabels + Initialize-GitHubLabel -OwnerName $script:ownerName -RepositoryName $repositoryName -Label $defaultLabels } AfterAll { @@ -376,7 +376,7 @@ try $label = $repo | New-GitHubLabel -Label ([Guid]::NewGuid().Guid) -Color 'BBBBBB' $newColor = 'AAAAAA' - $result = Update-GitHubLabel -OwnerName $script:ownerName -RepositoryName $repositoryName -Label $label.name -Color $newColor + $result = Set-GitHubLabel -OwnerName $script:ownerName -RepositoryName $repositoryName -Label $label.name -Color $newColor It 'Label should have different color' { $result.name | Should -Be $label.name @@ -396,7 +396,7 @@ try $label = $repo | New-GitHubLabel -Label ([Guid]::NewGuid().Guid) -Color 'BBBBBB' $newColor = '#AAAAAA' - $result = Update-GitHubLabel -OwnerName $script:ownerName -RepositoryName $repositoryName -Label $label.name -Color $newColor + $result = Set-GitHubLabel -OwnerName $script:ownerName -RepositoryName $repositoryName -Label $label.name -Color $newColor It 'Label should have different color' { $result.name | Should -Be $label.name @@ -416,7 +416,7 @@ try $label = $repo | New-GitHubLabel -Label ([Guid]::NewGuid().Guid) -Color 'BBBBBB' $newName = [Guid]::NewGuid().Guid - $result = Update-GitHubLabel -OwnerName $script:ownerName -RepositoryName $repositoryName -Label $label.name -NewName $newName + $result = Set-GitHubLabel -OwnerName $script:ownerName -RepositoryName $repositoryName -Label $label.name -NewName $newName It 'Label should have different name' { $result.name | Should -Be $newName @@ -436,7 +436,7 @@ try $label = $repo | New-GitHubLabel -Label ([Guid]::NewGuid().Guid) -Color 'BBBBBB' -Description 'test description' $newDescription = [Guid]::NewGuid().Guid - $result = Update-GitHubLabel -OwnerName $script:ownerName -RepositoryName $repositoryName -Label $label.name -Description $newDescription + $result = Set-GitHubLabel -OwnerName $script:ownerName -RepositoryName $repositoryName -Label $label.name -Description $newDescription It 'Label should have different name' { $result.name | Should -Be $label.name @@ -458,7 +458,7 @@ try $newName = [Guid]::NewGuid().Guid $newColor = 'AAAAAA' $newDescription = [Guid]::NewGuid().Guid - $result = Update-GitHubLabel -OwnerName $script:ownerName -RepositoryName $repositoryName -Label $label.name -NewName $newName -Color $newColor -Description $newDescription + $result = Set-GitHubLabel -OwnerName $script:ownerName -RepositoryName $repositoryName -Label $label.name -NewName $newName -Color $newColor -Description $newDescription It 'Label should have different everything' { $result.name | Should -Be $newName @@ -480,7 +480,7 @@ try $label = $repo | New-GitHubLabel -Label ([Guid]::NewGuid().Guid) -Color 'BBBBBB' $newColor = 'AAAAAA' - $result = $repo | Update-GitHubLabel -Label $label.name -Color $newColor + $result = $repo | Set-GitHubLabel -Label $label.name -Color $newColor It 'Label should have different color' { $result.name | Should -Be $label.name @@ -500,7 +500,7 @@ try $label = $repo | New-GitHubLabel -Label ([Guid]::NewGuid().Guid) -Color 'BBBBBB' $newName = [Guid]::NewGuid().Guid - $result = $label | Update-GitHubLabel -NewName $newName + $result = $label | Set-GitHubLabel -NewName $newName It 'Label should have different name' { $result.name | Should -Be $newName @@ -522,7 +522,7 @@ try $newName = [Guid]::NewGuid().Guid $newColor = 'AAAAAA' $newDescription = [Guid]::NewGuid().Guid - $result = $label | Update-GitHubLabel -NewName $newName -Color $newColor -Description $newDescription + $result = $label | Set-GitHubLabel -NewName $newName -Color $newColor -Description $newDescription It 'Label should have different everything' { $result.name | Should -Be $newName @@ -550,7 +550,7 @@ try } Context 'Applying a default set of labels' { - Set-GitHubLabel -OwnerName $script:ownerName -RepositoryName $repositoryName -Label $defaultLabels + Initialize-GitHubLabel -OwnerName $script:ownerName -RepositoryName $repositoryName -Label $defaultLabels $labels = @($repo | Get-GitHubLabel) @@ -578,7 +578,7 @@ try ) $originalLabels = @($repo | Get-GitHubLabel) - $null = $repo | Set-GitHubLabel -Label $newLabels + $null = $repo | Initialize-GitHubLabel -Label $newLabels $labels = @($repo | Get-GitHubLabel) It 'Should return the expected number of labels' { @@ -595,37 +595,29 @@ try } It 'Should have retained the ID''s of the pre-existing labels' { - $originalLabel = $originalLabels | Where-Object { $_.name -eq $newLabels[0].name } - $label = $labels | Where-Object { $_.name -eq $newLabels[0].name } - $label.id | Should -Be $originalLabel.id - - $originalLabel = $originalLabels | Where-Object { $_.name -eq $newLabels[1].name } - $label = $labels | Where-Object { $_.name -eq $newLabels[1].name } - $label.id | Should -Be $originalLabel.id - - $originalLabel = $originalLabels | Where-Object { $_.name -eq $newLabels[2].name } - $label = $labels | Where-Object { $_.name -eq $newLabels[2].name } - $label.id | Should -Be $originalLabel.id - - $originalLabel = $originalLabels | Where-Object { $_.name -eq $newLabels[3].name } - $label = $labels | Where-Object { $_.name -eq $newLabels[3].name } - $originalLabel | Should -BeNullOrEmpty - $label | Should -Not -BeNullOrEmpty + for ($i = 0; $i -le 2; $i++) + { + $originalLabel = $originalLabels | Where-Object { $_.name -eq $newLabels[$i].name } + $label = $labels | Where-Object { $_.name -eq $newLabels[$i].name } + $label.id | Should -Be $originalLabel.id + } - $originalLabel = $originalLabels | Where-Object { $_.name -eq $newLabels[4].name } - $label = $labels | Where-Object { $_.name -eq $newLabels[4].name } - $originalLabel | Should -BeNullOrEmpty - $label | Should -Not -BeNullOrEmpty + for ($i = 3; $i -le 4; $i++) + { + $originalLabel = $originalLabels | Where-Object { $_.name -eq $newLabels[$i].name } + $label = $labels | Where-Object { $_.name -eq $newLabels[$i].name } + $originalLabel | Should -BeNullOrEmpty + $label | Should -Not -BeNullOrEmpty + } } } - } Describe 'Adding labels to an issue' { BeforeAll { $repositoryName = [Guid]::NewGuid().Guid $repo = New-GitHubRepository -RepositoryName $repositoryName - $repo | Set-GitHubLabel -Label $defaultLabels + $repo | Initialize-GitHubLabel -Label $defaultLabels } AfterAll { @@ -853,7 +845,7 @@ try BeforeAll { $repositoryName = [Guid]::NewGuid().Guid $repo = New-GitHubRepository -RepositoryName $repositoryName - $repo | Set-GitHubLabel -Label $defaultLabels + $repo | Initialize-GitHubLabel -Label $defaultLabels } AfterAll { @@ -919,7 +911,7 @@ try BeforeAll { $repositoryName = [Guid]::NewGuid().Guid $repo = New-GitHubRepository -RepositoryName $repositoryName - $repo | Set-GitHubLabel -Label $defaultLabels + $repo | Initialize-GitHubLabel -Label $defaultLabels } AfterAll { @@ -1086,7 +1078,7 @@ try BeforeAll { $repositoryName = [Guid]::NewGuid().Guid $repo = New-GitHubRepository -RepositoryName $repositoryName - $repo | Set-GitHubLabel -Label $defaultLabels + $repo | Initialize-GitHubLabel -Label $defaultLabels } AfterAll { @@ -1235,7 +1227,7 @@ try BeforeAll { $repositoryName = [Guid]::NewGuid().Guid $repo = New-GitHubRepository -RepositoryName $repositoryName - $repo | Set-GitHubLabel -Label $defaultLabels + $repo | Initialize-GitHubLabel -Label $defaultLabels $milestone = $repo | New-GitHubMilestone -Title 'test milestone' diff --git a/Tests/GitHubMilestones.tests.ps1 b/Tests/GitHubMilestones.tests.ps1 index 048e5dac..cc6c2b89 100644 --- a/Tests/GitHubMilestones.tests.ps1 +++ b/Tests/GitHubMilestones.tests.ps1 @@ -182,21 +182,21 @@ try $milestone.open_issues | Should -Be 0 } - $issue = $issue | Update-GitHubIssue -Milestone $milestone.MilestoneNumber + $issue = $issue | Set-GitHubIssue -Milestone $milestone.MilestoneNumber $milestone = $milestone | Get-GitHubMilestone It "Should be associated to the milestone now" { $issue.milestone.number | Should -Be $milestone.MilestoneNumber $milestone.open_issues | Should -Be 1 } - $issue = $issue | Update-GitHubIssue -Milestone 0 + $issue = $issue | Set-GitHubIssue -Milestone 0 $milestone = $milestone | Get-GitHubMilestone It 'Should no longer be associated to the milestone' { $issue.milestone | Should -BeNullOrEmpty $milestone.open_issues | Should -Be 0 } - $issue = $issue | Update-GitHubIssue -Milestone $milestone.MilestoneNumber + $issue = $issue | Set-GitHubIssue -Milestone $milestone.MilestoneNumber $milestone = $milestone | Get-GitHubMilestone It "Should be associated to the milestone again" { $issue.milestone.number | Should -Be $milestone.MilestoneNumber diff --git a/Tests/GitHubRepositories.tests.ps1 b/Tests/GitHubRepositories.tests.ps1 index 37bd6983..9e7a0944 100644 --- a/Tests/GitHubRepositories.tests.ps1 +++ b/Tests/GitHubRepositories.tests.ps1 @@ -662,8 +662,8 @@ try $renamedRepo.PSObject.TypeNames[0] | Should -Be 'GitHub.Repository' } - It "Should be possible to rename with Update-GitHubRepository too" { - $renamedRepo = $repo | Update-GitHubRepository -NewName $newRepoName -Confirm:$false + It "Should be possible to rename with Set-GitHubRepository too" { + $renamedRepo = $repo | Set-GitHubRepository -NewName $newRepoName -Confirm:$false $renamedRepo.name | Should -Be $newRepoName $renamedRepo.PSObject.TypeNames[0] | Should -Be 'GitHub.Repository' } @@ -674,7 +674,7 @@ try } } - Describe 'GitHubRepositories\Update-GitHubRepository' { + Describe 'GitHubRepositories\Set-GitHubRepository' { Context -Name 'When updating a public repository' -Fixture { BeforeAll -ScriptBlock { @@ -699,7 +699,7 @@ try DeleteBranchOnMerge = $true IsTemplate = $true } - $updatedRepo = Update-GitHubRepository @updateGithubRepositoryParms + $updatedRepo = Set-GitHubRepository @updateGithubRepositoryParms } It 'Should return an object of the correct type' { @@ -731,7 +731,7 @@ try DisallowMergeCommit = $false DisallowRebaseMerge = $true } - $updatedRepo = Update-GitHubRepository @updateGithubRepositoryParms + $updatedRepo = Set-GitHubRepository @updateGithubRepositoryParms } It 'Should return an object of the correct type' { @@ -753,7 +753,7 @@ try RepositoryName = $repoName Archived = $true } - $updatedRepo = Update-GitHubRepository @updateGithubRepositoryParms + $updatedRepo = Set-GitHubRepository @updateGithubRepositoryParms } It 'Should return an object of the correct type' { @@ -784,7 +784,7 @@ try RepositoryName = $repoName Private = $false } - $updatedRepo = Update-GitHubRepository @updateGithubRepositoryParms + $updatedRepo = Set-GitHubRepository @updateGithubRepositoryParms } It 'Should return an object of the correct type' { diff --git a/USAGE.md b/USAGE.md index 2b275c61..441081e7 100644 --- a/USAGE.md +++ b/USAGE.md @@ -395,13 +395,16 @@ Remove-GitHubIssueLabel -OwnerName microsoft -RepositoryName DesiredStateConfigu #### Updating a Label With a New Name and Color ```powershell -Update-GitHubLabel -OwnerName microsoft -RepositoryName DesiredStateConfiguration -Name TestLabel -NewName NewTestLabel -Color BBBB00 +Set-GitHubLabel -OwnerName microsoft -RepositoryName DesiredStateConfiguration -Name TestLabel -NewName NewTestLabel -Color BBBB00 ``` #### Bulk Updating Labels in a Repository +This replaces the entire set of labels in a repository to only contain the labels in the provided array. +Any labels already in the repository that are not in this array will be removed upon execution. + ```powershell $labels = @( @{ 'name' = 'Label1'; 'color' = 'BBBB00'; 'description' = 'My label description' }, @{ 'name' = 'Label2'; 'color' = 'FF00000' }) -Set-GitHubLabel -OwnerName PowerShell -RepositoryName DesiredStateConfiguration -Label $labels +Initialize-GitHubLabel -OwnerName PowerShell -RepositoryName DesiredStateConfiguration -Label $labels ``` ---------- @@ -413,9 +416,9 @@ Set-GitHubLabel -OwnerName PowerShell -RepositoryName DesiredStateConfiguration Get-GitHubUser -Current ``` -#### Updating the current authenticated user +#### Updating the current authenticated user's profile ```powershell -Update-GitHubCurrentUser -Location 'Seattle, WA' -Hireable:$false +Set-GitHubProfile -Location 'Seattle, WA' -Hireable:$false ``` #### Getting any user @@ -569,7 +572,7 @@ $HasPermission = Test-GitHubAssignee -OwnerName microsoft -RepositoryName PowerS #### Add assignee to an issue ```powershell -New-GitHubAssignee -OwnerName microsoft -RepositoryName PowerShellForGitHub -Assignees $assignees -Issue 1 +Add-GitHubAssignee -OwnerName microsoft -RepositoryName PowerShellForGitHub -Assignees $assignees -Issue 1 ``` #### Remove assignee from an issue @@ -628,7 +631,7 @@ Get-GitHubMilestone -OwnerName microsoft -RepositoryName PowerShellForGitHub -Mi #### Assign an existing issue to a new milestone ```powershell New-GitHubMilestone -OwnerName microsoft -RepositoryName PowerShellForGitHub -Title "Testing this API" -Update-GitHubIssue -OwnerName microsoft -RepositoryName PowerShellForGitHub -Issue 2 -Milestone 1 +Set-GitHubIssue -OwnerName microsoft -RepositoryName PowerShellForGitHub -Issue 2 -Milestone 1 ``` #### Editing an existing milestone @@ -722,5 +725,5 @@ $issue = $repo | New-GitHubIssue -Title $IssueTitle -Body $body -Label 'blog com $issue | New-GitHubIssueComment -Body $CommentBody # Close issue -$issue | Update-GitHubIssue -State Closed +$issue | Set-GitHubIssue -State Closed ``` From 4287ac998b0fbd54d768692952f78159ac783d59 Mon Sep 17 00:00:00 2001 From: Howard Wolosky Date: Mon, 29 Jun 2020 16:33:07 -0700 Subject: [PATCH 47/60] Improve CI speed by changing repo targeted by Forks tests (#258) Pull #251 changed the repo targeted by the forks tests from `microsoft/PowerShellForGitHub` to `octocat/Hello-World` in order to prevent the accidental deletion of real forks when running the UT's locally against your own account. In practice, this almost doubled the execution time of the UT's, because the execution time of the Forks API's took so much longer against a repo with 1400+ forks. Changing the test over to use a repo that currently only has 39 forks (several orders of magnitude fewer). --- Tests/GitHubRepositoryForks.tests.ps1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Tests/GitHubRepositoryForks.tests.ps1 b/Tests/GitHubRepositoryForks.tests.ps1 index a44b1466..7ea08eb1 100644 --- a/Tests/GitHubRepositoryForks.tests.ps1 +++ b/Tests/GitHubRepositoryForks.tests.ps1 @@ -20,7 +20,7 @@ try # Define Script-scoped, readonly, hidden variables. @{ upstreamOwnerName = 'octocat' - upstreamRepositoryName = 'Hello-World' + upstreamRepositoryName = 'git-consortium' }.GetEnumerator() | ForEach-Object { Set-Variable -Force -Scope Script -Option ReadOnly -Visibility Private -Name $_.Key -Value $_.Value } From f31d79133df1310fac1f14643eea4cdb4972a26a Mon Sep 17 00:00:00 2001 From: Howard Wolosky Date: Tue, 30 Jun 2020 12:58:04 -0700 Subject: [PATCH 48/60] Fix pipeline support and testing for New-GitHubRepositoryFromTemplate (#259) Added in #221, `New-GitHubRepositoryFromTemplate was not capturing the passed-in value for the RepositoryName if provided via a Uri parameter. There was a pipeline test in place, however it missed this fact because it was only testing the pipeline input scenario using `-WhatIf`, and the error was only found when calling the actual REST API and GitHub noticed that the `RepositoryName` was missing from the URI. --- GitHubRepositories.ps1 | 1 + Tests/GitHubRepositories.tests.ps1 | 38 +++++++++++++++++++++++++++--- 2 files changed, 36 insertions(+), 3 deletions(-) diff --git a/GitHubRepositories.ps1 b/GitHubRepositories.ps1 index 8bc58389..847e82b5 100644 --- a/GitHubRepositories.ps1 +++ b/GitHubRepositories.ps1 @@ -364,6 +364,7 @@ filter New-GitHubRepositoryFromTemplate $elements = Resolve-RepositoryElements -BoundParameters $PSBoundParameters $OwnerName = $elements.ownerName + $RepositoryName = $elements.repositoryName $telemetryProperties = @{ RepositoryName = (Get-PiiSafeString -PlainText $RepositoryName) diff --git a/Tests/GitHubRepositories.tests.ps1 b/Tests/GitHubRepositories.tests.ps1 index 9e7a0944..6fbcd27a 100644 --- a/Tests/GitHubRepositories.tests.ps1 +++ b/Tests/GitHubRepositories.tests.ps1 @@ -321,16 +321,48 @@ try } $repo = New-GitHubRepositoryFromTemplate @newGitHubRepositoryFromTemplateParms + Start-Sleep -Seconds 1 # To work around a delay that GitHub may have with generating the repo } - It 'Should support pipeline input for the uri parameter' { + It 'Should have the expected type and addititional properties' { + $repo.PSObject.TypeNames[0] | Should -Be 'GitHub.Repository' + $repo.name | Should -Be $repoName + $repo.private | Should -BeFalse + $repo.owner.login | Should -Be $script:ownerName + $repo.description | Should -Be $newRepoDesc + $repo.is_template | Should -BeFalse + $repo.RepositoryId | Should -Be $repo.id + $repo.RepositoryUrl | Should -Be $repo.html_url + } + + It 'Should have created a .gitignore file' { + { Get-GitHubContent -Uri $repo.svn_url -Path '.gitignore' } | Should -Not -Throw + } + + It 'Should have created a LICENSE file' { + { Get-GitHubContent -Uri $repo.svn_url -Path 'LICENSE' } | Should -Not -Throw + } + + AfterAll { + if (Get-Variable -Name repo -ErrorAction SilentlyContinue) + { + Remove-GitHubRepository -Uri $repo.svn_url -Confirm:$false + } + } + } + + Context 'When creating a public repository from a template (via pipeline input)' { + BeforeAll { + $repoName = ([Guid]::NewGuid().Guid) + $newRepoDesc = 'New Repo Description' $newGitHubRepositoryFromTemplateParms = @{ TargetOwnerName = $ownerName TargetRepositoryName = $repoName + Description = $newRepoDesc } - { $templateRepo | New-GitHubRepositoryFromTemplate @newGitHubRepositoryFromTemplateParms -WhatIf } | - Should -Not -Throw + $repo = $templateRepo | New-GitHubRepositoryFromTemplate @newGitHubRepositoryFromTemplateParms + Start-Sleep -Seconds 1 # To work around a delay that GitHub may have with generating the repo } It 'Should have the expected type and addititional properties' { From 2740026e64f2246d3b10bd3ccca197ea4ca3c9d8 Mon Sep 17 00:00:00 2001 From: Howard Wolosky Date: Tue, 30 Jun 2020 15:05:08 -0700 Subject: [PATCH 49/60] Reimagining status for the module (#253) * Reimagining status for the module Status for commands was originally added to this module based on my experience with other REST API's where individual commands could easily take 10-20 seconds. Practical usage has shown that most GitHub requests in reality take under one second. The additional work that PowerShell has to do in order to display progress to the user can easily make the overall command take 4-6 times longer than its actual execution time. Therefore, status is being ripped out of this module (for the most part). `Invoke-GHRestMethod` and `Invoke-SendTelemetryEvent` no longer have bifurcating execution paths based on the value of `$NoStatus`. Everything runs synchronously now on the command prompt. * `DefaultNoStatus` has been deprecated. Its value will be ignored. * The `NoStatus` switch has not been removed from the module commands in order to avoid a breaking change. It may be removed in a future update. * `Invoke-GHRestMethod -ExtendedResult` has been updated to include the next page's number and the total number of pages for the REST request. * A new configuration value has been added: `MultiRequestProgressThreshold `Invoke-GHRestMethodMultipleResult` will display a ProgressBar to the user tracking the number of remaining requests for the overall execution of the requested command based on this threshold value. It will only display the progress bar if the number of requets needed meets or exceeds this threshold value. This defaults to 10, and can be disabled with a value of 0. Practical usage has shown that this adds less than a second of additional time to the overall execution of a multi-request command (quite different than the previous status). * `Wait-JobWithAnimation` has been removed since it's no longer used. Fixes #247 --- GitHubConfiguration.ps1 | 16 ++ GitHubCore.ps1 | 261 ++++++++++++--------------------- Helpers.ps1 | 136 ----------------- Telemetry.ps1 | 204 +++----------------------- Tests/Common.ps1 | 2 +- Tests/GitHubContents.tests.ps1 | 1 - 6 files changed, 138 insertions(+), 482 deletions(-) diff --git a/GitHubConfiguration.ps1 b/GitHubConfiguration.ps1 index 0320f399..7c50f858 100644 --- a/GitHubConfiguration.ps1 +++ b/GitHubConfiguration.ps1 @@ -78,6 +78,10 @@ function Set-GitHubConfiguration .PARAMETER DefaultNoStatus Control if the -NoStatus switch should be passed-in by default to all methods. + The -NoStatus switch has been deprecated. Commands in this module no longer display status + on the console, thus passing in -NoStatus to a command no longer has any impact. + Therefore, the value of this configuration setting also no longer has any impact on + command execution. .PARAMETER DefaultOwnerName The owner name that should be used with a command that takes OwnerName as a parameter @@ -132,6 +136,14 @@ function Set-GitHubConfiguration .PARAMETER LogTimeAsUtc If specified, all times logged will be logged as UTC instead of the local timezone. + .PARAMETER MultiRequestProgressThreshold + Some commands may require sending multiple requests to GitHub. In some situations, + getting the entirety of the request might take 70+ requests occurring over 20+ seconds. + A progress bar will be shown (displaying which sub-request is being executed) if the number + of requests required to complete this command is greater than or equal to this configuration + value. + Set to 0 to disable this feature. + .PARAMETER RetryDelaySeconds The number of seconds to wait before retrying a command again after receiving a 202 response. @@ -212,6 +224,8 @@ function Set-GitHubConfiguration [switch] $LogTimeAsUtc, + [int] $MultiRequestProgressThreshold, + [int] $RetryDelaySeconds, [switch] $SuppressNoTokenWarning, @@ -296,6 +310,7 @@ function Get-GitHubConfiguration 'LogProcessId', 'LogRequestBody', 'LogTimeAsUtc', + 'MultiRequestProgressThreshold', 'RetryDelaySeconds', 'SuppressNoTokenWarning', 'SuppressTelemetryReminder', @@ -640,6 +655,7 @@ function Import-GitHubConfiguration 'logProcessId' = $false 'logRequestBody' = $false 'logTimeAsUtc' = $false + 'multiRequestProgressThreshold' = 10 'retryDelaySeconds' = 30 'suppressNoTokenWarning' = $false 'suppressTelemetryReminder' = $false diff --git a/GitHubCore.ps1 b/GitHubCore.ps1 index 09068429..019ef6ac 100644 --- a/GitHubCore.ps1 +++ b/GitHubCore.ps1 @@ -84,23 +84,22 @@ function Invoke-GHRestMethod no bucket value will be used. .PARAMETER NoStatus - If this switch is specified, long-running commands will run on the main thread - with no commandline status update. When not specified, those commands run in - the background, enabling the command prompt to provide status information. + Deprecated switch after v0.14.0. Kept here for the time being to reduce module churn. + Specifying it does nothing. It used to control whether or not the web request would have + a corresponding progress UI. .OUTPUTS [PSCustomObject] - The result of the REST operation, in whatever form it comes in. .EXAMPLE - Invoke-GHRestMethod -UriFragment "applications/" -Method Get -Description "Get first 10 applications" + Invoke-GHRestMethod -UriFragment "users/octocat" -Method Get -Description "Get information on the octocat user" - Gets the first 10 applications for the connected dev account. + Gets the user information for Octocat. .EXAMPLE - Invoke-GHRestMethod -UriFragment "applications/0ABCDEF12345/submissions/1234567890123456789/" -Method Delete -Description "Delete Submission" -NoStatus + Invoke-GHRestMethod -UriFragment "user" -Method Get -Description "Get current user" - Deletes the specified submission, but the request happens in the foreground and there is - no additional status shown to the user until a response is returned from the REST request. + Gets information about the current authenticated user. .NOTES This wraps Invoke-WebRequest as opposed to Invoke-RestMethod because we want access @@ -200,7 +199,6 @@ function Invoke-GHRestMethod return } - $NoStatus = Resolve-ParameterWithDefaultConfigurationValue -Name NoStatus -ConfigValueName DefaultNoStatus $originalSecurityProtocol = [Net.ServicePointManager]::SecurityProtocol try @@ -209,136 +207,34 @@ function Invoke-GHRestMethod Write-Log -Message "Accessing [$Method] $url [Timeout = $(Get-GitHubConfiguration -Name WebRequestTimeoutSec))]" -Level Verbose $result = $null - if ($NoStatus) + $params = @{} + $params.Add("Uri", $url) + $params.Add("Method", $Method) + $params.Add("Headers", $headers) + $params.Add("UseDefaultCredentials", $true) + $params.Add("UseBasicParsing", $true) + $params.Add("TimeoutSec", (Get-GitHubConfiguration -Name WebRequestTimeoutSec)) + + if ($Method -in $ValidBodyContainingRequestMethods -and (-not [String]::IsNullOrEmpty($Body))) { - $params = @{} - $params.Add("Uri", $url) - $params.Add("Method", $Method) - $params.Add("Headers", $headers) - $params.Add("UseDefaultCredentials", $true) - $params.Add("UseBasicParsing", $true) - $params.Add("TimeoutSec", (Get-GitHubConfiguration -Name WebRequestTimeoutSec)) - - if ($Method -in $ValidBodyContainingRequestMethods -and (-not [String]::IsNullOrEmpty($Body))) + $bodyAsBytes = [System.Text.Encoding]::UTF8.GetBytes($Body) + $params.Add("Body", $bodyAsBytes) + Write-Log -Message "Request includes a body." -Level Verbose + if (Get-GitHubConfiguration -Name LogRequestBody) { - $bodyAsBytes = [System.Text.Encoding]::UTF8.GetBytes($Body) - $params.Add("Body", $bodyAsBytes) - Write-Log -Message "Request includes a body." -Level Verbose - if (Get-GitHubConfiguration -Name LogRequestBody) - { - Write-Log -Message $Body -Level Verbose - } - } - - # Disable Progress Bar in function scope during Invoke-WebRequest - $ProgressPreference = 'SilentlyContinue' - - [Net.ServicePointManager]::SecurityProtocol=[Net.SecurityProtocolType]::Tls12 - $result = Invoke-WebRequest @params - - if ($Method -eq 'Delete') - { - Write-Log -Message "Successfully removed." -Level Verbose + Write-Log -Message $Body -Level Verbose } } - else - { - $jobName = "Invoke-GHRestMethod-" + (Get-Date).ToFileTime().ToString() - - [scriptblock]$scriptBlock = { - param( - $Url, - $Method, - $Headers, - $Body, - $ValidBodyContainingRequestMethods, - $TimeoutSec, - $LogRequestBody, - $ScriptRootPath) - - # We need to "dot invoke" Helpers.ps1 and GitHubConfiguration.ps1 within - # the context of this script block since we're running in a different - # PowerShell process and need access to Get-HttpWebResponseContent and - # config values referenced within Write-Log. - . (Join-Path -Path $ScriptRootPath -ChildPath 'Helpers.ps1') - . (Join-Path -Path $ScriptRootPath -ChildPath 'GitHubConfiguration.ps1') - - $params = @{} - $params.Add("Uri", $Url) - $params.Add("Method", $Method) - $params.Add("Headers", $Headers) - $params.Add("UseDefaultCredentials", $true) - $params.Add("UseBasicParsing", $true) - $params.Add("TimeoutSec", $TimeoutSec) - - if ($Method -in $ValidBodyContainingRequestMethods -and (-not [String]::IsNullOrEmpty($Body))) - { - $bodyAsBytes = [System.Text.Encoding]::UTF8.GetBytes($Body) - $params.Add("Body", $bodyAsBytes) - Write-Log -Message "Request includes a body." -Level Verbose - if ($LogRequestBody) - { - Write-Log -Message $Body -Level Verbose - } - } - - try - { - # Disable Progress Bar in function scope during Invoke-WebRequest - $ProgressPreference = 'SilentlyContinue' - [Net.ServicePointManager]::SecurityProtocol=[Net.SecurityProtocolType]::Tls12 - Invoke-WebRequest @params - } - catch [System.Net.WebException] - { - # We need to access certain headers in the exception handling, - # but the actual *values* of the headers of a WebException don't get serialized - # when the RemoteException wraps it. To work around that, we'll extract the - # information that we actually care about *now*, and then we'll throw our own exception - # that is just a JSON object with the data that we'll later extract for processing in - # the main catch. - $ex = @{} - $ex.Message = $_.Exception.Message - $ex.StatusCode = $_.Exception.Response.StatusCode - $ex.StatusDescription = $_.Exception.Response.StatusDescription - $ex.InnerMessage = $_.ErrorDetails.Message - try - { - $ex.RawContent = Get-HttpWebResponseContent -WebResponse $_.Exception.Response - } - catch - { - Write-Log -Message "Unable to retrieve the raw HTTP Web Response:" -Exception $_ -Level Warning - } + # Disable Progress Bar in function scope during Invoke-WebRequest + $ProgressPreference = 'SilentlyContinue' - $jsonConversionDepth = 20 # Seems like it should be more than sufficient - throw (ConvertTo-Json -InputObject $ex -Depth $jsonConversionDepth) - } - } - - $null = Start-Job -Name $jobName -ScriptBlock $scriptBlock -Arg @( - $url, - $Method, - $headers, - $Body, - $ValidBodyContainingRequestMethods, - (Get-GitHubConfiguration -Name WebRequestTimeoutSec), - (Get-GitHubConfiguration -Name LogRequestBody), - $PSScriptRoot) - - Wait-JobWithAnimation -Name $jobName -Description $Description - $result = Receive-Job $jobName -AutoRemoveJob -Wait -ErrorAction SilentlyContinue -ErrorVariable remoteErrors - - if ($remoteErrors.Count -gt 0) - { - throw $remoteErrors[0].Exception - } + [Net.ServicePointManager]::SecurityProtocol=[Net.SecurityProtocolType]::Tls12 + $result = Invoke-WebRequest @params - if ($Method -eq 'Delete') - { - Write-Log -Message "Successfully removed." -Level Verbose - } + if ($Method -eq 'Delete') + { + Write-Log -Message "Successfully removed." -Level Verbose } # Record the telemetry for this event. @@ -346,7 +242,7 @@ function Invoke-GHRestMethod if (-not [String]::IsNullOrEmpty($TelemetryEventName)) { $telemetryMetrics = @{ 'Duration' = $stopwatch.Elapsed.TotalSeconds } - Set-TelemetryEvent -EventName $TelemetryEventName -Properties $localTelemetryProperties -Metrics $telemetryMetrics -NoStatus:$NoStatus + Set-TelemetryEvent -EventName $TelemetryEventName -Properties $localTelemetryProperties -Metrics $telemetryMetrics } $finalResult = $result.Content @@ -379,11 +275,26 @@ function Invoke-GHRestMethod $links = $result.Headers['Link'] -split ',' $nextLink = $null + $nextPageNumber = 1 + $numPages = 1 + $since = 0 foreach ($link in $links) { - if ($link -match '<(.*)>; rel="next"') + if ($link -match '<(.*page=(\d+)[^\d]*)>; rel="next"') + { + $nextLink = $Matches[1] + $nextPageNumber = [int]$Matches[2] + } + elseif ($link -match '<(.*since=(\d+)[^\d]*)>; rel="next"') + { + # Special case scenario for the users endpoint. + $nextLink = $Matches[1] + $since = [int]$Matches[2] + $numPages = 0 # Signifies an unknown number of pages. + } + elseif ($link -match '<.*page=(\d+)[^\d]+rel="last"') { - $nextLink = $matches[1] + $numPages = [int]$Matches[1] } } @@ -417,6 +328,9 @@ function Invoke-GHRestMethod 'statusCode' = $result.StatusCode 'requestId' = $result.Headers['X-GitHub-Request-Id'] 'nextLink' = $nextLink + 'nextPageNumber' = $nextPageNumber + 'numPages' = $numPages + 'since' = $since 'link' = $result.Headers['Link'] 'lastModified' = $result.Headers['Last-Modified'] 'ifNoneMatch' = $result.Headers['If-None-Match'] @@ -436,9 +350,6 @@ function Invoke-GHRestMethod } catch { - # We only know how to handle WebExceptions, which will either come in "pure" - # when running with -NoStatus, or will come in as a RemoteException when running - # normally (since it's coming from the asynchronous Job). $ex = $null $message = $null $statusCode = $null @@ -468,32 +379,10 @@ function Invoke-GHRestMethod $requestId = $ex.Response.Headers['X-GitHub-Request-Id'] } } - elseif (($_.Exception -is [System.Management.Automation.RemoteException]) -and - ($_.Exception.SerializedRemoteException.PSObject.TypeNames[0] -eq 'Deserialized.System.Management.Automation.RuntimeException')) - { - $ex = $_.Exception - try - { - $deserialized = $ex.Message | ConvertFrom-Json - $message = $deserialized.Message - $statusCode = $deserialized.StatusCode - $statusDescription = $deserialized.StatusDescription - $innerMessage = $deserialized.InnerMessage - $requestId = $deserialized['X-GitHub-Request-Id'] - $rawContent = $deserialized.RawContent - } - catch [System.ArgumentException] - { - # Will be thrown if $ex.Message isn't JSON content - Write-Log -Exception $_ -Level Error - Set-TelemetryException -Exception $ex -ErrorBucket $errorBucket -Properties $localTelemetryProperties -NoStatus:$NoStatus - throw - } - } else { Write-Log -Exception $_ -Level Error - Set-TelemetryException -Exception $_.Exception -ErrorBucket $errorBucket -Properties $localTelemetryProperties -NoStatus:$NoStatus + Set-TelemetryException -Exception $_.Exception -ErrorBucket $errorBucket -Properties $localTelemetryProperties throw } @@ -558,7 +447,7 @@ function Invoke-GHRestMethod $newLineOutput = ($output -join [Environment]::NewLine) Write-Log -Message $newLineOutput -Level Error - Set-TelemetryException -Exception $ex -ErrorBucket $errorBucket -Properties $localTelemetryProperties -NoStatus:$NoStatus + Set-TelemetryException -Exception $ex -ErrorBucket $errorBucket -Properties $localTelemetryProperties throw $newLineOutput } finally @@ -644,7 +533,6 @@ function Invoke-GHRestMethodMultipleResult shown to the user until a response is returned from the REST request. #> [CmdletBinding(SupportsShouldProcess)] - [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSShouldProcess", "", Justification="Methods called within here make use of PSShouldProcess, and the switch is passed on to them inherently.")] [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSReviewUnusedParameter", "", Justification="One or more parameters (like NoStatus) are only referenced by helper methods which get access to it from the stack via Get-Variable -Scope 1.")] [OutputType([Object[]])] param( @@ -669,6 +557,11 @@ function Invoke-GHRestMethodMultipleResult [switch] $NoStatus ) + if (-not $PSCmdlet.ShouldProcess($UriFragment, "Invoke-GHRestMethod")) + { + return + } + $AccessToken = Get-AccessToken -AccessToken $AccessToken $stopwatch = [System.Diagnostics.Stopwatch]::StartNew() @@ -683,10 +576,14 @@ function Invoke-GHRestMethodMultipleResult $currentDescription = $Description $nextLink = $UriFragment + $multiRequestProgressThreshold = Get-GitHubConfiguration -Name 'MultiRequestProgressThreshold' + $iteration = 0 + $progressId = $null try { do { + $iteration++ $params = @{ 'UriFragment' = $nextLink 'Method' = 'Get' @@ -706,7 +603,35 @@ function Invoke-GHRestMethodMultipleResult } $nextLink = $result.nextLink - $currentDescription = "$Description (getting additional results)" + $status = [String]::Empty + $percentComplete = 0 + if ($result.numPages -eq 0) + { + # numPages == 0 is a special case for when the total number of pages is simply unknown. + # This can happen with getting all GitHub users. + $status = "Getting additional results [page $iteration of (unknown)]" + $percentComplete = 10 # No idea what percentage to use in this scenario + } + else + { + $status = "Getting additional results [page $($result.nextPageNumber)/$($result.numPages)])" + $percentComplete = (($result.nextPageNumber / $result.numPages) * 100) + } + + $currentDescription = "$Description ($status)" + if (($multiRequestProgressThreshold -gt 0) -and + (($result.numPages -ge $multiRequestProgressThreshold) -or ($result.numPages -eq 0))) + { + $progressId = 1 + $progressParams = @{ + 'Activity' = $Description + 'Status' = $status + 'PercentComplete' = $percentComplete + 'Id' = $progressId + } + + Write-Progress @progressParams + } } until ($SinglePage -or ([String]::IsNullOrWhiteSpace($nextLink))) @@ -724,6 +649,14 @@ function Invoke-GHRestMethodMultipleResult { throw } + finally + { + # Ensure that we complete the progress bar once the command is done, regardless of outcome. + if ($null -ne $progressId) + { + Write-Progress -Activity $Description -Id $progressId -Completed + } + } } filter Split-GitHubUri diff --git a/Helpers.ps1 b/Helpers.ps1 index 17fa80e2..482c5875 100644 --- a/Helpers.ps1 +++ b/Helpers.ps1 @@ -1,142 +1,6 @@ # Copyright (c) Microsoft Corporation. All rights reserved. # Licensed under the MIT License. -function Wait-JobWithAnimation -{ -<# - .SYNOPSIS - Waits for a background job to complete by showing a cursor and elapsed time. - - .DESCRIPTION - Waits for a background job to complete by showing a cursor and elapsed time. - - The Git repo for this module can be found here: http://aka.ms/PowerShellForGitHub - - .PARAMETER Name - The name of the job(s) that we are waiting to complete. - - .PARAMETER Description - The text displayed next to the spinning cursor, explaining what the job is doing. - - .PARAMETER StopAllOnAnyFailure - Will call Stop-Job on any jobs still Running if any of the specified jobs entered - the Failed state. - - .EXAMPLE - Wait-JobWithAnimation Job1 - Waits for a job named "Job1" to exit the "Running" state. While waiting, shows - a waiting cursor and the elapsed time. - - .NOTES - This is not a stand-in replacement for Wait-Job. It does not provide the full - set of configuration options that Wait-Job does. -#> - [CmdletBinding()] - Param( - [Parameter(Mandatory)] - [string[]] $Name, - - [string] $Description = "", - - [switch] $StopAllOnAnyFailure - ) - - [System.Collections.ArrayList]$runningJobs = $Name - $allJobsCompleted = $true - $hasFailedJob = $false - - $animationFrames = '|','/','-','\' - $framesPerSecond = 9 - - $progressId = 1 - $iteration = 0 - [int] $seconds = 0 - $secondWord = 'seconds' - - while ($runningJobs.Count -gt 0) - { - # We'll run into issues if we try to modify the same collection we're iterating over - $jobsToCheck = $runningJobs.ToArray() - foreach ($jobName in $jobsToCheck) - { - $state = (Get-Job -Name $jobName).state - if ($state -ne 'Running') - { - $runningJobs.Remove($jobName) - - if ($state -ne 'Completed') - { - $allJobsCompleted = $false - } - - if ($state -eq 'Failed') - { - $hasFailedJob = $true - if ($StopAllOnAnyFailure) - { - break - } - } - } - } - - if ($hasFailedJob -and $StopAllOnAnyFailure) - { - foreach ($jobName in $runningJobs) - { - Stop-Job -Name $jobName - } - - $runingJobs.Clear() - } - - $seconds = [int]($iteration / $framesPerSecond) - $secondWord = 'seconds' - if (1 -eq $seconds) - { - $secondWord = 'second' - } - - $animationFrameNumber = $iteration % $($animationFrames.Length) - $progressParams = @{ - 'Activity' = $Description - 'Status' = "$($animationFrames[$animationFrameNumber]) Elapsed: $seconds $secondWord" - 'PercentComplete' = $(($animationFrameNumber / $animationFrames.Length) * 100) - 'Id' = $progressId - } - - Write-Progress @progressParams - Start-Sleep -Milliseconds ([int](1000 / $framesPerSecond)) - $iteration++ - } - - # Ensure that we complete the progress bar once the command is done, regardless of outcome. - Write-Progress -Activity $Description -Id $progressId -Completed - - # We'll wrap the description (if provided) in brackets for display purposes. - if (-not [string]::IsNullOrWhiteSpace($Description)) - { - $Description = "[$Description]" - } - - if ($allJobsCompleted) - { - Write-InteractiveHost "`rDONE - Operation took $seconds $secondWord $Description" -NoNewline -f Green - - # We forcibly set Verbose to false here since we don't need it printed to the screen, since we just did above -- we just need to log it. - Write-Log -Message "DONE - Operation took $seconds $secondWord $Description" -Level Verbose -Verbose:$false - } - else - { - Write-InteractiveHost "`rDONE (FAILED) - Operation took $seconds $secondWord $Description" -NoNewline -f Red - - # We forcibly set Verbose to false here since we don't need it printed to the screen, since we just did above -- we just need to log it. - Write-Log -Message "DONE (FAILED) - Operation took $seconds $secondWord $Description" -Level Verbose -Verbose:$false - } - - Write-InteractiveHost "" -} - function Get-SHA512Hash { <# diff --git a/Telemetry.ps1 b/Telemetry.ps1 index 16efb7c5..857bc373 100644 --- a/Telemetry.ps1 +++ b/Telemetry.ps1 @@ -130,11 +130,6 @@ function Invoke-SendTelemetryEvent .PARAMETER TelemetryEvent The raw object representing the event data to send to Application Insights. - .PARAMETER NoStatus - If this switch is specified, long-running commands will run on the main thread - with no commandline status update. When not specified, those commands run in - the background, enabling the command prompt to provide status information. - .OUTPUTS [PSCustomObject] - The result of the REST operation, in whatever form it comes in. @@ -145,17 +140,11 @@ function Invoke-SendTelemetryEvent easier to share out with other PowerShell projects. #> [CmdletBinding(SupportsShouldProcess)] - [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSAvoidGlobalVars", "", Justification="We use global variables sparingly and intentionally for module configuration, and employ a consistent naming convention.")] param( [Parameter(Mandatory)] - [PSCustomObject] $TelemetryEvent, - - [switch] $NoStatus + [PSCustomObject] $TelemetryEvent ) - # Temporarily forcing NoStatus to always be true to see if it improves user experience. - $NoStatus = $true - $jsonConversionDepth = 20 # Seems like it should be more than sufficient $uri = 'https://dc.services.visualstudio.com/v2/track' $method = 'POST' @@ -164,120 +153,31 @@ function Invoke-SendTelemetryEvent $body = ConvertTo-Json -InputObject $TelemetryEvent -Depth $jsonConversionDepth -Compress $bodyAsBytes = [System.Text.Encoding]::UTF8.GetBytes($body) + if (-not $PSCmdlet.ShouldProcess($uri, "Invoke-WebRequest")) + { + return + } + try { Write-Log -Message "Sending telemetry event data to $uri [Timeout = $(Get-GitHubConfiguration -Name WebRequestTimeoutSec))]" -Level Verbose - if ($NoStatus) - { - if ($PSCmdlet.ShouldProcess($url, "Invoke-WebRequest")) - { - $params = @{} - $params.Add("Uri", $uri) - $params.Add("Method", $method) - $params.Add("Headers", $headers) - $params.Add("UseDefaultCredentials", $true) - $params.Add("UseBasicParsing", $true) - $params.Add("TimeoutSec", (Get-GitHubConfiguration -Name WebRequestTimeoutSec)) - $params.Add("Body", $bodyAsBytes) - - # Disable Progress Bar in function scope during Invoke-WebRequest - $ProgressPreference = 'SilentlyContinue' - - $result = Invoke-WebRequest @params - } - } - else - { - $jobName = "Invoke-SendTelemetryEvent-" + (Get-Date).ToFileTime().ToString() + $params = @{} + $params.Add("Uri", $uri) + $params.Add("Method", $method) + $params.Add("Headers", $headers) + $params.Add("UseDefaultCredentials", $true) + $params.Add("UseBasicParsing", $true) + $params.Add("TimeoutSec", (Get-GitHubConfiguration -Name WebRequestTimeoutSec)) + $params.Add("Body", $bodyAsBytes) - if ($PSCmdlet.ShouldProcess($jobName, "Start-Job")) - { - [scriptblock]$scriptBlock = { - param($Uri, $Method, $Headers, $BodyAsBytes, $TimeoutSec, $ScriptRootPath) - - # We need to "dot invoke" Helpers.ps1 and GitHubConfiguration.ps1 within - # the context of this script block since we're running in a different - # PowerShell process and need access to Get-HttpWebResponseContent and - # config values referenced within Write-Log. - . (Join-Path -Path $ScriptRootPath -ChildPath 'Helpers.ps1') - . (Join-Path -Path $ScriptRootPath -ChildPath 'GitHubConfiguration.ps1') - - $params = @{} - $params.Add("Uri", $Uri) - $params.Add("Method", $Method) - $params.Add("Headers", $Headers) - $params.Add("UseDefaultCredentials", $true) - $params.Add("UseBasicParsing", $true) - $params.Add("TimeoutSec", $TimeoutSec) - $params.Add("Body", $BodyAsBytes) - - try - { - # Disable Progress Bar in function scope during Invoke-WebRequest - $ProgressPreference = 'SilentlyContinue' - - Invoke-WebRequest @params - } - catch [System.Net.WebException] - { - # We need to access certain headers in the exception handling, - # but the actual *values* of the headers of a WebException don't get serialized - # when the RemoteException wraps it. To work around that, we'll extract the - # information that we actually care about *now*, and then we'll throw our own exception - # that is just a JSON object with the data that we'll later extract for processing in - # the main catch. - $ex = @{} - $ex.Message = $_.Exception.Message - $ex.StatusCode = $_.Exception.Response.StatusCode - $ex.StatusDescription = $_.Exception.Response.StatusDescription - $ex.InnerMessage = $_.ErrorDetails.Message - try - { - $ex.RawContent = Get-HttpWebResponseContent -WebResponse $_.Exception.Response - } - catch - { - Write-Log -Message "Unable to retrieve the raw HTTP Web Response:" -Exception $_ -Level Warning - } - - $jsonConversionDepth = 20 # Seems like it should be more than sufficient - throw (ConvertTo-Json -InputObject $ex -Depth $jsonConversionDepth) - } - } - - $null = Start-Job -Name $jobName -ScriptBlock $scriptBlock -Arg @( - $uri, - $method, - $headers, - $bodyAsBytes, - (Get-GitHubConfiguration -Name WebRequestTimeoutSec), - $PSScriptRoot) + # Disable Progress Bar in function scope during Invoke-WebRequest + $ProgressPreference = 'SilentlyContinue' - if ($PSCmdlet.ShouldProcess($jobName, "Wait-JobWithAnimation")) - { - $description = 'Sending telemetry data' - Wait-JobWithAnimation -Name $jobName -Description $Description - } - - if ($PSCmdlet.ShouldProcess($jobName, "Receive-Job")) - { - $result = Receive-Job $jobName -AutoRemoveJob -Wait -ErrorAction SilentlyContinue -ErrorVariable remoteErrors - } - } - - if ($remoteErrors.Count -gt 0) - { - throw $remoteErrors[0].Exception - } - } - - return $result + return Invoke-WebRequest @params } catch { - # We only know how to handle WebExceptions, which will either come in "pure" when running with -NoStatus, - # or will come in as a RemoteException when running normally (since it's coming from the asynchronous Job). $ex = $null $message = $null $statusCode = $null @@ -301,27 +201,6 @@ function Invoke-SendTelemetryEvent Write-Log -Message "Unable to retrieve the raw HTTP Web Response:" -Exception $_ -Level Warning } } - elseif (($_.Exception -is [System.Management.Automation.RemoteException]) -and - ($_.Exception.SerializedRemoteException.PSObject.TypeNames[0] -eq 'Deserialized.System.Management.Automation.RuntimeException')) - { - $ex = $_.Exception - try - { - $deserialized = $ex.Message | ConvertFrom-Json - $message = $deserialized.Message - $statusCode = $deserialized.StatusCode - $statusDescription = $deserialized.StatusDescription - $innerMessage = $deserialized.InnerMessage - $rawContent = $deserialized.RawContent - } - catch [System.ArgumentException] - { - # Will be thrown if $ex.Message isn't the JSON content we prepared - # in the System.Net.WebException handler earlier in $scriptBlock. - Write-Log -Exception $_ -Level Error - throw - } - } else { Write-Log -Exception $_ -Level Error @@ -402,32 +281,16 @@ function Set-TelemetryEvent A collection of name/value pair metrics (string/double) that should be associated with this event. - .PARAMETER NoStatus - If this switch is specified, long-running commands will run on the main thread - with no commandline status update. When not specified, those commands run in - the background, enabling the command prompt to provide status information. - .EXAMPLE Set-TelemetryEvent "zFooTest1" - Posts a "zFooTest1" event with the default set of properties and metrics. If the telemetry - client needs to be created to accomplish this, and the required assemblies are not available - on the local machine, the download status will be presented at the command prompt. + Posts a "zFooTest1" event with the default set of properties and metrics. .EXAMPLE Set-TelemetryEvent "zFooTest1" @{"Prop1" = "Value1"} Posts a "zFooTest1" event with the default set of properties and metrics along with an - additional property named "Prop1" with a value of "Value1". If the telemetry client - needs to be created to accomplish this, and the required assemblies are not available - on the local machine, the download status will be presented at the command prompt. - - .EXAMPLE - Set-TelemetryEvent "zFooTest1" -NoStatus - - Posts a "zFooTest1" event with the default set of properties and metrics. If the telemetry - client needs to be created to accomplish this, and the required assemblies are not available - on the local machine, the command prompt will appear to hang while they are downloaded. + additional property named "Prop1" with a value of "Value1". .NOTES Because of the short-running nature of this module, we always "flush" the events as soon @@ -441,9 +304,7 @@ function Set-TelemetryEvent [hashtable] $Properties = @{}, - [hashtable] $Metrics = @{}, - - [switch] $NoStatus + [hashtable] $Metrics = @{} ) if (Get-GitHubConfiguration -Name DisableTelemetry) @@ -478,7 +339,7 @@ function Set-TelemetryEvent Add-Member -InputObject $telemetryEvent.data.baseData -Name 'measurements' -Value ([PSCustomObject] $measurements) -MemberType NoteProperty -Force } - $null = Invoke-SendTelemetryEvent -TelemetryEvent $telemetryEvent -NoStatus:$NoStatus + $null = Invoke-SendTelemetryEvent -TelemetryEvent $telemetryEvent } catch { @@ -521,26 +382,11 @@ function Set-TelemetryException prevents that automatic flushing (helpful in the scenario where the exception occurred when trying to do the actual Flush). - .PARAMETER NoStatus - If this switch is specified, long-running commands will run on the main thread - with no commandline status update. When not specified, those commands run in - the background, enabling the command prompt to provide status information. - .EXAMPLE Set-TelemetryException $_ Used within the context of a catch statement, this will post the exception that just - occurred, along with a default set of properties. If the telemetry client needs to be - created to accomplish this, and the required assemblies are not available on the local - machine, the download status will be presented at the command prompt. - - .EXAMPLE - Set-TelemetryException $_ -NoStatus - - Used within the context of a catch statement, this will post the exception that just - occurred, along with a default set of properties. If the telemetry client needs to be - created to accomplish this, and the required assemblies are not available on the local - machine, the command prompt will appear to hang while they are downloaded. + occurred, along with a default set of properties. .NOTES Because of the short-running nature of this module, we always "flush" the events as soon @@ -554,9 +400,7 @@ function Set-TelemetryException [string] $ErrorBucket, - [hashtable] $Properties = @{}, - - [switch] $NoStatus + [hashtable] $Properties = @{} ) if (Get-GitHubConfiguration -Name DisableTelemetry) @@ -627,7 +471,7 @@ function Set-TelemetryException } Add-Member -InputObject $telemetryEvent.data.baseData -Name 'exceptions' -Value @($exceptionData) -MemberType NoteProperty -Force - $null = Invoke-SendTelemetryEvent -TelemetryEvent $telemetryEvent -NoStatus:$NoStatus + $null = Invoke-SendTelemetryEvent -TelemetryEvent $telemetryEvent } catch { diff --git a/Tests/Common.ps1 b/Tests/Common.ps1 index 2e5d5512..054eb7e8 100644 --- a/Tests/Common.ps1 +++ b/Tests/Common.ps1 @@ -98,7 +98,7 @@ function Initialize-CommonTestSetup Reset-GitHubConfiguration Set-GitHubConfiguration -DisableTelemetry # We don't want UT's to impact telemetry Set-GitHubConfiguration -LogRequestBody # Make it easier to debug UT failures - Set-GitHubConfiguration -DefaultNoStatus # Status corrupts the raw CI logs for Linux and Mac, and makes runs take slightly longer. + Set-GitHubConfiguration -MultiRequestProgressThreshold 0 # Status corrupts the raw CI logs for Linux and Mac, and makes runs take slightly longer. Set-GitHubConfiguration -DisableUpdateCheck # The update check is unnecessary during tests. } diff --git a/Tests/GitHubContents.tests.ps1 b/Tests/GitHubContents.tests.ps1 index ba1ef8d4..c1b9661e 100644 --- a/Tests/GitHubContents.tests.ps1 +++ b/Tests/GitHubContents.tests.ps1 @@ -358,7 +358,6 @@ try CommitterEmail = $committerEmail authorName = $authorName authorEmail = $authorEmail - NoStatus = $true } $result = Set-GitHubContent @setGitHubContentParms From 79e5ac2fccb98405760e72c3f719ad424909a0dd Mon Sep 17 00:00:00 2001 From: Howard Wolosky Date: Wed, 1 Jul 2020 15:40:25 -0700 Subject: [PATCH 50/60] CI Build updates: build queuing and Windows PS7 (#260) This updates the CI build with two new features: 1. **Validation against Windows PS7 (PowerShell Core)** This pipeline previously had three platforms that it tested against: * `Windows PS5` * `Linux PS7 (PowerShell Core)` * `macOS PS7 (PowerShell Core)` With this change, we add a fourth platform: `Windows PS7 (PowerShell Core)` 2. **Build queuing** The unit tests operate against live GitHub accounts (as opposed to mocking out the execution of the API calls). Each platform has its own account that it operates against to alllow each platform to be tested in parallel. However, if more than one build is queued at once, then the builds can start to stomp over the expected state in the tests accounts. This change adds a new `job` to the pipeline which will create a "queue" of running builds by only allowing a build to continue processing once all previously queued builds have completed. Initial idea came from here: https://developercommunity.visualstudio.com/idea/365730/prevent-parallel-execution-of-the-same-build-defin.html - Fixes #239 --- build/pipelines/azure-pipelines.ci.yaml | 41 ++++- .../templates/run-staticAnalysis.yaml | 33 +++- build/pipelines/templates/run-unitTests.yaml | 26 ++- .../verify-testConfigSettingsHash.yaml | 64 ++++--- .../templates/wait-runningBuild.yaml | 39 ++++ build/scripts/Wait-RunningBuild.ps1 | 168 ++++++++++++++++++ 6 files changed, 327 insertions(+), 44 deletions(-) create mode 100644 build/pipelines/templates/wait-runningBuild.yaml create mode 100644 build/scripts/Wait-RunningBuild.ps1 diff --git a/build/pipelines/azure-pipelines.ci.yaml b/build/pipelines/azure-pipelines.ci.yaml index 19838c68..81adf58d 100644 --- a/build/pipelines/azure-pipelines.ci.yaml +++ b/build/pipelines/azure-pipelines.ci.yaml @@ -19,9 +19,42 @@ trigger: #- master jobs: -- job: Windows +- job: waitForRunningBuilds + displayName: 'Wait for Running Builds' pool: vmImage: 'windows-latest' + timeoutInMinutes: 360 + continueOnError: true + steps: + - template: ./templates/wait-runningBuild.yaml + parameters: + buildQueryPersonalAccessToken: $(BuildQueryPersonalAccessToken) + +- job: windowsWPS + displayName: 'Windows [Windows PowerShell]' + pool: + vmImage: 'windows-latest' + dependsOn: waitForRunningBuilds + steps: + - template: ./templates/verify-testConfigSettingsHash.yaml + parameters: + usePowerShellCore: false + - template: ./templates/run-staticAnalysis.yaml + parameters: + usePowerShellCore: false + - template: ./templates/run-unitTests.yaml + parameters: + gitHubAccessToken: $(WindowsPS5CIGitHubAccessToken) + gitHubOwnerName: $(WindowsPS5CIGitHubOwnerName) + gitHubOrganizationName: $(WindowsPS5CIGitHubOrganizationName) + platformName: 'Windows' + usePowerShellCore: false + +- job: windows + displayName: 'Windows [PowerShell Core]' + pool: + vmImage: 'windows-latest' + dependsOn: waitForRunningBuilds steps: - template: ./templates/verify-testConfigSettingsHash.yaml - template: ./templates/run-staticAnalysis.yaml @@ -32,9 +65,11 @@ jobs: gitHubOrganizationName: $(WindowsCIGitHubOrganizationName) platformName: 'Windows' -- job: Linux +- job: linux + displayName: 'Linux [PowerShell Core]' pool: vmImage: 'ubuntu-latest' + dependsOn: waitForRunningBuilds steps: - template: ./templates/verify-testConfigSettingsHash.yaml - template: ./templates/run-staticAnalysis.yaml @@ -46,8 +81,10 @@ jobs: platformName: 'Linux' - job: macOS + displayName: 'macOS [PowerShell Core]' pool: vmImage: 'macOS-latest' + dependsOn: waitForRunningBuilds steps: - template: ./templates/verify-testConfigSettingsHash.yaml - template: ./templates/run-staticAnalysis.yaml diff --git a/build/pipelines/templates/run-staticAnalysis.yaml b/build/pipelines/templates/run-staticAnalysis.yaml index 5325a767..9b9d6784 100644 --- a/build/pipelines/templates/run-staticAnalysis.yaml +++ b/build/pipelines/templates/run-staticAnalysis.yaml @@ -3,19 +3,34 @@ # report the results. # +parameters: +- name: 'usePowerShellCore' + default: true + type: boolean + steps: - - powershell: | - Install-Module -Name PSScriptAnalyzer -Repository PSGallery -Scope CurrentUser -Force -Verbose + - task: PowerShell@2 displayName: 'Install PSScriptAnalyzer' + inputs: + pwsh: eq('${{ parameters.usePowerShellCore }}', true) + errorActionPreference: 'stop' + targetType: 'inline' + script: | + Install-Module -Name PSScriptAnalyzer -Repository PSGallery -Scope CurrentUser -Force -Verbose - - powershell: | - $results = try { Invoke-ScriptAnalyzer -Settings ./PSScriptAnalyzerSettings.psd1 -Path ./ –Recurse -ErrorAction Stop } catch { 'Unexpected Error'; $_.Exception.StackTrace; if ($IsCoreCLR) { Get-Error }; throw } - $results | ForEach-Object { Write-Host "##vso[task.logissue type=$($_.Severity);sourcepath=$($_.ScriptPath);linenumber=$($_.Line);columnnumber=$($_.Column);]$($_.Message)" } - - $null = New-Item -Path ..\ -Name ScriptAnalyzer -ItemType Directory -Force - ./build/scripts/ConvertTo-NUnitXml.ps1 -ScriptAnalyzerResult $results -Path ../ScriptAnalyzer/test-results.xml - workingDirectory: '$(System.DefaultWorkingDirectory)' + - task: PowerShell@2 displayName: 'Run Static Code Analysis (PSScriptAnalyzer)' + inputs: + pwsh: eq('${{ parameters.usePowerShellCore }}', true) + errorActionPreference: 'stop' + workingDirectory: '$(System.DefaultWorkingDirectory)' + targetType: 'inline' + script: | + $results = try { Invoke-ScriptAnalyzer -Settings ./PSScriptAnalyzerSettings.psd1 -Path ./ –Recurse -ErrorAction Stop } catch { 'Unexpected Error'; $_.Exception.StackTrace; if ($IsCoreCLR) { Get-Error }; throw } + $results | ForEach-Object { Write-Host "##vso[task.logissue type=$($_.Severity);sourcepath=$($_.ScriptPath);linenumber=$($_.Line);columnnumber=$($_.Column);]$($_.Message)" } + + $null = New-Item -Path ..\ -Name ScriptAnalyzer -ItemType Directory -Force + ./build/scripts/ConvertTo-NUnitXml.ps1 -ScriptAnalyzerResult $results -Path ../ScriptAnalyzer/test-results.xml - task: PublishTestResults@2 displayName: 'Publish ScriptAnalyzer Test Results' diff --git a/build/pipelines/templates/run-unitTests.yaml b/build/pipelines/templates/run-unitTests.yaml index dc7d69ff..7b2b39fd 100644 --- a/build/pipelines/templates/run-unitTests.yaml +++ b/build/pipelines/templates/run-unitTests.yaml @@ -23,17 +23,31 @@ parameters: - name: 'platformName' default: 'Windows' type: string +- name: 'usePowerShellCore' + default: false + type: boolean steps: - - powershell: | - Install-Module -Name Pester -Repository PSGallery -Scope CurrentUser -AllowClobber -SkipPublisherCheck -RequiredVersion 4.10.1 -Force -Verbose + - task: PowerShell@2 displayName: 'Install Pester' + inputs: + pwsh: eq('${{ parameters.usePowerShellCore }}', true) + errorActionPreference: 'stop' + workingDirectory: '$(System.DefaultWorkingDirectory)' + targetType: 'inline' + script: | + Install-Module -Name Pester -Repository PSGallery -Scope CurrentUser -AllowClobber -SkipPublisherCheck -RequiredVersion 4.10.1 -Force -Verbose - - powershell: | - $null = New-Item -Path ..\ -Name Pester -ItemType Directory -Force - Invoke-Pester -CodeCoverage .\*.ps*1 -CodeCoverageOutputFile ../Pester/coverage.xml -CodeCoverageOutputFileFormat JaCoCo -EnableExit -Strict -OutputFile ../Pester/test-results.xml -OutputFormat NUnitXml - workingDirectory: '$(System.DefaultWorkingDirectory)' + - task: PowerShell@2 displayName: 'Run Unit Tests via Pester' + inputs: + pwsh: eq('${{ parameters.usePowerShellCore }}', true) + errorActionPreference: 'stop' + workingDirectory: '$(System.DefaultWorkingDirectory)' + targetType: 'inline' + script: | + $null = New-Item -Path ..\ -Name Pester -ItemType Directory -Force + Invoke-Pester -CodeCoverage .\*.ps*1 -CodeCoverageOutputFile ../Pester/coverage.xml -CodeCoverageOutputFileFormat JaCoCo -EnableExit -Strict -OutputFile ../Pester/test-results.xml -OutputFormat NUnitXml env: ciAccessToken: ${{ parameters.gitHubAccessToken }} ciOwnerName: ${{ parameters.gitHubOwnerName }} diff --git a/build/pipelines/templates/verify-testConfigSettingsHash.yaml b/build/pipelines/templates/verify-testConfigSettingsHash.yaml index 652aa144..a6065935 100644 --- a/build/pipelines/templates/verify-testConfigSettingsHash.yaml +++ b/build/pipelines/templates/verify-testConfigSettingsHash.yaml @@ -4,35 +4,45 @@ # the same in order to ensure the correct warning messages are presented to developers running UTs. # +parameters: +- name: 'usePowerShellCore' + default: true + type: boolean + steps: - - powershell: | - . ./Helpers.ps1 - $content = Get-Content -Path ./Tests/Config/Settings.ps1 -Raw -Encoding UTF8 - $hash = Get-SHA512Hash -PlainText $content - $configurationFile = Get-Content -Path ./GitHubConfiguration.ps1 -Raw -Encoding Utf8 - if($configurationFile -match "'testConfigSettingsHash' = '([^']+)'") - { - if($hash -ne $Matches[1]) + - task: PowerShell@2 + displayName: 'Set GitHubConfiguration.ps1 testConfigSettingsHash value.' + inputs: + pwsh: eq('${{ parameters.usePowerShellCore }}', true) + errorActionPreference: 'stop' + workingDirectory: '$(System.DefaultWorkingDirectory)' + targetType: 'inline' + script: | + . ./Helpers.ps1 + $content = Get-Content -Path ./Tests/Config/Settings.ps1 -Raw -Encoding UTF8 + $hash = Get-SHA512Hash -PlainText $content + $configurationFile = Get-Content -Path ./GitHubConfiguration.ps1 -Raw -Encoding Utf8 + if($configurationFile -match "'testConfigSettingsHash' = '([^']+)'") + { + if($hash -ne $Matches[1]) + { + $configHash = $Matches[1] + $message = @( + "`$testConfigSettingsHash value does not match the Get-SHA512Hash of file ./Tests/Config/Settings.ps1. If the contents of ", + "Settings.ps1 has been updated, please update the `$testConfigSettingsHash value in ./GitHubConfiguration.ps1's ", + "Import-GitHubConfiguration function. You can generate the new hash value using the commands:", + "", + "`t. ./Helpers.ps1", + "`tGet-SHA512Hash -PlainText (Get-Content -Path ./Tests/Config/Settings.ps1 -Raw -Encoding Utf8)", + "", + "`$testConfigSettingsHash = $configHash", + "Get-SHA512Hash(Settings.ps1) = $hash") + throw ($message -join [Environment]::NewLine) + } + } else { - $configHash = $Matches[1] $message = @( - "`$testConfigSettingsHash value does not match the Get-SHA512Hash of file ./Tests/Config/Settings.ps1. If the contents of ", - "Settings.ps1 has been updated, please update the `$testConfigSettingsHash value in ./GitHubConfiguration.ps1's ", - "Import-GitHubConfiguration function. You can generate the new hash value using the commands:", - "", - "`t. ./Helpers.ps1", - "`tGet-SHA512Hash -PlainText (Get-Content -Path ./Tests/Config/Settings.ps1 -Raw -Encoding Utf8)", - "", - "`$testConfigSettingsHash = $configHash", - "Get-SHA512Hash(Settings.ps1) = $hash") + "`$testConfigSettingsHash value not found in ./GitHubConfiguration.ps1. Please ensure ", + "the default hash value is set within the Import-GitHubConfiguration function.") throw ($message -join [Environment]::NewLine) } - } else - { - $message = @( - "`$testConfigSettingsHash value not found in ./GitHubConfiguration.ps1. Please ensure ", - "the default hash value is set within the Import-GitHubConfiguration function.") - throw ($message -join [Environment]::NewLine) - } - displayName: 'Set GitHubConfiguration.ps1 testConfigSettingsHash value.' - diff --git a/build/pipelines/templates/wait-runningBuild.yaml b/build/pipelines/templates/wait-runningBuild.yaml new file mode 100644 index 00000000..47306e17 --- /dev/null +++ b/build/pipelines/templates/wait-runningBuild.yaml @@ -0,0 +1,39 @@ +# +# This template contains the necessary jobs to create a queue for this pipeline and ensure that +# there is never more than one concurrent build actively running at any given time. +# + +#-------------------------------------------------------------------------------------------------- +# This template is dependent on the following pipeline variables being configured within the pipeline. +# +# 1. buildQueryPersonalAccessToken - An Azure DevOps Personal Access Token with Build READ permission. +# It should be configured as a "secret". +#-------------------------------------------------------------------------------------------------- + +parameters: +- name: 'buildQueryPersonalAccessToken' + type: string +- name: 'usePowerShellCore' + default: true + type: boolean + +steps: + - task: PowerShell@2 + displayName: 'Wait for previously queued builds' + inputs: + pwsh: eq('${{ parameters.usePowerShellCore }}', true) + errorActionPreference: 'stop' + workingDirectory: '$(System.DefaultWorkingDirectory)' + targetType: 'inline' + script: | + $params = @{ + PersonalAccessToken = $env:buildReadAccessToken + OrganizationName = 'ms' + ProjectName = 'PowerShellForGitHub' + BuildDefinitionId = $(System.DefinitionId) + BuildId = $(Build.BuildId) + } + + ./build/scripts/Wait-RunningBuild.ps1 @params + env: + buildReadAccessToken: ${{ parameters.buildQueryPersonalAccessToken }} diff --git a/build/scripts/Wait-RunningBuild.ps1 b/build/scripts/Wait-RunningBuild.ps1 new file mode 100644 index 00000000..9094506c --- /dev/null +++ b/build/scripts/Wait-RunningBuild.ps1 @@ -0,0 +1,168 @@ +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. + +<# + .DESCRIPTION + This script will enforce the concept of a queue for a build pipeline to ensure that + builds do not actively run concurrently. + + .PARAMETER PersonalAccessToken + A token that has Build READ permission. + + .PARAMETER OrganizationName + The name of the organization that this project is a part of. + + .PARAMETER ProjectName + The name of the project that this build pipeline can be found in. + + .PARAMETER BuildDefinitionId + The ID for the build definition that we are enforcing a queue on. + + .PARAMETER BuildId + The ID for this build. + + .PARAMETER NumSecondsSleepBetweenPolling + The number of seconds to sleep before polling attempt to check build pipeline status again. + + .PARAMETER MaxRetriesBeforeStarting + The number of successive retries that will be attempted to query for build pipeline status + before just allowing the build to start. + + .EXAMPLE + $params = @{ + PersonalAccessToken = $env:buildReadAccessToken + OrganizationName = 'ms' + ProjectName = 'PowerShellForGitHub' + BuildDefinitionId = $(System.DefinitionId) + BuildId = $(Build.BuildId) + } + ./Wait-RunningBuilds.ps1 @params +#> +[CmdletBinding()] +[Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSAvoidUsingWriteHost", "", Justification = "This is the preferred way of writing output for Azure DevOps.")] +param( + [Parameter(Mandatory)] + [ValidateNotNullOrEmpty()] + [string] $PersonalAccessToken, + + [Parameter(Mandatory)] + [ValidateNotNullOrEmpty()] + [string] $OrganizationName, + + [Parameter(Mandatory)] + [ValidateNotNullOrEmpty()] + [string] $ProjectName, + + [Parameter(Mandatory)] + [int64] $BuildDefinitionId, + + [Parameter(Mandatory)] + [int64] $BuildId, + + [int] $NumSecondsSleepBetweenPolling = 30, + + [int] $MaxRetriesBeforeStarting = 3 +) + +Write-Host '[Wait-RunningBuilds] - Starting' + +$elapsedTimeFormat = '{0:hh\:mm\:ss}' +$stopwatch = New-Object -TypeName System.Diagnostics.Stopwatch +$stopwatch.Start() + +$url = "https://dev.azure.com/$OrganizationName/$ProjectName/_apis/build/builds?api-version=5.1&definitions=$BuildDefinitionId" +$headers = @{ + 'Authorization' = [Convert]::ToBase64String([Text.Encoding]::ASCII.GetBytes(":$PersonalAccessToken")) +} + +$remainingRetries = $MaxRetriesBeforeStarting +do +{ + try + { + $params = @{ + Uri = $url + Method = 'Get' + ContentType = 'application/json' + Headers = $headers + UseBasicParsing = $true + } + + $builds = Invoke-RestMethod @params + + $remainingRetries = $MaxRetriesBeforeStarting # successfully got a result. Reset remaining retries + + $thisBuild = $builds.value | Where-Object { $_.id -eq $BuildId } + $runningBuilds = @($builds.value | Where-Object { $_.status -eq 'inProgress' }) + $currentRunningBuild = $runningBuilds | Sort-Object -Property 'Id' | Select-Object -First 1 + + if ($null -eq $currentRunningBuild) + { + Write-Host 'Failed to identify the currently running build. To prevent an indefinite wait, allowing this build to start.' + break + } + elseif ($BuildId -ne $currentRunningBuild.id) + { + $buildsAheadInQueue = @($runningBuilds | Where-Object { $_.id -lt $BuildId }) + + # We want to display how long the current build has been running for _actively_, + # so we need to take into account if it had been queued while the previous build was + # running and thus subtract that extra time. + $currentRunningBuildStartTime = Get-Date -Date $currentRunningBuild.startTime + $lastCompletedBuild = $builds.value | + Where-Object { $_.status -ne 'inProgress' } | + Select-Object -First 1 + if ($null -ne $lastCompletedBuild) + { + $lastCompletedBuildTime = Get-Date -Date $lastCompletedBuild.finishTime + if ($lastCompletedBuildTime -gt $currentRunningBuildStartTime) + { + $waitedDuration = $lastCompletedBuildTime - $currentRunningBuildStartTime + $currentRunningBuildStartTime.AddMilliseconds($waitedDuration.TotalMilliseconds) + } + } + + $currentRunningBuildElapsedTime = New-TimeSpan -Start $currentRunningBuildStartTime -End (Get-Date) + $currentRunningBuildElapsedTimeFormatted = $elapsedTimeFormat -f $currentRunningBuildElapsedTime + + $timeWaited = New-TimeSpan -Start $thisBuild.startTime -End (Get-Date) + $timeWaitedFormatted = $elapsedTimeFormat -f $timeWaited + + $message = @( + "* Time: $(Get-Date -Format 'o')", + " This build: $($thisBuild.id) ($($thisBuild.buildNumber)) [Waiting for $timeWaitedFormatted]", + " Builds ahead in queue: $($buildsAheadInQueue.buildNumber -join ', ')", + " Total queued builds: $($runningBuilds.Count - 1)", + " Currently running build: $($currentRunningBuild.id) ($($currentRunningBuild.buildNumber)) [Running for $currentRunningBuildElapsedTimeFormatted]", + " Waiting $NumSecondsSleepBetweenPolling seconds before polling build status for this pipeline again...", + '--------------------------') + Write-Host ($message -join [Environment]::NewLine) + } + else + { + break + } + } + catch + { + $remainingRetries-- + if ($remainingRetries -lt 0) + { + Write-Host 'Still unable to retrieve build status for this pipeline. Exhausted retries. To prevent an indefinite wait, allowing this build to start.' + break + } + else + { + Write-Host "Failed to get build status for this pipeline. Will try again in $NumSecondsSleepBetweenPolling seconds. $remainingRetries retries remaining." + } + } + + Start-Sleep -Seconds $NumSecondsSleepBetweenPolling +} +while ($true) + +$stopwatch.Stop() +$timeWaitedFormatted = $elapsedTimeFormat -f $stopwatch.Elapsed +Write-Host "Waiting completed after $timeWaitedFormatted. Starting this build." + +Write-Host '[Wait-RunningBuilds] - Exiting' \ No newline at end of file From 683187a94f05b7c69bc6ca3459ce615936f5a0d2 Mon Sep 17 00:00:00 2001 From: Howard Wolosky Date: Thu, 9 Jul 2020 17:19:21 -0700 Subject: [PATCH 51/60] Fix handling of numerical configuration properties across PS5 and PS7 (#262) `ConvertFrom-Json` defaults numbers to different types depending on the PowerShell version. PS5 defaults numbers to `[Int32]` while PS7 defaults numbers to `[Int64]`, but both versions default to `[Int32]` in a `[PSCustomObject]`. Due to this, if you had a number config value saved, on PS7 it would be ignored and the default value would be used because the expected and actual types wouldn't match. I've updated `Resolve-PropertyValue` to have equivalence logic for number types to avoid this issue. --- GitHubConfiguration.ps1 | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/GitHubConfiguration.ps1 b/GitHubConfiguration.ps1 index 7c50f858..944b2621 100644 --- a/GitHubConfiguration.ps1 +++ b/GitHubConfiguration.ps1 @@ -466,16 +466,18 @@ function Resolve-PropertyValue if ($Type -eq 'Boolean') { $typeType = [Boolean] } if ($Type -eq 'Int32') { $typeType = [Int32] } if ($Type -eq 'Int64') { $typeType = [Int64] } + $numberEquivalents = @('Int32', 'Int64', 'long', 'int') if (Test-PropertyExists -InputObject $InputObject -Name $Name) { - if ($InputObject.$Name -is $typeType) + if (($InputObject.$Name -is $typeType) -or + (($Type -in $numberEquivalents) -and ($InputObject.$Name.GetType().Name -in $numberEquivalents))) { return $InputObject.$Name } else { - $message = "The locally cached $Name configuration was not of type $Type. Reverting to default value." + $message = "The locally cached $Name configuration was not of type $Type (it was $($InputObject.$Name.GetType())). Reverting to default value." Write-Log -Message $message -Level Warning return $DefaultValue } From fcfe0c73b03be8f60ed0c75ced572240ff6769e1 Mon Sep 17 00:00:00 2001 From: Howard Wolosky Date: Wed, 15 Jul 2020 09:47:46 -0700 Subject: [PATCH 52/60] Formatter guidelines and CI job timeout extensions (#263) * Add Formatter guidelines to CONTRIBUTING and the PR template * Increase permitted time per-platform for CI validation from 60 -> 120 min --- .github/PULL_REQUEST_TEMPLATE.md | 1 + CONTRIBUTING.md | 23 ++++++++++++++++++++ build/pipelines/azure-pipelines.ci.yaml | 4 ++++ build/pipelines/azure-pipelines.release.yaml | 1 + 4 files changed, 29 insertions(+) diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index d4ac695e..6384fe98 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -34,6 +34,7 @@ - [ ] Comment-based help added/updated, including examples. - [ ] [Static analysis](https://github.com/microsoft/PowerShellForGitHub/blob/master/CONTRIBUTING.md#static-analysis) is reporting back clean. - [ ] New/changed code adheres to our [coding guidelines](https://github.com/microsoft/PowerShellForGitHub/blob/master/CONTRIBUTING.md#coding-guidelines). +- [ ] [Formatters were created](https://github.com/microsoft/PowerShellForGitHub/blob/master/CONTRIBUTING.md#formatters) for any new types being added. - [ ] New/changed code continues to [support the pipeline](https://github.com/microsoft/PowerShellForGitHub/blob/master/CONTRIBUTING.md#pipeline-support). - [ ] Changes to the manifest file follow the [manifest guidance](https://github.com/microsoft/PowerShellForGitHub/blob/master/CONTRIBUTING.md#module-manifest). - [ ] Unit tests were added/updated and are all passing. See [testing guidelines](https://github.com/microsoft/PowerShellForGitHub/blob/master/CONTRIBUTING.md#testing). This includes making sure that all pipeline input variations have been covered. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 3195ab63..b547f708 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -24,6 +24,7 @@ Looking for information on how to use this module? Head on over to [README.md]( * [Code Comments](#code-comments) * [Debugging Tips](#debugging-tips) * [Pipeline Support](#pipeline-support) +* [Formatters](#formatters) * [Testing](#testing) * [Installing Pester](#installing-pester) * [Configuring Your Environment](#configuring-your-environment) @@ -348,6 +349,28 @@ new functionality added to the module embraces this design. ---------- +### Formatters + +[Our goal](https://github.com/microsoft/PowerShellForGitHub/issues/27) is to have automattic +formatting for all `GitHub.*` types that this project defines. + +Formatting was first introduced to the project with [#205](https://github.com/microsoft/PowerShellForGitHub/pull/205), +and succcesive PR's which introduce new types have added their additional formatters as well. +Eventually we will get Formatters for all previously introduced types as well. + +Formatter files can be found in [/Formatters](https://github.com/microsoft/PowerShellForGitHub/tree/master/Formatters). + +When adding a new formatter file, keep the following in mind: + +* One formatter file per PowerShell module file, and name them similarly + (e.g. `GitHubRepositories.ps1` gets a `Formatters\GitHubRepositories.Format.ps1xml` file) +* Be sure to add the formatter file to the manifest (common mistake to forget this). +* Don't display all the type's properties ...just choose the most relevant pieces of information; + sometimes this might mean using a script block to grab an inner-property or to perform a + calculation. + +---------- + ### Testing [![Build status](https://dev.azure.com/ms/PowerShellForGitHub/_apis/build/status/PowerShellForGitHub-CI?branchName=master)](https://dev.azure.com/ms/PowerShellForGitHub/_build/latest?definitionId=109&branchName=master) [![Azure DevOps tests](https://img.shields.io/azure-devops/tests/ms/PowerShellForGitHub/109/master)](https://dev.azure.com/ms/PowerShellForGitHub/_build/latest?definitionId=109&branchName=master) diff --git a/build/pipelines/azure-pipelines.ci.yaml b/build/pipelines/azure-pipelines.ci.yaml index 81adf58d..33cf7f74 100644 --- a/build/pipelines/azure-pipelines.ci.yaml +++ b/build/pipelines/azure-pipelines.ci.yaml @@ -35,6 +35,7 @@ jobs: pool: vmImage: 'windows-latest' dependsOn: waitForRunningBuilds + timeoutInMinutes: 120 steps: - template: ./templates/verify-testConfigSettingsHash.yaml parameters: @@ -55,6 +56,7 @@ jobs: pool: vmImage: 'windows-latest' dependsOn: waitForRunningBuilds + timeoutInMinutes: 120 steps: - template: ./templates/verify-testConfigSettingsHash.yaml - template: ./templates/run-staticAnalysis.yaml @@ -70,6 +72,7 @@ jobs: pool: vmImage: 'ubuntu-latest' dependsOn: waitForRunningBuilds + timeoutInMinutes: 120 steps: - template: ./templates/verify-testConfigSettingsHash.yaml - template: ./templates/run-staticAnalysis.yaml @@ -85,6 +88,7 @@ jobs: pool: vmImage: 'macOS-latest' dependsOn: waitForRunningBuilds + timeoutInMinutes: 120 steps: - template: ./templates/verify-testConfigSettingsHash.yaml - template: ./templates/run-staticAnalysis.yaml diff --git a/build/pipelines/azure-pipelines.release.yaml b/build/pipelines/azure-pipelines.release.yaml index ff0b1010..9bdfa841 100644 --- a/build/pipelines/azure-pipelines.release.yaml +++ b/build/pipelines/azure-pipelines.release.yaml @@ -28,6 +28,7 @@ jobs: - job: Validate pool: vmImage: 'windows-latest' + timeoutInMinutes: 120 steps: - template: ./templates/run-staticAnalysis.yaml - template: ./templates/run-unitTests.yaml From d76f54b08ea7c3f3355ec188827fadc0035d0595 Mon Sep 17 00:00:00 2001 From: Simon Heather <32168619+X-Guardian@users.noreply.github.com> Date: Thu, 16 Jul 2020 19:42:21 +0100 Subject: [PATCH 53/60] GitHubBranches: Add New/Remove-GitHubRepositoryBranch Functions (#256) Adds the following functions to the `GitHubBranches` module: * `New-GitHubRepositoryBranch` * `Remove-GitHubRepositoryBranch` #### References [GitHub Refs API](https://developer.github.com/v3/git/refs/) --- GitHubBranches.ps1 | 364 ++++++++++++++++++++++++++++++++- PowerShellForGitHub.psd1 | 6 + Tests/GitHubBranches.tests.ps1 | 233 ++++++++++++++++++++- USAGE.md | 21 +- 4 files changed, 616 insertions(+), 8 deletions(-) diff --git a/GitHubBranches.ps1 b/GitHubBranches.ps1 index f595ace7..2e52cf40 100644 --- a/GitHubBranches.ps1 +++ b/GitHubBranches.ps1 @@ -154,6 +154,352 @@ filter Get-GitHubRepositoryBranch return (Invoke-GHRestMethodMultipleResult @params | Add-GitHubBranchAdditionalProperties) } +filter New-GitHubRepositoryBranch +{ + <# + .SYNOPSIS + Creates a new branch for a given GitHub repository. + + .DESCRIPTION + Creates a new branch for a given GitHub repository. + + The Git repo for this module can be found here: http://aka.ms/PowerShellForGitHub + + .PARAMETER OwnerName + Owner of the repository. + If not supplied here, the DefaultOwnerName configuration property value will be used. + + .PARAMETER RepositoryName + Name of the repository. + If not supplied here, the DefaultRepositoryName configuration property value will be used. + + .PARAMETER Uri + Uri for the repository. + The OwnerName and RepositoryName will be extracted from here instead of needing to provide + them individually. + + .PARAMETER BranchName + The name of the origin branch to create the new branch from. + + .PARAMETER TargetBranchName + Name of the branch to be created. + + .PARAMETER AccessToken + If provided, this will be used as the AccessToken for authentication with the + REST Api. Otherwise, will attempt to use the configured value or will run unauthenticated. + + .PARAMETER NoStatus + If this switch is specified, long-running commands will run on the main thread + with no commandline status update. When not specified, those commands run in + the background, enabling the command prompt to provide status information. + If not supplied here, the DefaultNoStatus configuration property value will be used. + + .INPUTS + GitHub.Branch + GitHub.Content + GitHub.Event + GitHub.Issue + GitHub.IssueComment + GitHub.Label + GitHub.Milestone + GitHub.PullRequest + GitHub.Project + GitHub.ProjectCard + GitHub.ProjectColumn + GitHub.Release + GitHub.Repository + + .OUTPUTS + GitHub.Branch + + .EXAMPLE + New-GitHubRepositoryBranch -OwnerName microsoft -RepositoryName PowerShellForGitHub -TargetBranchName new-branch + + Creates a new branch in the specified repository from the master branch. + + .EXAMPLE + New-GitHubRepositoryBranch -Uri 'https://github.com/microsoft/PowerShellForGitHub' -BranchName develop -TargetBranchName new-branch + + Creates a new branch in the specified repository from the 'develop' origin branch. + + .EXAMPLE + $repo = Get-GithubRepository -Uri https://github.com/You/YourRepo + $repo | New-GitHubRepositoryBranch -TargetBranchName new-branch + + You can also pipe in a repo that was returned from a previous command. +#> + [CmdletBinding( + SupportsShouldProcess, + DefaultParameterSetName = 'Elements', + PositionalBinding = $false + )] + [OutputType({$script:GitHubBranchTypeName})] + [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '', + Justification = 'Methods called within here make use of PSShouldProcess, and the switch is + passed on to them inherently.')] + [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSReviewUnusedParameter', '', + Justification = 'One or more parameters (like NoStatus) are only referenced by helper + methods which get access to it from the stack via Get-Variable -Scope 1.')] + [Alias('New-GitHubBranch')] + param( + [Parameter(ParameterSetName = 'Elements')] + [string] $OwnerName, + + [Parameter(ParameterSetName = 'Elements')] + [string] $RepositoryName, + + [Parameter( + Mandatory, + ValueFromPipelineByPropertyName, + Position = 1, + ParameterSetName = 'Uri')] + [Alias('RepositoryUrl')] + [string] $Uri, + + [string] $BranchName = 'master', + + [Parameter( + Mandatory, + ValueFromPipeline, + Position = 2)] + [string] $TargetBranchName, + + [string] $AccessToken, + + [switch] $NoStatus + ) + + Write-InvocationLog + + $elements = Resolve-RepositoryElements + $OwnerName = $elements.ownerName + $RepositoryName = $elements.repositoryName + + $telemetryProperties = @{ + 'OwnerName' = (Get-PiiSafeString -PlainText $OwnerName) + 'RepositoryName' = (Get-PiiSafeString -PlainText $RepositoryName) + } + + $originBranch = $null + + try + { + $getGitHubRepositoryBranchParms = @{ + OwnerName = $OwnerName + RepositoryName = $RepositoryName + BranchName = $BranchName + Whatif = $false + Confirm = $false + } + if ($PSBoundParameters.ContainsKey('AccessToken')) + { + $getGitHubRepositoryBranchParms['AccessToken'] = $AccessToken + } + if ($PSBoundParameters.ContainsKey('NoStatus')) + { + $getGitHubRepositoryBranchParms['NoStatus'] = $NoStatus + } + + Write-Log -Level Verbose "Getting $BranchName branch for sha reference" + $originBranch = Get-GitHubRepositoryBranch @getGitHubRepositoryBranchParms + } + catch + { + # Temporary code to handle current differences in exception object between PS5 and PS7 + $throwObject = $_ + + if ($PSVersionTable.PSedition -eq 'Core') + { + if ($_.Exception -is [Microsoft.PowerShell.Commands.HttpResponseException] -and + ($_.ErrorDetails.Message | ConvertFrom-Json).message -eq 'Branch not found') + { + $throwObject = "Origin branch $BranchName not found" + } + } + else + { + if ($_.Exception.Message -like '*Not Found*') + { + $throwObject = "Origin branch $BranchName not found" + } + } + + Write-Log -Message $throwObject -Level Error + throw $throwObject + } + + $uriFragment = "repos/$OwnerName/$RepositoryName/git/refs" + + $hashBody = @{ + ref = "refs/heads/$TargetBranchName" + sha = $originBranch.commit.sha + } + + $params = @{ + 'UriFragment' = $uriFragment + 'Body' = (ConvertTo-Json -InputObject $hashBody) + 'Method' = 'Post' + 'Description' = "Creating branch $TargetBranchName for $RepositoryName" + 'AccessToken' = $AccessToken + 'TelemetryEventName' = $MyInvocation.MyCommand.Name + 'TelemetryProperties' = $telemetryProperties + 'NoStatus' = (Resolve-ParameterWithDefaultConfigurationValue -Name NoStatus -ConfigValueName DefaultNoStatus) + } + + return (Invoke-GHRestMethod @params | Add-GitHubBranchAdditionalProperties) +} + +filter Remove-GitHubRepositoryBranch +{ + <# + .SYNOPSIS + Removes a branch from a given GitHub repository. + + .DESCRIPTION + Removes a branch from a given GitHub repository. + + The Git repo for this module can be found here: http://aka.ms/PowerShellForGitHub + + .PARAMETER OwnerName + Owner of the repository. + If not supplied here, the DefaultOwnerName configuration property value will be used. + + .PARAMETER RepositoryName + Name of the repository. + If not supplied here, the DefaultRepositoryName configuration property value will be used. + + .PARAMETER Uri + Uri for the repository. + The OwnerName and RepositoryName will be extracted from here instead of needing to provide + them individually. + + .PARAMETER BranchName + Name of the branch to be removed. + + .PARAMETER Force + If this switch is specified, you will not be prompted for confirmation of command execution. + + .PARAMETER AccessToken + If provided, this will be used as the AccessToken for authentication with the + REST Api. Otherwise, will attempt to use the configured value or will run unauthenticated. + + .PARAMETER NoStatus + If this switch is specified, long-running commands will run on the main thread + with no commandline status update. When not specified, those commands run in + the background, enabling the command prompt to provide status information. + If not supplied here, the DefaultNoStatus configuration property value will be used. + + .INPUTS + GitHub.Branch + GitHub.Content + GitHub.Event + GitHub.Issue + GitHub.IssueComment + GitHub.Label + GitHub.Milestone + GitHub.PullRequest + GitHub.Project + GitHub.ProjectCard + GitHub.ProjectColumn + GitHub.Release + GitHub.Repository + + .OUTPUTS + None + + .EXAMPLE + Remove-GitHubRepositoryBranch -OwnerName microsoft -RepositoryName PowerShellForGitHub -BranchName develop + + Removes the 'develop' branch from the specified repository. + + .EXAMPLE + Remove-GitHubRepositoryBranch -OwnerName microsoft -RepositoryName PowerShellForGitHub -BranchName develop -Force + + Removes the 'develop' branch from the specified repository without prompting for confirmation. + + .EXAMPLE + $branch = Get-GitHubRepositoryBranch -Uri https://github.com/You/YourRepo -BranchName BranchToDelete + $branch | Remove-GitHubRepositoryBranch -Force + + You can also pipe in a repo that was returned from a previous command. +#> + [CmdletBinding( + SupportsShouldProcess, + DefaultParameterSetName = 'Elements', + PositionalBinding = $false, + ConfirmImpact = 'High')] + [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSShouldProcess", "", + Justification = "Methods called within here make use of PSShouldProcess, and the switch is + passed on to them inherently.")] + [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSReviewUnusedParameter", "", + Justification = "One or more parameters (like NoStatus) are only referenced by helper + methods which get access to it from the stack via Get-Variable -Scope 1.")] + [Alias('Remove-GitHubBranch')] + [Alias('Delete-GitHubRepositoryBranch')] + [Alias('Delete-GitHubBranch')] + param( + [Parameter(ParameterSetName = 'Elements')] + [string] $OwnerName, + + [Parameter(ParameterSetName = 'Elements')] + [string] $RepositoryName, + + [Parameter( + Mandatory, + ValueFromPipelineByPropertyName, + Position = 1, + ParameterSetName = 'Uri')] + [Alias('RepositoryUrl')] + [string] $Uri, + + [Parameter( + Mandatory, + ValueFromPipelineByPropertyName, + Position = 2)] + [string] $BranchName, + + [switch] $Force, + + [string] $AccessToken, + + [switch] $NoStatus + ) + + $elements = Resolve-RepositoryElements + $OwnerName = $elements.ownerName + $RepositoryName = $elements.repositoryName + + $telemetryProperties = @{ + 'OwnerName' = (Get-PiiSafeString -PlainText $OwnerName) + 'RepositoryName' = (Get-PiiSafeString -PlainText $RepositoryName) + } + + $uriFragment = "repos/$OwnerName/$RepositoryName/git/refs/heads/$BranchName" + + if ($Force -and (-not $Confirm)) + { + $ConfirmPreference = 'None' + } + + if ($PSCmdlet.ShouldProcess($BranchName, "Remove Repository Branch")) + { + Write-InvocationLog + + $params = @{ + 'UriFragment' = $uriFragment + 'Method' = 'Delete' + 'Description' = "Deleting branch $BranchName from $RepositoryName" + 'AccessToken' = $AccessToken + 'TelemetryEventName' = $MyInvocation.MyCommand.Name + 'TelemetryProperties' = $telemetryProperties + 'NoStatus' = (Resolve-ParameterWithDefaultConfigurationValue ` + -Name NoStatus -ConfigValueName DefaultNoStatus) + } + + Invoke-GHRestMethod @params | Out-Null + } +} + filter Add-GitHubBranchAdditionalProperties { <# @@ -192,11 +538,25 @@ filter Add-GitHubBranchAdditionalProperties if (-not (Get-GitHubConfiguration -Name DisablePipelineSupport)) { - $elements = Split-GitHubUri -Uri $item.commit.url + if ($null -ne $item.url) + { + $elements = Split-GitHubUri -Uri $item.url + } + else + { + $elements = Split-GitHubUri -Uri $item.commit.url + } $repositoryUrl = Join-GitHubUri @elements + Add-Member -InputObject $item -Name 'RepositoryUrl' -Value $repositoryUrl -MemberType NoteProperty -Force - Add-Member -InputObject $item -Name 'BranchName' -Value $item.name -MemberType NoteProperty -Force + $branchName = $item.name + if ($null -eq $branchName) + { + $branchName = $item.ref -replace ('refs/heads/', '') + } + + Add-Member -InputObject $item -Name 'BranchName' -Value $branchName -MemberType NoteProperty -Force } Write-Output $item diff --git a/PowerShellForGitHub.psd1 b/PowerShellForGitHub.psd1 index cb7fcf06..a15992a6 100644 --- a/PowerShellForGitHub.psd1 +++ b/PowerShellForGitHub.psd1 @@ -121,6 +121,7 @@ 'New-GitHubPullRequest', 'New-GitHubRepository', 'New-GitHubRepositoryFromTemplate', + 'New-GitHubRepositoryBranch', 'New-GitHubRepositoryFork', 'Remove-GitHubAssignee', 'Remove-GitHubIssueComment', @@ -131,6 +132,7 @@ 'Remove-GitHubProjectCard', 'Remove-GitHubProjectColumn', 'Remove-GitHubRepository', + 'Remove-GitHubRepositoryBranch' 'Rename-GitHubRepository', 'Reset-GitHubConfiguration', 'Restore-GitHubConfiguration', @@ -157,6 +159,7 @@ ) AliasesToExport = @( + 'Delete-GitHubBranch', 'Delete-GitHubComment', 'Delete-GitHubIssueComment', 'Delete-GitHubLabel', @@ -165,10 +168,13 @@ 'Delete-GitHubProjectCard', 'Delete-GitHubProjectColumn' 'Delete-GitHubRepository', + 'Delete-GitHubRepositoryBranch', 'Get-GitHubBranch', 'Get-GitHubComment', 'New-GitHubAssignee', + 'New-GitHubBranch', 'New-GitHubComment', + 'Remove-GitHubBranch' 'Remove-GitHubComment', 'Set-GitHubComment', 'Transfer-GitHubRepositoryOwnership' diff --git a/Tests/GitHubBranches.tests.ps1 b/Tests/GitHubBranches.tests.ps1 index f0df1ca9..2d52a19b 100644 --- a/Tests/GitHubBranches.tests.ps1 +++ b/Tests/GitHubBranches.tests.ps1 @@ -39,7 +39,7 @@ try $branches.name | Should -Contain $branchName } - It 'Should have the exected type and addititional properties' { + It 'Should have the expected type and addititional properties' { $branches[0].PSObject.TypeNames[0] | Should -Be 'GitHub.Branch' $branches[0].RepositoryUrl | Should -Be $repo.RepositoryUrl $branches[0].BranchName | Should -Be $branches[0].name @@ -57,7 +57,7 @@ try $branches.name | Should -Contain $branchName } - It 'Should have the exected type and addititional properties' { + It 'Should have the expected type and addititional properties' { $branches[0].PSObject.TypeNames[0] | Should -Be 'GitHub.Branch' $branches[0].RepositoryUrl | Should -Be $repo.RepositoryUrl $branches[0].BranchName | Should -Be $branches[0].name @@ -71,7 +71,7 @@ try $branch.name | Should -Be $branchName } - It 'Should have the exected type and addititional properties' { + It 'Should have the expected type and addititional properties' { $branch.PSObject.TypeNames[0] | Should -Be 'GitHub.Branch' $branch.RepositoryUrl | Should -Be $repo.RepositoryUrl $branch.BranchName | Should -Be $branch.name @@ -85,7 +85,7 @@ try $branch.name | Should -Be $branchName } - It 'Should have the exected type and addititional properties' { + It 'Should have the expected type and addititional properties' { $branch.PSObject.TypeNames[0] | Should -Be 'GitHub.Branch' $branch.RepositoryUrl | Should -Be $repo.RepositoryUrl $branch.BranchName | Should -Be $branch.name @@ -100,13 +100,236 @@ try $branchAgain.name | Should -Be $branchName } - It 'Should have the exected type and addititional properties' { + It 'Should have the expected type and addititional properties' { $branchAgain.PSObject.TypeNames[0] | Should -Be 'GitHub.Branch' $branchAgain.RepositoryUrl | Should -Be $repo.RepositoryUrl $branchAgain.BranchName | Should -Be $branchAgain.name } } } + + Describe 'GitHubBranches\New-GitHubRepositoryBranch' { + BeforeAll { + $repoName = [Guid]::NewGuid().Guid + $originBranchName = 'master' + $newGitHubRepositoryParms = @{ + RepositoryName = $repoName + AutoInit = $true + } + + $repo = New-GitHubRepository @newGitHubRepositoryParms + } + + Context 'When creating a new GitHub repository branch' { + Context 'When using non-pipelined parameters' { + BeforeAll { + $newBranchName = 'develop1' + $newGitHubRepositoryBranchParms = @{ + OwnerName = $script:ownerName + RepositoryName = $repoName + BranchName = $originBranchName + TargetBranchName = $newBranchName + } + + $branch = New-GitHubRepositoryBranch @newGitHubRepositoryBranchParms + } + + It 'Should have the expected type and addititional properties' { + $branch.PSObject.TypeNames[0] | Should -Be 'GitHub.Branch' + $branch.RepositoryUrl | Should -Be $repo.RepositoryUrl + $branch.BranchName | Should -Be $newBranchName + } + + It 'Should have created the branch' { + $getGitHubRepositoryBranchParms = @{ + OwnerName = $script:ownerName + RepositoryName = $repoName + BranchName = $newBranchName + } + + { Get-GitHubRepositoryBranch @getGitHubRepositoryBranchParms } | + Should -Not -Throw + } + } + + Context 'When using pipelined parameters' { + Context 'When providing pipeline input for the "Uri" parameter' { + BeforeAll { + $newBranchName = 'develop2' + $branch = $repo | New-GitHubRepositoryBranch -TargetBranchName $newBranchName + } + + It 'Should have the expected type and addititional properties' { + $branch.PSObject.TypeNames[0] | Should -Be 'GitHub.Branch' + $branch.RepositoryUrl | Should -Be $repo.RepositoryUrl + $branch.BranchName | Should -Be $newBranchName + } + + It 'Should have created the branch' { + $getGitHubRepositoryBranchParms = @{ + OwnerName = $script:ownerName + RepositoryName = $repoName + BranchName = $newBranchName + } + + { Get-GitHubRepositoryBranch @getGitHubRepositoryBranchParms } | + Should -Not -Throw + } + } + + Context 'When providing pipeline input for the "TargetBranchName" parameter' { + BeforeAll { + $newBranchName = 'develop3' + $branch = $newBranchName | New-GitHubRepositoryBranch -Uri $repo.html_url + } + + It 'Should have the expected type and addititional properties' { + $branch.PSObject.TypeNames[0] | Should -Be 'GitHub.Branch' + $branch.RepositoryUrl | Should -Be $repo.RepositoryUrl + $branch.BranchName | Should -Be $newBranchName + } + + It 'Should have created the branch' { + $getGitHubRepositoryBranchParms = @{ + OwnerName = $script:ownerName + RepositoryName = $repoName + BranchName = $newBranchName + } + + { Get-GitHubRepositoryBranch @getGitHubRepositoryBranchParms } | + Should -Not -Throw + } + } + } + + Context 'When the origin branch cannot be found' { + BeforeAll -Scriptblock { + $missingOriginBranchName = 'Missing-Branch' + } + + It 'Should throw the correct exception' { + $errorMessage = "Origin branch $missingOriginBranchName not found" + + $newGitHubRepositoryBranchParms = @{ + OwnerName = $script:ownerName + RepositoryName = $repoName + BranchName = $missingOriginBranchName + TargetBranchName = $newBranchName + } + + { New-GitHubRepositoryBranch @newGitHubRepositoryBranchParms } | + Should -Throw $errorMessage + } + } + + Context 'When Get-GitHubRepositoryBranch throws an undefined HttpResponseException' { + It 'Should throw the correct exception' { + $newGitHubRepositoryBranchParms = @{ + OwnerName = $script:ownerName + RepositoryName = 'test' + BranchName = 'test' + TargetBranchName = 'test' + } + + { New-GitHubRepositoryBranch @newGitHubRepositoryBranchParms } | + Should -Throw 'Not Found' + } + } + } + + AfterAll -ScriptBlock { + if (Get-Variable -Name repo -ErrorAction SilentlyContinue) + { + Remove-GitHubRepository -Uri $repo.svn_url -Confirm:$false + } + } + } + + Describe 'GitHubBranches\Remove-GitHubRepositoryBranch' { + BeforeAll -Scriptblock { + $repoName = [Guid]::NewGuid().Guid + $originBranchName = 'master' + $newGitHubRepositoryParms = @{ + RepositoryName = $repoName + AutoInit = $true + } + + $repo = New-GitHubRepository @newGitHubRepositoryParms + } + + Context 'When using non-pipelined parameters' { + BeforeAll { + $newBranchName = 'develop1' + $newGitHubRepositoryBranchParms = @{ + OwnerName = $script:ownerName + RepositoryName = $repoName + BranchName = $originBranchName + TargetBranchName = $newBranchName + } + + $branch = New-GitHubRepositoryBranch @newGitHubRepositoryBranchParms + } + + It 'Should not throw an exception' { + $removeGitHubRepositoryBranchParms = @{ + OwnerName = $script:ownerName + RepositoryName = $repoName + BranchName = $newBranchName + Confirm = $false + } + + { Remove-GitHubRepositoryBranch @removeGitHubRepositoryBranchParms } | + Should -Not -Throw + } + + It 'Should have removed the branch' { + $getGitHubRepositoryBranchParms = @{ + OwnerName = $script:ownerName + RepositoryName = $repoName + BranchName = $newBranchName + } + + { Get-GitHubRepositoryBranch @getGitHubRepositoryBranchParms } | + Should -Throw + } + } + + Context 'When using pipelined parameters' { + BeforeAll { + $newBranchName = 'develop2' + $newGitHubRepositoryBranchParms = @{ + OwnerName = $script:ownerName + RepositoryName = $repoName + BranchName = $originBranchName + TargetBranchName = $newBranchName + } + + $branch = New-GitHubRepositoryBranch @newGitHubRepositoryBranchParms + } + + It 'Should not throw an exception' { + { $branch | Remove-GitHubRepositoryBranch -Force } | Should -Not -Throw + } + + It 'Should have removed the branch' { + $getGitHubRepositoryBranchParms = @{ + OwnerName = $script:ownerName + RepositoryName = $repoName + BranchName = $newBranchName + } + + { Get-GitHubRepositoryBranch @getGitHubRepositoryBranchParms } | + Should -Throw + } + } + + AfterAll -ScriptBlock { + if (Get-Variable -Name repo -ErrorAction SilentlyContinue) + { + Remove-GitHubRepository -Uri $repo.svn_url -Confirm:$false + } + } + } } finally { diff --git a/USAGE.md b/USAGE.md index 441081e7..bdd04488 100644 --- a/USAGE.md +++ b/USAGE.md @@ -39,6 +39,9 @@ * [Disable repository vulnerability alerts](#disable-repository-vulnerability-alerts) * [Enable repository automatic security fixes](#enable-repository-automatic-security-fixes) * [Disable repository automatic security fixes](#disable-repository-automatic-security-fixes) + * [Branches](#branches) + * [Adding a new Branch to a Repository](#adding-a-new-branch-to-a-repository) + * [Removing a Branch from a Repository](#removing-a-branch-from-a-repository) * [Forks](#forks) * [Get all the forks for a repository](#get-all-the-forks-for-a-repository) * [Create a new fork](#create-a-new-fork) @@ -432,6 +435,21 @@ Get-GitHubUser ``` > Warning: This will take a while. It's getting _every_ GitHub user. +---------- +### Repositories + +#### Adding a new Branch to a Repository + +```powershell +New-GitHubRepositoryBranch -OwnerName microsoft -RepositoryName PowerShellForGitHub -Name develop +``` + +#### Removing a Branch from a Repository + +```powershell +Remove-GitHubRepositoryBranch -OwnerName microsoft -RepositoryName PowerShellForGitHub -Name develop +``` + ---------- ### Repositories @@ -459,7 +477,8 @@ New-GitHubRepository -RepositoryName TestRepo -OrganizationName MyOrg -TeamId $m ```powershell New-GitHubRepositoryFromTemplate -OwnerName MyOrg -RepositoryName MyNewRepo-TemplateOwnerName MyOrg -TemplateRepositoryName MyTemplateRepo -======= +``` + #### Get repository vulnerability alert status ```powershell From 8e55f5af03aa0ae2d402e52b7cd50ca43ded03a7 Mon Sep 17 00:00:00 2001 From: Tyler James Leonhardt Date: Thu, 16 Jul 2020 11:45:59 -0700 Subject: [PATCH 54/60] Add GitHub Reactions support (#193) Adds support for `Issue` and `PullRequest` reactions by adding the following new functions: * [`Get-GitHubReaction`](https://developer.github.com/v3/reactions/#list-reactions-for-an-issue) * [`Set-GitHubReaction`](https://developer.github.com/v3/reactions/#create-reaction-for-an-issue) * [`Remove-GitHubReaction`](https://developer.github.com/v3/reactions/#delete-an-issue-reaction) --- GitHubAssignees.ps1 | 4 + GitHubBranches.ps1 | 1 + GitHubContents.ps1 | 1 + GitHubEvents.ps1 | 1 + GitHubIssueComments.ps1 | 4 + GitHubIssues.ps1 | 6 + GitHubLabels.ps1 | 8 + GitHubMilestones.ps1 | 4 + GitHubMiscellaneous.ps1 | 5 +- GitHubProjects.ps1 | 2 + GitHubPullRequests.ps1 | 2 + GitHubReactions.ps1 | 710 ++++++++++++++++++++++++++++++++ GitHubReleases.ps1 | 1 + GitHubRepositories.ps1 | 11 + GitHubRepositoryForks.ps1 | 2 + GitHubRepositoryTraffic.ps1 | 4 + GitHubTeams.ps1 | 1 + PowerShellForGitHub.psd1 | 9 +- Tests/GitHubReactions.Tests.ps1 | 118 ++++++ 19 files changed, 889 insertions(+), 5 deletions(-) create mode 100644 GitHubReactions.ps1 create mode 100644 Tests/GitHubReactions.Tests.ps1 diff --git a/GitHubAssignees.ps1 b/GitHubAssignees.ps1 index 0964573a..6bee8221 100644 --- a/GitHubAssignees.ps1 +++ b/GitHubAssignees.ps1 @@ -44,6 +44,7 @@ filter Get-GitHubAssignee GitHub.Project GitHub.ProjectCard GitHub.ProjectColumn + GitHub.Reaction GitHub.Release GitHub.Repository GitHub.User @@ -156,6 +157,7 @@ filter Test-GitHubAssignee GitHub.Project GitHub.ProjectCard GitHub.ProjectColumn + GitHub.Reaction GitHub.Release GitHub.Repository GitHub.User @@ -299,6 +301,7 @@ function Add-GitHubAssignee GitHub.Project GitHub.ProjectCard GitHub.ProjectColumn + GitHub.Reaction GitHub.Release GitHub.Repository GitHub.User @@ -476,6 +479,7 @@ function Remove-GitHubAssignee GitHub.Project GitHub.ProjectCard GitHub.ProjectColumn + GitHub.Reaction GitHub.Release GitHub.Repository diff --git a/GitHubBranches.ps1 b/GitHubBranches.ps1 index 2e52cf40..41e5b100 100644 --- a/GitHubBranches.ps1 +++ b/GitHubBranches.ps1 @@ -56,6 +56,7 @@ filter Get-GitHubRepositoryBranch GitHub.Project GitHub.ProjectCard GitHub.ProjectColumn + GitHub.Reaction GitHub.Release GitHub.Repository diff --git a/GitHubContents.ps1 b/GitHubContents.ps1 index 7ce6472a..53397540 100644 --- a/GitHubContents.ps1 +++ b/GitHubContents.ps1 @@ -71,6 +71,7 @@ GitHub.Project GitHub.ProjectCard GitHub.ProjectColumn + GitHub.Reaction GitHub.Release GitHub.Repository diff --git a/GitHubEvents.ps1 b/GitHubEvents.ps1 index c904bc6a..000574af 100644 --- a/GitHubEvents.ps1 +++ b/GitHubEvents.ps1 @@ -58,6 +58,7 @@ filter Get-GitHubEvent GitHub.Project GitHub.ProjectCard GitHub.ProjectColumn + GitHub.Reaction GitHub.Release GitHub.Repository diff --git a/GitHubIssueComments.ps1 b/GitHubIssueComments.ps1 index 1eacf806..0542581b 100644 --- a/GitHubIssueComments.ps1 +++ b/GitHubIssueComments.ps1 @@ -79,6 +79,7 @@ filter Get-GitHubIssueComment GitHub.Project GitHub.ProjectCard GitHub.ProjectColumn + GitHub.Reaction GitHub.Release GitHub.Repository @@ -343,6 +344,7 @@ filter New-GitHubIssueComment GitHub.Project GitHub.ProjectCard GitHub.ProjectColumn + GitHub.Reaction GitHub.Release GitHub.Repository GitHub.User @@ -486,6 +488,7 @@ filter Set-GitHubIssueComment GitHub.Project GitHub.ProjectCard GitHub.ProjectColumn + GitHub.Reaction GitHub.Release GitHub.Repository GitHub.User @@ -616,6 +619,7 @@ filter Remove-GitHubIssueComment GitHub.Project GitHub.ProjectCard GitHub.ProjectColumn + GitHub.Reaction GitHub.Release GitHub.Repository diff --git a/GitHubIssues.ps1 b/GitHubIssues.ps1 index 1010b19a..705a9da6 100644 --- a/GitHubIssues.ps1 +++ b/GitHubIssues.ps1 @@ -128,6 +128,7 @@ filter Get-GitHubIssue GitHub.Project GitHub.ProjectCard GitHub.ProjectColumn + GitHub.Reaction GitHub.Release GitHub.Repository GitHub.User @@ -431,6 +432,7 @@ filter Get-GitHubIssueTimeline GitHub.Project GitHub.ProjectCard GitHub.ProjectColumn + GitHub.Reaction GitHub.Release GitHub.Repository @@ -569,6 +571,7 @@ filter New-GitHubIssue GitHub.Project GitHub.ProjectCard GitHub.ProjectColumn + GitHub.Reaction GitHub.Release GitHub.Repository @@ -737,6 +740,7 @@ filter Set-GitHubIssue GitHub.Project GitHub.ProjectCard GitHub.ProjectColumn + GitHub.Reaction GitHub.Release GitHub.Repository @@ -887,6 +891,7 @@ filter Lock-GitHubIssue GitHub.Project GitHub.ProjectCard GitHub.ProjectColumn + GitHub.Reaction GitHub.Release GitHub.Repository @@ -1017,6 +1022,7 @@ filter Unlock-GitHubIssue GitHub.Project GitHub.ProjectCard GitHub.ProjectColumn + GitHub.Reaction GitHub.Release GitHub.Repository diff --git a/GitHubLabels.ps1 b/GitHubLabels.ps1 index 80f55480..dd1391ac 100644 --- a/GitHubLabels.ps1 +++ b/GitHubLabels.ps1 @@ -63,6 +63,7 @@ filter Get-GitHubLabel GitHub.Project GitHub.ProjectCard GitHub.ProjectColumn + GitHub.Reaction GitHub.Release GitHub.Repository @@ -250,6 +251,7 @@ filter New-GitHubLabel GitHub.Project GitHub.ProjectCard GitHub.ProjectColumn + GitHub.Reaction GitHub.Release GitHub.Repository @@ -391,6 +393,7 @@ filter Remove-GitHubLabel GitHub.Project GitHub.ProjectCard GitHub.ProjectColumn + GitHub.Reaction GitHub.Release GitHub.Repository @@ -548,6 +551,7 @@ filter Set-GitHubLabel GitHub.Project GitHub.ProjectCard GitHub.ProjectColumn + GitHub.Reaction GitHub.Release GitHub.Repository @@ -694,6 +698,7 @@ filter Initialize-GitHubLabel GitHub.Project GitHub.ProjectCard GitHub.ProjectColumn + GitHub.Reaction GitHub.Release GitHub.Repository @@ -833,6 +838,7 @@ function Add-GitHubIssueLabel GitHub.Project GitHub.ProjectCard GitHub.ProjectColumn + GitHub.Reaction GitHub.Release GitHub.Repository @@ -993,6 +999,7 @@ function Set-GitHubIssueLabel GitHub.Project GitHub.ProjectCard GitHub.ProjectColumn + GitHub.Reaction GitHub.Release GitHub.Repository @@ -1184,6 +1191,7 @@ filter Remove-GitHubIssueLabel GitHub.Project GitHub.ProjectCard GitHub.ProjectColumn + GitHub.Reaction GitHub.Release GitHub.Repository diff --git a/GitHubMilestones.ps1 b/GitHubMilestones.ps1 index ddbbd165..d2f2dc73 100644 --- a/GitHubMilestones.ps1 +++ b/GitHubMilestones.ps1 @@ -67,6 +67,7 @@ filter Get-GitHubMilestone GitHub.Project GitHub.ProjectCard GitHub.ProjectColumn + GitHub.Reaction GitHub.Release GitHub.Repository @@ -269,6 +270,7 @@ filter New-GitHubMilestone GitHub.Project GitHub.ProjectCard GitHub.ProjectColumn + GitHub.Reaction GitHub.Release GitHub.Repository @@ -450,6 +452,7 @@ filter Set-GitHubMilestone GitHub.Project GitHub.ProjectCard GitHub.ProjectColumn + GitHub.Reaction GitHub.Release GitHub.Repository @@ -630,6 +633,7 @@ filter Remove-GitHubMilestone GitHub.Project GitHub.ProjectCard GitHub.ProjectColumn + GitHub.Reaction GitHub.Release GitHub.Repository diff --git a/GitHubMiscellaneous.ps1 b/GitHubMiscellaneous.ps1 index a2498de5..20ecb3ee 100644 --- a/GitHubMiscellaneous.ps1 +++ b/GitHubMiscellaneous.ps1 @@ -235,7 +235,6 @@ filter Get-GitHubLicense the background, enabling the command prompt to provide status information. If not supplied here, the DefaultNoStatus configuration property value will be used. - .INPUTS [String] GitHub.Branch @@ -249,10 +248,10 @@ filter Get-GitHubLicense GitHub.Project GitHub.ProjectCard GitHub.ProjectColumn + GitHub.Reaction GitHub.Release GitHub.Repository - .OUTPUTS GitHub.License @@ -487,10 +486,10 @@ filter Get-GitHubCodeOfConduct GitHub.Project GitHub.ProjectCard GitHub.ProjectColumn + GitHub.Reaction GitHub.Release GitHub.Repository - .OUTPUTS GitHub.CodeOfConduct diff --git a/GitHubProjects.ps1 b/GitHubProjects.ps1 index 3476f263..c96dc30f 100644 --- a/GitHubProjects.ps1 +++ b/GitHubProjects.ps1 @@ -62,6 +62,7 @@ filter Get-GitHubProject GitHub.Project GitHub.ProjectCard GitHub.ProjectColumn + GitHub.Reaction GitHub.Release GitHub.Repository @@ -274,6 +275,7 @@ filter New-GitHubProject GitHub.Project GitHub.ProjectCard GitHub.ProjectColumn + GitHub.Reaction GitHub.Release GitHub.Repository diff --git a/GitHubPullRequests.ps1 b/GitHubPullRequests.ps1 index 09860e02..0eeb5ee4 100644 --- a/GitHubPullRequests.ps1 +++ b/GitHubPullRequests.ps1 @@ -76,6 +76,7 @@ filter Get-GitHubPullRequest GitHub.Project GitHub.ProjectCard GitHub.ProjectColumn + GitHub.Reaction GitHub.Release GitHub.Repository @@ -271,6 +272,7 @@ filter New-GitHubPullRequest GitHub.Project GitHub.ProjectCard GitHub.ProjectColumn + GitHub.Reaction GitHub.Release GitHub.Repository diff --git a/GitHubReactions.ps1 b/GitHubReactions.ps1 new file mode 100644 index 00000000..47c6e992 --- /dev/null +++ b/GitHubReactions.ps1 @@ -0,0 +1,710 @@ +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. + +@{ + GitHubReactionTypeName = 'GitHub.Reaction' + }.GetEnumerator() | ForEach-Object { + Set-Variable -Scope Script -Option ReadOnly -Name $_.Key -Value $_.Value + } + +filter Get-GitHubReaction +{ +<# + .SYNOPSIS + Retrieve reactions of a given GitHub Issue or Pull Request. + + .DESCRIPTION + Retrieve reactions of a given GitHub Issue or Pull Request. + + The Git repo for this module can be found here: http://aka.ms/PowerShellForGitHub + + .PARAMETER OwnerName + Owner of the repository. + If not supplied here, the DefaultOwnerName configuration property value will be used. + + .PARAMETER RepositoryName + Name of the repository. + If not supplied here, the DefaultRepositoryName configuration property value will be used. + + .PARAMETER Uri + Uri for the repository. + The OwnerName and RepositoryName will be extracted from here instead of needing to provide + them individually. + + .PARAMETER Issue + The issue number. + + .PARAMETER PullRequest + The pull request number. + + .PARAMETER ReactionType + The type of reaction you want to retrieve. This is also called the 'content' in + the GitHub API. Valid options are based off: + https://developer.github.com/v3/reactions/#reaction-types + + .PARAMETER AccessToken + If provided, this will be used as the AccessToken for authentication with the + REST Api. Otherwise, will attempt to use the configured value or will run + unauthenticated. + + .PARAMETER NoStatus + If this switch is specified, long-running commands will run on the main thread + with no commandline status update. When not specified, those commands run in + the background, enabling the command prompt to provide status information. + If not supplied here, the DefaultNoStatus configuration property value will be used. + + .INPUTS + GitHub.Branch + GitHub.Content + GitHub.Event + GitHub.Issue + GitHub.IssueComment + GitHub.Label + GitHub.Milestone + GitHub.PullRequest + GitHub.Project + GitHub.ProjectCard + GitHub.ProjectColumn + GitHub.Reaction + GitHub.Release + GitHub.Repository + + .OUTPUTS + GitHub.Reaction + + .EXAMPLE + Get-GitHubReaction -OwnerName Microsoft -RepositoryName PowerShellForGitHub -Issue 157 + + Gets the reactions for issue 157 from the Microsoft\PowerShellForGitHub + project. + + .EXAMPLE + Get-GitHubReaction -OwnerName Microsoft -RepositoryName PowerShellForGitHub -Issue 157 -ReactionType eyes + + Gets the 'eyes' reactions for issue 157 from the Microsoft\PowerShellForGitHub + project. + + .EXAMPLE + Get-GitHubIssue -OwnerName Microsoft -RepositoryName PowerShellForGitHub -Issue 157 | Get-GitHubReaction + + Gets a GitHub issue and pipe it into Get-GitHubReaction to get all + the reactions for that issue. + + .EXAMPLE + Get-GitHubPullRequest -Uri https://github.com/microsoft/PowerShellForGitHub -PullRequest 193 | Get-GitHubReaction + + Gets a GitHub pull request and pipes it into Get-GitHubReaction + to get all the reactions for that pull request. + + .NOTES + Currently, issue comments, pull request comments and commit comments are not supported. +#> + [CmdletBinding( + SupportsShouldProcess, + DefaultParameterSetName='ElementsIssue')] + [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSShouldProcess", "", Justification="Methods called within here make use of PSShouldProcess, and the switch is passed on to them inherently.")] + [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSReviewUnusedParameter", "", Justification="One or more parameters (like NoStatus) are only referenced by helper methods which get access to it from the stack via Get-Variable -Scope 1.")] + param( + [Parameter( + Mandatory, + ParameterSetName='ElementsIssue')] + [Parameter( + Mandatory, + ParameterSetName='ElementsPullRequest')] + [string] $OwnerName, + + [Parameter( + Mandatory, + ParameterSetName='ElementsIssue')] + [Parameter( + Mandatory, + ParameterSetName='ElementsPullRequest')] + [string] $RepositoryName, + + [Parameter( + Mandatory, + ValueFromPipelineByPropertyName, + ParameterSetName='UriIssue')] + [Parameter( + Mandatory, + ValueFromPipelineByPropertyName, + ParameterSetName='UriPullRequest')] + [Alias('RepositoryUrl')] + [string] $Uri, + + [Parameter( + Mandatory, + ValueFromPipelineByPropertyName, + ParameterSetName='ElementsIssue')] + [Parameter( + Mandatory, + ParameterSetName='UriIssue', + ValueFromPipelineByPropertyName)] + [Alias('IssueNumber')] + [int64] $Issue, + + [Parameter( + Mandatory, + ValueFromPipelineByPropertyName, + ParameterSetName='ElementsPullRequest')] + [Parameter( + Mandatory, + ValueFromPipelineByPropertyName, + ParameterSetName='UriPullRequest')] + [Alias('PullRequestNumber')] + [int64] $PullRequest, + + [ValidateSet('+1', '-1', 'Laugh', 'Confused', 'Heart', 'Hooray', 'Rocket', 'Eyes')] + [string] $ReactionType, + + [string] $AccessToken, + + [switch] $NoStatus + ) + + Write-InvocationLog + + $elements = Resolve-RepositoryElements + $OwnerName = $elements.ownerName + $RepositoryName = $elements.repositoryName + + $telemetryProperties = @{ + 'OwnerName' = (Get-PiiSafeString -PlainText $OwnerName) + 'RepositoryName' = (Get-PiiSafeString -PlainText $RepositoryName) + } + + $splatForAddedProperties = @{ + OwnerName = $OwnerName + Repository = $RepositoryName + } + + if ($Issue) + { + $splatForAddedProperties.Issue = $Issue + $targetObjectNumber = $Issue + $targetObjectTypeName = 'Issue' + $uriFragment = "/repos/$OwnerName/$RepositoryName/issues/$targetObjectNumber/reactions" + } + else + { + # Pull Request + $splatForAddedProperties.PullRequest = $PullRequest + $targetObjectNumber = $PullRequest + $targetObjectTypeName = 'Pull Request' + $uriFragment = "/repos/$OwnerName/$RepositoryName/issues/$targetObjectNumber/reactions" + } + + if ($PSBoundParameters.ContainsKey('ReactionType')) + { + $uriFragment += "?content=" + [Uri]::EscapeDataString($ReactionType.ToLower()) + } + + $description = "Getting reactions for $targetObjectTypeName $targetObjectNumber in $RepositoryName" + + $params = @{ + 'UriFragment' = $uriFragment + 'Description' = $description + 'AcceptHeader' = $script:squirrelGirlAcceptHeader + 'AccessToken' = $AccessToken + 'TelemetryEventName' = $MyInvocation.MyCommand.Name + 'TelemetryProperties' = $telemetryProperties + 'NoStatus' = (Resolve-ParameterWithDefaultConfigurationValue -Name NoStatus -ConfigValueName DefaultNoStatus) + } + + $result = Invoke-GHRestMethodMultipleResult @params + return ($result | Add-GitHubReactionAdditionalProperties @splatForAddedProperties) +} + +filter Set-GitHubReaction +{ +<# + .SYNOPSIS + Sets a reaction of a given GitHub Issue or Pull Request. + + .DESCRIPTION + Sets a reaction of a given GitHub Issue or Pull Request. + + The Git repo for this module can be found here: http://aka.ms/PowerShellForGitHub + + .PARAMETER OwnerName + Owner of the repository. + If not supplied here, the DefaultOwnerName configuration property value will be used. + + .PARAMETER RepositoryName + Name of the repository. + If not supplied here, the DefaultRepositoryName configuration property value will be used. + + .PARAMETER Uri + Uri for the repository. + The OwnerName and RepositoryName will be extracted from here instead of needing to provide + them individually. + + .PARAMETER Issue + The issue number. + + .PARAMETER PullRequest + The pull request number. + + .PARAMETER ReactionType + The type of reaction you want to set. This is aslo called the 'content' in the GitHub API. + Valid options are based off: https://developer.github.com/v3/reactions/#reaction-types + + .PARAMETER AccessToken + If provided, this will be used as the AccessToken for authentication with the + REST Api. Otherwise, will attempt to use the configured value or will run unauthenticated. + + .PARAMETER NoStatus + If this switch is specified, long-running commands will run on the main thread + with no commandline status update. When not specified, those commands run in + the background, enabling the command prompt to provide status information. + If not supplied here, the DefaultNoStatus configuration property value will be used. + + .INPUTS + GitHub.Branch + GitHub.Content + GitHub.Event + GitHub.Issue + GitHub.IssueComment + GitHub.Label + GitHub.Milestone + GitHub.PullRequest + GitHub.Project + GitHub.ProjectCard + GitHub.ProjectColumn + GitHub.Reaction + GitHub.Release + GitHub.Repository + + .OUTPUTS + GitHub.Reaction + + .EXAMPLE + Set-GitHubReaction -OwnerName PowerShell -RepositoryName PowerShell -Issue 12626 -ReactionType rocket + + Sets the 'rocket' reaction for issue 12626 of the PowerShell\PowerShell project. + + .EXAMPLE + Get-GitHubPullRequest -Uri https://github.com/microsoft/PowerShellForGitHub -PullRequest 193 | Set-GitHubReaction -ReactionType Heart + + Gets a GitHub pull request and pipes it into Set-GitHubReaction to set the + 'heart' reaction for that pull request. + + .NOTES + Currently, issue comments, pull request comments and commit comments are not supported. +#> + [CmdletBinding( + SupportsShouldProcess, + DefaultParameterSetName='ElementsIssue')] + [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSShouldProcess", "", Justification="Methods called within here make use of PSShouldProcess, and the switch is passed on to them inherently.")] + [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSReviewUnusedParameter", "", Justification="One or more parameters (like NoStatus) are only referenced by helper methods which get access to it from the stack via Get-Variable -Scope 1.")] + param( + [Parameter( + Mandatory, + ParameterSetName='ElementsIssue')] + [Parameter( + Mandatory, + ParameterSetName='ElementsPullRequest')] + [string] $OwnerName, + + [Parameter( + Mandatory, + ParameterSetName='ElementsIssue')] + [Parameter( + Mandatory, + ParameterSetName='ElementsPullRequest')] + [string] $RepositoryName, + + [Parameter( + Mandatory, + ValueFromPipelineByPropertyName, + ParameterSetName='UriIssue')] + [Parameter( + Mandatory, + ValueFromPipelineByPropertyName, + ParameterSetName='UriPullRequest')] + [Alias('RepositoryUrl')] + [string] $Uri, + + [Parameter( + Mandatory, + ValueFromPipelineByPropertyName, + ParameterSetName='ElementsIssue')] + [Parameter( + Mandatory, + ParameterSetName='UriIssue', + ValueFromPipelineByPropertyName)] + [Alias('IssueNumber')] + [int64] $Issue, + + [Parameter( + Mandatory, + ValueFromPipelineByPropertyName, + ParameterSetName='ElementsPullRequest')] + [Parameter( + Mandatory, + ValueFromPipelineByPropertyName, + ParameterSetName='UriPullRequest')] + [Alias('PullRequestNumber')] + [int64] $PullRequest, + + [ValidateSet('+1', '-1', 'Laugh', 'Confused', 'Heart', 'Hooray', 'Rocket', 'Eyes')] + [Parameter(Mandatory)] + [string] $ReactionType, + + [string] $AccessToken, + + [switch] $NoStatus + ) + + Write-InvocationLog + + $elements = Resolve-RepositoryElements + $OwnerName = $elements.ownerName + $RepositoryName = $elements.repositoryName + + $telemetryProperties = @{ + 'OwnerName' = (Get-PiiSafeString -PlainText $OwnerName) + 'RepositoryName' = (Get-PiiSafeString -PlainText $RepositoryName) + } + + $splatForAddedProperties = @{ + OwnerName = $OwnerName + Repository = $RepositoryName + } + + if ($Issue) + { + $splatForAddedProperties.Issue = $Issue + $targetObjectNumber = $Issue + $targetObjectTypeName = 'Issue' + $uriFragment = "/repos/$OwnerName/$RepositoryName/issues/$targetObjectNumber/reactions" + } + else + { + # Pull request + $splatForAddedProperties.PullRequest = $PullRequest + $targetObjectNumber = $PullRequest + $targetObjectTypeName = 'Pull Request' + $uriFragment = "/repos/$OwnerName/$RepositoryName/issues/$targetObjectNumber/reactions" + } + + $description = "Setting reaction $ReactionType for $targetObjectTypeName $targetObjectNumber in $RepositoryName" + + $params = @{ + 'UriFragment' = $uriFragment + 'Description' = $description + 'Method' = 'Post' + 'Body' = ConvertTo-Json -InputObject @{ content = $ReactionType.ToLower() } + 'AcceptHeader' = $script:squirrelGirlAcceptHeader + 'AccessToken' = $AccessToken + 'TelemetryEventName' = $MyInvocation.MyCommand.Name + 'TelemetryProperties' = $telemetryProperties + 'NoStatus' = (Resolve-ParameterWithDefaultConfigurationValue -Name NoStatus -ConfigValueName DefaultNoStatus) + } + + $result = Invoke-GHRestMethod @params + + return ($result | Add-GitHubReactionAdditionalProperties @splatForAddedProperties) +} + +filter Remove-GitHubReaction +{ +<# + .SYNOPSIS + Removes a reaction on a given GitHub Issue or Pull Request. + + .DESCRIPTION + Removes a reaction on a given GitHub Issue or Pull Request. + + The Git repo for this module can be found here: http://aka.ms/PowerShellForGitHub + + .PARAMETER OwnerName + Owner of the repository. + If not supplied here, the DefaultOwnerName configuration property value will be used. + + .PARAMETER RepositoryName + Name of the repository. + If not supplied here, the DefaultRepositoryName configuration property value will be used. + + .PARAMETER Uri + Uri for the repository. + The OwnerName and RepositoryName will be extracted from here instead of needing to provide + them individually. + + .PARAMETER Issue + The issue number. + + .PARAMETER PullRequest + The pull request number. + + .PARAMETER ReactionId + The Id of the reaction. You can get this from using Get-GitHubReaction. + + .PARAMETER Force + If this switch is specified, you will not be prompted for confirmation of command execution. + + .PARAMETER AccessToken + If provided, this will be used as the AccessToken for authentication with the + REST Api. Otherwise, will attempt to use the configured value or will run unauthenticated. + + .PARAMETER NoStatus + If this switch is specified, long-running commands will run on the main thread + with no commandline status update. When not specified, those commands run in + the background, enabling the command prompt to provide status information. + If not supplied here, the DefaultNoStatus configuration property value will be used. + + .INPUTS + GitHub.Branch + GitHub.Content + GitHub.Event + GitHub.Issue + GitHub.IssueComment + GitHub.Label + GitHub.Milestone + GitHub.PullRequest + GitHub.Project + GitHub.ProjectCard + GitHub.ProjectColumn + GitHub.Reaction + GitHub.Release + GitHub.Repository + + .OUTPUTS + None + + .EXAMPLE + Remove-GitHubReaction -OwnerName PowerShell -RepositoryName PowerShell -Issue 12626 ` + -ReactionId 1234 + + Remove a reaction by Id on Issue 12626 from the PowerShell\PowerShell project + interactively. + + .EXAMPLE + Remove-GitHubReaction -OwnerName PowerShell -RepositoryName PowerShell -Issue 12626 -ReactionId 1234 -Confirm:$false + + Remove a reaction by Id on Issue 12626 from the PowerShell\PowerShell project + non-interactively. + + .EXAMPLE + Get-GitHubReaction -OwnerName PowerShell -RepositoryName PowerShell -Issue 12626 -ReactionType rocket | Remove-GitHubReaction -Confirm:$false + + Gets a reaction using Get-GitHubReaction and pipes it into Remove-GitHubReaction. + + .NOTES + Currently, issue comments, pull request comments and commit comments are not supported. +#> + [CmdletBinding( + SupportsShouldProcess, + DefaultParameterSetName='ElementsIssue', + ConfirmImpact='High')] + [Alias('Delete-GitHubReaction')] + [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSReviewUnusedParameter", "", Justification="One or more parameters (like NoStatus) are only referenced by helper methods which get access to it from the stack via Get-Variable -Scope 1.")] + param( + [Parameter( + Mandatory, + ParameterSetName='ElementsIssue')] + [Parameter( + Mandatory, + ParameterSetName='ElementsPullRequest')] + [string] $OwnerName, + + [Parameter( + Mandatory, + ParameterSetName='ElementsIssue')] + [Parameter( + Mandatory, + ParameterSetName='ElementsPullRequest')] + [string] $RepositoryName, + + [Parameter( + Mandatory, + ValueFromPipelineByPropertyName, + ParameterSetName='UriIssue')] + [Parameter( + Mandatory, + ValueFromPipelineByPropertyName, + ParameterSetName='UriPullRequest')] + [Alias('RepositoryUrl')] + [string] $Uri, + + [Parameter( + Mandatory, + ValueFromPipelineByPropertyName, + ParameterSetName='ElementsIssue')] + [Parameter( + Mandatory, + ParameterSetName='UriIssue', + ValueFromPipelineByPropertyName)] + [Alias('IssueNumber')] + [int64] $Issue, + + [Parameter( + Mandatory, + ValueFromPipelineByPropertyName, + ParameterSetName='ElementsPullRequest')] + [Parameter( + Mandatory, + ValueFromPipelineByPropertyName, + ParameterSetName='UriPullRequest')] + [Alias('PullRequestNumber')] + [int64] $PullRequest, + + [Parameter( + Mandatory, + ValueFromPipelineByPropertyName, + ValueFromPipeline)] + [int64] $ReactionId, + + [Parameter()] + [switch] $Force, + + [string] $AccessToken, + + [switch] $NoStatus + ) + + Write-InvocationLog + + $elements = Resolve-RepositoryElements + $OwnerName = $elements.ownerName + $RepositoryName = $elements.repositoryName + + $telemetryProperties = @{ + 'OwnerName' = (Get-PiiSafeString -PlainText $OwnerName) + 'RepositoryName' = (Get-PiiSafeString -PlainText $RepositoryName) + } + + if ($Issue) + { + $targetObjectNumber = $Issue + $targetObjectTypeName = 'Issue' + $uriFragment = "/repos/$OwnerName/$RepositoryName/issues/$targetObjectNumber/reactions/$ReactionId" + } + else + { + # Pull request + $targetObjectNumber = $PullRequest + $targetObjectTypeName = 'Pull Request' + $uriFragment = "/repos/$OwnerName/$RepositoryName/issues/$targetObjectNumber/reactions/$ReactionId" + } + + $description = "Removing reaction $ReactionId for $targetObjectTypeName $targetObjectNumber in $RepositoryName" + + if ($Force -and (-not $Confirm)) + { + $ConfirmPreference = 'None' + } + + if ($PSCmdlet.ShouldProcess( + $ReactionId, + "Removing reaction for $targetObjectTypeName $targetObjectNumber in $RepositoryName")) + { + $params = @{ + 'UriFragment' = $uriFragment + 'Description' = $description + 'Method' = 'Delete' + 'AcceptHeader' = $script:squirrelGirlAcceptHeader + 'AccessToken' = $AccessToken + 'TelemetryEventName' = $MyInvocation.MyCommand.Name + 'TelemetryProperties' = $telemetryProperties + 'NoStatus' = (Resolve-ParameterWithDefaultConfigurationValue -Name NoStatus -ConfigValueName DefaultNoStatus) + } + + return Invoke-GHRestMethod @params + } +} + +filter Add-GitHubReactionAdditionalProperties +{ +<# + .SYNOPSIS + Adds type name and additional properties to ease pipelining to GitHub Reaction objects. + + .PARAMETER InputObject + The GitHub object to add additional properties to. + + .PARAMETER TypeName + The type that should be assigned to the object. + + .PARAMETER OwnerName + Owner of the repository. + + .PARAMETER RepositoryName + Name of the repository. + + .PARAMETER Issue + The issue number. + + .PARAMETER PullRequest + The pull request number. + + .INPUTS + [PSCustomObject] + + .OUTPUTS + GitHub.Reaction +#> + [CmdletBinding()] + [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSUseSingularNouns", "", Justification="Internal helper that is definitely adding more than one property.")] + param( + [Parameter( + Mandatory, + ValueFromPipeline)] + [AllowNull()] + [AllowEmptyCollection()] + [PSCustomObject[]] $InputObject, + + [ValidateNotNullOrEmpty()] + [string] $TypeName = $script:GitHubReactionTypeName, + + [Parameter(Mandatory)] + [string] $OwnerName, + + [Parameter(Mandatory)] + [string] $RepositoryName, + + [Parameter( + Mandatory, + ParameterSetName='Issue')] + [Alias('IssueNumber')] + [int64] $Issue, + + [Parameter( + Mandatory, + ParameterSetName='PullRequest')] + [Alias('PullRequestNumber')] + [int64] $PullRequest + ) + + foreach ($item in $InputObject) + { + $item.PSObject.TypeNames.Insert(0, $TypeName) + + if (-not (Get-GitHubConfiguration -Name DisablePipelineSupport)) + { + $repositoryUrl = Join-GitHubUri -OwnerName $OwnerName -RepositoryName $RepositoryName + Add-Member -InputObject $item -Name 'RepositoryUrl' -Value $repositoryUrl -MemberType NoteProperty -Force + Add-Member -InputObject $item -Name 'ReactionId' -Value $item.id -MemberType NoteProperty -Force + + if ($PullRequest) + { + Add-Member -InputObject $item -Name 'PullRequestNumber' -Value $PullRequest -MemberType NoteProperty -Force + } + else + { + # Issue + Add-Member -InputObject $item -Name 'IssueNumber' -Value $Issue -MemberType NoteProperty -Force + } + + @('assignee', 'assignees', 'user') | + ForEach-Object { + if ($null -ne $item.$_) + { + $null = Add-GitHubUserAdditionalProperties -InputObject $item.$_ + } + } + } + + Write-Output $item + } +} diff --git a/GitHubReleases.ps1 b/GitHubReleases.ps1 index 997faff6..7e5cf6c9 100644 --- a/GitHubReleases.ps1 +++ b/GitHubReleases.ps1 @@ -65,6 +65,7 @@ filter Get-GitHubRelease GitHub.Project GitHub.ProjectCard GitHub.ProjectColumn + GitHub.Reaction GitHub.Release GitHub.Repository diff --git a/GitHubRepositories.ps1 b/GitHubRepositories.ps1 index 847e82b5..6a3fbc89 100644 --- a/GitHubRepositories.ps1 +++ b/GitHubRepositories.ps1 @@ -448,6 +448,7 @@ filter Remove-GitHubRepository GitHub.Project GitHub.ProjectCard GitHub.ProjectColumn + GitHub.Reaction GitHub.Release GitHub.Repository @@ -614,6 +615,7 @@ filter Get-GitHubRepository GitHub.Project GitHub.ProjectCard GitHub.ProjectColumn + GitHub.Reaction GitHub.Release GitHub.Repository @@ -938,6 +940,7 @@ filter Rename-GitHubRepository GitHub.Project GitHub.ProjectCard GitHub.ProjectColumn + GitHub.Reaction GitHub.Release GitHub.Repository @@ -1119,6 +1122,7 @@ filter Set-GitHubRepository GitHub.Project GitHub.ProjectCard GitHub.ProjectColumn + GitHub.Reaction GitHub.Release GitHub.Repository @@ -1303,6 +1307,7 @@ filter Get-GitHubRepositoryTopic GitHub.Project GitHub.ProjectCard GitHub.ProjectColumn + GitHub.Reaction GitHub.Release GitHub.Repository @@ -1418,6 +1423,7 @@ function Set-GitHubRepositoryTopic GitHub.Project GitHub.ProjectCard GitHub.ProjectColumn + GitHub.Reaction GitHub.Release GitHub.Repository @@ -1605,6 +1611,7 @@ filter Get-GitHubRepositoryContributor GitHub.Project GitHub.ProjectCard GitHub.ProjectColumn + GitHub.Reaction GitHub.Release GitHub.Repository @@ -1757,6 +1764,7 @@ filter Get-GitHubRepositoryCollaborator GitHub.Project GitHub.ProjectCard GitHub.ProjectColumn + GitHub.Reaction GitHub.Release GitHub.Repository @@ -1875,6 +1883,7 @@ filter Get-GitHubRepositoryLanguage GitHub.Project GitHub.ProjectCard GitHub.ProjectColumn + GitHub.Reaction GitHub.Release GitHub.Repository @@ -1983,6 +1992,7 @@ filter Get-GitHubRepositoryTag GitHub.Project GitHub.ProjectCard GitHub.ProjectColumn + GitHub.Reaction GitHub.Release GitHub.Repository @@ -2097,6 +2107,7 @@ filter Move-GitHubRepositoryOwnership GitHub.Project GitHub.ProjectCard GitHub.ProjectColumn + GitHub.Reaction GitHub.Release GitHub.Repository diff --git a/GitHubRepositoryForks.ps1 b/GitHubRepositoryForks.ps1 index 98e311d2..23cc63ae 100644 --- a/GitHubRepositoryForks.ps1 +++ b/GitHubRepositoryForks.ps1 @@ -50,6 +50,7 @@ filter Get-GitHubRepositoryFork GitHub.Project GitHub.ProjectCard GitHub.ProjectColumn + GitHub.Reaction GitHub.Release GitHub.Repository @@ -167,6 +168,7 @@ filter New-GitHubRepositoryFork GitHub.Project GitHub.ProjectCard GitHub.ProjectColumn + GitHub.Reaction GitHub.Release GitHub.Repository diff --git a/GitHubRepositoryTraffic.ps1 b/GitHubRepositoryTraffic.ps1 index d3c26ddd..0d32c21e 100644 --- a/GitHubRepositoryTraffic.ps1 +++ b/GitHubRepositoryTraffic.ps1 @@ -56,6 +56,7 @@ filter Get-GitHubReferrerTraffic GitHub.Project GitHub.ProjectCard GitHub.ProjectColumn + GitHub.Reaction GitHub.Release GitHub.Repository @@ -169,6 +170,7 @@ filter Get-GitHubPathTraffic GitHub.Project GitHub.ProjectCard GitHub.ProjectColumn + GitHub.Reaction GitHub.Release GitHub.Repository @@ -289,6 +291,7 @@ filter Get-GitHubViewTraffic GitHub.Project GitHub.ProjectCard GitHub.ProjectColumn + GitHub.Reaction GitHub.Release GitHub.Repository @@ -413,6 +416,7 @@ filter Get-GitHubCloneTraffic GitHub.Project GitHub.ProjectCard GitHub.ProjectColumn + GitHub.Reaction GitHub.Release GitHub.Repository diff --git a/GitHubTeams.ps1 b/GitHubTeams.ps1 index 54b1f809..44c94399 100644 --- a/GitHubTeams.ps1 +++ b/GitHubTeams.ps1 @@ -60,6 +60,7 @@ filter Get-GitHubTeam GitHub.Project GitHub.ProjectCard GitHub.ProjectColumn + GitHub.Reaction GitHub.Release GitHub.Repository GitHub.Team diff --git a/PowerShellForGitHub.psd1 b/PowerShellForGitHub.psd1 index a15992a6..4be54289 100644 --- a/PowerShellForGitHub.psd1 +++ b/PowerShellForGitHub.psd1 @@ -41,6 +41,7 @@ 'GitHubProjectCards.ps1', 'GitHubProjectColumns.ps1', 'GitHubPullRequests.ps1', + 'GitHubReactions.ps1', 'GitHubReleases.ps1', 'GitHubRepositories.ps1', 'GitHubRepositoryForks.ps1', @@ -85,6 +86,7 @@ 'Get-GitHubProjectColumn', 'Get-GitHubPullRequest', 'Get-GitHubRateLimit', + 'Get-GitHubReaction', 'Get-GitHubReferrerTraffic', 'Get-GitHubRelease', 'Get-GitHubRepository', @@ -131,6 +133,7 @@ 'Remove-GitHubProject', 'Remove-GitHubProjectCard', 'Remove-GitHubProjectColumn', + 'Remove-GitHubReaction', 'Remove-GitHubRepository', 'Remove-GitHubRepositoryBranch' 'Rename-GitHubRepository', @@ -148,7 +151,8 @@ 'Set-GitHubProject', 'Set-GitHubProjectCard', 'Set-GitHubProjectColumn', - 'Set-GitHubRepository' + 'Set-GitHubReaction', + 'Set-GitHubRepository', 'Set-GitHubRepositoryTopic', 'Split-GitHubUri', 'Test-GitHubAssignee', @@ -166,7 +170,8 @@ 'Delete-GitHubMilestone', 'Delete-GitHubProject', 'Delete-GitHubProjectCard', - 'Delete-GitHubProjectColumn' + 'Delete-GitHubProjectColumn', + 'Delete-GitHubReaction', 'Delete-GitHubRepository', 'Delete-GitHubRepositoryBranch', 'Get-GitHubBranch', diff --git a/Tests/GitHubReactions.Tests.ps1 b/Tests/GitHubReactions.Tests.ps1 new file mode 100644 index 00000000..703b7f43 --- /dev/null +++ b/Tests/GitHubReactions.Tests.ps1 @@ -0,0 +1,118 @@ +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. + +<# +.Synopsis + Tests for GitHubReactions.ps1 module +#> + +[CmdletBinding()] +[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseDeclaredVarsMoreThanAssignments', '', + Justification='Suppress false positives in Pester code blocks')] +param() + +# This is common test code setup logic for all Pester test files +$moduleRootPath = Split-Path -Path $PSScriptRoot -Parent +. (Join-Path -Path $moduleRootPath -ChildPath 'Tests\Common.ps1') + +try +{ + # Define Script-scoped, readonly, hidden variables. + @{ + defaultIssueTitle = "Test Title" + defaultCommentBody = "This is a test body." + defaultReactionType = "+1" + otherReactionType = "eyes" + }.GetEnumerator() | ForEach-Object { + Set-Variable -Force -Scope Script -Option ReadOnly -Visibility Private -Name $_.Key -Value $_.Value + } + + Describe 'Creating, modifying and deleting reactions' { + BeforeAll { + $repo = New-GitHubRepository -RepositoryName ([Guid]::NewGuid().Guid) -AutoInit + $issue = New-GitHubIssue -Uri $repo.svn_url -Title $defaultIssueTitle + $issueComment = $issue | New-GitHubIssueComment -Body "Foo" + } + + Context 'For creating a reaction' { + Set-GitHubReaction -Uri $repo.svn_url -Issue $issue.IssueNumber -ReactionType $defaultReactionType + $existingReaction = Get-GitHubReaction -Uri $repo.svn_url -Issue $issue.IssueNumber + + It "Should have the expected reaction type" { + $existingReaction.content | Should -Be $defaultReactionType + } + } + + Context 'For getting reactions from an issue' { + Get-GitHubIssue -Uri $repo.svn_url -Issue $issue.IssueNumber | Set-GitHubReaction -ReactionType $otherReactionType + $allReactions = Get-GitHubReaction -Uri $repo.svn_url -Issue $issue.IssueNumber + $specificReactions = Get-GitHubReaction -Uri $repo.svn_url -Issue $issue.IssueNumber -ReactionType $otherReactionType + + It 'Should have the expected number of reactions' { + $allReactions.Count | Should -Be 2 + $specificReactions | Measure-Object | Select-Object -ExpandProperty Count | Should -Be 1 + } + + It 'Should have the expected reaction content' { + $specificReactions.content | Should -Be $otherReactionType + $specificReactions.RepositoryUrl | Should -Be $repo.RepositoryUrl + $specificReactions.IssueNumber | Should -Be $issue.IssueNumber + $specificReactions.ReactionId | Should -Be $specificReactions.id + $specificReactions.PSObject.TypeNames[0] | Should -Be 'GitHub.Reaction' + } + } + + Context 'For getting reactions from a pull request' { + # TODO: there are currently PRs out to add the ability to create new branches and add content to a repo. + # When those go in, this test can be refactored to use those so the test is more reliable using a test PR. + $url = 'https://github.com/microsoft/PowerShellForGitHub' + $pr = Get-GitHubPullRequest -Uri $url -PullRequest 193 + + $allReactions = $pr | Get-GitHubReaction + $specificReactions = $pr | Get-GitHubReaction -ReactionType $otherReactionType + + It 'Should have the expected number of reactions' { + $allReactions.Count | Should -Be 2 + $specificReactions | Measure-Object | Select-Object -ExpandProperty Count | Should -Be 1 + } + + It 'Should have the expected reaction content' { + $specificReactions.content | Should -Be $otherReactionType + $specificReactions.RepositoryUrl | Should -Be $url + $specificReactions.PullRequestNumber | Should -Be $pr.PullRequestNumber + $specificReactions.ReactionId | Should -Be $specificReactions.id + $specificReactions.PSObject.TypeNames[0] | Should -Be 'GitHub.Reaction' + } + } + + Context 'For getting reactions from an Issue and deleting them' { + $existingReactions = @(Get-GitHubReaction -Uri $repo.svn_url -Issue $issue.number) + + It 'Should have the expected number of reactions' { + $existingReactions.Count | Should -Be 2 + } + + $existingReactions | Remove-GitHubReaction -Force + + $existingReactions = @(Get-GitHubReaction -Uri $repo.svn_url -Issue $issue.number) + + It 'Should have no reactions' { + $existingReactions + $existingReactions.Count | Should -Be 0 + } + } + + AfterAll { + Remove-GitHubRepository -Uri $repo.svn_url -Confirm:$false + } + } +} +finally +{ + if (Test-Path -Path $script:originalConfigFile -PathType Leaf) + { + # Restore the user's configuration to its pre-test state + Restore-GitHubConfiguration -Path $script:originalConfigFile + $script:originalConfigFile = $null + } +} From f406cc5b8ab9eb0229c1834c3266060d8fc1ceb1 Mon Sep 17 00:00:00 2001 From: Howard Wolosky Date: Sat, 18 Jul 2020 12:06:17 -0700 Subject: [PATCH 55/60] Attempting to increase reliability of some Pester tests (#265) We're seeing some inconsistent failures in some of the Pester tests. The hypothesis is that GitHub may need a little bit more time after the creation of objects before performing certain operations on them (like renaming repos), or may need more time after deleting them before it will successfully return a 404 on a successive Get request. I have added a number of `Start-Sleep`'s throughout the test codebase wherever we've encountered inconsistent failures, and that appears to have resolved the problem. We may need to continue to do more of these if additional ones pop up. The duration of the sleep itself is controlled by `$script:defaultSleepSecondsForReliability` which is defined in `Tests/Common.ps1`. Long term, I have opened #267 which poses the idea of switching over to mocking out `Invoke-WebRequest` for the majority of the tests, and instead focus on validating the data that it's sending matches the expected values per the API documentation, and having just a limited number of tests that do actual end-to-end testing. Fixes #264 --- Tests/Common.ps1 | 4 + Tests/GitHubAssignees.tests.ps1 | 41 ++++++++++ Tests/GitHubContents.tests.ps1 | 8 ++ Tests/GitHubIssues.tests.ps1 | 9 ++- Tests/GitHubLabels.tests.ps1 | 65 +++++++++++++++ Tests/GitHubProjects.tests.ps1 | 8 ++ Tests/GitHubRepositories.tests.ps1 | 109 +++++++++++++++++++++++++- Tests/GitHubRepositoryForks.tests.ps1 | 13 ++- 8 files changed, 251 insertions(+), 6 deletions(-) diff --git a/Tests/Common.ps1 b/Tests/Common.ps1 index 054eb7e8..41253602 100644 --- a/Tests/Common.ps1 +++ b/Tests/Common.ps1 @@ -100,6 +100,10 @@ function Initialize-CommonTestSetup Set-GitHubConfiguration -LogRequestBody # Make it easier to debug UT failures Set-GitHubConfiguration -MultiRequestProgressThreshold 0 # Status corrupts the raw CI logs for Linux and Mac, and makes runs take slightly longer. Set-GitHubConfiguration -DisableUpdateCheck # The update check is unnecessary during tests. + + # The number of seconds to sleep after performing some operations to ensure that successive + # API calls properly reflect previously updated state. + $script:defaultSleepSecondsForReliability = 3 } Initialize-CommonTestSetup diff --git a/Tests/GitHubAssignees.tests.ps1 b/Tests/GitHubAssignees.tests.ps1 index 64a694b5..0501f183 100644 --- a/Tests/GitHubAssignees.tests.ps1 +++ b/Tests/GitHubAssignees.tests.ps1 @@ -107,12 +107,22 @@ try Context 'Adding and removing an assignee via parameters' { $issue = $repo | New-GitHubIssue -Title "Test issue" + + # The CI build has been unreliable with this test. + # Adding a short sleep to ensure successive queries reflect updated state. + Start-Sleep -Seconds $script:defaultSleepSecondsForReliability + It 'Should have no assignees when created' { $issue.assignee.login | Should -BeNullOrEmpty $issue.assignees | Should -BeNullOrEmpty } $updatedIssue = Add-GitHubAssignee -OwnerName $script:ownerName -RepositoryName $repo.name -Issue $issue.number -Assignee $owner.login + + # The CI build has been unreliable with this test. + # Adding a short sleep to ensure successive queries reflect updated state. + Start-Sleep -Seconds $script:defaultSleepSecondsForReliability + It 'Should have returned the same issue' { $updatedIssue.number | Should -Be $issue.number } @@ -144,12 +154,22 @@ try Context 'Adding an assignee with the repo on the pipeline' { $issue = $repo | New-GitHubIssue -Title "Test issue" + + # The CI build has been unreliable with this test. + # Adding a short sleep to ensure successive queries reflect updated state. + Start-Sleep -Seconds $script:defaultSleepSecondsForReliability + It 'Should have no assignees when created' { $issue.assignee.login | Should -BeNullOrEmpty $issue.assignees | Should -BeNullOrEmpty } $updatedIssue = $repo | Add-GitHubAssignee -Issue $issue.number -Assignee $owner.login + + # The CI build has been unreliable with this test. + # Adding a short sleep to ensure successive queries reflect updated state. + Start-Sleep -Seconds $script:defaultSleepSecondsForReliability + It 'Should have returned the same issue' { $updatedIssue.number | Should -Be $issue.number } @@ -181,12 +201,22 @@ try Context 'Adding an assignee with the issue on the pipeline' { $issue = $repo | New-GitHubIssue -Title "Test issue" + + # The CI build has been unreliable with this test. + # Adding a short sleep to ensure successive queries reflect updated state. + Start-Sleep -Seconds $script:defaultSleepSecondsForReliability + It 'Should have no assignees when created' { $issue.assignee.login | Should -BeNullOrEmpty $issue.assignees | Should -BeNullOrEmpty } $updatedIssue = $issue | Add-GitHubAssignee -Assignee $owner.login + + # The CI build has been unreliable with this test. + # Adding a short sleep to ensure successive queries reflect updated state. + Start-Sleep -Seconds $script:defaultSleepSecondsForReliability + It 'Should have returned the same issue' { $updatedIssue.number | Should -Be $issue.number } @@ -218,12 +248,22 @@ try Context 'Adding an assignee with the assignee user object on the pipeline' { $issue = $repo | New-GitHubIssue -Title "Test issue" + + # The CI build has been unreliable with this test. + # Adding a short sleep to ensure successive queries reflect updated state. + Start-Sleep -Seconds $script:defaultSleepSecondsForReliability + It 'Should have no assignees when created' { $issue.assignee.login | Should -BeNullOrEmpty $issue.assignees | Should -BeNullOrEmpty } $updatedIssue = $owner | Add-GitHubAssignee -OwnerName $script:ownerName -RepositoryName $repo.name -Issue $issue.number + + # The CI build has been unreliable with this test. + # Adding a short sleep to ensure successive queries reflect updated state. + Start-Sleep -Seconds $script:defaultSleepSecondsForReliability + It 'Should have returned the same issue' { $updatedIssue.number | Should -Be $issue.number } @@ -239,6 +279,7 @@ try } $updatedIssue = $owner | Remove-GitHubAssignee -OwnerName $script:ownerName -RepositoryName $repo.name -Issue $issue.number -Force + It 'Should have returned the same issue' { $updatedIssue.number | Should -Be $issue.number } diff --git a/Tests/GitHubContents.tests.ps1 b/Tests/GitHubContents.tests.ps1 index c1b9661e..a52ed473 100644 --- a/Tests/GitHubContents.tests.ps1 +++ b/Tests/GitHubContents.tests.ps1 @@ -37,6 +37,10 @@ try BeforeAll { # AutoInit will create a readme with the GUID of the repo name $repo = New-GitHubRepository -RepositoryName ($repoGuid) -AutoInit + + # The CI build has been unreliable with this test. + # Adding a short sleep to ensure successive queries reflect updated state. + Start-Sleep -Seconds $script:defaultSleepSecondsForReliability } AfterAll { @@ -260,6 +264,10 @@ try $repoName = [Guid]::NewGuid().Guid $repo = New-GitHubRepository -RepositoryName $repoName -AutoInit + + # The CI build has been unreliable with this test. + # Adding a short sleep to ensure successive queries reflect updated state. + Start-Sleep -Seconds $script:defaultSleepSecondsForReliability } Context 'When setting new file content' { diff --git a/Tests/GitHubIssues.tests.ps1 b/Tests/GitHubIssues.tests.ps1 index ed5c4d88..1cff5d0c 100644 --- a/Tests/GitHubIssues.tests.ps1 +++ b/Tests/GitHubIssues.tests.ps1 @@ -161,7 +161,9 @@ try for ($i = 0; $i -lt 4; $i++) { $newIssues += New-GitHubIssue -OwnerName $script:ownerName -RepositoryName $repo.name -Title ([Guid]::NewGuid().Guid) - Start-Sleep -Seconds 1 # Needed to ensure that there is a unique creation timestamp between issues + + # Needed to ensure that there is a unique creation timestamp between issues + Start-Sleep -Seconds $script:defaultSleepSecondsForReliability } $newIssues[0] = Set-GitHubIssue -OwnerName $script:ownerName -RepositoryName $repo.name -Issue $newIssues[0].number -State Closed @@ -547,6 +549,11 @@ try } Lock-GitHubIssue -OwnerName $script:OwnerName -RepositoryName $repo.name -Issue $issue.number + + # The CI build has been unreliable with this test. + # Adding a short sleep to ensure successive queries reflect updated state. + Start-Sleep -Seconds $script:defaultSleepSecondsForReliability + $timeline = @(Get-GitHubIssueTimeline -OwnerName $script:OwnerName -RepositoryName $repo.name -Issue $issue.number) It 'Should have an event now' { $timeline.Count | Should -Be 1 diff --git a/Tests/GitHubLabels.tests.ps1 b/Tests/GitHubLabels.tests.ps1 index 5c3a1e30..8c635f0f 100644 --- a/Tests/GitHubLabels.tests.ps1 +++ b/Tests/GitHubLabels.tests.ps1 @@ -81,6 +81,10 @@ try $repositoryName = [Guid]::NewGuid().Guid $repo = New-GitHubRepository -RepositoryName $repositoryName + # The CI build has been unreliable with this test. + # Adding a short sleep to ensure successive queries reflect updated state. + Start-Sleep -Seconds $script:defaultSleepSecondsForReliability + Initialize-GitHubLabel -OwnerName $script:ownerName -RepositoryName $repositoryName -Label $defaultLabels } @@ -205,6 +209,10 @@ try BeforeAll { $repositoryName = [Guid]::NewGuid().Guid $repo = New-GitHubRepository -RepositoryName $repositoryName + + # The CI build has been unreliable with this test. + # Adding a short sleep to ensure successive queries reflect updated state. + Start-Sleep -Seconds $script:defaultSleepSecondsForReliability } AfterAll { @@ -319,6 +327,10 @@ try BeforeAll { $repositoryName = [Guid]::NewGuid().Guid $repo = New-GitHubRepository -RepositoryName $repositoryName + + # The CI build has been unreliable with this test. + # Adding a short sleep to ensure successive queries reflect updated state. + Start-Sleep -Seconds $script:defaultSleepSecondsForReliability } AfterAll { @@ -327,6 +339,11 @@ try Context 'Removing a label with parameters' { $label = $repo | New-GitHubLabel -Label 'test' -Color 'CCCCCC' + + # The CI build has been unreliable with this test. + # Adding a short sleep to ensure successive queries reflect updated state. + Start-Sleep -Seconds $script:defaultSleepSecondsForReliability + Remove-GitHubLabel -OwnerName $script:ownerName -RepositoryName $repositoryName -Label $label.name -Force It 'Should be gone after being removed by parameter' { @@ -336,6 +353,11 @@ try Context 'Removing a label with the repo on the pipeline' { $label = $repo | New-GitHubLabel -Label 'test' -Color 'CCCCCC' + + # The CI build has been unreliable with this test. + # Adding a short sleep to ensure successive queries reflect updated state. + Start-Sleep -Seconds $script:defaultSleepSecondsForReliability + $repo | Remove-GitHubLabel -Label $label.name -Confirm:$false It 'Should be gone after being removed by parameter' { @@ -345,6 +367,11 @@ try Context 'Removing a label with the name on the pipeline' { $label = $repo | New-GitHubLabel -Label 'test' -Color 'CCCCCC' + + # The CI build has been unreliable with this test. + # Adding a short sleep to ensure successive queries reflect updated state. + Start-Sleep -Seconds $script:defaultSleepSecondsForReliability + $label.name | Remove-GitHubLabel -OwnerName $script:ownerName -RepositoryName $repositoryName -Force It 'Should be gone after being removed by parameter' { @@ -354,6 +381,11 @@ try Context 'Removing a label with the label object on the pipeline' { $label = $repo | New-GitHubLabel -Label 'test' -Color 'CCCCCC' + + # The CI build has been unreliable with this test. + # Adding a short sleep to ensure successive queries reflect updated state. + Start-Sleep -Seconds $script:defaultSleepSecondsForReliability + $label | Remove-GitHubLabel -Force It 'Should be gone after being removed by parameter' { @@ -366,6 +398,10 @@ try BeforeAll { $repositoryName = [Guid]::NewGuid().Guid $repo = New-GitHubRepository -RepositoryName $repositoryName + + # The CI build has been unreliable with this test. + # Adding a short sleep to ensure successive queries reflect updated state. + Start-Sleep -Seconds $script:defaultSleepSecondsForReliability } AfterAll { @@ -543,6 +579,10 @@ try BeforeAll { $repositoryName = [Guid]::NewGuid().Guid $repo = New-GitHubRepository -RepositoryName $repositoryName + + # The CI build has been unreliable with this test. + # Adding a short sleep to ensure successive queries reflect updated state. + Start-Sleep -Seconds $script:defaultSleepSecondsForReliability } AfterAll { @@ -617,6 +657,11 @@ try BeforeAll { $repositoryName = [Guid]::NewGuid().Guid $repo = New-GitHubRepository -RepositoryName $repositoryName + + # The CI build has been unreliable with this test. + # Adding a short sleep to ensure successive queries reflect updated state. + Start-Sleep -Seconds $script:defaultSleepSecondsForReliability + $repo | Initialize-GitHubLabel -Label $defaultLabels } @@ -845,6 +890,11 @@ try BeforeAll { $repositoryName = [Guid]::NewGuid().Guid $repo = New-GitHubRepository -RepositoryName $repositoryName + + # The CI build has been unreliable with this test. + # Adding a short sleep to ensure successive queries reflect updated state. + Start-Sleep -Seconds $script:defaultSleepSecondsForReliability + $repo | Initialize-GitHubLabel -Label $defaultLabels } @@ -911,6 +961,11 @@ try BeforeAll { $repositoryName = [Guid]::NewGuid().Guid $repo = New-GitHubRepository -RepositoryName $repositoryName + + # The CI build has been unreliable with this test. + # Adding a short sleep to ensure successive queries reflect updated state. + Start-Sleep -Seconds $script:defaultSleepSecondsForReliability + $repo | Initialize-GitHubLabel -Label $defaultLabels } @@ -1078,6 +1133,11 @@ try BeforeAll { $repositoryName = [Guid]::NewGuid().Guid $repo = New-GitHubRepository -RepositoryName $repositoryName + + # The CI build has been unreliable with this test. + # Adding a short sleep to ensure successive queries reflect updated state. + Start-Sleep -Seconds $script:defaultSleepSecondsForReliability + $repo | Initialize-GitHubLabel -Label $defaultLabels } @@ -1227,6 +1287,11 @@ try BeforeAll { $repositoryName = [Guid]::NewGuid().Guid $repo = New-GitHubRepository -RepositoryName $repositoryName + + # The CI build has been unreliable with this test. + # Adding a short sleep to ensure successive queries reflect updated state. + Start-Sleep -Seconds $script:defaultSleepSecondsForReliability + $repo | Initialize-GitHubLabel -Label $defaultLabels $milestone = $repo | New-GitHubMilestone -Title 'test milestone' diff --git a/Tests/GitHubProjects.tests.ps1 b/Tests/GitHubProjects.tests.ps1 index ccde85d6..3417c1d4 100644 --- a/Tests/GitHubProjects.tests.ps1 +++ b/Tests/GitHubProjects.tests.ps1 @@ -611,6 +611,10 @@ try $project = New-GitHubProject -OwnerName $script:ownerName -RepositoryName $repo.name -ProjectName $defaultRepoProject -Description $defaultRepoProjectDesc $null = Remove-GitHubProject -Project $project.id -Confirm:$false It 'Project should be removed' { + # The CI build has been unreliable with this test. + # Adding a short sleep to ensure successive queries reflect updated state. + Start-Sleep -Seconds $script:defaultSleepSecondsForReliability + {Get-GitHubProject -Project $project.id} | Should -Throw } } @@ -619,6 +623,10 @@ try $project = $repo | New-GitHubProject -ProjectName $defaultRepoProject -Description $defaultRepoProjectDesc $project | Remove-GitHubProject -Force It 'Project should be removed' { + # The CI build has been unreliable with this test. + # Adding a short sleep to ensure successive queries reflect updated state. + Start-Sleep -Seconds $script:defaultSleepSecondsForReliability + {$project | Get-GitHubProject} | Should -Throw } } diff --git a/Tests/GitHubRepositories.tests.ps1 b/Tests/GitHubRepositories.tests.ps1 index 6fbcd27a..5ea84b12 100644 --- a/Tests/GitHubRepositories.tests.ps1 +++ b/Tests/GitHubRepositories.tests.ps1 @@ -125,6 +125,10 @@ try IsTemplate = $true } $repo = New-GitHubRepository @newGitHubRepositoryParms + + # The CI build has been unreliable with this test. + # Adding a short sleep to ensure successive queries reflect updated state. + Start-Sleep -Seconds $script:defaultSleepSecondsForReliability } It 'Should return an object of the correct type' { @@ -306,6 +310,10 @@ try } $templateRepo = New-GitHubRepository @newGitHubRepositoryParms + + # The CI build has been unreliable with this test. + # Adding a short sleep to ensure successive queries reflect updated state. + Start-Sleep -Seconds $script:defaultSleepSecondsForReliability } Context 'When creating a public repository from a template' { @@ -321,7 +329,10 @@ try } $repo = New-GitHubRepositoryFromTemplate @newGitHubRepositoryFromTemplateParms - Start-Sleep -Seconds 1 # To work around a delay that GitHub may have with generating the repo + + # The CI build has been unreliable with this test. + # Adding a short sleep to ensure successive queries reflect updated state. + Start-Sleep -Seconds $script:defaultSleepSecondsForReliability } It 'Should have the expected type and addititional properties' { @@ -362,7 +373,10 @@ try } $repo = $templateRepo | New-GitHubRepositoryFromTemplate @newGitHubRepositoryFromTemplateParms - Start-Sleep -Seconds 1 # To work around a delay that GitHub may have with generating the repo + + # The CI build has been unreliable with this test. + # Adding a short sleep to ensure successive queries reflect updated state. + Start-Sleep -Seconds $script:defaultSleepSecondsForReliability } It 'Should have the expected type and addititional properties' { @@ -405,6 +419,10 @@ try BeforeAll { $publicRepo = New-GitHubRepository -RepositoryName ([Guid]::NewGuid().Guid) $privateRepo = New-GitHubRepository -RepositoryName ([Guid]::NewGuid().Guid) -Private + + # The CI build has been unreliable with this test. + # Adding a short sleep to ensure successive queries reflect updated state. + Start-Sleep -Seconds $script:defaultSleepSecondsForReliability } Context 'When specify the visibility parameter' { @@ -485,7 +503,8 @@ try } It "Should return the correct membership order" { - for ($i = 1; $i -le $sortedRepos.count; $i++) { + for ($i = 1; $i -le $sortedRepos.count; $i++) + { $sortedRepos[$i].full_name | Should -Be $sortedRepoFullNames[$i] $sortedDescendingRepos[$i].full_name | Should -Be $sortedDescendingRepoFullNames[$i] } @@ -527,7 +546,8 @@ try } It 'Should return the correct properties' { - foreach ($repo in $repos) { + foreach ($repo in $repos) + { $repo.owner.login | Should -Be $ownerName } } @@ -536,6 +556,10 @@ try Context 'When getting a repository for a specified organization' { BeforeAll { $repo = New-GitHubRepository -OrganizationName $script:organizationName -RepositoryName ([Guid]::NewGuid().Guid) + + # The CI build has been unreliable with this test. + # Adding a short sleep to ensure successive queries reflect updated state. + Start-Sleep -Seconds $script:defaultSleepSecondsForReliability } It "Should have results for the organization" { @@ -553,6 +577,10 @@ try $repo1 = New-GitHubRepository -RepositoryName ([Guid]::NewGuid().Guid) $repo2 = New-GitHubRepository -RepositoryName ([Guid]::NewGuid().Guid) + # The CI build has been unreliable with this test. + # Adding a short sleep to ensure successive queries reflect updated state. + Start-Sleep -Seconds $script:defaultSleepSecondsForReliability + $repos = Get-GitHubRepository -GetAllPublicRepositories -Since $repo1.id } @@ -584,6 +612,10 @@ try } $repo = New-GitHubRepository @newGitHubRepositoryParms + + # The CI build has been unreliable with this test. + # Adding a short sleep to ensure successive queries reflect updated state. + Start-Sleep -Seconds $script:defaultSleepSecondsForReliability } Context 'When specifiying the Uri parameter' { @@ -659,11 +691,21 @@ try It 'Should get no content using -Confirm:$false' { Remove-GitHubRepository -OwnerName $repo.owner.login -RepositoryName $repo.name -Confirm:$false + + # The CI build has been unreliable with this test. + # Adding a short sleep to ensure successive queries reflect updated state. + Start-Sleep -Seconds $script:defaultSleepSecondsForReliability + { Get-GitHubRepository -OwnerName $repo.owner.login -RepositoryName $repo.name } | Should -Throw } It 'Should get no content using -Force' { Remove-GitHubRepository -OwnerName $repo.owner.login -RepositoryName $repo.name -Force + + # The CI build has been unreliable with this test. + # Adding a short sleep to ensure successive queries reflect updated state. + Start-Sleep -Seconds $script:defaultSleepSecondsForReliability + { Get-GitHubRepository -OwnerName $repo.owner.login -RepositoryName $repo.name } | Should -Throw } } @@ -676,31 +718,55 @@ try $repo = New-GitHubRepository -RepositoryName ([Guid]::NewGuid().Guid) -AutoInit $suffixToAddToRepo = "_renamed" $newRepoName = "$($repo.name)$suffixToAddToRepo" + + # The CI build has been unreliable with this test. + # Adding a short sleep to ensure successive queries reflect updated state. + Start-Sleep -Seconds $script:defaultSleepSecondsForReliability } It "Should have the expected new repository name - by URI" { + # The CI build has been unreliable with this test. + # Adding a short sleep to ensure successive queries reflect updated state. + Start-Sleep -Seconds $script:defaultSleepSecondsForReliability + $renamedRepo = Rename-GitHubRepository -Uri ($repo.RepositoryUrl) -NewName $newRepoName -Force $renamedRepo.name | Should -Be $newRepoName } It "Should have the expected new repository name - by Elements" { + # The CI build has been unreliable with this test. + # Adding a short sleep to ensure successive queries reflect updated state. + Start-Sleep -Seconds $script:defaultSleepSecondsForReliability + $renamedRepo = Rename-GitHubRepository -OwnerName $repo.owner.login -RepositoryName $repo.name -NewName $newRepoName -Confirm:$false $renamedRepo.name | Should -Be $newRepoName } It "Should work via the pipeline" { + # The CI build has been unreliable with this test. + # Adding a short sleep to ensure successive queries reflect updated state. + Start-Sleep -Seconds $script:defaultSleepSecondsForReliability + $renamedRepo = $repo | Rename-GitHubRepository -NewName $newRepoName -Confirm:$false $renamedRepo.name | Should -Be $newRepoName $renamedRepo.PSObject.TypeNames[0] | Should -Be 'GitHub.Repository' } It "Should be possible to rename with Set-GitHubRepository too" { + # The CI build has been unreliable with this test. + # Adding a short sleep to ensure successive queries reflect updated state. + Start-Sleep -Seconds $script:defaultSleepSecondsForReliability + $renamedRepo = $repo | Set-GitHubRepository -NewName $newRepoName -Confirm:$false $renamedRepo.name | Should -Be $newRepoName $renamedRepo.PSObject.TypeNames[0] | Should -Be 'GitHub.Repository' } AfterEach -Scriptblock { + # The CI build has been unreliable with this test. + # Adding a short sleep to ensure successive queries reflect updated state. + Start-Sleep -Seconds $script:defaultSleepSecondsForReliability + Remove-GitHubRepository -Uri "$($repo.svn_url)$suffixToAddToRepo" -Confirm:$false } } @@ -712,6 +778,10 @@ try BeforeAll -ScriptBlock { $repoName = ([Guid]::NewGuid().Guid) $repo = New-GitHubRepository -RepositoryName $repoName + + # The CI build has been unreliable with this test. + # Adding a short sleep to ensure successive queries reflect updated state. + Start-Sleep -Seconds $script:defaultSleepSecondsForReliability } Context -Name 'When updating a repository with all possible settings' { @@ -731,6 +801,7 @@ try DeleteBranchOnMerge = $true IsTemplate = $true } + $updatedRepo = Set-GitHubRepository @updateGithubRepositoryParms } @@ -763,6 +834,7 @@ try DisallowMergeCommit = $false DisallowRebaseMerge = $true } + $updatedRepo = Set-GitHubRepository @updateGithubRepositoryParms } @@ -785,6 +857,7 @@ try RepositoryName = $repoName Archived = $true } + $updatedRepo = Set-GitHubRepository @updateGithubRepositoryParms } @@ -811,11 +884,16 @@ try $repoName = ([Guid]::NewGuid().Guid) $repo = New-GitHubRepository -RepositoryName $repoName -Private + # The CI build has been unreliable with this test. + # Adding a short sleep to ensure successive queries reflect updated state. + Start-Sleep -Seconds $script:defaultSleepSecondsForReliability + $updateGithubRepositoryParms = @{ OwnerName = $repo.owner.login RepositoryName = $repoName Private = $false } + $updatedRepo = Set-GitHubRepository @updateGithubRepositoryParms } @@ -865,6 +943,11 @@ try It 'Should be removable by the pipeline' { ($repo | Remove-GitHubRepository -Confirm:$false) | Should -BeNullOrEmpty + + # The CI build has been unreliable with this test. + # Adding a short sleep to ensure successive queries reflect updated state. + Start-Sleep -Seconds $script:defaultSleepSecondsForReliability + { $repo | Get-GitHubRepository } | Should -Throw } } @@ -900,6 +983,11 @@ try It 'Should be removable by the pipeline' { ($repo | Remove-GitHubRepository -Confirm:$false) | Should -BeNullOrEmpty + + # The CI build has been unreliable with this test. + # Adding a short sleep to ensure successive queries reflect updated state. + Start-Sleep -Seconds $script:defaultSleepSecondsForReliability + { $repo | Get-GitHubRepository } | Should -Throw } } @@ -970,6 +1058,11 @@ try Describe 'GitHubRepositories\Set-GitHubRepositoryTopic' { BeforeAll { $repo = New-GitHubRepository -RepositoryName ([Guid]::NewGuid().Guid) + + # The CI build has been unreliable with this test. + # Adding a short sleep to ensure successive queries reflect updated state. + Start-Sleep -Seconds $script:defaultSleepSecondsForReliability + $topic = Set-GitHubRepositoryTopic -OwnerName $repo.owner.login -RepositoryName $repo.name -Name $defaultRepoTopic } @@ -1249,6 +1342,10 @@ try Describe 'GitHubRepositories\Enable-GitHubRepositoryVulnerabilityAlert' { BeforeAll { $repo = New-GitHubRepository -RepositoryName ([Guid]::NewGuid().Guid) + + # The CI build has been unreliable with this test. + # Adding a short sleep to ensure successive queries reflect updated state. + Start-Sleep -Seconds $script:defaultSleepSecondsForReliability } Context 'When Enabling GitHub Repository Vulnerability Alerts' { @@ -1284,6 +1381,10 @@ try Describe 'GitHubRepositories\Enable-GitHubRepositorySecurityFix' { BeforeAll { $repo = New-GitHubRepository -RepositoryName ([Guid]::NewGuid().Guid) + + # The CI build has been unreliable with this test. + # Adding a short sleep to ensure successive queries reflect updated state. + Start-Sleep -Seconds $script:defaultSleepSecondsForReliability } Context 'When Enabling GitHub Repository Security Fixes' { diff --git a/Tests/GitHubRepositoryForks.tests.ps1 b/Tests/GitHubRepositoryForks.tests.ps1 index 7ea08eb1..b4cd6f46 100644 --- a/Tests/GitHubRepositoryForks.tests.ps1 +++ b/Tests/GitHubRepositoryForks.tests.ps1 @@ -29,6 +29,10 @@ try Context 'When a new fork is created' { BeforeAll { $repo = New-GitHubRepositoryFork -OwnerName $script:upstreamOwnerName -RepositoryName $script:upstreamRepositoryName + + # The CI build has been unreliable with this test. + # Adding a short sleep to ensure successive queries reflect updated state. + Start-Sleep -Seconds $script:defaultSleepSecondsForReliability } AfterAll { @@ -54,6 +58,10 @@ try BeforeAll { $upstream = Get-GitHubRepository -OwnerName $script:upstreamOwnerName -RepositoryName $script:upstreamRepositoryName $repo = $upstream | New-GitHubRepositoryFork + + # The CI build has been unreliable with this test. + # Adding a short sleep to ensure successive queries reflect updated state. + Start-Sleep -Seconds $script:defaultSleepSecondsForReliability } AfterAll { @@ -80,10 +88,13 @@ try Context 'When a new fork is created' { BeforeAll { $repo = New-GitHubRepositoryFork -OwnerName $script:upstreamOwnerName -RepositoryName $script:upstreamRepositoryName -OrganizationName $script:organizationName + + # The CI build has been unreliable with this test. + # Adding a short sleep to ensure successive queries reflect updated state. + Start-Sleep -Seconds $script:defaultSleepSecondsForReliability } AfterAll { - Start-Sleep -Seconds 3 # Trying to avoid an issue with deleting the repo if it's still being created by GitHub $repo | Remove-GitHubRepository -Force } From 402529aa8d72757087fa435a220a2a0a3dfe39df Mon Sep 17 00:00:00 2001 From: Howard Wolosky Date: Sun, 19 Jul 2020 11:35:40 -0700 Subject: [PATCH 56/60] Attempting to increase reliability of some Pester tests - take 2 (#268) This is re-implementing #265 in a more robust way, thanks to a suggestion from @X-Guardian in #267. Instead of adding in one-off sleeps throughout the test codebase, there is now a new configuration value `StateChangeDelaySeconds`) that will allow us to insert a delay before returning the result of _any_ state change request. This should ideally consistently add reliability throughout the entire test codebase. --- GitHubConfiguration.ps1 | 9 +++ GitHubCore.ps1 | 10 +++ Tests/Common.ps1 | 7 +- Tests/GitHubAssignees.tests.ps1 | 32 --------- Tests/GitHubContents.tests.ps1 | 9 --- Tests/GitHubIssues.tests.ps1 | 7 -- Tests/GitHubLabels.tests.ps1 | 66 ------------------ Tests/GitHubProjects.tests.ps1 | 8 --- Tests/GitHubRepositories.tests.ps1 | 98 +-------------------------- Tests/GitHubRepositoryForks.tests.ps1 | 12 ---- 10 files changed, 24 insertions(+), 234 deletions(-) diff --git a/GitHubConfiguration.ps1 b/GitHubConfiguration.ps1 index 944b2621..17ceacec 100644 --- a/GitHubConfiguration.ps1 +++ b/GitHubConfiguration.ps1 @@ -147,6 +147,11 @@ function Set-GitHubConfiguration .PARAMETER RetryDelaySeconds The number of seconds to wait before retrying a command again after receiving a 202 response. + .PARAMETER StateChangeDelaySeconds + The number of seconds to wait before returning the result after executing a command that + may result in a state change on the server. This is intended to only be used during test + execution in order to increase reliability. + .PARAMETER SuppressNoTokenWarning If an Access Token has not been configured, this module will provide a warning to the user informing them of this, once per session. If it is expected that this module will regularly @@ -228,6 +233,8 @@ function Set-GitHubConfiguration [int] $RetryDelaySeconds, + [int] $StateChangeDelaySeconds, + [switch] $SuppressNoTokenWarning, [switch] $SuppressTelemetryReminder, @@ -312,6 +319,7 @@ function Get-GitHubConfiguration 'LogTimeAsUtc', 'MultiRequestProgressThreshold', 'RetryDelaySeconds', + 'StateChangeDelaySeconds', 'SuppressNoTokenWarning', 'SuppressTelemetryReminder', 'TestConfigSettingsHash', @@ -659,6 +667,7 @@ function Import-GitHubConfiguration 'logTimeAsUtc' = $false 'multiRequestProgressThreshold' = 10 'retryDelaySeconds' = 30 + 'stateChangeDelaySeconds' = 0 'suppressNoTokenWarning' = $false 'suppressTelemetryReminder' = $false 'webRequestTimeoutSec' = 0 diff --git a/GitHubCore.ps1 b/GitHubCore.ps1 index 019ef6ac..0bfd7f5d 100644 --- a/GitHubCore.ps1 +++ b/GitHubCore.ps1 @@ -321,6 +321,16 @@ function Invoke-GHRestMethod } } + # Allow for a delay after a command that may result in a state change in order to + #increase the reliability of the UT's which attempt multiple successive state change + # on the same object. + $stateChangeDelaySeconds = $(Get-GitHubConfiguration -Name 'StateChangeDelaySeconds') + $stateChangeMethods = @('Delete', 'Post', 'Patch', 'Put') + if (($stateChangeDelaySeconds -gt 0) -and ($Method -in $stateChangeMethods)) + { + Start-Sleep -Seconds $stateChangeDelaySeconds + } + if ($ExtendedResult) { $finalResultEx = @{ diff --git a/Tests/Common.ps1 b/Tests/Common.ps1 index 41253602..d7ced214 100644 --- a/Tests/Common.ps1 +++ b/Tests/Common.ps1 @@ -101,9 +101,10 @@ function Initialize-CommonTestSetup Set-GitHubConfiguration -MultiRequestProgressThreshold 0 # Status corrupts the raw CI logs for Linux and Mac, and makes runs take slightly longer. Set-GitHubConfiguration -DisableUpdateCheck # The update check is unnecessary during tests. - # The number of seconds to sleep after performing some operations to ensure that successive - # API calls properly reflect previously updated state. - $script:defaultSleepSecondsForReliability = 3 + # We execute so many successive state changing commands on the same object that sometimes + # GitHub gets confused. We'll add an intentional delay to slow down our execution in an effort + # to increase the reliability of the tests. + Set-GitHubConfiguration -StateChangeDelaySeconds 3 } Initialize-CommonTestSetup diff --git a/Tests/GitHubAssignees.tests.ps1 b/Tests/GitHubAssignees.tests.ps1 index 0501f183..f9c0057a 100644 --- a/Tests/GitHubAssignees.tests.ps1 +++ b/Tests/GitHubAssignees.tests.ps1 @@ -108,10 +108,6 @@ try Context 'Adding and removing an assignee via parameters' { $issue = $repo | New-GitHubIssue -Title "Test issue" - # The CI build has been unreliable with this test. - # Adding a short sleep to ensure successive queries reflect updated state. - Start-Sleep -Seconds $script:defaultSleepSecondsForReliability - It 'Should have no assignees when created' { $issue.assignee.login | Should -BeNullOrEmpty $issue.assignees | Should -BeNullOrEmpty @@ -119,10 +115,6 @@ try $updatedIssue = Add-GitHubAssignee -OwnerName $script:ownerName -RepositoryName $repo.name -Issue $issue.number -Assignee $owner.login - # The CI build has been unreliable with this test. - # Adding a short sleep to ensure successive queries reflect updated state. - Start-Sleep -Seconds $script:defaultSleepSecondsForReliability - It 'Should have returned the same issue' { $updatedIssue.number | Should -Be $issue.number } @@ -155,10 +147,6 @@ try Context 'Adding an assignee with the repo on the pipeline' { $issue = $repo | New-GitHubIssue -Title "Test issue" - # The CI build has been unreliable with this test. - # Adding a short sleep to ensure successive queries reflect updated state. - Start-Sleep -Seconds $script:defaultSleepSecondsForReliability - It 'Should have no assignees when created' { $issue.assignee.login | Should -BeNullOrEmpty $issue.assignees | Should -BeNullOrEmpty @@ -166,10 +154,6 @@ try $updatedIssue = $repo | Add-GitHubAssignee -Issue $issue.number -Assignee $owner.login - # The CI build has been unreliable with this test. - # Adding a short sleep to ensure successive queries reflect updated state. - Start-Sleep -Seconds $script:defaultSleepSecondsForReliability - It 'Should have returned the same issue' { $updatedIssue.number | Should -Be $issue.number } @@ -202,10 +186,6 @@ try Context 'Adding an assignee with the issue on the pipeline' { $issue = $repo | New-GitHubIssue -Title "Test issue" - # The CI build has been unreliable with this test. - # Adding a short sleep to ensure successive queries reflect updated state. - Start-Sleep -Seconds $script:defaultSleepSecondsForReliability - It 'Should have no assignees when created' { $issue.assignee.login | Should -BeNullOrEmpty $issue.assignees | Should -BeNullOrEmpty @@ -213,10 +193,6 @@ try $updatedIssue = $issue | Add-GitHubAssignee -Assignee $owner.login - # The CI build has been unreliable with this test. - # Adding a short sleep to ensure successive queries reflect updated state. - Start-Sleep -Seconds $script:defaultSleepSecondsForReliability - It 'Should have returned the same issue' { $updatedIssue.number | Should -Be $issue.number } @@ -249,10 +225,6 @@ try Context 'Adding an assignee with the assignee user object on the pipeline' { $issue = $repo | New-GitHubIssue -Title "Test issue" - # The CI build has been unreliable with this test. - # Adding a short sleep to ensure successive queries reflect updated state. - Start-Sleep -Seconds $script:defaultSleepSecondsForReliability - It 'Should have no assignees when created' { $issue.assignee.login | Should -BeNullOrEmpty $issue.assignees | Should -BeNullOrEmpty @@ -260,10 +232,6 @@ try $updatedIssue = $owner | Add-GitHubAssignee -OwnerName $script:ownerName -RepositoryName $repo.name -Issue $issue.number - # The CI build has been unreliable with this test. - # Adding a short sleep to ensure successive queries reflect updated state. - Start-Sleep -Seconds $script:defaultSleepSecondsForReliability - It 'Should have returned the same issue' { $updatedIssue.number | Should -Be $issue.number } diff --git a/Tests/GitHubContents.tests.ps1 b/Tests/GitHubContents.tests.ps1 index a52ed473..24082ac8 100644 --- a/Tests/GitHubContents.tests.ps1 +++ b/Tests/GitHubContents.tests.ps1 @@ -37,10 +37,6 @@ try BeforeAll { # AutoInit will create a readme with the GUID of the repo name $repo = New-GitHubRepository -RepositoryName ($repoGuid) -AutoInit - - # The CI build has been unreliable with this test. - # Adding a short sleep to ensure successive queries reflect updated state. - Start-Sleep -Seconds $script:defaultSleepSecondsForReliability } AfterAll { @@ -262,12 +258,7 @@ try Describe 'GitHubContents/Set-GitHubContent' { BeforeAll { $repoName = [Guid]::NewGuid().Guid - $repo = New-GitHubRepository -RepositoryName $repoName -AutoInit - - # The CI build has been unreliable with this test. - # Adding a short sleep to ensure successive queries reflect updated state. - Start-Sleep -Seconds $script:defaultSleepSecondsForReliability } Context 'When setting new file content' { diff --git a/Tests/GitHubIssues.tests.ps1 b/Tests/GitHubIssues.tests.ps1 index 1cff5d0c..6e6808b9 100644 --- a/Tests/GitHubIssues.tests.ps1 +++ b/Tests/GitHubIssues.tests.ps1 @@ -161,9 +161,6 @@ try for ($i = 0; $i -lt 4; $i++) { $newIssues += New-GitHubIssue -OwnerName $script:ownerName -RepositoryName $repo.name -Title ([Guid]::NewGuid().Guid) - - # Needed to ensure that there is a unique creation timestamp between issues - Start-Sleep -Seconds $script:defaultSleepSecondsForReliability } $newIssues[0] = Set-GitHubIssue -OwnerName $script:ownerName -RepositoryName $repo.name -Issue $newIssues[0].number -State Closed @@ -550,10 +547,6 @@ try Lock-GitHubIssue -OwnerName $script:OwnerName -RepositoryName $repo.name -Issue $issue.number - # The CI build has been unreliable with this test. - # Adding a short sleep to ensure successive queries reflect updated state. - Start-Sleep -Seconds $script:defaultSleepSecondsForReliability - $timeline = @(Get-GitHubIssueTimeline -OwnerName $script:OwnerName -RepositoryName $repo.name -Issue $issue.number) It 'Should have an event now' { $timeline.Count | Should -Be 1 diff --git a/Tests/GitHubLabels.tests.ps1 b/Tests/GitHubLabels.tests.ps1 index 8c635f0f..1c48ce07 100644 --- a/Tests/GitHubLabels.tests.ps1 +++ b/Tests/GitHubLabels.tests.ps1 @@ -80,11 +80,6 @@ try BeforeAll { $repositoryName = [Guid]::NewGuid().Guid $repo = New-GitHubRepository -RepositoryName $repositoryName - - # The CI build has been unreliable with this test. - # Adding a short sleep to ensure successive queries reflect updated state. - Start-Sleep -Seconds $script:defaultSleepSecondsForReliability - Initialize-GitHubLabel -OwnerName $script:ownerName -RepositoryName $repositoryName -Label $defaultLabels } @@ -209,10 +204,6 @@ try BeforeAll { $repositoryName = [Guid]::NewGuid().Guid $repo = New-GitHubRepository -RepositoryName $repositoryName - - # The CI build has been unreliable with this test. - # Adding a short sleep to ensure successive queries reflect updated state. - Start-Sleep -Seconds $script:defaultSleepSecondsForReliability } AfterAll { @@ -327,10 +318,6 @@ try BeforeAll { $repositoryName = [Guid]::NewGuid().Guid $repo = New-GitHubRepository -RepositoryName $repositoryName - - # The CI build has been unreliable with this test. - # Adding a short sleep to ensure successive queries reflect updated state. - Start-Sleep -Seconds $script:defaultSleepSecondsForReliability } AfterAll { @@ -339,11 +326,6 @@ try Context 'Removing a label with parameters' { $label = $repo | New-GitHubLabel -Label 'test' -Color 'CCCCCC' - - # The CI build has been unreliable with this test. - # Adding a short sleep to ensure successive queries reflect updated state. - Start-Sleep -Seconds $script:defaultSleepSecondsForReliability - Remove-GitHubLabel -OwnerName $script:ownerName -RepositoryName $repositoryName -Label $label.name -Force It 'Should be gone after being removed by parameter' { @@ -353,11 +335,6 @@ try Context 'Removing a label with the repo on the pipeline' { $label = $repo | New-GitHubLabel -Label 'test' -Color 'CCCCCC' - - # The CI build has been unreliable with this test. - # Adding a short sleep to ensure successive queries reflect updated state. - Start-Sleep -Seconds $script:defaultSleepSecondsForReliability - $repo | Remove-GitHubLabel -Label $label.name -Confirm:$false It 'Should be gone after being removed by parameter' { @@ -367,11 +344,6 @@ try Context 'Removing a label with the name on the pipeline' { $label = $repo | New-GitHubLabel -Label 'test' -Color 'CCCCCC' - - # The CI build has been unreliable with this test. - # Adding a short sleep to ensure successive queries reflect updated state. - Start-Sleep -Seconds $script:defaultSleepSecondsForReliability - $label.name | Remove-GitHubLabel -OwnerName $script:ownerName -RepositoryName $repositoryName -Force It 'Should be gone after being removed by parameter' { @@ -381,11 +353,6 @@ try Context 'Removing a label with the label object on the pipeline' { $label = $repo | New-GitHubLabel -Label 'test' -Color 'CCCCCC' - - # The CI build has been unreliable with this test. - # Adding a short sleep to ensure successive queries reflect updated state. - Start-Sleep -Seconds $script:defaultSleepSecondsForReliability - $label | Remove-GitHubLabel -Force It 'Should be gone after being removed by parameter' { @@ -398,10 +365,6 @@ try BeforeAll { $repositoryName = [Guid]::NewGuid().Guid $repo = New-GitHubRepository -RepositoryName $repositoryName - - # The CI build has been unreliable with this test. - # Adding a short sleep to ensure successive queries reflect updated state. - Start-Sleep -Seconds $script:defaultSleepSecondsForReliability } AfterAll { @@ -579,10 +542,6 @@ try BeforeAll { $repositoryName = [Guid]::NewGuid().Guid $repo = New-GitHubRepository -RepositoryName $repositoryName - - # The CI build has been unreliable with this test. - # Adding a short sleep to ensure successive queries reflect updated state. - Start-Sleep -Seconds $script:defaultSleepSecondsForReliability } AfterAll { @@ -657,11 +616,6 @@ try BeforeAll { $repositoryName = [Guid]::NewGuid().Guid $repo = New-GitHubRepository -RepositoryName $repositoryName - - # The CI build has been unreliable with this test. - # Adding a short sleep to ensure successive queries reflect updated state. - Start-Sleep -Seconds $script:defaultSleepSecondsForReliability - $repo | Initialize-GitHubLabel -Label $defaultLabels } @@ -890,11 +844,6 @@ try BeforeAll { $repositoryName = [Guid]::NewGuid().Guid $repo = New-GitHubRepository -RepositoryName $repositoryName - - # The CI build has been unreliable with this test. - # Adding a short sleep to ensure successive queries reflect updated state. - Start-Sleep -Seconds $script:defaultSleepSecondsForReliability - $repo | Initialize-GitHubLabel -Label $defaultLabels } @@ -961,11 +910,6 @@ try BeforeAll { $repositoryName = [Guid]::NewGuid().Guid $repo = New-GitHubRepository -RepositoryName $repositoryName - - # The CI build has been unreliable with this test. - # Adding a short sleep to ensure successive queries reflect updated state. - Start-Sleep -Seconds $script:defaultSleepSecondsForReliability - $repo | Initialize-GitHubLabel -Label $defaultLabels } @@ -1133,11 +1077,6 @@ try BeforeAll { $repositoryName = [Guid]::NewGuid().Guid $repo = New-GitHubRepository -RepositoryName $repositoryName - - # The CI build has been unreliable with this test. - # Adding a short sleep to ensure successive queries reflect updated state. - Start-Sleep -Seconds $script:defaultSleepSecondsForReliability - $repo | Initialize-GitHubLabel -Label $defaultLabels } @@ -1287,11 +1226,6 @@ try BeforeAll { $repositoryName = [Guid]::NewGuid().Guid $repo = New-GitHubRepository -RepositoryName $repositoryName - - # The CI build has been unreliable with this test. - # Adding a short sleep to ensure successive queries reflect updated state. - Start-Sleep -Seconds $script:defaultSleepSecondsForReliability - $repo | Initialize-GitHubLabel -Label $defaultLabels $milestone = $repo | New-GitHubMilestone -Title 'test milestone' diff --git a/Tests/GitHubProjects.tests.ps1 b/Tests/GitHubProjects.tests.ps1 index 3417c1d4..ccde85d6 100644 --- a/Tests/GitHubProjects.tests.ps1 +++ b/Tests/GitHubProjects.tests.ps1 @@ -611,10 +611,6 @@ try $project = New-GitHubProject -OwnerName $script:ownerName -RepositoryName $repo.name -ProjectName $defaultRepoProject -Description $defaultRepoProjectDesc $null = Remove-GitHubProject -Project $project.id -Confirm:$false It 'Project should be removed' { - # The CI build has been unreliable with this test. - # Adding a short sleep to ensure successive queries reflect updated state. - Start-Sleep -Seconds $script:defaultSleepSecondsForReliability - {Get-GitHubProject -Project $project.id} | Should -Throw } } @@ -623,10 +619,6 @@ try $project = $repo | New-GitHubProject -ProjectName $defaultRepoProject -Description $defaultRepoProjectDesc $project | Remove-GitHubProject -Force It 'Project should be removed' { - # The CI build has been unreliable with this test. - # Adding a short sleep to ensure successive queries reflect updated state. - Start-Sleep -Seconds $script:defaultSleepSecondsForReliability - {$project | Get-GitHubProject} | Should -Throw } } diff --git a/Tests/GitHubRepositories.tests.ps1 b/Tests/GitHubRepositories.tests.ps1 index 5ea84b12..eb4bc666 100644 --- a/Tests/GitHubRepositories.tests.ps1 +++ b/Tests/GitHubRepositories.tests.ps1 @@ -124,11 +124,8 @@ try LicenseTemplate = $testLicenseTemplate IsTemplate = $true } - $repo = New-GitHubRepository @newGitHubRepositoryParms - # The CI build has been unreliable with this test. - # Adding a short sleep to ensure successive queries reflect updated state. - Start-Sleep -Seconds $script:defaultSleepSecondsForReliability + $repo = New-GitHubRepository @newGitHubRepositoryParms } It 'Should return an object of the correct type' { @@ -310,10 +307,6 @@ try } $templateRepo = New-GitHubRepository @newGitHubRepositoryParms - - # The CI build has been unreliable with this test. - # Adding a short sleep to ensure successive queries reflect updated state. - Start-Sleep -Seconds $script:defaultSleepSecondsForReliability } Context 'When creating a public repository from a template' { @@ -329,10 +322,6 @@ try } $repo = New-GitHubRepositoryFromTemplate @newGitHubRepositoryFromTemplateParms - - # The CI build has been unreliable with this test. - # Adding a short sleep to ensure successive queries reflect updated state. - Start-Sleep -Seconds $script:defaultSleepSecondsForReliability } It 'Should have the expected type and addititional properties' { @@ -373,10 +362,6 @@ try } $repo = $templateRepo | New-GitHubRepositoryFromTemplate @newGitHubRepositoryFromTemplateParms - - # The CI build has been unreliable with this test. - # Adding a short sleep to ensure successive queries reflect updated state. - Start-Sleep -Seconds $script:defaultSleepSecondsForReliability } It 'Should have the expected type and addititional properties' { @@ -419,10 +404,6 @@ try BeforeAll { $publicRepo = New-GitHubRepository -RepositoryName ([Guid]::NewGuid().Guid) $privateRepo = New-GitHubRepository -RepositoryName ([Guid]::NewGuid().Guid) -Private - - # The CI build has been unreliable with this test. - # Adding a short sleep to ensure successive queries reflect updated state. - Start-Sleep -Seconds $script:defaultSleepSecondsForReliability } Context 'When specify the visibility parameter' { @@ -556,10 +537,6 @@ try Context 'When getting a repository for a specified organization' { BeforeAll { $repo = New-GitHubRepository -OrganizationName $script:organizationName -RepositoryName ([Guid]::NewGuid().Guid) - - # The CI build has been unreliable with this test. - # Adding a short sleep to ensure successive queries reflect updated state. - Start-Sleep -Seconds $script:defaultSleepSecondsForReliability } It "Should have results for the organization" { @@ -576,11 +553,6 @@ try BeforeAll { $repo1 = New-GitHubRepository -RepositoryName ([Guid]::NewGuid().Guid) $repo2 = New-GitHubRepository -RepositoryName ([Guid]::NewGuid().Guid) - - # The CI build has been unreliable with this test. - # Adding a short sleep to ensure successive queries reflect updated state. - Start-Sleep -Seconds $script:defaultSleepSecondsForReliability - $repos = Get-GitHubRepository -GetAllPublicRepositories -Since $repo1.id } @@ -612,10 +584,6 @@ try } $repo = New-GitHubRepository @newGitHubRepositoryParms - - # The CI build has been unreliable with this test. - # Adding a short sleep to ensure successive queries reflect updated state. - Start-Sleep -Seconds $script:defaultSleepSecondsForReliability } Context 'When specifiying the Uri parameter' { @@ -691,21 +659,11 @@ try It 'Should get no content using -Confirm:$false' { Remove-GitHubRepository -OwnerName $repo.owner.login -RepositoryName $repo.name -Confirm:$false - - # The CI build has been unreliable with this test. - # Adding a short sleep to ensure successive queries reflect updated state. - Start-Sleep -Seconds $script:defaultSleepSecondsForReliability - { Get-GitHubRepository -OwnerName $repo.owner.login -RepositoryName $repo.name } | Should -Throw } It 'Should get no content using -Force' { Remove-GitHubRepository -OwnerName $repo.owner.login -RepositoryName $repo.name -Force - - # The CI build has been unreliable with this test. - # Adding a short sleep to ensure successive queries reflect updated state. - Start-Sleep -Seconds $script:defaultSleepSecondsForReliability - { Get-GitHubRepository -OwnerName $repo.owner.login -RepositoryName $repo.name } | Should -Throw } } @@ -718,55 +676,31 @@ try $repo = New-GitHubRepository -RepositoryName ([Guid]::NewGuid().Guid) -AutoInit $suffixToAddToRepo = "_renamed" $newRepoName = "$($repo.name)$suffixToAddToRepo" - - # The CI build has been unreliable with this test. - # Adding a short sleep to ensure successive queries reflect updated state. - Start-Sleep -Seconds $script:defaultSleepSecondsForReliability } It "Should have the expected new repository name - by URI" { - # The CI build has been unreliable with this test. - # Adding a short sleep to ensure successive queries reflect updated state. - Start-Sleep -Seconds $script:defaultSleepSecondsForReliability - $renamedRepo = Rename-GitHubRepository -Uri ($repo.RepositoryUrl) -NewName $newRepoName -Force $renamedRepo.name | Should -Be $newRepoName } It "Should have the expected new repository name - by Elements" { - # The CI build has been unreliable with this test. - # Adding a short sleep to ensure successive queries reflect updated state. - Start-Sleep -Seconds $script:defaultSleepSecondsForReliability - $renamedRepo = Rename-GitHubRepository -OwnerName $repo.owner.login -RepositoryName $repo.name -NewName $newRepoName -Confirm:$false $renamedRepo.name | Should -Be $newRepoName } It "Should work via the pipeline" { - # The CI build has been unreliable with this test. - # Adding a short sleep to ensure successive queries reflect updated state. - Start-Sleep -Seconds $script:defaultSleepSecondsForReliability - $renamedRepo = $repo | Rename-GitHubRepository -NewName $newRepoName -Confirm:$false $renamedRepo.name | Should -Be $newRepoName $renamedRepo.PSObject.TypeNames[0] | Should -Be 'GitHub.Repository' } It "Should be possible to rename with Set-GitHubRepository too" { - # The CI build has been unreliable with this test. - # Adding a short sleep to ensure successive queries reflect updated state. - Start-Sleep -Seconds $script:defaultSleepSecondsForReliability - $renamedRepo = $repo | Set-GitHubRepository -NewName $newRepoName -Confirm:$false $renamedRepo.name | Should -Be $newRepoName $renamedRepo.PSObject.TypeNames[0] | Should -Be 'GitHub.Repository' } AfterEach -Scriptblock { - # The CI build has been unreliable with this test. - # Adding a short sleep to ensure successive queries reflect updated state. - Start-Sleep -Seconds $script:defaultSleepSecondsForReliability - Remove-GitHubRepository -Uri "$($repo.svn_url)$suffixToAddToRepo" -Confirm:$false } } @@ -778,10 +712,6 @@ try BeforeAll -ScriptBlock { $repoName = ([Guid]::NewGuid().Guid) $repo = New-GitHubRepository -RepositoryName $repoName - - # The CI build has been unreliable with this test. - # Adding a short sleep to ensure successive queries reflect updated state. - Start-Sleep -Seconds $script:defaultSleepSecondsForReliability } Context -Name 'When updating a repository with all possible settings' { @@ -884,10 +814,6 @@ try $repoName = ([Guid]::NewGuid().Guid) $repo = New-GitHubRepository -RepositoryName $repoName -Private - # The CI build has been unreliable with this test. - # Adding a short sleep to ensure successive queries reflect updated state. - Start-Sleep -Seconds $script:defaultSleepSecondsForReliability - $updateGithubRepositoryParms = @{ OwnerName = $repo.owner.login RepositoryName = $repoName @@ -944,10 +870,6 @@ try It 'Should be removable by the pipeline' { ($repo | Remove-GitHubRepository -Confirm:$false) | Should -BeNullOrEmpty - # The CI build has been unreliable with this test. - # Adding a short sleep to ensure successive queries reflect updated state. - Start-Sleep -Seconds $script:defaultSleepSecondsForReliability - { $repo | Get-GitHubRepository } | Should -Throw } } @@ -983,11 +905,6 @@ try It 'Should be removable by the pipeline' { ($repo | Remove-GitHubRepository -Confirm:$false) | Should -BeNullOrEmpty - - # The CI build has been unreliable with this test. - # Adding a short sleep to ensure successive queries reflect updated state. - Start-Sleep -Seconds $script:defaultSleepSecondsForReliability - { $repo | Get-GitHubRepository } | Should -Throw } } @@ -1058,11 +975,6 @@ try Describe 'GitHubRepositories\Set-GitHubRepositoryTopic' { BeforeAll { $repo = New-GitHubRepository -RepositoryName ([Guid]::NewGuid().Guid) - - # The CI build has been unreliable with this test. - # Adding a short sleep to ensure successive queries reflect updated state. - Start-Sleep -Seconds $script:defaultSleepSecondsForReliability - $topic = Set-GitHubRepositoryTopic -OwnerName $repo.owner.login -RepositoryName $repo.name -Name $defaultRepoTopic } @@ -1342,10 +1254,6 @@ try Describe 'GitHubRepositories\Enable-GitHubRepositoryVulnerabilityAlert' { BeforeAll { $repo = New-GitHubRepository -RepositoryName ([Guid]::NewGuid().Guid) - - # The CI build has been unreliable with this test. - # Adding a short sleep to ensure successive queries reflect updated state. - Start-Sleep -Seconds $script:defaultSleepSecondsForReliability } Context 'When Enabling GitHub Repository Vulnerability Alerts' { @@ -1381,10 +1289,6 @@ try Describe 'GitHubRepositories\Enable-GitHubRepositorySecurityFix' { BeforeAll { $repo = New-GitHubRepository -RepositoryName ([Guid]::NewGuid().Guid) - - # The CI build has been unreliable with this test. - # Adding a short sleep to ensure successive queries reflect updated state. - Start-Sleep -Seconds $script:defaultSleepSecondsForReliability } Context 'When Enabling GitHub Repository Security Fixes' { diff --git a/Tests/GitHubRepositoryForks.tests.ps1 b/Tests/GitHubRepositoryForks.tests.ps1 index b4cd6f46..e78e6b82 100644 --- a/Tests/GitHubRepositoryForks.tests.ps1 +++ b/Tests/GitHubRepositoryForks.tests.ps1 @@ -29,10 +29,6 @@ try Context 'When a new fork is created' { BeforeAll { $repo = New-GitHubRepositoryFork -OwnerName $script:upstreamOwnerName -RepositoryName $script:upstreamRepositoryName - - # The CI build has been unreliable with this test. - # Adding a short sleep to ensure successive queries reflect updated state. - Start-Sleep -Seconds $script:defaultSleepSecondsForReliability } AfterAll { @@ -58,10 +54,6 @@ try BeforeAll { $upstream = Get-GitHubRepository -OwnerName $script:upstreamOwnerName -RepositoryName $script:upstreamRepositoryName $repo = $upstream | New-GitHubRepositoryFork - - # The CI build has been unreliable with this test. - # Adding a short sleep to ensure successive queries reflect updated state. - Start-Sleep -Seconds $script:defaultSleepSecondsForReliability } AfterAll { @@ -88,10 +80,6 @@ try Context 'When a new fork is created' { BeforeAll { $repo = New-GitHubRepositoryFork -OwnerName $script:upstreamOwnerName -RepositoryName $script:upstreamRepositoryName -OrganizationName $script:organizationName - - # The CI build has been unreliable with this test. - # Adding a short sleep to ensure successive queries reflect updated state. - Start-Sleep -Seconds $script:defaultSleepSecondsForReliability } AfterAll { From 356af2f5b69fa8cd60bc77670d250cde796ac1d6 Mon Sep 17 00:00:00 2001 From: Howard Wolosky Date: Mon, 20 Jul 2020 08:48:48 -0700 Subject: [PATCH 57/60] Complete support for Releases API (#177) This completes the required work to support the set of Releases API's. It adds the following functions: * `New-GitHubRelease` * `Set-GitHubRelease` * `Remove-GitHubRelease` * `Get-GitHubReleaseAsset` * `New-GitHubReleaseAsset` * `Set-GitHubReleaseAsset` * `Remove-GitHubReleaseAsset` `Invoke-GHRestMethod` has been updated to be able to upload a file (via the new `InFile` parameter) and download a file (via the `Save` switch which will cause it to return back a `FileInfo` object of a temporary file which can then be renamed as seen fit by the caller). This also adds formatters for `GitHub.Release` and `GitHub.ReleaseAsset`. Positional Binding has been set as `false` for the three functions, and `Position` attributes added to the function's mandatory parameters. Reference: [GitHub Releases](https://developer.github.com/v3/repos/releases/) Fixes #47 Fixes #110 --- Formatters/GitHubReleases.Format.ps1xml | 135 +++ GitHubAssignees.ps1 | 4 + GitHubBranches.ps1 | 1 + GitHubContents.ps1 | 1 + GitHubCore.ps1 | 239 +++- GitHubEvents.ps1 | 1 + GitHubIssueComments.ps1 | 4 + GitHubIssues.ps1 | 6 + GitHubLabels.ps1 | 8 + GitHubMilestones.ps1 | 4 + GitHubMiscellaneous.ps1 | 2 + GitHubProjects.ps1 | 4 + GitHubPullRequests.ps1 | 2 + GitHubReleases.ps1 | 1337 ++++++++++++++++++++++- GitHubRepositories.ps1 | 12 + GitHubRepositoryForks.ps1 | 2 + GitHubRepositoryTraffic.ps1 | 4 + GitHubTeams.ps1 | 2 + Helpers.ps1 | 11 +- PowerShellForGitHub.psd1 | 17 +- Tests/GitHubReleases.tests.ps1 | 1091 ++++++++++++++++-- USAGE.md | 145 +++ 22 files changed, 2887 insertions(+), 145 deletions(-) create mode 100644 Formatters/GitHubReleases.Format.ps1xml diff --git a/Formatters/GitHubReleases.Format.ps1xml b/Formatters/GitHubReleases.Format.ps1xml new file mode 100644 index 00000000..e5e9bc78 --- /dev/null +++ b/Formatters/GitHubReleases.Format.ps1xml @@ -0,0 +1,135 @@ + + + + + + GitHub.Release + + GitHub.Release + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + id + + + name + + + tag_name + + + target_commitish + + + draft + + + prerelease + + + created_at + + + published_at + + + + + + + + + GitHub.ReleaseAsset + + GitHub.ReleaseAsset + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + id + + + name + + + label + + + size + + + content_type + + + download_count + + + created_at + + + updated_at + + + + + + + + diff --git a/GitHubAssignees.ps1 b/GitHubAssignees.ps1 index 6bee8221..3f3fbb06 100644 --- a/GitHubAssignees.ps1 +++ b/GitHubAssignees.ps1 @@ -46,6 +46,7 @@ filter Get-GitHubAssignee GitHub.ProjectColumn GitHub.Reaction GitHub.Release + GitHub.ReleaseAsset GitHub.Repository GitHub.User @@ -159,6 +160,7 @@ filter Test-GitHubAssignee GitHub.ProjectColumn GitHub.Reaction GitHub.Release + GitHub.ReleaseAsset GitHub.Repository GitHub.User @@ -303,6 +305,7 @@ function Add-GitHubAssignee GitHub.ProjectColumn GitHub.Reaction GitHub.Release + GitHub.ReleaseAsset GitHub.Repository GitHub.User @@ -481,6 +484,7 @@ function Remove-GitHubAssignee GitHub.ProjectColumn GitHub.Reaction GitHub.Release + GitHub.ReleaseAsset GitHub.Repository .OUTPUTS diff --git a/GitHubBranches.ps1 b/GitHubBranches.ps1 index 41e5b100..502a785d 100644 --- a/GitHubBranches.ps1 +++ b/GitHubBranches.ps1 @@ -58,6 +58,7 @@ filter Get-GitHubRepositoryBranch GitHub.ProjectColumn GitHub.Reaction GitHub.Release + GitHub.ReleaseAsset GitHub.Repository .OUTPUTS diff --git a/GitHubContents.ps1 b/GitHubContents.ps1 index 53397540..85b290f8 100644 --- a/GitHubContents.ps1 +++ b/GitHubContents.ps1 @@ -73,6 +73,7 @@ GitHub.ProjectColumn GitHub.Reaction GitHub.Release + GitHub.ReleaseAsset GitHub.Repository .OUTPUTS diff --git a/GitHubCore.ps1 b/GitHubCore.ps1 index 0bfd7f5d..02c887fd 100644 --- a/GitHubCore.ps1 +++ b/GitHubCore.ps1 @@ -29,17 +29,17 @@ function Invoke-GHRestMethod { <# .SYNOPSIS - A wrapper around Invoke-WebRequest that understands the Store API. + A wrapper around Invoke-WebRequest that understands the GitHub API. .DESCRIPTION - A very heavy wrapper around Invoke-WebRequest that understands the Store API and + A very heavy wrapper around Invoke-WebRequest that understands the GitHub API and how to perform its operation with and without console status updates. It also understands how to parse and handle errors from the REST calls. The Git repo for this module can be found here: http://aka.ms/PowerShellForGitHub .PARAMETER UriFragment - The unique, tail-end, of the REST URI that indicates what Store REST action will + The unique, tail-end, of the REST URI that indicates what GitHub REST action will be performed. This should not start with a leading "/". .PARAMETER Method @@ -58,10 +58,22 @@ function Invoke-GHRestMethod Specify the media type in the Accept header. Different types of commands may require different media types. + .PARAMETER InFile + Gets the content of the web request from the specified file. Only valid for POST requests. + + .PARAMETER ContentType + Specifies the value for the MIME Content-Type header of the request. This will usually + be configured correctly automatically. You should only specify this under advanced + situations (like if the extension of InFile is of a type unknown to this module). + .PARAMETER ExtendedResult If specified, the result will be a PSObject that contains the normal result, along with the response code and other relevant header detail content. + .PARAMETER Save + If specified, this will save the result to a temporary file and return the FileInfo of that + temporary file. + .PARAMETER AccessToken If provided, this will be used as the AccessToken for authentication with the REST Api as opposed to requesting a new one. @@ -90,6 +102,7 @@ function Invoke-GHRestMethod .OUTPUTS [PSCustomObject] - The result of the REST operation, in whatever form it comes in. + [FileInfo] - The temporary file created for the downloaded file if -Save was specified. .EXAMPLE Invoke-GHRestMethod -UriFragment "users/octocat" -Method Get -Description "Get information on the octocat user" @@ -120,8 +133,15 @@ function Invoke-GHRestMethod [string] $AcceptHeader = $script:defaultAcceptHeader, + [ValidateNotNullOrEmpty()] + [string] $InFile, + + [string] $ContentType = $script:defaultJsonBodyContentType, + [switch] $ExtendedResult, + [switch] $Save, + [string] $AccessToken, [string] $TelemetryEventName = $null, @@ -135,6 +155,32 @@ function Invoke-GHRestMethod Invoke-UpdateCheck + # Minor error checking around $InFile + if ($PSBoundParameters.ContainsKey('InFile') -and ($Method -ne 'Post')) + { + $message = '-InFile may only be specified with Post requests.' + Write-Log -Message $message -Level Error + throw $message + } + + if ($PSBoundParameters.ContainsKey('InFile') -and (-not [String]::IsNullOrWhiteSpace($Body))) + { + $message = 'Cannot specify BOTH InFile and Body' + Write-Log -Message $message -Level Error + throw $message + } + + if ($PSBoundParameters.ContainsKey('InFile')) + { + $InFile = Resolve-UnverifiedPath -Path $InFile + if (-not (Test-Path -Path $InFile -PathType Leaf)) + { + $message = "Specified file [$InFile] does not exist or is inaccessible." + Write-Log -Message $message -Level Error + throw $message + } + } + # Normalize our Uri fragment. It might be coming from a method implemented here, or it might # be coming from the Location header in a previous response. Either way, we don't want there # to be a leading "/" or trailing '/' @@ -191,7 +237,23 @@ function Invoke-GHRestMethod if ($Method -in $ValidBodyContainingRequestMethods) { - $headers.Add("Content-Type", "application/json; charset=UTF-8") + if ($PSBoundParameters.ContainsKey('InFile') -and [String]::IsNullOrWhiteSpace($ContentType)) + { + $file = Get-Item -Path $InFile + $localTelemetryProperties['FileExtension'] = $file.Extension + + if ($script:extensionToContentType.ContainsKey($file.Extension)) + { + $ContentType = $script:extensionToContentType[$file.Extension] + } + else + { + $localTelemetryProperties['UnknownExtension'] = $file.Extension + $ContentType = $script:defaultInFileContentType + } + } + + $headers.Add("Content-Type", $ContentType) } if (-not $PSCmdlet.ShouldProcess($url, "Invoke-WebRequest")) @@ -201,6 +263,13 @@ function Invoke-GHRestMethod $originalSecurityProtocol = [Net.ServicePointManager]::SecurityProtocol + # When $Save is in use, we need to remember what file we're saving the result to. + $outFile = [String]::Empty + if ($Save) + { + $outFile = New-TemporaryFile + } + try { Write-Log -Message $Description -Level Verbose @@ -214,8 +283,10 @@ function Invoke-GHRestMethod $params.Add("UseDefaultCredentials", $true) $params.Add("UseBasicParsing", $true) $params.Add("TimeoutSec", (Get-GitHubConfiguration -Name WebRequestTimeoutSec)) + if ($PSBoundParameters.ContainsKey('InFile')) { $params.Add('InFile', $InFile) } + if (-not [String]::IsNullOrWhiteSpace($outFile)) { $params.Add('OutFile', $outFile) } - if ($Method -in $ValidBodyContainingRequestMethods -and (-not [String]::IsNullOrEmpty($Body))) + if (($Method -in $ValidBodyContainingRequestMethods) -and (-not [String]::IsNullOrEmpty($Body))) { $bodyAsBytes = [System.Text.Encoding]::UTF8.GetBytes($Body) $params.Add("Body", $bodyAsBytes) @@ -230,6 +301,7 @@ function Invoke-GHRestMethod $ProgressPreference = 'SilentlyContinue' [Net.ServicePointManager]::SecurityProtocol=[Net.SecurityProtocolType]::Tls12 + $result = Invoke-WebRequest @params if ($Method -eq 'Delete') @@ -248,7 +320,14 @@ function Invoke-GHRestMethod $finalResult = $result.Content try { - $finalResult = $finalResult | ConvertFrom-Json + if ($Save) + { + $finalResult = Get-Item -Path $outFile + } + else + { + $finalResult = $finalResult | ConvertFrom-Json + } } catch [ArgumentException] { @@ -258,7 +337,7 @@ function Invoke-GHRestMethod $finalResult = $finalResult } - if (-not (Get-GitHubConfiguration -Name DisableSmarterObjects)) + if ((-not $Save) -and (-not (Get-GitHubConfiguration -Name DisableSmarterObjects))) { # In the case of getting raw content from the repo, we'll end up with a large object/byte # array which isn't convertible to a smarter object, but by _trying_ we'll end up wasting @@ -273,28 +352,31 @@ function Invoke-GHRestMethod } } - $links = $result.Headers['Link'] -split ',' - $nextLink = $null - $nextPageNumber = 1 - $numPages = 1 - $since = 0 - foreach ($link in $links) + if ($result.Headers.Count -gt 0) { - if ($link -match '<(.*page=(\d+)[^\d]*)>; rel="next"') - { - $nextLink = $Matches[1] - $nextPageNumber = [int]$Matches[2] - } - elseif ($link -match '<(.*since=(\d+)[^\d]*)>; rel="next"') + $links = $result.Headers['Link'] -split ',' + $nextLink = $null + $nextPageNumber = 1 + $numPages = 1 + $since = 0 + foreach ($link in $links) { - # Special case scenario for the users endpoint. - $nextLink = $Matches[1] - $since = [int]$Matches[2] - $numPages = 0 # Signifies an unknown number of pages. - } - elseif ($link -match '<.*page=(\d+)[^\d]+rel="last"') - { - $numPages = [int]$Matches[1] + if ($link -match '<(.*page=(\d+)[^\d]*)>; rel="next"') + { + $nextLink = $Matches[1] + $nextPageNumber = [int]$Matches[2] + } + elseif ($link -match '<(.*since=(\d+)[^\d]*)>; rel="next"') + { + # Special case scenario for the users endpoint. + $nextLink = $Matches[1] + $since = [int]$Matches[2] + $numPages = 0 # Signifies an unknown number of pages. + } + elseif ($link -match '<.*page=(\d+)[^\d]+rel="last"') + { + $numPages = [int]$Matches[1] + } } } @@ -480,7 +562,7 @@ function Invoke-GHRestMethodMultipleResult The Git repo for this module can be found here: http://aka.ms/PowerShellForGitHub .PARAMETER UriFragment - The unique, tail-end, of the REST URI that indicates what Store REST action will + The unique, tail-end, of the REST URI that indicates what GitHub REST action will be performed. This should *not* include the 'top' and 'max' parameters. These will be automatically added as needed. @@ -1045,3 +1127,104 @@ function Get-MediaAcceptHeader return $resultHeaders } + +@{ + defaultJsonBodyContentType = 'application/json; charset=UTF-8' + defaultInFileContentType = 'text/plain' + + # Compiled mostly from https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/MIME_types/Common_types + extensionToContentType = @{ + '.3gp' = 'video/3gpp' # 3GPP audio/video container + '.3g2' = 'video/3gpp2' # 3GPP2 audio/video container + '.7z' = 'application/x-7z-compressed' # 7-zip archive + '.aac' = 'audio/aac' # AAC audio + '.abw' = 'application/x-abiword' # AbiWord document + '.arc' = 'application/x-freearc' # Archive document (multiple files embedded) + '.avi' = 'video/x-msvideo' # AVI: Audio Video Interleave + '.azw' = 'application/vnd.amazon.ebook' # Amazon Kindle eBook format + '.bin' = 'application/octet-stream' # Any kind of binary data + '.bmp' = 'image/bmp' # Windows OS/2 Bitmap Graphics + '.bz' = 'application/x-bzip' # BZip archive + '.bz2' = 'application/x-bzip2' # BZip2 archive + '.csh' = 'application/x-csh' # C-Shell script + '.css' = 'text/css' # Cascading Style Sheets (CSS) + '.csv' = 'text/csv' # Comma-separated values (CSV) + '.deb' = 'application/octet-stream' # Standard Uix archive format + '.doc' = 'application/msword' # Microsoft Word + '.docx' = 'application/vnd.openxmlformats-officedocument.wordprocessingml.document' # Microsoft Word (OpenXML) + '.eot' = 'application/vnd.ms-fontobject' # MS Embedded OpenType fonts + '.epub' = 'application/epub+zip' # Electronic publication (EPUB) + '.exe' = 'application/vnd.microsoft.portable-executable' # Microsoft application executable + '.gz' = 'application/x-gzip' # GZip Compressed Archive + '.gif' = 'image/gif' # Graphics Interchange Format (GIF) + '.htm' = 'text/html' # HyperText Markup Language (HTML) + '.html' = 'text/html' # HyperText Markup Language (HTML) + '.ico' = 'image/vnd.microsoft.icon' # Icon format + '.ics' = 'text/calendar' # iCalendar format + '.ini' = 'text/plain' # Text-based configuration file + '.jar' = 'application/java-archive' # Java Archive (JAR) + '.jpeg' = 'image/jpeg' # JPEG images + '.jpg' = 'image/jpeg' # JPEG images + '.js' = 'text/javascript' # JavaScript + '.json' = 'application/json' # JSON format + '.jsonld' = 'application/ld+json' # JSON-LD format + '.mid' = 'audio/midi' # Musical Instrument Digital Interface (MIDI) + '.midi' = 'audio/midi' # Musical Instrument Digital Interface (MIDI) + '.mjs' = 'text/javascript' # JavaScript module + '.mp3' = 'audio/mpeg' # MP3 audio + '.mp4' = 'video/mp4' # MP3 video + '.mov' = 'video/quicktime' # Quicktime video + '.mpeg' = 'video/mpeg' # MPEG Video + '.mpg' = 'video/mpeg' # MPEG Video + '.mpkg' = 'application/vnd.apple.installer+xml' # Apple Installer Package + '.msi' = 'application/octet-stream' # Windows Installer package + '.msix' = 'application/octet-stream' # Windows Installer package + '.mkv' = 'video/x-matroska' # Matroska Multimedia Container + '.odp' = 'application/vnd.oasis.opendocument.presentation' # OpenDocument presentation document + '.ods' = 'application/vnd.oasis.opendocument.spreadsheet' # OpenDocument spreadsheet document + '.odt' = 'application/vnd.oasis.opendocument.text' # OpenDocument text document + '.oga' = 'audio/ogg' # OGG audio + '.ogg' = 'application/ogg' # OGG audio or video + '.ogv' = 'video/ogg' # OGG video + '.ogx' = 'application/ogg' # OGG + '.opus' = 'audio/opus' # Opus audio + '.otf' = 'font/otf' # OpenType font + '.png' = 'image/png' # Portable Network Graphics + '.pdf' = 'application/pdf' # Adobe Portable Document Format (PDF) + '.php' = 'application/x-httpd-php' # Hypertext Preprocessor (Personal Home Page) + '.pkg' = 'application/octet-stream' # mac OS X installer file + '.ps1' = 'text/plain' # PowerShell script file + '.psd1' = 'text/plain' # PowerShell module definition file + '.psm1' = 'text/plain' # PowerShell module file + '.ppt' = 'application/vnd.ms-powerpoint' # Microsoft PowerPoint + '.pptx' = 'application/vnd.openxmlformats-officedocument.presentationml.presentation' # Microsoft PowerPoint (OpenXML) + '.rar' = 'application/vnd.rar' # RAR archive + '.rtf' = 'application/rtf' # Rich Text Format (RTF) + '.rpm' = 'application/octet-stream' # Red Hat Linux package format + '.sh' = 'application/x-sh' # Bourne shell script + '.svg' = 'image/svg+xml' # Scalable Vector Graphics (SVG) + '.swf' = 'application/x-shockwave-flash' # Small web format (SWF) or Adobe Flash document + '.tar' = 'application/x-tar' # Tape Archive (TAR) + '.tif' = 'image/tiff' # Tagged Image File Format (TIFF) + '.tiff' = 'image/tiff' # Tagged Image File Format (TIFF) + '.ts' = 'video/mp2t' # MPEG transport stream + '.ttf' = 'font/ttf' # TrueType Font + '.txt' = 'text/plain' # Text (generally ASCII or ISO 8859-n) + '.vsd' = 'application/vnd.visio' # Microsoft Visio + '.vsix' = 'application/zip' # Visual Studio application package archive + '.wav' = 'audio/wav' # Waveform Audio Format + '.weba' = 'audio/webm' # WEBM audio + '.webm' = 'video/webm' # WEBM video + '.webp' = 'image/webp' # WEBP image + '.woff' = 'font/woff' # Web Open Font Format (WOFF) + '.woff2' = 'font/woff2' # Web Open Font Format (WOFF) + '.xhtml' = 'application/xhtml+xml' # XHTML + '.xls' = 'application/vnd.ms-excel' # Microsoft Excel + '.xlsx' = 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' # Microsoft Excel (OpenXML) + '.xml' = 'application/xml' # XML + '.xul' = 'application/vnd.mozilla.xul+xml' # XUL + '.zip' = 'application/zip' # ZIP archive + } + }.GetEnumerator() | ForEach-Object { + Set-Variable -Scope Script -Option ReadOnly -Name $_.Key -Value $_.Value + } diff --git a/GitHubEvents.ps1 b/GitHubEvents.ps1 index 000574af..2dc597db 100644 --- a/GitHubEvents.ps1 +++ b/GitHubEvents.ps1 @@ -60,6 +60,7 @@ filter Get-GitHubEvent GitHub.ProjectColumn GitHub.Reaction GitHub.Release + GitHub.ReleaseAsset GitHub.Repository .OUTPUTS diff --git a/GitHubIssueComments.ps1 b/GitHubIssueComments.ps1 index 0542581b..48ec9447 100644 --- a/GitHubIssueComments.ps1 +++ b/GitHubIssueComments.ps1 @@ -81,6 +81,7 @@ filter Get-GitHubIssueComment GitHub.ProjectColumn GitHub.Reaction GitHub.Release + GitHub.ReleaseAsset GitHub.Repository .OUTPUTS @@ -346,6 +347,7 @@ filter New-GitHubIssueComment GitHub.ProjectColumn GitHub.Reaction GitHub.Release + GitHub.ReleaseAsset GitHub.Repository GitHub.User @@ -490,6 +492,7 @@ filter Set-GitHubIssueComment GitHub.ProjectColumn GitHub.Reaction GitHub.Release + GitHub.ReleaseAsset GitHub.Repository GitHub.User @@ -621,6 +624,7 @@ filter Remove-GitHubIssueComment GitHub.ProjectColumn GitHub.Reaction GitHub.Release + GitHub.ReleaseAsset GitHub.Repository .EXAMPLE diff --git a/GitHubIssues.ps1 b/GitHubIssues.ps1 index 705a9da6..afd90bcf 100644 --- a/GitHubIssues.ps1 +++ b/GitHubIssues.ps1 @@ -130,6 +130,7 @@ filter Get-GitHubIssue GitHub.ProjectColumn GitHub.Reaction GitHub.Release + GitHub.ReleaseAsset GitHub.Repository GitHub.User @@ -434,6 +435,7 @@ filter Get-GitHubIssueTimeline GitHub.ProjectColumn GitHub.Reaction GitHub.Release + GitHub.ReleaseAsset GitHub.Repository .OUTPUTS @@ -573,6 +575,7 @@ filter New-GitHubIssue GitHub.ProjectColumn GitHub.Reaction GitHub.Release + GitHub.ReleaseAsset GitHub.Repository .OUTPUTS @@ -742,6 +745,7 @@ filter Set-GitHubIssue GitHub.ProjectColumn GitHub.Reaction GitHub.Release + GitHub.ReleaseAsset GitHub.Repository .OUTPUTS @@ -893,6 +897,7 @@ filter Lock-GitHubIssue GitHub.ProjectColumn GitHub.Reaction GitHub.Release + GitHub.ReleaseAsset GitHub.Repository .EXAMPLE @@ -1024,6 +1029,7 @@ filter Unlock-GitHubIssue GitHub.ProjectColumn GitHub.Reaction GitHub.Release + GitHub.ReleaseAsset GitHub.Repository .EXAMPLE diff --git a/GitHubLabels.ps1 b/GitHubLabels.ps1 index dd1391ac..22cf446f 100644 --- a/GitHubLabels.ps1 +++ b/GitHubLabels.ps1 @@ -65,6 +65,7 @@ filter Get-GitHubLabel GitHub.ProjectColumn GitHub.Reaction GitHub.Release + GitHub.ReleaseAsset GitHub.Repository .OUTPUTS @@ -253,6 +254,7 @@ filter New-GitHubLabel GitHub.ProjectColumn GitHub.Reaction GitHub.Release + GitHub.ReleaseAsset GitHub.Repository .OUTPUTS @@ -395,6 +397,7 @@ filter Remove-GitHubLabel GitHub.ProjectColumn GitHub.Reaction GitHub.Release + GitHub.ReleaseAsset GitHub.Repository .EXAMPLE @@ -553,6 +556,7 @@ filter Set-GitHubLabel GitHub.ProjectColumn GitHub.Reaction GitHub.Release + GitHub.ReleaseAsset GitHub.Repository .OUTPUTS @@ -700,6 +704,7 @@ filter Initialize-GitHubLabel GitHub.ProjectColumn GitHub.Reaction GitHub.Release + GitHub.ReleaseAsset GitHub.Repository .EXAMPLE @@ -840,6 +845,7 @@ function Add-GitHubIssueLabel GitHub.ProjectColumn GitHub.Reaction GitHub.Release + GitHub.ReleaseAsset GitHub.Repository .OUTPUTS @@ -1001,6 +1007,7 @@ function Set-GitHubIssueLabel GitHub.ProjectColumn GitHub.Reaction GitHub.Release + GitHub.ReleaseAsset GitHub.Repository .OUTPUTS @@ -1193,6 +1200,7 @@ filter Remove-GitHubIssueLabel GitHub.ProjectColumn GitHub.Reaction GitHub.Release + GitHub.ReleaseAsset GitHub.Repository .EXAMPLE diff --git a/GitHubMilestones.ps1 b/GitHubMilestones.ps1 index d2f2dc73..7326bf5e 100644 --- a/GitHubMilestones.ps1 +++ b/GitHubMilestones.ps1 @@ -69,6 +69,7 @@ filter Get-GitHubMilestone GitHub.ProjectColumn GitHub.Reaction GitHub.Release + GitHub.ReleaseAsset GitHub.Repository .OUTPUTS @@ -272,6 +273,7 @@ filter New-GitHubMilestone GitHub.ProjectColumn GitHub.Reaction GitHub.Release + GitHub.ReleaseAsset GitHub.Repository .OUTPUTS @@ -454,6 +456,7 @@ filter Set-GitHubMilestone GitHub.ProjectColumn GitHub.Reaction GitHub.Release + GitHub.ReleaseAsset GitHub.Repository .OUTPUTS @@ -635,6 +638,7 @@ filter Remove-GitHubMilestone GitHub.ProjectColumn GitHub.Reaction GitHub.Release + GitHub.ReleaseAsset GitHub.Repository .EXAMPLE diff --git a/GitHubMiscellaneous.ps1 b/GitHubMiscellaneous.ps1 index 20ecb3ee..a90a0a41 100644 --- a/GitHubMiscellaneous.ps1 +++ b/GitHubMiscellaneous.ps1 @@ -250,6 +250,7 @@ filter Get-GitHubLicense GitHub.ProjectColumn GitHub.Reaction GitHub.Release + GitHub.ReleaseAsset GitHub.Repository .OUTPUTS @@ -488,6 +489,7 @@ filter Get-GitHubCodeOfConduct GitHub.ProjectColumn GitHub.Reaction GitHub.Release + GitHub.ReleaseAsset GitHub.Repository .OUTPUTS diff --git a/GitHubProjects.ps1 b/GitHubProjects.ps1 index c96dc30f..87566375 100644 --- a/GitHubProjects.ps1 +++ b/GitHubProjects.ps1 @@ -64,6 +64,7 @@ filter Get-GitHubProject GitHub.ProjectColumn GitHub.Reaction GitHub.Release + GitHub.ReleaseAsset GitHub.Repository .OUTPUTS @@ -277,6 +278,7 @@ filter New-GitHubProject GitHub.ProjectColumn GitHub.Reaction GitHub.Release + GitHub.ReleaseAsset GitHub.Repository .OUTPUTS @@ -457,6 +459,7 @@ filter Set-GitHubProject GitHub.ProjectCard GitHub.ProjectColumn GitHub.Release + GitHub.ReleaseAsset GitHub.Repository .OUTPUTS @@ -585,6 +588,7 @@ filter Remove-GitHubProject GitHub.ProjectCard GitHub.ProjectColumn GitHub.Release + GitHub.ReleaseAsset GitHub.Repository .EXAMPLE diff --git a/GitHubPullRequests.ps1 b/GitHubPullRequests.ps1 index 0eeb5ee4..50b87fea 100644 --- a/GitHubPullRequests.ps1 +++ b/GitHubPullRequests.ps1 @@ -78,6 +78,7 @@ filter Get-GitHubPullRequest GitHub.ProjectColumn GitHub.Reaction GitHub.Release + GitHub.ReleaseAsset GitHub.Repository .OUTPUTS @@ -274,6 +275,7 @@ filter New-GitHubPullRequest GitHub.ProjectColumn GitHub.Reaction GitHub.Release + GitHub.ReleaseAsset GitHub.Repository .OUTPUTS diff --git a/GitHubReleases.ps1 b/GitHubReleases.ps1 index 7e5cf6c9..3ec46e28 100644 --- a/GitHubReleases.ps1 +++ b/GitHubReleases.ps1 @@ -3,6 +3,7 @@ @{ GitHubReleaseTypeName = 'GitHub.Release' + GitHubReleaseAssetTypeName = 'GitHub.ReleaseAsset' }.GetEnumerator() | ForEach-Object { Set-Variable -Scope Script -Option ReadOnly -Name $_.Key -Value $_.Value } @@ -67,6 +68,7 @@ filter Get-GitHubRelease GitHub.ProjectColumn GitHub.Reaction GitHub.Release + GitHub.ReleaseAsset GitHub.Repository .OUTPUTS @@ -106,11 +108,8 @@ filter Get-GitHubRelease Information about published releases are available to everyone. Only users with push access will receive listings for draft releases. #> - [CmdletBinding( - SupportsShouldProcess, - DefaultParameterSetName='Elements')] + [CmdletBinding(DefaultParameterSetName='Elements')] [OutputType({$script:GitHubReleaseTypeName})] - [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSShouldProcess", "", Justification="Methods called within here make use of PSShouldProcess, and the switch is passed on to them inherently.")] param( [Parameter(ParameterSetName='Elements')] [Parameter(ParameterSetName="Elements-ReleaseId")] @@ -156,18 +155,18 @@ filter Get-GitHubRelease [Parameter( Mandatory, - ParameterSetName="Elements-Latest")] + ParameterSetName='Elements-Latest')] [Parameter( Mandatory, - ParameterSetName="Uri-Latest")] + ParameterSetName='Uri-Latest')] [switch] $Latest, [Parameter( Mandatory, - ParameterSetName="Elements-Tag")] + ParameterSetName='Elements-Tag')] [Parameter( Mandatory, - ParameterSetName="Uri-Tag")] + ParameterSetName='Uri-Tag')] [string] $Tag, [string] $AccessToken, @@ -181,10 +180,10 @@ filter Get-GitHubRelease $OwnerName = $elements.ownerName $RepositoryName = $elements.repositoryName - $telemetryProperties = @{} - - $telemetryProperties['OwnerName'] = Get-PiiSafeString -PlainText $OwnerName - $telemetryProperties['RepositoryName'] = Get-PiiSafeString -PlainText $RepositoryName + $telemetryProperties = @{ + 'OwnerName' = (Get-PiiSafeString -PlainText $OwnerName) + 'RepositoryName' = (Get-PiiSafeString -PlainText $RepositoryName) + } $uriFragment = "repos/$OwnerName/$RepositoryName/releases" $description = "Getting releases for $OwnerName/$RepositoryName" @@ -197,7 +196,7 @@ filter Get-GitHubRelease $description = "Getting release information for $Release from $OwnerName/$RepositoryName" } - if($Latest) + if ($Latest) { $telemetryProperties['GetLatest'] = $true @@ -205,7 +204,7 @@ filter Get-GitHubRelease $description = "Getting latest release from $OwnerName/$RepositoryName" } - if(-not [String]::IsNullOrEmpty($Tag)) + if (-not [String]::IsNullOrEmpty($Tag)) { $telemetryProperties['ProvidedTag'] = $true @@ -225,57 +224,1309 @@ filter Get-GitHubRelease return (Invoke-GHRestMethodMultipleResult @params | Add-GitHubReleaseAdditionalProperties) } +filter New-GitHubRelease +{ +<# + .SYNOPSIS + Create a new release for a repository on GitHub. + + .DESCRIPTION + Create a new release for a repository on GitHub. -filter Add-GitHubReleaseAdditionalProperties + The Git repo for this module can be found here: http://aka.ms/PowerShellForGitHub + + .PARAMETER OwnerName + Owner of the repository. + If not supplied here, the DefaultOwnerName configuration property value will be used. + + .PARAMETER RepositoryName + Name of the repository. + If not supplied here, the DefaultRepositoryName configuration property value will be used. + + .PARAMETER Uri + Uri for the repository. + The OwnerName and RepositoryName will be extracted from here instead of needing to provide + them individually. + + .PARAMETER Tag + The name of the tag. The tag will be created around the committish if it doesn't exist + in the remote, and will need to be synced back to the local repository afterwards. + + .PARAMETER Committish + The committish value that determines where the Git tag is created from. + Can be any branch or commit SHA. Unused if the Git tag already exists. + Will default to the repository's default branch (usually 'master'). + + .PARAMETER Name + The name of the release. + + .PARAMETER Body + Text describing the contents of the tag. + + .PARAMETER Draft + Specifies if this should be a draft (unpublished) release or a published one. + + .PARAMETER PreRelease + Indicates if this should be identified as a pre-release or as a full release. + + .PARAMETER AccessToken + If provided, this will be used as the AccessToken for authentication with the + REST Api. Otherwise, will attempt to use the configured value or will run unauthenticated. + + .PARAMETER NoStatus + If this switch is specified, long-running commands will run on the main thread + with no commandline status update. When not specified, those commands run in + the background, enabling the command prompt to provide status information. + If not supplied here, the DefaultNoStatus configuration property value will be used. + + .INPUTS + GitHub.Branch + GitHub.Content + GitHub.Event + GitHub.Issue + GitHub.IssueComment + GitHub.Label + GitHub.Milestone + GitHub.PullRequest + GitHub.Project + GitHub.ProjectCard + GitHub.ProjectColumn + GitHub.Release + GitHub.ReleaseAsset + GitHub.Repository + + .OUTPUTS + GitHub.Release + + .EXAMPLE + New-GitHubRelease -OwnerName microsoft -RepositoryName PowerShellForGitHub -Tag 0.12.0 + + .NOTES + Requires push access to the repository. + + This endpoind triggers notifications. Creating content too quickly using this endpoint + may result in abuse rate limiting. +#> + [CmdletBinding( + SupportsShouldProcess, + PositionalBinding = $false)] + [OutputType({$script:GitHubReleaseTypeName})] + param( + [Parameter(ParameterSetName='Elements')] + [string] $OwnerName, + + [Parameter(ParameterSetName='Elements')] + [string] $RepositoryName, + + [Parameter( + Mandatory, + ValueFromPipelineByPropertyName, + ParameterSetName='Uri', + Position = 1)] + [Alias('RepositoryUrl')] + [string] $Uri, + + [Parameter( + Mandatory, + Position = 2)] + [string] $Tag, + + [Alias('Sha')] + [Alias('BranchName')] + [Alias('Commitish')] # git documentation says "committish", but GitHub uses "commitish" + [string] $Committish, + + [string] $Name, + + [Alias('Description')] + [string] $Body, + + [switch] $Draft, + + [switch] $PreRelease, + + [string] $AccessToken, + + [switch] $NoStatus + ) + + Write-InvocationLog + + $elements = Resolve-RepositoryElements + $OwnerName = $elements.ownerName + $RepositoryName = $elements.repositoryName + + $telemetryProperties = @{ + 'OwnerName' = (Get-PiiSafeString -PlainText $OwnerName) + 'RepositoryName' = (Get-PiiSafeString -PlainText $RepositoryName) + 'ProvidedCommittish' = ($PSBoundParameters.ContainsKey('Committish')) + 'ProvidedName' = ($PSBoundParameters.ContainsKey('Name')) + 'ProvidedBody' = ($PSBoundParameters.ContainsKey('Body')) + 'ProvidedDraft' = ($PSBoundParameters.ContainsKey('Draft')) + 'ProvidedPreRelease' = ($PSBoundParameters.ContainsKey('PreRelease')) + } + + $hashBody = @{ + 'tag_name' = $Tag + } + + if ($PSBoundParameters.ContainsKey('Committish')) { $hashBody['target_commitish'] = $Committish } + if ($PSBoundParameters.ContainsKey('Name')) { $hashBody['name'] = $Name } + if ($PSBoundParameters.ContainsKey('Body')) { $hashBody['body'] = $Body } + if ($PSBoundParameters.ContainsKey('Draft')) { $hashBody['draft'] = $Draft.ToBool() } + if ($PSBoundParameters.ContainsKey('PreRelease')) { $hashBody['prerelease'] = $PreRelease.ToBool() } + + $params = @{ + 'UriFragment' = "/repos/$OwnerName/$RepositoryName/releases" + 'Body' = (ConvertTo-Json -InputObject $hashBody) + 'Method' = 'Post' + 'Description' = "Creating release at $Tag" + 'AccessToken' = $AccessToken + 'TelemetryEventName' = $MyInvocation.MyCommand.Name + 'TelemetryProperties' = $telemetryProperties + 'NoStatus' = (Resolve-ParameterWithDefaultConfigurationValue -Name NoStatus -ConfigValueName DefaultNoStatus) + } + + if (-not $PSCmdlet.ShouldProcess($Tag, "Create release for $RepositoryName at tag")) + { + return + } + + return (Invoke-GHRestMethod @params | Add-GitHubReleaseAdditionalProperties) +} + +filter Set-GitHubRelease { <# .SYNOPSIS - Adds type name and additional properties to ease pipelining to GitHub Release objects. + Edits a release for a repository on GitHub. - .PARAMETER InputObject - The GitHub object to add additional properties to. + .DESCRIPTION + Edits a release for a repository on GitHub. - .PARAMETER TypeName - The type that should be assigned to the object. + The Git repo for this module can be found here: http://aka.ms/PowerShellForGitHub + + .PARAMETER OwnerName + Owner of the repository. + If not supplied here, the DefaultOwnerName configuration property value will be used. + + .PARAMETER RepositoryName + Name of the repository. + If not supplied here, the DefaultRepositoryName configuration property value will be used. + + .PARAMETER Uri + Uri for the repository. + The OwnerName and RepositoryName will be extracted from here instead of needing to provide + them individually. + + .PARAMETER Release + The ID of the release to edit. + + .PARAMETER Tag + The name of the tag. + + .PARAMETER Committish + The committish value that determines where the Git tag is created from. + Can be any branch or commit SHA. Unused if the Git tag already exists. + Will default to the repository's default branch (usually 'master'). + + .PARAMETER Name + The name of the release. + + .PARAMETER Body + Text describing the contents of the tag. + + .PARAMETER Draft + Specifies if this should be a draft (unpublished) release or a published one. + + .PARAMETER PreRelease + Indicates if this should be identified as a pre-release or as a full release. + + .PARAMETER AccessToken + If provided, this will be used as the AccessToken for authentication with the + REST Api. Otherwise, will attempt to use the configured value or will run unauthenticated. + + .PARAMETER NoStatus + If this switch is specified, long-running commands will run on the main thread + with no commandline status update. When not specified, those commands run in + the background, enabling the command prompt to provide status information. + If not supplied here, the DefaultNoStatus configuration property value will be used. .INPUTS - [PSCustomObject] + GitHub.Branch + GitHub.Content + GitHub.Event + GitHub.Issue + GitHub.IssueComment + GitHub.Label + GitHub.Milestone + GitHub.PullRequest + GitHub.Project + GitHub.ProjectCard + GitHub.ProjectColumn + GitHub.Release + GitHub.ReleaseAsset + GitHub.Repository .OUTPUTS GitHub.Release + + .EXAMPLE + Set-GitHubRelease -OwnerName microsoft -RepositoryName PowerShellForGitHub -Tag 0.12.0 -Body 'Adds core support for Projects' + + .NOTES + Requires push access to the repository. #> - [CmdletBinding()] - [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSUseSingularNouns", "", Justification="Internal helper that is definitely adding more than one property.")] + [CmdletBinding( + SupportsShouldProcess, + PositionalBinding = $false)] + [OutputType({$script:GitHubReleaseTypeName})] param( + [Parameter(ParameterSetName='Elements')] + [string] $OwnerName, + + [Parameter(ParameterSetName='Elements')] + [string] $RepositoryName, + [Parameter( Mandatory, - ValueFromPipeline)] - [AllowNull()] - [AllowEmptyCollection()] - [PSCustomObject[]] $InputObject, + ValueFromPipelineByPropertyName, + ParameterSetName='Uri', + Position = 1)] + [Alias('RepositoryUrl')] + [string] $Uri, - [ValidateNotNullOrEmpty()] - [string] $TypeName = $script:GitHubReleaseTypeName + [Parameter( + Mandatory, + ValueFromPipelineByPropertyName, + Position = 2)] + [Alias('ReleaseId')] + [int64] $Release, + + [string] $Tag, + + [Alias('Sha')] + [Alias('BranchName')] + [Alias('Commitish')] # git documentation says "committish", but GitHub uses "commitish" + [string] $Committish, + + [string] $Name, + + [Alias('Description')] + [string] $Body, + + [switch] $Draft, + + [switch] $PreRelease, + + [string] $AccessToken, + + [switch] $NoStatus ) - foreach ($item in $InputObject) + Write-InvocationLog + + $elements = Resolve-RepositoryElements + $OwnerName = $elements.ownerName + $RepositoryName = $elements.repositoryName + + $telemetryProperties = @{ + 'OwnerName' = (Get-PiiSafeString -PlainText $OwnerName) + 'RepositoryName' = (Get-PiiSafeString -PlainText $RepositoryName) + 'ProvidedTag' = ($PSBoundParameters.ContainsKey('Tag')) + 'ProvidedCommittish' = ($PSBoundParameters.ContainsKey('Committish')) + 'ProvidedName' = ($PSBoundParameters.ContainsKey('Name')) + 'ProvidedBody' = ($PSBoundParameters.ContainsKey('Body')) + 'ProvidedDraft' = ($PSBoundParameters.ContainsKey('Draft')) + 'ProvidedPreRelease' = ($PSBoundParameters.ContainsKey('PreRelease')) + } + + $hashBody = @{} + if ($PSBoundParameters.ContainsKey('Tag')) { $hashBody['tag_name'] = $Tag } + if ($PSBoundParameters.ContainsKey('Committish')) { $hashBody['target_commitish'] = $Committish } + if ($PSBoundParameters.ContainsKey('Name')) { $hashBody['name'] = $Name } + if ($PSBoundParameters.ContainsKey('Body')) { $hashBody['body'] = $Body } + if ($PSBoundParameters.ContainsKey('Draft')) { $hashBody['draft'] = $Draft.ToBool() } + if ($PSBoundParameters.ContainsKey('PreRelease')) { $hashBody['prerelease'] = $PreRelease.ToBool() } + + $params = @{ + 'UriFragment' = "/repos/$OwnerName/$RepositoryName/releases/$Release" + 'Body' = (ConvertTo-Json -InputObject $hashBody) + 'Method' = 'Patch' + 'Description' = "Creating release at $Tag" + 'AccessToken' = $AccessToken + 'TelemetryEventName' = $MyInvocation.MyCommand.Name + 'TelemetryProperties' = $telemetryProperties + 'NoStatus' = (Resolve-ParameterWithDefaultConfigurationValue -Name NoStatus -ConfigValueName DefaultNoStatus) + } + + if (-not $PSCmdlet.ShouldProcess($Release, "Update GitHub Release")) { - $item.PSObject.TypeNames.Insert(0, $TypeName) + return + } - if (-not (Get-GitHubConfiguration -Name DisablePipelineSupport)) - { - if (-not [String]::IsNullOrEmpty($item.html_url)) - { - $elements = Split-GitHubUri -Uri $item.html_url - $repositoryUrl = Join-GitHubUri @elements - Add-Member -InputObject $item -Name 'RepositoryUrl' -Value $repositoryUrl -MemberType NoteProperty -Force - } + return (Invoke-GHRestMethod @params | Add-GitHubReleaseAdditionalProperties) +} - Add-Member -InputObject $item -Name 'ReleaseId' -Value $item.id -MemberType NoteProperty -Force +filter Remove-GitHubRelease +{ +<# + .SYNOPSIS + Removes a release from a repository on GitHub. - if ($null -ne $item.author) - { - $null = Add-GitHubUserAdditionalProperties -InputObject $item.author + .DESCRIPTION + Removes a release from a repository on GitHub. + + The Git repo for this module can be found here: http://aka.ms/PowerShellForGitHub + + .PARAMETER OwnerName + Owner of the repository. + If not supplied here, the DefaultOwnerName configuration property value will be used. + + .PARAMETER RepositoryName + Name of the repository. + If not supplied here, the DefaultRepositoryName configuration property value will be used. + + .PARAMETER Uri + Uri for the repository. + The OwnerName and RepositoryName will be extracted from here instead of needing to provide + them individually. + + .PARAMETER Release + The ID of the release to remove. + + .PARAMETER Force + If this switch is specified, you will not be prompted for confirmation of command execution. + + .PARAMETER AccessToken + If provided, this will be used as the AccessToken for authentication with the + REST Api. Otherwise, will attempt to use the configured value or will run unauthenticated. + + .PARAMETER NoStatus + If this switch is specified, long-running commands will run on the main thread + with no commandline status update. When not specified, those commands run in + the background, enabling the command prompt to provide status information. + If not supplied here, the DefaultNoStatus configuration property value will be used. + + .INPUTS + GitHub.Branch + GitHub.Content + GitHub.Event + GitHub.Issue + GitHub.IssueComment + GitHub.Label + GitHub.Milestone + GitHub.PullRequest + GitHub.Project + GitHub.ProjectCard + GitHub.ProjectColumn + GitHub.Release + GitHub.ReleaseAsset + GitHub.Repository + + .EXAMPLE + Remove-GitHubRelease -OwnerName microsoft -RepositoryName PowerShellForGitHub -Release 1234567890 + + .EXAMPLE + Remove-GitHubRelease -OwnerName microsoft -RepositoryName PowerShellForGitHub -Release 1234567890 -Confirm:$false + + Will not prompt for confirmation, as -Confirm:$false was specified. + + .NOTES + Requires push access to the repository. +#> + [CmdletBinding( + SupportsShouldProcess, + PositionalBinding = $false, + ConfirmImpact='High')] + [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSReviewUnusedParameter", "", Justification="One or more parameters (like NoStatus) are only referenced by helper methods which get access to it from the stack via Get-Variable -Scope 1.")] + [Alias('Delete-GitHubRelease')] + param( + [Parameter(ParameterSetName='Elements')] + [string] $OwnerName, + + [Parameter(ParameterSetName='Elements')] + [string] $RepositoryName, + + [Parameter( + Mandatory, + ValueFromPipelineByPropertyName, + ParameterSetName='Uri', + Position = 1)] + [Alias('RepositoryUrl')] + [string] $Uri, + + [Parameter( + Mandatory, + ValueFromPipelineByPropertyName, + Position = 2)] + [Alias('ReleaseId')] + [int64] $Release, + + [switch] $Force, + + [string] $AccessToken, + + [switch] $NoStatus + ) + + Write-InvocationLog + + $elements = Resolve-RepositoryElements + $OwnerName = $elements.ownerName + $RepositoryName = $elements.repositoryName + + $telemetryProperties = @{ + 'OwnerName' = (Get-PiiSafeString -PlainText $OwnerName) + 'RepositoryName' = (Get-PiiSafeString -PlainText $RepositoryName) + } + + $params = @{ + 'UriFragment' = "/repos/$OwnerName/$RepositoryName/releases/$Release" + 'Method' = 'Delete' + 'Description' = "Deleting release $Release" + 'AccessToken' = $AccessToken + 'TelemetryEventName' = $MyInvocation.MyCommand.Name + 'TelemetryProperties' = $telemetryProperties + 'NoStatus' = (Resolve-ParameterWithDefaultConfigurationValue -Name NoStatus -ConfigValueName DefaultNoStatus) + } + + if ($Force -and (-not $Confirm)) + { + $ConfirmPreference = 'None' + } + + if (-not $PSCmdlet.ShouldProcess($Release, "Remove GitHub Release")) + { + return + } + + return Invoke-GHRestMethod @params +} + +filter Get-GitHubReleaseAsset +{ +<# + .SYNOPSIS + Gets a a list of assets for a release, or downloads a single release asset. + + .DESCRIPTION + Gets a a list of assets for a release, or downloads a single release asset. + + The Git repo for this module can be found here: http://aka.ms/PowerShellForGitHub + + .PARAMETER OwnerName + Owner of the repository. + If not supplied here, the DefaultOwnerName configuration property value will be used. + + .PARAMETER RepositoryName + Name of the repository. + If not supplied here, the DefaultRepositoryName configuration property value will be used. + + .PARAMETER Uri + Uri for the repository. + The OwnerName and RepositoryName will be extracted from here instead of needing to provide + them individually. + + .PARAMETER Release + The ID of a specific release to see the assets for. + + .PARAMETER Asset + The ID of the specific asset to download. + + .PARAMETER Path + The path where the downloaded asset should be stored. + + .PARAMETER Force + If specified, will overwrite any file located at Path when downloading Asset. + + .PARAMETER AccessToken + If provided, this will be used as the AccessToken for authentication with the + REST Api. Otherwise, will attempt to use the configured value or will run unauthenticated. + + .PARAMETER NoStatus + If this switch is specified, long-running commands will run on the main thread + with no commandline status update. When not specified, those commands run in + the background, enabling the command prompt to provide status information. + If not supplied here, the DefaultNoStatus configuration property value will be used. + + .INPUTS + GitHub.Branch + GitHub.Content + GitHub.Event + GitHub.Issue + GitHub.IssueComment + GitHub.Label + GitHub.Milestone + GitHub.PullRequest + GitHub.Project + GitHub.ProjectCard + GitHub.ProjectColumn + GitHub.Release + GitHub.ReleaseAsset + GitHub.Repository + + .OUTPUTS + GitHub.ReleaseAsset + + .EXAMPLE + Get-GitHubReleaseAsset -OwnerName microsoft -RepositoryName PowerShellForGitHub -Release 1234567890 + + Gets a list of all the assets associated with this release + + .EXAMPLE + Get-GitHubReleaseAsset -OwnerName microsoft -RepositoryName PowerShellForGitHub -Asset 1234567890 -Path 'c:\users\PowerShellForGitHub\downloads\asset.zip' -Force + + Downloads the asset 1234567890 to 'c:\users\PowerShellForGitHub\downloads\asset.zip' and + overwrites the file that may already be there. +#> + [CmdletBinding(PositionalBinding = $false)] + [OutputType({$script:GitHubReleaseAssetTypeName})] + [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSReviewUnusedParameter", "", Justification="One or more parameters (like NoStatus) are only referenced by helper methods which get access to it from the stack via Get-Variable -Scope 1.")] + param( + [Parameter(ParameterSetName='Elements-List')] + [Parameter(ParameterSetName='Elements-Info')] + [Parameter(ParameterSetName='Elements-Download')] + [string] $OwnerName, + + [Parameter(ParameterSetName='Elements-List')] + [Parameter(ParameterSetName='Elements-Info')] + [Parameter(ParameterSetName='Elements-Download')] + [string] $RepositoryName, + + [Parameter( + Mandatory, + ValueFromPipelineByPropertyName, + ParameterSetName='Uri-Info', + Position = 1)] + [Parameter( + Mandatory, + ValueFromPipelineByPropertyName, + ParameterSetName='Uri-Download', + Position = 1)] + [Parameter( + Mandatory, + ValueFromPipelineByPropertyName, + ParameterSetName='Uri-List', + Position = 1)] + [Alias('RepositoryUrl')] + [string] $Uri, + + [Parameter( + Mandatory, + ValueFromPipelineByPropertyName, + ParameterSetName='Elements-List', + Position = 1)] + [Parameter( + Mandatory, + ValueFromPipelineByPropertyName, + ParameterSetName='Uri-List', + Position = 2)] + [Alias('ReleaseId')] + [int64] $Release, + + [Parameter( + Mandatory, + ValueFromPipelineByPropertyName, + ParameterSetName='Elements-Info', + Position = 1)] + [Parameter( + Mandatory, + ValueFromPipelineByPropertyName, + ParameterSetName='Elements-Download', + Position = 1)] + [Parameter( + Mandatory, + ValueFromPipelineByPropertyName, + ParameterSetName='Uri-Info', + Position = 2)] + [Parameter( + Mandatory, + ValueFromPipelineByPropertyName, + ParameterSetName='Uri-Download', + Position = 2)] + [Alias('AssetId')] + [int64] $Asset, + + [Parameter( + Mandatory, + ParameterSetName='Elements-Download', + Position = 2)] + [Parameter( + Mandatory, + ParameterSetName='Uri-Download', + Position = 3)] + [string] $Path, + + [Parameter(ParameterSetName='Elements-Download')] + [Parameter(ParameterSetName='Uri-Download')] + [switch] $Force, + + [string] $AccessToken, + + [switch] $NoStatus + ) + + Write-InvocationLog + + $elements = Resolve-RepositoryElements + $OwnerName = $elements.ownerName + $RepositoryName = $elements.repositoryName + + $telemetryProperties = @{ + 'OwnerName' = (Get-PiiSafeString -PlainText $OwnerName) + 'RepositoryName' = (Get-PiiSafeString -PlainText $RepositoryName) + } + + $uriFragment = [String]::Empty + $description = [String]::Empty + $shouldSave = $false + $acceptHeader = $script:defaultAcceptHeader + if ($PSCmdlet.ParameterSetName -in ('Elements-List', 'Uri-List')) + { + $uriFragment = "/repos/$OwnerName/$RepositoryName/releases/$Release/assets" + $description = "Getting list of assets for release $Release" + } + elseif ($PSCmdlet.ParameterSetName -in ('Elements-Info', 'Uri-Info')) + { + $uriFragment = "/repos/$OwnerName/$RepositoryName/releases/assets/$Asset" + $description = "Getting information about release asset $Asset" + } + elseif ($PSCmdlet.ParameterSetName -in ('Elements-Download', 'Uri-Download')) + { + $uriFragment = "/repos/$OwnerName/$RepositoryName/releases/assets/$Asset" + $description = "Downloading release asset $Asset" + $shouldSave = $true + $acceptHeader = 'application/octet-stream' + + $Path = Resolve-UnverifiedPath -Path $Path + } + + $params = @{ + 'UriFragment' = $uriFragment + 'Method' = 'Get' + 'Description' = $description + 'AcceptHeader' = $acceptHeader + 'Save' = $shouldSave + 'AccessToken' = $AccessToken + 'TelemetryEventName' = $MyInvocation.MyCommand.Name + 'TelemetryProperties' = $telemetryProperties + 'NoStatus' = (Resolve-ParameterWithDefaultConfigurationValue -Name NoStatus -ConfigValueName DefaultNoStatus) + } + + $result = Invoke-GHRestMethod @params + + if ($PSCmdlet.ParameterSetName -in ('Elements-Download', 'Uri-Download')) + { + Write-Log -Message "Moving [$($result.FullName)] to [$Path]" -Level Verbose + return (Move-Item -Path $result -Destination $Path -Force:$Force -ErrorAction Stop -PassThru) + } + else + { + return ($result | Add-GitHubReleaseAssetAdditionalProperties) + } +} + +filter New-GitHubReleaseAsset +{ +<# + .SYNOPSIS + Uploads a new asset for a release on GitHub. + + .DESCRIPTION + Uploads a new asset for a release on GitHub. + + The Git repo for this module can be found here: http://aka.ms/PowerShellForGitHub + + .PARAMETER OwnerName + Owner of the repository. + If not supplied here, the DefaultOwnerName configuration property value will be used. + + .PARAMETER RepositoryName + Name of the repository. + If not supplied here, the DefaultRepositoryName configuration property value will be used. + + .PARAMETER Uri + Uri for the repository. + The OwnerName and RepositoryName will be extracted from here instead of needing to provide + them individually. + + .PARAMETER Release + The ID of the release that the asset is for. + + .PARAMETER UploadUrl + The value of 'upload_url' from getting the asset details. + + .PARAMETER Path + The path to the file to upload as a new asset. + + .PARAMETER Label + An alternate short description of the asset. Used in place of the filename. + + .PARAMETER ContentType + The MIME Media Type for the file being uploaded. By default, this will be inferred based + on the file's extension. If the extension is not known by this module, it will fallback to + using text/plain. You may specify a ContentType here to override the module's logic. + + .PARAMETER AccessToken + If provided, this will be used as the AccessToken for authentication with the + REST Api. Otherwise, will attempt to use the configured value or will run unauthenticated. + + .PARAMETER NoStatus + If this switch is specified, long-running commands will run on the main thread + with no commandline status update. When not specified, those commands run in + the background, enabling the command prompt to provide status information. + If not supplied here, the DefaultNoStatus configuration property value will be used. + + .INPUTS + GitHub.Branch + GitHub.Content + GitHub.Event + GitHub.Issue + GitHub.IssueComment + GitHub.Label + GitHub.Milestone + GitHub.PullRequest + GitHub.Project + GitHub.ProjectCard + GitHub.ProjectColumn + GitHub.Release + GitHub.ReleaseAsset + GitHub.Repository + + .OUTPUTS + GitHub.ReleaseAsset + + .EXAMPLE + New-GitHubReleaseAsset -OwnerName microsoft -RepositoryName PowerShellForGitHub -Release 123456 -Path 'c:\foo.zip' + + Uploads the file located at 'c:\foo.zip' to the 123456 release in microsoft/PowerShellForGitHub + + .EXAMPLE + $release = New-GitHubRelease -OwnerName microsoft -RepositoryName PowerShellForGitHub -Tag 'stable' + $release | New-GitHubReleaseAsset -Path 'c:\bar.txt' + + Creates a new release tagged as 'stable' and then uploads 'c:\bar.txt' as an asset for + that release. + + .NOTES + GitHub renames asset filenames that have special characters, non-alphanumeric characters, + and leading or trailing periods. Get-GitHubReleaseAsset lists the renamed filenames. + + If you upload an asset with the same filename as another uploaded asset, you'll receive + an error and must delete the old file before you can re-upload the new asset. +#> + [CmdletBinding( + SupportsShouldProcess, + PositionalBinding = $false)] + [OutputType({$script:GitHubReleaseAssetTypeName})] + [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSReviewUnusedParameter", "", Justification="One or more parameters (like NoStatus) are only referenced by helper methods which get access to it from the stack via Get-Variable -Scope 1.")] + param( + [Parameter(ParameterSetName='Elements')] + [string] $OwnerName, + + [Parameter(ParameterSetName='Elements')] + [string] $RepositoryName, + + [Parameter( + Mandatory, + ValueFromPipelineByPropertyName, + ParameterSetName='Uri', + Position = 1)] + [Parameter( + ValueFromPipelineByPropertyName, + ParameterSetName='UploadUrl')] + [Alias('RepositoryUrl')] + [string] $Uri, + + [Parameter( + Mandatory, + ValueFromPipelineByPropertyName, + ParameterSetName='Elements', + Position = 1)] + [Parameter( + Mandatory, + ValueFromPipelineByPropertyName, + ParameterSetName='Uri', + Position = 2)] + [Parameter( + ValueFromPipelineByPropertyName, + ParameterSetName='UploadUrl')] + [Alias('ReleaseId')] + [int64] $Release, + + [Parameter( + Mandatory, + ValueFromPipelineByPropertyName, + ParameterSetName='UploadUrl', + Position = 1)] + [string] $UploadUrl, + + [Parameter( + Mandatory, + ValueFromPipeline)] + [ValidateScript( + {if (Test-Path -Path $_ -PathType Leaf) { $true } + else { throw "$_ does not exist or is inaccessible." }})] + [string] $Path, + + [string] $Label, + + [string] $ContentType, + + [string] $AccessToken, + + [switch] $NoStatus + ) + + Write-InvocationLog + + $telemetryProperties = @{ + 'ProvidedUploadUrl' = ($PSBoundParameters.ContainsKey('UploadUrl')) + 'ProvidedLabel' = ($PSBoundParameters.ContainsKey('Label')) + 'ProvidedContentType' = ($PSBoundParameters.ContainsKey('ContentType')) + } + + # If UploadUrl wasn't provided, we'll need to query for it first. + if ([String]::IsNullOrEmpty($UploadUrl)) + { + $elements = Resolve-RepositoryElements + $OwnerName = $elements.ownerName + $RepositoryName = $elements.repositoryName + + $telemetryProperties['OwnerName'] = (Get-PiiSafeString -PlainText $OwnerName) + $telemetryProperties['RepositoryName'] = (Get-PiiSafeString -PlainText $RepositoryName) + + $params = @{ + 'OwnerName' = $OwnerName + 'RepositoryName' = $RepositoryName + 'Release' = $Release + 'AccessToken' = $AccessToken + 'NoStatus' = (Resolve-ParameterWithDefaultConfigurationValue -Name NoStatus -ConfigValueName DefaultNoStatus) + } + + $releaseInfo = Get-GitHubRelease @params + $UploadUrl = $releaseInfo.upload_url + } + + # Remove the '{name,label}' from the Url if it's there + if ($UploadUrl -match '(.*){') + { + $UploadUrl = $Matches[1] + } + + $Path = Resolve-UnverifiedPath -Path $Path + $file = Get-Item -Path $Path + $fileName = $file.Name + $fileNameEncoded = [Uri]::EscapeDataString($fileName) + $queryParams = @("name=$fileNameEncoded") + + if ($PSBoundParameters.ContainsKey('Label')) + { + $labelEncoded = [Uri]::EscapeDataString($Label) + $queryParams += "label=$labelEncoded" + } + + if (-not $PSCmdlet.ShouldProcess($Path, "Create new GitHub Release Asset")) + { + return + } + + $params = @{ + 'UriFragment' = $UploadUrl + '?' + ($queryParams -join '&') + 'Method' = 'Post' + 'Description' = "Uploading release asset: $fileName" + 'InFile' = $Path + 'ContentType' = $ContentType + 'AccessToken' = $AccessToken + 'TelemetryEventName' = $MyInvocation.MyCommand.Name + 'TelemetryProperties' = $telemetryProperties + 'NoStatus' = (Resolve-ParameterWithDefaultConfigurationValue -Name NoStatus -ConfigValueName DefaultNoStatus) + } + + return (Invoke-GHRestMethod @params | Add-GitHubReleaseAssetAdditionalProperties) +} + +filter Set-GitHubReleaseAsset +{ +<# + .SYNOPSIS + Edits an existing asset for a release on GitHub. + + .DESCRIPTION + Edits an existing asset for a release on GitHub. + + The Git repo for this module can be found here: http://aka.ms/PowerShellForGitHub + + .PARAMETER OwnerName + Owner of the repository. + If not supplied here, the DefaultOwnerName configuration property value will be used. + + .PARAMETER RepositoryName + Name of the repository. + If not supplied here, the DefaultRepositoryName configuration property value will be used. + + .PARAMETER Uri + Uri for the repository. + The OwnerName and RepositoryName will be extracted from here instead of needing to provide + them individually. + + .PARAMETER Asset + The ID of the asset being updated. + + .PARAMETER Name + The new filename of the asset. + + .PARAMETER Label + An alternate short description of the asset. Used in place of the filename. + + .PARAMETER AccessToken + If provided, this will be used as the AccessToken for authentication with the + REST Api. Otherwise, will attempt to use the configured value or will run unauthenticated. + + .PARAMETER NoStatus + If this switch is specified, long-running commands will run on the main thread + with no commandline status update. When not specified, those commands run in + the background, enabling the command prompt to provide status information. + If not supplied here, the DefaultNoStatus configuration property value will be used. + + .INPUTS + GitHub.Branch + GitHub.Content + GitHub.Event + GitHub.Issue + GitHub.IssueComment + GitHub.Label + GitHub.Milestone + GitHub.PullRequest + GitHub.Project + GitHub.ProjectCard + GitHub.ProjectColumn + GitHub.Release + GitHub.ReleaseAsset + GitHub.Repository + + .OUTPUTS + GitHub.ReleaseAsset + + .EXAMPLE + Set-GitHubReleaseAsset -OwnerName microsoft -RepositoryName PowerShellForGitHub -Asset 123456 -Name bar.zip + + Renames the asset 123456 to be 'bar.zip'. + + .NOTES + Requires push access to the repository. +#> + [CmdletBinding( + SupportsShouldProcess, + PositionalBinding = $false)] + [OutputType({$script:GitHubReleaseAssetTypeName})] + [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSReviewUnusedParameter", "", Justification="One or more parameters (like NoStatus) are only referenced by helper methods which get access to it from the stack via Get-Variable -Scope 1.")] + param( + [Parameter(ParameterSetName='Elements')] + [string] $OwnerName, + + [Parameter(ParameterSetName='Elements')] + [string] $RepositoryName, + + [Parameter( + Mandatory, + ValueFromPipelineByPropertyName, + ParameterSetName='Uri', + Position = 1)] + [Alias('RepositoryUrl')] + [string] $Uri, + + [Parameter( + Mandatory, + ValueFromPipelineByPropertyName, + Position = 2)] + [Alias('AssetId')] + [int64] $Asset, + + [string] $Name, + + [string] $Label, + + [string] $AccessToken, + + [switch] $NoStatus + ) + + Write-InvocationLog + + $elements = Resolve-RepositoryElements + $OwnerName = $elements.ownerName + $RepositoryName = $elements.repositoryName + + $telemetryProperties = @{ + 'OwnerName' = (Get-PiiSafeString -PlainText $OwnerName) + 'RepositoryName' = (Get-PiiSafeString -PlainText $RepositoryName) + 'ProvidedName' = ($PSBoundParameters.ContainsKey('Name')) + 'ProvidedLabel' = ($PSBoundParameters.ContainsKey('Label')) + } + + $hashBody = @{} + if ($PSBoundParameters.ContainsKey('Name')) { $hashBody['name'] = $Name } + if ($PSBoundParameters.ContainsKey('Label')) { $hashBody['label'] = $Label } + + if (-not $PSCmdlet.ShouldProcess($Asset, "Update GitHub Release Asset")) + { + return + } + + $params = @{ + 'UriFragment' = "/repos/$OwnerName/$RepositoryName/releases/assets/$Asset" + 'Body' = (ConvertTo-Json -InputObject $hashBody) + 'Method' = 'Patch' + 'Description' = "Editing asset $Asset" + 'AccessToken' = $AccessToken + 'TelemetryEventName' = $MyInvocation.MyCommand.Name + 'TelemetryProperties' = $telemetryProperties + 'NoStatus' = (Resolve-ParameterWithDefaultConfigurationValue -Name NoStatus -ConfigValueName DefaultNoStatus) + } + + return (Invoke-GHRestMethod @params | Add-GitHubReleaseAssetAdditionalProperties) +} + +filter Remove-GitHubReleaseAsset +{ +<# + .SYNOPSIS + Removes an asset from a release on GitHub. + + .DESCRIPTION + Removes an asset from a release on GitHub. + + The Git repo for this module can be found here: http://aka.ms/PowerShellForGitHub + + .PARAMETER OwnerName + Owner of the repository. + If not supplied here, the DefaultOwnerName configuration property value will be used. + + .PARAMETER RepositoryName + Name of the repository. + If not supplied here, the DefaultRepositoryName configuration property value will be used. + + .PARAMETER Uri + Uri for the repository. + The OwnerName and RepositoryName will be extracted from here instead of needing to provide + them individually. + + .PARAMETER Asset + The ID of the asset to remove. + + .PARAMETER Force + If this switch is specified, you will not be prompted for confirmation of command execution. + + .PARAMETER AccessToken + If provided, this will be used as the AccessToken for authentication with the + REST Api. Otherwise, will attempt to use the configured value or will run unauthenticated. + + .PARAMETER NoStatus + If this switch is specified, long-running commands will run on the main thread + with no commandline status update. When not specified, those commands run in + the background, enabling the command prompt to provide status information. + If not supplied here, the DefaultNoStatus configuration property value will be used. + + .INPUTS + GitHub.Branch + GitHub.Content + GitHub.Event + GitHub.Issue + GitHub.IssueComment + GitHub.Label + GitHub.Milestone + GitHub.PullRequest + GitHub.Project + GitHub.ProjectCard + GitHub.ProjectColumn + GitHub.Release + GitHub.ReleaseAsset + GitHub.Repository + + .EXAMPLE + Remove-GitHubReleaseAsset -OwnerName microsoft -RepositoryName PowerShellForGitHub -Asset 1234567890 + + .EXAMPLE + Remove-GitHubReleaseAsset -OwnerName microsoft -RepositoryName PowerShellForGitHub -Asset 1234567890 -Confirm:$false + + Will not prompt for confirmation, as -Confirm:$false was specified. +#> + [CmdletBinding( + SupportsShouldProcess, + PositionalBinding = $false, + ConfirmImpact='High')] + [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSReviewUnusedParameter", "", Justification="One or more parameters (like NoStatus) are only referenced by helper methods which get access to it from the stack via Get-Variable -Scope 1.")] + [Alias('Delete-GitHubReleaseAsset')] + param( + [Parameter(ParameterSetName='Elements')] + [string] $OwnerName, + + [Parameter(ParameterSetName='Elements')] + [string] $RepositoryName, + + [Parameter( + Mandatory, + ValueFromPipelineByPropertyName, + ParameterSetName='Uri', + Position = 1)] + [Alias('RepositoryUrl')] + [string] $Uri, + + [Parameter( + Mandatory, + ValueFromPipelineByPropertyName, + Position = 2)] + [Alias('AssetId')] + [int64] $Asset, + + [switch] $Force, + + [string] $AccessToken, + + [switch] $NoStatus + ) + + Write-InvocationLog + + $elements = Resolve-RepositoryElements + $OwnerName = $elements.ownerName + $RepositoryName = $elements.repositoryName + + $telemetryProperties = @{ + 'OwnerName' = (Get-PiiSafeString -PlainText $OwnerName) + 'RepositoryName' = (Get-PiiSafeString -PlainText $RepositoryName) + } + + $params = @{ + 'UriFragment' = "/repos/$OwnerName/$RepositoryName/releases/assets/$Asset" + 'Method' = 'Delete' + 'Description' = "Deleting asset $Asset" + 'AccessToken' = $AccessToken + 'TelemetryEventName' = $MyInvocation.MyCommand.Name + 'TelemetryProperties' = $telemetryProperties + 'NoStatus' = (Resolve-ParameterWithDefaultConfigurationValue -Name NoStatus -ConfigValueName DefaultNoStatus) + } + + if ($Force -and (-not $Confirm)) + { + $ConfirmPreference = 'None' + } + + if (-not $PSCmdlet.ShouldProcess($Asset, "Delete GitHub Release Asset")) + { + return + } + + return Invoke-GHRestMethod @params +} + +filter Add-GitHubReleaseAdditionalProperties +{ +<# + .SYNOPSIS + Adds type name and additional properties to ease pipelining to GitHub Release objects. + + .PARAMETER InputObject + The GitHub object to add additional properties to. + + .PARAMETER TypeName + The type that should be assigned to the object. + + .INPUTS + [PSCustomObject] + + .OUTPUTS + GitHub.Release +#> + [CmdletBinding()] + [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSUseSingularNouns", "", Justification="Internal helper that is definitely adding more than one property.")] + param( + [Parameter( + Mandatory, + ValueFromPipeline)] + [AllowNull()] + [AllowEmptyCollection()] + [PSCustomObject[]] $InputObject, + + [ValidateNotNullOrEmpty()] + [string] $TypeName = $script:GitHubReleaseTypeName + ) + + foreach ($item in $InputObject) + { + $item.PSObject.TypeNames.Insert(0, $TypeName) + + if (-not (Get-GitHubConfiguration -Name DisablePipelineSupport)) + { + if (-not [String]::IsNullOrEmpty($item.html_url)) + { + $elements = Split-GitHubUri -Uri $item.html_url + $repositoryUrl = Join-GitHubUri @elements + Add-Member -InputObject $item -Name 'RepositoryUrl' -Value $repositoryUrl -MemberType NoteProperty -Force + } + + Add-Member -InputObject $item -Name 'ReleaseId' -Value $item.id -MemberType NoteProperty -Force + Add-Member -InputObject $item -Name 'UploadUrl' -Value $item.upload_url -MemberType NoteProperty -Force + + if ($null -ne $item.author) + { + $null = Add-GitHubUserAdditionalProperties -InputObject $item.author + } + } + + Write-Output $item + } +} + +filter Add-GitHubReleaseAssetAdditionalProperties +{ +<# + .SYNOPSIS + Adds type name and additional properties to ease pipelining to GitHub Release Asset objects. + + .PARAMETER InputObject + The GitHub object to add additional properties to. + + .PARAMETER TypeName + The type that should be assigned to the object. + + .INPUTS + [PSCustomObject] + + .OUTPUTS + GitHub.ReleaseAsset +#> + [CmdletBinding()] + [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSUseSingularNouns", "", Justification="Internal helper that is definitely adding more than one property.")] + param( + [Parameter( + Mandatory, + ValueFromPipeline)] + [AllowNull()] + [AllowEmptyCollection()] + [PSCustomObject[]] $InputObject, + + [ValidateNotNullOrEmpty()] + [string] $TypeName = $script:GitHubReleaseAssetTypeName + ) + + foreach ($item in $InputObject) + { + $item.PSObject.TypeNames.Insert(0, $TypeName) + + if (-not (Get-GitHubConfiguration -Name DisablePipelineSupport)) + { + $elements = Split-GitHubUri -Uri $item.url + $repositoryUrl = Join-GitHubUri @elements + Add-Member -InputObject $item -Name 'RepositoryUrl' -Value $repositoryUrl -MemberType NoteProperty -Force + + Add-Member -InputObject $item -Name 'AssetId' -Value $item.id -MemberType NoteProperty -Force + + if ($null -ne $item.uploader) + { + $null = Add-GitHubUserAdditionalProperties -InputObject $item.uploader } } diff --git a/GitHubRepositories.ps1 b/GitHubRepositories.ps1 index 6a3fbc89..e198ef29 100644 --- a/GitHubRepositories.ps1 +++ b/GitHubRepositories.ps1 @@ -110,6 +110,7 @@ filter New-GitHubRepository GitHub.ProjectCard GitHub.ProjectColumn GitHub.Release + GitHub.ReleaseAsset GitHub.Repository .OUTPUTS @@ -450,6 +451,7 @@ filter Remove-GitHubRepository GitHub.ProjectColumn GitHub.Reaction GitHub.Release + GitHub.ReleaseAsset GitHub.Repository .EXAMPLE @@ -617,6 +619,7 @@ filter Get-GitHubRepository GitHub.ProjectColumn GitHub.Reaction GitHub.Release + GitHub.ReleaseAsset GitHub.Repository .OUTPUTS @@ -942,6 +945,7 @@ filter Rename-GitHubRepository GitHub.ProjectColumn GitHub.Reaction GitHub.Release + GitHub.ReleaseAsset GitHub.Repository .OUTPUTS @@ -1124,6 +1128,7 @@ filter Set-GitHubRepository GitHub.ProjectColumn GitHub.Reaction GitHub.Release + GitHub.ReleaseAsset GitHub.Repository .OUTPUTS @@ -1309,6 +1314,7 @@ filter Get-GitHubRepositoryTopic GitHub.ProjectColumn GitHub.Reaction GitHub.Release + GitHub.ReleaseAsset GitHub.Repository .OUTPUTS @@ -1425,6 +1431,7 @@ function Set-GitHubRepositoryTopic GitHub.ProjectColumn GitHub.Reaction GitHub.Release + GitHub.ReleaseAsset GitHub.Repository .OUTPUTS @@ -1613,6 +1620,7 @@ filter Get-GitHubRepositoryContributor GitHub.ProjectColumn GitHub.Reaction GitHub.Release + GitHub.ReleaseAsset GitHub.Repository .OUTPUTS @@ -1766,6 +1774,7 @@ filter Get-GitHubRepositoryCollaborator GitHub.ProjectColumn GitHub.Reaction GitHub.Release + GitHub.ReleaseAsset GitHub.Repository .OUTPUTS @@ -1885,6 +1894,7 @@ filter Get-GitHubRepositoryLanguage GitHub.ProjectColumn GitHub.Reaction GitHub.Release + GitHub.ReleaseAsset GitHub.Repository .OUTPUTS @@ -1994,6 +2004,7 @@ filter Get-GitHubRepositoryTag GitHub.ProjectColumn GitHub.Reaction GitHub.Release + GitHub.ReleaseAsset GitHub.Repository .OUTPUTS @@ -2109,6 +2120,7 @@ filter Move-GitHubRepositoryOwnership GitHub.ProjectColumn GitHub.Reaction GitHub.Release + GitHub.ReleaseAsset GitHub.Repository .OUTPUTS diff --git a/GitHubRepositoryForks.ps1 b/GitHubRepositoryForks.ps1 index 23cc63ae..18a3ca31 100644 --- a/GitHubRepositoryForks.ps1 +++ b/GitHubRepositoryForks.ps1 @@ -52,6 +52,7 @@ filter Get-GitHubRepositoryFork GitHub.ProjectColumn GitHub.Reaction GitHub.Release + GitHub.ReleaseAsset GitHub.Repository .OUTPUTS @@ -170,6 +171,7 @@ filter New-GitHubRepositoryFork GitHub.ProjectColumn GitHub.Reaction GitHub.Release + GitHub.ReleaseAsset GitHub.Repository .OUTPUTS diff --git a/GitHubRepositoryTraffic.ps1 b/GitHubRepositoryTraffic.ps1 index 0d32c21e..606b64d6 100644 --- a/GitHubRepositoryTraffic.ps1 +++ b/GitHubRepositoryTraffic.ps1 @@ -58,6 +58,7 @@ filter Get-GitHubReferrerTraffic GitHub.ProjectColumn GitHub.Reaction GitHub.Release + GitHub.ReleaseAsset GitHub.Repository .OUTPUTS @@ -172,6 +173,7 @@ filter Get-GitHubPathTraffic GitHub.ProjectColumn GitHub.Reaction GitHub.Release + GitHub.ReleaseAsset GitHub.Repository .OUTPUTS @@ -293,6 +295,7 @@ filter Get-GitHubViewTraffic GitHub.ProjectColumn GitHub.Reaction GitHub.Release + GitHub.ReleaseAsset GitHub.Repository .OUTPUTS @@ -418,6 +421,7 @@ filter Get-GitHubCloneTraffic GitHub.ProjectColumn GitHub.Reaction GitHub.Release + GitHub.ReleaseAsset GitHub.Repository .OUTPUTS diff --git a/GitHubTeams.ps1 b/GitHubTeams.ps1 index 44c94399..07d38454 100644 --- a/GitHubTeams.ps1 +++ b/GitHubTeams.ps1 @@ -62,6 +62,7 @@ filter Get-GitHubTeam GitHub.ProjectColumn GitHub.Reaction GitHub.Release + GitHub.ReleaseAsset GitHub.Repository GitHub.Team @@ -201,6 +202,7 @@ filter Get-GitHubTeamMember GitHub.ProjectCard GitHub.ProjectColumn GitHub.Release + GitHub.ReleaseAsset GitHub.Repository GitHub.Team diff --git a/Helpers.ps1 b/Helpers.ps1 index 482c5875..5d1b5ef7 100644 --- a/Helpers.ps1 +++ b/Helpers.ps1 @@ -428,13 +428,14 @@ function New-TemporaryDirectory [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSShouldProcess", "", Justification="Methods called within here make use of PSShouldProcess, and the switch is passed on to them inherently.")] param() - $guid = [System.GUID]::NewGuid() - while (Test-Path -PathType Container (Join-Path -Path $env:TEMP -ChildPath $guid)) + $parentTempPath = [System.IO.Path]::GetTempPath() + $tempFolderPath = [String]::Empty + do { - $guid = [System.GUID]::NewGuid() + $guid = [System.Guid]::NewGuid() + $tempFolderPath = Join-Path -Path $parentTempPath -ChildPath $guid } - - $tempFolderPath = Join-Path -Path $env:TEMP -ChildPath $guid + while (Test-Path -Path $tempFolderPath -PathType Container) Write-Log -Message "Creating temporary directory: $tempFolderPath" -Level Verbose New-Item -ItemType Directory -Path $tempFolderPath diff --git a/PowerShellForGitHub.psd1 b/PowerShellForGitHub.psd1 index 4be54289..3063585b 100644 --- a/PowerShellForGitHub.psd1 +++ b/PowerShellForGitHub.psd1 @@ -15,6 +15,7 @@ # Format files (.ps1xml) to be loaded when importing this module FormatsToProcess = @( + 'Formatters/GitHubReleases.Format.ps1xml' 'Formatters/GitHubRepositories.Format.ps1xml' ) @@ -89,6 +90,7 @@ 'Get-GitHubReaction', 'Get-GitHubReferrerTraffic', 'Get-GitHubRelease', + 'Get-GitHubReleaseAsset', 'Get-GitHubRepository', 'Get-GitHubRepositoryBranch', 'Get-GitHubRepositoryCollaborator', @@ -121,6 +123,8 @@ 'New-GitHubProjectCard', 'New-GitHubProjectColumn', 'New-GitHubPullRequest', + 'New-GitHubRelease', + 'New-GitHubReleaseAsset', 'New-GitHubRepository', 'New-GitHubRepositoryFromTemplate', 'New-GitHubRepositoryBranch', @@ -134,6 +138,8 @@ 'Remove-GitHubProjectCard', 'Remove-GitHubProjectColumn', 'Remove-GitHubReaction', + 'Remove-GitHubRelease', + 'Remove-GitHubReleaseAsset', 'Remove-GitHubRepository', 'Remove-GitHubRepositoryBranch' 'Rename-GitHubRepository', @@ -152,6 +158,8 @@ 'Set-GitHubProjectCard', 'Set-GitHubProjectColumn', 'Set-GitHubReaction', + 'Set-GitHubRelease', + 'Set-GitHubReleaseAsset', 'Set-GitHubRepository', 'Set-GitHubRepositoryTopic', 'Split-GitHubUri', @@ -163,6 +171,7 @@ ) AliasesToExport = @( + 'Delete-GitHubAsset', 'Delete-GitHubBranch', 'Delete-GitHubComment', 'Delete-GitHubIssueComment', @@ -170,17 +179,23 @@ 'Delete-GitHubMilestone', 'Delete-GitHubProject', 'Delete-GitHubProjectCard', - 'Delete-GitHubProjectColumn', + 'Delete-GitHubProjectColumn' 'Delete-GitHubReaction', + 'Delete-GitHubRelease', + 'Delete-GitHubReleaseAsset', 'Delete-GitHubRepository', 'Delete-GitHubRepositoryBranch', + 'Get-GitHubAsset', 'Get-GitHubBranch', 'Get-GitHubComment', + 'New-GitHubAsset', 'New-GitHubAssignee', 'New-GitHubBranch', 'New-GitHubComment', + 'Remove-GitHubAsset', 'Remove-GitHubBranch' 'Remove-GitHubComment', + 'Set-GitHubAsset', 'Set-GitHubComment', 'Transfer-GitHubRepositoryOwnership' 'Update-GitHubIssue', diff --git a/Tests/GitHubReleases.tests.ps1 b/Tests/GitHubReleases.tests.ps1 index 10c1b42a..abac19b8 100644 --- a/Tests/GitHubReleases.tests.ps1 +++ b/Tests/GitHubReleases.tests.ps1 @@ -18,122 +18,1077 @@ $moduleRootPath = Split-Path -Path $PSScriptRoot -Parent try { Describe 'Getting releases from repository' { - $ownerName = "dotnet" - $repositoryName = "core" - $releases = @(Get-GitHubRelease -OwnerName $ownerName -RepositoryName $repositoryName) + Context 'Common test state' { + BeforeAll { + $dotNetOwnerName = "dotnet" + $repositoryName = "core" - Context 'When getting all releases' { - It 'Should return multiple releases' { - $releases.Count | Should -BeGreaterThan 1 + $releases = @(Get-GitHubRelease -OwnerName $dotNetOwnerName -RepositoryName $repositoryName) + } + + Context 'When getting all releases' { + It 'Should return multiple releases' { + $releases.Count | Should -BeGreaterThan 1 + } + + It 'Should have expected type and additional properties' { + $elements = Split-GitHubUri -Uri $releases[0].html_url + $repositoryUrl = Join-GitHubUri @elements + + $releases[0].PSObject.TypeNames[0] | Should -Be 'GitHub.Release' + $releases[0].RepositoryUrl | Should -Be $repositoryUrl + $releases[0].ReleaseId | Should -Be $releases[0].id + } + } + + Context 'When getting the latest releases' { + $latest = @(Get-GitHubRelease -OwnerName $dotNetOwnerName -RepositoryName $repositoryName -Latest) + + It 'Should return one value' { + $latest.Count | Should -Be 1 + } + + It 'Should return the first release from the full releases list' { + $latest[0].url | Should -Be $releases[0].url + $latest[0].name | Should -Be $releases[0].name + } + + It 'Should have expected type and additional properties' { + $elements = Split-GitHubUri -Uri $latest[0].html_url + $repositoryUrl = Join-GitHubUri @elements + + $latest[0].PSObject.TypeNames[0] | Should -Be 'GitHub.Release' + $latest[0].RepositoryUrl | Should -Be $repositoryUrl + $latest[0].ReleaseId | Should -Be $latest[0].id + } + } + + Context 'When getting the latest releases via the pipeline' { + $latest = @(Get-GitHubRepository -OwnerName $dotNetOwnerName -RepositoryName $repositoryName | + Get-GitHubRelease -Latest) + + It 'Should return one value' { + $latest.Count | Should -Be 1 + } + + It 'Should return the first release from the full releases list' { + $latest[0].url | Should -Be $releases[0].url + $latest[0].name | Should -Be $releases[0].name + } + + It 'Should have expected type and additional properties' { + $elements = Split-GitHubUri -Uri $latest[0].html_url + $repositoryUrl = Join-GitHubUri @elements + + $latest[0].PSObject.TypeNames[0] | Should -Be 'GitHub.Release' + $latest[0].RepositoryUrl | Should -Be $repositoryUrl + $latest[0].ReleaseId | Should -Be $latest[0].id + } + + $latestAgain = @($latest | Get-GitHubRelease) + It 'Should be the same release' { + $latest[0].ReleaseId | Should -Be $latestAgain[0].ReleaseId + } + } + + Context 'When getting a specific release' { + $specificIndex = 5 + $specific = @(Get-GitHubRelease -OwnerName $dotNetOwnerName -RepositoryName $repositoryName -ReleaseId $releases[$specificIndex].id) + + It 'Should return one value' { + $specific.Count | Should -Be 1 + } + + It 'Should return the correct release' { + $specific.name | Should -Be $releases[$specificIndex].name + } + + It 'Should have expected type and additional properties' { + $elements = Split-GitHubUri -Uri $specific[0].html_url + $repositoryUrl = Join-GitHubUri @elements + + $specific[0].PSObject.TypeNames[0] | Should -Be 'GitHub.Release' + $specific[0].RepositoryUrl | Should -Be $repositoryUrl + $specific[0].id | Should -Be $specific[0].ReleaseId + } + } + + Context 'When getting a tagged release' { + $taggedIndex = 8 + $tagged = @(Get-GitHubRelease -OwnerName $dotNetOwnerName -RepositoryName $repositoryName -Tag $releases[$taggedIndex].tag_name) + + It 'Should return one value' { + $tagged.Count | Should -Be 1 + } + + It 'Should return the correct release' { + $tagged.name | Should -Be $releases[$taggedIndex].name + } + + It 'Should have expected type and additional properties' { + $elements = Split-GitHubUri -Uri $tagged[0].html_url + $repositoryUrl = Join-GitHubUri @elements + + $tagged[0].PSObject.TypeNames[0] | Should -Be 'GitHub.Release' + $tagged[0].RepositoryUrl | Should -Be $repositoryUrl + $tagged[0].ReleaseId | Should -Be $tagged[0].id + } + } + } + } + + Describe 'Getting releases from default owner/repository' { + Context 'Common test state' { + BeforeAll { + $originalOwnerName = Get-GitHubConfiguration -Name DefaultOwnerName + $originalRepositoryName = Get-GitHubConfiguration -Name DefaultRepositoryName + + Set-GitHubConfiguration -DefaultOwnerName "dotnet" + Set-GitHubConfiguration -DefaultRepositoryName "core" + } + + AfterAll { + Set-GitHubConfiguration -DefaultOwnerName $originalOwnerName + Set-GitHubConfiguration -DefaultRepositoryName $originalRepositoryName + } + + Context 'When getting all releases' { + $releases = @(Get-GitHubRelease) + + It 'Should return multiple releases' { + $releases.Count | Should -BeGreaterThan 1 + } + } + } + } + + Describe 'Creating, changing and deleting releases with defaults' { + Context 'Common test state' { + BeforeAll { + $defaultTagName = '0.2.0' + $defaultReleaseName = 'Release Name' + $defaultReleaseBody = 'Releasey Body' + $repo = New-GitHubRepository -RepositoryName ([Guid]::NewGuid().Guid) -AutoInit -Private + $release = New-GitHubRelease -Uri $repo.svn_url -Tag $defaultTagName + $queried = Get-GitHubRelease -Uri $repo.svn_url -Release $release.id + } + + AfterAll { + Remove-GitHubRepository -Uri $repo.svn_url -Confirm:$false + } + + Context 'When creating a simple new release' { + It 'Should have expected type and additional properties' { + $elements = Split-GitHubUri -Uri $release.html_url + $repositoryUrl = Join-GitHubUri @elements + + $release.PSObject.TypeNames[0] | Should -Be 'GitHub.Release' + $release.RepositoryUrl | Should -Be $repositoryUrl + $release.ReleaseId | Should -Be $release.id + } + + It 'Should be queryable' { + $queried.id | Should -Be $release.id + $queried.tag_name | Should -Be $defaultTagName + } + + It 'Should have the expected default property values' { + $queried.name | Should -BeNullOrEmpty + $queried.body | Should -BeNullOrEmpty + $queried.draft | Should -BeFalse + $queried.prerelease | Should -BeFalse + } + + It 'Should be modifiable' { + Set-GitHubRelease -Uri $repo.svn_url -Release $release.id -Name $defaultReleaseName -Body $defaultReleaseBody -Draft -PreRelease + $queried = Get-GitHubRelease -Uri $repo.svn_url -Release $release.id + $queried.name | Should -Be $defaultReleaseName + $queried.body | Should -Be $defaultReleaseBody + $queried.draft | Should -BeTrue + $queried.prerelease | Should -BeTrue + } + + It 'Should be removable' { + Remove-GitHubRelease -Uri $repo.svn_url -Release $release.id -Confirm:$false + { Get-GitHubRelease -Uri $repo.svn_url -Release $release.id } | Should -Throw + } + } + } + } + + Describe 'Creating, changing and deleting releases with defaults using the pipeline' { + Context 'Common test state' { + BeforeAll { + $defaultTagName = '0.2.0' + $defaultReleaseName = 'Release Name' + $defaultReleaseBody = 'Releasey Body' + $repo = New-GitHubRepository -RepositoryName ([Guid]::NewGuid().Guid) -AutoInit -Private + $release = $repo | New-GitHubRelease -Tag $defaultTagName + $queried = $release | Get-GitHubRelease + } + + AfterAll { + $repo | Remove-GitHubRepository -Force + } + + Context 'When creating a simple new release' { + It 'Should have expected type and additional properties' { + $elements = Split-GitHubUri -Uri $release.html_url + $repositoryUrl = Join-GitHubUri @elements + + $release.PSObject.TypeNames[0] | Should -Be 'GitHub.Release' + $release.RepositoryUrl | Should -Be $repositoryUrl + $release.ReleaseId | Should -Be $release.id + } + + It 'Should be queryable' { + $queried.id | Should -Be $release.id + $queried.tag_name | Should -Be $defaultTagName + } + + It 'Should have the expected default property values' { + $queried.name | Should -BeNullOrEmpty + $queried.body | Should -BeNullOrEmpty + $queried.draft | Should -BeFalse + $queried.prerelease | Should -BeFalse + } + + It 'Should be modifiable with the release on the pipeline' { + $null = $release | Set-GitHubRelease -Name $defaultReleaseName -Body $defaultReleaseBody -Draft -PreRelease + $queried = $release | Get-GitHubRelease + $queried.name | Should -Be $defaultReleaseName + $queried.body | Should -Be $defaultReleaseBody + $queried.draft | Should -BeTrue + $queried.prerelease | Should -BeTrue + } + + It 'Should be modifiable with the URI on the pipeline' { + $null = $repo | Set-GitHubRelease -Release $release.id -Draft:$false + $queried = $repo | Get-GitHubRelease -Release $release.id + $queried.name | Should -Be $defaultReleaseName + $queried.body | Should -Be $defaultReleaseBody + $queried.draft | Should -BeFalse + $queried.prerelease | Should -BeTrue + } + + It 'Should be removable' { + $release | Remove-GitHubRelease -Force + { $release | Get-GitHubRelease } | Should -Throw + } + } + } + } + + Describe 'Creating and changing releases with non-defaults' { + Context 'Common test state' { + BeforeAll { + $defaultTagName = '0.2.0' + $defaultReleaseName = 'Release Name' + $defaultReleaseBody = 'Releasey Body' + $repo = New-GitHubRepository -RepositoryName ([Guid]::NewGuid().Guid) -AutoInit -Private + } + + AfterAll { + Remove-GitHubRepository -Uri $repo.svn_url -Confirm:$false + } + + Context 'When creating a simple new release' { + BeforeAll { + $release = New-GitHubRelease -Uri $repo.svn_url -Tag $defaultTagName -Name $defaultReleaseName -Body $defaultReleaseBody -Draft -PreRelease + $queried = Get-GitHubRelease -Uri $repo.svn_url -Release $release.id + } + + AfterAll { + $release | Remove-GitHubRelease -Force + } + + It 'Should be creatable with non-default property values' { + $queried.id | Should -Be $release.id + $queried.tag_name | Should -Be $defaultTagName + $queried.name | Should -Be $defaultReleaseName + $queried.body | Should -Be $defaultReleaseBody + $queried.draft | Should -BeTrue + $queried.prerelease | Should -BeTrue + } + } + + Context 'When creating a simple new release with the repo on the pipeline' { + BeforeAll { + $release = $repo | New-GitHubRelease -Tag $defaultTagName -Name $defaultReleaseName -Body $defaultReleaseBody -Draft -PreRelease + $queried = Get-GitHubRelease -Uri $repo.svn_url -Release $release.id + } + + AfterAll { + $release | Remove-GitHubRelease -Force + } + + It 'Should be creatable with non-default property values' { + $queried.id | Should -Be $release.id + $queried.tag_name | Should -Be $defaultTagName + $queried.name | Should -Be $defaultReleaseName + $queried.body | Should -Be $defaultReleaseBody + $queried.draft | Should -BeTrue + $queried.prerelease | Should -BeTrue + } + } + } + } + + Describe 'Get-GitHubReleaseAsset' { + BeforeAll { + $defaultTagName = '0.2.0' + + $repo = New-GitHubRepository -RepositoryName ([Guid]::NewGuid().Guid) -AutoInit -Private + + $tempFile = New-TemporaryFile + $zipFile = "$($tempFile.FullName).zip" + Move-Item -Path $tempFile -Destination $zipFile + + $tempFile = New-TemporaryFile + $txtFile = "$($tempFile.FullName).txt" + Move-Item -Path $tempFile -Destination $txtFile + Out-File -FilePath $txtFile -InputObject "txt file content" -Encoding utf8 + + # The file we'll save the downloaded contents to + $saveFile = New-TemporaryFile + + # Disable Progress Bar in function scope during Compress-Archive + $ProgressPreference = 'SilentlyContinue' + Compress-Archive -Path $txtFile -DestinationPath $zipFile -Force + + $labelBase = 'mylabel' + } + + AfterAll { + @($zipFile, $txtFile, $saveFile) | Remove-Item -ErrorAction SilentlyContinue | Out-Null + Remove-GitHubRepository -Uri $repo.svn_url -Confirm:$false + } + + Context 'Using parameters' { + BeforeAll { + $release = New-GitHubRelease -Uri $repo.svn_url -Tag $defaultTagName + + # We want to make sure we start out without the file being there. + Remove-Item -Path $saveFile -ErrorAction SilentlyContinue | Out-Null + } + + AfterAll { + $release | Remove-GitHubRelease -Force + } + + $assets = @(Get-GitHubReleaseAsset -Uri $repo.svn_url -Release $release.id) + It 'Should have no assets so far' { + $assets.Count | Should -Be 0 + } + + @($zipFile, $txtFile) | ForEach-Object { + $fileName = (Get-Item -Path $_).Name + $finalLabel = "$labelBase-$fileName" + $asset = New-GitHubReleaseAsset -Uri $repo.svn_url -Release $release.id -Path $_ -Label $finalLabel + It "Can add a release asset" { + $assetId = $asset.id + + $asset.name | Should -BeExactly $fileName + $asset.label | Should -BeExactly $finalLabel + } + + It 'Should have expected type and additional properties' { + $elements = Split-GitHubUri -Uri $asset.url + $repositoryUrl = Join-GitHubUri @elements + + $asset.PSObject.TypeNames[0] | Should -Be 'GitHub.ReleaseAsset' + $asset.RepositoryUrl | Should -Be $repositoryUrl + $asset.AssetId | Should -Be $asset.id + $asset.uploader.PSObject.TypeNames[0] | Should -Be 'GitHub.User' + } + } + + $assets = @(Get-GitHubReleaseAsset -Uri $repo.svn_url -Release $release.id) + It 'Should have both assets now' { + $assets.Count | Should -Be 2 + } + + It 'Should have expected type and additional properties' { + foreach ($asset in $assets) + { + $elements = Split-GitHubUri -Uri $asset.url + $repositoryUrl = Join-GitHubUri @elements + + $asset.PSObject.TypeNames[0] | Should -Be 'GitHub.ReleaseAsset' + $asset.RepositoryUrl | Should -Be $repositoryUrl + $asset.AssetId | Should -Be $asset.id + $asset.uploader.PSObject.TypeNames[0] | Should -Be 'GitHub.User' + } + } + + $txtFileName = (Get-Item -Path $txtFile).Name + $txtFileAsset = $assets | Where-Object { $_.name -eq $txtFileName } + $asset = Get-GitHubReleaseAsset -Uri $repo.svn_url -Asset $txtFileAsset.id + It 'Should be able to query for a single asset' { + $asset.id | Should -Be $txtFileAsset.id + } + + It 'Should have expected type and additional properties' { + $elements = Split-GitHubUri -Uri $asset.url + $repositoryUrl = Join-GitHubUri @elements + + $asset.PSObject.TypeNames[0] | Should -Be 'GitHub.ReleaseAsset' + $asset.RepositoryUrl | Should -Be $repositoryUrl + $asset.AssetId | Should -Be $asset.id + $asset.uploader.PSObject.TypeNames[0] | Should -Be 'GitHub.User' + } + + It 'Should not have the downloaded file yet' { + Test-Path -Path $saveFile -PathType Leaf | Should -BeFalse + } + + $downloadParams = @{ + OwnerName = $script:ownerName + RepositoryName = $repo.name + Asset = $txtFileAsset.id + Path = $saveFile + } + + $null = Get-GitHubReleaseAsset @downloadParams + It 'Should be able to download the asset file' { + Test-Path -Path $saveFile -PathType Leaf | Should -BeTrue + } + + It 'Should be able the same file' { + $compareParams = @{ + ReferenceObject = (Get-Content -Path $txtFile) + DifferenceObject = (Get-Content -Path $saveFile) + } + + Compare-Object @compareParams | Should -BeNullOrEmpty + } + + It 'Should fail if the download location already exists' { + { Get-GitHubReleaseAsset @downloadParams } | Should -Throw + } + + It 'Should work if the download location already exists and -Force is used' { + $null = Get-GitHubReleaseAsset @downloadParams -Force + + $compareParams = @{ + ReferenceObject = (Get-Content -Path $txtFile) + DifferenceObject = (Get-Content -Path $saveFile) + } + + Compare-Object @compareParams | Should -BeNullOrEmpty + } + } + + Context 'Using the repo on the pipeline' { + BeforeAll { + $release = $repo | New-GitHubRelease -Tag $defaultTagName + + # We want to make sure we start out without the file being there. + Remove-Item -Path $saveFile -ErrorAction SilentlyContinue | Out-Null + } + + AfterAll { + $release | Remove-GitHubRelease -Force + } + + $assets = @($repo | Get-GitHubReleaseAsset -Release $release.id) + It 'Should have no assets so far' { + $assets.Count | Should -Be 0 + } + + @($zipFile, $txtFile) | ForEach-Object { + $fileName = (Get-Item -Path $_).Name + $finalLabel = "$labelBase-$fileName" + $asset = $repo | New-GitHubReleaseAsset -Release $release.id -Path $_ -Label $finalLabel + It "Can add a release asset" { + $assetId = $asset.id + + $asset.name | Should -BeExactly $fileName + $asset.label | Should -BeExactly $finalLabel + } + + It 'Should have expected type and additional properties' { + $elements = Split-GitHubUri -Uri $asset.url + $repositoryUrl = Join-GitHubUri @elements + + $asset.PSObject.TypeNames[0] | Should -Be 'GitHub.ReleaseAsset' + $asset.RepositoryUrl | Should -Be $repositoryUrl + $asset.AssetId | Should -Be $asset.id + $asset.uploader.PSObject.TypeNames[0] | Should -Be 'GitHub.User' + } + } + + $assets = @($repo | Get-GitHubReleaseAsset -Release $release.id) + It 'Should have both assets now' { + $assets.Count | Should -Be 2 } It 'Should have expected type and additional properties' { - $releases[0].PSObject.TypeNames[0] | Should -Be 'GitHub.Release' - $releases[0].html_url.StartsWith($releases[0].RepositoryUrl) | Should -BeTrue - $releases[0].id | Should -Be $releases[0].ReleaseId + foreach ($asset in $assets) + { + $elements = Split-GitHubUri -Uri $asset.url + $repositoryUrl = Join-GitHubUri @elements + + $asset.PSObject.TypeNames[0] | Should -Be 'GitHub.ReleaseAsset' + $asset.RepositoryUrl | Should -Be $repositoryUrl + $asset.AssetId | Should -Be $asset.id + $asset.uploader.PSObject.TypeNames[0] | Should -Be 'GitHub.User' + } + } + + $txtFileName = (Get-Item -Path $txtFile).Name + $txtFileAsset = $assets | Where-Object { $_.name -eq $txtFileName } + $asset = $repo | Get-GitHubReleaseAsset -Asset $txtFileAsset.id + It 'Should be able to query for a single asset' { + $asset.id | Should -Be $txtFileAsset.id + } + + It 'Should have expected type and additional properties' { + $elements = Split-GitHubUri -Uri $asset.url + $repositoryUrl = Join-GitHubUri @elements + + $asset.PSObject.TypeNames[0] | Should -Be 'GitHub.ReleaseAsset' + $asset.RepositoryUrl | Should -Be $repositoryUrl + $asset.AssetId | Should -Be $asset.id + $asset.uploader.PSObject.TypeNames[0] | Should -Be 'GitHub.User' + } + + It 'Should not have the downloaded file yet' { + Test-Path -Path $saveFile -PathType Leaf | Should -BeFalse + } + + $downloadParams = @{ + Asset = $txtFileAsset.id + Path = $saveFile + } + + $null = $repo | Get-GitHubReleaseAsset @downloadParams + It 'Should be able to download the asset file' { + Test-Path -Path $saveFile -PathType Leaf | Should -BeTrue + } + + It 'Should be able the same file' { + $compareParams = @{ + ReferenceObject = (Get-Content -Path $txtFile) + DifferenceObject = (Get-Content -Path $saveFile) + } + + Compare-Object @compareParams | Should -BeNullOrEmpty + } + + It 'Should fail if the download location already exists' { + { $repo | Get-GitHubReleaseAsset @downloadParams } | Should -Throw + } + + It 'Should work if the download location already exists and -Force is used' { + $null = $repo | Get-GitHubReleaseAsset @downloadParams -Force + + $compareParams = @{ + ReferenceObject = (Get-Content -Path $txtFile) + DifferenceObject = (Get-Content -Path $saveFile) + } + + Compare-Object @compareParams | Should -BeNullOrEmpty } } - Context 'When getting the latest releases' { - $latest = @(Get-GitHubRelease -OwnerName $ownerName -RepositoryName $repositoryName -Latest) + Context 'Using the release on the pipeline' { + BeforeAll { + $release = $repo | New-GitHubRelease -Tag $defaultTagName + + # We want to make sure we start out without the file being there. + Remove-Item -Path $saveFile -ErrorAction SilentlyContinue | Out-Null + } + + AfterAll { + $release | Remove-GitHubRelease -Force + } - It 'Should return one value' { - $latest.Count | Should -Be 1 + $assets = @($release | Get-GitHubReleaseAsset) + It 'Should have no assets so far' { + $assets.Count | Should -Be 0 } - It 'Should return the first release from the full releases list' { - $latest[0].url | Should -Be $releases[0].url - $latest[0].name | Should -Be $releases[0].name + @($zipFile, $txtFile) | ForEach-Object { + $fileName = (Get-Item -Path $_).Name + $finalLabel = "$labelBase-$fileName" + $asset = $release | New-GitHubReleaseAsset -Path $_ -Label $finalLabel + It "Can add a release asset" { + $assetId = $asset.id + + $asset.name | Should -BeExactly $fileName + $asset.label | Should -BeExactly $finalLabel + } + + It 'Should have expected type and additional properties' { + $elements = Split-GitHubUri -Uri $asset.url + $repositoryUrl = Join-GitHubUri @elements + + $asset.PSObject.TypeNames[0] | Should -Be 'GitHub.ReleaseAsset' + $asset.RepositoryUrl | Should -Be $repositoryUrl + $asset.AssetId | Should -Be $asset.id + $asset.uploader.PSObject.TypeNames[0] | Should -Be 'GitHub.User' + } + } + + $assets = @($release | Get-GitHubReleaseAsset) + It 'Should have both assets now' { + $assets.Count | Should -Be 2 + } + + It 'Should have expected type and additional properties' { + foreach ($asset in $assets) + { + $elements = Split-GitHubUri -Uri $asset.url + $repositoryUrl = Join-GitHubUri @elements + + $asset.PSObject.TypeNames[0] | Should -Be 'GitHub.ReleaseAsset' + $asset.RepositoryUrl | Should -Be $repositoryUrl + $asset.AssetId | Should -Be $asset.id + $asset.uploader.PSObject.TypeNames[0] | Should -Be 'GitHub.User' + } + } + + $txtFileName = (Get-Item -Path $txtFile).Name + $txtFileAsset = $assets | Where-Object { $_.name -eq $txtFileName } + $asset = $release | Get-GitHubReleaseAsset -Asset $txtFileAsset.id + It 'Should be able to query for a single asset' { + $asset.id | Should -Be $txtFileAsset.id } It 'Should have expected type and additional properties' { - $latest[0].PSObject.TypeNames[0] | Should -Be 'GitHub.Release' - $latest[0].html_url.StartsWith($latest[0].RepositoryUrl) | Should -BeTrue - $latest[0].id | Should -Be $latest[0].ReleaseId + $elements = Split-GitHubUri -Uri $asset.url + $repositoryUrl = Join-GitHubUri @elements + + $asset.PSObject.TypeNames[0] | Should -Be 'GitHub.ReleaseAsset' + $asset.RepositoryUrl | Should -Be $repositoryUrl + $asset.AssetId | Should -Be $asset.id + $asset.uploader.PSObject.TypeNames[0] | Should -Be 'GitHub.User' + } + + It 'Should not have the downloaded file yet' { + Test-Path -Path $saveFile -PathType Leaf | Should -BeFalse + } + + $downloadParams = @{ + Asset = $txtFileAsset.id + Path = $saveFile + } + + $null = $release | Get-GitHubReleaseAsset @downloadParams + It 'Should be able to download the asset file' { + Test-Path -Path $saveFile -PathType Leaf | Should -BeTrue + } + + It 'Should be able the same file' { + $compareParams = @{ + ReferenceObject = (Get-Content -Path $txtFile) + DifferenceObject = (Get-Content -Path $saveFile) + } + + Compare-Object @compareParams | Should -BeNullOrEmpty + } + + It 'Should fail if the download location already exists' { + { $release | Get-GitHubReleaseAsset @downloadParams } | Should -Throw + } + + It 'Should work if the download location already exists and -Force is used' { + $null = $release | Get-GitHubReleaseAsset @downloadParams -Force + + $compareParams = @{ + ReferenceObject = (Get-Content -Path $txtFile) + DifferenceObject = (Get-Content -Path $saveFile) + } + + Compare-Object @compareParams | Should -BeNullOrEmpty } } - Context 'When getting the latest releases via the pipeline' { - $latest = @(Get-GitHubRepository -OwnerName $ownerName -RepositoryName $repositoryName | - Get-GitHubRelease -Latest) + Context 'Verifying a zip file' { + BeforeAll { + $release = $repo | New-GitHubRelease -Tag $defaultTagName + + # To get access to New-TemporaryDirectory + $moduleRootPath = Split-Path -Path $PSScriptRoot -Parent + . (Join-Path -Path $moduleRootPath -ChildPath 'Helpers.ps1') + $tempPath = New-TemporaryDirectory - It 'Should return one value' { - $latest.Count | Should -Be 1 + $tempFile = New-TemporaryFile + $downloadedZipFile = "$($tempFile.FullName).zip" + Move-Item -Path $tempFile -Destination $downloadedZipFile } - It 'Should return the first release from the full releases list' { - $latest[0].url | Should -Be $releases[0].url - $latest[0].name | Should -Be $releases[0].name + AfterAll { + $release | Remove-GitHubRelease -Force + + Remove-Item -Path $tempPath -Recurse -ErrorAction SilentlyContinue -Force + if (Get-Variable -Name downloadedZipFile -ErrorAction SilentlyContinue) + { + Remove-Item -Path $downloadedZipFile -ErrorAction SilentlyContinue + } + } + + $asset = $release | New-GitHubReleaseAsset -Path $zipFile -ContentType 'application/zip' + It "Has the expected content inside" { + $result = $asset | Get-GitHubReleaseAsset -Path $downloadedZipFile -Force + Expand-Archive -Path $downloadedZipFile -DestinationPath $tempPath + + $result.FullName | Should -BeExactly $downloadedZipFile + + $txtFileName = (Get-Item -Path $txtFile).Name + $downloadedTxtFile = (Get-ChildItem -Path $tempPath -Filter $txtFileName).FullName + + $compareParams = @{ + ReferenceObject = (Get-Content -Path $txtFile) + DifferenceObject = (Get-Content -Path $downloadedTxtFile) + } + + Compare-Object @compareParams | Should -BeNullOrEmpty + } + } + } + + Describe 'Set-GitHubReleaseAsset' { + BeforeAll { + $defaultTagName = '0.2.0' + + $repo = New-GitHubRepository -RepositoryName ([Guid]::NewGuid().Guid) -AutoInit -Private + $release = New-GitHubRelease -Uri $repo.svn_url -Tag $defaultTagName + + $tempFile = New-TemporaryFile + $txtFile = "$($tempFile.FullName).txt" + Move-Item -Path $tempFile -Destination $txtFile + Out-File -FilePath $txtFile -InputObject "txt file content" -Encoding utf8 + + $label = 'mylabel' + } + + AfterAll { + $txtFile | Remove-Item | Out-Null + Remove-GitHubRepository -Uri $repo.svn_url -Confirm:$false + } + + Context 'Using parameters' { + $fileName = (Get-Item -Path $txtFile).Name + $asset = New-GitHubReleaseAsset -Uri $repo.svn_url -Release $release.id -Path $txtFile -Label $label + + It 'Has the expected initial property values' { + $asset.name | Should -BeExactly $fileName + $asset.label | Should -BeExactly $label + } + + $setParams = @{ + OwnerName = $script:ownerName + RepositoryName = $repo.name + Asset = $asset.id + } + + $updated = Set-GitHubReleaseAsset @setParams + It 'Should have the original property values' { + $updated.name | Should -BeExactly $fileName + $updated.label | Should -BeExactly $label } It 'Should have expected type and additional properties' { - $latest[0].PSObject.TypeNames[0] | Should -Be 'GitHub.Release' - $latest[0].html_url.StartsWith($latest[0].RepositoryUrl) | Should -BeTrue - $latest[0].id | Should -Be $latest[0].ReleaseId + $elements = Split-GitHubUri -Uri $updated.url + $repositoryUrl = Join-GitHubUri @elements + + $updated.PSObject.TypeNames[0] | Should -Be 'GitHub.ReleaseAsset' + $updated.RepositoryUrl | Should -Be $repositoryUrl + $updated.AssetId | Should -Be $updated.id + $updated.uploader.PSObject.TypeNames[0] | Should -Be 'GitHub.User' } - $latestAgain = @($latest | Get-GitHubRelease) - It 'Should be the same release' { - $latest[0].ReleaseId | Should -Be $latestAgain[0].ReleaseId + $updatedFileName = 'updated1.txt' + $setParams = @{ + OwnerName = $script:ownerName + RepositoryName = $repo.name + Asset = $asset.id + Name = $updatedFileName + } + + $updated = Set-GitHubReleaseAsset @setParams + It 'Should have a new name and the original label' { + $updated.name | Should -BeExactly $updatedFileName + $updated.label | Should -BeExactly $label + } + + $updatedLabel = 'updatedLabel2' + $setParams = @{ + OwnerName = $script:ownerName + RepositoryName = $repo.name + Asset = $asset.id + Label = $updatedLabel + } + + $updated = Set-GitHubReleaseAsset @setParams + It 'Should have the current name and a new label' { + $updated.name | Should -BeExactly $updatedFileName + $updated.label | Should -BeExactly $updatedLabel + } + + $updatedFileName = 'updated3parameter.txt' + $updatedLabel = 'updatedLabel3parameter' + $setParams = @{ + OwnerName = $script:ownerName + RepositoryName = $repo.name + Asset = $asset.id + Name = $updatedFileName + Label = $updatedLabel + } + + $updated = Set-GitHubReleaseAsset @setParams + It 'Should have a new name and a new label' { + $updated.name | Should -BeExactly $updatedFileName + $updated.label | Should -BeExactly $updatedLabel } } - Context 'When getting a specific release' { - $specificIndex = 5 - $specific = @(Get-GitHubRelease -OwnerName $ownerName -RepositoryName $repositoryName -ReleaseId $releases[$specificIndex].id) + Context 'Using the repo on the pipeline' { + $fileName = (Get-Item -Path $txtFile).Name + $asset = $repo | New-GitHubReleaseAsset -Release $release.id -Path $txtFile -Label $label + + It 'Has the expected initial property values' { + $asset.name | Should -BeExactly $fileName + $asset.label | Should -BeExactly $label + } + + $setParams = @{ + Asset = $asset.id + } + + $updated = $repo | Set-GitHubReleaseAsset @setParams + It 'Should have the original property values' { + $updated.name | Should -BeExactly $fileName + $updated.label | Should -BeExactly $label + } + + It 'Should have expected type and additional properties' { + $elements = Split-GitHubUri -Uri $updated.url + $repositoryUrl = Join-GitHubUri @elements + + $updated.PSObject.TypeNames[0] | Should -Be 'GitHub.ReleaseAsset' + $updated.RepositoryUrl | Should -Be $repositoryUrl + $updated.AssetId | Should -Be $updated.id + $updated.uploader.PSObject.TypeNames[0] | Should -Be 'GitHub.User' + } + + $updatedFileName = 'updated1.txt' + $setParams = @{ + Asset = $asset.id + Name = $updatedFileName + } + + $updated = $repo | Set-GitHubReleaseAsset @setParams + It 'Should have a new name and the original label' { + $updated.name | Should -BeExactly $updatedFileName + $updated.label | Should -BeExactly $label + } + + $updatedLabel = 'updatedLabel2' + $setParams = @{ + Asset = $asset.id + Label = $updatedLabel + } + + $updated = $repo | Set-GitHubReleaseAsset @setParams + It 'Should have the current name and a new label' { + $updated.name | Should -BeExactly $updatedFileName + $updated.label | Should -BeExactly $updatedLabel + } + + $updatedFileName = 'updated3repo.txt' + $updatedLabel = 'updatedLabel3repo' + $setParams = @{ + Asset = $asset.id + Name = $updatedFileName + Label = $updatedLabel + } + + $updated = $repo | Set-GitHubReleaseAsset @setParams + It 'Should have a new name and a new label' { + $updated.name | Should -BeExactly $updatedFileName + $updated.label | Should -BeExactly $updatedLabel + } + } + + Context 'Using the release on the pipeline' { + $fileName = (Get-Item -Path $txtFile).Name + $asset = $release | New-GitHubReleaseAsset -Path $txtFile -Label $label + + It 'Has the expected initial property values' { + $asset.name | Should -BeExactly $fileName + $asset.label | Should -BeExactly $label + } - It 'Should return one value' { - $specific.Count | Should -Be 1 + $setParams = @{ + Asset = $asset.id } - It 'Should return the correct release' { - $specific.name | Should -Be $releases[$specificIndex].name + $updated = $release | Set-GitHubReleaseAsset @setParams + It 'Should have the original property values' { + $updated.name | Should -BeExactly $fileName + $updated.label | Should -BeExactly $label } It 'Should have expected type and additional properties' { - $specific[0].PSObject.TypeNames[0] | Should -Be 'GitHub.Release' - $specific[0].html_url.StartsWith($specific[0].RepositoryUrl) | Should -BeTrue - $specific[0].id | Should -Be $specific[0].ReleaseId + $elements = Split-GitHubUri -Uri $updated.url + $repositoryUrl = Join-GitHubUri @elements + + $updated.PSObject.TypeNames[0] | Should -Be 'GitHub.ReleaseAsset' + $updated.RepositoryUrl | Should -Be $repositoryUrl + $updated.AssetId | Should -Be $updated.id + $updated.uploader.PSObject.TypeNames[0] | Should -Be 'GitHub.User' + } + + $updatedFileName = 'updated1.txt' + $setParams = @{ + Asset = $asset.id + Name = $updatedFileName + } + + $updated = $release | Set-GitHubReleaseAsset @setParams + It 'Should have a new name and the original label' { + $updated.name | Should -BeExactly $updatedFileName + $updated.label | Should -BeExactly $label + } + + $updatedLabel = 'updatedLabel2' + $setParams = @{ + Asset = $asset.id + Label = $updatedLabel + } + + $updated = $release | Set-GitHubReleaseAsset @setParams + It 'Should have the current name and a new label' { + $updated.name | Should -BeExactly $updatedFileName + $updated.label | Should -BeExactly $updatedLabel + } + + $updatedFileName = 'updated3release.txt' + $updatedLabel = 'updatedLabel3release' + $setParams = @{ + Asset = $asset.id + Name = $updatedFileName + Label = $updatedLabel + } + + $updated = $release | Set-GitHubReleaseAsset @setParams + It 'Should have a new name and a new label' { + $updated.name | Should -BeExactly $updatedFileName + $updated.label | Should -BeExactly $updatedLabel } } - Context 'When getting a tagged release' { - $taggedIndex = 8 - $tagged = @(Get-GitHubRelease -OwnerName $ownerName -RepositoryName $repositoryName -Tag $releases[$taggedIndex].tag_name) + Context 'Using the asset on the pipeline' { + $fileName = (Get-Item -Path $txtFile).Name + $asset = $release | New-GitHubReleaseAsset -Path $txtFile -Label $label - It 'Should return one value' { - $tagged.Count | Should -Be 1 + It 'Has the expected initial property values' { + $asset.name | Should -BeExactly $fileName + $asset.label | Should -BeExactly $label } - It 'Should return the correct release' { - $tagged.name | Should -Be $releases[$taggedIndex].name + $updated = $asset | Set-GitHubReleaseAsset + It 'Should have the original property values' { + $updated.name | Should -BeExactly $fileName + $updated.label | Should -BeExactly $label } It 'Should have expected type and additional properties' { - $tagged[0].PSObject.TypeNames[0] | Should -Be 'GitHub.Release' - $tagged[0].html_url.StartsWith($tagged[0].RepositoryUrl) | Should -BeTrue - $tagged[0].id | Should -Be $tagged[0].ReleaseId + $elements = Split-GitHubUri -Uri $updated.url + $repositoryUrl = Join-GitHubUri @elements + + $updated.PSObject.TypeNames[0] | Should -Be 'GitHub.ReleaseAsset' + $updated.RepositoryUrl | Should -Be $repositoryUrl + $updated.AssetId | Should -Be $updated.id + $updated.uploader.PSObject.TypeNames[0] | Should -Be 'GitHub.User' + } + + $updatedFileName = 'updated1.txt' + $updated = $asset | Set-GitHubReleaseAsset -Name $updatedFileName + It 'Should have a new name and the original label' { + $updated.name | Should -BeExactly $updatedFileName + $updated.label | Should -BeExactly $label + } + + $updatedLabel = 'updatedLabel2' + $updated = $asset | Set-GitHubReleaseAsset -Label $updatedLabel + It 'Should have the current name and a new label' { + $updated.name | Should -BeExactly $updatedFileName + $updated.label | Should -BeExactly $updatedLabel + } + + $updatedFileName = 'updated3asset.txt' + $updatedLabel = 'updatedLabel3asset' + $updated = $asset | Set-GitHubReleaseAsset -Name $updatedFileName -Label $updatedLabel + It 'Should have a new name and a new label' { + $updated.name | Should -BeExactly $updatedFileName + $updated.label | Should -BeExactly $updatedLabel } } } - Describe 'Getting releases from default owner/repository' { - $originalOwnerName = Get-GitHubConfiguration -Name DefaultOwnerName - $originalRepositoryName = Get-GitHubConfiguration -Name DefaultRepositoryName + Describe 'Remove-GitHubReleaseAsset' { + BeforeAll { + $defaultTagName = '0.2.0' - try { - Set-GitHubConfiguration -DefaultOwnerName "dotnet" - Set-GitHubConfiguration -DefaultRepositoryName "core" - $releases = @(Get-GitHubRelease) + $repo = New-GitHubRepository -RepositoryName ([Guid]::NewGuid().Guid) -AutoInit -Private + $release = New-GitHubRelease -Uri $repo.svn_url -Tag $defaultTagName - Context 'When getting all releases' { - It 'Should return multiple releases' { - $releases.Count | Should -BeGreaterThan 1 - } + $tempFile = New-TemporaryFile + $txtFile = "$($tempFile.FullName).txt" + Move-Item -Path $tempFile -Destination $txtFile + Out-File -FilePath $txtFile -InputObject "txt file content" -Encoding utf8 + } + + AfterAll { + $txtFile | Remove-Item | Out-Null + Remove-GitHubRepository -Uri $repo.svn_url -Confirm:$false + } + + Context 'Using parameters' { + $asset = New-GitHubReleaseAsset -Uri $repo.svn_url -Release $release.id -Path $txtFile + + $params = @{ + OwnerName = $script:ownerName + RepositoryName = $repo.name + Asset = $asset.id + Force = $true + } + + Remove-GitHubReleaseAsset @params + It 'Should be successfully deleted' { + { Remove-GitHubReleaseAsset @params } | Should -Throw + } + } + + Context 'Using the repo on the pipeline' { + $asset = New-GitHubReleaseAsset -Uri $repo.svn_url -Release $release.id -Path $txtFile + + $repo | Remove-GitHubReleaseAsset -Asset $asset.id -Force + It 'Should be successfully deleted' { + { $repo | Remove-GitHubReleaseAsset -Asset $asset.id -Force } | Should -Throw + } + } + + Context 'Using the release on the pipeline' { + $asset = New-GitHubReleaseAsset -Uri $repo.svn_url -Release $release.id -Path $txtFile + + $release | Remove-GitHubReleaseAsset -Asset $asset.id -Force + It 'Should be successfully deleted' { + { $release | Remove-GitHubReleaseAsset -Asset $asset.id -Force } | Should -Throw + } + } + + Context 'Using the asset on the pipeline' { + $asset = New-GitHubReleaseAsset -Uri $repo.svn_url -Release $release.id -Path $txtFile + + $asset | Remove-GitHubReleaseAsset -Force + It 'Should be successfully deleted' { + { $asset | Remove-GitHubReleaseAsset -Force } | Should -Throw } - } finally { - Set-GitHubConfiguration -DefaultOwnerName $originalOwnerName - Set-GitHubConfiguration -DefaultRepositoryName $originalRepositoryName } } } @@ -145,4 +1100,4 @@ finally Restore-GitHubConfiguration -Path $script:originalConfigFile $script:originalConfigFile = $null } -} +} \ No newline at end of file diff --git a/USAGE.md b/USAGE.md index bdd04488..8e93a1c5 100644 --- a/USAGE.md +++ b/USAGE.md @@ -86,6 +86,17 @@ * [Add an existing issue as a card to a column](#add-an-existing-issue-as-a-card-to-a-column) * [Move a card to be after a certain card in the same column](Move-a-card-to-be-after-a-certain-card-in-the-same-column) * [Move a card to the bottom of another column](Move-a-card-to-the-bottom-of-another-column) + * [Releases](#Releases) + * [Get releases for a repository](#get-releases-for-a-repository) + * [Get an individual release for a repository](#get-an-individual-release-for-a-repository) + * [Create a new release](#create-a-new-release) + * [Update a release](#update-a-release) + * [Remove a release](#remove-a-release) + * [List assets for a release](#list-assets-for-a-release) + * [Download a release asset](#download-a-release-asset) + * [Create a release asset](#create-a-release-asset) + * [Update a release asset](#update-a-release-asset) + * [Remove a release asset](#remove-a-release-asset) * [Advanced](#advanced) * [Migrating blog comments to GitHub issues](#migrating-blog-comments-to-github-issues) @@ -728,6 +739,140 @@ Move-GitHubProjectCard -Card 4 -ColumnId 6 -Bottom ---------- +### Releases + +#### Get releases for a repository +```powershell +Get-GitHubRelease -OwnerName PowerShell -RepositoryName PowerShell +``` + +or with pipelining... + +```powershell +Get-GitHubRepository -OwnerName PowerShell -RepositoryName PowerShell | + Get-GitHubRelease +``` + +#### Get an individual release for a repository +```powershell +Get-GitHubRelease -OwnerName PowerShell -RepositoryName PowerShell | + Select-Object -First 1 | + Get-GitHubRelease +``` + +#### Create a new release +```powershell +New-GitHubRelease -OwnerName PowerShell -RepositoryName PowerShell -Tag 11.0 +``` + +or with pipelining... + +```powershell +Get-GitHubRepository -OwnerName PowerShell -RepositoryName PowerShell | + New-GitHubRelease -Tag 11.0 +``` + +#### Update a release +```powershell +Set-GitHubRelease -OwnerName PowerShell -RepositoryName PowerShell -Release 123456 -Body 'Updated body' +``` + +or with pipelining... + +```powershell +$repo | Set-GitHubRelease -Release 123456 -Body 'Updated body' + +# or + +$release | Set-GitHubRelease -Body 'Updated body' +``` + +#### Remove a release +```powershell +Remove-GitHubRelease -OwnerName PowerShell -RepositoryName PowerShell -Release 123456 -Force +``` + +or with pipelining... + +```powershell +$repo | Remove-GitHubRelease -Release 123456 -Force + +# or + +$release | Remove-GitHubRelease -Force +``` + +#### List assets for a release +```powershell +Get-GitHubReleaseAsset -OwnerName PowerShell -RepositoryName PowerShell -Release 123456 +``` + +or with pipelining... + +```powershell +$repo | Get-GitHubReleaseAsset -Release 123456 + +# or + +$release | Get-GitHubReleaseAsset +``` + +#### Download a release asset +```powershell +Get-GitHubReleaseAsset -OwnerName PowerShell -RepositoryName PowerShell -Asset 123456 -Path 'c:\downloads\asset' +``` + +or with pipelining... + +```powershell +# Downloads the first asset of the latest release from PowerShell\PowerShell to the file located +# at c:\downloads\asset +Get-GitHubRelease -OwnerName PowerShell -RepositoryName PowerShell -Latest | + Get-GitHubReleaseAsset | + Select-Object -First 1 | + Get-GitHubReleaseAsset -Path 'c:\downloads\asset' +``` + +#### Create a release asset +```powershell +New-GitHubReleaseAsset -OwnerName PowerShell -RepositoryName PowerShell -Release 123456 -Path 'c:\foo.zip' +``` + +or with pipelining... + +```powershell +$release | New-GitHubReleaseAsset -Path 'c:\foo.zip' + +# or + +@('c:\foo.zip', 'c:\bar.txt') | + New-GitHubReleaseAsset -OwnerName PowerShell -RepositoryName PowerShell -Release 123456 +``` + +#### Update a release asset +```powershell +Set-GitHubReleaseAsset -OwnerName PowerShell -RepositoryName PowerShell -Asset 123456 -Name 'newFileName.zip' +``` + +or with pipelining... + +```powershell +$asset | Set-GitHubReleaseAsset -Name 'newFileName.zip' +``` + +#### Remove a release asset +```powershell +Remove-GitHubReleaseAsset -OwnerName PowerShell -RepositoryName PowerShell -Asset 123456 -Force +``` + +or with pipelining... + +```powershell +$asset | Remove-GitHubReleaseAsset -force +``` + +---------- + ### Advanced #### Migrating blog comments to GitHub issues From 92c4aa8b3a0142752e68a50af73ac276db0c1ff6 Mon Sep 17 00:00:00 2001 From: Howard Wolosky Date: Mon, 20 Jul 2020 15:07:58 -0700 Subject: [PATCH 58/60] Add support for gists (#172) This completes the required work to support the full set of API's around gists. It adds the following functions: * `Get-GitHubGist` * `Remove-GitHubGist` * `Copy-GitHubGist` (aka. `Fork-GitHubGist`) * `Add-GitHubGistStar` * `Remove-GitHubGistStar` * `Set-GitHubGistStar` (just a wrapper around `Add/Remove-GitHubGistStar` * `Test-GitHubGistStar` * `New-GitHubGist` * `Set-GitHubGist` * `Rename-GitHubGistFile` (exposed helper function) * `Remove-GitHubGistFile` (exposed helper function) * `Set-GitHubGistFile` (exposed helper function, also known as `Add-GitHubGistFile`) * `Get-GitHubGistComment` * `Set-GitHubGistComment` * `New-GitHubGistComment` * `Remove-GitHubGistComment` This also adds formatters for all newly introduced types: `GitHub.Gist`, `GitHub.GistCommit`, `GitHub.GistDetail`, and `GitHub.GistFork`. Positional Binding has been set as `false`, and `Position` attributes added to the functions' mandatory parameters. References: [GitHub Gist](https://developer.github.com/v3/gists/) [GitHub Gist Comments](https://developer.github.com/v3/gists/comments/) Fixes #32 --- Formatters/GitHubGistComments.Format.ps1xml | 54 + Formatters/GitHubGists.Format.ps1xml | 241 +++ GitHubCore.ps1 | 9 + GitHubGistComments.ps1 | 495 +++++ GitHubGists.ps1 | 1884 +++++++++++++++++++ PowerShellForGitHub.psd1 | 28 + Tests/GitHubGistComments.tests.ps1 | 335 ++++ Tests/GitHubGists.tests.ps1 | 1193 ++++++++++++ USAGE.md | 175 ++ 9 files changed, 4414 insertions(+) create mode 100644 Formatters/GitHubGistComments.Format.ps1xml create mode 100644 Formatters/GitHubGists.Format.ps1xml create mode 100644 GitHubGistComments.ps1 create mode 100644 GitHubGists.ps1 create mode 100644 Tests/GitHubGistComments.tests.ps1 create mode 100644 Tests/GitHubGists.tests.ps1 diff --git a/Formatters/GitHubGistComments.Format.ps1xml b/Formatters/GitHubGistComments.Format.ps1xml new file mode 100644 index 00000000..b2035fca --- /dev/null +++ b/Formatters/GitHubGistComments.Format.ps1xml @@ -0,0 +1,54 @@ + + + + + + GitHub.GistComment + + GitHub.GistComment + + + + + + + + + + + + + + + + + + + + + + + + id + + + + $_.user.login + + + + body + + + created_at + + + updated_at + + + + + + + + diff --git a/Formatters/GitHubGists.Format.ps1xml b/Formatters/GitHubGists.Format.ps1xml new file mode 100644 index 00000000..3e02fa85 --- /dev/null +++ b/Formatters/GitHubGists.Format.ps1xml @@ -0,0 +1,241 @@ + + + + + + GitHub.Gist + + GitHub.Gist + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + id + + + + $_.owner.login + + + + description + + + public + + + + ($_.files | + Get-Member -MemberType NoteProperty | + Select-Object -ExpandProperty Name) -join ', ' + + + + created_at + + + updated_at + + + + + + + + + GitHub.GistCommit + + GitHub.GistCommit + + + + + + + + + + + + + + + + + + + + + GistId + + + version + + + + ($_.user.login) + + + + committed_at + + + + + + + + + GitHub.GistDetail + + GitHub.GistDetail + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + id + + + + ($_.owner.login) + + + + description + + + public + + + + ($_.files | + Get-Member -MemberType NoteProperty | + Select-Object -ExpandProperty Name) -join ', ' + + + + + ($_.forks.Count) + + + + created_at + + + updated_at + + + + + + + + + GitHub.GistFork + + GitHub.GistFork + + + + + + + + + + + + + + + + + + + + + id + + + + # There appears to be a bug in the GitHub API. + # Documentation says that the property should alway be "user", + # but it switches between "owner" and "user" depending on if it's a property + # of a gist, or the direct result from a gist forks query. + # https://github.community/t/gist-api-v3-documentation-incorrect-for-forks/122545 + if ($null -eq $_.user) + { + $_.owner.login + } + else + { + $_.user.login + } + + + + created_at + + + updated_at + + + + + + + + diff --git a/GitHubCore.ps1 b/GitHubCore.ps1 index 02c887fd..4ea6fabd 100644 --- a/GitHubCore.ps1 +++ b/GitHubCore.ps1 @@ -329,6 +329,15 @@ function Invoke-GHRestMethod $finalResult = $finalResult | ConvertFrom-Json } } + catch [InvalidOperationException] + { + # In some cases, the returned data might have two different keys of the same characters + # but different casing (this can happen with gists with two files named 'a.txt' and 'A.txt'). + # PowerShell 6 introduced the -AsHashtable switch to work around this issue, but this + # module wants to be compatible down to PowerShell 4, so we're unable to use that feature. + Write-Log -Message 'The returned object likely contains keys that differ only in casing. Unable to convert to an object. Returning the raw JSON as a fallback.' -Level Warning + $finalResult = $finalResult + } catch [ArgumentException] { # The content must not be JSON (which is a legitimate situation). diff --git a/GitHubGistComments.ps1 b/GitHubGistComments.ps1 new file mode 100644 index 00000000..b8d43b31 --- /dev/null +++ b/GitHubGistComments.ps1 @@ -0,0 +1,495 @@ +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. + +@{ + GitHubGistCommentTypeName = 'GitHub.GistComment' + }.GetEnumerator() | ForEach-Object { + Set-Variable -Scope Script -Option ReadOnly -Name $_.Key -Value $_.Value + } + +filter Get-GitHubGistComment +{ +<# + .SYNOPSIS + Retrieves comments for a specific gist from GitHub. + + .DESCRIPTION + Retrieves comments for a specific gist from GitHub. + + The Git repo for this module can be found here: http://aka.ms/PowerShellForGitHub + + .PARAMETER Gist + The ID of the specific gist that you wish to retrieve the comments for. + + .PARAMETER Comment + The ID of the specific comment on the gist that you wish to retrieve. + + .PARAMETER MediaType + The format in which the API will return the body of the comment. + + Raw - Return the raw markdown body. Response will include body. This is the default if you do not pass any specific media type. + Text - Return a text only representation of the markdown body. Response will include body_text. + Html - Return HTML rendered from the body's markdown. Response will include body_html. + Full - Return raw, text and HTML representations. Response will include body, body_text, and body_html. + + .PARAMETER AccessToken + If provided, this will be used as the AccessToken for authentication with the + REST Api. Otherwise, will attempt to use the configured value or will run unauthenticated. + + .PARAMETER NoStatus + If this switch is specified, long-running commands will run on the main thread + with no commandline status update. When not specified, those commands run in + the background, enabling the command prompt to provide status information. + If not supplied here, the DefaultNoStatus configuration property value will be used. + + .INPUTS + GitHub.Gist + GitHub.GistComment + GitHub.GistCommit + GitHub.GistFork + GitHub.GistSummary + + .OUTPUTS + GitHub.GistComment + + .EXAMPLE + Get-GitHubGistComment -Gist 6cad326836d38bd3a7ae + + Gets all comments on octocat's "hello_world.rb" gist. + + .EXAMPLE + Get-GitHubGistComment -Gist 6cad326836d38bd3a7ae -Comment 1507813 + + Gets comment 1507813 from octocat's "hello_world.rb" gist. +#> + [CmdletBinding(PositionalBinding = $false)] + [OutputType({$script:GitHubGistCommentTypeName})] + param( + [Parameter( + Mandatory, + ValueFromPipelineByPropertyName, + Position = 1)] + [Alias('GistId')] + [string] $Gist, + + [Parameter(ValueFromPipelineByPropertyName)] + [Alias('GistCommentId')] + [ValidateNotNullOrEmpty()] + [int64] $Comment, + + [ValidateSet('Raw', 'Text', 'Html', 'Full')] + [string] $MediaType = 'Full', + + [string] $AccessToken, + + [switch] $NoStatus + ) + + Write-InvocationLog -Invocation $MyInvocation + + $telemetryProperties = @{} + + $uriFragment = [String]::Empty + $description = [String]::Empty + + if ($PSBoundParameters.ContainsKey('Comment')) + { + $telemetryProperties['SpecifiedComment'] = $true + + $uriFragment = "gists/$Gist/comments/$Comment" + $description = "Getting comment $Comment for gist $Gist" + } + else + { + $uriFragment = "gists/$Gist/comments" + $description = "Getting comments for gist $Gist" + } + + $params = @{ + 'UriFragment' = $uriFragment + 'Description' = $description + 'AccessToken' = $AccessToken + 'AcceptHeader' = (Get-MediaAcceptHeader -MediaType $MediaType -AsJson) + 'TelemetryEventName' = $MyInvocation.MyCommand.Name + 'TelemetryProperties' = $telemetryProperties + 'NoStatus' = (Resolve-ParameterWithDefaultConfigurationValue -BoundParameters $PSBoundParameters -Name NoStatus -ConfigValueName DefaultNoStatus) + } + + return (Invoke-GHRestMethodMultipleResult @params | Add-GitHubGistCommentAdditionalProperties) +} + +filter Remove-GitHubGistComment +{ +<# + .SYNOPSIS + Removes/deletes a comment from a gist on GitHub. + + .DESCRIPTION + Removes/deletes a comment from a gist on GitHub. + + The Git repo for this module can be found here: http://aka.ms/PowerShellForGitHub + + .PARAMETER Gist + The ID of the specific gist that you wish to remove the comment from. + + .PARAMETER Comment + The ID of the comment to remove from the gist. + + .PARAMETER Force + If this switch is specified, you will not be prompted for confirmation of command execution. + + .PARAMETER AccessToken + If provided, this will be used as the AccessToken for authentication with the + REST Api. Otherwise, will attempt to use the configured value or will run unauthenticated. + + .PARAMETER NoStatus + If this switch is specified, long-running commands will run on the main thread + with no commandline status update. When not specified, those commands run in + the background, enabling the command prompt to provide status information. + If not supplied here, the DefaultNoStatus configuration property value will be used. + + .INPUTS + GitHub.Gist + GitHub.GistComment + GitHub.GistCommit + GitHub.GistFork + GitHub.GistSummary + + .EXAMPLE + Remove-GitHubGist -Gist 6cad326836d38bd3a7ae -Comment 12324567 + + Removes the specified comment from octocat's "hello_world.rb" gist + (assuming you have permission). + + .EXAMPLE + Remove-GitHubGist -Gist 6cad326836d38bd3a7ae -Comment 12324567 -Confirm:$false + + Removes the specified comment from octocat's "hello_world.rb" gist + (assuming you have permission). + Will not prompt for confirmation, as -Confirm:$false was specified. + + .EXAMPLE + Remove-GitHubGist -Gist 6cad326836d38bd3a7ae -Comment 12324567 -Force + + Removes the specified comment from octocat's "hello_world.rb" gist + (assuming you have permission). + Will not prompt for confirmation, as -Force was specified. +#> + [CmdletBinding( + SupportsShouldProcess, + PositionalBinding = $false, + ConfirmImpact="High")] + [Alias('Delete-GitHubGist')] + param( + [Parameter( + Mandatory, + ValueFromPipelineByPropertyName, + Position = 1)] + [Alias('GistId')] + [ValidateNotNullOrEmpty()] + [string] $Gist, + + [Parameter( + Mandatory, + ValueFromPipelineByPropertyName, + Position = 2)] + [Alias('GistCommentId')] + [ValidateNotNullOrEmpty()] + [int64] $Comment, + + [switch] $Force, + + [string] $AccessToken, + + [switch] $NoStatus + ) + + Write-InvocationLog -Invocation $MyInvocation + + if ($Force -and (-not $Confirm)) + { + $ConfirmPreference = 'None' + } + + if (-not $PSCmdlet.ShouldProcess($Comment, "Delete comment from gist $Gist")) + { + return + } + + $telemetryProperties = @{} + $params = @{ + 'UriFragment' = "gists/$Gist/comments/$Comment" + 'Method' = 'Delete' + 'Description' = "Removing comment $Comment from gist $Gist" + 'AccessToken' = $AccessToken + 'TelemetryEventName' = $MyInvocation.MyCommand.Name + 'TelemetryProperties' = $telemetryProperties + 'NoStatus' = (Resolve-ParameterWithDefaultConfigurationValue -BoundParameters $PSBoundParameters -Name NoStatus -ConfigValueName DefaultNoStatus) + } + + return Invoke-GHRestMethod @params +} + +filter New-GitHubGistComment +{ +<# + .SYNOPSIS + Creates a new comment on the specified gist from GitHub. + + .DESCRIPTION + Creates a new comment on the specified gist from GitHub. + + The Git repo for this module can be found here: http://aka.ms/PowerShellForGitHub + + .PARAMETER Gist + The ID of the specific gist that you wish to add the comment to. + + .PARAMETER Body + The body of the comment that you wish to leave on the gist. + + .PARAMETER AccessToken + If provided, this will be used as the AccessToken for authentication with the + REST Api. Otherwise, will attempt to use the configured value or will run unauthenticated. + + .PARAMETER NoStatus + If this switch is specified, long-running commands will run on the main thread + with no commandline status update. When not specified, those commands run in + the background, enabling the command prompt to provide status information. + If not supplied here, the DefaultNoStatus configuration property value will be used. + + .INPUTS + GitHub.Gist + GitHub.GistComment + GitHub.GistCommit + GitHub.GistFork + GitHub.GistSummary + + .OUTPUTS + GitHub.GistComment + + .EXAMPLE + New-GitHubGistComment -Gist 6cad326836d38bd3a7ae -Body 'Hello World' + + Adds a new comment of "Hello World" to octocat's "hello_world.rb" gist. +#> + [CmdletBinding( + SupportsShouldProcess, + PositionalBinding = $false)] + [OutputType({$script:GitHubGistCommentTypeName})] + param( + [Parameter( + Mandatory, + ValueFromPipelineByPropertyName, + Position = 1)] + [Alias('GistId')] + [ValidateNotNullOrEmpty()] + [string] $Gist, + + [Parameter( + Mandatory, + Position = 2)] + [ValidateNotNullOrEmpty()] + [string] $Body, + + [string] $AccessToken, + + [switch] $NoStatus + ) + + Write-InvocationLog -Invocation $MyInvocation + + $hashBody = @{ + 'body' = $Body + } + + if (-not $PSCmdlet.ShouldProcess($Gist, "Create new comment for gist")) + { + return + } + + $telemetryProperties = @{} + $params = @{ + 'UriFragment' = "gists/$Gist/comments" + 'Body' = (ConvertTo-Json -InputObject $hashBody) + 'Method' = 'Post' + 'Description' = "Creating new comment on gist $Gist" + 'AccessToken' = $AccessToken + 'TelemetryEventName' = $MyInvocation.MyCommand.Name + 'TelemetryProperties' = $telemetryProperties + 'NoStatus' = (Resolve-ParameterWithDefaultConfigurationValue -BoundParameters $PSBoundParameters -Name NoStatus -ConfigValueName DefaultNoStatus) + } + + return (Invoke-GHRestMethod @params | Add-GitHubGistCommentAdditionalProperties) +} + +filter Set-GitHubGistComment +{ + <# + .SYNOPSIS + Edits a comment on the specified gist from GitHub. + + .DESCRIPTION + Edits a comment on the specified gist from GitHub. + + The Git repo for this module can be found here: http://aka.ms/PowerShellForGitHub + + .PARAMETER Gist + The ID of the gist that the comment is on. + + .PARAMETER Comment + The ID of the comment that you wish to edit. + + .PARAMETER Body + The new text of the comment that you wish to leave on the gist. + + .PARAMETER AccessToken + If provided, this will be used as the AccessToken for authentication with the + REST Api. Otherwise, will attempt to use the configured value or will run unauthenticated. + + .PARAMETER NoStatus + If this switch is specified, long-running commands will run on the main thread + with no commandline status update. When not specified, those commands run in + the background, enabling the command prompt to provide status information. + If not supplied here, the DefaultNoStatus configuration property value will be used. + + .INPUTS + GitHub.Gist + GitHub.GistComment + GitHub.GistCommit + GitHub.GistFork + GitHub.GistSummary + + .OUTPUTS + GitHub.GistComment + + .EXAMPLE + New-GitHubGistComment -Gist 6cad326836d38bd3a7ae -Comment 1232456 -Body 'Hello World' + + Updates the body of the comment with ID 1232456 octocat's "hello_world.rb" gist to be + "Hello World". +#> + [CmdletBinding( + SupportsShouldProcess, + PositionalBinding = $false)] + [OutputType({$script:GitHubGistCommentTypeName})] + param( + [Parameter( + Mandatory, + ValueFromPipelineByPropertyName, + Position = 1)] + [Alias('GistId')] + [ValidateNotNullOrEmpty()] + [string] $Gist, + + [Parameter( + Mandatory, + ValueFromPipelineByPropertyName, + Position = 2)] + [Alias('GistCommentId')] + [ValidateNotNullOrEmpty()] + [int64] $Comment, + + [Parameter( + Mandatory, + Position = 3)] + [ValidateNotNullOrEmpty()] + [string] $Body, + + [string] $AccessToken, + + [switch] $NoStatus + ) + + Write-InvocationLog -Invocation $MyInvocation + + $hashBody = @{ + 'body' = $Body + } + + if (-not $PSCmdlet.ShouldProcess($Comment, "Update gist comment on gist $Gist")) + { + return + } + + $telemetryProperties = @{} + $params = @{ + 'UriFragment' = "gists/$Gist/comments/$Comment" + 'Body' = (ConvertTo-Json -InputObject $hashBody) + 'Method' = 'Patch' + 'Description' = "Creating new comment on gist $Gist" + 'AccessToken' = $AccessToken + 'TelemetryEventName' = $MyInvocation.MyCommand.Name + 'TelemetryProperties' = $telemetryProperties + 'NoStatus' = (Resolve-ParameterWithDefaultConfigurationValue -BoundParameters $PSBoundParameters -Name NoStatus -ConfigValueName DefaultNoStatus) + } + + return (Invoke-GHRestMethod @params | Add-GitHubGistCommentAdditionalProperties) +} + +filter Add-GitHubGistCommentAdditionalProperties +{ +<# + .SYNOPSIS + Adds type name and additional properties to ease pipelining to GitHub Gist Comment objects. + + .PARAMETER InputObject + The GitHub object to add additional properties to. + + .PARAMETER TypeName + The type that should be assigned to the object. + + .PARAMETER GistId + The ID of the gist that the comment is for. + + .INPUTS + [PSCustomObject] + + .OUTPUTS + GitHub.GistComment +#> + [CmdletBinding()] + [OutputType({$script:GitHubGisCommentTypeName})] + [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSUseSingularNouns", "", Justification="Internal helper that is definitely adding more than one property.")] + param( + [Parameter( + Mandatory, + ValueFromPipeline)] + [AllowNull()] + [AllowEmptyCollection()] + [PSCustomObject[]] $InputObject, + + [ValidateNotNullOrEmpty()] + [string] $TypeName = $script:GitHubGistCommentTypeName + ) + + foreach ($item in $InputObject) + { + $item.PSObject.TypeNames.Insert(0, $TypeName) + + if (-not (Get-GitHubConfiguration -Name DisablePipelineSupport)) + { + $hostName = $(Get-GitHubConfiguration -Name 'ApiHostName') + if ($item.url -match "^https?://(?:www\.|api\.|)$hostName/gists/([^/]+)/comments/(.+)$") + { + $gistId = $Matches[1] + $commentId = $Matches[2] + + if ($commentId -ne $item.id) + { + $message = "The gist comment url no longer follows the expected pattern. Please contact the PowerShellForGitHubTeam: $item.url" + Write-Log -Message $message -Level Warning + } + } + + Add-Member -InputObject $item -Name 'GistCommentId' -Value $item.id -MemberType NoteProperty -Force + Add-Member -InputObject $item -Name 'GistId' -Value $gistId -MemberType NoteProperty -Force + + if ($null -ne $item.user) + { + $null = Add-GitHubUserAdditionalProperties -InputObject $item.user + } + } + + Write-Output $item + } +} diff --git a/GitHubGists.ps1 b/GitHubGists.ps1 new file mode 100644 index 00000000..d357bc49 --- /dev/null +++ b/GitHubGists.ps1 @@ -0,0 +1,1884 @@ +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. + +@{ + GitHubGistTypeName = 'GitHub.Gist' + GitHubGistCommitTypeName = 'GitHub.GistCommit' + GitHubGistForkTypeName = 'GitHub.GistFork' + GitHubGistSummaryTypeName = 'GitHub.GistSummary' + }.GetEnumerator() | ForEach-Object { + Set-Variable -Scope Script -Option ReadOnly -Name $_.Key -Value $_.Value + } + +filter Get-GitHubGist +{ +<# + .SYNOPSIS + Retrieves gist information from GitHub. + + .DESCRIPTION + Retrieves gist information from GitHub. + + The Git repo for this module can be found here: http://aka.ms/PowerShellForGitHub + + .PARAMETER Gist + The ID of the specific gist that you wish to retrieve. + + .PARAMETER Sha + The specific revision of the gist that you wish to retrieve. + + .PARAMETER Forks + Gets the forks of the specified gist. + + .PARAMETER Commits + Gets the commits of the specified gist. + + .PARAMETER UserName + Gets public gists for the specified user. + + .PARAMETER Path + Download the files that are part of the specified gist to this path. + + .PARAMETER Force + If downloading files, this will overwrite any files with the same name in the provided path. + + .PARAMETER Current + Gets the authenticated user's gists. + + .PARAMETER Starred + Gets the authenticated user's starred gists. + + .PARAMETER Public + Gets public gists sorted by most recently updated to least recently updated. + The results will be limited to the first 3000. + + .PARAMETER Since + Only gists updated at or after this time are returned. + + .PARAMETER AccessToken + If provided, this will be used as the AccessToken for authentication with the + REST Api. Otherwise, will attempt to use the configured value or will run unauthenticated. + + .PARAMETER NoStatus + If this switch is specified, long-running commands will run on the main thread + with no commandline status update. When not specified, those commands run in + the background, enabling the command prompt to provide status information. + If not supplied here, the DefaultNoStatus configuration property value will be used. + + .INPUTS + GitHub.Gist + GitHub.GistComment + GitHub.GistCommit + GitHub.GistFork + GitHub.GistSummary + + .OUTPUTS + GitHub.Gist + GitHub.GistCommit + GitHub.GistFork + GitHub.GistSummary + + .EXAMPLE + Get-GitHubGist -Starred + + Gets all starred gists for the current authenticated user. + + .EXAMPLE + Get-GitHubGist -Public -Since ((Get-Date).AddDays(-2)) + + Gets all public gists that have been updated within the past two days. + + .EXAMPLE + Get-GitHubGist -Gist 6cad326836d38bd3a7ae + + Gets octocat's "hello_world.rb" gist. +#> + [CmdletBinding( + DefaultParameterSetName='Current', + PositionalBinding = $false)] + [OutputType({$script:GitHubGistTypeName})] + [OutputType({$script:GitHubGistCommitTypeName})] + [OutputType({$script:GitHubGistForkTypeName})] + [OutputType({$script:GitHubGistSummaryTypeName})] + param( + [Parameter( + Mandatory, + ValueFromPipelineByPropertyName, + ParameterSetName='Id', + Position = 1)] + [Parameter( + Mandatory, + ValueFromPipelineByPropertyName, + ParameterSetName='Download', + Position = 1)] + [Alias('GistId')] + [ValidateNotNullOrEmpty()] + [string] $Gist, + + [Parameter(ParameterSetName='Id')] + [Parameter(ParameterSetName='Download')] + [ValidateNotNullOrEmpty()] + [string] $Sha, + + [Parameter(ParameterSetName='Id')] + [switch] $Forks, + + [Parameter(ParameterSetName='Id')] + [switch] $Commits, + + [Parameter( + Mandatory, + ParameterSetName='User')] + [ValidateNotNullOrEmpty()] + [string] $UserName, + + [Parameter( + Mandatory, + ParameterSetName='Download', + Position = 2)] + [ValidateNotNullOrEmpty()] + [string] $Path, + + [Parameter(ParameterSetName='Download')] + [switch] $Force, + + [Parameter(ParameterSetName='Current')] + [switch] $Current, + + [Parameter(ParameterSetName='Current')] + [switch] $Starred, + + [Parameter(ParameterSetName='Public')] + [switch] $Public, + + [Parameter(ParameterSetName='User')] + [Parameter(ParameterSetName='Current')] + [Parameter(ParameterSetName='Public')] + [DateTime] $Since, + + [string] $AccessToken, + + [switch] $NoStatus + ) + + Write-InvocationLog -Invocation $MyInvocation + + $telemetryProperties = @{} + + $uriFragment = [String]::Empty + $description = [String]::Empty + $outputType = $script:GitHubGistSummaryTypeName + + if ($PSCmdlet.ParameterSetName -in ('Id', 'Download')) + { + $telemetryProperties['ById'] = $true + + if ($PSBoundParameters.ContainsKey('Sha')) + { + if ($Forks -or $Commits) + { + $message = 'Cannot check for forks or commits of a specific SHA. Do not specify SHA if you want to list out forks or commits.' + Write-Log -Message $message -Level Error + throw $message + } + + $telemetryProperties['SpecifiedSha'] = $true + + $uriFragment = "gists/$Gist/$Sha" + $description = "Getting gist $Gist with specified Sha" + $outputType = $script:GitHubGistTypeName + } + elseif ($Forks) + { + $uriFragment = "gists/$Gist/forks" + $description = "Getting forks of gist $Gist" + $outputType = $script:GitHubGistForkTypeName + } + elseif ($Commits) + { + $uriFragment = "gists/$Gist/commits" + $description = "Getting commits of gist $Gist" + $outputType = $script:GitHubGistCommitTypeName + } + else + { + $uriFragment = "gists/$Gist" + $description = "Getting gist $Gist" + $outputType = $script:GitHubGistTypeName + } + } + elseif ($PSCmdlet.ParameterSetName -eq 'User') + { + $telemetryProperties['ByUserName'] = $true + + $uriFragment = "users/$UserName/gists" + $description = "Getting public gists for $UserName" + $outputType = $script:GitHubGistSummaryTypeName + } + elseif ($PSCmdlet.ParameterSetName -eq 'Current') + { + $telemetryProperties['CurrentUser'] = $true + $outputType = $script:GitHubGistSummaryTypeName + + if ((Test-GitHubAuthenticationConfigured) -or (-not [String]::IsNullOrEmpty($AccessToken))) + { + if ($Starred) + { + $uriFragment = 'gists/starred' + $description = 'Getting starred gists for current authenticated user' + } + else + { + $uriFragment = 'gists' + $description = 'Getting gists for current authenticated user' + } + } + else + { + if ($Starred) + { + $message = 'Starred can only be specified for authenticated users. Either call Set-GitHubAuthentication first, or provide a value for the AccessToken parameter.' + Write-Log -Message $message -Level Error + throw $message + } + + $message = 'Specified -Current, but not currently authenticated. Either call Set-GitHubAuthentication first, or provide a value for the AccessToken parameter.' + Write-Log -Message $message -Level Error + throw $message + } + } + elseif ($PSCmdlet.ParameterSetName -eq 'Public') + { + $telemetryProperties['Public'] = $true + $outputType = $script:GitHubGistSummaryTypeName + + $uriFragment = "gists/public" + $description = 'Getting public gists' + } + + $getParams = @() + $sinceFormattedTime = [String]::Empty + if ($null -ne $Since) + { + $sinceFormattedTime = $Since.ToUniversalTime().ToString('o') + $getParams += "since=$sinceFormattedTime" + } + + $params = @{ + 'UriFragment' = $uriFragment + '?' + ($getParams -join '&') + 'Description' = $description + 'AccessToken' = $AccessToken + 'TelemetryEventName' = $MyInvocation.MyCommand.Name + 'TelemetryProperties' = $telemetryProperties + 'NoStatus' = (Resolve-ParameterWithDefaultConfigurationValue -BoundParameters $PSBoundParameters -Name NoStatus -ConfigValueName DefaultNoStatus) + } + + $result = (Invoke-GHRestMethodMultipleResult @params | + Add-GitHubGistAdditionalProperties -TypeName $outputType) + + if ($PSCmdlet.ParameterSetName -eq 'Download') + { + Save-GitHubGist -GistObject $result -Path $Path -Force:$Force + } + else + { + if ($result.truncated -eq $true) + { + $message = @( + 'Response has been truncated. The API will only return the first 3000 gist results', + 'the first 300 files within the gist, and the first 1 Mb of an individual', + 'file. If the file has been truncated, you can call', + '(Invoke-WebRequest -UseBasicParsing -Method Get -Uri ).Content)', + 'where is the value of raw_url for the file in question. Be aware that', + 'for files larger than 10 Mb, you''ll need to clone the gist via the URL provided', + 'by git_pull_url.') + + Write-Log -Message ($message -join ' ') -Level Warning + } + + return $result + } +} + +function Save-GitHubGist +{ +<# + .SYNOPSIS + Downloads the contents of a gist to the specified file path. + + .DESCRIPTION + Downloads the contents of a gist to the specified file path. + + The Git repo for this module can be found here: http://aka.ms/PowerShellForGitHub + + .PARAMETER GistObject + The Gist PSCustomObject + + .PARAMETER Path + Download the files that are part of the specified gist to this path. + + .PARAMETER Force + If downloading files, this will overwrite any files with the same name in the provided path. + + .NOTES + Internal-only helper +#> + [CmdletBinding(PositionalBinding = $false)] + param( + [Parameter(Mandatory)] + [PSCustomObject] $GistObject, + + [Parameter(Mandatory)] + [ValidateNotNullOrEmpty()] + [string] $Path, + + [switch] $Force + ) + + # First, check to see if the response is missing files. + if ($GistObject.truncated) + { + $message = @( + 'Gist response has been truncated. The API will only return information on', + 'the first 300 files within a gist. To download this entire gist,', + 'you''ll need to clone it via the URL provided by git_pull_url', + "[$($GistObject.git_pull_url)].") + + Write-Log -Message ($message -join ' ') -Level Error + throw $message + } + + # Then check to see if there are files we won't be able to download + $files = $GistObject.files | Get-Member -Type NoteProperty | Select-Object -ExpandProperty Name + foreach ($fileName in $files) + { + if ($GistObject.files.$fileName.truncated -and + ($GistObject.files.$fileName.size -gt 10mb)) + { + $message = @( + "At least one file ($fileName) in this gist is larger than 10mb.", + 'In order to download this gist, you''ll need to clone it via the URL', + "provided by git_pull_url [$($GistObject.git_pull_url)].") + + Write-Log -Message ($message -join ' ') -Level Error + throw $message + } + } + + # Finally, we're ready to directly save the non-truncated files, + # and download the ones that are between 1 - 10mb. + $originalSecurityProtocol = [Net.ServicePointManager]::SecurityProtocol + [Net.ServicePointManager]::SecurityProtocol=[Net.SecurityProtocolType]::Tls12 + try + { + $headers = @{} + $AccessToken = Get-AccessToken -AccessToken $AccessToken + if (-not [String]::IsNullOrEmpty($AccessToken)) + { + $headers['Authorization'] = "token $AccessToken" + } + + $Path = Resolve-UnverifiedPath -Path $Path + $null = New-Item -Path $Path -ItemType Directory -Force + foreach ($fileName in $files) + { + $filePath = Join-Path -Path $Path -ChildPath $fileName + if ((Test-Path -Path $filePath -PathType Leaf) -and (-not $Force)) + { + $message = "File already exists at path [$filePath]. Choose a different path or specify -Force" + Write-Log -Message $message -Level Error + throw $message + } + + if ($GistObject.files.$fileName.truncated) + { + # Disable Progress Bar in function scope during Invoke-WebRequest + $ProgressPreference = 'SilentlyContinue' + + $webRequestParams = @{ + UseBasicParsing = $true + Method = 'Get' + Headers = $headers + Uri = $GistObject.files.$fileName.raw_url + OutFile = $filePath + } + + Invoke-WebRequest @webRequestParams + } + else + { + $stream = New-Object -TypeName System.IO.StreamWriter -ArgumentList ($filePath) + try + { + $stream.Write($GistObject.files.$fileName.content) + } + finally + { + $stream.Close() + } + } + } + } + finally + { + [Net.ServicePointManager]::SecurityProtocol = $originalSecurityProtocol + } +} + +filter Remove-GitHubGist +{ +<# + .SYNOPSIS + Removes/deletes a gist from GitHub. + + .DESCRIPTION + Removes/deletes a gist from GitHub. + + The Git repo for this module can be found here: http://aka.ms/PowerShellForGitHub + + .PARAMETER Gist + The ID of the specific gist that you wish to retrieve. + + .PARAMETER Force + If this switch is specified, you will not be prompted for confirmation of command execution. + + .PARAMETER AccessToken + If provided, this will be used as the AccessToken for authentication with the + REST Api. Otherwise, will attempt to use the configured value or will run unauthenticated. + + .PARAMETER NoStatus + If this switch is specified, long-running commands will run on the main thread + with no commandline status update. When not specified, those commands run in + the background, enabling the command prompt to provide status information. + If not supplied here, the DefaultNoStatus configuration property value will be used. + + .INPUTS + GitHub.Gist + GitHub.GistComment + GitHub.GistCommit + GitHub.GistFork + GitHub.GistSummary + + .EXAMPLE + Remove-GitHubGist -Gist 6cad326836d38bd3a7ae + + Removes octocat's "hello_world.rb" gist (assuming you have permission). + + .EXAMPLE + Remove-GitHubGist -Gist 6cad326836d38bd3a7ae -Confirm:$false + + Removes octocat's "hello_world.rb" gist (assuming you have permission). + Will not prompt for confirmation, as -Confirm:$false was specified. + + .EXAMPLE + Remove-GitHubGist -Gist 6cad326836d38bd3a7ae -Force + + Removes octocat's "hello_world.rb" gist (assuming you have permission). + Will not prompt for confirmation, as -Force was specified. +#> + [CmdletBinding( + SupportsShouldProcess, + PositionalBinding = $false, + ConfirmImpact = 'High')] + [Alias('Delete-GitHubGist')] + param( + [Parameter( + Mandatory, + ValueFromPipelineByPropertyName, + Position = 1)] + [Alias('GistId')] + [ValidateNotNullOrEmpty()] + [string] $Gist, + + [switch] $Force, + + [string] $AccessToken, + + [switch] $NoStatus + ) + + Write-InvocationLog -Invocation $MyInvocation + + if ($Force -and (-not $Confirm)) + { + $ConfirmPreference = 'None' + } + + if (-not $PSCmdlet.ShouldProcess($Gist, "Delete gist")) + { + return + } + + $telemetryProperties = @{} + $params = @{ + 'UriFragment' = "gists/$Gist" + 'Method' = 'Delete' + 'Description' = "Removing gist $Gist" + 'AccessToken' = $AccessToken + 'TelemetryEventName' = $MyInvocation.MyCommand.Name + 'TelemetryProperties' = $telemetryProperties + 'NoStatus' = (Resolve-ParameterWithDefaultConfigurationValue -BoundParameters $PSBoundParameters -Name NoStatus -ConfigValueName DefaultNoStatus) + } + + return Invoke-GHRestMethod @params +} + +filter Copy-GitHubGist +{ +<# + .SYNOPSIS + Forks a gist from GitHub. + + .DESCRIPTION + Forks a gist from GitHub. + + The Git repo for this module can be found here: http://aka.ms/PowerShellForGitHub + + .PARAMETER Gist + The ID of the specific gist that you wish to fork. + + .PARAMETER AccessToken + If provided, this will be used as the AccessToken for authentication with the + REST Api. Otherwise, will attempt to use the configured value or will run unauthenticated. + + .PARAMETER NoStatus + If this switch is specified, long-running commands will run on the main thread + with no commandline status update. When not specified, those commands run in + the background, enabling the command prompt to provide status information. + If not supplied here, the DefaultNoStatus configuration property value will be used. + + .INPUTS + GitHub.Gist + GitHub.GistComment + GitHub.GistCommit + GitHub.GistFork + GitHub.GistSummary + + .OUTPUTS + GitHub.GistSummary + + .EXAMPLE + Copy-GitHubGist -Gist 6cad326836d38bd3a7ae + + Forks octocat's "hello_world.rb" gist. + + .EXAMPLE + Fork-GitHubGist -Gist 6cad326836d38bd3a7ae + + Forks octocat's "hello_world.rb" gist. This is using the alias for the command. + The result is the same whether you use Copy-GitHubGist or Fork-GitHubGist. +#> + [CmdletBinding( + SupportsShouldProcess, + PositionalBinding = $false)] + [OutputType({$script:GitHubGistSummaryTypeName})] + [Alias('Fork-GitHubGist')] + param( + [Parameter( + Mandatory, + ValueFromPipelineByPropertyName, + Position = 1)] + [Alias('GistId')] + [ValidateNotNullOrEmpty()] + [string] $Gist, + + [string] $AccessToken, + + [switch] $NoStatus + ) + + Write-InvocationLog -Invocation $MyInvocation + + if (-not $PSCmdlet.ShouldProcess($Gist, "Forking gist")) + { + return + } + + $telemetryProperties = @{} + $params = @{ + 'UriFragment' = "gists/$Gist/forks" + 'Method' = 'Post' + 'Description' = "Forking gist $Gist" + 'AccessToken' = $AccessToken + 'TelemetryEventName' = $MyInvocation.MyCommand.Name + 'TelemetryProperties' = $telemetryProperties + 'NoStatus' = (Resolve-ParameterWithDefaultConfigurationValue -BoundParameters $PSBoundParameters -Name NoStatus -ConfigValueName DefaultNoStatus) + } + + return (Invoke-GHRestMethod @params | + Add-GitHubGistAdditionalProperties -TypeName $script:GitHubGistSummaryTypeName) +} + +filter Set-GitHubGistStar +{ +<# + .SYNOPSIS + Changes the starred state of a gist on GitHub for the current authenticated user. + + .DESCRIPTION + Changes the starred state of a gist on GitHub for the current authenticated user. + + The Git repo for this module can be found here: http://aka.ms/PowerShellForGitHub + + .PARAMETER Gist + The ID of the specific Gist that you wish to change the starred state for. + + .PARAMETER Star + Include this switch to star the gist. Exclude the switch (or use -Star:$false) to + remove the star. + + .PARAMETER AccessToken + If provided, this will be used as the AccessToken for authentication with the + REST Api. Otherwise, will attempt to use the configured value or will run unauthenticated. + + .PARAMETER NoStatus + If this switch is specified, long-running commands will run on the main thread + with no commandline status update. When not specified, those commands run in + the background, enabling the command prompt to provide status information. + If not supplied here, the DefaultNoStatus configuration property value will be used. + + .INPUTS + GitHub.Gist + GitHub.GistComment + GitHub.GistCommit + GitHub.GistFork + GitHub.GistSummary + + .EXAMPLE + Set-GitHubGistStar -Gist 6cad326836d38bd3a7ae -Star + + Stars octocat's "hello_world.rb" gist. + + .EXAMPLE + Set-GitHubGistStar -Gist 6cad326836d38bd3a7ae + + Unstars octocat's "hello_world.rb" gist. + + .EXAMPLE + Get-GitHubGist -Gist 6cad326836d38bd3a7ae | Set-GitHubGistStar -Star:$false + + Unstars octocat's "hello_world.rb" gist. + +#> + [CmdletBinding( + SupportsShouldProcess, + PositionalBinding = $false)] + param( + [Parameter( + Mandatory, + ValueFromPipelineByPropertyName, + Position = 1)] + [Alias('GistId')] + [ValidateNotNullOrEmpty()] + [string] $Gist, + + [switch] $Star, + + [string] $AccessToken, + + [switch] $NoStatus + ) + + Write-InvocationLog -Invocation $MyInvocation + Set-TelemetryEvent -EventName $MyInvocation.MyCommand.Name + + $PSBoundParameters.Remove('Star') + if ($Star) + { + return Add-GitHubGistStar @PSBoundParameters + } + else + { + return Remove-GitHubGistStar @PSBoundParameters + } +} + +filter Add-GitHubGistStar +{ +<# + .SYNOPSIS + Star a gist from GitHub. + + .DESCRIPTION + Star a gist from GitHub. + + The Git repo for this module can be found here: http://aka.ms/PowerShellForGitHub + + .PARAMETER Gist + The ID of the specific Gist that you wish to star. + + .PARAMETER AccessToken + If provided, this will be used as the AccessToken for authentication with the + REST Api. Otherwise, will attempt to use the configured value or will run unauthenticated. + + .PARAMETER NoStatus + If this switch is specified, long-running commands will run on the main thread + with no commandline status update. When not specified, those commands run in + the background, enabling the command prompt to provide status information. + If not supplied here, the DefaultNoStatus configuration property value will be used. + + .INPUTS + GitHub.Gist + GitHub.GistComment + GitHub.GistCommit + GitHub.GistFork + GitHub.GistSummary + + .EXAMPLE + Add-GitHubGistStar -Gist 6cad326836d38bd3a7ae + + Stars octocat's "hello_world.rb" gist. + + .EXAMPLE + Star-GitHubGist -Gist 6cad326836d38bd3a7ae + + Stars octocat's "hello_world.rb" gist. This is using the alias for the command. + The result is the same whether you use Add-GitHubGistStar or Star-GitHubGist. +#> + [CmdletBinding( + SupportsShouldProcess, + PositionalBinding = $false)] + [Alias('Star-GitHubGist')] + param( + [Parameter( + Mandatory, + ValueFromPipelineByPropertyName, + Position = 1)] + [Alias('GistId')] + [ValidateNotNullOrEmpty()] + [string] $Gist, + + [string] $AccessToken, + + [switch] $NoStatus + ) + + Write-InvocationLog -Invocation $MyInvocation + + if (-not $PSCmdlet.ShouldProcess($Gist, "Starring gist")) + { + return + } + + $telemetryProperties = @{} + $params = @{ + 'UriFragment' = "gists/$Gist/star" + 'Method' = 'Put' + 'Description' = "Starring gist $Gist" + 'AccessToken' = $AccessToken + 'TelemetryEventName' = $MyInvocation.MyCommand.Name + 'TelemetryProperties' = $telemetryProperties + 'NoStatus' = (Resolve-ParameterWithDefaultConfigurationValue -BoundParameters $PSBoundParameters -Name NoStatus -ConfigValueName DefaultNoStatus) + } + + return Invoke-GHRestMethod @params +} + +filter Remove-GitHubGistStar +{ +<# + .SYNOPSIS + Unstar a gist from GitHub. + + .DESCRIPTION + Unstar a gist from GitHub. + + The Git repo for this module can be found here: http://aka.ms/PowerShellForGitHub + + .PARAMETER Gist + The ID of the specific gist that you wish to unstar. + + .PARAMETER AccessToken + If provided, this will be used as the AccessToken for authentication with the + REST Api. Otherwise, will attempt to use the configured value or will run unauthenticated. + + .PARAMETER NoStatus + If this switch is specified, long-running commands will run on the main thread + with no commandline status update. When not specified, those commands run in + the background, enabling the command prompt to provide status information. + If not supplied here, the DefaultNoStatus configuration property value will be used. + + .INPUTS + GitHub.Gist + GitHub.GistComment + GitHub.GistCommit + GitHub.GistFork + GitHub.GistSummary + + .EXAMPLE + Remove-GitHubGistStar -Gist 6cad326836d38bd3a7ae + + Unstars octocat's "hello_world.rb" gist. + + .EXAMPLE + Unstar-GitHubGist -Gist 6cad326836d38bd3a7ae + + Unstars octocat's "hello_world.rb" gist. This is using the alias for the command. + The result is the same whether you use Remove-GitHubGistStar or Unstar-GitHubGist. +#> + [CmdletBinding( + SupportsShouldProcess, + PositionalBinding = $false)] + [Alias('Unstar-GitHubGist')] + param( + [Parameter( + Mandatory, + ValueFromPipelineByPropertyName, + Position = 1)] + [Alias('GistId')] + [ValidateNotNullOrEmpty()] + [string] $Gist, + + [string] $AccessToken, + + [switch] $NoStatus + ) + + Write-InvocationLog -Invocation $MyInvocation + + if (-not $PSCmdlet.ShouldProcess($Gist, "Unstarring gist")) + { + return + } + + $telemetryProperties = @{} + $params = @{ + 'UriFragment' = "gists/$Gist/star" + 'Method' = 'Delete' + 'Description' = "Unstarring gist $Gist" + 'AccessToken' = $AccessToken + 'TelemetryEventName' = $MyInvocation.MyCommand.Name + 'TelemetryProperties' = $telemetryProperties + 'NoStatus' = (Resolve-ParameterWithDefaultConfigurationValue -BoundParameters $PSBoundParameters -Name NoStatus -ConfigValueName DefaultNoStatus) + } + + return Invoke-GHRestMethod @params +} + +filter Test-GitHubGistStar +{ +<# + .SYNOPSIS + Checks if a gist from GitHub is starred. + + .DESCRIPTION + Checks if a gist from GitHub is starred. + Will return $false if it isn't starred, as well as if it couldn't be checked + (due to permissions or non-existence). + + The Git repo for this module can be found here: http://aka.ms/PowerShellForGitHub + + .PARAMETER Gist + The ID of the specific gist that you wish to check. + + .PARAMETER AccessToken + If provided, this will be used as the AccessToken for authentication with the + REST Api. Otherwise, will attempt to use the configured value or will run unauthenticated. + + .PARAMETER NoStatus + If this switch is specified, long-running commands will run on the main thread + with no commandline status update. When not specified, those commands run in + the background, enabling the command prompt to provide status information. + If not supplied here, the DefaultNoStatus configuration property value will be used. + + .INPUTS + GitHub.Gist + GitHub.GistComment + GitHub.GistCommit + GitHub.GistFork + GitHub.GistSummary + + .OUTPUTS + Boolean indicating if the gist was both found and determined to be starred. + + .EXAMPLE + Test-GitHubGistStar -Gist 6cad326836d38bd3a7ae + + Returns $true if the gist is starred, or $false if isn't starred or couldn't be checked + (due to permissions or non-existence). +#> + [CmdletBinding(PositionalBinding = $false)] + [OutputType([bool])] + param( + [Parameter( + Mandatory, + ValueFromPipelineByPropertyName, + Position = 1)] + [Alias('GistId')] + [ValidateNotNullOrEmpty()] + [string] $Gist, + + [string] $AccessToken, + + [switch] $NoStatus + ) + + Write-InvocationLog -Invocation $MyInvocation + + $telemetryProperties = @{} + $params = @{ + 'UriFragment' = "gists/$Gist/star" + 'Method' = 'Get' + 'Description' = "Checking if gist $Gist is starred" + 'AccessToken' = $AccessToken + 'TelemetryEventName' = $MyInvocation.MyCommand.Name + 'TelemetryProperties' = $telemetryProperties + 'ExtendedResult' = $true + 'NoStatus' = (Resolve-ParameterWithDefaultConfigurationValue -BoundParameters $PSBoundParameters -Name NoStatus -ConfigValueName DefaultNoStatus) + } + + try + { + $response = Invoke-GHRestMethod @params + return $response.StatusCode -eq 204 + } + catch + { + return $false + } +} + +filter New-GitHubGist +{ +<# + .SYNOPSIS + Creates a new gist on GitHub. + + .DESCRIPTION + Creates a new gist on GitHub. + + The Git repo for this module can be found here: http://aka.ms/PowerShellForGitHub + + .PARAMETER File + An array of filepaths that should be part of this gist. + Use this when you have multiple files that should be part of a gist, or when you simply + want to reference an existing file on disk. + + .PARAMETER FileName + The name of the file that Content should be stored in within the newly created gist. + + .PARAMETER Content + The content of a single file that should be part of the gist. + + .PARAMETER Description + A descriptive name for this gist. + + .PARAMETER Public + When specified, the gist will be public and available for anyone to see. + + .PARAMETER AccessToken + If provided, this will be used as the AccessToken for authentication with the + REST Api. Otherwise, will attempt to use the configured value or will run unauthenticated. + + .PARAMETER NoStatus + If this switch is specified, long-running commands will run on the main thread + with no commandline status update. When not specified, those commands run in + the background, enabling the command prompt to provide status information. + If not supplied here, the DefaultNoStatus configuration property value will be used. + + .INPUTS + String - Filename(s) of file(s) that should be the content of the gist. + + .OUTPUTS + GitHub.GitDetail + + .EXAMPLE + New-GitHubGist -FileName 'sample.txt' -Content 'Body of my file.' -Description 'This is my gist!' -Public + + Creates a new public gist with a single file named 'sample.txt' that has the body of "Body of my file." + + .EXAMPLE + New-GitHubGist -File 'c:\files\foo.txt' -Description 'This is my gist!' + + Creates a new private gist with a single file named 'foo.txt'. Will populate it with the + content of the file at c:\files\foo.txt. + + .EXAMPLE + New-GitHubGist -File ('c:\files\foo.txt', 'c:\other\bar.txt', 'c:\octocat.ps1') -Description 'This is my gist!' + + Creates a new private gist with a three files named 'foo.txt', 'bar.txt' and 'octocat.ps1'. + Each will be populated with the content from the file on disk at the specified location. +#> + [CmdletBinding( + SupportsShouldProcess, + DefaultParameterSetName='FileRef', + PositionalBinding = $false)] + [OutputType({$script:GitHubGistTypeName})] + param( + [Parameter( + Mandatory, + ValueFromPipeline, + ParameterSetName='FileRef', + Position = 1)] + [ValidateNotNullOrEmpty()] + [string[]] $File, + + [Parameter( + Mandatory, + ParameterSetName='Content', + Position = 1)] + [ValidateNotNullOrEmpty()] + [string] $FileName, + + [Parameter( + Mandatory, + ParameterSetName='Content', + Position = 2)] + [ValidateNotNullOrEmpty()] + [string] $Content, + + [string] $Description, + + [switch] $Public, + + [string] $AccessToken, + + [switch] $NoStatus + ) + + begin + { + $files = @{} + } + + process + { + foreach ($path in $File) + { + $path = Resolve-UnverifiedPath -Path $path + if (-not (Test-Path -Path $path -PathType Leaf)) + { + $message = "Specified file [$path] could not be found or was inaccessible." + Write-Log -Message $message -Level Error + throw $message + } + + $content = [System.IO.File]::ReadAllText($path) + $fileName = (Get-Item -Path $path).Name + + if ($files.ContainsKey($fileName)) + { + $message = "You have specified more than one file with the same name [$fileName]. gists don't have a concept of directory structures, so please ensure each file has a unique name." + Write-Log -Message $message -Level Error + throw $message + } + + $files[$fileName] = @{ 'content' = $Content } + } + } + + end + { + Write-InvocationLog -Invocation $MyInvocation + + $telemetryProperties = @{} + + if ($PSCmdlet.ParameterSetName -eq 'Content') + { + $files[$FileName] = @{ 'content' = $Content } + } + + if (($files.Keys.StartsWith('gistfile') | Where-Object { $_ -eq $true }).Count -gt 0) + { + $message = "Don't name your files starting with 'gistfile'. This is the format of the automatic naming scheme that Gist uses internally." + Write-Log -Message $message -Level Error + throw $message + } + + $hashBody = @{ + 'description' = $Description + 'public' = $Public.ToBool() + 'files' = $files + } + + if (-not $PSCmdlet.ShouldProcess('Create new gist')) + { + return + } + + $params = @{ + 'UriFragment' = "gists" + 'Body' = (ConvertTo-Json -InputObject $hashBody) + 'Method' = 'Post' + 'Description' = "Creating a new gist" + 'AccessToken' = $AccessToken + 'TelemetryEventName' = $MyInvocation.MyCommand.Name + 'TelemetryProperties' = $telemetryProperties + 'NoStatus' = (Resolve-ParameterWithDefaultConfigurationValue -BoundParameters $PSBoundParameters -Name NoStatus -ConfigValueName DefaultNoStatus) + } + + return (Invoke-GHRestMethod @params | + Add-GitHubGistAdditionalProperties -TypeName $script:GitHubGistTypeName) + } +} + +filter Set-GitHubGist +{ +<# + .SYNOPSIS + Updates a gist on GitHub. + + .DESCRIPTION + Updates a gist on GitHub. + + The Git repo for this module can be found here: http://aka.ms/PowerShellForGitHub + + .PARAMETER Gist + The ID for the gist to update. + + .PARAMETER Update + A hashtable of files to update in the gist. + The key should be the name of the file in the gist as it exists right now. + The value should be another hashtable with the following optional key/value pairs: + fileName - Specify a new name here if you want to rename the file. + filePath - Specify a path to a file on disk if you wish to update the contents of the + file in the gist with the contents of the specified file. + Should not be specified if you use 'content' (below) + content - Directly specify the raw content that the file in the gist should be updated with. + Should not be used if you use 'filePath' (above). + + .PARAMETER Delete + A list of filenames that should be removed from this gist. + + .PARAMETER Description + New description for this gist. + + .PARAMETER Force + If this switch is specified, you will not be prompted for confirmation of command execution. + + .PARAMETER AccessToken + If provided, this will be used as the AccessToken for authentication with the + REST Api. Otherwise, will attempt to use the configured value or will run unauthenticated. + + .PARAMETER NoStatus + If this switch is specified, long-running commands will run on the main thread + with no commandline status update. When not specified, those commands run in + the background, enabling the command prompt to provide status information. + If not supplied here, the DefaultNoStatus configuration property value will be used. + + .INPUTS + GitHub.Gist + GitHub.GistComment + GitHub.GistCommit + GitHub.GistFork + GitHub.GistSummary + + .OUTPUTS + GitHub.GistDetail + + .EXAMPLE + Set-GitHubGist -Gist 6cad326836d38bd3a7ae -Description 'This is my newer description' + + Updates the description for the specified gist. + + .EXAMPLE + Set-GitHubGist -Gist 6cad326836d38bd3a7ae -Delete 'hello_world.rb' -Force + + Deletes the 'hello_world.rb' file from the specified gist without prompting for confirmation. + + .EXAMPLE + Set-GitHubGist -Gist 6cad326836d38bd3a7ae -Delete 'hello_world.rb' -Description 'This is my newer description' + + Deletes the 'hello_world.rb' file from the specified gist and updates the description. + + .EXAMPLE + Set-GitHubGist -Gist 6cad326836d38bd3a7ae -Update @{'hello_world.rb' = @{ 'fileName' = 'hello_universe.rb' }} + + Renames the 'hello_world.rb' file in the specified gist to be 'hello_universe.rb'. + + .EXAMPLE + Set-GitHubGist -Gist 6cad326836d38bd3a7ae -Update @{'hello_world.rb' = @{ 'fileName' = 'hello_universe.rb' }} + + Renames the 'hello_world.rb' file in the specified gist to be 'hello_universe.rb'. +#> + [CmdletBinding( + SupportsShouldProcess, + DefaultParameterSetName='Content', + PositionalBinding = $false)] + [OutputType({$script:GitHubGistTypeName})] + param( + [Parameter( + Mandatory, + ValueFromPipelineByPropertyName, + Position = 1)] + [Alias('GistId')] + [ValidateNotNullOrEmpty()] + [string] $Gist, + + [hashtable] $Update, + + [string[]] $Delete, + + [string] $Description, + + [switch] $Force, + + [string] $AccessToken, + + [switch] $NoStatus + ) + + Write-InvocationLog -Invocation $MyInvocation + + $telemetryProperties = @{} + + $files = @{} + + $shouldProcessMessage = 'Update gist' + + # Mark the files that should be deleted. + if ($Delete.Count -gt 0) + { + $ConfirmPreference = 'Low' + $shouldProcessMessage = 'Update gist (and remove files)' + + foreach ($toDelete in $Delete) + { + $files[$toDelete] = $null + } + } + + # Then figure out which ones need content updates and/or file renames + if ($null -ne $Update) + { + foreach ($toUpdate in $Update.GetEnumerator()) + { + $currentFileName = $toUpdate.Key + + $providedContent = $toUpdate.Value.Content + $providedFileName = $toUpdate.Value.FileName + $providedFilePath = $toUpdate.Value.FilePath + + if (-not [String]::IsNullOrWhiteSpace($providedContent)) + { + $files[$currentFileName] = @{ 'content' = $providedContent } + } + + if (-not [String]::IsNullOrWhiteSpace($providedFilePath)) + { + if (-not [String]::IsNullOrWhiteSpace($providedContent)) + { + $message = "When updating a file [$currentFileName], you cannot provide both a path to a file [$providedFilePath] and the raw content." + Write-Log -Message $message -Level Error + throw $message + } + + $providedFilePath = Resolve-Path -Path $providedFilePath + if (-not (Test-Path -Path $providedFilePath -PathType Leaf)) + { + $message = "Specified file [$providedFilePath] could not be found or was inaccessible." + Write-Log -Message $message -Level Error + throw $message + } + + $newContent = [System.IO.File]::ReadAllText($providedFilePath) + $files[$currentFileName] = @{ 'content' = $newContent } + } + + # The user has chosen to rename the file. + if (-not [String]::IsNullOrWhiteSpace($providedFileName)) + { + $files[$currentFileName] = @{ 'filename' = $providedFileName } + } + } + } + + $hashBody = @{} + if (-not [String]::IsNullOrWhiteSpace($Description)) { $hashBody['description'] = $Description } + if ($files.Keys.count -gt 0) { $hashBody['files'] = $files } + + if ($Force -and (-not $Confirm)) + { + $ConfirmPreference = 'None' + } + + if (-not $PSCmdlet.ShouldProcess($Gist, $shouldProcessMessage)) + { + return + } + + $ConfirmPreference = 'None' + $params = @{ + 'UriFragment' = "gists/$Gist" + 'Body' = (ConvertTo-Json -InputObject $hashBody) + 'Method' = 'Patch' + 'Description' = "Updating gist $Gist" + 'AccessToken' = $AccessToken + 'TelemetryEventName' = $MyInvocation.MyCommand.Name + 'TelemetryProperties' = $telemetryProperties + 'NoStatus' = (Resolve-ParameterWithDefaultConfigurationValue -BoundParameters $PSBoundParameters -Name NoStatus -ConfigValueName DefaultNoStatus) + } + + try + { + return (Invoke-GHRestMethod @params | + Add-GitHubGistAdditionalProperties -TypeName $script:GitHubGistTypeName) + } + catch + { + if ($_.Exception.Message -like '*(422)*') + { + $message = 'This error can happen if you try to delete a file that doesn''t exist. Be aware that casing matters. ''A.txt'' is not the same as ''a.txt''.' + Write-Log -Message $message -Level Warning + } + + throw + } +} + +function Set-GitHubGistFile +{ +<# + .SYNOPSIS + Updates content of file(s) in an existing gist on GitHub, + or adds them if they aren't already part of the gist. + + .DESCRIPTION + Updates content of file(s) in an existing gist on GitHub, + or adds them if they aren't already part of the gist. + + This is a helper function built on top of Set-GitHubGist. + + The Git repo for this module can be found here: http://aka.ms/PowerShellForGitHub + + .PARAMETER Gist + The ID for the gist to update. + + .PARAMETER File + An array of filepaths that should be part of this gist. + Use this when you have multiple files that should be part of a gist, or when you simply + want to reference an existing file on disk. + + .PARAMETER FileName + The name of the file that Content should be stored in within the newly created gist. + + .PARAMETER Content + The content of a single file that should be part of the gist. + + .PARAMETER AccessToken + If provided, this will be used as the AccessToken for authentication with the + REST Api. Otherwise, will attempt to use the configured value or will run unauthenticated. + + .PARAMETER NoStatus + If this switch is specified, long-running commands will run on the main thread + with no commandline status update. When not specified, those commands run in + the background, enabling the command prompt to provide status information. + If not supplied here, the DefaultNoStatus configuration property value will be used. + + .INPUTS + GitHub.Gist + GitHub.GistComment + GitHub.GistCommit + GitHub.GistFork + GitHub.GistSummary + + .OUTPUTS + GitHub.Gist + + .EXAMPLE + Set-GitHubGistFile -Gist 1234567 -Content 'Body of my file.' -FileName 'sample.txt' + + Adds a file named 'sample.txt' that has the body of "Body of my file." to the existing + specified gist, or updates the contents of 'sample.txt' in the gist if is already there. + + .EXAMPLE + Set-GitHubGistFile -Gist 1234567 -File 'c:\files\foo.txt' + + Adds the file 'foo.txt' to the existing specified gist, or updates its content if it + is already there. + + .EXAMPLE + Set-GitHubGistFile -Gist 1234567 -File ('c:\files\foo.txt', 'c:\other\bar.txt', 'c:\octocat.ps1') + + Adds all three files to the existing specified gist, or updates the contents of the files + in the gist if they are already there. +#> + [CmdletBinding( + SupportsShouldProcess, + DefaultParameterSetName='Content', + PositionalBinding = $false)] + [OutputType({$script:GitHubGistTypeName})] + [Alias('Add-GitHubGistFile')] + [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSShouldProcess", "", Justification="This is a helper method for Set-GitHubGist which will handle ShouldProcess.")] + param( + [Parameter( + Mandatory, + ValueFromPipelineByPropertyName, + Position = 1)] + [Alias('GistId')] + [ValidateNotNullOrEmpty()] + [string] $Gist, + + [Parameter( + Mandatory, + ValueFromPipeline, + ParameterSetName='FileRef', + Position = 2)] + [ValidateNotNullOrEmpty()] + [string[]] $File, + + [Parameter( + Mandatory, + ParameterSetName='Content', + Position = 2)] + [ValidateNotNullOrEmpty()] + [string] $FileName, + + [Parameter( + Mandatory, + ParameterSetName='Content', + Position = 3)] + [ValidateNotNullOrEmpty()] + [string] $Content, + + [string] $AccessToken, + + [switch] $NoStatus + ) + + begin + { + $files = @{} + } + + process + { + foreach ($path in $File) + { + $path = Resolve-UnverifiedPath -Path $path + if (-not (Test-Path -Path $path -PathType Leaf)) + { + $message = "Specified file [$path] could not be found or was inaccessible." + Write-Log -Message $message -Level Error + throw $message + } + + $fileName = (Get-Item -Path $path).Name + $files[$fileName] = @{ 'filePath' = $path } + } + } + + end + { + Write-InvocationLog -Invocation $MyInvocation + Set-TelemetryEvent -EventName $MyInvocation.MyCommand.Name + + if ($PSCmdlet.ParameterSetName -eq 'Content') + { + $files[$FileName] = @{ 'content' = $Content } + } + + $params = @{ + 'Gist' = $Gist + 'Update' = $files + 'AccessToken' = $AccessToken + 'NoStatus' = (Resolve-ParameterWithDefaultConfigurationValue -BoundParameters $PSBoundParameters -Name NoStatus -ConfigValueName DefaultNoStatus) + } + + return (Set-GitHubGist @params) + } +} + +function Remove-GitHubGistFile +{ +<# + .SYNOPSIS + Removes one or more files from an existing gist on GitHub. + + .DESCRIPTION + Removes one or more files from an existing gist on GitHub. + + This is a helper function built on top of Set-GitHubGist. + + The Git repo for this module can be found here: http://aka.ms/PowerShellForGitHub + + .PARAMETER Gist + The ID for the gist to update. + + .PARAMETER FileName + An array of filenames (no paths, just names) to remove from the gist. + + .PARAMETER Force + If this switch is specified, you will not be prompted for confirmation of command execution. + + .PARAMETER AccessToken + If provided, this will be used as the AccessToken for authentication with the + REST Api. Otherwise, will attempt to use the configured value or will run unauthenticated. + + .PARAMETER NoStatus + If this switch is specified, long-running commands will run on the main thread + with no commandline status update. When not specified, those commands run in + the background, enabling the command prompt to provide status information. + If not supplied here, the DefaultNoStatus configuration property value will be used. + + .INPUTS + GitHub.Gist + GitHub.GistComment + GitHub.GistCommit + GitHub.GistFork + GitHub.GistSummary + + .OUTPUTS + GitHub.Gist + + .EXAMPLE + Remove-GitHubGistFile -Gist 1234567 -FileName ('foo.txt') + + Removes the file 'foo.txt' from the specified gist. + + .EXAMPLE + Remove-GitHubGistFile -Gist 1234567 -FileName ('foo.txt') -Force + + Removes the file 'foo.txt' from the specified gist without prompting for confirmation. + + .EXAMPLE + @('foo.txt', 'bar.txt') | Remove-GitHubGistFile -Gist 1234567 + + Removes the files 'foo.txt' and 'bar.txt' from the specified gist. +#> + [CmdletBinding( + SupportsShouldProcess, + PositionalBinding = $false)] + [OutputType({$script:GitHubGistTypeName})] + [Alias('Delete-GitHubGistFile')] + [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSShouldProcess", "", Justification="This is a helper method for Set-GitHubGist which will handle ShouldProcess.")] + param( + [Parameter( + Mandatory, + ValueFromPipelineByPropertyName, + Position = 1)] + [Alias('GistId')] + [ValidateNotNullOrEmpty()] + [string] $Gist, + + [Parameter( + Mandatory, + ValueFromPipeline, + Position = 2)] + [ValidateNotNullOrEmpty()] + [string[]] $FileName, + + [switch] $Force, + + [string] $AccessToken, + + [switch] $NoStatus + ) + + begin + { + $files = @() + } + + process + { + foreach ($name in $FileName) + { + $files += $name + } + } + + end + { + Write-InvocationLog -Invocation $MyInvocation + Set-TelemetryEvent -EventName $MyInvocation.MyCommand.Name + + $params = @{ + 'Gist' = $Gist + 'Delete' = $files + 'Force' = $Force + 'Confirm' = ($Confirm -eq $true) + 'AccessToken' = $AccessToken + 'NoStatus' = (Resolve-ParameterWithDefaultConfigurationValue -BoundParameters $PSBoundParameters -Name NoStatus -ConfigValueName DefaultNoStatus) + } + + return (Set-GitHubGist @params) + } +} + +filter Rename-GitHubGistFile +{ +<# + .SYNOPSIS + Renames a file in a gist on GitHub. + + .DESCRIPTION + Renames a file in a gist on GitHub. + + This is a helper function built on top of Set-GitHubGist. + + The Git repo for this module can be found here: http://aka.ms/PowerShellForGitHub + + .PARAMETER Gist + The ID for the gist to update. + + .PARAMETER FileName + The current file in the gist to be renamed. + + .PARAMETER NewName + The new name of the file for the gist. + + .PARAMETER AccessToken + If provided, this will be used as the AccessToken for authentication with the + REST Api. Otherwise, will attempt to use the configured value or will run unauthenticated. + + .PARAMETER NoStatus + If this switch is specified, long-running commands will run on the main thread + with no commandline status update. When not specified, those commands run in + the background, enabling the command prompt to provide status information. + If not supplied here, the DefaultNoStatus configuration property value will be used. + + .INPUTS + GitHub.Gist + GitHub.GistComment + GitHub.GistCommit + GitHub.GistFork + GitHub.GistSummary + + .OUTPUTS + GitHub.Gist + + .EXAMPLE + Rename-GitHubGistFile -Gist 1234567 -FileName 'foo.txt' -NewName 'bar.txt' + + Renames the file 'foo.txt' to 'bar.txt' in the specified gist. +#> + [CmdletBinding( + SupportsShouldProcess, + PositionalBinding = $false)] + [OutputType({$script:GitHubGistTypeName})] + [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSShouldProcess", "", Justification="This is a helper method for Set-GitHubGist which will handle ShouldProcess.")] + param( + [Parameter( + Mandatory, + ValueFromPipelineByPropertyName, + Position = 1)] + [Alias('GistId')] + [ValidateNotNullOrEmpty()] + [string] $Gist, + + [Parameter( + Mandatory, + Position = 2)] + [ValidateNotNullOrEmpty()] + [string] $FileName, + + [Parameter( + Mandatory, + Position = 3)] + [ValidateNotNullOrEmpty()] + [string] $NewName, + + [string] $AccessToken, + + [switch] $NoStatus + ) + + Write-InvocationLog -Invocation $MyInvocation + Set-TelemetryEvent -EventName $MyInvocation.MyCommand.Name + + $params = @{ + 'Gist' = $Gist + 'Update' = @{$FileName = @{ 'fileName' = $NewName }} + 'AccessToken' = $AccessToken + 'NoStatus' = (Resolve-ParameterWithDefaultConfigurationValue -BoundParameters $PSBoundParameters -Name NoStatus -ConfigValueName DefaultNoStatus) + } + + return (Set-GitHubGist @params) +} + +filter Add-GitHubGistAdditionalProperties +{ +<# + .SYNOPSIS + Adds type name and additional properties to ease pipelining to GitHub Gist objects. + + .PARAMETER InputObject + The GitHub object to add additional properties to. + + .PARAMETER TypeName + The type that should be assigned to the object. + + .INPUTS + [PSCustomObject] + + .OUTPUTS + GitHub.Gist + GitHub.GistCommit + GitHub.GistFork + GitHub.GistSummary +#> + [CmdletBinding()] + [OutputType({$script:GitHubGistTypeName})] + [OutputType({$script:GitHubGistFormTypeName})] + [OutputType({$script:GitHubGistSummaryTypeName})] + [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSUseSingularNouns", "", Justification="Internal helper that is definitely adding more than one property.")] + param( + [Parameter( + Mandatory, + ValueFromPipeline)] + [AllowNull()] + [AllowEmptyCollection()] + [PSCustomObject[]] $InputObject, + + [ValidateNotNullOrEmpty()] + [string] $TypeName = $script:GitHubGistSummaryTypeName + ) + + if ($TypeName -eq $script:GitHubGistCommitTypeName) + { + return Add-GitHubGistCommitAdditionalProperties -InputObject $InputObject + } + elseif ($TypeName -eq $script:GitHubGistForkTypeName) + { + return Add-GitHubGistForkAdditionalProperties -InputObject $InputObject + } + + foreach ($item in $InputObject) + { + $item.PSObject.TypeNames.Insert(0, $TypeName) + + if (-not (Get-GitHubConfiguration -Name DisablePipelineSupport)) + { + Add-Member -InputObject $item -Name 'GistId' -Value $item.id -MemberType NoteProperty -Force + + @('user', 'owner') | + ForEach-Object { + if ($null -ne $item.$_) + { + $null = Add-GitHubUserAdditionalProperties -InputObject $item.$_ + } + } + + if ($null -ne $item.forks) + { + $item.forks = Add-GitHubGistForkAdditionalProperties -InputObject $item.forks + } + + if ($null -ne $item.history) + { + $item.history = Add-GitHubGistCommitAdditionalProperties -InputObject $item.history + } + } + + Write-Output $item + } +} + +filter Add-GitHubGistCommitAdditionalProperties +{ +<# + .SYNOPSIS + Adds type name and additional properties to ease pipelining to GitHub GistCommit objects. + + .PARAMETER InputObject + The GitHub object to add additional properties to. + + .PARAMETER TypeName + The type that should be assigned to the object. + + .INPUTS + [PSCustomObject] + + .OUTPUTS + GitHub.GistCommit +#> + [CmdletBinding()] + [OutputType({$script:GitHubGistCommitTypeName})] + [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSUseSingularNouns", "", Justification="Internal helper that is definitely adding more than one property.")] + param( + [Parameter( + Mandatory, + ValueFromPipeline)] + [AllowNull()] + [AllowEmptyCollection()] + [PSCustomObject[]] $InputObject, + + [ValidateNotNullOrEmpty()] + [string] $TypeName = $script:GitHubGistCommitTypeName + ) + + foreach ($item in $InputObject) + { + $item.PSObject.TypeNames.Insert(0, $TypeName) + + if (-not (Get-GitHubConfiguration -Name DisablePipelineSupport)) + { + $hostName = $(Get-GitHubConfiguration -Name 'ApiHostName') + if ($item.url -match "^https?://(?:www\.|api\.|)$hostName/gists/([^/]+)/(.+)$") + { + $id = $Matches[1] + $sha = $Matches[2] + + if ($sha -ne $item.version) + { + $message = "The gist commit url no longer follows the expected pattern. Please contact the PowerShellForGitHubTeam: $item.uri" + Write-Log -Message $message -Level Warning + } + } + + Add-Member -InputObject $item -Name 'GistId' -Value $id -MemberType NoteProperty -Force + Add-Member -InputObject $item -Name 'Sha' -Value $item.version -MemberType NoteProperty -Force + + $null = Add-GitHubUserAdditionalProperties -InputObject $item.user + } + + Write-Output $item + } +} + +filter Add-GitHubGistForkAdditionalProperties +{ +<# + .SYNOPSIS + Adds type name and additional properties to ease pipelining to GitHub Gist Fork objects. + + .PARAMETER InputObject + The GitHub object to add additional properties to. + + .PARAMETER TypeName + The type that should be assigned to the object. + + .INPUTS + [PSCustomObject] + + .OUTPUTS + GitHub.GistFork +#> + [CmdletBinding()] + [OutputType({$script:GitHubGistForkTypeName})] + [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSUseSingularNouns", "", Justification="Internal helper that is definitely adding more than one property.")] + param( + [Parameter( + Mandatory, + ValueFromPipeline)] + [AllowNull()] + [AllowEmptyCollection()] + [PSCustomObject[]] $InputObject, + + [ValidateNotNullOrEmpty()] + [string] $TypeName = $script:GitHubGistForkTypeName + ) + + foreach ($item in $InputObject) + { + $item.PSObject.TypeNames.Insert(0, $TypeName) + + if (-not (Get-GitHubConfiguration -Name DisablePipelineSupport)) + { + Add-Member -InputObject $item -Name 'GistId' -Value $item.id -MemberType NoteProperty -Force + + # See here for why we need to work with both 'user' _and_ 'owner': + # https://github.community/t/gist-api-v3-documentation-incorrect-for-forks/122545 + @('user', 'owner') | + ForEach-Object { + if ($null -ne $item.$_) + { + $null = Add-GitHubUserAdditionalProperties -InputObject $item.$_ + } + } + } + + Write-Output $item + } +} \ No newline at end of file diff --git a/PowerShellForGitHub.psd1 b/PowerShellForGitHub.psd1 index 3063585b..5cbc5e48 100644 --- a/PowerShellForGitHub.psd1 +++ b/PowerShellForGitHub.psd1 @@ -15,6 +15,8 @@ # Format files (.ps1xml) to be loaded when importing this module FormatsToProcess = @( + 'Formatters/GitHubGistComments.Format.ps1xml', + 'Formatters/GitHubGists.Format.ps1xml', 'Formatters/GitHubReleases.Format.ps1xml' 'Formatters/GitHubRepositories.Format.ps1xml' ) @@ -32,6 +34,8 @@ 'GitHubCore.ps1', 'GitHubContents.ps1', 'GitHubEvents.ps1', + 'GitHubGistComments.ps1', + 'GitHubGists.ps1', 'GitHubIssueComments.ps1', 'GitHubIssues.ps1', 'GitHubLabels.ps1', @@ -59,9 +63,11 @@ FunctionsToExport = @( 'Add-GitHubAssignee', 'Add-GitHubIssueLabel', + 'Add-GitHubGistStar', 'Backup-GitHubConfiguration', 'Clear-GitHubAuthentication', 'ConvertFrom-GitHubMarkdown', + 'Copy-GitHubGist', 'Disable-GitHubRepositorySecurityFix', 'Disable-GitHubRepositoryVulnerabilityAlert', 'Enable-GitHubRepositorySecurityFix', @@ -73,6 +79,8 @@ 'Get-GitHubContent', 'Get-GitHubEmoji', 'Get-GitHubEvent', + 'Get-GitHubGist', + 'Get-GitHubGistComment', 'Get-GitHubGitIgnore', 'Get-GitHubIssue', 'Get-GitHubIssueComment', @@ -115,6 +123,8 @@ 'Move-GitHubProjectCard', 'Move-GitHubProjectColumn', 'Move-GitHubRepositoryOwnership', + 'New-GitHubGist', + 'New-GitHubGistComment', 'New-GitHubIssue', 'New-GitHubIssueComment', 'New-GitHubLabel', @@ -130,6 +140,11 @@ 'New-GitHubRepositoryBranch', 'New-GitHubRepositoryFork', 'Remove-GitHubAssignee', + 'Remove-GitHubComment', + 'Remove-GitHubGist', + 'Remove-GitHubGistComment', + 'Remove-GitHubGistFile', + 'Remove-GitHubGistStar', 'Remove-GitHubIssueComment', 'Remove-GitHubIssueLabel', 'Remove-GitHubLabel', @@ -142,12 +157,17 @@ 'Remove-GitHubReleaseAsset', 'Remove-GitHubRepository', 'Remove-GitHubRepositoryBranch' + 'Rename-GitHubGistFile', 'Rename-GitHubRepository', 'Reset-GitHubConfiguration', 'Restore-GitHubConfiguration', 'Set-GitHubAuthentication', 'Set-GitHubConfiguration', 'Set-GitHubContent', + 'Set-GitHubGist', + 'Set-GitHubGistComment', + 'Set-GitHubGistFile', + 'Set-GitHubGistStar', 'Set-GitHubIssue', 'Set-GitHubIssueComment', 'Set-GitHubIssueLabel', @@ -165,15 +185,20 @@ 'Split-GitHubUri', 'Test-GitHubAssignee', 'Test-GitHubAuthenticationConfigured', + 'Test-GitHubGistStar', 'Test-GitHubOrganizationMember', 'Test-GitHubRepositoryVulnerabilityAlert', 'Unlock-GitHubIssue' ) AliasesToExport = @( + 'Add-GitHubGistFile', 'Delete-GitHubAsset', 'Delete-GitHubBranch', 'Delete-GitHubComment', + 'Delete-GitHubGist', + 'Delete-GitHubGistComment', + 'Delete-GitHubGistFile', 'Delete-GitHubIssueComment', 'Delete-GitHubLabel', 'Delete-GitHubMilestone', @@ -185,6 +210,7 @@ 'Delete-GitHubReleaseAsset', 'Delete-GitHubRepository', 'Delete-GitHubRepositoryBranch', + 'Fork-GitHubGist', 'Get-GitHubAsset', 'Get-GitHubBranch', 'Get-GitHubComment', @@ -197,7 +223,9 @@ 'Remove-GitHubComment', 'Set-GitHubAsset', 'Set-GitHubComment', + 'Star-GitHubGist', 'Transfer-GitHubRepositoryOwnership' + 'Unstar-GitHubGist' 'Update-GitHubIssue', 'Update-GitHubLabel', 'Update-GitHubCurrentUser', diff --git a/Tests/GitHubGistComments.tests.ps1 b/Tests/GitHubGistComments.tests.ps1 new file mode 100644 index 00000000..ab14b510 --- /dev/null +++ b/Tests/GitHubGistComments.tests.ps1 @@ -0,0 +1,335 @@ +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. + +<# +.Synopsis + Tests for GitHubGistCommentss.ps1 module +#> + +[CmdletBinding()] +[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseDeclaredVarsMoreThanAssignments', '', + Justification='Suppress false positives in Pester code blocks')] +param() + +# This is common test code setup logic for all Pester test files +$moduleRootPath = Split-Path -Path $PSScriptRoot -Parent +. (Join-Path -Path $moduleRootPath -ChildPath 'Tests\Common.ps1') + +try +{ + Describe 'Get-GitHubGistComment' { + BeforeAll { + $body = 'Comment body' + } + + Context 'By parameters' { + BeforeAll { + $gist = New-GitHubGist -FileName 'sample.txt' -Content 'Sample text' + $body = 'Comment body' + } + + AfterAll { + $gist | Remove-GitHubGist -Force + } + + $comments = @(Get-GitHubGistComment -Gist $gist.id -MediaType 'Raw') + It 'Should have no comments so far' { + $comments.Count | Should -Be 0 + } + + $firstComment = New-GitHubGistComment -Gist $gist.id -Body $body + $comments = @(Get-GitHubGistComment -Gist $gist.id -MediaType 'Text') + It 'Should have one comments so far' { + $comments.Count | Should -Be 1 + $comments[0].id | Should -Be $firstComment.id + $comments[0].body | Should -BeNullOrEmpty + $comments[0].body_html | Should -BeNullOrEmpty + $comments[0].body_text | Should -Not -BeNullOrEmpty + } + + It 'Should have the expected type and additional properties' { + $comments[0].PSObject.TypeNames[0] | Should -Be 'GitHub.GistComment' + $comments[0].GistCommentId | Should -Be $comments[0].id + $comments[0].GistId | Should -Be $gist.id + $comments[0].user.PSObject.TypeNames[0] | Should -Be 'GitHub.User' + } + + $null = New-GitHubGistComment -Gist $gist.id -Body $body + $comments = @(Get-GitHubGistComment -Gist $gist.id -MediaType 'Html') + It 'Should have one comments so far' { + $comments.Count | Should -Be 2 + foreach ($comment in $comments) + { + $comment.body | Should -BeNullOrEmpty + $comment.body_html | Should -Not -BeNullOrEmpty + $comment.body_text | Should -BeNullOrEmpty + } + } + + It 'Should have the expected type and additional properties' { + foreach ($comment in $comments) + { + $comment.PSObject.TypeNames[0] | Should -Be 'GitHub.GistComment' + $comment.GistCommentId | Should -Be $comment.id + $comment.GistId | Should -Be $gist.id + $comment.user.PSObject.TypeNames[0] | Should -Be 'GitHub.User' + } + } + + $comment = Get-GitHubGistComment -Gist $gist.id -Comment $firstComment.id -MediaType 'Html' + It 'Should retrieve the specific comment' { + $comment.id | Should -Be $firstComment.id + $comment.body | Should -BeNullOrEmpty + $comment.body_html | Should -Not -BeNullOrEmpty + $comment.body_text | Should -BeNullOrEmpty + } + + It 'Should have the expected type and additional properties' { + $comment.PSObject.TypeNames[0] | Should -Be 'GitHub.GistComment' + $comment.GistCommentId | Should -Be $comment.id + $comment.GistId | Should -Be $gist.id + $comment.user.PSObject.TypeNames[0] | Should -Be 'GitHub.User' + } + } + + Context 'Gist on the pipeline' { + BeforeAll { + $gist = New-GitHubGist -FileName 'sample.txt' -Content 'Sample text' + $body = 'Comment body' + } + + AfterAll { + $gist | Remove-GitHubGist -Force + } + + $comments = @(Get-GitHubGistComment -Gist $gist.id -MediaType 'Text') + It 'Should have no comments so far' { + $comments.Count | Should -Be 0 + } + + $firstComment = $gist | New-GitHubGistComment -Body $body + $comments = @($gist | Get-GitHubGistComment -MediaType 'Raw') + It 'Should have one comments so far' { + $comments.Count | Should -Be 1 + $comments[0].id | Should -Be $firstComment.id + $comments[0].body | Should -Not -BeNullOrEmpty + $comments[0].body_html | Should -BeNullOrEmpty + $comments[0].body_text | Should -BeNullOrEmpty + } + + It 'Should have the expected type and additional properties' { + $comments[0].PSObject.TypeNames[0] | Should -Be 'GitHub.GistComment' + $comments[0].GistCommentId | Should -Be $comments[0].id + $comments[0].GistId | Should -Be $gist.id + $comments[0].user.PSObject.TypeNames[0] | Should -Be 'GitHub.User' + } + + $null = $gist | New-GitHubGistComment -Body $body + $comments = @($gist | Get-GitHubGistComment -MediaType 'Full') + It 'Should have one comments so far' { + $comments.Count | Should -Be 2 + foreach ($comment in $comments) + { + $comment.body | Should -Not -BeNullOrEmpty + $comment.body_html | Should -Not -BeNullOrEmpty + $comment.body_text | Should -Not -BeNullOrEmpty + } + } + + It 'Should have the expected type and additional properties' { + foreach ($comment in $comments) + { + $comment.PSObject.TypeNames[0] | Should -Be 'GitHub.GistComment' + $comment.GistCommentId | Should -Be $comment.id + $comment.GistId | Should -Be $gist.id + $comment.user.PSObject.TypeNames[0] | Should -Be 'GitHub.User' + } + } + + $comment = Get-GitHubGistComment -Gist $gist.id -Comment $firstComment.id -MediaType 'Html' + It 'Should retrieve the specific comment' { + $comment.id | Should -Be $firstComment.id + $comment.body | Should -BeNullOrEmpty + $comment.body_html | Should -Not -BeNullOrEmpty + $comment.body_text | Should -BeNullOrEmpty + } + + It 'Should have the expected type and additional properties' { + $comment.PSObject.TypeNames[0] | Should -Be 'GitHub.GistComment' + $comment.GistCommentId | Should -Be $comment.id + $comment.GistId | Should -Be $gist.id + $comment.user.PSObject.TypeNames[0] | Should -Be 'GitHub.User' + } + + $comment = $firstComment | Get-GitHubGistComment -MediaType 'Html' + It 'Should retrieve the specific comment with the comment on the pipeline' { + $comment.id | Should -Be $firstComment.id + $comment.body | Should -BeNullOrEmpty + $comment.body_html | Should -Not -BeNullOrEmpty + $comment.body_text | Should -BeNullOrEmpty + } + + It 'Should have the expected type and additional properties' { + $comment.PSObject.TypeNames[0] | Should -Be 'GitHub.GistComment' + $comment.GistCommentId | Should -Be $comment.id + $comment.GistId | Should -Be $gist.id + $comment.user.PSObject.TypeNames[0] | Should -Be 'GitHub.User' + } + } + } + + Describe 'New-GitHubGistComment' { + BeforeAll { + $gist = New-GitHubGist -FileName 'sample.txt' -Content 'Sample text' + $body = 'Comment body' + } + + AfterAll { + $gist | Remove-GitHubGist -Force + } + + Context 'By parameters' { + $comment = New-GitHubGistComment -Gist $gist.id -Body $body + It 'Should have the expected result' { + $comment.body | Should -Be $body + } + + It 'Should have the expected type and additional properties' { + $comment.PSObject.TypeNames[0] | Should -Be 'GitHub.GistComment' + $comment.GistCommentId | Should -Be $comment.id + $comment.GistId | Should -Be $gist.id + $comment.user.PSObject.TypeNames[0] | Should -Be 'GitHub.User' + } + } + + Context 'Gist on the pipeline' { + $comment = $gist | New-GitHubGistComment -Body $body + It 'Should have the expected result' { + $comment.body | Should -Be $body + } + + It 'Should have the expected type and additional properties' { + $comment.PSObject.TypeNames[0] | Should -Be 'GitHub.GistComment' + $comment.GistCommentId | Should -Be $comment.id + $comment.GistId | Should -Be $gist.id + $comment.user.PSObject.TypeNames[0] | Should -Be 'GitHub.User' + } + } + } + + Describe 'New-GitHubGistComment' { + BeforeAll { + $gist = New-GitHubGist -Filename 'sample.txt' -Content 'Sample text' + $body = 'Comment body' + $updatedBody = 'Updated comment body' + } + + AfterAll { + $gist | Remove-GitHubGist -Force + } + + Context 'By parameters' { + $comment = New-GitHubGistComment -Gist $gist.id -Body $body + It 'Should have the expected result' { + $comment.body | Should -Be $body + } + + $comment = Set-GitHubGistComment -Gist $gist.id -Comment $comment.id -Body $updatedBody + It 'Should have the expected result' { + $comment.body | Should -Be $updatedBody + } + + It 'Should have the expected type and additional properties' { + $comment.PSObject.TypeNames[0] | Should -Be 'GitHub.GistComment' + $comment.GistCommentId | Should -Be $comment.id + $comment.GistId | Should -Be $gist.id + $comment.user.PSObject.TypeNames[0] | Should -Be 'GitHub.User' + } + } + + Context 'Gist on the pipeline' { + $comment = $gist | New-GitHubGistComment -Body $body + It 'Should have the expected result' { + $comment.body | Should -Be $body + } + + $comment = $gist | Set-GitHubGistComment -Comment $comment.id -Body $updatedBody + It 'Should have the expected result' { + $comment.body | Should -Be $updatedBody + } + + It 'Should have the expected type and additional properties' { + $comment.PSObject.TypeNames[0] | Should -Be 'GitHub.GistComment' + $comment.GistCommentId | Should -Be $comment.id + $comment.GistId | Should -Be $gist.id + $comment.user.PSObject.TypeNames[0] | Should -Be 'GitHub.User' + } + } + + Context 'Gist Comment on the pipeline' { + $comment = $gist | New-GitHubGistComment -Body $body + It 'Should have the expected result' { + $comment.body | Should -Be $body + } + + $comment = $comment | Set-GitHubGistComment -Body $updatedBody + It 'Should have the expected result' { + $comment.body | Should -Be $updatedBody + } + + It 'Should have the expected type and additional properties' { + $comment.PSObject.TypeNames[0] | Should -Be 'GitHub.GistComment' + $comment.GistCommentId | Should -Be $comment.id + $comment.GistId | Should -Be $gist.id + $comment.user.PSObject.TypeNames[0] | Should -Be 'GitHub.User' + } + } + } + + Describe 'Remove-GitHubGistComment' { + BeforeAll { + $gist = New-GitHubGist -FileName 'sample.txt' -Content 'Sample text' + $body = 'Comment body' + } + + AfterAll { + $gist | Remove-GitHubGist -Force + } + + Context 'By parameters' { + $comment = New-GitHubGistComment -Gist $gist.id -Body $body + + Remove-GitHubGistComment -Gist $gist.id -Comment $comment.id -Force + It 'Should be gone' { + { Get-GitHubGistComment -Gist $gist.id -Comment $comment.id } | Should -Throw + } + } + + Context 'Gist on the pipeline' { + $comment = $gist | New-GitHubGistComment -Body $body + + $gist | Remove-GitHubGistComment -Comment $comment.id -Force + It 'Should be gone' { + { $gist | Get-GitHubGistComment -Comment $comment.id } | Should -Throw + } + } + + Context 'Gist Comment on the pipeline' { + $comment = $gist | New-GitHubGistComment -Body $body + + $comment | Remove-GitHubGistComment -Force + It 'Should be gone' { + { $comment | Get-GitHubGistComment } | Should -Throw + } + } + } +} +finally +{ + if (Test-Path -Path $script:originalConfigFile -PathType Leaf) + { + # Restore the user's configuration to its pre-test state + Restore-GitHubConfiguration -Path $script:originalConfigFile + $script:originalConfigFile = $null + } +} diff --git a/Tests/GitHubGists.tests.ps1 b/Tests/GitHubGists.tests.ps1 new file mode 100644 index 00000000..67e06ccf --- /dev/null +++ b/Tests/GitHubGists.tests.ps1 @@ -0,0 +1,1193 @@ +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. + +<# +.Synopsis + Tests for GitHubGists.ps1 module +#> + +[CmdletBinding()] +[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseDeclaredVarsMoreThanAssignments', '', + Justification='Suppress false positives in Pester code blocks')] +param() + +# This is common test code setup logic for all Pester test files +$moduleRootPath = Split-Path -Path $PSScriptRoot -Parent +. (Join-Path -Path $moduleRootPath -ChildPath 'Tests\Common.ps1') + +filter New-LargeFile +{ +<# + .SYNOPSIS + Creates a large dummy file with random conntent + + .DESCRIPTION + Creates a large dummy file with random conntent + Credits for the random content creation logic goes to Robert Robelo + + .PARAMETER Path + The full path to the file to create. + + .PARAMETER SizeMB + The size of the random file to be genrated. Default is one MB + + .PARAMETER Type + The type of file should be created. + + .PARAMETER Force + Will allow this to overwrite the target file if it already exists. + + .EXAMPLE + New-LargeFile -Path C:\Temp\LF\bigfile.txt -SizeMB 10 +#> + + [CmdletBinding(SupportsShouldProcess)] + param( + [Parameter(ValueFromPipeline)] + [String] $Path, + + [ValidateRange(1, 5120)] + [UInt16] $SizeMB = 1, + + [ValidateSet('Text', 'Binary')] + [string] $Type = 'Text', + + [switch] $Force + ) + + $tempFile = New-TemporaryFile + + if ($Type -eq 'Text') + { + $streamWriter = New-Object -TypeName IO.StreamWriter -ArgumentList ($tempFile) + try + { + # get a 64 element Char[]; I added the - and \n to have 64 chars + [char[]]$chars = 'azertyuiopqsdfghjklmwxcvbnAZERTYUIOPQSDFGHJKLMWXCVBN0123456789-\n' + 1..$SizeMB | ForEach-Object { + # get 1MB of chars from 4 256KB strings + 1..4 | ForEach-Object { + $randomizedChars = $chars | Get-Random -Count $chars.Count + + # repeat random string 4096 times to get a 256KB string + $output = (-join $randomizedChars) * 4kb + + # write 256KB string to file + $streamWriter.Write($output) + + # release resources + Clear-Variable -Name @('randomizedChars', 'output') + } + } + } + catch + { + Remove-File -Path $tempFile -ErrorAction SilentlyContinue + } + finally + { + $streamWriter.Close() + $streamWriter.Dispose() + + # Force the immediate garbage collection of allocated resources + [GC]::Collect() + } + } + else + { + $content = New-Object -TypeName Byte[] -ArgumentList ($SizeMB * 1mb) + (New-Object -TypeName Random).NextBytes($content) + [IO.File]::WriteAllBytes($tempFile, $content) + } + + try + { + if ($PSBoundParameters.ContainsKey('Path')) + { + return (Move-Item -Path $tempFile -Destination $Path -Force:$Force) + } + else + { + return (Get-Item -Path $tempFile) + } + } + catch + { + Remove-File -Path $tempFile -ErrorAction SilentlyContinue + } +} + +try +{ + Describe 'Get-GitHubGist' { + Context 'Specific Gist' { + $id = '0831f3fbd83ac4d46451' # octocat/git-author-rewrite.sh + $gist = Get-GitHubGist -Gist $id + It 'Should be the expected gist' { + $gist.id | Should -Be $id + } + + It 'Should have the expected type and additional properties' { + $gist.PSObject.TypeNames[0] | Should -Be 'GitHub.Gist' + $gist.GistId | Should -Be $gist.id + $gist.owner.PSObject.TypeNames[0] | Should -Be 'GitHub.User' + $gist.history[0].PSObject.TypeNames[0] | Should -Be 'GitHub.GistCommit' + $gist.forks[0].PSObject.TypeNames[0] | Should -Be 'GitHub.GistFork' + } + + $gist = $gist | Get-GitHubGist + It 'Should be the expected gist with the gist on the pipeline' { + $gist.id | Should -Be $id + } + } + + Context 'Commits and specific Gist with Sha' { + $id = '0831f3fbd83ac4d46451' # octocat/git-author-rewrite.sh + + $gist = Get-GitHubGist -Gist $id + $commits = Get-GitHubGist -Gist $gist.id -Commits + + It 'Should have multiple commits' { + $commits.Count | Should -BeGreaterThan 1 + } + + It 'Should have the expected type and additional properties' { + foreach ($commit in $commits) + { + $commit.PSObject.TypeNames[0] | Should -Be 'GitHub.GistCommit' + $commit.GistId | Should -Be $gist.id + $commit.Sha | Should -Be $commit.version + $commit.user.PSObject.TypeNames[0] | Should -Be 'GitHub.User' + } + } + + $oldestSha = $commits | Sort-Object -Property 'committed_at' | Select-Object -First 1 + + $firstGistCommit = Get-GitHubGist -Gist $gist.id -Sha $oldestSha.version + It 'Should be the expected commit' { + $firstGistCommit.created_at | Should -Be $oldestSha.committed_at + } + + It 'Should have the expected type and additional properties' { + $firstGistCommit.PSObject.TypeNames[0] | Should -Be 'GitHub.Gist' + $firstGistCommit.GistId | Should -Be $firstGistCommit.id + $gist.owner.PSObject.TypeNames[0] | Should -Be 'GitHub.User' + $gist.history[0].PSObject.TypeNames[0] | Should -Be 'GitHub.GistCommit' + $gist.forks[0].PSObject.TypeNames[0] | Should -Be 'GitHub.GistFork' + } + + It 'Should fail if we specify Sha _and_ Commits' { + { Get-GitHubGist -Gist $gist.id -Commits -Sha $oldestSha.version } | Should -Throw + } + + It 'Should fail if we specify Sha _and_ Forks' { + { Get-GitHubGist -Gist $gist.id -Forks -Sha $oldestSha.version } | Should -Throw + } + + $firstGistCommit = $gist | Get-GitHubGist -Sha $oldestSha.version + It 'Should be the expected gist commit with the gist on the pipeline' { + $firstGistCommit.created_at | Should -Be $oldestSha.committed_at + } + + $firstGistCommit = $firstGistCommit | Get-GitHubGist + It 'Should be the expected gist commit with the gist commit on the pipeline' { + $firstGistCommit.created_at | Should -Be $oldestSha.committed_at + } + } + + Context 'Forks' { + $id = '0831f3fbd83ac4d46451' # octocat/git-author-rewrite.sh + + $gist = Get-GitHubGist -Gist $id + $forks = Get-GitHubGist -Gist $gist.id -Forks + + It 'Should have multiple forks' { + $forks.Count | Should -BeGreaterThan 1 + } + + It 'Should have the expected type and additional properties' { + foreach ($fork in $forks) + { + $fork.PSObject.TypeNames[0] | Should -Be 'GitHub.GistFork' + $fork.GistId | Should -Be $fork.id + $fork.owner.PSObject.TypeNames[0] | Should -Be 'GitHub.User' + } + } + + $forks = $gist | Get-GitHubGist -Forks + + It 'Should have multiple forks when gist is on the pipeline' { + $forks.Count | Should -BeGreaterThan 1 + } + } + + Context 'All gists for a specific user' { + $username = 'octocat' + $gists = Get-GitHubGist -UserName $username + + It 'Should have multiple gists' { + $gists.Count | Should -BeGreaterThan 1 + } + + It 'Should have the expected type and additional properties' { + foreach ($gist in $gists) + { + $gist.PSObject.TypeNames[0] | Should -Be 'GitHub.GistSummary' + $gist.GistId | Should -Be $gist.id + $gist.owner.PSObject.TypeNames[0] | Should -Be 'GitHub.User' + } + } + + $since = (Get-Date -Date '01/01/2016') + $sinceGists = Get-GitHubGist -UserName $username -Since $since + It 'Should have fewer results with using the since parameter' { + $sinceGists.Count | Should -BeGreaterThan 0 + $sinceGists.Count | Should -BeLessThan $gists.Count + } + } + + Context 'All gists for the current authenticated user' { + $gist = New-GitHubGist -Filename 'sample.txt' -Content 'Sample text' + $gists = @(Get-GitHubGist) + It 'Should at least one gist including the one just created' { + $gists.Count | Should -BeGreaterOrEqual 1 + $gists.id | Should -Contain $gist.id + } + + It 'Should have the expected type and additional properties' { + foreach ($gist in $gists) + { + $gist.PSObject.TypeNames[0] | Should -Be 'GitHub.GistSummary' + $gist.GistId | Should -Be $gist.id + $gist.owner.PSObject.TypeNames[0] | Should -Be 'GitHub.User' + } + } + + It 'Should be removed' { + { $gist | Remove-GitHubGist -Force } | Should -Not -Throw + } + } + + Context 'All gists for the current authenticated user, but not authenticated' { + # This would just be testing that an exception is thrown. + # There's no easy way to cover unauthenticated sessions in the UT's, + # so we'll just accept the lower code coverage here. + } + + Context 'All starred gists for the current authenticated user' { + $id = '0831f3fbd83ac4d46451' # octocat/git-author-rewrite.sh + Add-GitHubGistStar -Gist $id + + $gists = @(Get-GitHubGist -Starred) + It 'Should include the one we just starred' { + $gists.Count | Should -BeGreaterOrEqual 1 + $gists.id | Should -Contain $id + } + + Remove-GitHubGistStar -Gist $id + } + + Context 'All starred gists for the current authenticated user, but not authenticated' { + # This would just be testing that an exception is thrown. + # There's no easy way to cover unauthenticated sessions in the UT's, + # so we'll just accept the lower code coverage here. + } + + Context 'All public gists' { + # This would require 100 queries, taking over 2 minutes. + # Given the limited additional value that we'd get from this additional test relative + # to the time it would take to execute, we'll just accept the lower code coverage here. + } + } + + Describe 'Get-GitHubGist/Download' { + BeforeAll { + # To get access to New-TemporaryDirectory + $moduleRootPath = Split-Path -Path $PSScriptRoot -Parent + . (Join-Path -Path $moduleRootPath -ChildPath 'Helpers.ps1') + $tempPath = New-TemporaryDirectory + } + + AfterAll { + if (Get-Variable -Name tempPath -ErrorAction SilentlyContinue) + { + Remove-Item -Path $tempPath -Recurse -ErrorAction SilentlyContinue -Force + } + } + + Context 'Download gist content' { + BeforeAll { + $tempFile = New-TemporaryFile + $fileA = "$($tempFile.FullName).ps1" + Move-Item -Path $tempFile -Destination $fileA + $fileAName = (Get-Item -Path $fileA).Name + $fileAContent = 'fileA content' + Out-File -FilePath $fileA -InputObject $fileAContent -Encoding utf8 + + $tempFile = New-TemporaryFile + $fileB = "$($tempFile.FullName).txt" + Move-Item -Path $tempFile -Destination $fileB + $fileBName = (Get-Item -Path $fileB).Name + $fileBContent = 'fileB content' + Out-File -FilePath $fileB -InputObject $fileBContent -Encoding utf8 + + $tempFile = New-LargeFile -SizeMB 1 + $twoMegFile = "$($tempFile.FullName).bin" + Move-Item -Path $tempFile -Destination $twoMegFile + $twoMegFileName = (Get-Item -Path $twoMegFile).Name + + $gist = @($fileA, $fileB, $twoMegFile) | New-GitHubGist + } + + AfterAll { + $gist | Remove-GitHubGist -Force + @($fileA, $fileB, $twoMegFile) | + Remove-Item -Force -ErrorAction SilentlyContinue | Out-Null + } + + It 'Should have no files at the download path' { + @(Get-ChildItem -Path $tempPath).Count | Should -Be 0 + } + + Get-GitHubGist -Gist $gist.id -Path $tempPath + It 'Should download all of the files' { + @(Get-ChildItem -Path $tempPath).Count | Should -Be 3 + [System.IO.File]::ReadAllText($fileA).Trim() | + Should -Be ([System.IO.File]::ReadAllText((Join-Path -Path $tempPath -ChildPath $fileAName)).Trim()) + [System.IO.File]::ReadAllText($fileB).Trim() | + Should -Be ([System.IO.File]::ReadAllText((Join-Path -Path $tempPath -ChildPath $fileBName)).Trim()) + (Get-FileHash -Path $twoMegFile).Hash | + Should -Be (Get-FileHash -Path (Join-Path -Path $tempPath -ChildPath $twoMegFileName)).Hash + } + + $gist | Get-GitHubGist -Path $tempPath -Force + It 'Should download all of the files with the gist on the pipeline and -Force' { + @(Get-ChildItem -Path $tempPath).Count | Should -Be 3 + [System.IO.File]::ReadAllText($fileA).Trim() | + Should -Be ([System.IO.File]::ReadAllText((Join-Path -Path $tempPath -ChildPath $fileAName)).Trim()) + [System.IO.File]::ReadAllText($fileB).Trim() | + Should -Be ([System.IO.File]::ReadAllText((Join-Path -Path $tempPath -ChildPath $fileBName)).Trim()) + (Get-FileHash -Path $twoMegFile).Hash | + Should -Be (Get-FileHash -Path (Join-Path -Path $tempPath -ChildPath $twoMegFileName)).Hash + } + } + + Context 'More than 300 files' { + BeforeAll { + $files = @() + 1..301 | ForEach-Object { + $tempFile = New-TemporaryFile + $file = "$($tempFile.FullName)-$_.ps1" + Move-Item -Path $tempFile -Destination $file + $fileContent = "file-$_ content" + Out-File -FilePath $file -InputObject $fileContent -Encoding utf8 + $files += $file + } + } + + AfterAll { + $files | Remove-Item -Force -ErrorAction SilentlyContinue | Out-Null + } + + # May want to consider skipping this test. + # It works just fine, but takes 26 second to execute. + # (May not be worth it for the moderate improvement to code coverage.) + It 'Should throw an exception because there are too many files' { + $gist = $files | New-GitHubGist + { $gist | Get-GitHubGist -Path $tempPath -Force } | Should -Throw + $gist | Remove-GitHubGist -Force + } + + } + + Context 'Download gist content' { + BeforeAll { + $tempFile = New-LargeFile -SizeMB 10 + $tenMegFile = "$($tempFile.FullName).bin" + Move-Item -Path $tempFile -Destination $tenMegFile + $tenMegFileName = (Get-Item -Path $tenMegFile).Name + } + + AfterAll { + @($tenMegFile) | + Remove-Item -Force -ErrorAction SilentlyContinue | Out-Null + } + + # May want to consider skipping this test. + # It works just fine, but takes 26 second to execute. + # (May not be worth it for the moderate improvement to code coverage.) + It 'Should throw an exception because the file is too large to download' { + $gist = $tenMegFile | New-GitHubGist + { $gist | Get-GitHubGist -Path $tempPath -Force } | Should -Throw + $gist | Remove-GitHubGist -Force + } + } + } + + Describe 'Remove-GitHubGist' { + Context 'With parameters' { + $gist = New-GitHubGist -FileName 'sample.txt' -Content 'Sample text' + It 'Should be there' { + { Get-GitHubGist -Gist $gist.id } | Should -Not -Throw + } + + It 'Should remove the gist successfully' { + { Remove-GitHubGist -Gist $gist.id -Force } | Should -Not -Throw + } + + It 'Should be removed' { + { Get-GitHubGist -Gist $gist.id } | Should -Throw + } + } + + Context 'With the gist on the pipeline' { + $gist = New-GitHubGist -FileName 'sample.txt' -Content 'Sample text' + It 'Should be there' { + { $gist | Get-GitHubGist } | Should -Not -Throw + } + + It 'Should remove the gist successfully' { + { $gist | Remove-GitHubGist -Force } | Should -Not -Throw + } + + It 'Should be removed' { + { $gist | Get-GitHubGist } | Should -Throw + } + } + } + + Describe 'Copy-GitHubGist' { + BeforeAll { + $originalGist = Get-GitHubGist -Gist '1169852' # octocat/test.cs + } + + Context 'By parameters' { + $gist = Copy-GitHubGist -Gist $originalGist.id + It 'Should have been forked' { + $gist.files.Count | Should -Be $originalGist.files.Count + foreach ($file in $gist.files) + { + $originalFile = $originalGist.files | + Where-Object { $_.filename -eq $file.filename } + $file.filename | Should -Be $originalFile.filename + $file.size | Should -Be $originalFile.size + } + } + + It 'Should have the expected additional type and properties' { + $gist.PSObject.TypeNames[0] | Should -Be 'GitHub.GistSummary' + $gist.GistId | Should -Be $gist.id + $gist.owner.PSObject.TypeNames[0] | Should -Be 'GitHub.User' + } + + It 'Should be removed' { + { Remove-GitHubGist -Gist $gist.id -Force } | Should -Not -Throw + } + } + + Context 'Gist on the pipeline' { + $gist = $originalGist | Copy-GitHubGist + It 'Should have been forked' { + $gist.files.Count | Should -Be $originalGist.files.Count + foreach ($file in $gist.files) + { + $originalFile = $originalGist.files | + Where-Object { $_.filename -eq $file.filename } + $file.filename | Should -Be $originalFile.filename + $file.size | Should -Be $originalFile.size + } + } + + It 'Should have the expected additional type and properties' { + $gist.PSObject.TypeNames[0] | Should -Be 'GitHub.GistSummary' + $gist.GistId | Should -Be $gist.id + $gist.owner.PSObject.TypeNames[0] | Should -Be 'GitHub.User' + } + + It 'Should be removed' { + { $gist | Remove-GitHubGist -Force } | Should -Not -Throw + } + } + } + + Describe 'Add/Remove/Set/Test-GitHubGistStar' { + BeforeAll { + $gist = New-GitHubGist -FileName 'sample.txt' -Content 'Sample text' + } + + AfterAll { + $gist | Remove-GitHubGist -Force + } + + Context 'With parameters' { + $starred = Test-GitHubGistStar -Gist $gist.id + It 'Should not be starred yet' { + $starred | Should -BeFalse + } + + Add-GitHubGistStar -Gist $gist.id + $starred = Test-GitHubGistStar -Gist $gist.id + It 'Should now be starred' { + $starred | Should -BeTrue + } + + Remove-GitHubGistStar -Gist $gist.id + $starred = Test-GitHubGistStar -Gist $gist.id + It 'Should no longer be starred' { + $starred | Should -BeFalse + } + + Set-GitHubGistStar -Gist $gist.id -Star + $starred = Test-GitHubGistStar -Gist $gist.id + It 'Should now be starred' { + $starred | Should -BeTrue + } + + Set-GitHubGistStar -Gist $gist.id + $starred = Test-GitHubGistStar -Gist $gist.id + It 'Should no longer be starred' { + $starred | Should -BeFalse + } + } + + Context 'With the gist on the pipeline' { + $starred = $gist | Test-GitHubGistStar + It 'Should not be starred yet' { + $starred | Should -BeFalse + } + + $gist | Add-GitHubGistStar + $starred = $gist | Test-GitHubGistStar + It 'Should now be starred' { + $starred | Should -BeTrue + } + + $gist | Remove-GitHubGistStar + $starred = $gist | Test-GitHubGistStar + It 'Should no longer be starred' { + $starred | Should -BeFalse + } + + $gist | Set-GitHubGistStar -Star + $starred = $gist | Test-GitHubGistStar + It 'Should now be starred' { + $starred | Should -BeTrue + } + + $gist | Set-GitHubGistStar + $starred = $gist | Test-GitHubGistStar + It 'Should no longer be starred' { + $starred | Should -BeFalse + } + } + } + + Describe 'New-GitHubGist' { + Context 'By content' { + BeforeAll { + $content = 'This is my content' + $filename = 'sample.txt' + $description = 'my description' + } + + $gist = New-GitHubGist -FileName $filename -Content $content -Public + It 'Should have the expected result' { + $gist.public | Should -BeTrue + $gist.description | Should -BeNullOrEmpty + $gist.files.$filename.content | Should -Be $content + } + + It 'Should have the expected type and additional properties' { + $gist.PSObject.TypeNames[0] | Should -Be 'GitHub.Gist' + $gist.GistId | Should -Be $gist.id + $gist.owner.PSObject.TypeNames[0] | Should -Be 'GitHub.User' + } + + It 'Should be removed' { + { $gist | Remove-GitHubGist -Force } | Should -Not -Throw + } + + $gist = New-GitHubGist -FileName $filename -Content $content -Description $description -Public:$false + It 'Should have the expected result' { + $gist.public | Should -BeFalse + $gist.description | Should -Be $description + $gist.files.$filename.content | Should -Be $content + } + + It 'Should have the expected type and additional properties' { + $gist.PSObject.TypeNames[0] | Should -Be 'GitHub.Gist' + $gist.GistId | Should -Be $gist.id + $gist.owner.PSObject.TypeNames[0] | Should -Be 'GitHub.User' + } + + It 'Should error if file starts with "gistfile"' { + { New-GitHubGist -FileName 'gistfile1' -Content $content } | Should -Throw + } + + It 'Should be removed' { + { $gist | Remove-GitHubGist -Force } | Should -Not -Throw + } + } + + Context 'By files' { + BeforeAll { + $tempFile = New-TemporaryFile + $fileA = "$($tempFile.FullName).ps1" + Move-Item -Path $tempFile -Destination $fileA + $fileAName = (Get-Item -Path $fileA).Name + $fileAContent = 'fileA content' + Out-File -FilePath $fileA -InputObject $fileAContent -Encoding utf8 + + $tempFile = New-TemporaryFile + $fileB = "$($tempFile.FullName).txt" + Move-Item -Path $tempFile -Destination $fileB + $fileBName = (Get-Item -Path $fileB).Name + $fileBContent = 'fileB content' + Out-File -FilePath $fileB -InputObject $fileBContent -Encoding utf8 + + $description = 'my description' + } + + AfterAll { + @($fileA, $fileB) | Remove-Item -Force -ErrorAction SilentlyContinue | Out-Null + } + + $gist = New-GitHubGist -File @($fileA, $fileB) -Public + It 'Should have the expected result' { + $gist.public | Should -BeTrue + $gist.description | Should -BeNullOrEmpty + ($gist.files | Get-Member -Type NoteProperty).Count | Should -Be 2 + $gist.files.$fileAName.content.Trim() | Should -Be $fileAContent + $gist.files.$fileBName.content.Trim() | Should -Be $fileBContent + } + + It 'Should have the expected type and additional properties' { + $gist.PSObject.TypeNames[0] | Should -Be 'GitHub.Gist' + $gist.GistId | Should -Be $gist.id + $gist.owner.PSObject.TypeNames[0] | Should -Be 'GitHub.User' + } + + It 'Should be removed' { + { $gist | Remove-GitHubGist -Force } | Should -Not -Throw + } + + $gist = New-GitHubGist -File @($fileA, $fileB) -Description $description -Public:$false + It 'Should have the expected result' { + $gist.public | Should -BeFalse + $gist.description | Should -Be $description + ($gist.files | Get-Member -Type NoteProperty).Count | Should -Be 2 + $gist.files.$fileAName.content.Trim() | Should -Be $fileAContent + $gist.files.$fileBName.content.Trim() | Should -Be $fileBContent + } + + It 'Should be removed' { + { $gist | Remove-GitHubGist -Force } | Should -Not -Throw + } + + $gist = @($fileA, $fileB) | New-GitHubGist -Description $description -Public:$false + It 'Should have the expected result with the files on the pipeline' { + $gist.public | Should -BeFalse + $gist.description | Should -Be $description + ($gist.files | Get-Member -Type NoteProperty).Count | Should -Be 2 + $gist.files.$fileAName.content.Trim() | Should -Be $fileAContent + $gist.files.$fileBName.content.Trim() | Should -Be $fileBContent + } + } + } + + Describe 'Set-GitHubGist' { + BeforeAll { + $fileAName = 'foo.txt' + $fileAContent = 'foo content' + $fileAUpdatedContent = 'foo updated content' + $fileANewName = 'gamma.txt' + + $fileBName = 'bar.txt' + $fileBContent = 'bar content' + $fileBUpdatedContent = 'bar updated content' + + $fileCName = 'alpha.txt' + $fileCContent = 'alpha content' + $fileCUpdatedContent = 'alpha updated content' + $fileCNewName = 'gamma.txt' + + $tempFile = New-TemporaryFile + $fileD = "$($tempFile.FullName).txt" + Move-Item -Path $tempFile -Destination $fileD + $fileDName = (Get-Item -Path $fileD).Name + $fileDContent = 'fileD content' + Out-File -FilePath $fileD -InputObject $fileDContent -Encoding utf8 + $fileDUpdatedContent = 'fileD updated content' + + $description = 'my description' + $updatedDescription = 'updated description' + } + + AfterAll { + @($fileD) | Remove-Item -Force -ErrorAction SilentlyContinue | Out-Null + } + + Context 'With parameters' { + BeforeAll { + $gist = New-GitHubGist -FileName $fileAName -Content $fileAContent -Description $description + } + + AfterAll { + $gist | Remove-GitHubGist -Force + } + + $gist = Set-GitHubGistFile -Gist $gist.id -FileName $fileBName -Content $fileBContent + It 'Should be in the expected, original state' { + $gist.description | Should -Be $description + ($gist.files | Get-Member -Type NoteProperty).Count | Should -Be 2 + $gist.files.$fileAName.content | Should -Be $fileAContent + $gist.files.$fileBName.content | Should -Be $fileBContent + } + + $setParams = @{ + Gist = $gist.id + Description = $updatedDescription + Delete = @($fileBName) + Update = @{ + $fileAName = @{ + fileName = $fileANewName + content = $fileAUpdatedContent + } + $fileCName = @{ content = $fileCContent } + $fileDName = @{ filePath = $fileD } + } + } + + $gist = Set-GitHubGist @setParams -Force + It 'Should have been properly updated' { + $gist.description | Should -Be $updatedDescription + ($gist.files | Get-Member -Type NoteProperty).Count | Should -Be 3 + $gist.files.$fileAName | Should -BeNullOrEmpty + $gist.files.$fileANewName.content | Should -Be $fileAContent + $gist.files.$fileBName | Should -BeNullOrEmpty + $gist.files.$fileCName.content | Should -Be $fileCContent + $gist.files.$fileDName.content.Trim() | Should -Be $fileDContent + } + + It 'Should have the expected type and additional properties' { + $gist.PSObject.TypeNames[0] | Should -Be 'GitHub.Gist' + $gist.GistId | Should -Be $gist.id + $gist.owner.PSObject.TypeNames[0] | Should -Be 'GitHub.User' + } + + $setParams = @{ + Gist = $gist.id + Update = @{ + $fileDName = @{ + content = 'updated content' + filePath = $fileD + } + } + } + + It 'Should throw if updating a file with both a filePath and content' { + { $gist = Set-GitHubGist @setParams } | Should -Throw + } + } + + Context 'With the gist on the pipeline' { + BeforeAll { + $gist = New-GitHubGist -FileName $fileAName -Content $fileAContent -Description $description + } + + AfterAll { + $gist | Remove-GitHubGist -Force + } + + $gist = Set-GitHubGistFile -Gist $gist.id -FileName $fileBName -Content $fileBContent + It 'Should be in the expected, original state' { + $gist.description | Should -Be $description + ($gist.files | Get-Member -Type NoteProperty).Count | Should -Be 2 + $gist.files.$fileAName.content | Should -Be $fileAContent + $gist.files.$fileBName.content | Should -Be $fileBContent + } + + $setParams = @{ + Description = $updatedDescription + Delete = @($fileBName) + Update = @{ + $fileAName = @{ + fileName = $fileANewName + content = $fileAUpdatedContent + } + $fileCName = @{ content = $fileCContent } + $fileDName = @{ filePath = $fileD } + } + } + + $gist = $gist | Set-GitHubGist @setParams -Confirm:$false + It 'Should have been properly updated' { + $gist.description | Should -Be $updatedDescription + ($gist.files | Get-Member -Type NoteProperty).Count | Should -Be 3 + $gist.files.$fileAName | Should -BeNullOrEmpty + $gist.files.$fileANewName.content | Should -Be $fileAContent + $gist.files.$fileBName | Should -BeNullOrEmpty + $gist.files.$fileCName.content | Should -Be $fileCContent + $gist.files.$fileDName.content.Trim() | Should -Be $fileDContent + } + + It 'Should have the expected type and additional properties' { + $gist.PSObject.TypeNames[0] | Should -Be 'GitHub.Gist' + $gist.GistId | Should -Be $gist.id + $gist.owner.PSObject.TypeNames[0] | Should -Be 'GitHub.User' + } + + $setParams = @{ + Update = @{ + $fileDName = @{ + content = 'updated content' + filePath = $fileD + } + } + } + + It 'Should throw if updating a file with both a filePath and content' { + { $gist = $gist | Set-GitHubGist @setParams } | Should -Throw + } + } + } + + Describe 'Set-GitHubGistFile' { + BeforeAll { + $origFileName = 'foo.txt' + $origContent = 'original content' + $updatedOrigContent = 'updated content' + + $newFileName = 'bar.txt' + $newContent = 'new content' + + $gist = New-GitHubGist -FileName $origFileName -Content $origContent + } + + AfterAll { + $gist | Remove-GitHubGist -Force + } + + Context 'By content with parameters' { + $gist = Set-GitHubGistFile -Gist $gist.id -FileName $origFileName -Content $updatedOrigContent + It 'Should have the expected result' { + $gist.files.$origFileName.content | Should -Be $updatedOrigContent + $gist.files.$newFileName | Should -BeNullOrEmpty + } + + It 'Should have the expected type and additional properties' { + $gist.PSObject.TypeNames[0] | Should -Be 'GitHub.Gist' + $gist.GistId | Should -Be $gist.id + $gist.owner.PSObject.TypeNames[0] | Should -Be 'GitHub.User' + } + + $gist = Set-GitHubGistFile -Gist $gist.id -FileName $newFileName -Content $newContent + It 'Should have the expected result' { + $gist.files.$origFileName.content | Should -Be $updatedOrigContent + $gist.files.$newFileName.content | Should -Be $newContent + } + + It 'Should have the expected type and additional properties' { + $gist.PSObject.TypeNames[0] | Should -Be 'GitHub.Gist' + $gist.GistId | Should -Be $gist.id + $gist.owner.PSObject.TypeNames[0] | Should -Be 'GitHub.User' + } + + $gist = Set-GitHubGistFile -Gist $gist.id -FileName $origFileName -Content $origContent + It 'Should remove the new file' { + { $gist | Remove-GitHubGistFile -FileName $newFileName -Force } | Should -Not -Throw + } + } + + Context 'By content with the gist on the pipeline' { + $gist = $gist | Set-GitHubGistFile -FileName $origFileName -Content $updatedOrigContent + It 'Should have the expected result' { + $gist.files.$origFileName.content | Should -Be $updatedOrigContent + $gist.files.$newFileName | Should -BeNullOrEmpty + } + + It 'Should have the expected type and additional properties' { + $gist.PSObject.TypeNames[0] | Should -Be 'GitHub.Gist' + $gist.GistId | Should -Be $gist.id + $gist.owner.PSObject.TypeNames[0] | Should -Be 'GitHub.User' + } + + $gist = $gist | Set-GitHubGistFile -FileName $newFileName -Content $newContent + It 'Should have the expected result' { + $gist.files.$origFileName.content | Should -Be $updatedOrigContent + $gist.files.$newFileName.content | Should -Be $newContent + } + + It 'Should have the expected type and additional properties' { + $gist.PSObject.TypeNames[0] | Should -Be 'GitHub.Gist' + $gist.GistId | Should -Be $gist.id + $gist.owner.PSObject.TypeNames[0] | Should -Be 'GitHub.User' + } + + $gist = Set-GitHubGistFile -Gist $gist.id -FileName $origFileName -Content $origContent + It 'Should remove the new file' { + { $gist | Remove-GitHubGistFile -FileName $newFileName -Force } | Should -Not -Throw + } + } + + Context 'By files with parameters' { + BeforeAll { + $tempFile = New-TemporaryFile + $fileA = "$($tempFile.FullName).txt" + Move-Item -Path $tempFile -Destination $fileA + $fileAName = (Get-Item -Path $fileA).Name + $fileAContent = 'fileA content' + Out-File -FilePath $fileA -InputObject $fileAContent -Encoding utf8 + $fileAUpdatedContent = 'fileA content updated' + } + + AfterAll { + @($fileA) | Remove-Item -Force -ErrorAction SilentlyContinue | Out-Null + } + + $gist = Set-GitHubGistFile -Gist $gist.id -File $fileA + It 'Should have the expected result' { + ($gist.files | Get-Member -Type NoteProperty).Count | Should -Be 2 + $gist.files.$origFileName.content | Should -Be $origContent + $gist.files.$fileAName.content.Trim() | Should -Be $fileAContent + } + + It 'Should have the expected type and additional properties' { + $gist.PSObject.TypeNames[0] | Should -Be 'GitHub.Gist' + $gist.GistId | Should -Be $gist.id + $gist.owner.PSObject.TypeNames[0] | Should -Be 'GitHub.User' + } + + Out-File -FilePath $fileA -InputObject $fileAUpdatedContent -Encoding utf8 + $gist = Set-GitHubGistFile -Gist $gist.id -File $fileA + It 'Should have the expected result' { + ($gist.files | Get-Member -Type NoteProperty).Count | Should -Be 2 + $gist.files.$origFileName.content | Should -Be $origContent + $gist.files.$fileAName.content.Trim() | Should -Be $fileAUpdatedContent + } + + It 'Should have the expected type and additional properties' { + $gist.PSObject.TypeNames[0] | Should -Be 'GitHub.Gist' + $gist.GistId | Should -Be $gist.id + $gist.owner.PSObject.TypeNames[0] | Should -Be 'GitHub.User' + } + + $gist = Set-GitHubGistFile -Gist $gist.id -FileName $origFileName -Content $origContent + It 'Should remove the new file' { + { $gist | Remove-GitHubGistFile -FileName $fileAName -Force } | Should -Not -Throw + } + } + + Context 'By files with the gist on the pipeline' { + BeforeAll { + $tempFile = New-TemporaryFile + $fileA = "$($tempFile.FullName).txt" + Move-Item -Path $tempFile -Destination $fileA + $fileAName = (Get-Item -Path $fileA).Name + $fileAContent = 'fileA content' + Out-File -FilePath $fileA -InputObject $fileAContent -Encoding utf8 + $fileAUpdatedContent = 'fileA content updated' + } + + AfterAll { + @($fileA) | Remove-Item -Force -ErrorAction SilentlyContinue | Out-Null + } + + $gist = $gist | Set-GitHubGistFile -File $fileA + It 'Should have the expected result' { + ($gist.files | Get-Member -Type NoteProperty).Count | Should -Be 2 + $gist.files.$origFileName.content | Should -Be $origContent + $gist.files.$fileAName.content.Trim() | Should -Be $fileAContent + } + + It 'Should have the expected type and additional properties' { + $gist.PSObject.TypeNames[0] | Should -Be 'GitHub.Gist' + $gist.GistId | Should -Be $gist.id + $gist.owner.PSObject.TypeNames[0] | Should -Be 'GitHub.User' + } + + Out-File -FilePath $fileA -InputObject $fileAUpdatedContent -Encoding utf8 + $gist = $gist | Set-GitHubGistFile -File $fileA + It 'Should have the expected result' { + ($gist.files | Get-Member -Type NoteProperty).Count | Should -Be 2 + $gist.files.$origFileName.content | Should -Be $origContent + $gist.files.$fileAName.content.Trim() | Should -Be $fileAUpdatedContent + } + + It 'Should have the expected type and additional properties' { + $gist.PSObject.TypeNames[0] | Should -Be 'GitHub.Gist' + $gist.GistId | Should -Be $gist.id + $gist.owner.PSObject.TypeNames[0] | Should -Be 'GitHub.User' + } + + $gist = Set-GitHubGistFile -Gist $gist.id -FileName $origFileName -Content $origContent + It 'Should remove the new file' { + { $gist | Remove-GitHubGistFile -FileName $fileAName -Force } | Should -Not -Throw + } + } + + Context 'By files with the file on the pipeline' { + BeforeAll { + $tempFile = New-TemporaryFile + $fileA = "$($tempFile.FullName).txt" + Move-Item -Path $tempFile -Destination $fileA + $fileAName = (Get-Item -Path $fileA).Name + $fileAContent = 'fileA content' + Out-File -FilePath $fileA -InputObject $fileAContent -Encoding utf8 + $fileAUpdatedContent = 'fileA content updated' + } + + AfterAll { + @($fileA) | Remove-Item -Force -ErrorAction SilentlyContinue | Out-Null + } + + $gist = $fileA | Set-GitHubGistFile -Gist $gist.id + It 'Should have the expected result' { + ($gist.files | Get-Member -Type NoteProperty).Count | Should -Be 2 + $gist.files.$origFileName.content | Should -Be $origContent + $gist.files.$fileAName.content.Trim() | Should -Be $fileAContent + } + + It 'Should have the expected type and additional properties' { + $gist.PSObject.TypeNames[0] | Should -Be 'GitHub.Gist' + $gist.GistId | Should -Be $gist.id + $gist.owner.PSObject.TypeNames[0] | Should -Be 'GitHub.User' + } + + Out-File -FilePath $fileA -InputObject $fileAUpdatedContent -Encoding utf8 + $gist = $fileA | Set-GitHubGistFile -Gist $gist.id + It 'Should have the expected result' { + ($gist.files | Get-Member -Type NoteProperty).Count | Should -Be 2 + $gist.files.$origFileName.content | Should -Be $origContent + $gist.files.$fileAName.content.Trim() | Should -Be $fileAUpdatedContent + } + + It 'Should have the expected type and additional properties' { + $gist.PSObject.TypeNames[0] | Should -Be 'GitHub.Gist' + $gist.GistId | Should -Be $gist.id + $gist.owner.PSObject.TypeNames[0] | Should -Be 'GitHub.User' + } + + $gist = Set-GitHubGistFile -Gist $gist.id -FileName $origFileName -Content $origContent + It 'Should remove the new file' { + { $gist | Remove-GitHubGistFile -FileName $fileAName -Force } | Should -Not -Throw + } + } + } + + Describe 'Rename-GitHubGistFile' { + BeforeAll { + $originalName = 'foo.txt' + $newName = 'bar.txt' + $content = 'sample content' + } + + Context 'With parameters' { + $gist = New-GitHubGist -FileName $originalName -Content $content + It 'Should have the expected file' { + $gist.files.$originalName.content | Should -Be $content + $gist.files.$newName | Should -BeNullOrEmpty + } + + $gist = Rename-GitHubGistFile -Gist $gist.id -FileName $originalName -NewName $newName + It 'Should have been renamed' { + $gist.files.$originalName | Should -BeNullOrEmpty + $gist.files.$newName.content | Should -Be $content + } + + It 'Should have the expected additional type and properties' { + $gist.PSObject.TypeNames[0] | Should -Be 'GitHub.Gist' + $gist.GistId | Should -Be $gist.id + $gist.owner.PSObject.TypeNames[0] | Should -Be 'GitHub.User' + } + + It 'Should successfully remove the gist' { + { $gist | Remove-GitHubGist -Force } | Should -Not -Throw + } + } + + Context 'With the gist on the pipeline' { + $gist = New-GitHubGist -FileName $originalName -Content $content + It 'Should have the expected file' { + $gist.files.$originalName.content | Should -Be $content + $gist.files.$newName | Should -BeNullOrEmpty + } + + $gist = $gist | Rename-GitHubGistFile -FileName $originalName -NewName $newName + It 'Should have been renamed' { + $gist.files.$originalName | Should -BeNullOrEmpty + $gist.files.$newName.content | Should -Be $content + } + + It 'Should have the expected additional type and properties' { + $gist.PSObject.TypeNames[0] | Should -Be 'GitHub.Gist' + $gist.GistId | Should -Be $gist.id + $gist.owner.PSObject.TypeNames[0] | Should -Be 'GitHub.User' + } + + It 'Should successfully remove the gist' { + { $gist | Remove-GitHubGist -Force } | Should -Not -Throw + } + } + } + + Describe 'Remove-GitHubGistFile' { + BeforeAll { + $fileName = 'sample.txt' + $content = 'sample' + } + + Context 'With parameters' { + $gist = New-GitHubGist -FileName $fileName -Content $content + It 'Should have the expected file' { + $gist.files.$fileName | Should -Not -BeNullOrEmpty + } + + $gist = Remove-GitHubGistFile -Gist $gist.id -FileName $fileName -Force + It 'Should have been removed' { + $gist.files.$fileName | Should -BeNullOrEmpty + } + + It 'Should have the expected additional type and properties' { + $gist.PSObject.TypeNames[0] | Should -Be 'GitHub.Gist' + $gist.GistId | Should -Be $gist.id + $gist.owner.PSObject.TypeNames[0] | Should -Be 'GitHub.User' + } + + It 'Should successfully remove the gist' { + { $gist | Remove-GitHubGist -Force } | Should -Not -Throw + } + } + + Context 'With the gist on the pipeline' { + $gist = New-GitHubGist -FileName $fileName -Content $content + It 'Should have the expected file' { + $gist.files.$fileName | Should -Not -BeNullOrEmpty + } + + $gist = $gist | Remove-GitHubGistFile -FileName $fileName -Confirm:$false + It 'Should have been removed' { + $gist.files.$fileName | Should -BeNullOrEmpty + } + + It 'Should have the expected additional type and properties' { + $gist.PSObject.TypeNames[0] | Should -Be 'GitHub.Gist' + $gist.GistId | Should -Be $gist.id + $gist.owner.PSObject.TypeNames[0] | Should -Be 'GitHub.User' + } + + It 'Should successfully remove the gist' { + { $gist | Remove-GitHubGist -Force } | Should -Not -Throw + } + } + } +} +finally +{ + if (Test-Path -Path $script:originalConfigFile -PathType Leaf) + { + # Restore the user's configuration to its pre-test state + Restore-GitHubConfiguration -Path $script:originalConfigFile + $script:originalConfigFile = $null + } +} diff --git a/USAGE.md b/USAGE.md index 8e93a1c5..456c0186 100644 --- a/USAGE.md +++ b/USAGE.md @@ -97,6 +97,18 @@ * [Create a release asset](#create-a-release-asset) * [Update a release asset](#update-a-release-asset) * [Remove a release asset](#remove-a-release-asset) + * [Gists](#gists) + * [Getting gists](#getting-gists) + * [Download a gist](#download-a-gist) + * [Fork a gist](#fork-a-gist) + * [Creating a gist](#creating-a-gist) + * [Removing a gist](#removing-a-gist) + * [Updating a gist](#updating-a-gist) + * [Starring a gist](#starring-a-gist) + * [Getting gist comments](#getting-gist-comments) + * [Adding a gist comment](#adding-a-gist-comment) + * [Changing a gist comment](#changing-a-gist-comment) + * [Removing a gist comment](#removing-a-gist-comment) * [Advanced](#advanced) * [Migrating blog comments to GitHub issues](#migrating-blog-comments-to-github-issues) @@ -869,6 +881,169 @@ or with pipelining... ```powershell $asset | Remove-GitHubReleaseAsset -force + +---------- + +### Gists + +#### Getting gists +```powershell +# There are many options here: + +# 1. Getting all gists for the current authenticated user: +Get-GitHubGist + +# 1b. Getting all gists for the current authenticated user that were updated in the past 6 days. +Get-GitHubGist -Since ((Get-Date).AddDays(-6)) + +# 2. Get all starred gists for the current authenticated user +Get-GitHubGist -Starred + +# 3. Get all public gists for a specific user +Get-GitHubGist -UserName 'octocat' + +# 4. Get all public gists (well, the first 3000): +Get-GitHubGist -Public + +# 5. Get a specific gist +Get-GitHubGist -Gist '6cad326836d38bd3a7ae' + +# 5a. List all commits for a specific gist +Get-GitHubGist -Gist '6cad326836d38bd3a7ae' -Commits + +# 5b. Get a gist at a specific commit (Sha) +Get-GitHubGist -Gist '6cad326836d38bd3a7ae' -Sha 'de5b9b59d1f28206e8d646c7c8025e9809d0ed73' + +# 5c. Get all of the forks for a gist +Get-GitHubGist -Gist '6cad326836d38bd3a7ae' -Forks +``` + +#### Download a gist +```powershell +Get-GitHubGist -Gist '6cad326836d38bd3a7ae' -Path 'c:\users\octocat\downloads\gist\' +``` + +#### Fork a gist +```powershell +Fork-GitHubGist -Gist '6cad326836d38bd3a7ae' +``` + +#### Creating a gist +```powershell +# You can create a gist by specifying a single file's content in-line... +New-GitHubGist -FileName 'foo.txt' -Content 'foo content' + +# or by providing one or more files that should be part of the gist +New-GitHubGist -File @('c:\files\foo.txt', 'c:\files\bar.txt') +@('c:\files\foo.txt', 'c:\files\bar.txt') | New-GitHubGist +``` + +#### Removing a gist +```powershell +Remove-GitHubGist -Gist '6cad326836d38bd3a7ae' +``` + +#### Updating a gist +```powershell +$gist = New-GitHubGist -FileName 'foo.txt' -Content 'content' + +# The main method to use is Set-GitHubGist, however it is quite complicated. +$params = @{ + Description = 'new description' # modifies the description of the gist + Update = @{ + 'foo.txt' = @{ + fileName = 'alpha.txt' # Will rename foo.txt -> alpha.txt + content = 'updated content' # and will also update its content + } + 'bar.txt' = @{ + filePath = 'c:\files\bar.txt' # Will upload the content of bar.txt to the gist. + } + } + Delete = @('bar.txt') + Force = $true # avoid confirmation prompting due to the deletion +} + +Set-GitHubGist -Gist $gist.id @params + +# Therefore, you can use simpler helper methods to accomplish atomic tasks +Set-GistHubGistFile -Gist $gist.id -FileName 'foo.txt' -Content 'updated content' + +# This will update the text in the existing file 'foo.txt' and add the file 'bar.txt' +$gist | Set-GitHubGistFile -File ('c:\files\foo.txt', 'c:\files\bar.txt') + +Rename-GistHubGistFile -Gist $gist.id -FileName 'foo.txt' -NewName 'bar.txt' + +$gist | Remove-GitHubGistFile -FileName 'bar.txt' -Force + +``` + +#### Starring a gist +```powershell +$gistId = '6cad326836d38bd3a7ae' + +# All of these options will star the same gist +Star-GitHubGist -Gist $gistId +Add-GitHubGistStar -Gist $gistId +Set-GitHubGistStar -Gist $gistId -Star +Get-GitHubGist -Gist $gistId | Star-GitHubGist + +# All of these options will unstar the same gist +Unstar-GitHubGist -Gist $gistId +Remove-GitHubGistStar -Gist $gistId +Set-GitHubGistStar -Gist $gistId +Set-GitHubGistStar -Gist $gistId -Star:$false +Get-GitHubGist -Gist $gistId | Unstar-GitHubGist + +# All of these options will tell you if you have starred a gist +Test-GitHubGistStar -Gist $gistId +Get-GitHubGist -Gist $gistId | Test-GitHubGistStar +``` + +#### Getting gist comments +```powershell +$gistId = '6cad326836d38bd3a7ae' +$commentId = 1507813 + +# You can get all comments for a gist with any of these options: +Get-GitHubGistComment -Gist $gistId +Get-GitHubGist -Gist $gistId | Get-GitHubGistComment + +# You can retrieve an individual comment like this: +Get-GitHubGistComment -Gist $gistId -Comment $commentId +``` + +#### Adding a gist comment +```powershell +$gistId = '6cad326836d38bd3a7ae' + +New-GitHubGistComment -Gist $gistId -Body 'Hello World' + +# or with the pipeline +Get-GitHubGist -Gist $gistId | New-GitHubGistComment -Body 'Hello World' +``` + +#### Changing a gist comment +```powershell +$gistId = '6cad326836d38bd3a7ae' +$commentId = 1507813 + +Set-GitHubGistComment -Gist $gistId -Comment $commentId -Body 'Updated comment' + +# or with the pipeline +Get-GitHubGist -Gist $gistId -Comment $commentId | Set-GitHubGistComment -Body 'Updated comment' +``` + +#### Removing a gist comment +```powershell +$gistId = '6cad326836d38bd3a7ae' +$commentId = 1507813 + +# If you don't specify -Force, it will prompt for confirmation before it will delete the comment + +Remove-GitHubGistComment -Gist $gistId -Comment $commentId -Force + +# or with the pipeline +Get-GitHubGist -Gist $gistId -Comment $commentId | Remove-GitHubGistComment -Force ``` ---------- From 6503e577dbfc5979aba4176d6a09284175c3e32a Mon Sep 17 00:00:00 2001 From: Tyler Leonhardt Date: Tue, 26 May 2020 18:18:12 -0700 Subject: [PATCH 59/60] First attempt at GitHub Secrets --- GitHubSecrets.ps1 | 493 ++++++++++++++++++++++++++++++++++ PowerShellForGitHub.psd1 | 4 + Tests/GitHubSecrets.Tests.ps1 | 26 ++ 3 files changed, 523 insertions(+) create mode 100644 GitHubSecrets.ps1 create mode 100644 Tests/GitHubSecrets.Tests.ps1 diff --git a/GitHubSecrets.ps1 b/GitHubSecrets.ps1 new file mode 100644 index 00000000..5ddeb514 --- /dev/null +++ b/GitHubSecrets.ps1 @@ -0,0 +1,493 @@ +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. + +function Get-GitHubRepositoryPublicKey { +<# + .SYNOPSIS + Gets the public key for a given repository, which is needed to encrypt secrets. + + .DESCRIPTION + Gets the public key for a given repository, which is needed to encrypt secrets before creating or updating. + Anyone with read access to the repository can use this cmdlet. + If the repository is private you must use an access token with the repo scope. + GitHub Apps must have the secrets repository permission to use this cmdlet. + + .PARAMETER OwnerName + Owner of the repository. + If not supplied here, the DefaultOwnerName configuration property value will be used. + + .PARAMETER RepositoryName + Name of the repository. + If not supplied here, the DefaultRepositoryName configuration property value will be used. + + .PARAMETER Uri + Uri for the repository. + The OwnerName and RepositoryName will be extracted from here instead of needing to provide + them individually. + + .PARAMETER AccessToken + If provided, this will be used as the AccessToken for authentication with the + REST Api. Otherwise, will attempt to use the configured value or will run unauthenticated. + + .PARAMETER NoStatus + If this switch is specified, long-running commands will run on the main thread + with no commandline status update. When not specified, those commands run in + the background, enabling the command prompt to provide status information. + If not supplied here, the DefaultNoStatus configuration property value will be used. + + .EXAMPLE + Get-GitHubRepositoryPublicKey -OwnerName Microsoft -RepositoryName PowerShellForGitHub + + .EXAMPLE + Get-GitHubRepositoryPublicKey -Uri 'https://github.com/Microsoft/PowerShellForGitHub' +#> + [CmdletBinding( + SupportsShouldProcess, + DefaultParameterSetName='Elements')] + [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSShouldProcess", "", Justification="Methods called within here make use of PSShouldProcess, and the switch is passed on to them inherently.")] + param( + [Parameter(ParameterSetName='Elements')] + [string] $OwnerName, + + [Parameter(ParameterSetName='Elements')] + [string] $RepositoryName, + + [Parameter(Mandatory, ParameterSetName='Uri')] + [string] $Uri, + + [string] $AccessToken, + + [switch] $NoStatus + ) + + Write-InvocationLog + + $elements = Resolve-RepositoryElements + $OwnerName = $elements.ownerName + $RepositoryName = $elements.repositoryName + + $telemetryProperties = @{ + 'OwnerName' = (Get-PiiSafeString -PlainText $OwnerName) + 'RepositoryName' = (Get-PiiSafeString -PlainText $RepositoryName) + } + + $params = @{ + 'UriFragment' = "/repos/$OwnerName/$RepositoryName/actions/secrets/public-key" + 'Description' = "Getting public key for $RepositoryName." + 'AcceptHeader' = 'application/vnd.github.symmetra-preview+json' + 'AccessToken' = $AccessToken + 'TelemetryEventName' = $MyInvocation.MyCommand.Name + 'TelemetryProperties' = $telemetryProperties + 'NoStatus' = (Resolve-ParameterWithDefaultConfigurationValue -Name NoStatus -ConfigValueName DefaultNoStatus) + } + + return Invoke-GHRestMethodMultipleResult @params +} + +function Get-GitHubSecretInfo { +<# + .SYNOPSIS + Lists all secrets or gets a particular secret available in a repository without revealing their encrypted values. + + .DESCRIPTION + Lists all secrets or gets a particular secret available in a repository without revealing their encrypted values. + You must authenticate using an access token with the repo scope to use this cmdlet. + GitHub Apps must have the secrets repository permission to use this cmdlet. + + .PARAMETER OwnerName + Owner of the repository. + If not supplied here, the DefaultOwnerName configuration property value will be used. + + .PARAMETER RepositoryName + Name of the repository. + If not supplied here, the DefaultRepositoryName configuration property value will be used. + + .PARAMETER Uri + Uri for the repository. + The OwnerName and RepositoryName will be extracted from here instead of needing to provide + them individually. + + .PARAMETER Name + Name of the secret. + If not provided, it will retrieve all secrets in a repository. + + .PARAMETER AccessToken + If provided, this will be used as the AccessToken for authentication with the + REST Api. Otherwise, will attempt to use the configured value or will run unauthenticated. + + .PARAMETER NoStatus + If this switch is specified, long-running commands will run on the main thread + with no commandline status update. When not specified, those commands run in + the background, enabling the command prompt to provide status information. + If not supplied here, the DefaultNoStatus configuration property value will be used. + + .EXAMPLE + Get-GitHubSecretInfo -OwnerName Microsoft -RepositoryName PowerShellForGitHub + + .EXAMPLE + Get-GitHubSecretInfo -Uri 'https://github.com/Microsoft/PowerShellForGitHub' + + .EXAMPLE + Get-GitHubSecretInfo -OwnerName Microsoft -RepositoryName PowerShellForGitHub -SecretName MySecret +#> + [CmdletBinding( + SupportsShouldProcess, + DefaultParameterSetName='Elements')] + [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSShouldProcess", "", Justification="Methods called within here make use of PSShouldProcess, and the switch is passed on to them inherently.")] + param( + [Parameter(ParameterSetName='Elements')] + [string] $OwnerName, + + [Parameter(ParameterSetName='Elements')] + [string] $RepositoryName, + + [Parameter(Mandatory, ParameterSetName='Uri')] + [string] $Uri, + + [string] $SecretName, + + [string] $AccessToken, + + [switch] $NoStatus + ) + + if ([WildcardPattern]::ContainsWildcardCharacters($SecretName)) + { + throw "The Name parameter cannot contain wild card characters." + } + + Write-InvocationLog + + $elements = Resolve-RepositoryElements + $OwnerName = $elements.ownerName + $RepositoryName = $elements.repositoryName + + $telemetryProperties = @{ + 'OwnerName' = (Get-PiiSafeString -PlainText $OwnerName) + 'RepositoryName' = (Get-PiiSafeString -PlainText $RepositoryName) + } + + if ($PSBoundParameters.ContainsKey('SecretName')) + { + $description = "Getting secret info of $SecretName for $RepositoryName" + $uriFragment = "/repos/$OwnerName/$RepositoryName/actions/secrets/$SecretName" + } + else + { + $description = "Getting secret infos for $RepositoryName" + $uriFragment = "/repos/$OwnerName/$RepositoryName/actions/secrets" + } + + $params = @{ + 'UriFragment' = $uriFragment + 'Description' = $description + 'AcceptHeader' = 'application/vnd.github.symmetra-preview+json' + 'AccessToken' = $AccessToken + 'TelemetryEventName' = $MyInvocation.MyCommand.Name + 'TelemetryProperties' = $telemetryProperties + 'NoStatus' = (Resolve-ParameterWithDefaultConfigurationValue -Name NoStatus -ConfigValueName DefaultNoStatus) + } + + try { + $result = Invoke-GHRestMethodMultipleResult @params + } catch { + $message = $_.ErrorDetails.Message + if ($message -notmatch 'Not Found') { + throw $_ + } + } + + if($PSBoundParameters.ContainsKey('SecretName')) { + $result + } else { + $result.secrets + } +} + +function Set-GitHubSecret { +<# + .SYNOPSIS + Creates or updates a repository secret with a value. + + .DESCRIPTION + Creates or updates a repository secret with a value. The value is encrypted using PSSodium which + is simple wrapper around Sodium.Core. + + .PARAMETER OwnerName + Owner of the repository. + If not supplied here, the DefaultOwnerName configuration property value will be used. + + .PARAMETER RepositoryName + Name of the repository. + If not supplied here, the DefaultRepositoryName configuration property value will be used. + + .PARAMETER Uri + Uri for the repository. + The OwnerName and RepositoryName will be extracted from here instead of needing to provide + them individually. + + .PARAMETER Name + Name of the secret. + If not provided, it will retrieve all secrets in a repository. + + .PARAMETER Value + Value for the secret. + If not provided, it will retrieve all secrets in a repository. + + .PARAMETER AccessToken + If provided, this will be used as the AccessToken for authentication with the + REST Api. Otherwise, will attempt to use the configured value or will run unauthenticated. + + .PARAMETER NoStatus + If this switch is specified, long-running commands will run on the main thread + with no commandline status update. When not specified, those commands run in + the background, enabling the command prompt to provide status information. + If not supplied here, the DefaultNoStatus configuration property value will be used. + + .EXAMPLE + Set-GitHubSecret -OwnerName Microsoft -RepositoryName PowerShellForGitHub -SecretName MySecret -SecretValue 'my text' + + .EXAMPLE + Set-GitHubSecret -Uri 'https://github.com/Microsoft/PowerShellForGitHub' -SecretName MySecret -SecretValue 'my text' +#> + [CmdletBinding( + SupportsShouldProcess, + DefaultParameterSetName='Elements')] + [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSShouldProcess", "", Justification="Methods called within here make use of PSShouldProcess, and the switch is passed on to them inherently.")] + param( + [Parameter(ParameterSetName='Elements')] + [string] $OwnerName, + + [Parameter(ParameterSetName='Elements')] + [string] $RepositoryName, + + [Parameter(Mandatory, ParameterSetName='Uri')] + [string] $Uri, + + [Parameter(Mandatory, ParameterSetName='Uri')] + [Parameter(Mandatory, ParameterSetName='Elements')] + [string] $SecretName, + + [Parameter(Mandatory, ParameterSetName='Uri')] + [Parameter(Mandatory, ParameterSetName='Elements')] + [SecureString] $SecretValue, + + [string] $AccessToken, + + [switch] $NoStatus + ) + + Write-InvocationLog + + $elements = Resolve-RepositoryElements + $OwnerName = $elements.ownerName + $RepositoryName = $elements.repositoryName + + $publicKeyInfo = Get-GitHubRepositoryPublicKey -OwnerName $OwnerName -RepositoryName $RepositoryName -NoStatus + + $hashBody = @{ + encrypted_value = ConvertTo-SodiumEncryptedString -Text $SecretValue -PublicKey $publicKeyInfo.key + key_id = $publicKeyInfo.key_id + } + + $telemetryProperties = @{ + 'OwnerName' = (Get-PiiSafeString -PlainText $OwnerName) + 'RepositoryName' = (Get-PiiSafeString -PlainText $RepositoryName) + } + + $description = "Setting secret of $SecretName for $RepositoryName" + $uriFragment = "/repos/$OwnerName/$RepositoryName/actions/secrets/$SecretName" + + $params = @{ + 'UriFragment' = $uriFragment + 'Description' = $description + 'Body' = (ConvertTo-Json -InputObject $hashBody) + 'Method' = 'Put' + 'AcceptHeader' = 'application/vnd.github.symmetra-preview+json' + 'AccessToken' = $AccessToken + 'TelemetryEventName' = $MyInvocation.MyCommand.Name + 'TelemetryProperties' = $telemetryProperties + 'NoStatus' = (Resolve-ParameterWithDefaultConfigurationValue -Name NoStatus -ConfigValueName DefaultNoStatus) + } + + Invoke-GHRestMethod @params +} + +function New-GitHubSecret { +<# + .SYNOPSIS + Creates a repository secret with a value. Throws if the secret already exists. + + .DESCRIPTION + Creates a repository secret with a value. Throws if the secret already exists. + The value is encrypted using PSSodium which is simple wrapper around Sodium.Core. + + .PARAMETER OwnerName + Owner of the repository. + If not supplied here, the DefaultOwnerName configuration property value will be used. + + .PARAMETER RepositoryName + Name of the repository. + If not supplied here, the DefaultRepositoryName configuration property value will be used. + + .PARAMETER Uri + Uri for the repository. + The OwnerName and RepositoryName will be extracted from here instead of needing to provide + them individually. + + .PARAMETER Name + Name of the secret. + If not provided, it will retrieve all secrets in a repository. + + .PARAMETER Value + Value for the secret. + If not provided, it will retrieve all secrets in a repository. + + .PARAMETER AccessToken + If provided, this will be used as the AccessToken for authentication with the + REST Api. Otherwise, will attempt to use the configured value or will run unauthenticated. + + .PARAMETER NoStatus + If this switch is specified, long-running commands will run on the main thread + with no commandline status update. When not specified, those commands run in + the background, enabling the command prompt to provide status information. + If not supplied here, the DefaultNoStatus configuration property value will be used. + + .EXAMPLE + New-GitHubSecret -OwnerName Microsoft -RepositoryName PowerShellForGitHub -SecretName MySecret -SecretValue 'my text' + + .EXAMPLE + New-GitHubSecret -Uri 'https://github.com/Microsoft/PowerShellForGitHub' -SecretName MySecret -SecretValue 'my text' +#> + [CmdletBinding( + SupportsShouldProcess, + DefaultParameterSetName='Elements')] + [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSShouldProcess", "", Justification="Methods called within here make use of PSShouldProcess, and the switch is passed on to them inherently.")] + param( + [Parameter(ParameterSetName='Elements')] + [string] $OwnerName, + + [Parameter(ParameterSetName='Elements')] + [string] $RepositoryName, + + [Parameter(Mandatory, ParameterSetName='Uri')] + [string] $Uri, + + [Parameter(Mandatory, ParameterSetName='Uri')] + [Parameter(Mandatory, ParameterSetName='Elements')] + [string] $SecretName, + + [Parameter(Mandatory, ParameterSetName='Uri')] + [Parameter(Mandatory, ParameterSetName='Elements')] + [SecureString] $SecretValue, + + [string] $AccessToken, + + [switch] $NoStatus + ) + + Write-InvocationLog + + $elements = Resolve-RepositoryElements + $OwnerName = $elements.ownerName + $RepositoryName = $elements.repositoryName + + if(Get-GitHubSecretInfo -OwnerName $OwnerName -RepositoryName $RepositoryName -SecretName $SecretName -ErrorAction Ignore) { + throw "Secret already exists." + } + + Set-GitHubSecret @PSBoundParameters +} + +function Remove-GitHubSecret { +<# + .SYNOPSIS + Removes a repository secret with a value. + + .DESCRIPTION + Removes a repository secret with a value. The value is encrypted using PSSodium which + is simple wrapper around Sodium.Core. + + .PARAMETER OwnerName + Owner of the repository. + If not supplied here, the DefaultOwnerName configuration property value will be used. + + .PARAMETER RepositoryName + Name of the repository. + If not supplied here, the DefaultRepositoryName configuration property value will be used. + + .PARAMETER Uri + Uri for the repository. + The OwnerName and RepositoryName will be extracted from here instead of needing to provide + them individually. + + .PARAMETER Name + Name of the secret. + If not provided, it will retrieve all secrets in a repository. + + .PARAMETER AccessToken + If provided, this will be used as the AccessToken for authentication with the + REST Api. Otherwise, will attempt to use the configured value or will run unauthenticated. + + .PARAMETER NoStatus + If this switch is specified, long-running commands will run on the main thread + with no commandline status update. When not specified, those commands run in + the background, enabling the command prompt to provide status information. + If not supplied here, the DefaultNoStatus configuration property value will be used. + + .EXAMPLE + Set-GitHubSecret -OwnerName Microsoft -RepositoryName PowerShellForGitHub -SecretName MySecret -SecretValue 'my text' + + .EXAMPLE + Set-GitHubSecret -Uri 'https://github.com/Microsoft/PowerShellForGitHub' -SecretName MySecret -SecretValue 'my text' +#> + [CmdletBinding( + SupportsShouldProcess, + DefaultParameterSetName='Elements')] + [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSShouldProcess", "", Justification="Methods called within here make use of PSShouldProcess, and the switch is passed on to them inherently.")] + param( + [Parameter(ParameterSetName='Elements')] + [string] $OwnerName, + + [Parameter(ParameterSetName='Elements')] + [string] $RepositoryName, + + [Parameter(Mandatory, ParameterSetName='Uri')] + [string] $Uri, + + [Parameter(Mandatory, ParameterSetName='Uri')] + [Parameter(Mandatory, ParameterSetName='Elements')] + [string] $SecretName, + + [string] $AccessToken, + + [switch] $NoStatus + ) + + Write-InvocationLog + + $elements = Resolve-RepositoryElements + $OwnerName = $elements.ownerName + $RepositoryName = $elements.repositoryName + + $telemetryProperties = @{ + 'OwnerName' = (Get-PiiSafeString -PlainText $OwnerName) + 'RepositoryName' = (Get-PiiSafeString -PlainText $RepositoryName) + } + + $description = "Setting secret of $SecretName for $RepositoryName" + $uriFragment = "/repos/$OwnerName/$RepositoryName/actions/secrets/$SecretName" + + $params = @{ + 'UriFragment' = $uriFragment + 'Description' = $description + 'Method' = 'Delete' + 'AcceptHeader' = 'application/vnd.github.symmetra-preview+json' + 'AccessToken' = $AccessToken + 'TelemetryEventName' = $MyInvocation.MyCommand.Name + 'TelemetryProperties' = $telemetryProperties + 'NoStatus' = (Resolve-ParameterWithDefaultConfigurationValue -Name NoStatus -ConfigValueName DefaultNoStatus) + } + + Invoke-GHRestMethod @params +} diff --git a/PowerShellForGitHub.psd1 b/PowerShellForGitHub.psd1 index 5cbc5e48..23f3eece 100644 --- a/PowerShellForGitHub.psd1 +++ b/PowerShellForGitHub.psd1 @@ -51,6 +51,7 @@ 'GitHubRepositories.ps1', 'GitHubRepositoryForks.ps1', 'GitHubRepositoryTraffic.ps1', + 'GitHubSecrets.ps1', 'GitHubTeams.ps1', 'GitHubUsers.ps1', 'Telemetry.ps1', @@ -105,9 +106,11 @@ 'Get-GitHubRepositoryContributor', 'Get-GitHubRepositoryFork', 'Get-GitHubRepositoryLanguage', + 'Get-GitHubRepositoryPublicKey', 'Get-GitHubRepositoryTag', 'Get-GitHubRepositoryTopic', 'Get-GitHubRepositoryUniqueContributor', + 'Get-GitHubSecretInfo', 'Get-GitHubTeam', 'Get-GitHubTeamMember', 'Get-GitHubUser', @@ -159,6 +162,7 @@ 'Remove-GitHubRepositoryBranch' 'Rename-GitHubGistFile', 'Rename-GitHubRepository', + 'Remove-GitHubSecret', 'Reset-GitHubConfiguration', 'Restore-GitHubConfiguration', 'Set-GitHubAuthentication', diff --git a/Tests/GitHubSecrets.Tests.ps1 b/Tests/GitHubSecrets.Tests.ps1 new file mode 100644 index 00000000..f2fea4ea --- /dev/null +++ b/Tests/GitHubSecrets.Tests.ps1 @@ -0,0 +1,26 @@ +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. + +<# +.Synopsis + Tests for GitHubSecrets.ps1 module +#> + +# This is common test code setup logic for all Pester test files +$moduleRootPath = Split-Path -Path $PSScriptRoot -Parent +. (Join-Path -Path $moduleRootPath -ChildPath 'Tests\Common.ps1') + +try +{ + # How do we test secrets? + # Ideally _with_ GitHub Actions +} +finally +{ + if (Test-Path -Path $script:originalConfigFile -PathType Leaf) + { + # Restore the user's configuration to its pre-test state + Restore-GitHubConfiguration -Path $script:originalConfigFile + $script:originalConfigFile = $null + } +} From fdf647752f8a80aeb055e57177e998282ae5b872 Mon Sep 17 00:00:00 2001 From: Tyler Leonhardt Date: Mon, 20 Jul 2020 22:11:44 -0700 Subject: [PATCH 60/60] remove new/set secret --- GitHubSecrets.ps1 | 195 ---------------------------------------------- 1 file changed, 195 deletions(-) diff --git a/GitHubSecrets.ps1 b/GitHubSecrets.ps1 index 5ddeb514..1c836008 100644 --- a/GitHubSecrets.ps1 +++ b/GitHubSecrets.ps1 @@ -204,201 +204,6 @@ function Get-GitHubSecretInfo { } } -function Set-GitHubSecret { -<# - .SYNOPSIS - Creates or updates a repository secret with a value. - - .DESCRIPTION - Creates or updates a repository secret with a value. The value is encrypted using PSSodium which - is simple wrapper around Sodium.Core. - - .PARAMETER OwnerName - Owner of the repository. - If not supplied here, the DefaultOwnerName configuration property value will be used. - - .PARAMETER RepositoryName - Name of the repository. - If not supplied here, the DefaultRepositoryName configuration property value will be used. - - .PARAMETER Uri - Uri for the repository. - The OwnerName and RepositoryName will be extracted from here instead of needing to provide - them individually. - - .PARAMETER Name - Name of the secret. - If not provided, it will retrieve all secrets in a repository. - - .PARAMETER Value - Value for the secret. - If not provided, it will retrieve all secrets in a repository. - - .PARAMETER AccessToken - If provided, this will be used as the AccessToken for authentication with the - REST Api. Otherwise, will attempt to use the configured value or will run unauthenticated. - - .PARAMETER NoStatus - If this switch is specified, long-running commands will run on the main thread - with no commandline status update. When not specified, those commands run in - the background, enabling the command prompt to provide status information. - If not supplied here, the DefaultNoStatus configuration property value will be used. - - .EXAMPLE - Set-GitHubSecret -OwnerName Microsoft -RepositoryName PowerShellForGitHub -SecretName MySecret -SecretValue 'my text' - - .EXAMPLE - Set-GitHubSecret -Uri 'https://github.com/Microsoft/PowerShellForGitHub' -SecretName MySecret -SecretValue 'my text' -#> - [CmdletBinding( - SupportsShouldProcess, - DefaultParameterSetName='Elements')] - [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSShouldProcess", "", Justification="Methods called within here make use of PSShouldProcess, and the switch is passed on to them inherently.")] - param( - [Parameter(ParameterSetName='Elements')] - [string] $OwnerName, - - [Parameter(ParameterSetName='Elements')] - [string] $RepositoryName, - - [Parameter(Mandatory, ParameterSetName='Uri')] - [string] $Uri, - - [Parameter(Mandatory, ParameterSetName='Uri')] - [Parameter(Mandatory, ParameterSetName='Elements')] - [string] $SecretName, - - [Parameter(Mandatory, ParameterSetName='Uri')] - [Parameter(Mandatory, ParameterSetName='Elements')] - [SecureString] $SecretValue, - - [string] $AccessToken, - - [switch] $NoStatus - ) - - Write-InvocationLog - - $elements = Resolve-RepositoryElements - $OwnerName = $elements.ownerName - $RepositoryName = $elements.repositoryName - - $publicKeyInfo = Get-GitHubRepositoryPublicKey -OwnerName $OwnerName -RepositoryName $RepositoryName -NoStatus - - $hashBody = @{ - encrypted_value = ConvertTo-SodiumEncryptedString -Text $SecretValue -PublicKey $publicKeyInfo.key - key_id = $publicKeyInfo.key_id - } - - $telemetryProperties = @{ - 'OwnerName' = (Get-PiiSafeString -PlainText $OwnerName) - 'RepositoryName' = (Get-PiiSafeString -PlainText $RepositoryName) - } - - $description = "Setting secret of $SecretName for $RepositoryName" - $uriFragment = "/repos/$OwnerName/$RepositoryName/actions/secrets/$SecretName" - - $params = @{ - 'UriFragment' = $uriFragment - 'Description' = $description - 'Body' = (ConvertTo-Json -InputObject $hashBody) - 'Method' = 'Put' - 'AcceptHeader' = 'application/vnd.github.symmetra-preview+json' - 'AccessToken' = $AccessToken - 'TelemetryEventName' = $MyInvocation.MyCommand.Name - 'TelemetryProperties' = $telemetryProperties - 'NoStatus' = (Resolve-ParameterWithDefaultConfigurationValue -Name NoStatus -ConfigValueName DefaultNoStatus) - } - - Invoke-GHRestMethod @params -} - -function New-GitHubSecret { -<# - .SYNOPSIS - Creates a repository secret with a value. Throws if the secret already exists. - - .DESCRIPTION - Creates a repository secret with a value. Throws if the secret already exists. - The value is encrypted using PSSodium which is simple wrapper around Sodium.Core. - - .PARAMETER OwnerName - Owner of the repository. - If not supplied here, the DefaultOwnerName configuration property value will be used. - - .PARAMETER RepositoryName - Name of the repository. - If not supplied here, the DefaultRepositoryName configuration property value will be used. - - .PARAMETER Uri - Uri for the repository. - The OwnerName and RepositoryName will be extracted from here instead of needing to provide - them individually. - - .PARAMETER Name - Name of the secret. - If not provided, it will retrieve all secrets in a repository. - - .PARAMETER Value - Value for the secret. - If not provided, it will retrieve all secrets in a repository. - - .PARAMETER AccessToken - If provided, this will be used as the AccessToken for authentication with the - REST Api. Otherwise, will attempt to use the configured value or will run unauthenticated. - - .PARAMETER NoStatus - If this switch is specified, long-running commands will run on the main thread - with no commandline status update. When not specified, those commands run in - the background, enabling the command prompt to provide status information. - If not supplied here, the DefaultNoStatus configuration property value will be used. - - .EXAMPLE - New-GitHubSecret -OwnerName Microsoft -RepositoryName PowerShellForGitHub -SecretName MySecret -SecretValue 'my text' - - .EXAMPLE - New-GitHubSecret -Uri 'https://github.com/Microsoft/PowerShellForGitHub' -SecretName MySecret -SecretValue 'my text' -#> - [CmdletBinding( - SupportsShouldProcess, - DefaultParameterSetName='Elements')] - [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSShouldProcess", "", Justification="Methods called within here make use of PSShouldProcess, and the switch is passed on to them inherently.")] - param( - [Parameter(ParameterSetName='Elements')] - [string] $OwnerName, - - [Parameter(ParameterSetName='Elements')] - [string] $RepositoryName, - - [Parameter(Mandatory, ParameterSetName='Uri')] - [string] $Uri, - - [Parameter(Mandatory, ParameterSetName='Uri')] - [Parameter(Mandatory, ParameterSetName='Elements')] - [string] $SecretName, - - [Parameter(Mandatory, ParameterSetName='Uri')] - [Parameter(Mandatory, ParameterSetName='Elements')] - [SecureString] $SecretValue, - - [string] $AccessToken, - - [switch] $NoStatus - ) - - Write-InvocationLog - - $elements = Resolve-RepositoryElements - $OwnerName = $elements.ownerName - $RepositoryName = $elements.repositoryName - - if(Get-GitHubSecretInfo -OwnerName $OwnerName -RepositoryName $RepositoryName -SecretName $SecretName -ErrorAction Ignore) { - throw "Secret already exists." - } - - Set-GitHubSecret @PSBoundParameters -} - function Remove-GitHubSecret { <# .SYNOPSIS