Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
99374e5
Refactor GitHub Actions workflow to include .NET setup and module bui…
Shresht7 Mar 30, 2026
7db76f7
Update GitHub Actions workflow to use latest checkout action and enha…
Shresht7 Mar 30, 2026
4eb5cef
Update README to clarify build and testing instructions, and add publ…
Shresht7 Mar 30, 2026
01fd1d4
Update GitHub Actions workflow to trigger on version tag pushes and c…
Shresht7 Mar 30, 2026
1026210
Enhance GitHub Actions workflow by adding artifact attestation
Shresht7 Mar 30, 2026
f42db66
Refactor GitHub Actions workflow to enhance test job configuration
Shresht7 Mar 30, 2026
04254df
Update build configuration and test command in `tasks.json`
Shresht7 Mar 30, 2026
583a7da
Enhance `Build.ps1` script
Shresht7 Mar 30, 2026
bcb17b2
Enhance error handling in file copy process and ensure destination di…
Shresht7 Mar 30, 2026
208c6b1
Refactor `Initialize-Configuration` function to ensure directory and …
Shresht7 Mar 30, 2026
5f7ff89
Add step to create configuration directory for Linux in GitHub Action…
Shresht7 Mar 30, 2026
d4aeb36
Update GitHub Actions workflow to create a configuration directory an…
Shresht7 Mar 30, 2026
b915bdb
Add step to create configuration directory and file for Windows in Gi…
Shresht7 Mar 30, 2026
7161694
Add steps to create configuration directory and file for Linux and Wi…
Shresht7 Mar 30, 2026
f7ede6f
Update build task command in tasks.json to use pwsh with proper argum…
Shresht7 Mar 30, 2026
279efb9
Update README.md to enhance build and testing instructions, and add p…
Shresht7 Mar 30, 2026
914e01b
Refactor Initialize-Configuration to streamline directory and file cr…
Shresht7 Mar 30, 2026
aa8a832
Update config path for Windows to use `LOCALAPPDATA` instead of `APPD…
Shresht7 Mar 30, 2026
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
87 changes: 71 additions & 16 deletions .github/workflows/publish.yml
Original file line number Diff line number Diff line change
@@ -1,25 +1,80 @@
name: Publish PowerShell Module

on:
release:
types: [created]
push:
tags:
- "v*.*.*"

permissions:
contents: write
id-token: write
attestations: write

jobs:
publish-to-gallery:
publish:
runs-on: ubuntu-latest
steps:
- name: Check out repository code
uses: actions/checkout@v4
- name: Checkout Repository Code
uses: actions/checkout@v6

- name: Setup .NET
uses: actions/setup-dotnet@v5
with:
dotnet-version: 8.0.x

# Build the C# predictor and copy the DLL to the Module directory
- name: Build Module
shell: pwsh
run: ./Build.ps1 -Configuration Release

# Ensure the config directory and file exist for both Linux and Windows runners, since the tests expect them to be present.
- name: Create Config Directory and File (Linux)
if: runner.os == 'Linux'
run: |
mkdir -p ~/.local/share/PSFavorite
echo '' > ~/.local/share/PSFavorite/Favorites.txt

- name: Create Config Directory and File (Windows)
if: runner.os == 'Windows'
shell: pwsh
run: |
$configPath = "$env:LOCALAPPDATA\PSFavorite"
New-Item -ItemType Directory -Path $configPath -Force | Out-Null
New-Item -ItemType File -Path "$configPath\Favorites.txt" -Force | Out-Null

# Ensure the built module passes all tests before proceeding
- name: Run Tests
shell: pwsh
run: |
Import-Module ./Module/PSFavorite.psd1 -Force
Invoke-Pester -Path ./Module/Tests -Output Detailed -PassThru

- name: Run tests
# Zip the entire module directory (including the DLL) for attestation and release
- name: Zip Module
shell: pwsh
run: Invoke-Pester -Output Detailed -PassThru

# TODO: Enable this step
# - name: Build and publish
# env:
# NUGET_KEY: ${{ secrets.NUGET_KEY }}
# shell: pwsh
# run: |
# ./Build.ps1 -Configuration Release
# Publish-Module -Path "./Module" -NuGetApiKey $env:NUGET_KEY -Verbose
run: Compress-Archive -Path ./Module -DestinationPath ./PSFavorite.zip

# actions/attest@v4 is the modern, consolidated action for all attestations.
# See: https://docs.github.com/en/actions/how-tos/secure-your-work/use-artifact-attestations/use-artifact-attestations
- name: Attest Build Provenance
uses: actions/attest@v4
with:
subject-path: ${{ github.workspace }}/PSFavorite.zip

# Create a release, attach the zipped module, and auto-generate notes from commits
# See: https://cli.github.com/manual/gh_release
- name: Create GitHub Release
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
gh release create ${{ github.ref_name }} ./PSFavorite.zip \
--title "Release ${{ github.ref_name }}" \
--generate-notes

# TODO: Enable this when ready to publish to PowerShell Gallery
# - name: Publish to PowerShell Gallery
# env:
# NUGET_KEY: ${{ secrets.NUGET_KEY }}
# shell: pwsh
# run: |
# Publish-Module -Path "./Module" -NuGetApiKey $env:NUGET_KEY -Verbose
64 changes: 50 additions & 14 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
@@ -1,19 +1,55 @@
name: Test

on:
push:
branches: [ "main" ]
pull_request:
branches: [ "main" ]
push:
branches: ["main"]
paths:
- "Module/**"
- "Predictor/**"
- "Build.ps1"
pull_request:
branches: ["main"]

jobs:
test:
name: Pester test
runs-on: ubuntu-latest
steps:
- name: Check out repository code
uses: actions/checkout@v4

- name: Run tests
shell: pwsh
run: Invoke-Pester -Output Detailed -PassThru
test:
name: Pester test (${{ matrix.os }})
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, windows-latest]

steps:
- name: Checkout Repository Code
uses: actions/checkout@v6
with:
fetch-depth: 1 # Fetch only the latest commit to speed up the checkout process

- name: Setup .NET
uses: actions/setup-dotnet@v5
with:
dotnet-version: 8.0.x

- name: Build Module
shell: pwsh
run: ./Build.ps1 -Configuration Release

- name: Create Config Directory and File (Linux)
if: runner.os == 'Linux'
run: |
mkdir -p ~/.local/share/PSFavorite
echo '' > ~/.local/share/PSFavorite/Favorites.txt

- name: Create Config Directory and File (Windows)
if: runner.os == 'Windows'
shell: pwsh
run: |
$configPath = "$env:LOCALAPPDATA\PSFavorite"
New-Item -ItemType Directory -Path $configPath -Force | Out-Null
New-Item -ItemType File -Path "$configPath\Favorites.txt" -Force | Out-Null

- name: Run Tests
shell: pwsh
run: |
Import-Module ./Module/PSFavorite.psd1 -Force
Invoke-Pester -Path ./Module/Tests -Output Detailed -PassThru
15 changes: 11 additions & 4 deletions .vscode/tasks.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,13 @@
{
"label": "build",
"type": "shell",
"command": "./Build.ps1",
"command": "pwsh",
"args": [
"-NoProfile",
"-Command",
"./Build.ps1 -Verbose",
"-Configuration",
"Release",
"Debug",
],
"group": {
"kind": "build",
Expand All @@ -24,8 +27,12 @@
{
"label": "test",
"type": "shell",
"command": "Invoke-Pester -Output Detailed",
"args": [],
"command": "pwsh",
"args": [
"-NoProfile",
"-Command",
"./Build.ps1; Import-Module ./Module/PSFavorite.psd1 -Force; Invoke-Pester -Path ./Module/Tests -Output Detailed"
],
"group": {
"kind": "test",
"isDefault": true
Expand Down
75 changes: 65 additions & 10 deletions Build.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,18 @@
Build the project
.DESCRIPTION
Build the project and copy the DLL to the Module directory.
.PARAMETER Configuration
The configuration of the build (`Debug` or `Release`) [Default: `Debug`]
.PARAMETER TargetFramework
The target framework of the build (`net7.0` or `net8.0`) [Default: `net8.0`]
.PARAMETER Clean
If specified, cleans the project before building.
.EXAMPLE
. ./Build.ps1
./Build.ps1
Run the build script to build the project (in debug configuration) and copy the DLL to the Module directory.
.EXAMPLE
. ./Build.ps1 -Configuration Release
Run the build script to build the project (in release configuration) and copy the DLL to the Module directory.
./Build.ps1 -Configuration Release -Clean
Clean and build the project in release configuration.
#>

[CmdletBinding(DefaultParameterSetName = 'Build')]
Expand All @@ -21,17 +27,33 @@ param(
# The target framework of the build (`net7.0` or `net8.0`) [Default: `net8.0`]
[ValidateSet("net7.0", "net8.0")]
[ValidateNotNullOrEmpty()]
[string] $TargetFramework = "net8.0"
[string] $TargetFramework = "net8.0",

# Clean the project before building
[switch] $Clean
)

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

# Clean the project if the -Clean switch is specified
if ($Clean) {
Write-Host "Cleaning project ($Configuration)..."
dotnet clean $Project -c $Configuration -f $TargetFramework
}

# Build the Predictor DLL
Write-Host "Building project ($Configuration)..."
dotnet build $Project -c $Configuration -f $TargetFramework

# Fail fast: If dotnet build failed, do not attempt to copy files
if ($LASTEXITCODE -ne 0) {
Write-Error "Build failed. Please check the output above."
exit $LASTEXITCODE
}

# Items to copy to the Module directory
@(
$Items = @(
@{
Source = "$PSScriptRoot\Predictor\bin\$Configuration\$TargetFramework\PSFavoritePredictor.dll"
Destination = "$PSScriptRoot\Module\Library\PSFavoritePredictor.dll"
Expand All @@ -44,10 +66,43 @@ dotnet build $Project -c $Configuration -f $TargetFramework
Source = "$PSScriptRoot\LICENSE"
Destination = "$PSScriptRoot\Module\LICENSE"
}
) | ForEach-Object {
if (Test-Path -Path $_.Destination) {
Remove-Item -Path $_.Destination -Force
)

# Copy items with Error Handling
foreach ($item in $Items) {
try {
# Ensure the destination directory exists
$destDir = Split-Path -Path $item.Destination -Parent
if (!(Test-Path -Path $destDir)) {
New-Item -ItemType Directory -Path $destDir -Force | Out-Null
Write-Verbose "Created directory: $destDir"
}

# If the destination file already exists, remove it before copying the new file
if (Test-Path -Path $item.Destination) {
Remove-Item -Path $item.Destination -Force
}

# Copy the file to the destination
Copy-Item -Path $item.Source -Destination $item.Destination -Force
Write-Output "Copied $($item.Source) to $($item.Destination)"
}
catch {
$msg = $_.Exception.Message
# Specific handling for the locked DLL issue vs other errors
if ($item.Destination -like "*PSFavoritePredictor.dll" -and ($msg -like "*used by another process*" -or $msg -like "*Access to the path*denied*")) {
Write-Host ""
Write-Host "CRITICAL ERROR: Access Denied to PSFavoritePredictor.dll" -ForegroundColor Red
Write-Host "The DLL is likely locked because the module is loaded in an active PowerShell session." -ForegroundColor Yellow
Write-Host "To fix this, please close all PowerShell windows and try again." -ForegroundColor Yellow
Write-Host ""
}
else {
Write-Error "Failed to copy $($item.Source) to $($item.Destination): $msg"
}
exit 1
}
Copy-Item -Path $_.Source -Destination $_.Destination -Force
Write-Output "Copied $($_.Source) to $($_.Destination)"
}


Write-Host "Build and Copy completed successfully!" -ForegroundColor Green
28 changes: 14 additions & 14 deletions Module/Private/Initialize-Configuration.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -26,24 +26,24 @@ function Initialize-Configuration(
# If the FavoritesPath parameter is specified, use it ...
if ($FavoritesPath) {
$Script:FavoritesPath = $FavoritesPath
return
}
# ... otherwise,
# ... otherwise, use the default path
else {
# Create the PSFavorite directory if it doesn't already exist
$local = if ($IsWindows) { $Env:LOCALAPPDATA } else { "~/.local/share" }
$folder = Join-Path $local $ModuleName
if (!(Test-Path -Path $folder)) {
New-Item -ItemType Directory -Path $folder
}

$local = if ($IsWindows) { $Env:LOCALAPPDATA } else { "$HOME/.local/share" }
$folder = Join-Path $local $ModuleName
# Path to the Favorites file
$Script:FavoritesPath = Join-Path $folder "Favorites.txt"

# If the file doesn't exist, create an empty file
if (!(Test-Path -Path $Script:FavoritesPath)) {
New-Item -ItemType File -Path $Script:FavoritesPath
}
$Script:FavoritesPath = Join-Path $folder "Favorites.txt"
}

# Create the directory if it doesn't exist
$folder = Split-Path $Script:FavoritesPath
if (-not (Test-Path -Path $folder)) {
New-Item -ItemType Directory -Path $folder -Force | Out-Null
}

# Create the Favorites file if it doesn't exist
if (-not (Test-Path -Path $Script:FavoritesPath)) {
New-Item -ItemType File -Path $Script:FavoritesPath -Force | Out-Null
}
}
20 changes: 16 additions & 4 deletions Module/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -75,19 +75,31 @@ Import-Module -Name PSFavorite

### 📜 Scripts

- [`Build.ps1`](./Build.ps1) - Builds the PowerShell module
- [`Build.ps1`](./Build.ps1) - Builds the C# predictor and copies the DLL to the Module directory.

```pwsh
./Build.ps1 -Configuration Release
```

### 🧪 Testing

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

```pwsh
Invoke-Pester
./Build.ps1
Import-Module ./Module/PSFavorite.psd1 -Force
Invoke-Pester -Path ./Module/Tests
```

### 🚢 Publishing

The module is automatically published to the PowerShell Gallery when a new **Release** is created on GitHub.

Ensure the `ModuleVersion` in `Module/PSFavorite.psd1` is updated before creating a release.

## 📕 Reference

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

---

Expand Down
Loading