Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
189 changes: 129 additions & 60 deletions .github/copilot-instructions.md
Original file line number Diff line number Diff line change
@@ -1,84 +1,153 @@
# Copilot Instructions for IncludeHelper
# ProjectHelper Copilot Instructions

IncludeHelper is a PowerShell module that provides shared utility functions and a framework for distributing reusable code components ("includes") across other modules.
## Project Overview
**ProjectHelper** is a PowerShell module for GitHub Projects interaction via GraphQL API. It provides CLI-like functions to manage GitHub projects, items, users, and collaborators from PowerShell.

## Architecture Overview
## Architecture & Core Patterns

**Module Structure:**
- `config/`: Configuration utilities and module initialization
- `helper/`: Core module helpers (module path resolution, folder management)
- `include/`: Shared utility functions (logging, API calls, database, config management)
- `private/`: Internal functions not exported
- `public/`: Exported functions (the public API)
- `Test/`: Mirrored structure with unit tests and mocks
### Module Structure
- **Loading Order** (`ProjectHelper.psm1`): `config` → `helper` → `include` → `private` → `public`
- **Public Functions**: Exported via `Export-ModuleMember -Function <name>` in each file
- **Private Functions**: In `/private` folder, not exported; used internally by public functions
- **Include Files** (`/include`): Shared utilities loaded early; example: `callAPI.ps1` (GraphQL/REST), `MyWrite.ps1` (logging)
- **Driver Functions** (`/public/driver`): Low-level integration with GitHub APIs; marked with comment "integration function not intended for direct user use"

**Loading Order** (in `IncludeHelper.psm1`): `config` → `helper` → `include` → `private` → `public`
### Key Files & Responsibilities
- `include/callAPI.ps1`: Handles `Invoke-GraphQL` and `Invoke-RestAPI` calls to GitHub
- `helper/invokeCommand.helper.ps1`: Provides `Invoke-MyCommand` for alias-based command dispatch (enables test mocking)
- `include/config.ps1`: Configuration storage in `~/.helpers/ProjectHelper/config/`
- `public/graphql/*.{query,mutant,tag}`: GraphQL template files (fragments and queries)
- `Test/Test.psd1`: Parallel test module with identical structure to main module

## PowerShell Function Conventions

### All Functions
- Must include `[CmdletBinding()]` attribute
- Must include `param()` block (even if empty)
- Use proper script documentation with `<# .SYNOPSIS #>` blocks
### Invocation Pattern (Critical for Testing)
```powershell
# Production: Direct API calls
Invoke-GraphQL -Query $query -Variables $variables

### Public Functions (`public/` folder)
- Add `Export-ModuleMember -Function 'FunctionName'` on the closing `}` line
- Example:
```powershell
function Add-IncludeToWorkspace {
[CmdletBinding()]
param([Parameter(Mandatory)][string]$Name)
# Logic
} Export-ModuleMember -Function 'Add-IncludeToWorkspace'
```
# High-level: Uses Invoke-MyCommand alias dispatch (mockable)
Invoke-MyCommand -Command "findProject" -Parameters @{owner=$Owner; pattern=$Pattern}

### Helper Functions (`helper/` folder)
- Available module-wide; follow naming convention: verbs that clearly indicate utility purpose
- Key helpers: `Find-ModuleRootPath`, `Get-ModuleFolder`, `Get-Ps1FullPath`
# Set custom implementation for testing/mocking:
Set-MyInvokeCommandAlias -Alias $alias -Command $Command
```

## Logging and Debugging
## Development Workflows

- Use `MyWrite.ps1` functions: `Write-MyError`, `Write-MyWarning`, `Write-MyVerbose`, `Write-MyDebug`, `Write-MyHost`
- Control verbosity via environment variables: `$env:ModuleHelper_VERBOSE="all"` or specific function names
- Control debugging via `$env:ModuleHelper_DEBUG="all"` or specific sections
- Test verbosity/debug state with `Test-MyVerbose` and `Test-MyDebug`
### Running Tests
```powershell
./test.ps1 # Run all tests
./test.ps1 -ShowTestErrors # Show error details
./test.ps1 -TestName "Test_*" # Run specific test
```

## Test Conventions
Uses **TestingHelper** module from PSGallery (installed automatically).

