diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 00000000..96c2e0d3 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,15 @@ +# Needed for publishing of examples, build worker defaults to core.autocrlf=input. +* text eol=autocrlf + +*.mof text eol=crlf +*.sh text eol=lf +*.svg eol=lf + +# Ensure any exe files are treated as binary +*.exe binary +*.jpg binary +*.xl* binary +*.pfx binary +*.png binary +*.dll binary +*.so binary diff --git a/.github/linters/.markdown-lint.yml b/.github/linters/.markdown-lint.yml new file mode 100644 index 00000000..b9ecdfa9 --- /dev/null +++ b/.github/linters/.markdown-lint.yml @@ -0,0 +1,25 @@ +########################### +## Markdown Linter rules ## +########################### + +# Linter rules doc: +# - https://github.com/DavidAnson/markdownlint + +############### +# Rules by id # +############### +MD004: false # Unordered list style +MD007: + indent: 2 # Unordered list indentation +MD013: + line_length: 808 # Line length +MD026: + punctuation: ".,;:!。,;:" # List of not allowed +MD029: false # Ordered list item prefix +MD033: false # Allow inline HTML +MD036: false # Emphasis used instead of a heading + +################# +# Rules by tags # +################# +blank_lines: false # Error on blank lines diff --git a/.github/release.yml b/.github/release.yml new file mode 100644 index 00000000..9f0f4ca7 --- /dev/null +++ b/.github/release.yml @@ -0,0 +1,17 @@ +# https://docs.github.com/en/repositories/releasing-projects-on-github/automatically-generated-release-notes#configuring-automatically-generated-release-notes + +changelog: + categories: + - title: Breaking Changes + labels: + - Major + - Breaking + - title: New Features + labels: + - Minor + - Feature + - Improvement + - Enhancement + - title: Other Changes + labels: + - '*' diff --git a/.github/workflows/Auto-Release.yml b/.github/workflows/Auto-Release.yml new file mode 100644 index 00000000..4734f273 --- /dev/null +++ b/.github/workflows/Auto-Release.yml @@ -0,0 +1,35 @@ +name: Auto-Release + +run-name: "Auto-Release - [${{ github.event.pull_request.title }} #${{ github.event.pull_request.number }}] by @${{ github.actor }}" + +on: + pull_request_target: + branches: + - main + types: + - closed + - opened + - reopened + - synchronize + - labeled + +concurrency: + group: ${{ github.workflow }} + +permissions: + contents: write + pull-requests: write + +jobs: + Auto-Release: + runs-on: ubuntu-latest + steps: + - name: Checkout Code + uses: actions/checkout@v4 + + - name: Auto-Release + uses: PSModule/Auto-Release@v1 + env: + GITHUB_TOKEN: ${{ github.token }} # Used for GitHub CLI authentication + with: + IncrementalPrerelease: false diff --git a/.github/workflows/Linter.yml b/.github/workflows/Linter.yml new file mode 100644 index 00000000..89ac29ac --- /dev/null +++ b/.github/workflows/Linter.yml @@ -0,0 +1,18 @@ +name: Linter + +run-name: "Linter - [${{ github.event.pull_request.title }} #${{ github.event.pull_request.number }}] by @${{ github.actor }}" + +on: [pull_request] + +jobs: + Lint: + name: Lint code base + runs-on: ubuntu-latest + steps: + - name: Checkout repo + uses: actions/checkout@v4 + + - name: Lint code base + uses: github/super-linter@latest + env: + GITHUB_TOKEN: ${{ github.token }} diff --git a/.github/workflows/Workflow-Test.yml b/.github/workflows/Workflow-Test.yml new file mode 100644 index 00000000..c3113ac9 --- /dev/null +++ b/.github/workflows/Workflow-Test.yml @@ -0,0 +1,13 @@ +name: Workflow-Test + +run-name: "Workflow-Test - [${{ github.event.pull_request.title }} #${{ github.event.pull_request.number }}] by @${{ github.actor }}" + +on: [pull_request] + +jobs: + WorkflowTestDefault: + uses: ./.github/workflows/workflow.yml + secrets: inherit + with: + Name: PSModule + TestProcess: true diff --git a/.github/workflows/workflow.yml b/.github/workflows/workflow.yml new file mode 100644 index 00000000..393e7bfd --- /dev/null +++ b/.github/workflows/workflow.yml @@ -0,0 +1,69 @@ +name: Process-PSModule + +on: + workflow_call: + secrets: + APIKey: + description: The API key to use when publishing modules + required: true + inputs: + Name: + type: string + description: The name of the module to process. Scripts default to the repository name if nothing is specified. + required: false + SkipTests: + type: boolean + description: Whether to skip tests. + required: false + default: false + TestProcess: + type: boolean + description: Whether to test the process. + required: false + default: false + +env: + GITHUB_TOKEN: ${{ github.token }} # Used for GitHub CLI authentication + +jobs: + Process-PSModule: + name: Process module + runs-on: ubuntu-latest + steps: + - name: Checkout Code + uses: actions/checkout@v4 + + - name: Initialize environment + uses: PSModule/Initialize-PSModule@v1 + + - name: Test source code + uses: PSModule/Test-PSModule@v1 + if: ${{ inputs.SkipTests != true }} + with: + Name: ${{ inputs.Name }} + Path: src + RunModuleTests: false + + - name: Build module + uses: PSModule/Build-PSModule@v1 + with: + Name: ${{ inputs.Name }} + Path: src + ModulesOutputPath: outputs/modules + DocsOutputPath: outputs/docs + + - name: Test built module + uses: PSModule/Test-PSModule@v1 + if: ${{ inputs.SkipTests != true }} + with: + Name: ${{ inputs.Name }} + Path: outputs/modules + + - name: Publish module + uses: PSModule/Publish-PSModule@v1 + with: + Name: ${{ inputs.Name }} + ModulePath: outputs/modules + DocsPath: outputs/docs + APIKey: ${{ secrets.APIKEY }} + WhatIf: ${{ inputs.TestProcess }} diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..af4061f4 --- /dev/null +++ b/.gitignore @@ -0,0 +1,11 @@ +## Ignore Visual Studio Code temporary files, build results, and +## files generated by popular Visual Studio Code add-ons. +## +## Get latest from https://github.com/github/gitignore/blob/master/Global/VisualStudioCode.gitignore +.vscode/* +!.vscode/settings.json +!.vscode/extensions.json +*.code-workspace + +# Local History for Visual Studio Code +.history/ diff --git a/LICENSE b/LICENSE new file mode 100644 index 00000000..92f43842 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2024 PSModule + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md index 81a2d1d1..2e76e334 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,111 @@ -# Actions -Repo for GitHub actions and workflows +# Process-PSModule + +A workflow for the PSModule process, stitching together the `Initialize`, `Build`, `Test`, and `Publish` actions to create a complete +CI/CD pipeline for PowerShell modules. The workflow is used by all PowerShell modules in the PSModule organization. + +## Specifications and practices + +Process-PSModule follows: + +- [Test-Driven Development](https://testdriven.io/test-driven-development/) using [Pester](https://pester.dev) and [PSScriptAnalyzer](https://learn.microsoft.com/en-us/powershell/utility-modules/psscriptanalyzer/overview?view=ps-modules) +- [GitHub Flow specifications](https://docs.github.com/en/get-started/using-github/github-flow) +- [SemVer 2.0.0 specifications](https://semver.org) +- [Continiuous Delivery practices](https://en.wikipedia.org/wiki/Continuous_delivery) + +## How it works + +The workflow is designed to be trigger on pull requests to the repository's default branch. +When a pull request is opened, closed, reopened, synchronized (push), or labeled, the workflow will run. +Depending on the labels in the pull requests, the workflow will result in different outcomes. + +- [Initialize-PSModule](https://github.com/PSModule/Initialize-PSModule/) - To prepare the runner for all requirements of the framework. +- [Test-PSModule](https://github.com/PSModule/Test-PSModule/) - Testing the source code using only PSScriptAnalyzer and the PSModule test suites. +- [Build-PSModule](https://github.com/PSModule/Build-PSModule/) - To compile the repository into an efficient PowerShell module. +- [Test-PSModule](https://github.com/PSModule/Test-PSModule/) - Testing the compiled module using PSScriptAnalyzer, PSModule test suites and custom module tests. +- [Publish-PSModule](https://github.com/PSModule/Publish-PSModule/) - Publish the module to the PowerShell Gallery, publish docs to GitHub Pages, and create a release on the GitHub repository. + +To use the workflow, create a new file in the `.github/workflows` directory of the module repository and add the following content. +
+Workflow suggestion + +```yaml +name: Process-PSModule + +on: + pull_request: + branches: + - main + types: + - closed + - opened + - reopened + - synchronize + - labeled + +concurrency: + group: ${{ github.workflow }} + +permissions: + contents: write + pull-requests: write + +jobs: + Process-PSModule: + uses: PSModule/Process-PSModule/.github/workflows/workflow.yml@v1 + secrets: inherit + +``` +
+ +## Usage + +### Inputs + +| Name | Type | Description | Required | Default | +| ---- | ---- | ----------- | -------- | ------- | +| `Name` | `string` | The name of the module to process. This defaults to the repository name if nothing is specified. | `false` | N/A | +| `SkipTests` | `boolean` | Whether to skip the tests. | false | `false` | +| `TestProcess` | `boolean` | Whether to test the process. | false | `false` | + +### Secrets + +The following secrets are **required** for the workflow to run: + +| Name | Location | Description | Default | +| ---- | -------- | ----------- | ------- | +| `GITHUB_TOKEN` | `github` context | The token used to authenticate with GitHub. | `${{ secrets.GITHUB_TOKEN }}` | +| `APIKey` | GitHub secrets | The API key for the PowerShell Gallery. | N/A | + +## In detail + +The following steps will be run when the workflow triggers: + +- Checkout Code [actions/checkout](https://github.com/actions/checkout/) + - Checks out the code of the repository to the runner. +- Initialize environment [PSModule/Initialize-PSModule](https://github.com/PSModule/Initialize-PSModule/) +- Test source code [PSModule/Test-PSModule](https://github.com/PSModule/Test-PSModule/) + - Looks for the module in the `src` directory and runs the PSScriptAnalyzer and PSModule testing suite on the code. +- Build module [PSModule/Build-PSModule](https://github.com/PSModule/Build-PSModule/) + - Build the manifest file for the module. + - Compiles the `src` directory into a PowerShell module and docs. + - The compiled module is output to the `outputs/modules` directory. + - The compiled docs are output to the `outputs/docs` directory. +- Test built module [PSModule/Test-PSModule](https://github.com/PSModule/Test-PSModule/) + - Looks for the module in the `outputs/modules` directory and runs the PSScriptAnalyzer, PSModule testing suite and the custom module tests from the `tests` directory on the code. +- Publish module [PSModule/Publish-PSModule](https://github.com/PSModule/Publish-PSModule/) + - Calculates the next version of the module based on the latest release and labels on the PR. + - Publishes the module to the PowerShell Gallery using the API key stored in as a secret named `APIKey`. + - Publishes the docs to GitHub Pages from the `outputs/docs` directory. + - Creates a release on the GitHub repository with the source code. + +## Permissions + +The action requires the following permissions: + +If running the action in a restrictive mode, the following permissions needs to be granted to the action: + +```yaml +permissions: + contents: write # Required to create releases + pull-requests: write # Required to create comments on the PRs +``` diff --git a/src/PSModule/PSModule.psd1 b/src/PSModule/PSModule.psd1 new file mode 100644 index 00000000..f2ac5e28 --- /dev/null +++ b/src/PSModule/PSModule.psd1 @@ -0,0 +1,5 @@ +@{ + ModuleVersion = '0.0.1' + RootModule = 'PSModule.psm1' + Description = 'PSModule Framework Test Module' +} diff --git a/src/PSModule/PSModule.psm1 b/src/PSModule/PSModule.psm1 new file mode 100644 index 00000000..302c035f --- /dev/null +++ b/src/PSModule/PSModule.psm1 @@ -0,0 +1,32 @@ +[Cmdletbinding()] +param() + +Write-Verbose 'Importing subcomponents' +$Folders = 'classes', 'private', 'public' +# Import everything in these folders +Foreach ($Folder in $Folders) { + $Root = Join-Path -Path $PSScriptRoot -ChildPath $Folder + Write-Verbose "Processing folder: $Root" + if (Test-Path -Path $Root) { + Write-Verbose "Getting all files in $Root" + $Files = $null + $Files = Get-ChildItem -Path $Root -Include '*.ps1', '*.psm1' -Recurse + # dot source each file + foreach ($File in $Files) { + Write-Verbose "Importing $($File)" + Import-Module $File + Write-Verbose "Importing $($File): Done" + } + } +} + +$Param = @{ + Function = (Get-ChildItem -Path "$PSScriptRoot\public" -Include '*.ps1' -Recurse).BaseName + Variable = '*' + Cmdlet = '*' + Alias = '*' +} + +Write-Verbose 'Exporting module members' + +Export-ModuleMember @Param diff --git a/src/PSModule/public/Test-PSModule.ps1 b/src/PSModule/public/Test-PSModule.ps1 new file mode 100644 index 00000000..131dcffb --- /dev/null +++ b/src/PSModule/public/Test-PSModule.ps1 @@ -0,0 +1,18 @@ +Function Test-PSModule { + <# + .SYNOPSIS + Performs tests on a module. + + .EXAMPLE + Test-PSModule -Name 'World' + + "Hello, World!" + #> + [CmdletBinding()] + param ( + # Name of the person to greet. + [Parameter(Mandatory)] + [string] $Name + ) + Write-Output "Hello, $Name!" +} diff --git a/tests/PSModule.Tests.ps1 b/tests/PSModule.Tests.ps1 new file mode 100644 index 00000000..2e152118 --- /dev/null +++ b/tests/PSModule.Tests.ps1 @@ -0,0 +1,11 @@ +Describe 'PSModule.Tests.ps1' { + It "Should be able to import the module" { + Import-Module -Name 'PSModule' + Get-Module -Name 'PSModule' | Should -Not -BeNullOrEmpty + Write-Verbose (Get-Module -Name 'PSModule' | Out-String) -Verbose + } + It "Should be able to call the function" { + Test-PSModule -Name 'World' | Should -Be "Hello, World!" + Write-Verbose (Test-PSModule -Name 'World' | Out-String) -Verbose + } +}