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
5 changes: 3 additions & 2 deletions .github/workflows/publish-module.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ on:
- Shmuelie.Copilot
- Shmuelie.Node
- Shmuelie.Utilities
- Shmuelie.Dsc

jobs:
publish:
Expand All @@ -38,11 +39,11 @@ jobs:
INPUT_MODULE: ${{ inputs.module }}
REF_NAME: ${{ github.ref_name }}
run: |
$allowed = @('Shmuelie.Git', 'Shmuelie.Copilot', 'Shmuelie.Node', 'Shmuelie.Utilities')
$allowed = @('Shmuelie.Git', 'Shmuelie.Copilot', 'Shmuelie.Node', 'Shmuelie.Utilities', 'Shmuelie.Dsc')
$module = $env:INPUT_MODULE
$tagVersion = $null
if (-not $module) {
if ($env:REF_NAME -notmatch '^(?<module>Shmuelie\.(?:Git|Copilot|Node|Utilities))-v(?<version>\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?)$') {
if ($env:REF_NAME -notmatch '^(?<module>Shmuelie\.(?:Git|Copilot|Node|Utilities|Dsc))-v(?<version>\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?)$') {
throw "Invalid release tag: $env:REF_NAME"
}
$module = $Matches.module
Expand Down
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,14 @@ between releases this file tracks catalog-level changes under `[Unreleased]`.
site.
- `Build-Module.ps1` now stages optional module `Classes` folders for shared
PowerShell class definitions.
- `Build-Module.ps1` now stages a module's `Public` folder only when it exists,
so modules without `Public` scripts (such as DSC resource modules) build; it
also fails fast if a module declares functions but has no `Public` folder.

### Added
- New `Shmuelie.Dsc` module: class-based DSC v3 resources (`SavePSResource`,
`SymbolicLink`, `CopilotPlugin`, `CopilotMarketplace`, `UvTool`), wired into
the build, publish workflow, and documentation site.
- GitHub Pages documentation workflow now redeploys automatically when docs or its workflow change.
- Pester v5 unit test suite under `tests/`, a `build/Invoke-Tests.ps1` runner,
and CI wiring (`ci.yml` plus a gate in `publish-module.yml`) so behavioral
Expand Down
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ Install-PSResource Shmuelie.Git
Install-PSResource Shmuelie.Copilot
Install-PSResource Shmuelie.Node
Install-PSResource Shmuelie.Utilities
Install-PSResource Shmuelie.Dsc
```

## Modules
Expand All @@ -18,6 +19,7 @@ Install-PSResource Shmuelie.Utilities
| [Shmuelie.Copilot](docs/modules.md#shmuelie-copilot) | Copilot CLI sessions, plugins, marketplaces, MCP, and launcher |
| [Shmuelie.Node](docs/modules.md#shmuelie-node) | Node.js, nvm-windows, npm, and ADO npm credentials |
| [Shmuelie.Utilities](docs/modules.md#shmuelie-utilities) | .NET, Python, VS Code, Terminal, services, and WPR |
| [Shmuelie.Dsc](docs/modules.md#shmuelie-dsc) | DSC v3 resources for setup: modules, symlinks, Copilot plugins/marketplaces, uv tools |

## Documentation

Expand Down
10 changes: 8 additions & 2 deletions build/Build-Module.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
[CmdletBinding()]
param(
[Parameter(Mandatory)]
[ValidateSet('Shmuelie.Git', 'Shmuelie.Copilot', 'Shmuelie.Node', 'Shmuelie.Utilities')]
[ValidateSet('Shmuelie.Git', 'Shmuelie.Copilot', 'Shmuelie.Node', 'Shmuelie.Utilities', 'Shmuelie.Dsc')]
[string]$Module,

[string]$OutputPath
Expand All @@ -34,7 +34,13 @@ New-Item $stage -ItemType Directory -Force | Out-Null
foreach ($name in @("$Module.psd1", "$Module.psm1", 'README.md', 'CHANGELOG.md')) {
Copy-Item (Join-Path $source $name) $stage
}
Copy-Item (Join-Path $source 'Public') $stage -Recurse
$public = Join-Path $source 'Public'
if (Test-Path $public) {
Copy-Item $public $stage -Recurse
}
if ($manifest.ExportedFunctions.Count -gt 0 -and -not (Test-Path $public)) {
throw "$Module declares FunctionsToExport but has no Public folder to stage."
}
$classes = Join-Path $source 'Classes'
if (Test-Path $classes) {
Copy-Item $classes $stage -Recurse
Expand Down
2 changes: 1 addition & 1 deletion build/Publish-Module.ps1
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
[CmdletBinding(SupportsShouldProcess)]
param(
[Parameter(Mandatory)]
[ValidateSet('Shmuelie.Git', 'Shmuelie.Copilot', 'Shmuelie.Node', 'Shmuelie.Utilities')]
[ValidateSet('Shmuelie.Git', 'Shmuelie.Copilot', 'Shmuelie.Node', 'Shmuelie.Utilities', 'Shmuelie.Dsc')]
[string]$Module,

[Parameter(Mandatory)]
Expand Down
2 changes: 1 addition & 1 deletion build/Test-Modules.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ param()

$ErrorActionPreference = 'Stop'
$repoRoot = Split-Path $PSScriptRoot -Parent
$modules = @('Shmuelie.Git', 'Shmuelie.Copilot', 'Shmuelie.Node', 'Shmuelie.Utilities')
$modules = @('Shmuelie.Git', 'Shmuelie.Copilot', 'Shmuelie.Node', 'Shmuelie.Utilities', 'Shmuelie.Dsc')

foreach ($module in $modules) {
Write-Information "Building $module" -InformationAction Continue
Expand Down
1 change: 1 addition & 0 deletions docs/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ install and upgrade each module on its own.
| [Shmuelie.Copilot](modules.md#shmuelie-copilot) | Copilot CLI sessions, plugins, marketplaces, MCP, and launcher | 0.2.0 |
| [Shmuelie.Node](modules.md#shmuelie-node) | Node.js, nvm-windows, npm, and ADO npm credentials | 0.1.3 |
| [Shmuelie.Utilities](modules.md#shmuelie-utilities) | .NET, Python, VS Code, Terminal, services, and WPR | 0.2.3 |
| [Shmuelie.Dsc](modules.md#shmuelie-dsc) | DSC v3 resources for setup: modules, symlinks, Copilot plugins/marketplaces, uv tools | 0.1.0 |

## Quick start

Expand Down
14 changes: 14 additions & 0 deletions docs/modules.md
Original file line number Diff line number Diff line change
Expand Up @@ -85,3 +85,17 @@ Highlights:
- Core helpers: `Test-IsElevated`, `Invoke-InLocation`, `Reset-TerminalModes`.
- Tool management for `dotnet`, `pip`, `uv`, and VS Code extensions.
- `Get-ServiceProcess` resolves a service to its hosting process.

## Shmuelie.Dsc

Class-based [DSC v3](https://learn.microsoft.com/powershell/dsc/overview)
resources for developer machine setup. **Version 0.1.0.**
[README](https://github.com/shmuelie/powershell-modules/blob/main/modules/Shmuelie.Dsc/README.md)

Highlights:

- `SavePSResource` saves a PowerShell module to a local path; `SymbolicLink`
creates and verifies symbolic links.
- `CopilotPlugin` and `CopilotMarketplace` install GitHub Copilot CLI plugins
and register marketplaces.
- `UvTool` installs a Python tool via `uv tool install`.
17 changes: 17 additions & 0 deletions modules/Shmuelie.Dsc/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# Changelog

The format is based on [Keep a Changelog](https://keepachangelog.com/).
Versions change only when a release is cut; unreleased work stays under
`[Unreleased]`.

## [Unreleased]

### Added
- Initial class-based DSC v3 resources: `SavePSResource` (save a module to a
local path, with an optional `Version`), `SymbolicLink` (create/verify a
symbolic link), `CopilotPlugin` (install a GitHub Copilot CLI plugin, with an
optional `Name` for URL sources), `CopilotMarketplace` (register a Copilot CLI
marketplace), and `UvTool` (install a Python tool via `uv`). Presence checks
use whole-token matching over ANSI-stripped CLI output, CLI arguments are
validated as shell-safe, `Get()` reports actual state, and CLI failures
include the tool's output.
88 changes: 88 additions & 0 deletions modules/Shmuelie.Dsc/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
# Shmuelie.Dsc

Class-based [DSC v3](https://learn.microsoft.com/powershell/dsc/overview)
resources for developer machine setup. Each resource is a PowerShell class
implementing `Get()`, `Test()`, and `Set()`, exported via
`DscResourcesToExport`. The Copilot and uv resources depend only on the public
`copilot` and `uv` CLIs; the others use built-in PowerShell only.

**Version:** 0.1.0

## Install

```powershell
Install-PSResource Shmuelie.Dsc
```

## Resources

| Resource | Key | Purpose |
|---|---|---|
| `SavePSResource` | `Name` | Save a PowerShell module to a local path via `Save-PSResource` (defaults to `PSGallery`). |
| `SymbolicLink` | `Path` | Create/verify a symbolic link to a target path. |
| `CopilotPlugin` | `Source` | Install a GitHub Copilot CLI plugin (`owner/repo`, `plugin@marketplace`, or URL). |
| `CopilotMarketplace` | `Name` | Register a GitHub Copilot CLI plugin marketplace (`owner/repo`). |
| `UvTool` | `Name` | Install a Python tool via `uv tool install`. |

## Usage

These are DSC v3 resources, addressed as `Shmuelie.Dsc/<ResourceName>`:

```yaml
- name: Save Pester
type: Shmuelie.Dsc/SavePSResource
properties:
Name: Pester
Path: C:\Modules

- name: Symlink .gitconfig
type: Shmuelie.Dsc/SymbolicLink
properties:
Path: C:\Users\me\.gitconfig
Target: C:\dotfiles\.gitconfig

- name: Install a Copilot plugin
type: Shmuelie.Dsc/CopilotPlugin
properties:
Source: owner/repo

- name: Register a Copilot marketplace
type: Shmuelie.Dsc/CopilotMarketplace
properties:
Name: dotnet-skills
Repository: dotnet/skills

- name: Install a uv tool
type: Shmuelie.Dsc/UvTool
properties:
Name: fast-agent-mcp
```

## Notes

- **Idempotency / presence checks.** `CopilotPlugin`, `CopilotMarketplace`, and
`UvTool` determine "already installed" by a whole-token match against the
relevant CLI list output (color/ANSI is stripped first), so a desired name
that is a substring of another entry does not produce a false positive.
- **`CopilotPlugin` URL sources.** The installed plugin name is derived from
`Source` for `owner/repo`, `plugin@marketplace`, and `market:plugin@marketplace`
forms. For a URL source the name cannot be derived reliably — set the optional
`Name` property so the presence check matches, otherwise the plugin is
re-installed on every apply.
- **`SavePSResource` version.** Set the optional `Version` property to make the
presence check (and the save) version-specific.
- **Shell-safe arguments.** Values passed to the `copilot`/`uv` CLIs are
validated to reject characters that Windows would re-parse when the CLI
resolves to a `.cmd`/`.bat` shim.

## Requirements

- PowerShell 7.4 or later.
- `SavePSResource` requires `Microsoft.PowerShell.PSResourceGet` (`Save-PSResource`).
- `CopilotPlugin` / `CopilotMarketplace` require the GitHub Copilot CLI (`copilot`) on `PATH`.
- `UvTool` requires the `uv` CLI on `PATH`.
- `SymbolicLink` on Windows requires Developer Mode or an elevated session.

## Changelog

See [CHANGELOG.md](CHANGELOG.md).
23 changes: 23 additions & 0 deletions modules/Shmuelie.Dsc/Shmuelie.Dsc.psd1
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
@{
RootModule = 'Shmuelie.Dsc.psm1'
ModuleVersion = '0.1.0'
GUID = 'd4406e2b-fd8f-4311-aa4e-178d2794102a'
Author = 'Shmueli Englard'
CompanyName = 'Shmuelie'
Copyright = '(c) Shmueli Englard. All rights reserved.'
Description = 'Class-based DSC v3 resources for developer machine setup: save PowerShell modules to a path, manage symbolic links, install GitHub Copilot CLI plugins and marketplaces, and install uv Python tools.'
PowerShellVersion = '7.4'
CompatiblePSEditions = @('Core')
FunctionsToExport = @()
CmdletsToExport = @()
VariablesToExport = @()
AliasesToExport = @()
DscResourcesToExport = @('SavePSResource', 'SymbolicLink', 'CopilotPlugin', 'CopilotMarketplace', 'UvTool')
PrivateData = @{
PSData = @{
Tags = @('DSC', 'DSCv3', 'DesiredStateConfiguration', 'PSDscResource', 'Setup', 'DeveloperTools')
LicenseUri = 'https://github.com/shmuelie/powershell-modules/blob/main/LICENSE'
ProjectUri = 'https://github.com/shmuelie/powershell-modules'
}
}
}
Loading