Start by reading the general coding standards for PSModule
which is the basis for all modules in the framework.
Additions or adjustments to those defaults are covered in this document to ensure that the modules drive consistancy for all developers.
-
PowerShell Keywords
- All PowerShell keywords (e.g.
if
,return
,throw
,function
,param
) must be in lowercase.
- All PowerShell keywords (e.g.
-
Brace Style
- Use One True Bracing Style (OTBS).
- Opening brace on the same line as the statement; closing brace on its own line.
-
Coverage
- We do not need 100% coverage of the GitHub API.
- Maintain a separate file listing the endpoints you intentionally do not cover, so the coverage report can mark them accordingly (e.g.,
⚠️ ).
-
Convert Filter Types
- Wherever filters are used, ensure they are implemented as standard PowerShell functions with
begin
,process
, andend
blocks.
- Wherever filters are used, ensure they are implemented as standard PowerShell functions with
-
Grouping
- Group functions by the object type they handle, using folders named for that object type (e.g. “Repository”), not by the API endpoint URL.
-
Naming
- Public function name format:
Verb-GitHubNoun
. - Private function name format:
Verb-GitHubNoun
(same style but no aliases). Get-
functions must not include[CmdletBinding(SupportsShouldProcess)]
. You only useSupportsShouldProcess
on commands that change or remove data (Set-
,Remove-
,Add-
, etc.).
- Public function name format:
-
Default Parameter Sets
- Do not declare
DefaultParameterSetName = '__AllParameterSets'
. - Only specify a
DefaultParameterSetName
if it is actually different from the first parameter set.
- Do not declare
-
One API Call = One Function
- If you find that a single function is handling multiple distinct API calls, split it into multiple functions.
-
Public vs. Private
- Public Functions
- Support pipeline input if appropriate.
- Should begin by calling
Resolve-GitHubContext
to handle theContext
parameter, which can be either a string or aGitHubContext
object. - Use parameter sets (with
begin
,process
,end
) if you have multiple ways to call the same logical operation. - If choosing among multiple underlying private functions, use a
switch
statement in theprocess
block keyed on the parameter set name. - If a parameter like
$Repository
is missing, you can default to$Context.Repo
. If no value is found, throw an error.
- Private Functions
- No pipeline input.
- No aliases on either the function or its parameters.
Context
is mandatory (typeGitHubContext
), since public functions should already have resolved it.Owner
,Organization
,ID
,Repository
are also mandatory if required by the endpoint.- Must not contain logic to default parameters from
Context
; that is resolved in public functions.
- Public Functions
All function documentation follows standard PowerShell help conventions, with some notes:
-
.SYNOPSIS, .DESCRIPTION, .EXAMPLES
- Examples in your code should include fencing (e.g., triple backticks) because the PSModule framework removes default fences.
-
.PARAMETER
- Do not store parameter documentation in a comment block separate from the parameter. Instead, use inline parameter documentation via the
[Parameter()]
attribute and descriptions in triple-slash (///
) comments above each parameter.
- Do not store parameter documentation in a comment block separate from the parameter. Instead, use inline parameter documentation via the
-
.NOTES
- Include a link to the official documentation (if any) that the function is based on, so it’s discoverable via online help.
-
.LINK
- First link should be the function’s own local documentation (generated for the PowerShell module).
- Additional links can point to official GitHub or API documentation.
-
Always Declare [Parameter()]
- Every parameter must explicitly have a
[Parameter()]
attribute, even if empty. - Place these attributes in a consistent order (see Parameter Attributes Order below).
- Every parameter must explicitly have a
-
Parameter Types
- Always specify a type, e.g.
[string] $Owner
(rather than$Owner
alone).
- Always specify a type, e.g.
-
Parameter Naming
- Use PascalCase for parameters.
- Convert snake_case from the API docs to PascalCase in the function.
ID
should be the short name. If needed, add an alias for a long form (e.g.,[Alias('SomeLongName')]
) or for a different style ('id'
,'Id'
), depending on user expectations.- If the function name implies the object (e.g.,
Get-GitHubRepository
), do not name the parameterRepositoryId
. JustID
(orName
, etc.) suffices. Keep it object-oriented rather than repeating the context. Owner
should always have the aliases:Organization
andUser
.Username
can have the aliasLogin
if relevant for a particular API.- Use
Repository
(notRepo
). If you need an alias for backward compatibility, add[Alias('Repo')]
.
-
Parameter Attribute Order
[Parameter()]
[ValidateNotNullOrEmpty()]
or other validation attributes[Alias()]
if any- Then the parameter definition itself:
[string] $ParamName
-
Parameter Defaulting
- For public functions, if the user hasn’t provided a parameter (like
$Repository
), default it from the context:if (-not $Repository) { $Repository = $Context.Repo } if (-not $Repository) { throw "Repository not specified and not found in the context." }
- For private functions, the calling function should already have done this. Private functions assume mandatory parameters.
- For public functions, if the user hasn’t provided a parameter (like
-
Remove
[org]
Alias- Do not use
[Alias('org')]
on the$Organization
parameter. Use[Alias('User','Organization')]
on$Owner
instead.
- Do not use
-
Structure
- Always use
begin
,process
, andend
blocks. begin
: Validate parameters, callAssert-GitHubContext
if needed, set up any local state.- Add a comment stating which permissions are required for the API call.
process
: Main logic, including pipeline handling if public.end
: Cleanup if necessary.
- Always use
-
ShouldProcess
- Only use
[CmdletBinding(SupportsShouldProcess)]
for commands that create, update, or remove data. Do not apply it toGet-
commands.
- Only use
-
API Method Naming
- Use PascalCase for the method in your splat (e.g.,
Post
,Delete
,Put
,Get
). - The
Method
property in your hashtable toInvoke-GitHubAPI
(or other REST calls) should reflect that standard.
- Use PascalCase for the method in your splat (e.g.,
-
Splatting
- Always splat the API call. The standard order in the splat is:
Method
APIEndpoint
(orEndpoint
, withAPIEndpoint
as an alias if necessary)Body
Context
- Body is always a hashtable containing the payload for
POST
,PATCH
, orPUT
calls.
- Always splat the API call. The standard order in the splat is:
-
Removing String Checks
- Do not use
if ([string]::IsNullOrEmpty($Param))
. Instead, check-not $Param
or rely on[ValidateNotNullOrEmpty()]
.
- Do not use
-
Pipeline Output
- After calling
Invoke-GitHubAPI @inputObject
, you can either:ForEach-Object { Write-Output $_.Response }
- or
Select-Object -ExpandProperty Response
- Choose which pattern best fits your scenario, but be consistent within a function.
- After calling
- Functions that get versioned objects, gets the latest version if nothing else is specified.
- Functions can specify -Name or -AllVersions to change the behavior.
-
One Class per Resource
- Each distinct resource type gets its own
.ps1
or.psm1
with a single class definition.
- Each distinct resource type gets its own
-
Property and Method Naming
- Use PascalCase for all public properties and methods.
-
Return Types / Interfaces
- Each class that you return should have a consistent interface.
- Remove any properties that are purely “API wrapper” fields (e.g., raw HTTP artifacts that aren’t relevant to the user).
- Cl
- Classes should have ID as the main resource ID, this is the databaseID. The node_id is spesifically in the NodeID property.
- Classes that use nodeid and databaseid should extend the class called GitHubNode.
- Objects that belong inside another scope, has the parts of the scope in properties of the class, i.e. Enterprise, Owner/Organization/Account, Repository, Environment, etc.
- To make a property alias, use types files to copy one property into another named property.
- Classes have their class name as a property with the value set as the property that is typically used in commands.
- Examples to this is, GitHubRepository, has
Repository
as a "alias" property using types, and its value is theName
property.
- Examples to this is, GitHubRepository, has
- All properties that reference size on disk, should be converted to store bytes, and be called Size.
-
Endpoint Coverage File
- Maintain a list of endpoints you’re deliberately not implementing, so that your coverage reporting can include a
⚠️ for them.
- Maintain a list of endpoints you’re deliberately not implementing, so that your coverage reporting can include a
-
Parameter Name Design
- Use object-oriented naming that reflects the entity. For example, if the function is
Remove-GitHubRepository
, simply use-ID
(or-Name
) rather than-RepositoryID
.
- Use object-oriented naming that reflects the entity. For example, if the function is
-
Aliases
- Private functions have no aliases (function-level or parameter-level).
- Public functions can add aliases where it makes sense (
Owner
has-User
/-Organization
,Repository
might have-Repo
alias if needed,Username
might have-Login
).
-
Mandatory Context for Private
- Private functions must always expect a resolved
[GitHubContext] $Context
. Public functions handle any string-based or null context resolution logic.
- Private functions must always expect a resolved
-
We Do Not Have to Cover Every Possible API
- Some endpoints (e.g., “hovercards” or other rarely used features) can be excluded.
That’s it. This spec captures all the bullet points and original guidelines in one place. Use it as the authoritative reference for coding style, function naming, parameter declarations, and general best practices in your module.