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
19 changes: 19 additions & 0 deletions .github/workflows/validate.yml
Original file line number Diff line number Diff line change
Expand Up @@ -39,3 +39,22 @@ jobs:

- name: Scan SKILL.md bodies for safety issues
run: python scripts/check_skill_safety.py

smoke-powershell-module:
name: Smoke-test powershell-module templates (pwsh on Linux)
runs-on: ubuntu-latest
defaults:
run:
shell: pwsh
steps:
- name: Check out repository
uses: actions/checkout@v4

- name: Install PSScriptAnalyzer and Pester
run: |
Set-PSRepository PSGallery -InstallationPolicy Trusted
Install-Module PSScriptAnalyzer -Force -Scope CurrentUser
Install-Module Pester -Force -Scope CurrentUser

- name: Stamp templates and verify the module works
run: ./scripts/smoke_test_powershell_module.ps1
13 changes: 13 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,19 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

### Added

- A cross-platform smoke test for the `powershell-module` templates
(`scripts/smoke_test_powershell_module.ps1`): stamps the core-tier templates with dummy values
and verifies the result parses (`Test-ModuleManifest`), imports, lints clean (PSScriptAnalyzer),
and passes its Pester scaffold. CI runs it with pwsh on `ubuntu-latest`, so the templates are now
exercised on Linux for every push/PR (ADR-0005).

### Changed

- The `{{Guid}}` placeholder instruction in `/new-repo` is now shell-agnostic (`uuidgen`, pwsh, or
python) instead of assuming PowerShell is available on the scaffolding host.

## [0.2.0] - 2026-07-06

### Added
Expand Down
38 changes: 38 additions & 0 deletions docs/adr/0005-cross-platform-pwsh.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
# ADR-0005: RepoKit runs from any host OS; PowerShell templates are Linux-tested

- **Status:** accepted
- **Date:** 2026-07-07

## Context

RepoKit's plugin is prose and templates — it ships no scripts that run on the user's machine.
A scaffolding session shells out in whatever shell the host provides, so on Windows the author's
sessions ran PowerShell, which raised the question: does any of this work on Linux/macOS?

