Skip to content

Commit a4d43cf

Browse files
authored
Merge pull request #15 from Shresht7/gh-actions
Refactor and enhance GitHub Actions workflow and documentation
2 parents 38161d4 + aa8a832 commit a4d43cf

7 files changed

Lines changed: 243 additions & 66 deletions

File tree

.github/workflows/publish.yml

Lines changed: 71 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,80 @@
11
name: Publish PowerShell Module
22

33
on:
4-
release:
5-
types: [created]
4+
push:
5+
tags:
6+
- "v*.*.*"
7+
8+
permissions:
9+
contents: write
10+
id-token: write
11+
attestations: write
612

713
jobs:
8-
publish-to-gallery:
14+
publish:
915
runs-on: ubuntu-latest
1016
steps:
11-
- name: Check out repository code
12-
uses: actions/checkout@v4
17+
- name: Checkout Repository Code
18+
uses: actions/checkout@v6
19+
20+
- name: Setup .NET
21+
uses: actions/setup-dotnet@v5
22+
with:
23+
dotnet-version: 8.0.x
24+
25+
# Build the C# predictor and copy the DLL to the Module directory
26+
- name: Build Module
27+
shell: pwsh
28+
run: ./Build.ps1 -Configuration Release
29+
30+
# Ensure the config directory and file exist for both Linux and Windows runners, since the tests expect them to be present.
31+
- name: Create Config Directory and File (Linux)
32+
if: runner.os == 'Linux'
33+
run: |
34+
mkdir -p ~/.local/share/PSFavorite
35+
echo '' > ~/.local/share/PSFavorite/Favorites.txt
36+
37+
- name: Create Config Directory and File (Windows)
38+
if: runner.os == 'Windows'
39+
shell: pwsh
40+
run: |
41+
$configPath = "$env:LOCALAPPDATA\PSFavorite"
42+
New-Item -ItemType Directory -Path $configPath -Force | Out-Null
43+
New-Item -ItemType File -Path "$configPath\Favorites.txt" -Force | Out-Null
44+
45+
# Ensure the built module passes all tests before proceeding
46+
- name: Run Tests
47+
shell: pwsh
48+
run: |
49+
Import-Module ./Module/PSFavorite.psd1 -Force
50+
Invoke-Pester -Path ./Module/Tests -Output Detailed -PassThru
1351
14-
- name: Run tests
52+
# Zip the entire module directory (including the DLL) for attestation and release
53+
- name: Zip Module
1554
shell: pwsh
16-
run: Invoke-Pester -Output Detailed -PassThru
17-
18-
# TODO: Enable this step
19-
# - name: Build and publish
20-
# env:
21-
# NUGET_KEY: ${{ secrets.NUGET_KEY }}
22-
# shell: pwsh
23-
# run: |
24-
# ./Build.ps1 -Configuration Release
25-
# Publish-Module -Path "./Module" -NuGetApiKey $env:NUGET_KEY -Verbose
55+
run: Compress-Archive -Path ./Module -DestinationPath ./PSFavorite.zip
56+
57+
# actions/attest@v4 is the modern, consolidated action for all attestations.
58+
# See: https://docs.github.com/en/actions/how-tos/secure-your-work/use-artifact-attestations/use-artifact-attestations
59+
- name: Attest Build Provenance
60+
uses: actions/attest@v4
61+
with:
62+
subject-path: ${{ github.workspace }}/PSFavorite.zip
63+
64+
# Create a release, attach the zipped module, and auto-generate notes from commits
65+
# See: https://cli.github.com/manual/gh_release
66+
- name: Create GitHub Release
67+
env:
68+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
69+
run: |
70+
gh release create ${{ github.ref_name }} ./PSFavorite.zip \
71+
--title "Release ${{ github.ref_name }}" \
72+
--generate-notes
73+
74+
# TODO: Enable this when ready to publish to PowerShell Gallery
75+
# - name: Publish to PowerShell Gallery
76+
# env:
77+
# NUGET_KEY: ${{ secrets.NUGET_KEY }}
78+
# shell: pwsh
79+
# run: |
80+
# Publish-Module -Path "./Module" -NuGetApiKey $env:NUGET_KEY -Verbose

