Skip to content

Commit a489f06

Browse files
🚀 [Feature]: Get-GitHubRepository now returns custom properties inline (#555)
`Get-GitHubRepository` now includes custom properties directly on the returned object — no separate API call needed. GraphQL queries that encounter unavailable fields now return partial results with warnings instead of failing, and commands that target non-existent resources return nothing instead of throwing errors. Various spelling corrections across source files, documentation, and tests are also included. - Fixes #554 - Fixes #557 - Fixes #558 - Fixes #559 ## New: Custom properties on `Get-GitHubRepository` results `Get-GitHubRepository` now returns custom properties inline when retrieving a repository by name. Previously, retrieving custom properties required a separate call to `Get-GitHubRepositoryCustomProperty`. ```powershell $repo = Get-GitHubRepository -Owner 'PSModule' -Name 'GitHub' $repo.CustomProperties | Format-Table # Name Value # ---- ----- # Type Module # Status Active ``` A new strongly-typed `GitHubCustomProperty` class provides `Name` and `Value` properties with consistent casing regardless of whether the data comes from the REST or GraphQL API. > **Note**: Custom properties are populated when using `Get-GitHubRepository` to fetch a specific repository. Other commands that return repository objects (e.g., listing repositories) may not include custom properties depending on the underlying API response. `Get-GitHubRepositoryCustomProperty` remains available if you only need custom properties without the full repository object. ## Fixed: Queries no longer fail when some fields are unavailable GraphQL queries that encounter fields unavailable for some repositories (such as custom properties on repos where permissions are limited) now return the available data and emit warnings for the errors. Previously, any GraphQL error — even with valid data — caused a terminating error. ## Fixed: Commands no longer throw when a resource doesn't exist Commands that query a specific repository, enterprise, or release by name now return nothing instead of throwing an error when the resource doesn't exist. This makes it safe to use these commands in conditional logic without wrapping them in try/catch. ## Technical Details - New `GitHubCustomProperty` class in `src/classes/public/Repositories/GitHubCustomProperty.ps1` with constructors accepting both REST (`property_name`) and GraphQL (`propertyName`) field names. - `GitHubRepository` class: `CustomProperties` property changed from `[PSCustomObject]` to `[GitHubCustomProperty[]]`. `PropertyToGraphQLMap` entry now maps to `repositoryCustomPropertyValues(first: 100) { nodes { propertyName value } }`. - `CustomProperties` removed from the GraphQL field exclusion list in `Get-GitHubRepositoryByName` and `Get-GitHubMyRepositoryByName` only — these are the private functions behind `Get-GitHubRepository`. - `Invoke-GitHubGraphQLQuery`: Error handling split into partial-success (data + errors → warnings) and full-failure (errors only → terminating error) branches. - Null guards added to `Get-GitHubRepositoryByName`, `Get-GitHubMyRepositoryByName`, `Get-GitHubEnterpriseByName`, `Get-GitHubReleaseAssetByTag`, and `Get-GitHubReleaseAssetFromLatest`. - Spelling corrections across 18 files in `.github/`, `examples/`, `src/classes/`, `src/functions/`, and `tests/`. --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: MariusStorhaug <17722253+MariusStorhaug@users.noreply.github.com> Co-authored-by: Marius Storhaug <marstor@hotmail.com>
1 parent 39b98ce commit a489f06

26 files changed

Lines changed: 108 additions & 61 deletions

.github/copilot-instructions.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
# Coding Standards for `GitHub`
22

33
Start by reading the general coding standards for [`PSModule`](https://psmodule.io/docs) which is the basis for all modules in the framework.
4-
Additions or adjustments to those defaults are covered in this document to ensure that the modules drive consistancy for all developers.
4+
Additions or adjustments to those defaults are covered in this document to ensure that the modules drive consistency for all developers.
55

66
## General Coding Standards
77

@@ -173,7 +173,7 @@ All function documentation follows standard PowerShell help conventions, with so
173173
- Remove any properties that are purely “API wrapper” fields (e.g., raw HTTP artifacts that aren’t relevant to the user).
174174
- Cl
175175
176-
- Classes should have ID as the main resource ID, this is the databaseID. The node_id is spesifically in the NodeID property.
176+
- Classes should have ID as the main resource ID, this is the databaseID. The node_id is specifically in the NodeID property.
177177
- Classes that use nodeid and databaseid should extend the class called GitHubNode.
178178
- Objects that belong inside another scope, has the parts of the scope in properties of the class, i.e. Enterprise, Owner/Organization/Account,
179179
Repository, Environment, etc.

examples/Connecting.ps1

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,18 +7,18 @@ Connect-GitHub
77

88
# Log on to a specific instance of GitHub (enterprise)
99
Connect-GitHub -Host 'msx.ghe.com'
10-
Get-GitHubRepository -Context 'msx.ghe.com/MariusStorhaug' # Contexts are selectable/overrideable on any call
10+
Get-GitHubRepository -Context 'msx.ghe.com/MariusStorhaug' # Contexts are selectable/overridable on any call
1111

1212
# Connect to GitHub interactively using OAuth App and Device Flow.
1313
Connect-GitHub -Mode 'OAuthApp' -Scope 'gist read:org repo workflow'
1414

1515
# Connect to GitHub interactively using less desired PAT flow, supports both fine-grained and classic PATs
1616
Connect-GitHub -UseAccessToken
1717

18-
# Connect to GitHub programatically (GitHub App Installation Access Token or PAT)
18+
# Connect to GitHub programmatically (GitHub App Installation Access Token or PAT)
1919
Connect-GitHub -Token ***********
2020

21-
# Connect to GitHub programatically (GitHub Actions)
21+
# Connect to GitHub programmatically (GitHub Actions)
2222
Connect-GitHub # Looks for the GITHUB_TOKEN variable
2323

2424
# Connect using a GitHub App and its private key (local signing of JWT)

src/classes/public/Releases/GitHubRelease.ps1

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
# Example: "## What's Changed\n### Other Changes\n* Fix: Enhance repository deletion feedback and fix typo..."
1818
[string] $Notes
1919

20-
# Specifies the commitish value that determines where the Git tag is created from
20+
# Specifies the committish value that determines where the Git tag is created from
2121
# Example: "main"
2222
[string] $Target
2323

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
class GitHubCustomProperty {
2+
# The name of the custom property.
3+
[string] $Name
4+
5+
# The value of the custom property.
6+
[string] $Value
7+
8+
GitHubCustomProperty() {}
9+
10+
GitHubCustomProperty([PSCustomObject] $Object) {
11+
$this.Name = $Object.property_name ?? $Object.propertyName ?? $Object.Name
12+
$this.Value = $Object.value ?? $Object.Value
13+
}
14+
15+
[string] ToString() {
16+
return $this.Name
17+
}
18+
}

src/classes/public/Repositories/GitHubRepository.ps1

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -182,7 +182,7 @@
182182
[GithubRepository] $ForkRepository
183183

184184
# Custom properties for the repository.
185-
[PSCustomObject] $CustomProperties
185+
[GitHubCustomProperty[]] $CustomProperties
186186

187187
# The clone URL of the repository.
188188
# Example: git://github.com/octocat/Hello-World.git
@@ -242,7 +242,7 @@
242242
MergeCommitMessage = 'mergeCommitMessage'
243243
TemplateRepository = 'templateRepository { id databaseId name owner { login } }'
244244
ForkRepository = 'parent { id databaseId name owner { login } }'
245-
CustomProperties = ''
245+
CustomProperties = 'repositoryCustomPropertyValues(first: 100) { nodes { propertyName value } }'
246246
CloneUrl = 'url'
247247
SshUrl = 'sshUrl'
248248
GitUrl = 'url'
@@ -298,7 +298,9 @@
298298
$this.SquashMergeCommitTitle = $Object.squash_merge_commit_title
299299
$this.MergeCommitMessage = $Object.merge_commit_message
300300
$this.MergeCommitTitle = $Object.merge_commit_title
301-
$this.CustomProperties = $Object.custom_properties
301+
$this.CustomProperties = $Object.custom_properties | ForEach-Object {
302+
[GitHubCustomProperty]::new($_)
303+
}
302304
$this.TemplateRepository = $null -ne $Object.template_repository ? [GitHubRepository]::New($Object.template_repository) : $null
303305
$this.ForkRepository = $null -ne $Object.parent ? [GitHubRepository]::New($Object.parent) : $null
304306
$this.CloneUrl = $Object.clone_url
@@ -353,6 +355,11 @@
353355
$this.SquashMergeCommitMessage = $Object.squashMergeCommitMessage
354356
$this.MergeCommitTitle = $Object.mergeCommitTitle
355357
$this.MergeCommitMessage = $Object.mergeCommitMessage
358+
if ($null -ne $Object.repositoryCustomPropertyValues -and $null -ne $Object.repositoryCustomPropertyValues.nodes) {
359+
$this.CustomProperties = $Object.repositoryCustomPropertyValues.nodes | ForEach-Object {
360+
[GitHubCustomProperty]::new($_)
361+
}
362+
}
356363
$this.TemplateRepository = $null -ne $Object.templateRepository ? [GitHubRepository]::New($Object.templateRepository) : $null
357364
$this.ForkRepository = $null -ne $Object.parent ? [GitHubRepository]::New($Object.parent) : $null
358365
$this.CloneUrl = -not [string]::IsNullOrEmpty($Object.url) ? $Object.url + '.git' : $null

src/functions/private/Auth/DeviceFlow/Update-GitHubUserAccessToken.ps1

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@
3131
)]
3232
[Diagnostics.CodeAnalysis.SuppressMessageAttribute(
3333
'PSAvoidUsingConvertToSecureStringWithPlainText', '',
34-
Justification = 'The tokens are recieved as clear text. Mitigating exposure by removing variables and performing garbage collection.'
34+
Justification = 'The tokens are received as clear text. Mitigating exposure by removing variables and performing garbage collection.'
3535
)]
3636
[Diagnostics.CodeAnalysis.SuppressMessageAttribute(
3737
'PSAvoidLongLines', '',

src/functions/private/Enterprise/Get-GitHubEnterpriseByName.ps1

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,9 @@ query(`$Slug: String!) {
6767
Context = $Context
6868
}
6969
$enterpriseResult = Invoke-GitHubGraphQLQuery @enterpriseQuery
70-
[GitHubEnterprise]::new($enterpriseResult.enterprise)
70+
if ($enterpriseResult.enterprise) {
71+
[GitHubEnterprise]::new($enterpriseResult.enterprise)
72+
}
7173
}
7274

7375
end {

src/functions/private/Releases/Assets/Get-GitHubReleaseAssetByTag.ps1

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -115,12 +115,14 @@ query(`$owner: String!, `$repository: String!, `$tag: String!, `$perPage: Int, `
115115

116116
Invoke-GitHubGraphQLQuery @apiParams | ForEach-Object {
117117
$release = $_.repository.release
118-
$assets = $release.releaseAssets
119-
foreach ($asset in $assets.nodes) {
120-
[GitHubReleaseAsset]::new($asset)
118+
if ($release) {
119+
$assets = $release.releaseAssets
120+
foreach ($asset in $assets.nodes) {
121+
[GitHubReleaseAsset]::new($asset)
122+
}
123+
$hasNextPage = $assets.pageInfo.hasNextPage
124+
$after = $assets.pageInfo.endCursor
121125
}
122-
$hasNextPage = $assets.pageInfo.hasNextPage
123-
$after = $assets.pageInfo.endCursor
124126
}
125127
} while ($hasNextPage)
126128
}

src/functions/private/Releases/Assets/Get-GitHubReleaseAssetFromLatest.ps1

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -114,12 +114,14 @@ query(`$owner: String!, `$repository: String!, `$perPage: Int, `$after: String)
114114

115115
Invoke-GitHubGraphQLQuery @apiParams | ForEach-Object {
116116
$release = $_.repository.latestRelease
117-
$assets = $release.releaseAssets
118-
foreach ($asset in $assets.nodes) {
119-
[GitHubReleaseAsset]::new($asset)
117+
if ($release) {
118+
$assets = $release.releaseAssets
119+
foreach ($asset in $assets.nodes) {
120+
[GitHubReleaseAsset]::new($asset)
121+
}
122+
$hasNextPage = $assets.pageInfo.hasNextPage
123+
$after = $assets.pageInfo.endCursor
120124
}
121-
$hasNextPage = $assets.pageInfo.hasNextPage
122-
$after = $assets.pageInfo.endCursor
123125
}
124126
} while ($hasNextPage)
125127
}

src/functions/private/Repositories/Get-GitHubMyRepositoryByName.ps1

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,6 @@
7878
'MergeCommitMessage',
7979
'TemplateRepository',
8080
'ForkRepository',
81-
'CustomProperties',
8281
'CloneUrl',
8382
'SshUrl',
8483
'GitUrl'
@@ -128,7 +127,10 @@ $graphQLFields
128127
}
129128

130129
Invoke-GitHubGraphQLQuery @apiParams | ForEach-Object {
131-
[GitHubRepository]::new($_.viewer.repository)
130+
$repository = $_.viewer.repository
131+
if ($repository) {
132+
[GitHubRepository]::new($repository)
133+
}
132134
}
133135
}
134136

0 commit comments

Comments
 (0)