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
3 changes: 2 additions & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,12 @@ jobs:
- name: Install Pester
shell: pwsh
run: |
Install-Module -Name Pester -MinimumVersion 5.0.0 -Force -Scope CurrentUser -SkipPublisherCheck
Install-Module -Name Pester -MinimumVersion 5.0.0 -MaximumVersion 5.99.99 -Force -Scope CurrentUser -SkipPublisherCheck

- name: Run Pester tests
shell: pwsh
run: |
Import-Module Pester -MinimumVersion 5.0.0 -MaximumVersion 5.99.99 -Force
$config = New-PesterConfiguration
$config.Run.Path = './tests'
$config.Run.Exit = $true
Expand Down
67 changes: 67 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
# Changelog

All notable changes to this project are documented here. The format follows
[Keep a Changelog](https://keepachangelog.com/en/1.1.0/) and versions follow
[Semantic Versioning](https://semver.org/).

## [1.2.0] - 2026-09-04

Gallery-native release: everything that used to require a git clone now works
on a PowerShell Gallery install.

### Added
- `Initialize-Toolkit`: first-run setup that creates the data folder, copies
`config.example.json` to `config.json`, creates the `creds` folder and opens
the config in your editor. Works for git-clone and Gallery installs.
- `New-SSHCredential`: prompts for an SSH username/password and stores it
DPAPI-encrypted in the creds folder. Replaces the manual `Export-Clixml` steps.
- Tab completion for SSH server aliases on `cssh` / `tunnel` / `Connect-SSH` /
`Connect-SSHTunnel`, and for database names on the tunnel port argument.
- `PSDT_HOME` environment variable to override where config and creds live.
- `PSDT_SKIP_UPDATE_CHECK` environment variable to disable the startup update check.
- `helpme` shows the install type and the resolved `config.json` path.
- This changelog.

### Changed
- Config and credentials now resolve through a single data folder: the repo root
for git clones (unchanged), `%LOCALAPPDATA%\PowerShellDevToolkit` for Gallery
installs, or `PSDT_HOME` when set.
- `config.example.json` moved from the repo root into the module folder so it
ships in the Gallery package.
- `Update-Toolkit` and the startup update check detect Gallery installs and
compare against the Gallery, updating with `Update-Module` / `Update-PSResource`.
- A missing `config.json` no longer prompts from inside other commands; commands
print where they looked and point to `Initialize-Toolkit`.
- `e` / `Edit-File` no longer prints a config warning when `config.json` is absent.

### Fixed
- Gallery installs could not locate `config.json`, the `creds` folder or
`config.example.json`, and `Update-Toolkit` failed with "not a git repository".

### Internal
- Shared `Resolve-SSHTarget` and `Test-WslAvailable` helpers replace about 100
duplicated lines in `Connect-SSH` and `Connect-SSHTunnel`.
- New `Get-ToolkitPaths` helper is the single source of truth for on-disk paths.
- 50+ new Pester tests; Gallery behavior is tested with mocks, never the network.

## [1.1.0] - 2026-04-09

### Added
- Proper PowerShell module (`PowerShellDevToolkit`) with manifest, Public/Private
layout and exported aliases; published to the PowerShell Gallery.
- All commands from the documentation implemented as module functions.
- `Update-Toolkit` self-update and daily startup update check.
- Pester 5 test suite and GitHub Actions CI on Windows.
- Logo assets in README, setup and `helpme`.

### Fixed
- Multiple bugs found in code review and by the new tests (parameter alias
conflict in `Copy-ToClipboard`, git detection from subdirectories, Vue/Nuxt
detection, `Write-Warning` shadowing in setup, `Set-ProjectEnv` scope bug).

## [1.0.0] - 2025-12-11

### Added
- Initial release as a collection of standalone scripts: SSH connect and tunnel
with credential and key-file support, dev server launcher, port tools, project
detection, AI rules generation, log tailing and quick utilities.
15 changes: 9 additions & 6 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,7 @@ Enhancement suggestions are tracked as GitHub issues. When creating an enhanceme
- **Never hardcode user-specific information**
- **Use `config.json` for user settings**
- **Provide sensible defaults**
- **Document all configuration options** in `config.example.json`
- **Document all configuration options** in `PowerShellDevToolkit\config.example.json`

### Security

Expand All @@ -122,7 +122,7 @@ Enhancement suggestions are tracked as GitHub issues. When creating an enhanceme
- Update `README.md` if you add new features
- Add examples to help text
- Document configuration options
- Update `helpme.ps1` for new commands
- Update `Show-Help` (`Public\Show-Help.ps1`) and `docs/COMMANDS.md` for new commands, and add a `CHANGELOG.md` entry

## Testing

Expand Down Expand Up @@ -162,16 +162,19 @@ Also test manually:
powershell-dev-toolkit/
├── PowerShellDevToolkit/ # The PS module
│ ├── PowerShellDevToolkit.psd1 # Module manifest (version, exports)
│ ├── PowerShellDevToolkit.psm1 # Root module (auto-loader, aliases)
│ ├── PowerShellDevToolkit.psm1 # Root module (auto-loader, aliases, completers)
│ ├── config.example.json # Configuration template (ships in the Gallery package)
│ ├── Public/ # Exported functions (one per file)
│ │ ├── Connect-SSH.ps1
│ │ ├── Get-GitQuick.ps1
│ │ ├── Initialize-Toolkit.ps1
│ │ └── ...
│ └── Private/ # Internal helpers (not exported)
│ └── Get-ScriptConfig.ps1
│ ├── Get-ToolkitPaths.ps1 # Install type + data folder resolution
│ ├── Get-ScriptConfig.ps1
│ ├── Resolve-SSHTarget.ps1
│ └── ...
├── tests/ # Pester tests
├── docs/ # Documentation
├── config.example.json # Configuration template
├── Setup-Environment.ps1 # Bootstrap / installer
├── README.md
├── LICENSE
Expand Down
8 changes: 4 additions & 4 deletions Invoke-Tests.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,12 @@

$ErrorActionPreference = 'Stop'

if (-not (Get-Module -ListAvailable -Name Pester | Where-Object { $_.Version -ge '5.0' })) {
Write-Host "Pester 5+ not found. Installing from PSGallery..." -ForegroundColor Yellow
Install-Module -Name Pester -MinimumVersion 5.0.0 -Force -Scope CurrentUser -SkipPublisherCheck
if (-not (Get-Module -ListAvailable -Name Pester | Where-Object { $_.Version -ge '5.0' -and $_.Version -lt '6.0' })) {
Write-Host "Pester 5.x not found. Installing from PSGallery..." -ForegroundColor Yellow
Install-Module -Name Pester -MinimumVersion 5.0.0 -MaximumVersion 5.99.99 -Force -Scope CurrentUser -SkipPublisherCheck
}

Import-Module Pester -MinimumVersion 5.0.0
Import-Module Pester -MinimumVersion 5.0.0 -MaximumVersion 5.99.99 -Force

$config = New-PesterConfiguration
$config.Run.Path = Join-Path $PSScriptRoot 'tests'
Expand Down
16 changes: 15 additions & 1 deletion PowerShellDevToolkit/PowerShellDevToolkit.psd1
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
@{
RootModule = 'PowerShellDevToolkit.psm1'
ModuleVersion = '1.1.0'
ModuleVersion = '1.2.0'
GUID = '882e07c2-69ad-46e6-aea6-07adb025f6b3'
Author = 'PowerShell Dev Toolkit Contributors'
CompanyName = 'Community'
Expand Down Expand Up @@ -50,6 +50,8 @@
# Toolkit management
'Update-Toolkit'
'Test-ToolkitUpdate'
'Initialize-Toolkit'
'New-SSHCredential'
)

CmdletsToExport = @()
Expand Down Expand Up @@ -99,6 +101,18 @@
Tags = @('Windows', 'Developer', 'Productivity', 'SSH', 'DevTools')
LicenseUri = 'https://github.com/joshuaevan/powershell-dev-toolkit/blob/main/LICENSE'
ProjectUri = 'https://github.com/joshuaevan/powershell-dev-toolkit'
ReleaseNotes = @'
1.2.0 - Gallery-native
- New: Initialize-Toolkit creates config.json and the creds folder for any install type.
- New: New-SSHCredential stores SSH username/password (DPAPI-encrypted) in the creds folder.
- New: Tab completion for SSH server aliases (cssh, tunnel) and database port names.
- New: PSDT_HOME overrides the data folder; PSDT_SKIP_UPDATE_CHECK disables the startup check.
- Changed: Gallery installs keep config and creds in %LOCALAPPDATA%\PowerShellDevToolkit; git clones keep using the repo root.
- Changed: Update-Toolkit and the startup update check work on Gallery installs.
- Changed: config.example.json now ships inside the module folder.
- Fixed: Gallery installs could not find config.json, creds, or config.example.json.
See CHANGELOG.md for details.
'@
}
}
}
11 changes: 9 additions & 2 deletions PowerShellDevToolkit/PowerShellDevToolkit.psm1
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
# PowerShellDevToolkit Root Module
# Repo root is one level above the module folder
$script:ModuleRoot = $PSScriptRoot
# Repo root is one level above the module folder (only meaningful for git installs;
# used for repo-only assets such as the ASCII logo). User data paths come from Get-ToolkitPaths.
$script:ToolkitRoot = Split-Path $PSScriptRoot

