diff --git a/Coverage.md b/Coverage.md index 82f272f63..e175e4051 100644 --- a/Coverage.md +++ b/Coverage.md @@ -413,7 +413,7 @@ | `/repos/{owner}/{repo}/commits/{ref}/statuses` | | :x: | | | | | `/repos/{owner}/{repo}/community/profile` | | :x: | | | | | `/repos/{owner}/{repo}/compare/{basehead}` | | :x: | | | | -| `/repos/{owner}/{repo}/contents/{path}` | :x: | :x: | | | :x: | +| `/repos/{owner}/{repo}/contents/{path}` | :x: | :white_check_mark: | | | :x: | | `/repos/{owner}/{repo}/contributors` | | :white_check_mark: | | | | | `/repos/{owner}/{repo}/dependabot/alerts` | | :x: | | | | | `/repos/{owner}/{repo}/dependabot/alerts/{alert_number}` | | :x: | :x: | | | @@ -534,7 +534,7 @@ | `/repos/{owner}/{repo}/pulls/{pull_number}/reviews/{review_id}/dismissals` | | | | | :x: | | `/repos/{owner}/{repo}/pulls/{pull_number}/reviews/{review_id}/events` | | | | :x: | | | `/repos/{owner}/{repo}/pulls/{pull_number}/update-branch` | | | | | :x: | -| `/repos/{owner}/{repo}/readme` | | :x: | | | | +| `/repos/{owner}/{repo}/readme` | | :white_check_mark: | | | | | `/repos/{owner}/{repo}/readme/{dir}` | | :x: | | | | | `/repos/{owner}/{repo}/releases` | | :white_check_mark: | | :white_check_mark: | | | `/repos/{owner}/{repo}/releases/assets/{asset_id}` | :white_check_mark: | :white_check_mark: | :white_check_mark: | | | diff --git a/src/functions/public/Repositories/Contents/Get-GitHubRepositoryContent.ps1 b/src/functions/public/Repositories/Contents/Get-GitHubRepositoryContent.ps1 new file mode 100644 index 000000000..38a5363fe --- /dev/null +++ b/src/functions/public/Repositories/Contents/Get-GitHubRepositoryContent.ps1 @@ -0,0 +1,94 @@ +function Get-GitHubRepositoryContent { + <# + .SYNOPSIS + Retrieves the contents of a file or directory from a GitHub repository. + + .DESCRIPTION + This function calls the GitHub API endpoint that returns the contents of a repository. + You can specify a file or directory path using -Path. If you leave -Path empty, + the function will return the repository's root directory contents. + + Optionally, you can supply a specific commit, branch, or tag via -Ref. + The function relies on the provided GitHub context for authentication and configuration. + + .EXAMPLE + Get-GitHubRepositoryContent -Owner "octocat" -Repo "Hello-World" -Path "README.md" -Ref "main" + + Output: + ```powershell + { + "name": "README.md", + "path": "README.md", + "sha": "123abc456def", + "size": 1256, + "url": "https://api.github.com/repos/octocat/Hello-World/contents/README.md", + "type": "file" + } + ``` + + Retrieves the README.md file from the main branch of the repository. + + .OUTPUTS + System.Object + + .NOTES + The response object containing details about the repository contents. + + .LINK + https://psmodule.io/GitHub/Functions/Get-GitHubRepositoryContent/ + #> + [CmdletBinding(SupportsShouldProcess)] + param( + # The GitHub account owner. + [Parameter(Mandatory)] + [Alias('Organization')] + [Alias('User')] + [string] $Owner, + + # The name of the repository. + [Parameter(Mandatory)] + [string] $Repository, + + # The file or directory path in the repository. + [Parameter()] + [string] $Path, + + # Optional reference (commit, branch, or tag) to get content from. + [Parameter()] + [string] $Ref, + + # The GitHub context for the API call. + [Parameter()] + [object] $Context = (Get-GitHubContext) + ) + + begin { + $stackPath = Get-PSCallStackPath + Write-Debug "[$stackPath] - Start" + $Context = Resolve-GitHubContext -Context $Context + Assert-GitHubContext -Context $Context -AuthType IAT, PAT, UAT + } + + process { + # Build the query parameters (only ref is supported here). + $body = @{ + ref = $Ref + } + + # Prepare the input for the GitHub API call. + $inputObject = @{ + Method = 'GET' + APIEndpoint = "/repos/$Owner/$Repository/contents/$Path" + Body = $body + Context = $Context + } + + Invoke-GitHubAPI @inputObject | ForEach-Object { + Write-Output $_.Response + } + } + + end { + Write-Debug "[$stackPath] - End" + } +} diff --git a/tests/GitHub.Tests.ps1 b/tests/GitHub.Tests.ps1 index df7f4fce5..be1ff79cf 100644 --- a/tests/GitHub.Tests.ps1 +++ b/tests/GitHub.Tests.ps1 @@ -680,6 +680,56 @@ Describe 'API' { } } } + Context 'Repository' { + Context 'Content' { + BeforeEach { + Connect-GitHubApp -Organization 'psmodule-test-org' -Default + } + AfterEach { + Get-GitHubContext -ListAvailable | Disconnect-GitHubAccount + } + It 'Get-GitHubRepositoryContent - retrieves README.md from main branch' { + $Owner = 'github' + $Repository = 'rest-api-description' + $Path = 'README.md' + $Ref = 'main' + + $Result = Get-GitHubRepositoryContent -Owner $Owner -Repository $Repository -Path $Path -Ref $Ref + + $Result | Should -Not -BeNullOrEmpty + $Result | Should -BeOfType 'System.Object' + $result | Format-List | Out-String -Stream | ForEach-Object { Write-Verbose $_ } + $Result.name | Should -Be 'README.md' + } + It 'Get-GitHubRepositoryContent - retrieves root directory contents' { + $Owner = 'github' + $Repository = 'rest-api-description' + + $Result = Get-GitHubRepositoryContent -Owner $Owner -Repository $Repository + $result | Format-List | Out-String -Stream | ForEach-Object { Write-Verbose $_ } + $Result | Should -Not -BeNullOrEmpty + $Result | Should -BeOfType 'System.Object' + } + It 'Get-GitHubRepositoryContent - returns error for non-existent file' { + $Owner = 'github' + $Repository = 'rest-api-description' + $Path = 'nonexistentfile.md' + + { Get-GitHubRepositoryContent -Owner $Owner -Repository $Repository -Path $Path } | Should -Throw + } + It 'Get-GitHubRepositoryContent - retrieves content from a feature branch' { + $Owner = 'github' + $Repository = 'rest-api-description' + $Path = 'README.md' + $Ref = 'feature-branch' + + $Result = Get-GitHubRepositoryContent -Owner $Owner -Repository $Repository -Path $Path -Ref $Ref + $result | Format-List | Out-String -Stream | ForEach-Object { Write-Verbose $_ } + $Result | Should -Not -BeNullOrEmpty + $Result | Should -BeOfType 'System.Object' + } + } + } } Describe 'Emojis' { diff --git a/tools/Get-GitHubAPIDescriptor.ps1 b/tools/Get-GitHubAPIDescriptor.ps1 new file mode 100644 index 000000000..d01a65a83 --- /dev/null +++ b/tools/Get-GitHubAPIDescriptor.ps1 @@ -0,0 +1,81 @@ +### +### List Top-Level Folders and Subdirectories in a GitHub Repository +### + +# Define repository owner and name +$owner = 'github' +$Repository = 'rest-api-description' + +# GitHub API URL for root contents +$rootUrl = "https://api.github.com/repos/$owner/$Repository/contents" + +# Invoke the REST API to get root contents (add User-Agent header if needed) +$rootContents = Invoke-RestMethod -Uri $rootUrl -Headers @{ 'Accept' = 'application/vnd.github.v3+json' } +$rootContents | Format-Table -AutoSize + +$rootContents | ForEach-Object { + [PSCustomObject]@{ + Name = $_.name + Size = $_.size + Type = $_.type + } +} | Sort-Object Type, Name | Format-Table + +# Filter for directories in the root (type equals "dir") +$topLevelFolders = $rootContents | Where-Object { $_.type -eq 'dir' } | Select-Object -ExpandProperty name + +# Output the top-level folder names +$topLevelFolders + + +### +### List Subdirectories for Each Top Folder: +### + +foreach ($folder in $topLevelFolders) { + Write-Host "`nSubfolders in '$folder':" + # Construct URL for the folder's contents + $folderUrl = "https://api.github.com/repos/$owner/$Repository/contents/$folder" + $folderContents = Invoke-RestMethod -Uri $folderUrl -Headers @{ 'Accept' = 'application/vnd.github.v3+json' } + + # Filter for subdirectories (type "dir") within this folder + $subDirs = $folderContents | Where-Object { $_.type -eq 'dir' } | Select-Object -ExpandProperty name + + # Print each subfolder name + $subDirs | ForEach-Object { Write-Host "- $_" } +} + + + +# (Optional) Get entire tree recursively - may return a lot of data for large repos +$branch = 'main' # or specify default branch/commit SHA +$treeUrl = "https://api.github.com/repos/$owner/$Repository/git/trees/$branch`?recursive=1" +$treeData = Invoke-RestMethod -Uri $treeUrl -Headers @{ 'Accept' = 'application/vnd.github.v3+json' } + +# $treeData.tree is a list of all paths in the repository (each with type "blob" for file or "tree" for folder). +# We can filter this list for type "tree" to get directories. +$allDirs = $treeData.tree | Where-Object { $_.type -eq 'tree' } | Select-Object -ExpandProperty path + + + +function Get-GitHubAPIDescription { + <# + .SYNOPSIS + Retrieves the GitHub REST API description from the GitHub REST API description repository. + + .DESCRIPTION + Retrieves the GitHub REST API description from the GitHub REST API description repository. + The API description is used to generate the GitHub REST API functions. + + .EXAMPLE + Get-GitHubAPIDescription + #> + + # GitHub REST API description repository + $APIDocURI = 'https://raw.githubusercontent.com/github/rest-api-description/main' + $Bundled = '/descriptions/api.github.com/api.github.com.json' + $APIDocURI = $APIDocURI + $Bundled + $response = Invoke-RestMethod -Uri $APIDocURI -Method Get + + return $response +} diff --git a/tools/utilities/Get-GitHubAPIDescriptor.ps1 b/tools/utilities/Get-GitHubAPIDescriptor.ps1 new file mode 100644 index 000000000..1f387a894 --- /dev/null +++ b/tools/utilities/Get-GitHubAPIDescriptor.ps1 @@ -0,0 +1,72 @@ +### +### List Top-Level Folders and Subdirectories in a GitHub Repository +### + +# Define repository owner and name +$owner = 'github' +$repo = 'rest-api-description' + +# GitHub API URL for root contents +$rootUrl = "https://api.github.com/repos/$owner/$repo/contents" + +# Invoke the REST API to get root contents (add User-Agent header if needed) +$rootContents = Invoke-RestMethod -Uri $rootUrl -Headers @{ 'Accept' = 'application/vnd.github.v3+json' } + +# Filter for directories in the root (type equals "dir") +$topLevelFolders = $rootContents | Where-Object { $_.type -eq 'dir' } | Select-Object -ExpandProperty name + +# Output the top-level folder names +$topLevelFolders + + +### +### List Subdirectories for Each Top Folder: +### + +foreach ($folder in $topLevelFolders) { + Write-Host "`nSubfolders in '$folder':" + # Construct URL for the folder's contents + $folderUrl = "https://api.github.com/repos/$owner/$repo/contents/$folder" + $folderContents = Invoke-RestMethod -Uri $folderUrl -Headers @{ 'Accept' = 'application/vnd.github.v3+json' } + + # Filter for subdirectories (type "dir") within this folder + $subDirs = $folderContents | Where-Object { $_.type -eq 'dir' } | Select-Object -ExpandProperty name + + # Print each subfolder name + $subDirs | ForEach-Object { Write-Host "- $_" } +} + + + +# (Optional) Get entire tree recursively - may return a lot of data for large repos +$branch = 'main' # or specify default branch/commit SHA +$treeUrl = "https://api.github.com/repos/$owner/$repo/git/trees/$branch`?recursive=1" +$treeData = Invoke-RestMethod -Uri $treeUrl -Headers @{ 'Accept' = 'application/vnd.github.v3+json' } + +# $treeData.tree is a list of all paths in the repository (each with type "blob" for file or "tree" for folder). +# We can filter this list for type "tree" to get directories. +$allDirs = $treeData.tree | Where-Object { $_.type -eq 'tree' } | Select-Object -ExpandProperty path + + + +function Get-GitHubAPIDescription { + <# + .SYNOPSIS + Retrieves the GitHub REST API description from the GitHub REST API description repository. + + .DESCRIPTION + Retrieves the GitHub REST API description from the GitHub REST API description repository. + The API description is used to generate the GitHub REST API functions. + + .EXAMPLE + Get-GitHubAPIDescription + #> + + # GitHub REST API description repository + $APIDocURI = 'https://raw.githubusercontent.com/github/rest-api-description/main' + $Bundled = '/descriptions/api.github.com/api.github.com.json' + $APIDocURI = $APIDocURI + $Bundled + $response = Invoke-RestMethod -Uri $APIDocURI -Method Get + + return $response +} diff --git a/tools/utilities/New-FunctionTemplate.ps1 b/tools/utilities/New-FunctionTemplate.ps1 index 5716bf6f0..a02c8c0f3 100644 --- a/tools/utilities/New-FunctionTemplate.ps1 +++ b/tools/utilities/New-FunctionTemplate.ps1 @@ -33,53 +33,28 @@ Write-Debug "[$stackPath] - Start" $Context = Resolve-GitHubContext -Context $Context Assert-GitHubContext -Context $Context -AuthType IAT, PAT, UAT - - if ([string]::IsNullOrEmpty($Enterprise)) { - $Enterprise = $Context.Enterprise - } - Write-Debug "Enterprise : [$($Context.Enterprise)]" - - if ([string]::IsNullOrEmpty($Owner)) { - $Owner = $Context.Owner - } - Write-Debug "Owner : [$($Context.Owner)]" - - if ([string]::IsNullOrEmpty($Repo)) { - $Repo = $Context.Repo - } - Write-Debug "Repo : [$($Context.Repo)]" } process { - try { - $body = @{ - per_page = $PerPage - } + $body = @{ + per_page = $PerPage + } - $inputObject = @{ - Context = $Context - APIEndpoint = "/orgs/$OrganizationName/blocks" - Method = 'GET' - Body = $body - } + $inputObject = @{ + Method = 'GET' + APIEndpoint = "/orgs/$OrganizationName/blocks" + Body = $body + Context = $Context + } - if ($PSCmdlet.ShouldProcess('Target', 'Operation')) { - Invoke-GitHubAPI @inputObject | ForEach-Object { - Write-Output $_.Response - } + if ($PSCmdlet.ShouldProcess('Target', 'Operation')) { + Invoke-GitHubAPI @inputObject | ForEach-Object { + Write-Output $_.Response } - } catch { - Write-Debug "Error: $_" - } finally { - Write-Debug 'Finally' } } end { Write-Debug "[$stackPath] - End" } - - clean { - Write-Debug 'Clean' - } }