OCram85/DroneHelper
OCram85
/
DroneHelper
Archived
1
0
Fork 0

initial migration
continuous-integration/drone/tag Build was killed Details

This commit is contained in:
OCram85 2022-07-13 13:59:25 +02:00
parent 97761f112c
commit 8fd180b776
Signed by: OCram85
GPG Key ID: 456940F03DE016E1
80 changed files with 3997 additions and 1 deletions

122
.drone.yml Normal file
View File

@ -0,0 +1,122 @@
---
kind: pipeline
type: docker
name: "Build Pipeline"
trigger:
branch:
exclude:
- droneDocs/*
steps:
- name: "Pwsh FileLinter"
image: mcr.microsoft.com/dotnet/sdk:6.0-focal
failure: ignore
environment:
EXCLUDE: "(.exe|.dll|.ico|.gitkeep)"
commands:
- |
pwsh -NonInteractive -c "& {
Import-Module './src/DroneHelper.psd1';
Invoke-FileLinter
}"
- name: "ScriptAnalyzer"
image: mcr.microsoft.com/dotnet/sdk:6.0-focal
failure: ignore
commands:
- |
pwsh -NonInteractive -c "& {
Import-Module './src/DroneHelper.psd1';
Invoke-Linter
}"
- name: "Pester"
image: mcr.microsoft.com/dotnet/sdk:6.0-focal
failure: ignore
commands:
- |
pwsh -NonInteractive -c "& {
Import-Module './src/DroneHelper.psd1';
Invoke-UnitTest -ExcludeTag 'Integration'
}"
- name: "PRComment"
image: mcr.microsoft.com/dotnet/sdk:6.0-focal
failure: ignore
environment:
GITEA_TOKEN:
from_secret: GITEA_TOKEN
CUSTOM_PIPELINE_STATE: true
LOG_FILES: "build/*.log"
commands:
- |
pwsh -NonInteractive -c "& {
Import-Module './src/DroneHelper.psd1';
Send-PRComment
}"
when:
event:
include:
- pull_request
- name: "buildState"
image: mcr.microsoft.com/dotnet/sdk:6.0-focal
commands:
- |
pwsh -NonInteractive -c "& {
Import-Module './src/DroneHelper.psd1';
Invoke-BuildState
}"
---
kind: pipeline
type: docker
name: "Publish Pipeline"
depends_on:
- "Build Pipeline"
trigger:
event:
- tag
steps:
- name: BuildArtifacts
image: mcr.microsoft.com/dotnet/sdk:6.0-focal
#failure: ignore
commands:
- |
pwsh -NonInteractive -c "& {
Import-Module './src/DroneHelper.psd1';
Update-ModuleMeta -Verbose
}"
- |
pwsh -NonInteractive -c "& {
Import-Module './src/DroneHelper.psd1';
New-BuildPackage -AdditionalPath './src/Rules' -Verbose
}"
- name: GiteaRelease
image: plugins/gitea-release
settings:
api_key:
from_secret: GITEA_TOKEN
base_url: https://gitea.ocram85.com
files:
- "bin/${DRONE_REPO_NAME}.zip"
title: ${DRONE_TAG}
note: CHANGELOG.md
- name: "PublishModule"
image: mcr.microsoft.com/dotnet/sdk:6.0-focal
#failure: ignore
environment:
NexusToken:
from_secret: NexusToken
commands:
- |
pwsh -NonInteractive -c "& {
Import-Module './src/DroneHelper.psd1';
Invoke-Publish -Verbose
}"

27
.editorconfig Normal file
View File

@ -0,0 +1,27 @@
root = true
[*]
indent_style = space
indent_size = 4
end_of_line = crlf
charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true
[*.yml]
indent_style = space
indent_size = 2
end_of_line = lf
charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true
[*.md]
indent_style = space
indent_size = 2
end_of_line = lf
charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true

21
.gitattributes vendored Normal file
View File

@ -0,0 +1,21 @@
# images
*.jpg binary
*.jpeg binary
*.bmp binary
*.tiff binary
*.png binary
*.svg binary
*.ico binary
# binary files
*.exe binary
*.dll binary
# PowerShell specific
*.ps1 working-tree-encoding=UTF-8
*.psm1 working-tree-encoding=UTF-8
*.psd1 working-tree-encoding=UTF-8
# set markdown to lf for online editing
*.md working-tree-encoding=UTF-8

11
.gitea/ISSUE_TEMPLATE.md Normal file
View File

@ -0,0 +1,11 @@
---
name: "Default"
about: "Use this template if nothing seems to work."
title: ":question: "
labels:
- question
---
#### :grey_question: Simply ask your question here: :grey_question:
<!-- Take your time an think about your problem.... -->

View File

@ -0,0 +1,48 @@
---
name: "Bug"
about: "This template is used to report bugs!"
title: ":lady_beetle: "
labels:
- bug
---
<!--
This bug report is ony for content provided in this repository!
- Make sure you're able to reproduce the error in the latest version of this package.
- Search of already existing issues.
- Refer to the known issues and FAQ section.
-->
#### :bomb: Steps to reproduce
```bash
```
#### :rocket: Expected behavior
```bash
```
#### :boom: Actual behavior
```bash
```
#### :notebook: Environment data
<!-- You can add additional environment data here--->
```bash
```
#### :framed_picture: Screenshots
<!-- Paste your screenshots here. -->
#### :bookmark: Refs
<!-- A place for additional references to other issues and PRs -->

View File

@ -0,0 +1,24 @@
---
name: "Enhancement"
about: "Wite about new features."
title: ":flying_saucer: "
labels:
- enhancement
---
<!--
This issue template is used to describe whished features.
-->
#### :satellite: Suggestion
#### :artificial_satellite: Implementation ideas
#### :framed_picture: Mock-up Images
#### :bookmark: Refs.

View File

@ -0,0 +1,19 @@
#### :book: Summary
<!-- Provide a summary of your changes. Describe the why and not how. -->
#### :bookmark_tabs: Test Plan
> :bulb: Select your test plan for the code changes.
- [ ] Tested via Drone.io pipeline
- [ ] Custom test
- [ ] No test plan
##### Details / Justification
<!-- Add your test details or justification for missing tests here. -->
#### :books: Additional Notes
<!-- A place for additional detail notes. -->

14
.gitignore vendored Normal file
View File

@ -0,0 +1,14 @@
# Basic ignore patterns
*.log
*.secret
# data dir related prod files
data/*.csv
# Ignore templ build artifacts
/bin/*
!bin/.gitkeep
# Ignore Unit Test result files
coverage.xml
testResults.xml

19
.gitlocal Normal file
View File

@ -0,0 +1,19 @@
[alias]
# simplified logs
log1 = log --graph --abbrev-commit --decorate --date=relative --format=format:'%C(bold blue)%h%C(reset) - %C(bold green)(%ar)%C(reset) %C(white)%s%C(reset) %C(dim white)- %an%C(reset)%C(bold yellow)%d%C(reset)' --all
log2 = log --graph --abbrev-commit --decorate --format=format:'%C(bold blue)%h%C(reset) - %C(bold cyan)%aD%C(reset) %C(bold green)(%ar)%C(reset)%C(bold yellow)%d%C(reset)%n'' %C(white)%s%C(reset) %C(dim white)- %an%C(reset)' --all
# Phabricator inspired workflow
# 1. Create new feature branch
feature = "!f(){ b=$1; git checkout master; git pull; git checkout -b "$b" master; };f"
# 2. Working with code
wip = !"git add -A; git commit -m '[WIP]'"
squish = !"git add -A; git commit --no-edit --amend"
# 3. Push to origin
pod = !"git push origin dev"
# 4. Push to custom remote branch
poc = "!f(){ b=$1; git push origin "$b";};f"

22
.vscode/cSpell.json vendored Normal file
View File

@ -0,0 +1,22 @@
// cSpell Settings
{
// Version of the setting file. Always 0.1
"version": "0.1",
// language - current active spelling language
"language": "en,de,de-DE",
// words - list of words to be always considered correct
"words": [],
// flagWords - list of words to be always considered incorrect
// This is useful for offensive words and common spelling errors.
// For example "hte" should be "the"
"flagWords": [],
"dictionaryDefinitions": [
{
"name": "default",
"path": "./dictionaries/default.txt"
}
],
"dictionaries": [
"default"
]
}

0
.vscode/dictionaries/default.txt vendored Normal file
View File

10
.vscode/extensions.json vendored Normal file
View File

@ -0,0 +1,10 @@
{
"recommendations": [
"streetsidesoftware.code-spell-checker",
"streetsidesoftware.code-spell-checker-german",
"editorconfig.editorconfig",
"eamodio.gitlens",
"vscode-icons-team.vscode-icons",
"ryanluker.vscode-coverage-gutters"
]
}

48
.vscode/launch.json vendored Normal file
View File

@ -0,0 +1,48 @@
{
// Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"type": "PowerShell",
"request": "launch",
"name": "PowerShell Launch Current File",
"script": "${file}",
"args": [],
"cwd": "${file}"
},
{
"type": "PowerShell",
"request": "launch",
"name": "PowerShell Launch Current File in Temporary Console",
"script": "${file}",
"args": [],
"cwd": "${file}",
"createTemporaryIntegratedConsole": true
},
{
"type": "PowerShell",
"request": "launch",
"name": "PowerShell Launch Current File w/Args Prompt",
"script": "${file}",
"args": [
"${command:SpecifyScriptArgs}"
],
"cwd": "${file}"
},
{
"type": "PowerShell",
"request": "attach",
"name": "PowerShell Attach to Host Process",
"processId": "${command:PickPSHostProcess}",
"runspaceId": 1
},
{
"type": "PowerShell",
"request": "launch",
"name": "PowerShell Interactive Session",
"cwd": "${workspaceRoot}"
}
]
}

31
.vscode/pwsh.code-snippets vendored Normal file
View File

@ -0,0 +1,31 @@
{
// Place your PowerShell-Module workspace snippets here. Each snippet is defined under a snippet name and has a scope, prefix, body and
// description. Add comma separated ids of the languages where the snippet is applicable in the scope field. If scope
// is left empty or omitted, the snippet gets applied to all languages. The prefix is what is
// used to trigger the snippet and the body will be expanded and inserted. Possible variables are:
// $1, $2 for tab stops, $0 for the final cursor position, and ${1:label}, ${2:another} for placeholders.
// Placeholders with the same ids are connected.
// Example:
// "Print to console": {
// "scope": "javascript,typescript",
// "prefix": "log",
// "body": [
// "console.log('$1');",
// "$2"
// ],
// "description": "Log output to console"
// }
// PSScriptAnalyzder Rule Suppression
"Rule Suppression": {
"scope": "powershell",
"prefix": "[Diag",
"description": "Suppresses Scriptanalyzer Rules",
"body": [
"[Diagnostics.CodeAnalysis.SuppressMessageAttribute(",
" '${1|PSProvideCommentHelp,PSAvoidLongLines,PSAvoidUsingWriteHost,PSUseShouldProcessForStateChangingFunctions,PSUseConsistentWhitespace|}',",
" '',",
" Justification = '${justification}'",
")]"
]
}
}

62
.vscode/settings.json vendored Normal file
View File

@ -0,0 +1,62 @@
{
"files.encoding": "utf8",
"files.eol": "auto",
"files.trimTrailingWhitespace": true,
"files.insertFinalNewline": true,
"editor.renderWhitespace": "boundary",
"editor.formatOnSave": true,
"editor.formatOnType": true,
"editor.rulers": [
116
],
"cSpell.enabled": true,
"cSpell.enabledLanguageIds": [
"c",
"cpp",
"csharp",
"go",
"javascript",
"javascriptreact",
"json",
"latex",
"markdown",
"php",
"plaintext",
"powershell",
"python",
"text",
"typescript",
"typescriptreact",
"yaml",
"yml"
],
"cSpell.language": "en,de,de-DE",
// powershell general
"powershell.startAutomatically": true,
"powershell.useX86Host": false,
"powershell.enableProfileLoading": true,
"powershell.scriptAnalysis.enable": true,
// powershell code Formatting
"powershell.codeFormatting.openBraceOnSameLine": true,
"powershell.codeFormatting.newLineAfterOpenBrace": true,
"powershell.codeFormatting.newLineAfterCloseBrace": true,
"powershell.codeFormatting.whitespaceBeforeOpenBrace": true,
"powershell.codeFormatting.whitespaceBeforeOpenParen": true,
"powershell.codeFormatting.whitespaceAroundOperator": true,
"powershell.codeFormatting.whitespaceAfterSeparator": true,
"powershell.codeFormatting.ignoreOneLineBlock": true,
"powershell.codeFormatting.alignPropertyValuePairs": true,
"powershell.scriptAnalysis.settingsPath": "resources/PSScriptAnalyzerSettings.psd1",
"[yaml]": {
"editor.insertSpaces": true,
"editor.tabSize": 2,
"editor.quickSuggestions": {
"other": true,
"comments": false,
"strings": true
},
"editor.autoIndent": "full"
},
"coverage-gutters.showGutterCoverage": false,
"coverage-gutters.showLineCoverage": true
}

196
.vscode/tasks.json vendored Normal file
View File

@ -0,0 +1,196 @@
// A task runner that invokes Pester to run all Pester tests under the
// current workspace folder.
// NOTE: This Test task runner requires an updated version of Pester (>=4.0.3)
// in order for the problemMatcher to find failed test information (message, line, file).
// If you don't have that version, you can update Pester from the PowerShell Gallery
// with this command:
//
// PS C:\> Update-Module Pester
//
// If that gives an error like:
// "Module 'Pester' was not installed by using Install-Module, so it cannot be updated."
// then execute:
//
// PS C:\> Install-Module Pester -Scope CurrentUser -Force
//
// NOTE: The Clean, Build and Publish tasks require PSake. PSake can be installed
// from the PowerShell Gallery with this command:
//
// PS C:\> Install-Module PSake -Scope CurrentUser -Force
//
// Available variables which can be used inside of strings:
// ${workspaceFolder} the path of the workspace folder that contains the tasks.json file
// ${workspaceFolderBasename} the name of the workspace folder that contains the tasks.json file without any slashes (/)
// ${file} the current opened file
// ${relativeFile} the current opened file relative to the workspace folder containing the file
// ${fileBasename} the current opened file's basename
// ${fileBasenameNoExtension} the current opened file's basename without the extension
// ${fileDirname} the current opened file's dirname
// ${fileExtname} the current opened file's extension
// ${cwd} the task runner's current working directory on startup
// ${lineNumber} the current selected line number in the active file
{
"version": "2.0.0",
//"windows": {
// "options": {
// "shell": {
// // switch back to windows powershell 5.1
// // "executable": "C:\\Windows\\System32\\WindowsPowerShell\\v1.0\\powershell.exe",
// "executable": "pwsh.exe",
// "args": [
// "-NoProfile",
// "-ExecutionPolicy",
// "Bypass",
// "-Command"
// ]
// }
// }
//},
//"linux": {
// "options": {
// "shell": {
// "executable": "/usr/bin/pwsh",
// "args": [
// "-NoProfile",
// "-Command"
// ]
// }
// }
//},
//"osx": {
// "options": {
// "shell": {
// "executable": "/usr/local/bin/pwsh",
// "args": [
// "-NoProfile",
// "-Command"
// ]
// }
// }
//},
"tasks": [
{
"label": "DroneHelper: Invoke-FileLinter",
"type": "shell",
"command": [
"Remove-Item ./build/FileLinter-Results.log -ErrorAction 'SilentlyContinue';",
"Remove-Item ./failure.log -ErrorAction 'SilentlyContinue';",
"Import-Module ./src/DroneHelper.psd1;",
"Invoke-FileLinter"
],
"group": "test",
"problemMatcher": [
"$pester"
]
},
{
"label": "DroneHelper: Invoke-Linter",
"type": "shell",
"command": [
"Remove-Item ./build/ScriptAnalyzer-Results.log -ErrorAction 'SilentlyContinue';",
"Remove-Item ./failure.log -ErrorAction 'SilentlyContinue';",
"Import-Module ./src/DroneHelper.psd1;",
"Invoke-Linter"
],
"group": "test",
"problemMatcher": [
"$pester"
]
},
{
"label": "DroneHelper: Invoke-UnitTest",
"type": "shell",
"command": [
"Remove-Item ./coverage.xml -ErrorAction 'SilentlyContinue';",
"Remove-Item ./testResults.xml -ErrorAction 'SilentlyContinue';",
"Remove-Item ./build/FileLinter-Results.log -ErrorAction 'SilentlyContinue';",
"Remove-Item ./build/Pester-Results.log -ErrorAction 'SilentlyContinue';",
"Remove-Item ./failure.log -ErrorAction 'SilentlyContinue';",
"Import-Module ./src/DroneHelper.psd1;",
"Invoke-UnitTest -CoverageFormat 'CoverageGutters' -Verbosity 'Detailed'"
],
"group": "test",
"problemMatcher": [
"$pester"
]
},
{
"label": "DroneHelper: Full Test",
"type": "shell",
"command": [
"Remove-Item ./build/ScriptAnalyzer-Results.log -ErrorAction 'SilentlyContinue';",
"Remove-Item ./build/FileLinter-Results.log -ErrorAction 'SilentlyContinue';",
"Remove-Item ./build/Pester-Results.log -ErrorAction 'SilentlyContinue';",
"Remove-Item ./coverage.xml -ErrorAction 'SilentlyContinue';",
"Remove-Item ./testResults.xml -ErrorAction 'SilentlyContinue';",
"Remove-Item ./failure.log -ErrorAction 'SilentlyContinue';",
"Import-Module ./src/DroneHelper.psd1;",
"Invoke-FileLinter;",
"Invoke-Linter;",
"Invoke-UnitTest -CoverageFormat 'CoverageGutters' -Verbosity 'Detailed'"
],
"group": {
"kind": "test",
"isDefault": true
},
"problemMatcher": [
"$pester"
]
},
{
"label": "Add dictionary item",
"type": "shell",
"command": [
"$DefaultFile = Get-ChildItem -Path './.vscode/dictionaries/default.txt';",
"$Content = Get-Content -Path $DefaultFile;",
"$Content += '${input:DictionaryItem}';",
"$Content = $Content | Sort-Object -Unique;",
"Set-Content -Value $Content -Path $DefaultFile"
],
"group": "none",
"problemMatcher": [
"$pester"
]
},
{
"label": "DroneHelper: New Docs",
"type": "shell",
"command": [
"Import-Module ./src/DroneHelper.psd1;",
"New-Docs"
],
"problemMatcher": []
},
{
"label": "DroneHelper: Update Docs",
"type": "shell",
"command": [
"Import-Module ./src/DroneHelper.psd1;",
"Update-Docs"
],
"problemMatcher": []
},
{
"label": "DroneHelper: Update Changelog",
"type": "shell",
"command": [
"Import-Module ./src/DroneHelper.psd1;",
"Update-Changelog -NewVersion '${input:NewVersion}'"
],
"problemMatcher": []
}
],
"inputs": [
{
"id": "DictionaryItem",
"type": "promptString",
"description": "Input for dictionary file default.txt"
},
{
"id": "NewVersion",
"type": "promptString",
"description": "Enter a new semver version id",
"default": "v1.0.0"
}
]
}

193
README.md
View File

@ -1,3 +1,194 @@
# DroneHelper
Helper module for Drone.io CI.
[![Build Status](https://drone.ocram85.com/api/badges/OCram85/DroneHelper/status.svg)](https://drone.ocram85.com/OCram85/DroneHelper)
## Description
Helper module for Drone.io based build pipelines.
## About
The DroneHelper PowerShell Modules adds several feature to a `Gitea -> Drone.IO` based build pipeline. It's designed
to perform all needed tasks for PowerShell Module development like:
- `FileLinter` -> Runs basic FileLinter tests with console and log file output
- `Linter` -> Runs PSScriptAnalyer with embedded to custom profiles.
- `UnitTest` -> Executes Pester tests with code coverage with console and log file output.
- `BuildReport` -> Takes all generated reports and reports them back as Pull Request Comment for a simplified overview.
- `StateReporter` -> Marks the current pipeline run / build as failed if the previous steps also raised errors.
- `DoksUpdater` -> Automatically updates the markdown based docs generated form your Comment Based Help blocks in your functions
To use these feature, all you have to do, is follow the `.drone.yml` template:
### `.drone.yml` Template
```yaml
---
kind: pipeline
type: docker
name: "Build Pipeline"
trigger:
branch:
exclude:
- droneDocs/*
steps:
- name: "Pwsh FileLinter"
image: mcr.microsoft.com/dotnet/sdk:6.0-focal
failure: ignore
environment:
EXCLUDE: "(.exe|.dll|.ico|.gitkeep)"
commands:
- |
pwsh -NonInteractive -c "& {
Import-Module 'DroneHelper';
Invoke-FileLinter
}"
- name: "ScriptAnalyzer"
image: mcr.microsoft.com/dotnet/sdk:6.0-focal
failure: ignore
commands:
- |
pwsh -NonInteractive -c "& {
Import-Module -Name 'DroneHelper';
Install-ModuleDependency;
Invoke-Linter
}"
- name: "Pester"
image: mcr.microsoft.com/dotnet/sdk:6.0-focal
failure: ignore
commands:
- |
pwsh -NonInteractive -c "& {
Import-Module -Name 'DroneHelper';
Install-ModuleDependency;
Invoke-UnitTest -Verbosity 'Detailed' -ExcludeTag 'Integration'
}"
- name: "PRComment"
image: mcr.microsoft.com/dotnet/sdk:6.0-focal
failure: ignore
environment:
GITEA_TOKEN:
from_secret: GITEA_TOKEN
CUSTOM_PIPELINE_STATE: true
LOG_FILES: "build/*.log"
commands:
- |
pwsh -NonInteractive -c "& {
Import-Module 'DroneHelper';
Send-PRComment
}"
when:
event:
include:
- pull_request
- name: "buildState"
image: mcr.microsoft.com/dotnet/sdk:6.0-focal
commands:
- |
pwsh -NonInteractive -c "& {
Import-Module -Name 'DroneHelper';
Install-ModuleDependency;
Invoke-BuildState
}"
---
kind: pipeline
type: docker
name: "Publish Pipeline"
depends_on:
- "Build Pipeline"
trigger:
event:
- tag
steps:
- name: BuildArtifacts
image: mcr.microsoft.com/dotnet/sdk:6.0-focal
#failure: ignore
commands:
- |
pwsh -NonInteractive -c "& {
Import-Module -Name 'DroneHelper';
Install-ModuleDependency;
Update-ModuleMeta -Verbose
}"
- |
pwsh -NonInteractive -c "& {
Import-Module -Name 'DroneHelper';
Install-ModuleDependency;
New-BuildPackage -Verbose
}"
- name: GiteaRelease
image: plugins/gitea-release
settings:
api_key:
from_secret: GITEA_TOKEN
base_url: https://gitea.ocram85.com
files:
- "bin/${DRONE_REPO_NAME}.zip"
- "bin/PSModule.zip"
title: "${DRONE_TAG}"
note: CHANGELOG.md
- name: "PublishModule"
image: mcr.microsoft.com/dotnet/sdk:6.0-focal
#failure: ignore
environment:
NexusToken:
from_secret: NexusToken
commands:
- |
pwsh -NonInteractive -c "& {
Import-Module -Name 'DroneHelper';
Install-ModuleDependency;
Invoke-Publish -Verbose
}"
---
kind: pipeline
type: docker
name: "Update Docs"
depends_on:
- "Build Pipeline"
trigger:
event:
exclude:
- pull_request
branch:
include:
- master
steps:
- name: "Update Docs"
image: mcr.microsoft.com/dotnet/sdk:6.0-focal
commands:
- |
pwsh -NonInteractive -c "& {
Import-Module -Name 'DroneHelper';
Install-ModuleDependency;
Update-Docs -Verbose
}"
- name: "push commit"
image: appleboy/drone-git-push
settings:
remote_name: origin
branch: "droneDocs/${DRONE_COMMIT:0:8}"
local_ref: droneDocs
commit: true
commit_message: "docs updated by drone [CI SKIP]"
author_name: drone
author_email: noreply@ocram85.com
force: true
```

1
assets/.gitkeep Normal file
View File

@ -0,0 +1 @@
git placeholder file

1
bin/.gitkeep Normal file
View File

@ -0,0 +1 @@
git placeholder file

1
build/.gitkeep Normal file
View File

@ -0,0 +1 @@
git placeholder file

0
resources/.gitkeep Normal file
View File

View File

@ -0,0 +1,141 @@
@{
Severity = 'Error', 'Warning', 'Information'
IncludeRules = @(
'PSAlignAssignmentStatement',
'PSAvoidAssignmentToAutomaticVariable',
'PSAvoidDefaultValueForMandatoryParameter',
'PSAvoidDefaultValueSwitchParameter',
'PSAvoidGlobalAliases',
'PSAvoidGlobalFunctions',
'PSAvoidGlobalVars',
'PSAvoidInvokingEmptyMembers',
'PSAvoidLongLines',
'PSAvoidNullOrEmptyHelpMessageAttribute',
'PSAvoidOverwritingBuiltInCmdlets',
'PSAvoidShouldContinueWithoutForce',
'PSAvoidTrailingWhitespace',
'PSAvoidUsingCmdletAliases',
'PSAvoidUsingComputerNameHardcoded',
'PSAvoidUsingConvertToSecureStringWithPlainText',
'PSAvoidUsingDeprecatedManifestFields',
'PSAvoidUsingDoubleQuotesForConstantString',
'PSAvoidUsingEmptyCatchBlock',
'PSAvoidUsingInvokeExpression',
'PSAvoidUsingPlainTextForPassword',
'PSAvoidUsingPositionalParameters',
'PSAvoidUsingUsernameAndPasswordParams',
'PSAvoidUsingWMICmdlet',
'PSAvoidUsingWriteHost',
'PSMisleadingBacktick',
'PSMissingModuleManifestField',
'PSPlaceCloseBrace',
'PSPlaceOpenBrace',
'PSPossibleIncorrectComparisonWithNull',
'PSPossibleIncorrectUsageOfAssignmentOperator',
'PSPossibleIncorrectUsageOfRedirectionOperator',
'PSProvideCommentHelp',
'PSReservedCmdletChar',
'PSReservedParams',
'PSReviewUnusedParameter',
'PSShouldProcess',
'PSUseApprovedVerbs',
'PSUseBOMForUnicodeEncodedFile',
'PSUseCmdletCorrectly',
# There is no predefined set for Pwsh7 Cmdlets
'PSUseCompatibleCmdlets',
#'PSUseCompatibleCommands',
'PSUseCompatibleSyntax',
#'PSUseCompatibleTypes',
'PSUseConsistentIndentation',
# Disable if bug in 1.19.1 version occurs.
'PSUseConsistentWhitespace',
'PSUseCorrectCasing',
'PSUseDeclaredVarsMoreThanAssignments',
'PSUseLiteralInitializerForHashtable',
'PSUseOutputTypeCorrectly',
'PSUsePSCredentialType',
'PSUseProcessBlockForPipelineCommand',
'PSUseShouldProcessForStateChangingFunctions',
'PSUseSingularNouns',
'PSUseSupportsShouldProcess',
'PSUseToExportFieldsInManifest',
'PSUseUTF8EncodingForHelpFile',
'PSUseUsingScopeModifierInNewRunspaces'
)
Rules = @{
PSAvoidLongLines = @{
Enable = $true
MaximumLineLength = 116
}
PSPlaceOpenBrace = @{
Enable = $true
OnSameLine = $true
NewLineAfter = $true
IgnoreOneLineBlock = $true
}
PSPlaceCloseBrace = @{
Enable = $true
NewLineAfter = $true
IgnoreOneLineBlock = $true
NoEmptyLineBefore = $false
}
PSProvideCommentHelp = @{
Enable = $true
ExportedOnly = $false
BlockComment = $true
VSCodeSnippetCorrection = $false
Placement = "begin"
}
PSUseCompatibleCmdlets = @{
compatibility = @(
"desktop-5.1.14393.206-windows",
"core-6.1.0-windows"
)
}
PSUseCompatibleSyntax = @{
Enable = $true
TargetVersions = @(
"7.0",
"5.1"
)
}
PSUseConsistentIndentation = @{
Enable = $true
Kind = 'space'
PipelineIndentation = 'IncreaseIndentationForFirstPipeline'
IndentationSize = 4
}
PSUseConsistentWhitespace = @{
Enable = $true
CheckInnerBrace = $true
CheckOpenBrace = $true
CheckOpenParen = $true
CheckOperator = $true
CheckPipe = $true
CheckPipeForRedundantWhitespace = $false
CheckSeparator = $true
CheckParameter = $false
IgnoreAssignmentOperatorInsideHashTable = $true
}
PSAlignAssignmentStatement = @{
Enable = $true
CheckHashtable = $false
}
PSUseCorrectCasing = @{
Enable = $true
}
}
}

View File

@ -0,0 +1,9 @@
This is just a test file. Used for testing the encoding linter
#### Test Data ####
ABCD
abcdefg
12345678
öäü
!"§$%&/()

View File

@ -0,0 +1,21 @@
BeforeAll {
$Repo = Get-RepoPath
Import-Module $Repo.Src.Manifest.Item.FullName -Force
}
Describe 'Merge-ModuleRoot' {
Context 'Default tests' -Tag 'Default' {
It 'Test Function' {
{ Get-Command -Name 'Merge-ModuleRoot' -Module $Repo.Artifact } | Should -Not -Throw
}
It 'Test Help' {
{ Get-Help -Name 'Merge-ModuleRoot' } | Should -Not -Throw
}
It 'Help Content' {
$foo = Get-Help -Name 'Merge-ModuleRoot'
$foo.Synopsis.Length | Should -BeGreaterThan 5
$foo.Description.Count | Should -BeGreaterOrEqual 1
$foo.Description[0].Text.Length | Should -BeGreaterThan 5
}
}
}

View File

@ -0,0 +1,42 @@
function Merge-ModuleRoot {
<#
.SYNOPSIS
Merges single ps1 files into one module script file.
.DESCRIPTION
This Cmdlet is used in build pipeline to reduce the file load and import performance to the target module.
.INPUTS
[None] No pipeline input.
.OUTPUTS
[None] No pipeline output.
.EXAMPLE
Import-Module -Name DroneHelper; Merge-ModuleRoot
#>
[CmdletBinding()]
param ()
process {
$Repo = Get-RepoPath
$srcFiles = Get-ChildItem -Path $Repo.Src.Path -Recurse -File | Where-Object {
($_.Name -notmatch '.Tests.') -and ($_.Name -match '.ps1') -and ($_.Name -notmatch '.ps1xml')
}
$Output = @()
foreach ($psFile in $srcFiles) {
$fileContent = Get-Content -Path $psFile.FullName -Raw -Encoding 'utf8'
$Output += '# srcFile: {0}' -f $psFile.FullName
$Output += $fileContent.TrimEnd()
$Output += '{0}' -f [Environment]::NewLine
}
try {
$Output | Out-File -FilePath $Repo.Bin.ScriptModuleName -Encoding 'utf8' -Force -ErrorAction Stop
}
catch {
Write-FailureStateFile -StepName 'MergeModuleRoot'
throw 'Could not write the final module root script file!'
}
}
}

View File

@ -0,0 +1,21 @@
BeforeAll {
$Repo = Get-RepoPath
Import-Module $Repo.Src.Manifest.Item.FullName -Force
}
Describe 'New-BuildPackage' {
Context 'Default tests' -Tag 'Default' {
It 'Test Function' {
{ Get-Command -Name 'New-BuildPackage' -Module $Repo.Artifact } | Should -Not -Throw
}
It 'Test Help' {
{ Get-Help -Name 'New-BuildPackage' } | Should -Not -Throw
}
It 'Help Content' {
$foo = Get-Help -Name 'New-BuildPackage'
$foo.Synopsis.Length | Should -BeGreaterThan 5
$foo.Description.Count | Should -BeGreaterOrEqual 1
$foo.Description[0].Text.Length | Should -BeGreaterThan 5
}
}
}

View File

@ -0,0 +1,74 @@
function New-BuildPackage {
<#
.SYNOPSIS
Creates a new module package as compressed archive.
.DESCRIPTION
This function is used in build pipeline to create an uploadable module version for the Gitea release page.
.PARAMETER AdditionalPath
You can provide additional paths to add files or folders in published module.
.INPUTS
[None] No pipeline input.
.OUTPUTS
[None] No pipeline output.
.EXAMPLE
Import-Module -Name DroneHelper; New-BuildPackage
#>
[CmdletBinding()]
[OutputType()]
[Diagnostics.CodeAnalysis.SuppressMessageAttribute(
'PSUseConsistentWhitespace',
'',
Justification = 'Hashtable bug in ScriptAnalyzer 1.19.1'
)]
[Diagnostics.CodeAnalysis.SuppressMessageAttribute(
'PSUseShouldProcessForStateChangingFunctions',
'',
Justification = 'system state does not change permanent in temp build clients.'
)]
param (
[Parameter(Mandatory = $false)]
[String[]]$AdditionalPath
)
process {
$Repo = Get-RepoPath
$res = @()
foreach ($item in $AdditionalPath) {
try {
$res += Resolve-Path -Path $item -ErrorAction Stop
}
catch {
Write-Error -Message ('The given additional path does not exist! ({0})' -f $item) -ErrorAction Stop
}
}
Merge-ModuleRoot -ErrorAction Stop
$CompressParams = @{
Path = @(
# psm1 file
$Repo.Bin.ScriptModuleName
# psd1 file
$Repo.Src.Manifest.Item.FullName
# Formats/ folder
$Repo.Src.Formats.Path
)
DestinationPath = $Repo.Bin.ArtifactPath
Force = $true
ErrorAction = 'Stop'
Verbose = $VerbosePreference
}
$CompressParams.Path += $res
try {
Compress-Archive @CompressParams
}
catch {
Write-FailureStateFile -StepName 'BuildPackage'
throw $_.Exception.Message
}
}
}

View File

@ -0,0 +1,21 @@
BeforeAll {
$Repo = Get-RepoPath
Import-Module $Repo.Src.Manifest.Item.FullName -Force
}
Describe 'Update-ModuleMeta' {
Context 'Default tests' -Tag 'Default' {
It 'Test Function' {
{ Get-Command -Name 'Update-ModuleMeta' -Module $Repo.Artifact } | Should -Not -Throw
}
It 'Test Help' {
{ Get-Help -Name 'Update-ModuleMeta' } | Should -Not -Throw
}
It 'Help Content' {
$foo = Get-Help -Name 'Update-ModuleMeta'
$foo.Synopsis.Length | Should -BeGreaterThan 5
$foo.Description.Count | Should -BeGreaterOrEqual 1
$foo.Description[0].Text.Length | Should -BeGreaterThan 5
}
}
}

View File

@ -0,0 +1,75 @@
function Update-ModuleMeta {
<#
.SYNOPSIS
Updates the module manifest file fields to prepare the new build.
.DESCRIPTION
Replaces the version fields in the manifest file. Uses Drone env vars populated by pushed tags.
.INPUTS
[None] No pipeline input.
.OUTPUTS
[None] No pipeline output.
.EXAMPLE
Import-Module -Name DroneHelper; Update-ModuleMeta
#>
[CmdletBinding()]
[OutputType()]
[Diagnostics.CodeAnalysis.SuppressMessageAttribute(
'PSUseConsistentWhitespace',
'',
Justification = 'Hashtable bug in ScriptAnalyzer 1.19.1'
)]
[Diagnostics.CodeAnalysis.SuppressMessageAttribute(
'PSUseShouldProcessForStateChangingFunctions',
'',
Justification = 'system state does not change permanent in temp build clients.'
)]
param ()
process {
if ($Env:DRONE) {
$Repo = Get-RepoPath
if ($Env:DRONE_BUILD_EVENT -eq 'tag') {
if ($null -ne $Env:DRONE_SEMVER) {
$nVersion = $Env:DRONE_SEMVER_SHORT
if ($null -ne $Env:DRONE_SEMVER_PRERELEASE) {
$nPreRelease = $Env:DRONE_SEMVER_PRERELEASE
}
$ModManifestParams = @{
Path = $Repo.Src.Manifest.Item.FullName
ModuleVersion = $nVersion
ErrorAction = 'Stop'
}
if ($nPreRelease) {
$ModManifestParams.PreRelease = $nPreRelease
}
$ManifestData = Test-ModuleManifest -Path $Repo.Src.Manifest.Item.FullName
if (
($nVersion -ne $ManifestData.Version) -or
($nVersion -ne $ManifestData.PrivateData.PSData.Prerelease)
) {
Update-ModuleManifest @ModManifestParams
}
else {
Write-Verbose -Message 'Identical version given. Skipping update.'
}
}
else {
Write-Verbose -Message 'Could not read the new Tag / Semver!'
}
}
else {
Write-Verbose -Message 'This pipeline was not triggered by a tag.'
}
}
else {
Write-Verbose -Message 'Running outside of drone.io pipeline. Skipping module update!'
}
}
}

3
src/CHANGELOG.md Normal file
View File

@ -0,0 +1,3 @@
# Changelog
<!-- insertMark -->

View File

@ -0,0 +1,21 @@
BeforeAll {
$Repo = Get-RepoPath
Import-Module $Repo.Src.Manifest.Item.FullName -Force
}
Describe 'Update-Changelog' {
Context 'Default tests' -Tag 'Default' {
It 'Test Function' {
{ Get-Command -Name 'Update-Changelog' -Module $Repo.Artifact } | Should -Not -Throw
}
It 'Test Help' {
{ Get-Help -Name 'Update-Changelog' } | Should -Not -Throw
}
It 'Help Content' {
$foo = Get-Help -Name 'Update-Changelog'
$foo.Synopsis.Length | Should -BeGreaterThan 5
$foo.Description.Count | Should -BeGreaterOrEqual 1
$foo.Description[0].Text.Length | Should -BeGreaterThan 5
}
}
}

View File

@ -0,0 +1,93 @@
function Update-Changelog {
<#
.SYNOPSIS
Updates the changelog file with recent commits
.DESCRIPTION
This helper function is used to insert recent changes for an upcoming release.
.Parameter NewVersion
Provide a valid semver based version tag for the upcoming release like:
- `v0.0.1-dev1`
- `v1.0.0`
.Parameter SkipCleanup
You can skip the tag update and additional test.
.INPUTS
[None] No pipeline input.
.OUTPUTS
[None] no pipeline putput.
.EXAMPLE
Import-Module -Name DroneHelper; Update-Changelog -NewVersion '0.0.1-dev5'
#>
[CmdletBinding()]
[Diagnostics.CodeAnalysis.SuppressMessageAttribute(
'PSAvoidUsingInvokeExpression',
'',
Justification = 'raw git commands needed'
)]
[Diagnostics.CodeAnalysis.SuppressMessageAttribute(
'PSUseShouldProcessForStateChangingFunctions',
'',
Justification = 'system state does not change permanent in temp build clients.'
)]
param (
[Parameter(Mandatory = $true)]
[ValidatePattern('^(v)?([0-9]+)\.([0-9]+)\.([0-9]+)(-([A-Za-z]+)([0-9]+))?$')]
[String]$NewVersion,
[Parameter(Mandatory = $false)]
[Switch]$SkipCleanup
)
process {
if (-not $SkipCleanup.IsPresent) {
Invoke-Expression -Command 'git tag -d $(git tag -l)' | Write-Verbose
Invoke-Expression -Command 'git fetch --tags --prune' | Write-Verbose
$GitState = Get-GitStatus
if ($GitState.Branch -eq 'master') {
Write-Error -Message 'You can nor update the changelog within the master branch!' -ErrorAction Stop
}
if (
($GitState.BehindBy -ne 0) -or
($GitState.AheadBy -ne 0) -or
($GitState.HasUntracked -ne $false) -or
($GitState.HasWorking -ne $false)
) {
Write-Error -Message 'Your branch is a mess! Cleanup and try it again.' -ErrorAction Stop
}
}
$Repo = Get-RepoPath
$Tags = Invoke-Expression -Command 'git tag'
$NormalizedTags = $Tags | Where-Object { $_ -notmatch '-' } | ForEach-Object {
[PSCustomObject]@{
tag = $_
rawVersion = (
($_ -split '-')[0] -replace 'v', ''
)
}
}
$LTag = $NormalizedTags | Sort-Object -Property 'rawVersion' | Select-Object -ExpandProperty 'tag' -Last 1
Write-Debug -Message ('Last tag: {0}' -f $LTag)
if ($null -eq $LTag) {
Write-Error -Message 'No tags found!' -ErrorAction 'Stop'
}
$Expr = "git log {0}..HEAD --format='- (%h) %s'" -f $LTag
$Res = Invoke-Expression -Command $Expr
Write-Debug -Message ('New Changelog: {0}' -f $Res)
if ($Repo.Changelog.Exists) {
$Content = Get-Content -Path $Repo.Changelog.Item.FullName
$Content[2] += "{0}## ``{2}``{0}{0}{1}" -f [Environment]::NewLine, ($Res | Out-String), $NewVersion
$Content | Out-File -FilePath $Repo.Changelog.Item.FullName -Encoding utf8
Set-EOL -Path $Repo.Changelog.Item.FullName
}
else {
Write-Error -Message 'Changelog file does not exist!' -ErrorAction Stop
}
}
}

View File

@ -0,0 +1,21 @@
BeforeAll {
$Repo = Get-RepoPath
Import-Module $Repo.Src.Manifest.Item.FullName -Force
}
Describe 'Invoke-Publish' {
Context 'Default tests' -Tag 'Default' {
It 'Test Function' {
{ Get-Command -Name 'Invoke-Publish' -Module $Repo.Artifact } | Should -Not -Throw
}
It 'Test Help' {
{ Get-Help -Name 'Invoke-Publish' } | Should -Not -Throw
}
It 'Help Content' {
$foo = Get-Help -Name 'Invoke-Publish'
$foo.Synopsis.Length | Should -BeGreaterThan 5
$foo.Description.Count | Should -BeGreaterOrEqual 1
$foo.Description[0].Text.Length | Should -BeGreaterThan 5
}
}
}

View File

@ -0,0 +1,46 @@
function Invoke-Publish {
<#
.SYNOPSIS
Publishes powershell module to internal Nexus repository.
.DESCRIPTION
This Cmdlet is used to publish the module via Drone pipeline.
.INPUTS
[None] No pipeline input.
.OUTPUTS
[None] No pipeline output.
.EXAMPLE
Invoke-Publish
#>
[CmdletBinding()]
[Diagnostics.CodeAnalysis.SuppressMessageAttribute(
'PSUseConsistentWhitespace',
'',
Justification = 'Hashtable bug in ScriptAnalyzer 1.19.1'
)]
param ()
process {
$Repo = Get-RepoPath
$ExpandParams = @{
Path = $Repo.Bin.ArtifactPath
DestinationPath = $Repo.Bin.ExpandPath
Force = $true
ErrorAction = 'Stop'
Verbose = $VerbosePreference
}
Expand-Archive @ExpandParams
$PublishParams = @{
Repository = 'Nexus'
Path = $Repo.Bin.ExpandPath
NuGetApiKey = $Env:NexusToken
Verbose = $VerbosePreference
ErrorAction = 'Stop'
}
Publish-Module @PublishParams
}
}

View File

@ -0,0 +1,32 @@
BeforeAll {
$Repo = Get-RepoPath
Import-Module $Repo.Src.Manifest.Item.FullName -Force
}
Describe 'Install-ModuleDependency' {
Context 'Default tests' -Tag 'Default' {
It 'Test Function' {
{ Get-Command -Name 'Install-ModuleDependency' -Module $Repo.Artifact } | Should -Not -Throw
}
It 'Test Help' {
{ Get-Help -Name 'Install-ModuleDependency' } | Should -Not -Throw
}
It 'Help Content' {
$foo = Get-Help -Name 'Install-ModuleDependency'
$foo.Synopsis.Length | Should -BeGreaterThan 5
$foo.Description.Count | Should -BeGreaterOrEqual 1
$foo.Description[0].Text.Length | Should -BeGreaterThan 5
}
}
Context 'Unit tests' -Tag 'Unit' {
It 'Should throw' {
{ Install-ModuleDependency -foo } | Should -Throw
}
#It 'Should not throw' {
# Mock 'Install-Module' -ModuleName $Repo.Artifact {}
# { Install-ModuleDependency } | Should -Not -Throw
#}
}
}

View File

@ -0,0 +1,74 @@
function Install-ModuleDependency {
<#
.SYNOPSIS
Install required modules of the module manifest file.
.DESCRIPTION
Use this cmdlet to install required modules of the module manifest file.
.INPUTS
[None]
.OUTPUTS
[None]
.EXAMPLE
Install-ModuleDependency
.NOTES
#>
[CmdletBinding()]
#[OutputType([String])]
param ()
begin {
}
process {
$Repo = Get-RepoPath
$ManifestContent = Import-PowerShellDataFile -Path $Repo.Src.Manifest.Item.FullName
if ($ManifestContent.RequiredModules) {
foreach ($Module in $ManifestContent.RequiredModules) {
if ($Module.RequiredVersion) {
$ParamsInstallModule = @{
Name = $Module.ModuleName
Scope = 'AllUsers'
RequiredVersion = $Module.RequiredVersion
Force = $true
AllowClobber = $true
Verbose = $VerbosePreference
ErrorAction = 'Stop'
}
}
else {
$ParamsInstallModule = @{
Name = $Module.ModuleName
Scope = 'AllUsers'
MinimumVersion = $Module.ModuleVersion
Force = $true
AllowClobber = $true
Verbose = $VerbosePreference
ErrorAction = 'Stop'
}
}
try {
Install-Module @ParamsInstallModule
$Message = 'Module <{0}> successfully installed' -f $Module.ModuleName
Write-Verbose -Message $Message
}
catch {
$Message = 'Module <{0}> could not be installed! ' -f $Module.ModuleName
$Message += $_.Exception.Message
Write-Error -Message $Message -ErrorAction 'Stop'
}
}
}
else {
Write-Verbose -Message 'no required modules found...'
}
}
end {
}
}

View File

@ -0,0 +1,21 @@
BeforeAll {
$Repo = Get-RepoPath
Import-Module $Repo.Src.Manifest.Item.FullName -Force
}
Describe 'Invoke-InstallDependency' {
Context 'Default tests' -Tag 'Default' {
It 'Test Function' {
{ Get-Command -Name 'Invoke-InstallDependency' -Module $Repo.Artifact } | Should -Not -Throw
}
It 'Test Help' {
{ Get-Help -Name 'Invoke-InstallDependency' } | Should -Not -Throw
}
It 'Help Content' {
$foo = Get-Help -Name 'Invoke-InstallDependency'
$foo.Synopsis.Length | Should -BeGreaterThan 5
$foo.Description.Count | Should -BeGreaterOrEqual 1
$foo.Description[0].Text.Length | Should -BeGreaterThan 5
}
}
}

View File

@ -0,0 +1,72 @@
function Invoke-InstallDependency {
<#
.SYNOPSIS
Install required modules for executing the DroneHelper pipeline helpers.
.DESCRIPTION
This can be used in drone.io docker pipeline if the modules are not integrated in the build image.
.INPUTS
[None] No Input required.
.OUTPUTS
[None] No Output
.EXAMPLE
Import-Module -Name DroneHelper; Invoke-Install-Dependency
#>
[CmdletBinding()]
[OutputType()]
param ()
process {
try {
$PSScriptParams = @{
Name = 'PSScriptAnalyzer'
Scope = 'CurrentUser'
RequiredVersion = '1.20.0'
Force = $true
SkipPublisherCheck = $true
AllowClobber = $true
Verbose = $VerbosePreference
ErrorAction = 'Stop'
}
Install-Module @PSScriptParams
$PesterParams = @{
Name = 'Pester'
Scope = 'CurrentUser'
RequiredVersion = '5.3.1'
Force = $true
SkipPublisherCheck = $true
AllowClobber = $true
Verbose = $VerbosePreference
ErrorAction = 'Stop'
}
Install-Module @PesterParams
$PoshParams = @{
Name = 'posh-git'
Scope = 'CurrentUser'
RequiredVersion = '1.0.0'
Force = $true
SkipPublisherCheck = $true
AllowClobber = $true
Verbose = $VerbosePreference
ErrorAction = 'Stop'
}
Install-Module @PoshParams
}
catch {
$ExecParams = @{
Exception = [System.Exception]::new(
'Could not install required build dependencies!',
$PSItem.Exception
)
ErrorAction = 'Stop'
}
Write-Error @ExecParams
}
}
}

View File

@ -0,0 +1,21 @@
BeforeAll {
$Repo = Get-RepoPath
Import-Module $Repo.Src.Manifest.Item.FullName -Force
}
Describe 'New-Docs' {
Context 'Default tests' -Tag 'Default' {
It 'Test Function' {
{ Get-Command -Name 'New-Docs' -Module $Repo.Artifact } | Should -Not -Throw
}
It 'Test Help' {
{ Get-Help -Name 'New-Docs' } | Should -Not -Throw
}
It 'Help Content' {
$foo = Get-Help -Name 'New-Docs'
$foo.Synopsis.Length | Should -BeGreaterThan 5
$foo.Description.Count | Should -BeGreaterOrEqual 1
$foo.Description[0].Text.Length | Should -BeGreaterThan 5
}
}
}

50
src/Docs/New-Docs.ps1 Normal file
View File

@ -0,0 +1,50 @@
function New-Docs {
<#
.SYNOPSIS
Creates a ne set of markdown based help in the docs folder.
.DESCRIPTION
This Cmdlet should be used once locally, or after adding new functions. The function `Update-Docs`
can be used via pipeline to keep the docs up to date.
.INPUTS
[None] No pipeline input.
.OUTPUTS
[None] No pipeline output.
.EXAMPLE
New-Docs
#>
[CmdletBinding()]
[Diagnostics.CodeAnalysis.SuppressMessageAttribute(
'PSUseSingularNouns',
'',
Justification = 'New-Doc already in use by other popular modules.'
)]
[Diagnostics.CodeAnalysis.SuppressMessageAttribute(
'PSUseShouldProcessForStateChangingFunctions',
'',
Justification = 'system state does not change permanent in temp build clients.'
)]
param ()
process {
$Repo = Get-RepoPath
Import-Module $Repo.Src.Manifest.Item.FullName -Global -Force
Import-Module -Name 'platyPS'
$MarkdownParams = @{
Module = $Repo.Artifact
OutputFolder = $Repo.Docs.Path
WithModulePage = $true
ModulePagePath = $Repo.Docs.ModulePagePath
Force = $true
}
New-MarkdownHelp @MarkdownParams
$Docs = Get-Item -Path $Repo.Docs.MarkdownFilter
foreach ($Doc in $Docs) {
Write-Verbose -Message ('Converting {0}' -f $Doc.FullName)
Set-EOL -Path $Doc
}
}
}

View File

@ -0,0 +1,21 @@
BeforeAll {
$Repo = Get-RepoPath
Import-Module $Repo.Src.Manifest.Item.FullName -Force
}
Describe 'Update-Docs' {
Context 'Default tests' -Tag 'Default' {
It 'Test Function' {
{ Get-Command -Name 'Update-Docs' -Module $Repo.Artifact } | Should -Not -Throw
}
It 'Test Help' {
{ Get-Help -Name 'Update-Docs' } | Should -Not -Throw
}
It 'Help Content' {
$foo = Get-Help -Name 'Update-Docs'
$foo.Synopsis.Length | Should -BeGreaterThan 5
$foo.Description.Count | Should -BeGreaterOrEqual 1
$foo.Description[0].Text.Length | Should -BeGreaterThan 5
}
}
}

49
src/Docs/Update-Docs.ps1 Normal file
View File

@ -0,0 +1,49 @@
function Update-Docs {
<#
.SYNOPSIS
Publishes powershell module to internal Nexus repository.
.DESCRIPTION
This Cmdlet is used to publish the module via Drone pipeline.
.INPUTS
[None] No pipeline input.
.OUTPUTS
[None] No pipeline output.
.EXAMPLE
Update-Docs
#>
[CmdletBinding()]
[Diagnostics.CodeAnalysis.SuppressMessageAttribute(
'PSUseShouldProcessForStateChangingFunctions',
'',
Justification = 'Underlying platyPS can not be mocked.'
)]
[Diagnostics.CodeAnalysis.SuppressMessageAttribute(
'PSUseSingularNouns',
'',
Justification = 'New-Doc already in use by other popular modules.'
)]
param ()
process {
$Repo = Get-RepoPath
Import-Module $Repo.Src.Manifest.Item.FullName -Global -Force
Import-Module -Name 'platyPS'
$MarkdownParams = @{
Path = $Repo.Docs.Path
RefreshModulePage = $true
ModulePagePath = $Repo.Docs.ModulePagePath
Force = $true
}
Update-MarkdownHelpModule @MarkdownParams
$Docs = Get-Item -Path $Repo.Docs.MarkdownFilter
foreach ($Doc in $Docs) {
Write-Verbose -Message ('Converting {0}' -f $Doc.FullName)
Set-EOL -Path $Doc
}
}
}

190
src/DroneHelper.psd1 Normal file
View File

@ -0,0 +1,190 @@
#
# Module manifest for module 'DroneHelper'
#
# Generated by: OCram85
#
# Generated on: 13.06.2022
#
@{
# Script module or binary module file associated with this manifest.
RootModule = 'ModuleRoot.psm1'
# Version number of this module.
ModuleVersion = '0.0.1'
# Supported PSEditions
CompatiblePSEditions = @(
'Desktop',
'Core'
)
# ID used to uniquely identify this module
GUID = '4293292f-eac8-42ed-8e9d-437d4f405d2c'
# Author of this module
Author = 'OCram85'
# Company or vendor of this module
CompanyName = ''
# Copyright statement for this module
Copyright = '(c) OCram85. All rights reserved.'
# Description of the functionality provided by this module
Description = 'Helper module for Drone.io CI.'
# Minimum version of the PowerShell engine required by this module
PowerShellVersion = '5.1'
# Name of the PowerShell host required by this module
# PowerShellHostName = ''
# Minimum version of the PowerShell host required by this module
# PowerShellHostVersion = ''
# Minimum version of Microsoft .NET Framework required by this module.
# This prerequisite is valid for the PowerShell Desktop edition only.
# DotNetFrameworkVersion = ''
# Minimum version of the common language runtime (CLR) required by this module.
# This prerequisite is valid for the PowerShell Desktop edition only.
# ClrVersion = ''
# Processor architecture (None, X86, Amd64) required by this module
# ProcessorArchitecture = ''
# Modules that must be imported into the global environment prior to importing this module
RequiredModules = @(
@{
ModuleName = 'PSScriptAnalyzer'
RequiredVersion = '1.20.0'
},
@{
ModuleName = 'Pester'
RequiredVersion = '5.3.1'
},
@{
ModuleName = 'posh-git'
ModuleVersion = '1.1.0'
}
)
# Assemblies that must be loaded prior to importing this module
# RequiredAssemblies = @()
# Script files (.ps1) that are run in the caller's environment prior to importing this module.
# ScriptsToProcess = @()
# Type files (.ps1xml) to be loaded when importing this module
# TypesToProcess = @()
# Format files (.ps1xml) to be loaded when importing this module
# FormatsToProcess = @()
# Modules to import as nested modules of the module specified in RootModule/ModuleToProcess
# NestedModules = @()
# Functions to export from this module, for best performance, do not use wildcards and do not delete the entry,
# use an empty array if there are no functions to export.
FunctionsToExport = @(
# Build
'Merge-ModuleRoot',
'New-BuildPackage',
'Update-ModuleMeta',
# Changelog
'Update-Changelog',
# Deploy
'Invoke-Publish',
# Deps
'Install-ModuleDependency',
'Invoke-InstallDependency',
# Docs
'New-Docs',
'Update-Docs',
# FileLinter
'Invoke-FileLinter',
'Test-FileBOM',
'Test-FileEncoding',
'Test-FileEOF',
'Test-FileEOL',
'Test-FileTab',
'Test-FileTailingWhitespace',
# Helper
'Get-RepoPath',
'Set-EOL',
# Pester
'Invoke-UnitTest',
# PRComment
'Send-PRComment',
# PSScriptAnalyzer
'Invoke-Linter',
# State
'Invoke-BuildState',
'Write-FailureStateFile',
'Write-ResultFile'
)
# Cmdlets to export from this module, for best performance, do not use wildcards and do not delete the entry,
# use an empty array if there are no cmdlets to export.
CmdletsToExport = @()
# Variables to export from this module
VariablesToExport = '*'
# Aliases to export from this module, for best performance, do not use wildcards and do not delete the entry,
# use an empty array if there are no aliases to export.
AliasesToExport = @()
# DSC resources to export from this module
# DscResourcesToExport = @()
# List of all modules packaged with this module
# ModuleList = @()
# List of all files packaged with this module
# FileList = @()
# Private data to pass to the module specified in RootModule/ModuleToProcess. This may also contain a PSData
# hashtable with additional module metadata used by PowerShell.
PrivateData = @{
PSData = @{
# Tags applied to this module. These help with module discovery in online galleries.
# Tags = @()
# A URL to the license for this module.
# LicenseUri = ''
# A URL to the main website for this project.
ProjectUri = 'https://gitea.ocram85.com/OCram85/DroneHelper'
# A URL to an icon representing this module.
# IconUri = ''
# ReleaseNotes of this module
# ReleaseNotes = ''
# Prerelease string of this module
# Prerelease = ''
# Flag to indicate whether the module requires explicit user acceptance for install/update/save
# RequireLicenseAcceptance = false
# External dependent modules of this module
# ExternalModuleDependencies = @()
} # End of PSData hashtable
} # End of PrivateData hashtable
# HelpInfo URI of this module
# HelpInfoURI = ''
# Default prefix for commands exported from this module. Override the default prefix using
# Import-Module -Prefix.
# DefaultCommandPrefix = ''
}

View File

@ -0,0 +1,81 @@
BeforeAll {
$Repo = Get-RepoPath
Import-Module $Repo.Src.Manifest.Item.FullName -Force
$MoveBackup = @{
Path = $Repo.Build.FileLinterLogPath
Destination = Join-Path -Path $Repo.Build.Path -ChildPath 'FileLinter.backup'
}
Move-Item @MoveBackup
}
Describe 'Invoke-FileLinter' {
Context 'Default tests' -Tag 'Default' {
It 'Test Function' {
{ Get-Command -Name 'Invoke-FileLinter' -Module $Repo.Artifact } | Should -Not -Throw
}
It 'Test Help' {
{ Get-Help -Name 'Invoke-FileLinter' } | Should -Not -Throw
}
It 'Help Content' {
$foo = Get-Help -Name 'Invoke-FileLinter'
$foo.Synopsis.Length | Should -BeGreaterThan 5
$foo.Description.Count | Should -BeGreaterOrEqual 1
$foo.Description[0].Text.Length | Should -BeGreaterThan 5
}
}
Context 'Coding tests' -Tag 'Unit' {
# Mocking doesn't get executed. Switch to Integration test class to avoid duplicated output.
BeforeEach {
Mock 'Write-FailureStateFile' {
Write-Debug 'Mocking function Write-FailureStateFile.'
} -ModuleName 'DroneHelper'
Mock 'Write-ResultFile' {
Write-Debug 'Mocking function Write-ResultFile.'
} -ModuleName 'DroneHelper'
}
It 'Should throw' {
#Mock 'Write-FailureStateFile' { Write-Debug 'Mocking Report generator' }
#Mock 'Write-ResultFile' { Write-Debug 'Mocking function Write-ResultFile.' }
{ Invoke-FileLinter -foo } | Should -Throw
}
It 'Should not throw' {
#Mock 'Write-FailureStateFile' { Write-Debug 'Mocking Report generator' }
#Mock 'Write-ResultFile' { Write-Debug 'Mocking function Write-ResultFile.' }
{ Invoke-FileLinter } | Should -Not -Throw
}
It 'Should have found files' {
#Mock Write-FailureStateFile { Write-Debug 'Mocking Report generator' }
#Mock 'Write-ResultFile' { Write-Debug 'Mocking function Write-ResultFile.' }
$res = Invoke-FileLinter -PassThru
$res.FilesCount | Should -Not -BeNullOrEmpty
$res.FilesCount | Should -BeGreaterThan 0
}
It 'Should run without linter issues' {
#Mock 'Write-FailureStateFile' { Write-Debug 'Mocking Report generator' }
#Mock 'Write-ResultFile' { Write-Debug 'Mocking function Write-ResultFile.' }
$res = Invoke-FileLinter -PassThru
$res.FailedCount | Should -Be 0
}
It 'Should Sum up ' {
#Mock 'Write-FailureStateFile' { Write-Debug 'Mocking Report generator' }
#Mock 'Write-ResultFile' { Write-Debug 'Mocking function Write-ResultFile.' }
$res = Invoke-FileLinter -PassThru
$res.FailedCount | Should -Be 0
}
}
}
AfterAll {
$Repo = Get-RepoPath
$RestoreBackup = @{
Path = Join-Path -Path $Repo.Build.Path -ChildPath 'FileLinter.backup'
Destination = $Repo.Build.FileLinterLogPath
}
Move-Item @RestoreBackup -Force
}

View File

@ -0,0 +1,115 @@
function Invoke-FileLinter {
<#
.SYNOPSIS
Runs the file linter for all src files found in current repository.
.DESCRIPTION
Invoke-FileLinter runs the basic file tests and generates a report file for furher usage in the
drone pipeline.
.INPUTS
[None]
.OUTPUTS
[DroneHelper.FileLinter.Report]
.EXAMPLE
Invoke-FileLinter
.NOTES
#>
[CmdletBinding()]
[OutputType('DroneHelper.FileLinter.Report')]
param (
[Parameter(Mandatory = $false)]
[switch]$PassThru
)
begin {
}
process {
$FileSet = @()
$Repo = Get-RepoPath
$RawFiles = (Get-ChildItem -Path $Repo.src.Path -Recurse -File).FullName
Write-Debug -Message ('EXCLUDE Filter. {0}' -f $Env:EXCLUDE)
if ($Env:EXCLUDE) {
$Files = $RawFiles -notmatch $Env:EXCLUDE
Write-Debug -Message ('Raw File List: {0} | Filtered Files: {1}' -f $RawFiles.Count, $Files.Count)
}
else {
$Files = $RawFiles
}
foreach ($file in $Files) {
Write-Verbose -Message ('Running FileLinter tests for: {0}' -f $file)
$FileResults = [PSCustomObject]@{
Name = $file
FailedCount = 0
Tests = [ordered]@{
Encoding = (Test-FileEncoding -Path $file)
BOM = (Test-FileBOM -Path $file)
EOL = (Test-FileEOL -Path $file)
EOF = (Test-FileEOF -Path $file)
TAB = (Test-FileTab -Path $file)
TailingWhite = (Test-FileTailingWhitespace -Path $file)
}
}
Write-Verbose -Message ('Populating property FailedCount for current file.')
foreach ($item in $FileResults.Tests.Keys.GetEnumerator()) {
if (($FileResults.Tests.$item) -ne $true) {
$FileResults.FailedCount++
}
}
$FileSet += [PSCustomObject]$FileResults
}
$LinterReport = [PSCustomObject]@{
Success = $null
FilesCount = ($FileSet | Measure-Object).Count
FailedCount = 0
Files = $FileSet
}
Write-Verbose -Message ('Populating total FailedCount property.')
foreach ($i in $LinterReport.Files.FailedCount) {
if ($i -ne 0) {
$LinterReport.FailedCount = $LinterReport.FailedCount + $i
}
}
if ($LinterReport.FailedCount -eq 0) {
$LinterReport.Success = $true
}
else {
$LinterReport.Success = $false
}
$LinterReport.PSObject.TypeNames.Insert(0, 'DroneHelper.FileLinter.Report')
$LinterReport.Files | Out-String | Write-Verbose -Verbose
$LinterReport | Format-Table -Property @(
'Success',
'FilesCount',
'FailedCount'
) | Out-String | Write-Verbose -Verbose
$ResultParams = @{
Type = 'FileLinter'
Path = $Repo.Build.FileLinterLogPath
InputObject = $LinterReport
}
Write-ResultFile @ResultParams
if (-not ($LinterReport.Success)) {
Write-FailureStateFile -StepName 'FileLinter'
throw 'FileLinter failed!'
}
}
end {
if ($PassThru.IsPresent) {
Write-Output $LinterReport
}
}
}

View File

@ -0,0 +1,37 @@
BeforeAll {
$Repo = Get-RepoPath
Import-Module $Repo.Src.Manifest.Item.FullName -Force
#$srcFile = Join-Path -Path $Repo.Src.Path -ChildPath 'FileLinter/Test-FileBOM.ps1'
#. "$srcFile"
}
Describe 'Test-FileBOM' {
Context 'Default tests' -Tag 'Default' {
It 'Test Function' {
{ Get-Command -Name 'Test-FileBOM' -Module $Repo.Artifact } | Should -Not -Throw
}
It 'Test Help' {
{ Get-Help -Name 'Test-FileBOM' } | Should -Not -Throw
}
It 'Help Content' {
$foo = Get-Help -Name 'Test-FileBOM'
$foo.Synopsis.Length | Should -BeGreaterThan 5
$foo.Description.Count | Should -BeGreaterOrEqual 1
$foo.Description[0].Text.Length | Should -BeGreaterThan 5
}
}
Context 'Unit tests' -Tag 'Unit' {
It 'Should throw' {
{ Test-FileBOM -foo } | Should -Throw
}
It 'Should not throw' {
{ Test-FileBOM -Path $Repo.Src.Manifest.Item.FullName } | Should -Not -Throw
}
It 'Should return true' {
Test-FileBOM -Path $Repo.Src.Manifest.Item.FullName | Should -Be $true
}
}
}

View File

@ -0,0 +1,61 @@
function Test-FileBOM {
<#
.SYNOPSIS
Tests given file if native utf8 w/o BOM is used. Returns false if BOM is present.
.DESCRIPTION
This function is used to test for a valid encoding without BOM.
.PARAMETER Path
Full or relative path to existing file.
.INPUTS
[None]
.OUTPUTS
[bool]
.EXAMPLE
Test-FileBOM -Path './Testfile.txt'
.NOTES
#>
[CmdletBinding()]
[OutputType([bool])]
param (
[Parameter(Mandatory = $true)]
[ValidateScript(
{
Test-Path -Path $_
}
)]
[string]$Path
)
begin {
}
process {
try {
$contents = [byte[]]::new(3)
$stream = [System.IO.File]::OpenRead($Path)
$stream.Read($contents, 0, 3) | Out-Null
$stream.Close()
}
catch {
Write-Error -Message 'Could not read the given file!' -ErrorAction 'Stop'
}
Write-Debug -Message ('BOM Content was: {0}' -f ([System.BitConverter]::ToString($contents)))
if ( $contents[0] -eq 0xEF -and $contents[1] -eq 0xBB -and $contents -eq 0xBF ) {
Write-Output $false
}
else {
Write-Output $true
}
}
end {
}
}

View File

@ -0,0 +1,37 @@
BeforeAll {
$Repo = Get-RepoPath
Import-Module $Repo.Src.Manifest.Item.FullName -Force
#$srcFile = Join-Path -Path $Repo.Src.Path -ChildPath 'FileLinter/Test-FileEOF.ps1'
#. "$srcFile"
#$srcFile = Join-Path -Path $Repo.Src.Path -ChildPath 'FileLinter/Test-FileEOL.ps1'
#. "$srcFile"
}
Describe 'Test-FileEOF' {
Context 'Default tests' -Tag 'Default' {
It 'Test Function' {
{ Get-Command -Name 'Test-FileEOF' -Module $Repo.Artifact } | Should -Not -Throw
}
It 'Test Help' {
{ Get-Help -Name 'Test-FileEOF' } | Should -Not -Throw
}
It 'Help Content' {
$foo = Get-Help -Name 'Test-FileEOF'
$foo.Synopsis.Length | Should -BeGreaterThan 5
$foo.Description.Count | Should -BeGreaterOrEqual 1
$foo.Description[0].Text.Length | Should -BeGreaterThan 5
}
}
Context 'Coding tests' -Tag 'Unit' {
It 'Should throw' {
{ Test-FileEOF -foo } | Should -Throw
}
It 'Should not throw' {
{ Test-FileEOF -Path $Repo.Src.Manifest.Item.FullName } | Should -Not -Throw
}
It 'Should be true' {
Test-FileEOF -Path $Repo.Src.Manifest.Item.FullName | Should -Be $true
}
}
}

View File

@ -0,0 +1,56 @@
function Test-FileEOF {
<#
.SYNOPSIS
Returns false if EOF isn't an empty line.
.DESCRIPTION
Test the given file against the EOF standard (final empty/blank line + CRLF) and returns true or false.
.PARAMETER Path
Relative or full path to an existing file.
.INPUTS
[none]
.OUTPUTS
[bool]
.EXAMPLE
Test-FileEOF -Path './testfile.txt'
.NOTES
#>
[CmdletBinding()]
[OutputType([bool])]
param (
[Parameter(Mandatory = $true)]
[string]$Path
)
begin {
}
process {
if (-not (Test-FileEOL -Path $Path)) {
Write-Warning -Message ('The given file does not use CRLF! ({0})' -f $Path)
Write-Output $false
}
$content = Get-Content -Path $Path -Raw -Encoding 'utf8'
$lastLine = ($content -split "`r`n")[-1].Length
# Test for multiple lines without content on EOF
$perLine = ($content -split "`r`n")[-2].Length
if (($lastLine -eq 0) -and ($perLine -ne 0)) {
Write-Debug -Message ('EOF: LastLine {0}; PenultimateLine {1} -> true' -f $lastLine, $perLine)
Write-Output $true
}
else {
Write-Debug -Message ('EOF: LastLine {0}; PenultimateLine {1} -> false' -f $lastLine, $perLine)
Write-Output $false
}
}
end {
}
}

View File

@ -0,0 +1,38 @@
BeforeAll {
$Repo = Get-RepoPath
Import-Module $Repo.Src.Manifest.Item.FullName -Force
#$srcFile = Join-Path -Path $Repo.Src.Path -ChildPath 'FileLinter/Test-FileEOL.ps1'
#. "$srcFile"
}
Describe 'Test-FileEOL' {
Context 'Default tests' -Tag 'Default' {
It 'Test Function' {
{ Get-Command -Name 'Test-FileEOL' -Module $Repo.Artifact } | Should -Not -Throw
}
It 'Test Help' {
{ Get-Help -Name 'Test-FileEOL' } | Should -Not -Throw
}
It 'Help Content' {
$foo = Get-Help -Name 'Test-FileEOL'
$foo.Synopsis.Length | Should -BeGreaterThan 5
$foo.Description.Count | Should -BeGreaterOrEqual 1
$foo.Description[0].Text.Length | Should -BeGreaterThan 5
}
}
Context 'Coding tests' -Tag 'Unit' {
It 'Should throw' {
{ Test-FileEOL -foo } | Should -Throw
}
It 'Should not throw' {
{ Test-FileEOL -Path $Repo.Src.Manifest.Item.FullName } | Should -Not -Throw
}
It 'Should be true' {
Test-FileEOL -Path $Repo.Src.Manifest.Item.FullName | Should -Be $true
}
It 'Should be false' {
Test-FileEOL -Path $Repo.Changelog.Path | Should -Be $false
}
}
}

View File

@ -0,0 +1,61 @@
function Test-FileEOL {
<#
.SYNOPSIS
Returns false if EOL isn't CRLF
.DESCRIPTION
Tests given file against valid EOL. Returns true if CRLF is used.
.PARAMETER Path
Relative or full path to an existing file.
.INPUTS
[None]
.OUTPUTS
[bool]
.EXAMPLE
Test-FileEOL -Path './TestFile.txt'
.NOTES
#>
[CmdletBinding()]
[OutputType([bool])]
param (
[Parameter(Mandatory = $true)]
[ValidateScript(
{
Test-Path -Path $_
}
)]
[string]$Path
)
begin {
}
process {
$content = Get-Content -Path $Path -Raw -Encoding 'utf8'
$CRLFCount = ([regex]::Matches($content, "`r`n$")).Count
$LFCount = ([regex]::Matches($content, "`n$")).Count
if ($CRLFCount -eq $LFCount) {
Write-Debug -Message 'EOL: CRLFCount = LFCount -> true'
Write-Output $true
}
elseif ($CRLFCount -gt $LFCount) {
Write-Debug -Message 'EOL: CRLFCount > LFCount -> false'
Write-Output $false
}
elseif ($LFCount -gt $CRLFCount) {
Write-Debug -Message 'EOL: CRLFCount < LFCount -> false'
Write-Output $false
}
}
end {
}
}

View File

@ -0,0 +1,41 @@
BeforeAll {
$Repo = Get-RepoPath
Import-Module $Repo.Src.Manifest.Item.FullName -Force
#$srcFile = Join-Path -Path $Repo.Src.Path -ChildPath 'FileLinter/Test-FileEncoding.ps1'
#. "$srcFile"
}
Describe 'Test-FileEncoding' {
Context 'Default tests' -Tag 'Default' {
It 'Test Function' {
{ Get-Command -Name 'Test-FileEncoding' -Module $Repo.Artifact } | Should -Not -Throw
}
It 'Test Help' {
{ Get-Help -Name 'Test-FileEncoding' } | Should -Not -Throw
}
It 'Help Content' {
$foo = Get-Help -Name 'Test-FileEncoding'
$foo.Synopsis.Length | Should -BeGreaterThan 5
$foo.Description.Count | Should -BeGreaterOrEqual 1
$foo.Description[0].Text.Length | Should -BeGreaterThan 5
}
}
Context 'Coding tests' -Tag 'Unit' {
It 'Should throw' {
{ Test-FileEncoding -foo } | Should -Throw
}
It 'Should not throw' {
{ Test-FileEncoding -Path $Repo.Src.Manifest.Item.FullName } | Should -Not -Throw
}
It 'Should return true' {
{ Test-FileEncoding -Path $Repo.Src.Manifest.Item.FullName } | Should -Be $true
}
It 'UTF-8 test file should return true' {
Test-FileEncoding -Path (
Join-Path -Path $repo.Resources.Path -ChildPath 'TestData/encodingTest.txt'
) | Should -BeTrue
}
}
}

View File

@ -0,0 +1,73 @@
function Test-FileEncoding {
<#
.SYNOPSIS
Returns true if the given file is written in a valid encoding
.DESCRIPTION
Test the given file against the encoding regex and returns true or false
.PARAMETER Path
Relative or full path to an existing file.
.PARAMETER Encoding
Optional custom encoding regex string. Default is (utf8|ascii|xml).
.INPUTS
[none]
.OUTPUTS
[bool]
.EXAMPLE
Test-FileEncoding -Path './testfile.txt'
.NOTES
#>
[CmdletBinding()]
[Diagnostics.CodeAnalysis.SuppressMessageAttribute(
'PSAvoidUsingInvokeExpression',
'',
Justification = 'static input without user manipulation'
)]
[OutputType([bool])]
param (
[Parameter(Mandatory = $true)]
[ValidateScript(
{
Test-Path -Path $_
}
)]
[string]$Path,
[Parameter(Mandatory = $false)]
[string]$Encoding = '(utf8|utf-8|ascii|xml)'
)
begin {
}
process {
try {
Get-Command -Name 'file' -ErrorAction 'Stop' | Out-Null
}
catch {
Write-Error -Message "Could not find command called 'file'!" -ErrorAction 'Stop'
}
$Res = Invoke-Expression -Command ("file '{0}' " -f $Path)
# Remove the file from matching. Use the latest array element if split doesn't work.
$ParsedResult = ($Res -split ':')[-1]
Write-Debug -Message ('Encoding: Raw file output {0}' -f $Res)
Write-Debug -Message ('Parsed match string: {0}' -f $ParsedResult)
if ($ParsedResult -match $Encoding) {
Write-Output $true
}
else {
Write-Output $false
}
}
end {
}
}

View File

@ -0,0 +1,36 @@
BeforeAll {
$Repo = Get-RepoPath
Import-Module $Repo.Src.Manifest.Item.FullName -Force
#$srcFile = Join-Path -Path $Repo.Src.Path -ChildPath 'FileLinter/Test-FileTab.ps1'
#. "$srcFile"
}
Describe 'Test-FileTab' {
Context 'Default tests' -Tag 'Default' {
It 'Test Function' {
{ Get-Command -Name 'Test-FileTab' -Module $Repo.Artifact } | Should -Not -Throw
}
It 'Test Help' {
{ Get-Help -Name 'Test-FileTab' } | Should -Not -Throw
}
It 'Help Content' {
$foo = Get-Help -Name 'Test-FileTab'
$foo.Synopsis.Length | Should -BeGreaterThan 5
$foo.Description.Count | Should -BeGreaterOrEqual 1
$foo.Description[0].Text.Length | Should -BeGreaterThan 5
}
}
Context 'Coding tests' -Tag 'Unit' {
It 'Should throw' {
{ Test-FileTab -foo } | Should -Throw
}
It 'Should not throw' {
{ Test-FileTab -Path $Repo.Src.Manifest.Item.FullName } | Should -Not -Throw
}
It 'Should be true' {
Test-FileTab -Path $Repo.Src.Manifest.Item.FullName | Should -Be $true
}
}
}

View File

@ -0,0 +1,56 @@
function Test-FileTab {
<#
.SYNOPSIS
Returns false if tab char is used in file.
.DESCRIPTION
Test the given file if tabs are used. Returns false if any tabs were found.
.PARAMETER Path
elative or full path to an existing file.
.INPUTS
[none]
.OUTPUTS
[bool]
.EXAMPLE
Test-FileTab -Path './testfile.txt'
.NOTES
#>
[CmdletBinding()]
[OutputType([bool])]
param (
[Parameter(Mandatory = $true)]
[ValidateScript(
{
Test-Path -Path $_
}
)]
[string]$Path
)
begin {
}
process {
$content = Get-Content -Path $Path -Raw -Encoding 'utf8'
$Tabs = ([regex]::Matches($content, "`t")).Count
if ($Tabs -ne 0 ) {
Write-Debug -Message ('Tabs: {0} -> false' -f $Tabs)
Write-Output $false
}
else {
Write-Debug -Message ('Tabs: {0} -> true' -f $Tabs)
Write-Output $true
}
}
end {
}
}

View File

@ -0,0 +1,36 @@
BeforeAll {
$Repo = Get-RepoPath
Import-Module $Repo.Src.Manifest.Item.FullName -Force
#$srcFile = Join-Path -Path $Repo.Src.Path -ChildPath 'FileLinter/Test-FileTailingWhitespace.ps1'
#. "$srcFile"
}
Describe 'Test-FileTailingWhitespace' {
Context 'Default tests' -Tag 'Default' {
It 'Test Function' {
{ Get-Command -Name 'Test-FileTailingWhitespace' -Module $Repo.Artifact } | Should -Not -Throw
}
It 'Test Help' {
{ Get-Help -Name 'Test-FileTailingWhitespace' } | Should -Not -Throw
}
It 'Help Content' {
$foo = Get-Help -Name 'Test-FileTailingWhitespace'
$foo.Synopsis.Length | Should -BeGreaterThan 5
$foo.Description.Count | Should -BeGreaterOrEqual 1
$foo.Description[0].Text.Length | Should -BeGreaterThan 5
}
}
Context 'Coding tests' -Tag 'Unit' {
It 'Should throw' {
{ Test-FileTailingWhitespace -foo } | Should -Throw
}
It 'Should not throw' {
{ Test-FileTailingWhitespace -Path $Repo.Src.Manifest.Item.FullName } | Should -Not -Throw
}
It 'Should be true' {
Test-FileTailingWhitespace -Path $Repo.Src.Manifest.Item.FullName | Should -Be $true
}
}
}

View File

@ -0,0 +1,62 @@
function Test-FileTailingWhitespace {
<#
.SYNOPSIS
Returns false if there are any tailing whitespace in lines.
.DESCRIPTION
Tests the given file for tailing whitespace. Returns true if not found.
.PARAMETER Path
Relative or full path to an existing file.
.INPUTS
[none]
.OUTPUTS
[bool]
.EXAMPLE
Test-FileTailingWhitespace.ps1 -Path './testfile.txt'
.NOTES
#>
[CmdletBinding()]
[OutputType([Bool])]
param (
[Parameter(Mandatory = $true)]
[ValidateScript(
{
Test-Path -Path $_
}
)]
[string]$Path
)
begin {
}
process {
$content = Get-Content -Path $Path -Encoding 'utf8'
$WhiteSpace = 0
foreach ($line in $content) {
$c = ([regex]::Matches($line, "\s+$")).Count
if ( $c -gt 0 ) {
$WhiteSpace++
}
}
if ($WhiteSpace -ne 0 ) {
Write-Debug -Message ('WhiteSpace: {0} -> false' -f $WhiteSpace)
Write-Output $false
}
else {
Write-Debug -Message ('WhiteSpace: {0} -> true' -f $WhiteSpace)
Write-Output $true
}
}
end {
}
}

1
src/Formats/.gitkeep Normal file
View File

@ -0,0 +1 @@
git placeholder file

View File

@ -0,0 +1,21 @@
BeforeAll {
$Repo = Get-RepoPath
Import-Module $Repo.Src.Manifest.Item.FullName -Force
}
Describe 'Get-RepoPath' {
Context 'Default tests' -Tag 'Default' {
It 'Test Function' {
{ Get-Command -Name 'Get-RepoPath' -Module $Repo.Artifact } | Should -Not -Throw
}
It 'Test Help' {
{ Get-Help -Name 'Get-RepoPath' } | Should -Not -Throw
}
It 'Help Content' {
$foo = Get-Help -Name 'Get-RepoPath'
$foo.Synopsis.Length | Should -BeGreaterThan 5
$foo.Description.Count | Should -BeGreaterOrEqual 1
$foo.Description[0].Text.Length | Should -BeGreaterThan 5
}
}
}

167
src/Helper/Get-RepoPath.ps1 Normal file
View File

@ -0,0 +1,167 @@
function Get-RepoPath {
<#
.SYNOPSIS
Updates the module manifest file fields to prepare the new build.
.DESCRIPTION
Replaces the version fields in the manifest file. Uses Drone env vars populated by pushed tags.
.Parameter SubPath
An optional string array of sub directories relative to the root.
.INPUTS
[None] No pipeline input.
.OUTPUTS
[DroneHelper.Repo.Path] Returns a folder structured like object with relevant full paths.s
.EXAMPLE
Import-Module -Name DroneHelper; Get-RepoPath
#>
[CmdletBinding()]
[OutputType('DroneHelper.Repo.Path')]
[Diagnostics.CodeAnalysis.SuppressMessageAttribute(
'PSUseConsistentWhitespace',
'',
Justification = 'Hashtable bug in ScriptAnalyzer 1.19.1'
)]
param (
[Parameter(Mandatory = $false)]
[ValidateNotNullOrEmpty()]
[String[]]$SubPath
)
process {
$root = Split-Path -Path (Get-GitDirectory)
$BaseName = Get-Item -Path ('{0}/src/*.psd1' -f $root) | Select-Object -ExpandProperty 'BaseName'
$failureLogPath = Join-Path -Path $root -ChildPath 'failure.log'
# *.psd1 related
$manifestPath = Join-Path -Path $root -ChildPath 'src/*.psd1'
$manifest = Get-Item -Path $manifestPath
# *.psm1 related
$scriptModulePath = Join-Path -Path $root -ChildPath 'src/*.psm1'
$scriptModule = Get-Item -Path $scriptModulePath
# Subdir related
$srcPath = Join-Path -Path $root -ChildPath 'src'
$binPath = Join-Path -Path $root -ChildPath 'bin'
$buildPath = Join-Path -Path $root -ChildPath 'build'
$resourcePath = Join-Path -Path $root -ChildPath 'resources'
# bin + build artifact related
$mergedScriptModulePath = Join-Path -Path $binPath -ChildPath $scriptModule.Name
$artifactName = '{0}.zip' -f $BaseName
$artifactPath = Join-Path -Path $binPath -ChildPath $artifactName
$expandPath = Join-Path -Path $binPath -ChildPath $BaseName
# iteration through the optional sub paths
$formatPath = Join-Path -Path $srcPath -ChildPath 'Formats/'
$cachePath = Join-Path -Path $srcPath -ChildPath 'Cache/'
$docsPath = Join-Path -Path $root -ChildPath 'docs'
$modulePagePath = Join-Path -Path $docsPath -ChildPath 'README.md'
$docsMarkdownFilter = Join-Path -Path $docsPath -ChildPath '*.md'
$subDir = @{}
foreach ($dir in $SubPath) {
$subDir.$dir = Join-Path -Path $root -ChildPath $dir
}
$changelogPath = Join-Path -Path $root -ChildPath 'CHANGELOG.md'
$changelogExits = Test-Path -Path $changelogPath
$ps1Filter = Join-Path -Path $srcPath -ChildPath '*.ps1'
$pesterLogPath = Join-Path -Path $buildPath -ChildPath 'Pester-Results.log'
$scriptAnalyzerLogPath = Join-Path -Path $buildPath -ChildPath 'ScriptAnalyzer-Results.log'
$fileLinterLogPath = Join-Path -Path $buildPath -ChildPath 'FileLinter-Results.log'
$scriptAnalyzerSettingsPath = Join-Path -Path $resourcePath -ChildPath 'PSScriptAnalyzerSettings.psd1'
# DroneHelper Module specific
$droneModuleBase = $MyInvocation.MyCommand.Module.ModuleBase
$PathParams = @{
Path = $droneModuleBase
ChildPath = 'Rules/PSScriptAnalyzerSettings.psd1'
}
$droneAnalyzerDefaultPath = Join-Path @PathParams
if ($changelogExits) {
$changelog = Get-Item -Path $changelogPath
}
else {
$changelog = $null
}
$Path = [PSCustomObject]@{
Artifact = $BaseName
Root = $root
Src = [PSCustomObject]@{
Path = $srcPath
Manifest = [PSCustomObject] @{
Path = $manifestPath
Item = $manifest
}
ScriptModule = [PSCustomObject]@{
Path = $scriptModulePath
Item = $scriptModule
}
Formats = [PSCustomObject]@{
Path = $formatPath
Exists = Test-Path -Path $formatPath
}
Cache = [PSCustomObject]@{
Path = $cachePath
Exists = Test-Path -Path $cachePath
}
PS1Filter = $ps1Filter
}
Bin = [PSCustomObject]@{
Path = $binPath
ScriptModuleName = $mergedScriptModulePath
ArtifactName = $artifactName
ArtifactPath = $artifactPath
ExpandPath = $expandPath
}
Build = [PSCustomObject]@{
Path = $buildPath
PesterLogPath = $pesterLogPath
ScriptAnalyzerLogPath = $scriptAnalyzerLogPath
FileLinterLogPath = $fileLinterLogPath
}
Changelog = [PSCustomObject]@{
Path = $changelogPath
Exists = $changelogExits
Item = $changelog
}
Docs = [PSCustomObject]@{
Path = $docsPath
ModulePagePath = $modulePagePath
MarkdownFilter = $docsMarkdownFilter
}
DroneHelper = [PSCustomObject]@{
ModuleBase = $MyInvocation.MyCommand.Module.ModuleBase
ScriptAnalyzerDefaultsPath = $droneAnalyzerDefaultPath
}
Resources = [PSCustomObject]@{
Path = $resourcePath
ScriptAnalyzerSettingsPath = $scriptAnalyzerSettingsPath
ScriptAnalyzerSettingsExist = Test-Path -Path $scriptAnalyzerSettingsPath
}
FailureLogPath = $failureLogPath
SubDir = $subDir
}
$Path.PSObject.TypeNames.Insert(0, 'DroneHelper.Repo.Path')
Write-Output -InputObject $Path
}
}

View File

@ -0,0 +1,21 @@
BeforeAll {
$Repo = Get-RepoPath
Import-Module $Repo.Src.Manifest.Item.FullName -Force
}
Describe 'Set-EOL' {
Context 'Default tests' -Tag 'Default' {
It 'Test Function' {
{ Get-Command -Name 'Set-EOL' -Module $Repo.Artifact } | Should -Not -Throw
}
It 'Test Help' {
{ Get-Help -Name 'Set-EOL' } | Should -Not -Throw
}
It 'Help Content' {
$foo = Get-Help -Name 'Set-EOL'
$foo.Synopsis.Length | Should -BeGreaterThan 5
$foo.Description.Count | Should -BeGreaterOrEqual 1
$foo.Description[0].Text.Length | Should -BeGreaterThan 5
}
}
}

59
src/Helper/Set-EOL.ps1 Normal file
View File

@ -0,0 +1,59 @@
function Set-EOL {
<#
.SYNOPSIS
Helper function to set the EOL sequence to LF or CRLF.
.DESCRIPTION
Helper for changing the EOL independent to the current OS defaults.
.PARAMETER Style
Optional style parameter for `unix` or `win.`. Default is `unix`.
.PARAMETER Path
Mandatory path for target file.
.INPUTS
[None] No pipeline input.
.OUTPUTS
[DroneHelper.Repo.Path] Returns a folder structured like object with relevant full paths.s
.EXAMPLE
Import-Module -Name DroneHelper; Set-EOL -Path './Readme.md'
#>
[CmdletBinding()]
[Diagnostics.CodeAnalysis.SuppressMessageAttribute(
'PSUseShouldProcessForStateChangingFunctions',
'',
Justification = 'system state does not change permanent in temp build clients.'
)]
param (
[Parameter(Mandatory = $false)]
[ValidateSet('unix', 'win')]
[String]$Style = 'unix',
[Parameter(Mandatory = $true)]
[System.IO.FileInfo]$Path
)
process {
if (!(Test-Path $Path.FullName)) {
Write-Error -Message ('{0} not found!' -f $Path.FullName) -ErrorAction Stop
}
switch ($Style) {
'unix' {
$eol = "`n"
Write-Verbose -Message ('Reading {0}' -f $Path.FullName)
$text = [IO.File]::ReadAllText($Path.FullName) -replace "`r`n", $eol
Write-Debug -Message $text
}
'win' {
$eol = "`r`n"
$text = [IO.File]::ReadAllText($Path.FullName) -replace "`n", $eol
}
}
Write-Verbose -Message ("Writing back {0}" -f $Path.FullName)
[IO.File]::WriteAllText($Path.FullName, $text)
}
}

6
src/ModuleRoot.psm1 Normal file
View File

@ -0,0 +1,6 @@
# Get all child items in the Script path and exclude the Deploy script (if present.)
$Functions = Get-ChildItem -Path $PSScriptRoot\*.ps1 -Recurse | Where-Object { $_.BaseName -notmatch '.Tests' }
ForEach ($Item in $Functions) {
. $Item.FullName
}

View File

@ -0,0 +1,41 @@
BeforeAll {
$Repo = Get-RepoPath
Import-Module $Repo.Src.Manifest.Item.FullName -Force
}
Describe 'Send-PRComment' {
Context 'Default tests' -Tag 'Default' {
It 'Test Function' {
{ Get-Command -Name 'Send-PRComment' -Module $Repo.Artifact } | Should -Not -Throw
}
It 'Test Help' {
{ Get-Help -Name 'Send-PRComment' } | Should -Not -Throw
}
It 'Help Content' {
$foo = Get-Help -Name 'Send-PRComment'
$foo.Synopsis.Length | Should -BeGreaterThan 5
$foo.Description.Count | Should -BeGreaterOrEqual 1
$foo.Description[0].Text.Length | Should -BeGreaterThan 5
}
}
Context 'Unit tests' -Tag 'Unit' {
It 'Should throw' {
{ Send-PRComment -foo } | Should -Throw
}
#
#It 'Should not throw' {
#Mock 'FunctionName' -ModuleName $Repo.Artifact {}
# { Send-PRComment } | Should -Not -Throw
#}
}
<#
Context 'Integration tests' -Tag 'Integration' {
It 'Real world example' {
{ Send-PRComment } | Should -Should -Be $null
}
}
#>
}

View File

@ -0,0 +1,173 @@
function Send-PRComment {
<#
.SYNOPSIS
Sends build report as Gitea PR comment.
.DESCRIPTION
Send-PRComment is used to report the build details from drone.io pipeline.
.PARAMETER Mode
Sets the report mode. Default is 'Renew'. This mode deletes the old pr comments and creates a new onw.
Also available:
- 'Add' -> simply adds new pr comments.
- 'Edit' -> Edits the last known pr comment. Doesn't clean old ones.
.INPUTS
[None].
.OUTPUTS
[None]
.EXAMPLE
Send-PRComment
Depends on Drone.IO injected environment vars. Doesn't work locally on dev systems.
.NOTES
#>
[CmdletBinding()]
#[OutputType([string])]
param (
[Parameter(Mandatory = $false, HelpMessage = 'HelpMessage')]
[ValidateSet('Add', 'Edit', 'Renew')]
[string]$Mode = 'Renew'
)
begin {
}
process {
$Repo = Get-RepoPath
$Workspace = $Repo.Root
Write-Debug -Message ('Workspace: {0}' -f $Workspace)
$PRCommentFile = Join-Path -Path $Workspace -ChildPath 'pr_comment.log'
Write-Debug -Message ('PRCommentFile: {0}' -f $PRCommentFile)
$PipelineStateFile = Join-Path -Path $Workspace -ChildPath 'failure.log'
Write-Debug -Message ('PipelineStateFile: {0}' -f $PipelineStateFile)
Write-Debug -Message ('CUSTOM_PIPELINE_STATE: {0}' -f $Env:CUSTOM_PIPELINE_STATE)
if ($Env:CUSTOM_PIPELINE_STATE -eq $true) {
if (Test-Path $PipelineStateFile) {
Write-Debug -Message ('Setting custom pipeline status to failed')
$PipelineState = 'failed'
}
else {
Write-Debug -Message ('Setting custom pipeline status to success')
$PipelineState = 'success'
}
}
else {
Write-Debug -Message ('Setting global drone status {0}' -f $Env:DRONE_BUILD_STATUS)
$PipelineState = $Env:DRONE_BUILD_STATUS
}
if ($Env:GITEA_BASE) {
$GiteaBase = $Env:GITEA_BASE
}
else {
$GiteaBase = 'https://gitea.ocram85.com'
}
$APIHeaders = @{
accept = 'application/json'
'Content-Type' = 'application/json'
}
# Can be used with POST method to add new comment. Used with GET method returns all comments.
$CommentAPICall = ('{0}/api/v1/repos/{1}/{2}/issues/{3}/comments?access_token={4}' -f
$GiteaBase,
$Env:DRONE_REPO_OWNER,
$Env:DRONE_REPO_NAME,
$Env:DRONE_PULL_REQUEST,
$Env:GITEA_TOKEN
)
# Update Comment API endpoint: 0 - GiteaBase, 1 - Owner, 2- Repo, 3 - PR, 4 - Token
# Method Delete - removes the given comment. Patch - updates the given comment.
$UpdateAPICall = '{0}/api/v1/repos/{1}/{2}/issues/comments/{3}?access_token={4}'
if ($Mode -eq 'Renew') {
$Comments = Invoke-RestMethod -Method 'Get' -Headers $APIHeaders -Uri $CommentAPICall
$DroneComments = $Comments | Where-Object {
$_.user.login -eq 'drone'
} | Select-Object -ExpandProperty 'id'
Write-Debug -Message ('Found Drone comments: {0}.' -f ($DroneComments -join ', '))
foreach ($id in $DroneComments) {
$ExtAPI = $UpdateAPICall -f @(
$GiteaBase,
$Env:DRONE_REPO_OWNER,
$Env:DRONE_REPO_NAME,
$id,
$Env:GITEA_TOKEN
)
Write-Debug -Message ('Exec API Call: {0}' -f $ExtAPI)
Invoke-RestMethod -Method 'Delete' -Headers $APIHeaders -Uri $ExtAPI
}
}
if ($Mode -eq 'Edit') {
$Comments = Invoke-RestMethod -Method 'Get' -Headers $APIHeaders -Uri $CommentAPICall
$DroneComments = $Comments | Where-Object {
$_.user.login -eq 'drone'
} | Select-Object -ExpandProperty 'id'
Write-Debug -Message ('Found Drone comments: {0}.' -f ($DroneComments -join ', '))
$EditId = $DroneComments | Sort-Object | Select-Object -Last 1
Write-Debug -Message ('Edit Comment with id {0}' -f $EditId)
}
$PRCommentHeader = ('> Drone.io PR Build No. [#{0}]({1}://{2}/{3}/{4}): ``{5}``' -f
$Env:DRONE_BUILD_NUMBER,
$Env:DRONE_SYSTEM_PROTO,
$Env:DRONE_SYSTEM_HOST,
$Env:DRONE_REPO,
$Env:DRONE_BUILD_NUMBER,
$PipelineState
)
$PRCommentHeader | Out-File -FilePath $PRCommentFile -Encoding 'utf8'
$LogFiles = (Get-ChildItem -Path $Env:LOG_FILES -File).FullName
foreach ($file in $LogFiles) {
if (Test-Path -Path $file) {
('#### ``{0}``' -f $file) | Out-File -FilePath $PRCommentFile -Append -Encoding 'utf8'
$fileContent = Get-Content -Path $file -Raw -Encoding utf8
$fileContent | Out-File -FilePath $PRCommentFile -Append -Encoding 'utf8'
[Environment]::NewLine | Out-File -FilePath $PRCommentFile -Append -Encoding 'utf8' -NoNewline
}
else {
Write-Warning -Message ('Given file {0} not found!' -f $file)
('##### ``{0}`` not found!' -f $file) | Out-File -FilePath $PRCommentFile -Append -Encoding 'utf8'
}
}
if ($Mode -eq 'Edit') {
'Last mod: {0}' -f (Get-Date -Format 'u') | Out-File -FilePath $PRCommentFile -Append -Encoding 'utf8'
}
'end.' | Out-File -FilePath $PRCommentFile -Append -Encoding 'utf8'
$PRCommentJSON = ConvertTo-Json -InputObject @{
Body = Get-Content -Path $PRCommentFile -Encoding utf8 -Raw
}
Write-Debug -Message ('PR JSON body has a size of {0} chars' -f $PRCommentJSON.length)
if ($Mode -ne 'Edit') {
Write-Debug -Message 'Adding new Comment.'
Invoke-RestMethod -Method 'Post' -Headers $APIHeaders -Uri $CommentAPICall -Body $PRCommentJSON
}
else {
$ExtAPI = $UpdateAPICall -f @(
$GiteaBase,
$Env:DRONE_REPO_OWNER,
$Env:DRONE_REPO_NAME,
$EditId,
$Env:GITEA_TOKEN
)
Write-Debug -Message 'Edit last comment.'
Invoke-RestMethod -Method 'Patch' -Headers $APIHeaders -Uri $ExtAPI -Body $PRCommentJSON
}
}
end {
}
}

14
src/PSModule.Tests.ps1 Normal file
View File

@ -0,0 +1,14 @@
Describe 'Test module meta' {
It 'Test manifest file' {
$ManifestFile = (Get-Item -Path "./src/*.psd1").FullName
Test-ModuleManifest -Path $ManifestFile | Should -Be $true
}
It 'Import Module' {
$ManifestFile = (Get-Item -Path "./src/*.psd1").FullName
{ Import-Module $ManifestFile } | Should -Not -Throw
}
# Dummy test to force pester error
#It 'Force Pester Error' {
# $true | Should -BeFalse
#}
}

View File

@ -0,0 +1,21 @@
BeforeAll {
$Repo = Get-RepoPath
Import-Module $Repo.Src.Manifest.Item.FullName -Force
}
Describe 'Invoke-Linter' {
Context 'Default tests' -Tag 'Default' {
It 'Test Function' {
{ Get-Command -Name 'Invoke-Linter' -Module $Repo.Artifact } | Should -Not -Throw
}
It 'Test Help' {
{ Get-Help -Name 'Invoke-Linter' } | Should -Not -Throw
}
It 'Help Content' {
$foo = Get-Help -Name 'Invoke-Linter'
$foo.Synopsis.Length | Should -BeGreaterThan 5
$foo.Description.Count | Should -BeGreaterOrEqual 1
$foo.Description[0].Text.Length | Should -BeGreaterThan 5
}
}
}

View File

@ -0,0 +1,71 @@
function Invoke-Linter() {
<#
.SYNOPSIS
Runs all PSScriptAnalyzer Rules within this repo.
.DESCRIPTION
This Cmdlet is used in Drone pipeline to run the PSScriptAnalyzer rules..
.INPUTS
[None] No pipeline input.
.OUTPUTS
[None] No pipeline output.
.EXAMPLE
Invoke-Linter
#>
[CmdletBinding()]
[Diagnostics.CodeAnalysis.SuppressMessageAttribute(
'PSUseConsistentWhitespace',
'',
Justification = 'Hashtable bug in ScriptAnalyzer 1.19.1'
)]
param()
process {
$Repo = Get-RepoPath
# Use repo local defaults. if not present use the DroneHelper included as defaults.
if ($Repo.Resources.ScriptAnalyzerSettingsExist) {
$currentRules = $Repo.Resources.ScriptAnalyzerSettingsPath
}
else {
$currentRules = $repo.DroneHelper.ScriptAnalyzerDefaultsPath
}
$AnalyzerParams = @{
Path = $Repo.Src.Path
Recurse = $true
Settings = $currentRules
Verbose = $VerbosePreference
ReportSummary = $true
}
$AnalyzerResults = Invoke-ScriptAnalyzer @AnalyzerParams
if ( $AnalyzerResults ) {
$AnalyzerResults | Sort-Object -Property @(
"ScriptName",
"Line"
) | Format-Table @(
"Severity",
"ScriptName",
"Line",
"RuleName",
"Message"
) -AutoSize | Out-String | Write-Verbose -Verbose
$ResultParams = @{
Type = 'PSScriptAnalyzer'
Path = $Repo.Build.ScriptAnalyzerLogPath
InputObject = $AnalyzerResults
}
Write-ResultFile @ResultParams
Write-FailureStateFile -StepName 'PSScriptAnalyzer'
throw 'PS Script Analyzer failed!'
}
else {
$ResultParams = @{
Type = 'Custom'
Path = $Repo.Build.ScriptAnalyzerLogPath
InputObject = ':heavy_check_mark: No violations found.'
}
Write-ResultFile @ResultParams
}
}
}

View File

@ -0,0 +1,21 @@
BeforeAll {
$Repo = Get-RepoPath
Import-Module $Repo.Src.Manifest.Item.FullName -Force
}
Describe 'Invoke-UnitTest' {
Context 'Default tests' -Tag 'Default' {
It 'Test Function' {
{ Get-Command -Name 'Invoke-UnitTest' -Module $Repo.Artifact } | Should -Not -Throw
}
It 'Test Help' {
{ Get-Help -Name 'Invoke-UnitTest' } | Should -Not -Throw
}
It 'Help Content' {
$foo = Get-Help -Name 'Invoke-UnitTest'
$foo.Synopsis.Length | Should -BeGreaterThan 5
$foo.Description.Count | Should -BeGreaterOrEqual 1
$foo.Description[0].Text.Length | Should -BeGreaterThan 5
}
}
}

View File

@ -0,0 +1,110 @@
function Invoke-UnitTest {
<#
.SYNOPSIS
Runs all Pester tests within this repo.
.DESCRIPTION
This Cmdlet is used in Drone pipeline to perform the Pester based unit tests.
.PARAMETER CoverageFormat
Pester provides the formats JaCoCo ans CoverageGutters. Default is JaCoCo.
These are the known use cases:
- JaCoCo -> Used as standard coverage report used by sonar
- CoverageGutters -> Custom Format to show coverage in VSCode.
.PARAMETER Verbosity
This parameter sets the Pester detail level. Default is 'Normal.' Available values are:
'None', 'Normal', 'Detailed', 'Diagnostic'
.PARAMETER PassThru
Tells Invoke-UnitTest to write back the Pester results into your variable / output.
.PARAMETER Tag
Pester build in tag filter as string array.
.PARAMETER ExcludeTag
Pester build in exclude filter for tests as string array.
.INPUTS
[None] No pipeline input.
.OUTPUTS
[None] No pipeline output.
.EXAMPLE
Invoke-UnitTest
#>
[CmdletBinding()]
[Diagnostics.CodeAnalysis.SuppressMessageAttribute(
'PSUseConsistentWhitespace',
'',
Justification = 'Hashtable bug in ScriptAnalyzer 1.19.1'
)]
param (
[Parameter( Mandatory = $false )]
[ValidateSet('JaCoCo', 'CoverageGutters')]
[string]$CoverageFormat = 'JaCoCo',
[Parameter(Mandatory = $false)]
[ValidateSet('None', 'Normal', 'Detailed', 'Diagnostic')]
[string]$Verbosity = 'Normal',
[Parameter(Mandatory = $false)]
[switch]$PassThru,
[Parameter(Mandatory = $false)]
[ValidateNotNullOrEmpty()]
[string[]]$Tag,
[Parameter(Mandatory = $false)]
[ValidateNotNullOrEmpty()]
[string[]]$ExcludeTag
)
process {
$Repo = Get-RepoPath
Write-Verbose -Message '===== Running Pester =====' -Verbose:$VerbosePreference
$PesterConf = New-PesterConfiguration
$PesterConf.Run.Path = (Resolve-Path -Path './src').Path
$PesterConf.Run.Exit = $false
$PesterConf.Run.PassThru = $true
$PesterConf.CodeCoverage.Enabled = $true
$PesterConf.CodeCoverage.OutputFormat = $CoverageFormat
$PesterConf.TestResult.Enabled = $true
$CovFiles = Get-ChildItem -Path $Repo.Src.PS1Filter -Recurse | Where-Object {
$_.BaseName -notmatch '.Tests'
} | Select-Object -ExpandProperty 'FullName'
$PesterConf.CodeCoverage.Path = $CovFiles
$PesterConf.Output.Verbosity = $Verbosity
# Set Tags if given
if ($Tag) {
$PesterConf.Filter.Tag = $Tag
}
if ($ExcludeTag) {
$PesterConf.Filter.ExcludeTag = $ExcludeTag
}
$TestResults = Invoke-Pester -Configuration $PesterConf -ErrorAction 'Stop'
try {
$ResFileParams = @{
InputObject = $TestResults
Path = $Repo.Build.PesterLogPath
Type = 'Pester'
ErrorAction = 'Stop'
}
Write-ResultFile @ResFileParams
}
catch {
Write-FailureStateFile -StepName 'Pester'
throw ('{0} tests failed!' -f $TestResults.FailedCount)
}
if ($TestResults.FailedCount -gt 0) {
Write-FailureStateFile -StepName 'Pester'
throw ('{0} tests failed!' -f $TestResults.FailedCount)
}
if ($PassThru.IsPresent) {
Write-Output -InputObject $TestResults
}
}
}

View File

@ -0,0 +1,43 @@
function Format-FileLinterReport {
<#
.SYNOPSIS
Private helper function used by Write-ResultFile.
#>
[CmdletBinding()]
[OutputType([string])]
param (
[Parameter(Mandatory = $true)]
[ValidateNotNullOrEmpty()]
[PSTypeName('DroneHelper.FileLinter.Report')]$InputObject
)
begin {
}
process {
$Output = @()
if ($InputObject.Success) {
$Output += ':heavy_check_mark: No FileLinter violations in {0} files found.' -f $InputObject.FilesCount
}
else {
$Output += "| Result | File | Failed |"
$Output += "| :----: | :--- | -----: |"
foreach ($file in $InputObject.Files) {
if ($file.FailedCount -gt 0) {
$failedTestNames = (
$file.Tests.GetEnumerator() | Where-Object {
$_.Value -eq $false
} | Select-Object -ExpandProperty 'Name'
) -join ', '
$Output += "| :heavy_exclamation_mark: | ``{0}`` | ``{1}`` |" -f $file.Name, $failedTestNames
}
}
}
Write-Output $Output
}
end {
}
}

View File

@ -0,0 +1,74 @@
function Format-PesterReport {
<#
.SYNOPSIS
Private helper function used by Write-ResultFile.
#>
[CmdletBinding()]
[OutputType([string])]
param (
[Parameter(Mandatory = $true)]
[PSCustomObject]$InputObject,
[Parameter(Mandatory = $false)]
[ValidateSet('Normal', 'Detailed')]
[string]$Verbosity = 'Normal'
)
begin {
}
process {
$Output = @()
$Output += "| Result | Test | Duration |"
$Output += "| :----: | :--- | -------: |"
foreach ($Result in $InputObject.Tests) {
switch ($Result.Result) {
'Passed' {
if ($Verbosity -eq 'Detailed') {
$RawString = "| :heavy_check_mark: | ``{0}`` | *{1}ms* |"
$Output += $RawString -f $Result.ExpandedPath, $Result.UserDuration.Milliseconds
}
}
'Failed' {
$RawString = "| :heavy_exclamation_mark: | ``{0}`` | *{1}ms* |"
$Output += $RawString -f $Result.ExpandedPath, $Result.UserDuration.Milliseconds
$Parsed = $Result.ErrorRecord.Exception.Message -split "`n" | Select-Object -First 1
$Output += "| :fire: | **{0}** | :fire: |" -f $Parsed
}
'NotRun' {
$RawString = "| :trident: | ``{0}`` | *n/a* |"
$Output += $RawString -f $Result.ExpandedPath
}
Default {
$RawString = "| :warning: | ``{0}`` | *{1}ms* |"
$Output += $RawString -f $Result.ExpandedPath, $Result.UserDuration.Milliseconds
}
}
}
$Output += [Environment]::NewLine
# Writing test result summary
$Output += @(
':test_tube: **{0}** Total Tests (' -f $InputObject.TotalCount +
':heavy_check_mark: ``{0} Passed`` :white_small_square:' -f $InputObject.PassedCount +
':trident: ``{0} Skipped / NotRun`` :white_small_square: ' -f (
$InputObject.SkippedCount + $InputObject.NotRunCount
) +
':warning: ``Unknown`` :white_small_square: ' +
':heavy_exclamation_mark: ``{0} Failed``)' -f $InputObject.FailedCount
)
# Writing code coverage summary
# Covered 37,38% / 75%. 610 analyzed Commands in 26 Files.
$Output += @(
':bookmark_tabs: Covered **{0}%** / ' -f [Math]::Round($InputObject.CodeCoverage.CoveragePercent, 2) +
'{0}%. (' -f $InputObject.CodeCoverage.CoveragePercentTarget +
':bookmark: ``{0} analyzed Commands`` ' -f $InputObject.CodeCoverage.CommandsAnalyzedCount +
':page_facing_up: ``in {0} Files``)' -f $InputObject.CodeCoverage.FilesAnalyzedCount
)
Write-Output $Output
}
end {
}
}

View File

@ -0,0 +1,40 @@
function Format-ScriptAnalyzerReport {
<#
.SYNOPSIS
Private helper function used by Write-ResultFile.
#>
[CmdletBinding()]
[OutputType([string])]
param (
[Parameter(Mandatory = $true)]
[PSCustomObject]$InputObject
)
begin {
}
process {
$Output = @()
$Output += "| Severity | ScriptName | Line | RuleName | Message |"
$Output += "| :------: | :--------- | :--: | :------- | :------ |"
foreach ( $v in $InputObject ) {
switch ($v.Severity) {
'Warning' { $Emoji = ':warning:' }
'Error' { $Emoji = ':heavy_exclamation_mark:' }
'Information' { $Emoji = ':mag:' }
Default { $Emoji = ':fried_egg:' }
}
$RawString = "| {0} | {1} | {2} | {3} | {4} |"
$Output += $RawString -f $Emoji, $v.ScriptName, $v.Line, $v.RuleName, $v.Message
}
$RuleURL = 'https://github.com/PowerShell/PSScriptAnalyzer/tree/master/RuleDocumentation'
$Output += "`n> See [RuleDocumentation]({0}) for additional help.`n" -f $RuleURL
Write-Output $Output
}
end {
}
}

View File

@ -0,0 +1,140 @@
@{
Severity = 'Error', 'Warning', 'Information'
IncludeRules = @(
'PSAlignAssignmentStatement',
'PSAvoidAssignmentToAutomaticVariable',
'PSAvoidDefaultValueForMandatoryParameter',
'PSAvoidDefaultValueSwitchParameter',
'PSAvoidGlobalAliases',
'PSAvoidGlobalFunctions',
'PSAvoidGlobalVars',
'PSAvoidInvokingEmptyMembers',
'PSAvoidLongLines',
'PSAvoidNullOrEmptyHelpMessageAttribute',
'PSAvoidOverwritingBuiltInCmdlets',
'PSAvoidShouldContinueWithoutForce',
'PSAvoidTrailingWhitespace',
'PSAvoidUsingCmdletAliases',
'PSAvoidUsingComputerNameHardcoded',
'PSAvoidUsingConvertToSecureStringWithPlainText',
'PSAvoidUsingDeprecatedManifestFields',
'PSAvoidUsingDoubleQuotesForConstantString',
'PSAvoidUsingEmptyCatchBlock',
'PSAvoidUsingInvokeExpression',
'PSAvoidUsingPlainTextForPassword',
'PSAvoidUsingPositionalParameters',
'PSAvoidUsingUsernameAndPasswordParams',
'PSAvoidUsingWMICmdlet',
'PSAvoidUsingWriteHost',
'PSMisleadingBacktick',
'PSMissingModuleManifestField',
'PSPlaceCloseBrace',
'PSPlaceOpenBrace',
'PSPossibleIncorrectComparisonWithNull',
'PSPossibleIncorrectUsageOfAssignmentOperator',
'PSPossibleIncorrectUsageOfRedirectionOperator',
'PSProvideCommentHelp',
'PSReservedCmdletChar',
'PSReservedParams',
'PSReviewUnusedParameter',
'PSShouldProcess',
'PSUseApprovedVerbs',
'PSUseBOMForUnicodeEncodedFile',
'PSUseCmdletCorrectly',
# There is no predefined set for Pwsh7 Cmdlets
'PSUseCompatibleCmdlets',
#'PSUseCompatibleCommands',
'PSUseCompatibleSyntax',
#'PSUseCompatibleTypes',
'PSUseConsistentIndentation',
# Disable if bug in 1.19.1 version occurs.
'PSUseConsistentWhitespace',
'PSUseCorrectCasing',
'PSUseDeclaredVarsMoreThanAssignments',
'PSUseLiteralInitializerForHashtable',
'PSUseOutputTypeCorrectly',
'PSUsePSCredentialType',
'PSUseProcessBlockForPipelineCommand',
'PSUseShouldProcessForStateChangingFunctions',
'PSUseSingularNouns',
'PSUseSupportsShouldProcess',
'PSUseToExportFieldsInManifest',
'PSUseUTF8EncodingForHelpFile',
'PSUseUsingScopeModifierInNewRunspaces'
)
Rules = @{
PSAvoidLongLines = @{
Enable = $true
MaximumLineLength = 116
}
PSPlaceOpenBrace = @{
Enable = $true
OnSameLine = $true
NewLineAfter = $true
IgnoreOneLineBlock = $true
}
PSPlaceCloseBrace = @{
Enable = $true
NewLineAfter = $true
IgnoreOneLineBlock = $true
NoEmptyLineBefore = $false
}
PSProvideCommentHelp = @{
Enable = $true
ExportedOnly = $false
BlockComment = $true
VSCodeSnippetCorrection = $false
Placement = "begin"
}
PSUseCompatibleCmdlets = @{
compatibility = @(
"desktop-5.1.14393.206-windows",
"core-6.1.0-windows"
)
}
PSUseCompatibleSyntax = @{
Enable = $true
TargetVersions = @(
"7.0",
"5.1"
)
}
PSUseConsistentIndentation = @{
Enable = $true
Kind = 'space'
PipelineIndentation = 'IncreaseIndentationForFirstPipeline'
IndentationSize = 4
}
PSUseConsistentWhitespace = @{
Enable = $true
CheckInnerBrace = $true
CheckOpenBrace = $true
CheckOpenParen = $true
CheckOperator = $true
CheckPipe = $true
CheckPipeForRedundantWhitespace = $false
CheckSeparator = $true
CheckParameter = $false
IgnoreAssignmentOperatorInsideHashTable = $true
}
PSAlignAssignmentStatement = @{
Enable = $true
CheckHashtable = $false
}
PSUseCorrectCasing = @{
Enable = $true
}
}
}

View File

@ -0,0 +1,27 @@
function Invoke-BuildState {
<#
.SYNOPSIS
Sets final Drone pipeline build state.
.DESCRIPTION
Marks the pipeline ass succeeded of fail based on the custom state file.
.INPUTS
[None] No pipeline input.
.OUTPUTS
[None] No pipeline output.
.EXAMPLE
Invoke-BuildState
#>
[CmdletBinding()]
param()
process {
$Repo = Get-RepoPath
if ( Test-Path -Path $Repo.FailureLogPath ) {
throw 'One one more pipeline steps failed. Marking the pipeline as failed!'
}
}
}

View File

@ -0,0 +1,21 @@
BeforeAll {
$Repo = Get-RepoPath
Import-Module $Repo.Src.Manifest.Item.FullName -Force
}
Describe 'Invoke-BuildState' {
Context 'Default tests' -Tag 'Default' {
It 'Test Function' {
{ Get-Command -Name 'Invoke-BuildState' -Module $Repo.Artifact } | Should -Not -Throw
}
It 'Test Help' {
{ Get-Help -Name 'Invoke-BuildState' } | Should -Not -Throw
}
It 'Help Content' {
$foo = Get-Help -Name 'Invoke-BuildState'
$foo.Synopsis.Length | Should -BeGreaterThan 5
$foo.Description.Count | Should -BeGreaterOrEqual 1
$foo.Description[0].Text.Length | Should -BeGreaterThan 5
}
}
}

View File

@ -0,0 +1,21 @@
BeforeAll {
$Repo = Get-RepoPath
Import-Module $Repo.Src.Manifest.Item.FullName -Force
}
Describe 'Write-FailureStateFile' {
Context 'Default tests' -Tag 'Default' {
It 'Test Function' {
{ Get-Command -Name 'Write-FailureStateFile' -Module $Repo.Artifact } | Should -Not -Throw
}
It 'Test Help' {
{ Get-Help -Name 'Write-FailureStateFile' } | Should -Not -Throw
}
It 'Help Content' {
$foo = Get-Help -Name 'Write-FailureStateFile'
$foo.Synopsis.Length | Should -BeGreaterThan 5
$foo.Description.Count | Should -BeGreaterOrEqual 1
$foo.Description[0].Text.Length | Should -BeGreaterThan 5
}
}
}

View File

@ -0,0 +1,48 @@
function Write-FailureStateFile() {
<#
.SYNOPSIS
Writes the current pipeline step into failure log.
.DESCRIPTION
This Cmdlet is used to mark single steps as failed without stopping the complete pipeline.
.PARAMETER StepName
The current DroneHelper step name which should be added into to the log.
.INPUTS
[None] No pipeline input.
.OUTPUTS
[None] No pipeline output.
.EXAMPLE
Write-FailureStateFile
#>
[CmdletBinding()]
[Diagnostics.CodeAnalysis.SuppressMessageAttribute(
'PSUseConsistentWhitespace',
'',
Justification = 'justification'
)]
param (
[Parameter(Mandatory = $true)]
[string]$StepName
)
process {
$Repo = Get-RepoPath
$WriteParams = @{
FilePath = $Repo.FailureLogPath
Encoding = 'utf8'
NoClobber = $true
Force = $true
InputObject = $StepName
}
if ( Test-Path -Path $Repo.FailureLogPath ) {
$WriteParams.Append = $true
}
Out-File @WriteParams
}
}

View File

@ -0,0 +1,21 @@
BeforeAll {
$Repo = Get-RepoPath
Import-Module $Repo.Src.Manifest.Item.FullName -Force
}
Describe 'Write-ResultFile' {
Context 'Default tests' -Tag 'Default' {
It 'Test Function' {
{ Get-Command -Name 'Write-ResultFile' -Module $Repo.Artifact } | Should -Not -Throw
}
It 'Test Help' {
{ Get-Help -Name 'Write-ResultFile' } | Should -Not -Throw
}
It 'Help Content' {
$foo = Get-Help -Name 'Write-ResultFile'
$foo.Synopsis.Length | Should -BeGreaterThan 5
$foo.Description.Count | Should -BeGreaterOrEqual 1
$foo.Description[0].Text.Length | Should -BeGreaterThan 5
}
}
}

View File

@ -0,0 +1,59 @@
function Write-ResultFile {
<#
.SYNOPSIS
Writes the current pipeline step into failure log.
.DESCRIPTION
This Cmdlet is used to mark single steps as failed without stopping the complete pipeline.
.PARAMETER StepName
The current DroneHelper step name which should be added into to the log.
.INPUTS
[None] No pipeline input.
.OUTPUTS
[None] No pipeline output.
.EXAMPLE
Write-FailureStateFile
#>
[CmdletBinding()]
param(
[Parameter(Mandatory = $true)]
[PSCustomObject]$InputObject,
[Parameter(Mandatory = $true)]
[String]$Path,
[Parameter(Mandatory = $true)]
[ValidateSet('Pester', 'PSScriptAnalyzer', 'FileLinter', 'Custom')]
[String]$Type
)
process {
[String[]]$Output = @()
if ($BlockDescription -ne "") {
$BlockDescription | Out-File -FilePath $Path -Encoding utf8 -Force -NoClobber -Append
}
switch ($Type) {
'Pester' {
$Output = Format-PesterReport -InputObject $InputObject
}
'PSScriptAnalyzer' {
$Output = Format-ScriptAnalyzerReport -InputObject $InputObject
}
'FileLinter' {
$Output = Format-FileLinterReport -InputObject $InputObject
}
'Custom' {
# nothing to do here
$Output = $InputObject + [Environment]::NewLine
}
}
$Output | Out-File -FilePath $Path -Encoding utf8 -Force -NoClobber -Append
}
}