Tests use `TestingHelper` module and follow pattern: `function Test_FunctionName_Scenario`
### Building & Deploying
```powershell
./build.ps1 # Build module
./deploy.ps1 -VersionTag "v1.0.0" # Deploy to PSGallery
./sync.ps1 # Sync with TestingHelper templates
```

**Test Structure:**
### Debugging
Enable module debug output:
```powershell
function Test_AddIncludeToWorkspace {
# Arrange - Setup test data and mocks
Import-Module -Name TestingHelper
New-ModuleV3 -Name TestModule
Enable-ProjectHelperDebug
Disable-ProjectHelperDebug
```

# Act - Execute the function being tested
Add-IncludeToWorkspace -Name "getHashCode.ps1" -FolderName "Include" -DestinationModulePath "TestModule"
## Code Patterns & Conventions

# Assert - Verify results
Assert-ItemExist -path (Join-Path $folderPath "getHashCode.ps1")
}
### GraphQL Integration
1. Store GraphQL in template files: `/public/graphql/queryName.query` or `.mutant`
2. Retrieve via: `Get-GraphQLString "queryName.query"`
3. Execute: `Invoke-GraphQL -Query $query -Variables $variables`

Example:
```powershell
$query = Get-GraphQLString "findProject.query"
$variables = @{ login = $Owner; pattern = $Pattern }
$response = Invoke-GraphQL -Query $query -Variables $variables
```

- Use `Assert-NotImplemented` for unfinished tests
- Test files in `Test/public/` mirror functions in `public/`
- Run tests with `./test.ps1` (uses `TestingHelper` module)
### Public vs. Private Functions
- **Public** (`/public`): User-facing, high-level; transform data, handle caching
- **Private** (`/private`): Lower-level helpers; return raw GitHub data
- **Driver** (`/public/driver`): Thin wrappers around API calls; minimal logic

## Core Patterns
### Command Aliases with Parameter Templates
Use `Set-MyInvokeCommandAlias` for dynamic command dispatch:
```powershell
Set-MyInvokeCommandAlias -Alias "findProject" -Command "Invoke-FindProject -Owner {owner} -Pattern {pattern}"
Invoke-MyCommand -Command "findProject" -Parameters @{owner="foo"; pattern="bar"}
```
This enables mocking in tests without changing implementation.

**Module Discovery:** Use `Find-ModuleRootPath` to locate module root by searching up from current path for `*.psd1` files (skips Test.psd1).
### Pipeline & Object Transformation
Functions support pipeline input for bulk operations:
```powershell
"user1", "user2" | Add-ProjectUser -Owner $owner -ProjectNumber 123 -Role "WRITER"
```

**Folder Management:** `Get-ModuleFolder` maps logical names (`Include`, `Public`, `TestPrivate`, etc.) to filesystem paths. Valid names defined in `helper/module.helper.ps1` `$VALID_FOLDER_NAMES`.
### Error Handling
- GraphQL errors: Check `$response.errors` before processing
- Include meaningful context in error messages
- Use `Write-MyError`, `Write-MyVerbose`, `Write-MyDebug` for consistent logging

## Testing Patterns

### Test File Location
Test files must mirror the module structure:
- **Source**: `public/code.ps1` → **Test**: `Test/public/code.test.ps1`
- **Source**: `public/driver/invoke-getnode.ps1` → **Test**: `Test/public/driver/invoke-getnode.test.ps1`
- **Source**: `private/dates.ps1` → **Test**: `Test/private/dates.test.ps1`

The folder structure in `Test/` must exactly match the structure in the main module.

### Test Function Naming
- **Format**: `Test_<FunctionName>_<Scenario>`
- **Examples**:
- `Test_FindProject_SUCCESS` (success case)
- `Test_AddProjectUser_SUCCESS_SingleUser` (specific variant)
- `Test_GetProjectIssue_NotFound` (error case)
- **Conventions**:
- Use PascalCase matching the actual function name (e.g., `Get-SomeInfo` → `Test_GetSomeInfo_<tip>`)
- `<tip>` should be a descriptive word indicating the test goal (SUCCESS, NotFound, InvalidInput, etc.)
- Use assertions: `Assert-IsTrue`, `Assert-Contains`, `Assert-AreEqual`, `Assert-Count`, `Assert-IsNull`

### Mock System
Located in `Test/include/`:
- `invokeCommand.mock.ps1`: Mocks `Invoke-MyCommand` calls via JSON files in `Test/private/mocks/`
- `callPrivateContext.ps1`: Execute private functions in module context