.github/workflows/test.yml

Lines changed: 50 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,55 @@
11
name: Test
22

33
on:
4-
push:
5-
branches: [ "main" ]
6-
pull_request:
7-
branches: [ "main" ]
4+
push:
5+
branches: ["main"]
6+
paths:
7+
- "Module/**"
8+
- "Predictor/**"
9+
- "Build.ps1"
10+
pull_request:
11+
branches: ["main"]
812

913
jobs:
10-
test:
11-
name: Pester test
12-
runs-on: ubuntu-latest
13-
steps:
14-
- name: Check out repository code
15-
uses: actions/checkout@v4
16-
17-
- name: Run tests
18-
shell: pwsh
19-
run: Invoke-Pester -Output Detailed -PassThru
14+
test:
15+
name: Pester test (${{ matrix.os }})
16+
runs-on: ${{ matrix.os }}
17+
strategy:
18+
fail-fast: false
19+
matrix:
20+
os: [ubuntu-latest, windows-latest]
21+
22+
steps:
23+
- name: Checkout Repository Code
24+
uses: actions/checkout@v6
25+
with:
26+
fetch-depth: 1 # Fetch only the latest commit to speed up the checkout process
27+
28+
- name: Setup .NET
29+
uses: actions/setup-dotnet@v5
30+
with:
31+
dotnet-version: 8.0.x
32+
33+
- name: Build Module
34+
shell: pwsh
35+
run: ./Build.ps1 -Configuration Release
36+
37+
- name: Create Config Directory and File (Linux)
38+
if: runner.os == 'Linux'
39+
run: |
40+
mkdir -p ~/.local/share/PSFavorite
41+
echo '' > ~/.local/share/PSFavorite/Favorites.txt
42+
43+
- name: Create Config Directory and File (Windows)
44+
if: runner.os == 'Windows'
45+
shell: pwsh
46+
run: |
47+
$configPath = "$env:LOCALAPPDATA\PSFavorite"
48+
New-Item -ItemType Directory -Path $configPath -Force | Out-Null
49+
New-Item -ItemType File -Path "$configPath\Favorites.txt" -Force | Out-Null
50+
51+
- name: Run Tests
52+
shell: pwsh
53+
run: |
54+
Import-Module ./Module/PSFavorite.psd1 -Force
55+
Invoke-Pester -Path ./Module/Tests -Output Detailed -PassThru