# Dot-source private functions first, then public
Expand All @@ -11,6 +13,9 @@ foreach ($file in @($Private + $Public)) {
catch { Write-Error "Failed to import $($file.FullName): $_" }
}

# Tab completion for SSH server aliases and database port names
Register-ToolkitArgumentCompleters

# Aliases — existing commands
New-Alias -Name cssh -Value Connect-SSH -Force -Scope Global
New-Alias -Name tunnel -Value Connect-SSHTunnel -Force -Scope Global
Expand Down Expand Up @@ -60,4 +65,6 @@ function global:la { Get-DirectoryListing -Force @args }
function global:o. { Open-Item . }

# Startup update check (runs once per configured interval, silent on error)
if ([Environment]::UserInteractive) { try { Test-ToolkitUpdate } catch { } }
if ([Environment]::UserInteractive -and [string]::IsNullOrEmpty($env:PSDT_SKIP_UPDATE_CHECK)) {
try { Test-ToolkitUpdate } catch { }
}
53 changes: 21 additions & 32 deletions PowerShellDevToolkit/Private/Get-ScriptConfig.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -4,53 +4,42 @@ function Get-ScriptConfig {
Load configuration for PowerShell Dev Toolkit.

.DESCRIPTION
Loads configuration from config.json relative to the toolkit root.
If config.json doesn't exist, prompts to create from example.
Loads config.json from the toolkit data folder (see Get-ToolkitPaths).
Returns $null when the file is missing or cannot be parsed.

.PARAMETER Quiet
Suppress all host output. Use from tab completers and from commands
that only need the config if it happens to exist.

.EXAMPLE
$config = Get-ScriptConfig
$config.ssh.servers
#>
[CmdletBinding()]
param()
param(
[switch]$Quiet
)

$configPath = Join-Path $script:ToolkitRoot "config.json"
$examplePath = Join-Path $script:ToolkitRoot "config.example.json"
$configPath = (Get-ToolkitPaths).ConfigPath

if (-not (Test-Path $configPath)) {
Write-Host ""
Write-Host "Configuration file not found!" -ForegroundColor Yellow
Write-Host ""
Write-Host "To set up your configuration:" -ForegroundColor Cyan
Write-Host " 1. Copy config.example.json to config.json" -ForegroundColor White
Write-Host " 2. Edit config.json with your settings" -ForegroundColor White
Write-Host ""

if (Test-Path $examplePath) {
Write-Host "Would you like to create config.json from the example now? (Y/N): " -NoNewline -ForegroundColor Yellow
$response = Read-Host

if ($response -eq 'Y' -or $response -eq 'y') {
Copy-Item $examplePath $configPath
Write-Host ""
Write-Host "Created config.json - please edit it with your settings." -ForegroundColor Green
Write-Host "Location: $configPath" -ForegroundColor Gray
Write-Host ""

if (Get-Command notepad -ErrorAction SilentlyContinue) {
Start-Process notepad $configPath
}
}
if (-not $Quiet) {
Write-Host ""
Write-Host "Configuration file not found: $configPath" -ForegroundColor Yellow
Write-Host "Run " -NoNewline -ForegroundColor Cyan
Write-Host "Initialize-Toolkit" -NoNewline -ForegroundColor Yellow
Write-Host " to create it." -ForegroundColor Cyan
Write-Host ""
}

return $null
}

try {
$config = Get-Content $configPath -Raw | ConvertFrom-Json
return $config
return (Get-Content $configPath -Raw | ConvertFrom-Json)
} catch {
Write-Host "Error loading config.json: $($_.Exception.Message)" -ForegroundColor Red
if (-not $Quiet) {
Write-Host "Error loading config.json ($configPath): $($_.Exception.Message)" -ForegroundColor Red
}
return $null
}
}
38 changes: 38 additions & 0 deletions PowerShellDevToolkit/Private/Get-ToolkitGalleryStatus.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
function Get-ToolkitGalleryStatus {
<#
.SYNOPSIS
Compare the installed module version with the PowerShell Gallery.

.PARAMETER Paths
The object returned by Get-ToolkitPaths.

.OUTPUTS
PSCustomObject with Installed, Latest, UpdateAvailable and ReleaseNotes,
or $null when the Gallery cannot be reached.
#>
[CmdletBinding()]
param(
[Parameter(Mandatory = $true)]
$Paths
)

$manifest = Import-PowerShellDataFile (Join-Path $Paths.ModuleRoot 'PowerShellDevToolkit.psd1')
$installed = [version]$manifest.ModuleVersion

try {
$latest = Find-Module -Name PowerShellDevToolkit -Repository PSGallery -ErrorAction Stop
}
catch {
return $null
}
if (-not $latest) { return $null }

$latestVersion = [version]$latest.Version

return [pscustomobject]@{
Installed = $installed
Latest = $latestVersion
UpdateAvailable = ($latestVersion -gt $installed)
ReleaseNotes = $latest.ReleaseNotes
}
}
47 changes: 47 additions & 0 deletions PowerShellDevToolkit/Private/Get-ToolkitPaths.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
function Get-ToolkitPaths {
<#
.SYNOPSIS
Resolve every on-disk location the toolkit uses.

.DESCRIPTION
Returns an object describing where the module lives, whether it was
installed from a git checkout or the PowerShell Gallery, and where user
data (config.json, the creds folder, the update-check stamp) is stored.

Data folder resolution order:
1. $env:PSDT_HOME when set
2. The repo root when the module sits inside a git checkout
3. $env:LOCALAPPDATA\PowerShellDevToolkit

Evaluated on every call so PSDT_HOME can change at runtime.

.EXAMPLE
$paths = Get-ToolkitPaths
$paths.ConfigPath
#>
[CmdletBinding()]
param()

$moduleRoot = $script:ModuleRoot
$parentDir = Split-Path $moduleRoot -Parent
$isGit = Test-Path (Join-Path $parentDir '.git')

$dataRoot = if (-not [string]::IsNullOrWhiteSpace($env:PSDT_HOME)) {
$env:PSDT_HOME
} elseif ($isGit) {
$parentDir
} else {
Join-Path $env:LOCALAPPDATA 'PowerShellDevToolkit'
}

[pscustomobject]@{
InstallType = if ($isGit) { 'Git' } else { 'Gallery' }
ModuleRoot = $moduleRoot
RepoRoot = if ($isGit) { $parentDir } else { $null }
DataRoot = $dataRoot
ConfigPath = Join-Path $dataRoot 'config.json'
ExampleConfigPath = Join-Path $moduleRoot 'config.example.json'
CredsPath = Join-Path $dataRoot 'creds'
UpdateStampPath = Join-Path $dataRoot '.last-update-check'
}
}
Loading
Loading