### Mock Data Structure
```powershell
# Test/private/mocks/mockCommands.json defines:
{
"Command": "Invoke-GetUser -Handle rulasg",
"FileName": "invoke-GetUser-rulasg.json"
}
```

**Configuration:** JSON-based, stored in `~/.helpers/{ModuleName}/config/`. Use `Get-Configuration`, `Save-Configuration`, `Test-Configuration` from `include/config.ps1`.
### Common Test Setup
```powershell
Reset-InvokeCommandMock
Mock_DatabaseRoot
MockCall_GetProject $project -SkipItems
MockCallJson -Command "command" -File "response.json"
```

**Command Aliasing:** Use `Set-MyInvokeCommandAlias` and `Invoke-MyCommand` for mockable external commands (database calls, API invocations).
## Key Dependencies
- **GitHub API**: GraphQL (primary), REST (legacy)
- **TestingHelper**: Test framework from PSGallery
- **InvokeHelper**: Command dispatch/mocking library (external)

## Development Commands
## Important Gotchas
1. **Module Loading**: Functions depend on proper load order; new files in `/private` or `/public` auto-loaded
2. **Aliases**: Use `Set-MyInvokeCommandAlias` before calling `Invoke-MyCommand` for consistency
3. **GraphQL Templates**: Fragment files (`.tag`) must match schema; test with actual GitHub API responses
4. **Configuration**: Stored per-module; reset with `Reset-ProjectHelperEnvironment`
5. **PSScriptAnalyzer**: PR checks fail on warnings; review `.github/workflows/powershell.yml` rules

- `./test.ps1` - Run all unit tests
- `./sync.ps1` - Sync includes to workspace/module
- `./deploy.ps1` - Deploy module
- `./release.ps1` - Release module version
## PR Branch & Active Work
Currently on `projectv2ContributionUpdate` - Implementing project access management with new user collaboration features. Recent changes focus on `Invoke-UpdateProjectV2Collaborators` and `Add-ProjectUser` functions with proper string splitting via `-split` with `[System.StringSplitOptions]::RemoveEmptyEntries`.
30 changes: 27 additions & 3 deletions .github/workflows/deploy_module_on_release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,22 @@ jobs:
run: |
echo $EVENT_CONTEXT

- name: Run test.ps1
shell: pwsh
run: |
$result = ./test.ps1 -ShowTestErrors

Write-Output $result

import-module ./tools/Test_Helper/

$passed = Test-Result -Result $result

if($passed)
{ "All test passed" | Write-Verbose -verbose ; exit 0 }
else
{ "Not all tests passed" | Write-Verbose -verbose ; exit 1 }

- name: deploy_ps1
shell: pwsh
env:
Expand All @@ -27,8 +43,16 @@ jobs:
RELEASE_TAG: ${{ github.event.release.tag_name }}
RELEASE_NAME: ${{ github.event.release.name }}
run: |
$env:EVENT_REF = $env:REF

# Import required modules for deployment
import-module ./tools/Test_Helper/

Get-RequiredModule -Verbose | Import-RequiredModule -AllowPrerelease

# GET TAG NAME

## Ref definition. Branch or Tag
$env:EVENT_REF = $env:REF
If ([string]::IsNullOrEmpty($env:EVENT_REF)) {
# Release published trigger
$tag = $env:RELEASE_TAG
Expand All @@ -41,8 +65,8 @@ jobs:

If([string]::IsNullorwhitespace($tag)) {
# Tag name is empty, exit
write-error "Tag name is empty"
exit 1
throw "Tag name is empty"
}

# DEPLOYMENT
./deploy.ps1 -VersionTag $tag -NugetApiKey $env:NUGETAPIKEY
9 changes: 5 additions & 4 deletions .github/workflows/test_with_TestingHelper.yml
Original file line number Diff line number Diff line change
Expand Up @@ -33,11 +33,12 @@ jobs:
shell: pwsh
run: |
$result = ./test.ps1 -ShowTestErrors
$result

# Allow Not Implemented and Skipped tests to pass
$passed = $result.Tests -eq $result.Pass + $result.NotImplemented + $result.Skipped
# $passed = $result.Tests -eq $result.Pass
Write-Output $result

import-module ./tools/Test_Helper/

$passed = Test-Result -Result $result

if($passed)
{ "All test passed" | Write-Verbose -verbose ; exit 0 }
Expand Down
Loading
Loading