.vscode/tasks.json

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,13 @@
66
{
77
"label": "build",
88
"type": "shell",
9-
"command": "./Build.ps1",
9+
"command": "pwsh",
1010
"args": [
11+
"-NoProfile",
12+
"-Command",
13+
"./Build.ps1 -Verbose",
1114
"-Configuration",
12-
"Release",
15+
"Debug",
1316
],
1417
"group": {
1518
"kind": "build",
@@ -24,8 +27,12 @@
2427
{
2528
"label": "test",
2629
"type": "shell",
27-
"command": "Invoke-Pester -Output Detailed",
28-
"args": [],
30+
"command": "pwsh",
31+
"args": [
32+
"-NoProfile",
33+
"-Command",
34+
"./Build.ps1; Import-Module ./Module/PSFavorite.psd1 -Force; Invoke-Pester -Path ./Module/Tests -Output Detailed"
35+
],
2936
"group": {
3037
"kind": "test",
3138
"isDefault": true

Build.ps1

Lines changed: 65 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,18 @@
33
Build the project
44
.DESCRIPTION
55
Build the project and copy the DLL to the Module directory.
6+
.PARAMETER Configuration
7+
The configuration of the build (`Debug` or `Release`) [Default: `Debug`]
8+
.PARAMETER TargetFramework
9+
The target framework of the build (`net7.0` or `net8.0`) [Default: `net8.0`]
10+
.PARAMETER Clean
11+
If specified, cleans the project before building.
612
.EXAMPLE
7-
. ./Build.ps1
13+
./Build.ps1
814
Run the build script to build the project (in debug configuration) and copy the DLL to the Module directory.
915
.EXAMPLE
10-
. ./Build.ps1 -Configuration Release
11-
Run the build script to build the project (in release configuration) and copy the DLL to the Module directory.
16+
./Build.ps1 -Configuration Release -Clean
17+
Clean and build the project in release configuration.
1218
#>
1319

1420
[CmdletBinding(DefaultParameterSetName = 'Build')]
@@ -21,17 +27,33 @@ param(
2127
# The target framework of the build (`net7.0` or `net8.0`) [Default: `net8.0`]
2228
[ValidateSet("net7.0", "net8.0")]
2329
[ValidateNotNullOrEmpty()]
24-
[string] $TargetFramework = "net8.0"
30+
[string] $TargetFramework = "net8.0",
31+
32+
# Clean the project before building
33+
[switch] $Clean
2534
)
2635

2736
# Path to the Predictor project
2837
$Project = "$PSScriptRoot\Predictor\PSFavoritePredictor.csproj"
2938

39+
# Clean the project if the -Clean switch is specified
40+
if ($Clean) {
41+
Write-Host "Cleaning project ($Configuration)..."
42+
dotnet clean $Project -c $Configuration -f $TargetFramework
43+
}
44+
3045
# Build the Predictor DLL
46+
Write-Host "Building project ($Configuration)..."
3147
dotnet build $Project -c $Configuration -f $TargetFramework
3248

49+
# Fail fast: If dotnet build failed, do not attempt to copy files
50+
if ($LASTEXITCODE -ne 0) {
51+
Write-Error "Build failed. Please check the output above."
52+
exit $LASTEXITCODE
53+
}
54+
3355
# Items to copy to the Module directory
34-
@(
56+
$Items = @(
3557
@{
3658
Source = "$PSScriptRoot\Predictor\bin\$Configuration\$TargetFramework\PSFavoritePredictor.dll"
3759
Destination = "$PSScriptRoot\Module\Library\PSFavoritePredictor.dll"
@@ -44,10 +66,43 @@ dotnet build $Project -c $Configuration -f $TargetFramework
4466
Source = "$PSScriptRoot\LICENSE"
4567
Destination = "$PSScriptRoot\Module\LICENSE"
4668
}
47-
) | ForEach-Object {
48-
if (Test-Path -Path $_.Destination) {
49-
Remove-Item -Path $_.Destination -Force
69+
)
70+
71+
# Copy items with Error Handling
72+
foreach ($item in $Items) {
73+
try {
74+
# Ensure the destination directory exists
75+
$destDir = Split-Path -Path $item.Destination -Parent
76+
if (!(Test-Path -Path $destDir)) {
77+
New-Item -ItemType Directory -Path $destDir -Force | Out-Null
78+
Write-Verbose "Created directory: $destDir"
79+
}
80+
81+
# If the destination file already exists, remove it before copying the new file
82+
if (Test-Path -Path $item.Destination) {
83+
Remove-Item -Path $item.Destination -Force
84+
}
85+
86+
# Copy the file to the destination
87+
Copy-Item -Path $item.Source -Destination $item.Destination -Force
88+
Write-Output "Copied $($item.Source) to $($item.Destination)"
89+
}
90+
catch {
91+
$msg = $_.Exception.Message
92+
# Specific handling for the locked DLL issue vs other errors
93+
if ($item.Destination -like "*PSFavoritePredictor.dll" -and ($msg -like "*used by another process*" -or $msg -like "*Access to the path*denied*")) {
94+
Write-Host ""
95+
Write-Host "CRITICAL ERROR: Access Denied to PSFavoritePredictor.dll" -ForegroundColor Red
96+
Write-Host "The DLL is likely locked because the module is loaded in an active PowerShell session." -ForegroundColor Yellow
97+
Write-Host "To fix this, please close all PowerShell windows and try again." -ForegroundColor Yellow
98+
Write-Host ""
99+
}
100+
else {
101+
Write-Error "Failed to copy $($item.Source) to $($item.Destination): $msg"
102+
}
103+
exit 1
50104
}
51-
Copy-Item -Path $_.Source -Destination $_.Destination -Force
52-
Write-Output "Copied $($_.Source) to $($_.Destination)"
53105
}
106+
107+
108+
Write-Host "Build and Copy completed successfully!" -ForegroundColor Green

Module/Private/Initialize-Configuration.ps1

Lines changed: 14 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -26,24 +26,24 @@ function Initialize-Configuration(
2626
# If the FavoritesPath parameter is specified, use it ...
2727
if ($FavoritesPath) {
2828
$Script:FavoritesPath = $FavoritesPath
29-
return
3029
}
31-
# ... otherwise,
30+
# ... otherwise, use the default path
3231
else {
3332
# Create the PSFavorite directory if it doesn't already exist
34-
$local = if ($IsWindows) { $Env:LOCALAPPDATA } else { "~/.local/share" }
35-
$folder = Join-Path $local $ModuleName
36-
if (!(Test-Path -Path $folder)) {
37-
New-Item -ItemType Directory -Path $folder
38-
}
39-
33+
$local = if ($IsWindows) { $Env:LOCALAPPDATA } else { "$HOME/.local/share" }
34+
$folder = Join-Path $local $ModuleName
4035
# Path to the Favorites file
41-
$Script:FavoritesPath = Join-Path $folder "Favorites.txt"
42-
43-
# If the file doesn't exist, create an empty file
44-
if (!(Test-Path -Path $Script:FavoritesPath)) {
45-
New-Item -ItemType File -Path $Script:FavoritesPath
46-
}
36+
$Script:FavoritesPath = Join-Path $folder "Favorites.txt"
4737
}
4838

39+
# Create the directory if it doesn't exist
40+
$folder = Split-Path $Script:FavoritesPath
41+
if (-not (Test-Path -Path $folder)) {
42+
New-Item -ItemType Directory -Path $folder -Force | Out-Null
43+
}
44+
45+
# Create the Favorites file if it doesn't exist
46+
if (-not (Test-Path -Path $Script:FavoritesPath)) {
47+
New-Item -ItemType File -Path $Script:FavoritesPath -Force | Out-Null
48+
}
4949
}

Module/README.md

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -75,19 +75,31 @@ Import-Module -Name PSFavorite
7575

7676
### 📜 Scripts
7777

78-
- [`Build.ps1`](./Build.ps1) - Builds the PowerShell module
78+
- [`Build.ps1`](./Build.ps1) - Builds the C# predictor and copies the DLL to the Module directory.
79+
80+
```pwsh
81+
./Build.ps1 -Configuration Release
82+
```
7983

8084
### 🧪 Testing
8185

82-
This module uses [Pester](https://pester.dev/) for testing. Run the following command to test the PowerShell module.
86+
This module uses [Pester](https://pester.dev/) for testing. To test the module, first build it and then run the following command.
8387

8488
```pwsh
85-
Invoke-Pester
89+
./Build.ps1
90+
Import-Module ./Module/PSFavorite.psd1 -Force
91+
Invoke-Pester -Path ./Module/Tests
8692
```
8793

94+
### 🚢 Publishing
95+
96+
The module is automatically published to the PowerShell Gallery when a new **Release** is created on GitHub.
97+
98+
Ensure the `ModuleVersion` in `Module/PSFavorite.psd1` is updated before creating a release.
99+
88100
## 📕 Reference
89101

90-
- https://learn.microsoft.com/en-us/powershell/scripting/dev-cross-plat/create-cmdline-predictor?view=powershell-7.3
102+
- https://learn.microsoft.com/en-us/powershell/scripting/dev-cross-plat/create-cmdline-predictor
91103

92104
---
93105

0 commit comments

Comments
 (0)