An audit found the moving parts were already cross-platform by design: skill instructions use
`git`, `gh`, and `rg`; the repo's own validation is Python on `ubuntu-latest`; and the
`powershell-module` templates target pwsh 7 (`PowerShellVersion = '7.0'`,
`CompatiblePSEditions = @('Core')`, forward-slash paths) with CI on `ubuntu-latest` +
`shell: pwsh`. But two gaps remained: nothing in *this* repo ever executed the stamped templates
(only a scaffolded repo's own CI would, after the fact), and one skill instruction assumed
PowerShell on the scaffolding host (`{{Guid}}` via `[guid]::NewGuid()`).

## Decision

1. **Skill instructions must not assume a host shell.** Use cross-platform CLIs (`git`, `gh`,
`rg`) or list per-OS equivalents (as the `{{Guid}}` row now does).
2. **PowerShell templates target pwsh 7+ / PSEdition Core only** — no Windows PowerShell 5.1
compatibility, no Windows-only cmdlets or path assumptions.
3. **This repo's CI proves it on Linux:** `scripts/smoke_test_powershell_module.ps1` stamps the
`powershell-module` core templates with dummy values and verifies the result parses, imports,
lints clean, and passes its Pester scaffold. `validate.yml` runs it with pwsh on
`ubuntu-latest` for every push/PR.

## Consequences

- A template regression that breaks on Linux now fails PR CI here, instead of surfacing in the
first repo someone scaffolds.
- macOS is not in the CI matrix; pwsh Core is the same engine there, and the Linux run already
catches case-sensitivity and path-separator mistakes — acceptable until proven otherwise.
- A future repo type that ships *executable* templates should add a matching smoke job; the
script's stamp-then-verify shape is the pattern to copy.
2 changes: 1 addition & 1 deletion plugins/repokit/skills/new-repo/references/placeholders.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
| Token | How to compute |
|-------|----------------|
| `{{year}}` | the current year |
| `{{Guid}}` | a **fresh** GUID per repo — run `[guid]::NewGuid()` (PowerShell) or equivalent. Never reuse a literal GUID; a hardcoded one would collide across every scaffolded module. |
| `{{Guid}}` | a **fresh** GUID per repo — use whatever the host offers: `uuidgen` (Linux/macOS), `[guid]::NewGuid()` (pwsh), or `python3 -c "import uuid; print(uuid.uuid4())"`. Never reuse a literal GUID; a hardcoded one would collide across every scaffolded module. |
| `{{START_HERE_MAP}}` | the where-things-live table you build in step 4 from the resolved file set |

## Post-scaffold self-check
Expand Down
80 changes: 80 additions & 0 deletions scripts/smoke_test_powershell_module.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
#!/usr/bin/env pwsh
#Requires -Version 7.0
# Smoke-test for the powershell-module type templates. Stamps the core-tier
# templates with dummy values into a temp directory, then proves the result is a
# working module: manifest parses, module imports, PSScriptAnalyzer is clean, and
# the scaffolded Pester test passes. Runs on any pwsh 7 platform; CI runs it on
# ubuntu-latest so the templates are exercised on Linux for every push/PR.

$ErrorActionPreference = 'Stop'

$repoRoot = Split-Path -Parent $PSScriptRoot
$templateRoot = Join-Path $repoRoot 'plugins/repokit/skills/new-repo/templates/types/powershell-module/core'
$moduleName = 'RepoKitSmoke'
$stageDir = Join-Path ([IO.Path]::GetTempPath()) "repokit-template-smoke/$moduleName"

if (-not (Test-Path $templateRoot)) { throw "Template root not found: $templateRoot" }
if (Test-Path $stageDir) { Remove-Item $stageDir -Recurse -Force }
New-Item -ItemType Directory -Path $stageDir -Force | Out-Null

# Dummy values for every placeholder the templates can contain
# (see skills/new-repo/references/placeholders.md).
$values = @{
ModuleName = $moduleName
Guid = [guid]::NewGuid().ToString()
name = 'repokit-smoke'
description = 'RepoKit powershell-module template smoke test.'
author = 'RepoKit CI'
license = 'Apache-2.0'
type = 'powershell-module'
tier = 'Core'
year = (Get-Date).Year
}

Write-Host "Stamping templates from $templateRoot"
Get-ChildItem -Path $templateRoot -Recurse -File | ForEach-Object {
$rel = [IO.Path]::GetRelativePath($templateRoot, $_.FullName) -replace '\.tmpl$', ''
foreach ($k in $values.Keys) { $rel = $rel.Replace("{{$k}}", [string]$values[$k]) }
$target = Join-Path $stageDir $rel
New-Item -ItemType Directory -Path (Split-Path -Parent $target) -Force | Out-Null
$content = Get-Content -Path $_.FullName -Raw
if ($null -eq $content) { $content = '' }
foreach ($k in $values.Keys) { $content = $content.Replace("{{$k}}", [string]$values[$k]) }
Set-Content -Path $target -Value $content -NoNewline
Write-Host " $rel"
}

Write-Host "`nCheck 1: no leftover placeholder tokens"
$tokenPattern = '\{\{(name|description|author|year|license|type|tier|ModuleName|Guid|START_HERE_MAP)\}\}'
$leftovers = Get-ChildItem -Path $stageDir -Recurse -File | Select-String -Pattern $tokenPattern
if ($leftovers) {
$leftovers | ForEach-Object { Write-Host " LEFTOVER: $_" }
throw "Placeholder tokens survived stamping."
}

Write-Host "Check 2: Test-ModuleManifest"
$manifest = Join-Path $stageDir "$moduleName.psd1"
Test-ModuleManifest -Path $manifest | Out-Null

Write-Host "Check 3: Import-Module"
Import-Module $manifest -Force
Remove-Module $moduleName -Force

Write-Host "Check 4: PSScriptAnalyzer (Error severity)"
if (Get-Module -ListAvailable PSScriptAnalyzer) {
$issues = Invoke-ScriptAnalyzer -Path $stageDir -Recurse -Severity Error
if ($issues) {
$issues | Format-Table -AutoSize | Out-String | Write-Host
throw "PSScriptAnalyzer found $($issues.Count) error-severity issue(s)."
}
} else {
Write-Warning 'PSScriptAnalyzer not installed - lint check skipped (CI installs it).'
}

Write-Host "Check 5: Pester scaffold test"
Import-Module Pester -MinimumVersion 5.0 -Force
$result = Invoke-Pester -Path (Join-Path $stageDir 'Tests') -PassThru
if ($result.TotalCount -eq 0) { throw 'No Pester tests were discovered in the stamped Tests/ directory.' }
if ($result.FailedCount -gt 0) { throw "$($result.FailedCount) Pester test(s) failed." }

Write-Host "`nSmoke test passed: templates stamp into a working module on $($PSVersionTable.Platform ?? 'Windows') / pwsh $($PSVersionTable.PSVersion)."
Loading