diff --git a/.github/mkdocs.yml b/.github/mkdocs.yml deleted file mode 100644 index df5e17a..0000000 --- a/.github/mkdocs.yml +++ /dev/null @@ -1,75 +0,0 @@ -site_name: -{{ REPO_NAME }}- -theme: - name: material - language: en - font: - text: Roboto - code: Sono - logo: Assets/icon.png - favicon: Assets/icon.png - palette: - # Palette toggle for automatic mode - - media: "(prefers-color-scheme)" - toggle: - icon: material/link - name: Switch to dark mode - # Palette toggle for dark mode - - media: '(prefers-color-scheme: dark)' - scheme: slate - toggle: - primary: black - accent: green - icon: material/toggle-switch-off-outline - name: Switch to light mode - # Palette toggle for light mode - - media: '(prefers-color-scheme: light)' - scheme: default - toggle: - primary: indigo - accent: green - icon: material/toggle-switch - name: Switch to system preference - icon: - repo: material/github - features: - - navigation.instant - - navigation.instant.progress - - navigation.indexes - - navigation.top - - navigation.tracking - - navigation.expand - - search.suggest - - search.highlight - -repo_name: -{{ REPO_OWNER }}-/-{{ REPO_NAME }}- -repo_url: https://github.com/-{{ REPO_OWNER }}-/-{{ REPO_NAME }}- - -plugins: - - search - -markdown_extensions: - - toc: - permalink: true # Adds a link icon to headings - - attr_list - - admonition - - md_in_html - - pymdownx.details # Enables collapsible admonitions - -extra: - social: - - icon: fontawesome/brands/discord - link: https://discord.gg/jedJWCPAhD - name: -{{ REPO_OWNER }}- on Discord - - icon: fontawesome/brands/github - link: https://github.com/-{{ REPO_OWNER }}-/ - name: -{{ REPO_OWNER }}- on GitHub - consent: - title: Cookie consent - description: >- - We use cookies to recognize your repeated visits and preferences, as well - as to measure the effectiveness of our documentation and whether users - find what they're searching for. With your consent, you're helping us to - make our documentation better. - actions: - - accept - - reject diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md new file mode 100644 index 0000000..ff2067e --- /dev/null +++ b/.github/pull_request_template.md @@ -0,0 +1,36 @@ + + + + + +## New: + + + +## Technical Details + + + +
+Related issues + + + +
diff --git a/.github/workflows/Process-PSModule.yml b/.github/workflows/Process-PSModule.yml index 9462763..f0ca651 100644 --- a/.github/workflows/Process-PSModule.yml +++ b/.github/workflows/Process-PSModule.yml @@ -4,6 +4,9 @@ on: workflow_dispatch: schedule: - cron: '0 0 * * *' + push: + branches: + - main pull_request: branches: - main @@ -13,19 +16,22 @@ on: - reopened - synchronize - labeled + - unlabeled concurrency: - group: ${{ github.workflow }}-${{ github.ref }} - cancel-in-progress: true + group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} + cancel-in-progress: ${{ github.event_name == 'pull_request' }} -permissions: - contents: write - pull-requests: write - statuses: write - pages: write - id-token: write +permissions: {} jobs: Process-PSModule: - uses: PSModule/Process-PSModule/.github/workflows/workflow.yml@205d193f34cbbaf9992955c21d842bcf98a1859f # v5.4.6 - secrets: inherit + permissions: + contents: read + pages: write + id-token: write + uses: PSModule/Process-PSModule/.github/workflows/workflow.yml@v8 + secrets: + PSGALLERY_API_KEY: ${{ secrets.PSGALLERY_API_KEY }} + GitHubAppClientId: ${{ secrets.SHELLY_CLIENT_ID }} + GitHubAppPrivateKey: ${{ secrets.SHELLY_PRIVATE_KEY }} diff --git a/.gitignore b/.gitignore index 8af6555..bcf4801 100644 --- a/.gitignore +++ b/.gitignore @@ -14,6 +14,9 @@ # PSModule framework outputs folder outputs/* +# Zensical generated site +docs/site/ + # .Net build output bin/ obj/ diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..db6785f --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,31 @@ +# Agents + +## Main directive + +Everything is a work in progress and can be improved. +If you find a problem or improvement, fix if small; otherwise open an issue. + +## Repo guidance + +- [`README.md`](README.md) — what this module is and how to use it. +- [`CONTRIBUTING.md`](CONTRIBUTING.md) — how to contribute to this repository. + +## PSModule Framework guidance + +For PSModule-specific build, layout, and process guidance: + +- [Template quickstart](https://psmodule.github.io/docs/Modules/Process-PSModule/template-quickstart/) +- [Repository defaults](https://psmodule.github.io/docs/Modules/Repository-Defaults/) +- [Module anatomy](https://psmodule.github.io/docs/Modules/Process-PSModule/module-anatomy/) +- [Build, test, pack, publish](https://psmodule.github.io/docs/Modules/Process-PSModule/build-test-pack-publish/) +- [Standards](https://psmodule.github.io/docs/Modules/Standards/) +- [PSModule/memory](https://github.com/PSModule/memory) + +## Org-wide guidance + +For cross-cutting ways of working and standards: + +- [Agentic Development](https://msxorg.github.io/docs/Ways-of-Working/Agentic-Development/) +- [Ways of Working](https://msxorg.github.io/docs/Ways-of-Working/) +- [Coding Standards](https://msxorg.github.io/docs/Coding-Standards/) +- [MSXOrg/memory](https://github.com/MSXOrg/memory) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..15f724a --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,44 @@ +# Contributing + +Thank you for contributing to this module. +Read [`AGENTS.md`](AGENTS.md) first for the full guidance chain and documentation references. + +## Before you start + +1. Read [`README.md`](README.md) to understand what the module does. +2. Familiarise yourself with the [repository defaults](https://psmodule.github.io/docs/Modules/Repository-Defaults/) that this repository must satisfy. +3. Check the open issues and pull requests to avoid duplicate work. + +## Workflow + +This project follows the [MSXOrg contribution workflow](https://msxorg.github.io/docs/Ways-of-Working/Contribution-Workflow/): + +1. Open or pick up an issue that describes the change. +2. Create a branch from `main` following the `/-` convention (e.g. `feat/42-add-security-md`). +3. Make small, focused commits and push often. +4. Open a **draft PR** as soon as the change has a basic shape — early feedback is preferred. +5. Run the **Copilot review loop**: request a Copilot review, address its feedback, and repeat until it reports a clean round. File an issue for any out-of-scope findings rather than expanding the PR. +6. Mark the PR ready for review and enable auto-merge. It lands automatically once the required checks pass and a reviewer approves. + +For branching details, see [Branching and Merging](https://msxorg.github.io/docs/Ways-of-Working/Branching-and-Merging/). + +## Pull requests + +- Keep PRs small and focused on a single deliverable. +- The PR title should follow the [commit conventions](https://msxorg.github.io/docs/Ways-of-Working/Commit-Conventions/). + +For PR format guidance, see [PR Format](https://msxorg.github.io/docs/Ways-of-Working/PR-Format/). + +## Issues + +Use GitHub Issues to report bugs, request features, or propose improvements. +Follow the [issue format](https://msxorg.github.io/docs/Ways-of-Working/Issue-Format/) guidance. + +## Code standards + +PowerShell in this module follows the [PSModule Standards](https://psmodule.github.io/docs/Modules/Standards/) and the +[MSXOrg Coding Standards](https://msxorg.github.io/docs/Coding-Standards/). + +## Questions + +Open a GitHub Discussion or file an issue if something is unclear. diff --git a/docs/content/Assets/icon.png b/docs/content/Assets/icon.png new file mode 100644 index 0000000..be83fd5 Binary files /dev/null and b/docs/content/Assets/icon.png differ diff --git a/docs/content/index.md b/docs/content/index.md new file mode 100644 index 0000000..cc6ee34 --- /dev/null +++ b/docs/content/index.md @@ -0,0 +1,37 @@ +# PowerShellGallery + +PowerShellGallery is a PowerShell module for interacting with the [PowerShell Gallery](https://www.powershellgallery.com). + +Version 1 focuses on two publisher-oriented workflows: + +- **Discovery** — read package and API metadata from the public OData v2 endpoint. +- **Listing management** — list and unlist package versions you own, using gallery API credentials. + +## Installation + +Install the module from the PowerShell Gallery when a published release is available: + +```powershell +Install-PSResource -Name PowerShellGallery -TrustRepository +``` + +## Quick start + +```powershell +# Read gallery API metadata +Get-PowerShellGalleryAPI + +# Search for packages +Find-PowerShellGalleryPackage -Name 'Pester' -First 5 + +# Get a specific package +Get-PowerShellGalleryPackage -Name 'Pester' -Version '5.5.0' +``` + +## Documentation + +- [v1 Specification](specs/v1.md) — functional specification for the initial release. + +## Contributing + +See `CONTRIBUTING.md` and `AGENTS.md` in the repository root. diff --git a/docs/content/specs/v1.md b/docs/content/specs/v1.md new file mode 100644 index 0000000..b33467f --- /dev/null +++ b/docs/content/specs/v1.md @@ -0,0 +1,270 @@ +# PowerShellGallery v1 Specification + +## Overview + +`PowerShellGallery` is a PowerShell module for interacting with the [PowerShell Gallery](https://www.powershellgallery.com). Version 1 focuses on two publisher-oriented workflows: + +1. **Discovery** — read package and API metadata from the public OData v2 endpoint. +2. **Listing management** — list and unlist package versions you own, using gallery API credentials. + +This spec defines the public API, expected behavior, error handling, and testing strategy for the v1 release. + +## Supported PowerShell version + +Latest PowerShell Long-Term Support (LTS) release, per the PSModule module standard. Windows PowerShell 5.1 is not a v1 target. + +## Out of scope for v1 + +- Installing, updating, or saving packages (covered by `PowerShellGet`). +- Publishing packages to the gallery. +- User or organization management. +- Admin-level gallery operations. +- Cross-gallery support (e.g., private NuGet feeds). + +## Naming convention + +Public nouns are prefixed with the module term of art: `PowerShellGallery`. + +Existing stub names `Hide-PowerShellGalleryItem` and `Show-PowerShellGalleryItem` are renamed to use the more precise noun `Package`, with backward-compatible aliases retained for the v1 major release. + +## Public API + +### `Get-PowerShellGalleryAPI` + +Get metadata from the PowerShell Gallery v2 API root. + +#### Synopsis + +```text +Get-PowerShellGalleryAPI [[-Uri] ] [] +``` + +#### Parameters + +| Name | Type | Mandatory | Default | Description | +| ---- | ---- | --------- | ------- | ----------- | +| `Uri` | `string` | No | `https://www.powershellgallery.com/api/v2/` | Base URI of the gallery OData endpoint. | + +#### Behavior + +- Performs an HTTP `GET` against the provided URI with `application/json` content negotiation. +- Returns the raw service response (commonly an OData service document / JSON). +- Throws a terminating error if the endpoint is unreachable or returns a non-success status code. + +#### Example + +```powershell +Get-PowerShellGalleryAPI +``` + +--- + +### `Find-PowerShellGalleryPackage` + +Search for packages on the PowerShell Gallery. + +#### Synopsis + +```text +Find-PowerShellGalleryPackage [[-Name] ] [-Tag ] [-IncludePrerelease] [-First ] + [-Skip ] [-OrderBy ] [] +``` + +#### Parameters + +| Name | Type | Mandatory | Default | Description | +| ---- | ---- | --------- | ------- | ----------- | +| `Name` | `string` | No | `*` | Package ID or search term. Supports wildcards. | +| `Tag` | `string[]` | No | none | Filter to packages that declare all specified tags. | +| `IncludePrerelease` | `switch` | No | false | Include prerelease versions in results. | +| `First` | `uint` | No | `50` | Maximum number of results to return. Capped at `1000`. | +| `Skip` | `uint` | No | `0` | Number of results to skip for pagination. | +| `OrderBy` | `string` | No | `DownloadCount desc` | OData `$orderby` expression. | + +#### Behavior + +- Queries `https://www.powershellgallery.com/api/v2/Packages()`. +- Supports pipeline input for `Name`. +- Returns one object per package version by default. +- Use `Get-PowerShellGalleryPackage` when you need a single package/version. + +#### Example + +```powershell +Find-PowerShellGalleryPackage -Name 'Pester' -First 5 +'Az.*' | Find-PowerShellGalleryPackage -First 10 +``` + +--- + +### `Get-PowerShellGalleryPackage` + +Get detailed metadata for a specific package and optionally a specific version. + +#### Synopsis + +```text +Get-PowerShellGalleryPackage [-Name] [[-Version] ] [-IncludePrerelease] + [] +``` + +#### Parameters + +| Name | Type | Mandatory | Default | Description | +| ---- | ---- | --------- | ------- | ----------- | +| `Name` | `string` | Yes | — | Exact package ID. | +| `Version` | `string` | No | latest stable | Specific version to retrieve. Accepts NuGet version strings including prerelease labels. | +| `IncludePrerelease` | `switch` | No | false | When `Version` is omitted, return the latest prerelease instead of the latest stable. | + +#### Behavior + +- If `Version` is provided, queries `Packages(Id='{Name}',Version='{Version}')`. +- If `Version` is omitted, queries the package feed filtered by ID, ordered by published date, and returns the newest item. +- Throws a terminating error if the package or version does not exist. + +#### Example + +```powershell +Get-PowerShellGalleryPackage -Name 'Pester' +Get-PowerShellGalleryPackage -Name 'Pester' -Version '5.5.0' +``` + +--- + +### `Hide-PowerShellGalleryPackage` + +Unlist a package version on the PowerShell Gallery. + +#### Synopsis + +```text +Hide-PowerShellGalleryPackage [-Name] [-Version] [-APIKey] + [-WhatIf] [-Confirm] [] +``` + +#### Aliases + +- `Unlist-PowerShellGalleryPackage` + +#### Parameters + +| Name | Type | Mandatory | Default | Description | +| ---- | ---- | --------- | ------- | ----------- | +| `Name` | `string` | Yes | — | Exact package ID. | +| `Version` | `string` | Yes | — | Exact version to unlist. | +| `APIKey` | `string` | Yes | — | PowerShell Gallery API key for the package owner. | + +#### Behavior + +- Requires ownership of the package. +- Uses the gallery listing endpoint to mark the version as unlisted. +- Supports `-WhatIf` and `-Confirm` via `SupportsShouldProcess`. +- Writes a non-terminating warning if the operation is not yet implemented or the gallery endpoint is unavailable. + +#### Example + +```powershell +Hide-PowerShellGalleryPackage -Name 'MyModule' -Version '1.0.0' -APIKey $env:PSGalleryAPIKey +``` + +--- + +### `Show-PowerShellGalleryPackage` + +List a package version on the PowerShell Gallery. + +#### Synopsis + +```text +Show-PowerShellGalleryPackage [-Name] [-Version] [-APIKey] + [-WhatIf] [-Confirm] [] +``` + +#### Aliases + +- `List-PowerShellGalleryPackage` + +#### Parameters + +| Name | Type | Mandatory | Default | Description | +| ---- | ---- | --------- | ------- | ----------- | +| `Name` | `string` | Yes | — | Exact package ID. | +| `Version` | `string` | Yes | — | Exact version to list. | +| `APIKey` | `string` | Yes | — | PowerShell Gallery API key for the package owner. | + +#### Behavior + +- Requires ownership of the package. +- Uses the gallery listing endpoint to mark the version as listed. +- Supports `-WhatIf` and `-Confirm` via `SupportsShouldProcess`. + +#### Example + +```powershell +Show-PowerShellGalleryPackage -Name 'MyModule' -Version '1.0.0' -APIKey $env:PSGalleryAPIKey +``` + +## Private helpers + +Private functions are grouped under `src/functions/private/Gallery/`. + +| Function | Responsibility | +| -------- | -------------- | +| `Invoke-PowerShellGalleryAPI` | Shared REST wrapper: handles URI building, headers, content negotiation, timeout, and error translation. | +| `Get-PowerShellGalleryResource` | Generic OData resource GET (to be merged into or replaced by `Invoke-PowerShellGalleryAPI`). | +| `Update-PowerShellGalleryResourceListing` | Shared POST to the gallery listing endpoint used by `Hide`/`Show`. | + +## Return types + +Commands return plain objects deserialized from OData JSON by default. A future release may introduce typed output classes; v1 does not. + +## Error handling + +- Use `ErrorAction` semantics consistently. +- Wrap `Invoke-RestMethod` failures in descriptive error records where the gallery response adds useful context. +- Authentication or authorization failures from listing endpoints produce clear, actionable messages. +- Discovery commands must not throw on empty result sets; they return nothing. + +## Authentication + +- Read commands require no authentication. +- Listing-management commands require a PowerShell Gallery API key with ownership of the target package. +- The API key is passed via the `APIKey` parameter. Do not persist keys in the module; callers supply them per command. + +## Logging and progress + +- Emit `Verbose` output for URIs, pagination, and auth-skipping details. +- Emit `Warning` when a listing command cannot complete because the gallery endpoint is unavailable. +- Do not emit progress bars for simple HTTP calls. + +## Testing + +Follow the [PSModule Test Specification](https://psmodule.github.io/docs/Modules/Test-Specification/): + +- No mocks; use real inputs and the public gallery endpoint. +- Test each public command with `Describe`/`Context`/`It` hierarchy. +- Discovery tests use well-known packages (e.g., `Pester`, `PowerShellGet`) to ensure stable results. +- Listing-management tests validate parameter binding and `-WhatIf` behavior; they do not mutate real packages. +- Code coverage target remains 50% as configured in `.github/PSModule.yml` for v1. + +## Documentation + +- Each public function has full comment-based help with synopsis, description, examples, and parameter help. +- A `.md` overview file is added alongside public functions. +- README is updated to describe v1 capabilities. + +## Migration from current stubs + +| Current file | v1 disposition | +| ------------ | -------------- | +| `Get-PSGalleryAPI.ps1` | Rename to `Get-PowerShellGalleryAPI.ps1`; keep URI default. | +| `Hide-PowerShellGalleryItem.ps1` | Rename to `Hide-PowerShellGalleryPackage.ps1`; add `Unlist-PowerShellGalleryPackage` alias. | +| `Show-PowerShellGalleryItem.ps1` | Rename to `Show-PowerShellGalleryPackage.ps1`; add `List-PowerShellGalleryPackage` alias. | +| `Get-PSGalleryResource.ps1` | Refactor into `Invoke-PowerShellGalleryAPI` or retain as private helper. | +| `Update-PSGalleryResourceListing.ps1` | Retain as private helper; fix auth and parameter handling. | + +## Open questions + +1. Does the gallery expose a stable, API-key-only listing endpoint, or do we need a full web-login + anti-forgery token flow? +2. Should `Find-PowerShellGalleryPackage` collapse multiple versions of the same package ID by default? +3. Should v1 include `Save-PowerShellGalleryPackage` (download `.nupkg`) or leave that to `PowerShellGet`? diff --git a/docs/zensical.toml b/docs/zensical.toml new file mode 100644 index 0000000..933befe --- /dev/null +++ b/docs/zensical.toml @@ -0,0 +1,64 @@ +[project] +site_name = "-{{ REPO_NAME }}-" +docs_dir = "content" +repo_name = "-{{ REPO_OWNER }}-/-{{ REPO_NAME }}-" +repo_url = "https://github.com/-{{ REPO_OWNER }}-/-{{ REPO_NAME }}-" + +[project.theme] +variant = "classic" +language = "en" +logo = "Assets/icon.png" +favicon = "Assets/icon.png" +features = [ + "navigation.instant", + "navigation.instant.progress", + "navigation.top", + "navigation.tracking", + "navigation.expand", + "search.suggest", + "search.highlight", + "content.code.copy" +] + +[[project.theme.palette]] +media = "(prefers-color-scheme)" +toggle.icon = "lucide/sun-moon" +toggle.name = "Switch to dark mode" + +[[project.theme.palette]] +media = "(prefers-color-scheme: dark)" +scheme = "slate" +primary = "black" +accent = "light-blue" +toggle.icon = "lucide/moon" +toggle.name = "Switch to light mode" + +[[project.theme.palette]] +media = "(prefers-color-scheme: light)" +scheme = "default" +primary = "black" +accent = "light-blue" +toggle.icon = "lucide/sun" +toggle.name = "Switch to system preference" + +[project.theme.icon] +repo = "fontawesome/brands/github" + +[project.markdown_extensions.toc] +permalink = true + +[project.markdown_extensions.attr_list] +[project.markdown_extensions.admonition] +[project.markdown_extensions.md_in_html] +[project.markdown_extensions.pymdownx.details] +[project.markdown_extensions.pymdownx.superfences] + +[[project.extra.social]] +icon = "fontawesome/brands/discord" +link = "https://discord.psmodule.io" +name = "-{{ REPO_OWNER }}- on Discord" + +[[project.extra.social]] +icon = "fontawesome/brands/github" +link = "https://github.com/-{{ REPO_OWNER }}-/" +name = "-{{ REPO_OWNER }}- on GitHub" diff --git a/tests/PowerShellGallery.Tests.ps1 b/tests/PowerShellGallery.Tests.ps1 index 0d9b261..eeb8634 100644 --- a/tests/PowerShellGallery.Tests.ps1 +++ b/tests/PowerShellGallery.Tests.ps1 @@ -1,16 +1,29 @@ -Describe 'PowerShellGallery' { +#Requires -Modules @{ ModuleName = 'Pester'; ModuleVersion = '6.0.0'; MaximumVersion = '6.*' } + +[Diagnostics.CodeAnalysis.SuppressMessageAttribute( + 'PSReviewUnusedParameter', '', + Justification = 'Required for Pester tests' +)] +[Diagnostics.CodeAnalysis.SuppressMessageAttribute( + 'PSUseDeclaredVarsMoreThanAssignments', '', + Justification = 'Required for Pester tests' +)] +[CmdletBinding()] +param() + +Describe 'PowerShellGallery' { Context 'Function: Get-PSGalleryAPI' { - It 'Should not throw' { + It 'Get-PSGalleryAPI - does not throw' { { Get-PSGalleryAPI } | Should -Not -Throw } } Context 'Function: Hide-PowerShellGalleryItem' { - It 'Should not throw' { + It 'Hide-PowerShellGalleryItem - does not throw' { { Hide-PowerShellGalleryItem } | Should -Not -Throw } } Context 'Function: Show-PowerShellGalleryItem' { - It 'Should not throw' { + It 'Show-PowerShellGalleryItem - does not throw' { { Show-PowerShellGalleryItem } | Should -Not -Throw } }