From f639b51f9808d594448dbc8aaa257e6b6260698c Mon Sep 17 00:00:00 2001 From: Luke Evans Date: Thu, 23 Jul 2026 22:30:09 +0100 Subject: [PATCH 1/3] chore: renormalize line endings to LF per .gitattributes MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Commit 12b623b added .gitattributes declaring `* text=auto eol=lf`, but the existing files were never renormalized — their stored blobs kept CRLF. Git therefore flagged all 28 as modified on every clone/pull/status, because the working tree (CRLF) no longer matched the declared policy (LF). `git add --renormalize .` rewrites the blobs to LF so they match. Content is unchanged: the staged diff has equal insertions and deletions, and `git diff --ignore-cr-at-eol` is empty — this is line endings only. Stops the phantom-modified state recurring for every consumer of the repo. Claude-Session: https://claude.ai/code/session_01RmUNAvT3AenY6hrt4D9GAx --- .github/CODE_OF_CONDUCT.md | 150 +-- .github/CONTRIBUTING.md | 46 +- .github/ISSUE_TEMPLATE/bug-report.md | 78 +- .github/ISSUE_TEMPLATE/feature_request.md | 40 +- .github/PULL_REQUEST_TEMPLATE.md | 26 +- .github/SECURITY.md | 34 +- .gitignore | 56 +- .vscode/extensions.json | 16 +- .vscode/settings.json | 16 +- .vscode/tasks.json | 536 ++++----- LICENSE | 40 +- README.md | 142 +-- actions_bootstrap.ps1 | 124 +- docs/MIGRATION.md | 416 +++---- docs/docs/Get-RedditOAuthToken.md | 92 +- docs/docs/Get-RedditSubredditPost.md | 442 +++---- docs/docs/Get-RedditUserPost.md | 436 +++---- docs/docs/assets/stylesheets/custom.css | 28 +- docs/docs/module-manifest.md | 40 +- docs/package.json | 28 +- src/MarkdownRepair.ps1 | 272 ++--- src/PSReddit.Settings.ps1 | 4 +- src/PSReddit.build.ps1 | 1236 ++++++++++---------- src/PSReddit/Imports.ps1 | 12 +- src/PSReddit/PSReddit.psm1 | 62 +- src/PSScriptAnalyzerSettings.psd1 | 122 +- src/Tests/Unit/ExportedFunctions.Tests.ps1 | 308 ++--- src/Tests/Unit/PSReddit-Module.Tests.ps1 | 96 +- 28 files changed, 2449 insertions(+), 2449 deletions(-) diff --git a/.github/CODE_OF_CONDUCT.md b/.github/CODE_OF_CONDUCT.md index a862cd4..7f8bc3d 100644 --- a/.github/CODE_OF_CONDUCT.md +++ b/.github/CODE_OF_CONDUCT.md @@ -1,75 +1,75 @@ -# Contributor Covenant Code of Conduct - -## Our Pledge - -In the interest of fostering an open and welcoming environment, we as -contributors and maintainers pledge to making participation in our project and -our community a harassment-free experience for everyone, regardless of age, body -size, disability, ethnicity, sex characteristics, gender identity and expression, -level of experience, education, socio-economic status, nationality, personal -appearance, race, religion, or sexual identity and orientation. - -## Our Standards - -Examples of behavior that contributes to creating a positive environment -include: - -* Using welcoming and inclusive language -* Being respectful of differing viewpoints and experiences -* Gracefully accepting constructive criticism -* Focusing on what is best for the community -* Showing empathy towards other community members - -Examples of unacceptable behavior by participants include: - -* The use of sexualized language or imagery and unwelcome sexual attention or - advances -* Trolling, insulting/derogatory comments, and personal or political attacks -* Public or private harassment -* Publishing others' private information, such as a physical or electronic - address, without explicit permission -* Other conduct which could reasonably be considered inappropriate in a - professional setting - -## Our Responsibilities - -Project maintainers are responsible for clarifying the standards of acceptable -behavior and are expected to take appropriate and fair corrective action in -response to any instances of unacceptable behavior. - -Project maintainers have the right and responsibility to remove, edit, or -reject comments, commits, code, wiki edits, issues, and other contributions -that are not aligned to this Code of Conduct, or to ban temporarily or -permanently any contributor for other behaviors that they deem inappropriate, -threatening, offensive, or harmful. - -## Scope - -This Code of Conduct applies both within project spaces and in public spaces -when an individual is representing the project or its community. Examples of -representing a project or community include using an official project e-mail -address, posting via an official social media account, or acting as an appointed -representative at an online or offline event. Representation of a project may be -further defined and clarified by project maintainers. - -## Enforcement - -Instances of abusive, harassing, or otherwise unacceptable behavior may be -reported by contacting the project team at **[EMAIL REQUIRED]**. All -complaints will be reviewed and investigated and will result in a response that -is deemed necessary and appropriate to the circumstances. The project team is -obligated to maintain confidentiality with regard to the reporter of an incident. -Further details of specific enforcement policies may be posted separately. - -Project maintainers who do not follow or enforce the Code of Conduct in good -faith may face temporary or permanent repercussions as determined by other -members of the project's leadership. - -## Attribution - -This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, -available at [https://www.contributor-covenant.org/version/1/4/code-of-conduct.html](https://www.contributor-covenant.org/version/1/4/code-of-conduct.html) - -[homepage]: https://www.contributor-covenant.org - -For answers to common questions about this code of conduct, see [https://www.contributor-covenant.org/faq](https://www.contributor-covenant.org/faq) +# Contributor Covenant Code of Conduct + +## Our Pledge + +In the interest of fostering an open and welcoming environment, we as +contributors and maintainers pledge to making participation in our project and +our community a harassment-free experience for everyone, regardless of age, body +size, disability, ethnicity, sex characteristics, gender identity and expression, +level of experience, education, socio-economic status, nationality, personal +appearance, race, religion, or sexual identity and orientation. + +## Our Standards + +Examples of behavior that contributes to creating a positive environment +include: + +* Using welcoming and inclusive language +* Being respectful of differing viewpoints and experiences +* Gracefully accepting constructive criticism +* Focusing on what is best for the community +* Showing empathy towards other community members + +Examples of unacceptable behavior by participants include: + +* The use of sexualized language or imagery and unwelcome sexual attention or + advances +* Trolling, insulting/derogatory comments, and personal or political attacks +* Public or private harassment +* Publishing others' private information, such as a physical or electronic + address, without explicit permission +* Other conduct which could reasonably be considered inappropriate in a + professional setting + +## Our Responsibilities + +Project maintainers are responsible for clarifying the standards of acceptable +behavior and are expected to take appropriate and fair corrective action in +response to any instances of unacceptable behavior. + +Project maintainers have the right and responsibility to remove, edit, or +reject comments, commits, code, wiki edits, issues, and other contributions +that are not aligned to this Code of Conduct, or to ban temporarily or +permanently any contributor for other behaviors that they deem inappropriate, +threatening, offensive, or harmful. + +## Scope + +This Code of Conduct applies both within project spaces and in public spaces +when an individual is representing the project or its community. Examples of +representing a project or community include using an official project e-mail +address, posting via an official social media account, or acting as an appointed +representative at an online or offline event. Representation of a project may be +further defined and clarified by project maintainers. + +## Enforcement + +Instances of abusive, harassing, or otherwise unacceptable behavior may be +reported by contacting the project team at **[EMAIL REQUIRED]**. All +complaints will be reviewed and investigated and will result in a response that +is deemed necessary and appropriate to the circumstances. The project team is +obligated to maintain confidentiality with regard to the reporter of an incident. +Further details of specific enforcement policies may be posted separately. + +Project maintainers who do not follow or enforce the Code of Conduct in good +faith may face temporary or permanent repercussions as determined by other +members of the project's leadership. + +## Attribution + +This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, +available at [https://www.contributor-covenant.org/version/1/4/code-of-conduct.html](https://www.contributor-covenant.org/version/1/4/code-of-conduct.html) + +[homepage]: https://www.contributor-covenant.org + +For answers to common questions about this code of conduct, see [https://www.contributor-covenant.org/faq](https://www.contributor-covenant.org/faq) diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md index abfbdde..e3bacb9 100644 --- a/.github/CONTRIBUTING.md +++ b/.github/CONTRIBUTING.md @@ -1,24 +1,24 @@ -# Contributing - -Thanks for your interest in contributing to the **PSReddit Project**. - -Whether it's a bug report, new feature, correction, or additional documentation, your feedback and contributions are appreciated. - -Please read through this document before submitting any issues or pull requests to ensure all the necessary information is provided to effectively respond to your bug report or contribution. - -Please note there is a code of conduct, please follow it in all your interactions with the project. - -## Reporting Bugs/Feature Requests - -## Contributing via Pull Requests - -## Finding contributions to work on - -## Code of Conduct - -This project has a [Code of Conduct](CODE_OF_CONDUCT.md). - -## Licensing - -See the [LICENSE](LICENSE.txt) file for our project's licensing. +# Contributing + +Thanks for your interest in contributing to the **PSReddit Project**. + +Whether it's a bug report, new feature, correction, or additional documentation, your feedback and contributions are appreciated. + +Please read through this document before submitting any issues or pull requests to ensure all the necessary information is provided to effectively respond to your bug report or contribution. + +Please note there is a code of conduct, please follow it in all your interactions with the project. + +## Reporting Bugs/Feature Requests + +## Contributing via Pull Requests + +## Finding contributions to work on + +## Code of Conduct + +This project has a [Code of Conduct](CODE_OF_CONDUCT.md). + +## Licensing + +See the [LICENSE](LICENSE.txt) file for our project's licensing. diff --git a/.github/ISSUE_TEMPLATE/bug-report.md b/.github/ISSUE_TEMPLATE/bug-report.md index 67f3c29..40812c7 100644 --- a/.github/ISSUE_TEMPLATE/bug-report.md +++ b/.github/ISSUE_TEMPLATE/bug-report.md @@ -1,39 +1,39 @@ ---- -name: Bug report -about: Submit a new bug -title: Bug report -labels: 'bug' -assignees: '' - ---- - - - -### Expected Behavior - - -### Current Behavior - - -### Possible Solution - - -### Steps to Reproduce - - - -1. -2. -3. -4. - -### Context (Environment) - - -* Operating System and version as reported by `$PSVersionTable.OS`: -* PowerShell versions as reported by `$PSVersionTable.PSEdition`: - - - -### Detailed Description - +--- +name: Bug report +about: Submit a new bug +title: Bug report +labels: 'bug' +assignees: '' + +--- + + + +### Expected Behavior + + +### Current Behavior + + +### Possible Solution + + +### Steps to Reproduce + + + +1. +2. +3. +4. + +### Context (Environment) + + +* Operating System and version as reported by `$PSVersionTable.OS`: +* PowerShell versions as reported by `$PSVersionTable.PSEdition`: + + + +### Detailed Description + diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md index cb1a9fb..a3a401e 100644 --- a/.github/ISSUE_TEMPLATE/feature_request.md +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -1,20 +1,20 @@ ---- -name: Feature request -about: Suggest an idea for this project -title: 'Feature request' -labels: 'enhancement' -assignees: '' - ---- - -### Description - - -### Describe the solution you'd like - - -### Describe any alternatives you've considered - - -### Additional context - +--- +name: Feature request +about: Suggest an idea for this project +title: 'Feature request' +labels: 'enhancement' +assignees: '' + +--- + +### Description + + +### Describe the solution you'd like + + +### Describe any alternatives you've considered + + +### Additional context + diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index a7660f8..4b6ea22 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -1,13 +1,13 @@ -# Pull Request - -## Issue - -Issue #, if available: - -## Description - -Description of changes: - -## License - -By submitting this pull request, I confirm that my contribution is made under the terms of the projects associated license. +# Pull Request + +## Issue + +Issue #, if available: + +## Description + +Description of changes: + +## License + +By submitting this pull request, I confirm that my contribution is made under the terms of the projects associated license. diff --git a/.github/SECURITY.md b/.github/SECURITY.md index 184ed7d..c782d6c 100644 --- a/.github/SECURITY.md +++ b/.github/SECURITY.md @@ -1,18 +1,18 @@ -# Security Policy - -## Reporting a Vulnerability - - - -If you discover a vulnerability in PSReddit, please follow the _following process_: - -1. Open a generic bug issue advising you have discovered a vulnerability. - - Avoid sharing specifics or details of the vulnerability in an open GitHub issue. -2. A repo owner will reach out to you to establish a private form of communication. -3. We will evaluate the vulnerability and, if necessary, release a fix or mitigating steps to address it. We will contact you to let you know the outcome, and will credit you in the report. - - Please **do not disclose the vulnerability publicly** until a fix is released! - +# Security Policy + +## Reporting a Vulnerability + + + +If you discover a vulnerability in PSReddit, please follow the _following process_: + +1. Open a generic bug issue advising you have discovered a vulnerability. + - Avoid sharing specifics or details of the vulnerability in an open GitHub issue. +2. A repo owner will reach out to you to establish a private form of communication. +3. We will evaluate the vulnerability and, if necessary, release a fix or mitigating steps to address it. We will contact you to let you know the outcome, and will credit you in the report. + + Please **do not disclose the vulnerability publicly** until a fix is released! + 4. Once we have either a) published a fix, or b) declined to address the vulnerability for whatever reason, you are free to publicly disclose it. \ No newline at end of file diff --git a/.gitignore b/.gitignore index 2cf5ecf..9fc8b9e 100644 --- a/.gitignore +++ b/.gitignore @@ -1,29 +1,29 @@ -Archive -Artifacts -cov.xml -coverage.xml - -# OS generated files # -###################### -.DS_Store -.DS_Store? -._* -.Spotlight-V100 -.Trashes -ehthumbs.db -Thumbs.db - -# Rules from original module -########################### -# Exclude secrets and build artifacts -*.secret -*.env -*.user -*.nupkg -node_modules/ -reddit_secrets.json - -# Zensical documentation build artifacts -######################################### -docs/site/ +Archive +Artifacts +cov.xml +coverage.xml + +# OS generated files # +###################### +.DS_Store +.DS_Store? +._* +.Spotlight-V100 +.Trashes +ehthumbs.db +Thumbs.db + +# Rules from original module +########################### +# Exclude secrets and build artifacts +*.secret +*.env +*.user +*.nupkg +node_modules/ +reddit_secrets.json + +# Zensical documentation build artifacts +######################################### +docs/site/ .zensical/ \ No newline at end of file diff --git a/.vscode/extensions.json b/.vscode/extensions.json index 7380068..52609dd 100644 --- a/.vscode/extensions.json +++ b/.vscode/extensions.json @@ -1,9 +1,9 @@ -{ - // See http://go.microsoft.com/fwlink/?LinkId=827846 - // for the documentation about the extensions.json format - "recommendations": [ - "ms-vscode.PowerShell", - "ryanluker.vscode-coverage-gutters", - "DavidAnson.vscode-markdownlint" - ] +{ + // See http://go.microsoft.com/fwlink/?LinkId=827846 + // for the documentation about the extensions.json format + "recommendations": [ + "ms-vscode.PowerShell", + "ryanluker.vscode-coverage-gutters", + "DavidAnson.vscode-markdownlint" + ] } \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json index b6d2d10..f26afb2 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,9 +1,9 @@ -{ - // When enabled, will trim trailing whitespace when you save a file. - "files.trimTrailingWhitespace": true, - // specifies the location of the explicitly ScriptAnalyzer settings file - "powershell.scriptAnalysis.settingsPath": "PSScriptAnalyzerSettings.psd1", - // specifies the PowerShell coding style used in this project (https://github.com/PoshCode/PowerShellPracticeAndStyle/issues/81) - "powershell.codeFormatting.preset":"OTBS" -} +{ + // When enabled, will trim trailing whitespace when you save a file. + "files.trimTrailingWhitespace": true, + // specifies the location of the explicitly ScriptAnalyzer settings file + "powershell.scriptAnalysis.settingsPath": "PSScriptAnalyzerSettings.psd1", + // specifies the PowerShell coding style used in this project (https://github.com/PoshCode/PowerShellPracticeAndStyle/issues/81) + "powershell.codeFormatting.preset":"OTBS" +} diff --git a/.vscode/tasks.json b/.vscode/tasks.json index a40ffa9..0040c07 100644 --- a/.vscode/tasks.json +++ b/.vscode/tasks.json @@ -1,269 +1,269 @@ -// Portions of this file were generated using New-VSCodeTask.ps1 -// Modify the build script or tasks-merge.json and recreate. -// https://code.visualstudio.com/docs/editor/tasks -// Available variables which can be used inside of strings. -// https://code.visualstudio.com/docs/editor/variables-reference#_predefined-variables -// ${workspaceFolder}: path of the folder opened in VS Code -// ${workspaceFolderBasename} - name of the folder opened in VS Code without any slashes (/) -// ${file}: current opened file -// ${fileWorkspaceFolder} - current opened file's workspace folder -// ${relativeFile}: the current opened file relative to workspaceFolder -// ${relativeFileDirname}: current opened file's dirname relative to workspaceFolder -// ${fileBasename}: current opened file's basename -// ${fileBasenameNoExtension} - current opened file's basename with no file extension -// ${fileDirname}: current opened file's dirname -// ${fileExtname}: current opened file's extension -// ${cwd}: the current working directory of the spawned process -// ${lineNumber} - current selected line number in the active file -// ${selectedText} - current selected text in the active file -// ${execPath} - path to the running VS Code executable -// ${defaultBuildTask} - name of the default build task -// ${pathSeparator} - character used by the operating system to separate components in file paths -{ - "version": "2.0.0", - "windows": { - "options": { - "shell": { - // "executable": "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": ".", - "type": "shell", - "command": "$PSVersionTable;Invoke-Build -Task . -File '${workspaceFolder}/src/${workspaceFolderBasename}.build.ps1'", - "problemMatcher": "$msCompile", - "presentation": { - "echo": false, - "showReuseMessage": false - }, - "group": { - "kind": "build", - "isDefault": true - } - }, - { - "label": "TestLocal", - "type": "shell", - "command": "Invoke-Build -Task TestLocal -File '${workspaceFolder}/src/${workspaceFolderBasename}.build.ps1'", - "problemMatcher": "$msCompile", - "presentation": { - "echo": false, - "showReuseMessage": false - }, - "group": "test" - }, - { - "label": "HelpLocal", - "type": "shell", - "command": "Invoke-Build -Task HelpLocal -File '${workspaceFolder}/src/${workspaceFolderBasename}.build.ps1'", - "problemMatcher": "$msCompile", - "presentation": { - "echo": false, - "showReuseMessage": false - }, - "group": "none" - }, - { - "label": "Clean", - "type": "shell", - "command": "Invoke-Build -Task Clean -File '${workspaceFolder}/src/${workspaceFolderBasename}.build.ps1'", - "problemMatcher": "$msCompile", - "presentation": { - "echo": false, - "showReuseMessage": false - }, - "group": "none" - }, - { - "label": "ValidateRequirements", - "type": "shell", - "command": "Invoke-Build -Task ValidateRequirements -File '${workspaceFolder}/src/${workspaceFolderBasename}.build.ps1'", - "problemMatcher": "$msCompile", - "presentation": { - "echo": false, - "showReuseMessage": false - }, - "group": "none" - }, - { - "label": "Analyze", - "type": "shell", - "problemMatcher": "$msCompile", - "command": "Invoke-Build -Task Analyze -File '${workspaceFolder}/src/${workspaceFolderBasename}.build.ps1'", - "presentation": { - "echo": false, - "showReuseMessage": false - }, - "group": "none" - }, - { - "label": "AnalyzeTests", - "type": "shell", - "command": "Invoke-Build -Task AnalyzeTests -File '${workspaceFolder}/src/${workspaceFolderBasename}.build.ps1'", - "problemMatcher": "$msCompile", - "presentation": { - "echo": false, - "showReuseMessage": false - }, - "group": "none" - }, - { - "label": "FormattingCheck", - "type": "shell", - "command": "Invoke-Build -Task FormattingCheck -File '${workspaceFolder}/src/${workspaceFolderBasename}.build.ps1'", - "problemMatcher": "$msCompile", - "presentation": { - "echo": false, - "showReuseMessage": false - }, - "group": "none" - }, - { - "label": "Test", - "type": "shell", - "command": "Invoke-Build -Task Test -File '${workspaceFolder}/src/${workspaceFolderBasename}.build.ps1'", - "problemMatcher": "$msCompile", - "presentation": { - "echo": false, - "showReuseMessage": false - }, - "group": { - "kind": "test", - "isDefault": true - } - }, - { - "label": "DevCC", - "type": "shell", - "command": "Invoke-Build -Task DevCC -File '${workspaceFolder}/src/${workspaceFolderBasename}.build.ps1'", - "problemMatcher": "$msCompile", - "presentation": { - "echo": false, - "showReuseMessage": false - }, - "group": "test" - }, - { - "label": "Build", - "type": "shell", - "command": "Invoke-Build -Task Build -File '${workspaceFolder}/src/${workspaceFolderBasename}.build.ps1'", - "problemMatcher": "$msCompile", - "presentation": { - "echo": false, - "showReuseMessage": false - }, - "group": "none" - }, - { - "label": "BuildNoIntegration", - "type": "shell", - "command": "Invoke-Build -Task BuildNoIntegration -File '${workspaceFolder}/src/${workspaceFolderBasename}.build.ps1'", - "problemMatcher": "$msCompile", - "presentation": { - "echo": false, - "showReuseMessage": false - }, - "group": "build" - }, - { - "label": "IntegrationTest", - "type": "shell", - "command": "Invoke-Build -Task IntegrationTest -File '${workspaceFolder}/src/${workspaceFolderBasename}.build.ps1'", - "problemMatcher": "$msCompile", - "presentation": { - "echo": false, - "showReuseMessage": false - }, - "group": "test" - }, - { - "label": "PesterTest", - "type": "shell", - "command": "Invoke-Pester '${workspaceFolder}/src/Tests/Unit' -Output Detailed", - "problemMatcher": "$pester", - "group": "test" - }, - { - "label": "Pester-Single-Coverage", - "type": "shell", - "command": "Import-Module -Name '${workspaceFolder}/src/${workspaceFolderBasename}/${workspaceFolderBasename}.psm1';$pesterConfig = New-PesterConfiguration;$pesterConfig.run.Path = '${workspaceFolder}/src/Tests/Unit/*/${input:functionName}.Tests.ps1';$pesterConfig.CodeCoverage.Enabled = $true;$pesterConfig.CodeCoverage.Path = '${workspaceFolder}/src/${workspaceFolderBasename}/*/${input:functionName}.ps1';Invoke-Pester -Configuration $pesterConfig", - "problemMatcher": "$pester", - "presentation": { - "echo": false, - "showReuseMessage": false - }, - "group": "test" - }, - { - "label": "Pester-Single-Detailed", - "type": "shell", - "command": "Import-Module -Name '${workspaceFolder}/src/${workspaceFolderBasename}/${workspaceFolderBasename}.psm1';Invoke-Pester '${workspaceFolder}/src/Tests/Unit/*/${input:functionName}.Tests.ps1' -Output Detailed", - "problemMatcher": "$pester", - "presentation": { - "echo": false, - "showReuseMessage": false - }, - "group": "test" - }, - { - "label": "DevCC-Single", - "type": "shell", - "command": "Import-Module -Name '${workspaceFolder}/src/${workspaceFolderBasename}/${workspaceFolderBasename}.psm1';$pesterConfig = New-PesterConfiguration;$pesterConfig.run.Path = '${workspaceFolder}/src/Tests/Unit/*/${input:functionName}.Tests.ps1';$pesterConfig.CodeCoverage.Enabled = $true;$pesterConfig.CodeCoverage.Path = '${workspaceFolder}/src/${workspaceFolderBasename}/*/${input:functionName}.ps1';$pesterConfig.CodeCoverage.OutputPath = '${workspaceFolder}/cov.xml';$pesterConfig.CodeCoverage.OutputFormat = 'CoverageGutters';Invoke-Pester -Configuration $pesterConfig", - "problemMatcher": "$pester", - "presentation": { - "echo": false, - "showReuseMessage": false - }, - "group": "test" - }, - { - "label": "Integration-Single-Detailed", - "type": "shell", - "command": "Import-Module -Name '${workspaceFolder}/src/${workspaceFolderBasename}/${workspaceFolderBasename}.psm1';Invoke-Pester '${workspaceFolder}/src/Tests/Integration/*/${input:functionName}.Tests.ps1' -Output Detailed", - "problemMatcher": "$pester", - "presentation": { - "echo": false, - "showReuseMessage": false - }, - "group": "test" - } - ], - "inputs": [ - { - "type": "promptString", - "id": "functionName", - "description": "Name of PowerShell function you want to test" - } - ] +// Portions of this file were generated using New-VSCodeTask.ps1 +// Modify the build script or tasks-merge.json and recreate. +// https://code.visualstudio.com/docs/editor/tasks +// Available variables which can be used inside of strings. +// https://code.visualstudio.com/docs/editor/variables-reference#_predefined-variables +// ${workspaceFolder}: path of the folder opened in VS Code +// ${workspaceFolderBasename} - name of the folder opened in VS Code without any slashes (/) +// ${file}: current opened file +// ${fileWorkspaceFolder} - current opened file's workspace folder +// ${relativeFile}: the current opened file relative to workspaceFolder +// ${relativeFileDirname}: current opened file's dirname relative to workspaceFolder +// ${fileBasename}: current opened file's basename +// ${fileBasenameNoExtension} - current opened file's basename with no file extension +// ${fileDirname}: current opened file's dirname +// ${fileExtname}: current opened file's extension +// ${cwd}: the current working directory of the spawned process +// ${lineNumber} - current selected line number in the active file +// ${selectedText} - current selected text in the active file +// ${execPath} - path to the running VS Code executable +// ${defaultBuildTask} - name of the default build task +// ${pathSeparator} - character used by the operating system to separate components in file paths +{ + "version": "2.0.0", + "windows": { + "options": { + "shell": { + // "executable": "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": ".", + "type": "shell", + "command": "$PSVersionTable;Invoke-Build -Task . -File '${workspaceFolder}/src/${workspaceFolderBasename}.build.ps1'", + "problemMatcher": "$msCompile", + "presentation": { + "echo": false, + "showReuseMessage": false + }, + "group": { + "kind": "build", + "isDefault": true + } + }, + { + "label": "TestLocal", + "type": "shell", + "command": "Invoke-Build -Task TestLocal -File '${workspaceFolder}/src/${workspaceFolderBasename}.build.ps1'", + "problemMatcher": "$msCompile", + "presentation": { + "echo": false, + "showReuseMessage": false + }, + "group": "test" + }, + { + "label": "HelpLocal", + "type": "shell", + "command": "Invoke-Build -Task HelpLocal -File '${workspaceFolder}/src/${workspaceFolderBasename}.build.ps1'", + "problemMatcher": "$msCompile", + "presentation": { + "echo": false, + "showReuseMessage": false + }, + "group": "none" + }, + { + "label": "Clean", + "type": "shell", + "command": "Invoke-Build -Task Clean -File '${workspaceFolder}/src/${workspaceFolderBasename}.build.ps1'", + "problemMatcher": "$msCompile", + "presentation": { + "echo": false, + "showReuseMessage": false + }, + "group": "none" + }, + { + "label": "ValidateRequirements", + "type": "shell", + "command": "Invoke-Build -Task ValidateRequirements -File '${workspaceFolder}/src/${workspaceFolderBasename}.build.ps1'", + "problemMatcher": "$msCompile", + "presentation": { + "echo": false, + "showReuseMessage": false + }, + "group": "none" + }, + { + "label": "Analyze", + "type": "shell", + "problemMatcher": "$msCompile", + "command": "Invoke-Build -Task Analyze -File '${workspaceFolder}/src/${workspaceFolderBasename}.build.ps1'", + "presentation": { + "echo": false, + "showReuseMessage": false + }, + "group": "none" + }, + { + "label": "AnalyzeTests", + "type": "shell", + "command": "Invoke-Build -Task AnalyzeTests -File '${workspaceFolder}/src/${workspaceFolderBasename}.build.ps1'", + "problemMatcher": "$msCompile", + "presentation": { + "echo": false, + "showReuseMessage": false + }, + "group": "none" + }, + { + "label": "FormattingCheck", + "type": "shell", + "command": "Invoke-Build -Task FormattingCheck -File '${workspaceFolder}/src/${workspaceFolderBasename}.build.ps1'", + "problemMatcher": "$msCompile", + "presentation": { + "echo": false, + "showReuseMessage": false + }, + "group": "none" + }, + { + "label": "Test", + "type": "shell", + "command": "Invoke-Build -Task Test -File '${workspaceFolder}/src/${workspaceFolderBasename}.build.ps1'", + "problemMatcher": "$msCompile", + "presentation": { + "echo": false, + "showReuseMessage": false + }, + "group": { + "kind": "test", + "isDefault": true + } + }, + { + "label": "DevCC", + "type": "shell", + "command": "Invoke-Build -Task DevCC -File '${workspaceFolder}/src/${workspaceFolderBasename}.build.ps1'", + "problemMatcher": "$msCompile", + "presentation": { + "echo": false, + "showReuseMessage": false + }, + "group": "test" + }, + { + "label": "Build", + "type": "shell", + "command": "Invoke-Build -Task Build -File '${workspaceFolder}/src/${workspaceFolderBasename}.build.ps1'", + "problemMatcher": "$msCompile", + "presentation": { + "echo": false, + "showReuseMessage": false + }, + "group": "none" + }, + { + "label": "BuildNoIntegration", + "type": "shell", + "command": "Invoke-Build -Task BuildNoIntegration -File '${workspaceFolder}/src/${workspaceFolderBasename}.build.ps1'", + "problemMatcher": "$msCompile", + "presentation": { + "echo": false, + "showReuseMessage": false + }, + "group": "build" + }, + { + "label": "IntegrationTest", + "type": "shell", + "command": "Invoke-Build -Task IntegrationTest -File '${workspaceFolder}/src/${workspaceFolderBasename}.build.ps1'", + "problemMatcher": "$msCompile", + "presentation": { + "echo": false, + "showReuseMessage": false + }, + "group": "test" + }, + { + "label": "PesterTest", + "type": "shell", + "command": "Invoke-Pester '${workspaceFolder}/src/Tests/Unit' -Output Detailed", + "problemMatcher": "$pester", + "group": "test" + }, + { + "label": "Pester-Single-Coverage", + "type": "shell", + "command": "Import-Module -Name '${workspaceFolder}/src/${workspaceFolderBasename}/${workspaceFolderBasename}.psm1';$pesterConfig = New-PesterConfiguration;$pesterConfig.run.Path = '${workspaceFolder}/src/Tests/Unit/*/${input:functionName}.Tests.ps1';$pesterConfig.CodeCoverage.Enabled = $true;$pesterConfig.CodeCoverage.Path = '${workspaceFolder}/src/${workspaceFolderBasename}/*/${input:functionName}.ps1';Invoke-Pester -Configuration $pesterConfig", + "problemMatcher": "$pester", + "presentation": { + "echo": false, + "showReuseMessage": false + }, + "group": "test" + }, + { + "label": "Pester-Single-Detailed", + "type": "shell", + "command": "Import-Module -Name '${workspaceFolder}/src/${workspaceFolderBasename}/${workspaceFolderBasename}.psm1';Invoke-Pester '${workspaceFolder}/src/Tests/Unit/*/${input:functionName}.Tests.ps1' -Output Detailed", + "problemMatcher": "$pester", + "presentation": { + "echo": false, + "showReuseMessage": false + }, + "group": "test" + }, + { + "label": "DevCC-Single", + "type": "shell", + "command": "Import-Module -Name '${workspaceFolder}/src/${workspaceFolderBasename}/${workspaceFolderBasename}.psm1';$pesterConfig = New-PesterConfiguration;$pesterConfig.run.Path = '${workspaceFolder}/src/Tests/Unit/*/${input:functionName}.Tests.ps1';$pesterConfig.CodeCoverage.Enabled = $true;$pesterConfig.CodeCoverage.Path = '${workspaceFolder}/src/${workspaceFolderBasename}/*/${input:functionName}.ps1';$pesterConfig.CodeCoverage.OutputPath = '${workspaceFolder}/cov.xml';$pesterConfig.CodeCoverage.OutputFormat = 'CoverageGutters';Invoke-Pester -Configuration $pesterConfig", + "problemMatcher": "$pester", + "presentation": { + "echo": false, + "showReuseMessage": false + }, + "group": "test" + }, + { + "label": "Integration-Single-Detailed", + "type": "shell", + "command": "Import-Module -Name '${workspaceFolder}/src/${workspaceFolderBasename}/${workspaceFolderBasename}.psm1';Invoke-Pester '${workspaceFolder}/src/Tests/Integration/*/${input:functionName}.Tests.ps1' -Output Detailed", + "problemMatcher": "$pester", + "presentation": { + "echo": false, + "showReuseMessage": false + }, + "group": "test" + } + ], + "inputs": [ + { + "type": "promptString", + "id": "functionName", + "description": "Name of PowerShell function you want to test" + } + ] } diff --git a/LICENSE b/LICENSE index 45b7e36..2415024 100644 --- a/LICENSE +++ b/LICENSE @@ -1,21 +1,21 @@ -MIT License - -Copyright (c) 2025 Luke Evans - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +MIT License + +Copyright (c) 2025 Luke Evans + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/README.md b/README.md index 8c66747..bf25994 100644 --- a/README.md +++ b/README.md @@ -1,71 +1,71 @@ -# PSReddit - -## Synopsis - -A cross-platform PowerShell 7+ module for authenticating with Reddit and retrieving posts from subreddits. - -## Description - -This module provides cmdlets to authenticate with the Reddit API using OAuth 2.0 client credentials flow and fetch posts based on subreddit, sort order, and timeframe. It requires PowerShell 7+ and relies on environment variables for storing Reddit API client ID and secret. - -## Why - - - -## Documentation - -Full documentation is available at: [https://lukeevanstech.github.io/PSReddit/](https://lukeevanstech.github.io/PSReddit/) - -## Features - -- OAuth authentication (using environment variables for secrets) -- Retrieve posts by subreddit with `Get-RedditSubredditPost` -- Retrieve posts from specific users with `Get-RedditUserPost` -- Support for various sort types (Top, New, Hot, etc.) and timeframes (for Top/Controversial) -- Pester tests for integration testing -- Comprehensive MkDocs documentation - -## Getting Started - -### Prerequisites - -- PowerShell 7+ -- Reddit API Client ID and Secret stored in environment variables: - - `REDDIT_CLIENT_ID` - - `REDDIT_CLIENT_SECRET` - -### Installation - -```powershell -# Import the module locally from the source directory -Import-Module ./src/PSReddit/PSReddit.psd1 -Force -``` - -### Quick start (Usage Example) - -```powershell -# Set environment variables (replace with your actual credentials) -$env:REDDIT_CLIENT_ID = 'your-client-id' -$env:REDDIT_CLIENT_SECRET = 'your-client-secret' - -# Get an OAuth token -$token = Get-RedditOAuthToken - -# Get the top 5 posts from the 'powershell' subreddit from the last day -$posts = Get-RedditSubredditPost -Subreddit 'powershell' -Sort Top -LastDay -Count 5 - -# Display post titles -$posts.title -``` - -## Docs -See the [documentation](./docs/index.md) for full usage, installation, and development information. - -## Development -- Run tests: `Invoke-Pester ./src/Tests -CI` (Ensure Pester module is installed) -- Style Guides: Refer to project configuration files (e.g., `.editorconfig`, `PSScriptAnalyzerSettings.psd1`). -- Contributions welcome! Please follow standard fork & pull request workflow. - -## Author - -Luke Evans +# PSReddit + +## Synopsis + +A cross-platform PowerShell 7+ module for authenticating with Reddit and retrieving posts from subreddits. + +## Description + +This module provides cmdlets to authenticate with the Reddit API using OAuth 2.0 client credentials flow and fetch posts based on subreddit, sort order, and timeframe. It requires PowerShell 7+ and relies on environment variables for storing Reddit API client ID and secret. + +## Why + + + +## Documentation + +Full documentation is available at: [https://lukeevanstech.github.io/PSReddit/](https://lukeevanstech.github.io/PSReddit/) + +## Features + +- OAuth authentication (using environment variables for secrets) +- Retrieve posts by subreddit with `Get-RedditSubredditPost` +- Retrieve posts from specific users with `Get-RedditUserPost` +- Support for various sort types (Top, New, Hot, etc.) and timeframes (for Top/Controversial) +- Pester tests for integration testing +- Comprehensive MkDocs documentation + +## Getting Started + +### Prerequisites + +- PowerShell 7+ +- Reddit API Client ID and Secret stored in environment variables: + - `REDDIT_CLIENT_ID` + - `REDDIT_CLIENT_SECRET` + +### Installation + +```powershell +# Import the module locally from the source directory +Import-Module ./src/PSReddit/PSReddit.psd1 -Force +``` + +### Quick start (Usage Example) + +```powershell +# Set environment variables (replace with your actual credentials) +$env:REDDIT_CLIENT_ID = 'your-client-id' +$env:REDDIT_CLIENT_SECRET = 'your-client-secret' + +# Get an OAuth token +$token = Get-RedditOAuthToken + +# Get the top 5 posts from the 'powershell' subreddit from the last day +$posts = Get-RedditSubredditPost -Subreddit 'powershell' -Sort Top -LastDay -Count 5 + +# Display post titles +$posts.title +``` + +## Docs +See the [documentation](./docs/index.md) for full usage, installation, and development information. + +## Development +- Run tests: `Invoke-Pester ./src/Tests -CI` (Ensure Pester module is installed) +- Style Guides: Refer to project configuration files (e.g., `.editorconfig`, `PSScriptAnalyzerSettings.psd1`). +- Contributions welcome! Please follow standard fork & pull request workflow. + +## Author + +Luke Evans diff --git a/actions_bootstrap.ps1 b/actions_bootstrap.ps1 index db2b4a3..24c9869 100644 --- a/actions_bootstrap.ps1 +++ b/actions_bootstrap.ps1 @@ -1,63 +1,63 @@ -# Bootstrap dependencies - -# https://docs.microsoft.com/powershell/module/packagemanagement/get-packageprovider -Get-PackageProvider -Name Nuget -ForceBootstrap | Out-Null - -# https://docs.microsoft.com/powershell/module/powershellget/set-psrepository -Set-PSRepository -Name PSGallery -InstallationPolicy Trusted - -# List of PowerShell Modules required for the build -$modulesToInstall = New-Object System.Collections.Generic.List[object] -# https://github.com/pester/Pester -[void]$modulesToInstall.Add(([PSCustomObject]@{ - ModuleName = 'Pester' - ModuleVersion = '5.7.1' - })) -# https://github.com/nightroman/Invoke-Build -[void]$modulesToInstall.Add(([PSCustomObject]@{ - ModuleName = 'InvokeBuild' - ModuleVersion = '5.12.1' - })) -# https://github.com/PowerShell/PSScriptAnalyzer -[void]$modulesToInstall.Add(([PSCustomObject]@{ - ModuleName = 'PSScriptAnalyzer' - ModuleVersion = '1.23.0' - })) -# https://github.com/PowerShell/platyPS -# older version used due to: https://github.com/PowerShell/platyPS/issues/457 -[void]$modulesToInstall.Add(([PSCustomObject]@{ - ModuleName = 'platyPS' - ModuleVersion = '0.12.0' - })) - - - -'Installing PowerShell Modules' -foreach ($module in $modulesToInstall) { - $installSplat = @{ - Name = $module.ModuleName - RequiredVersion = $module.ModuleVersion - Repository = 'PSGallery' - SkipPublisherCheck = $true - Force = $true - ErrorAction = 'Stop' - } - try { - if ($module.ModuleName -eq 'Pester' -and ($IsWindows -or $PSVersionTable.PSVersion -le [version]'5.1')) { - # special case for Pester certificate mismatch with older Pester versions - https://github.com/pester/Pester/issues/2389 - # this only affects windows builds - Install-Module @installSplat -SkipPublisherCheck - } - else { - Install-Module @installSplat - } - Import-Module -Name $module.ModuleName -ErrorAction Stop - ' - Successfully installed {0}' -f $module.ModuleName - } - catch { - $message = 'Failed to install {0}' -f $module.ModuleName - " - $message" - throw - } -} +# Bootstrap dependencies + +# https://docs.microsoft.com/powershell/module/packagemanagement/get-packageprovider +Get-PackageProvider -Name Nuget -ForceBootstrap | Out-Null + +# https://docs.microsoft.com/powershell/module/powershellget/set-psrepository +Set-PSRepository -Name PSGallery -InstallationPolicy Trusted + +# List of PowerShell Modules required for the build +$modulesToInstall = New-Object System.Collections.Generic.List[object] +# https://github.com/pester/Pester +[void]$modulesToInstall.Add(([PSCustomObject]@{ + ModuleName = 'Pester' + ModuleVersion = '5.7.1' + })) +# https://github.com/nightroman/Invoke-Build +[void]$modulesToInstall.Add(([PSCustomObject]@{ + ModuleName = 'InvokeBuild' + ModuleVersion = '5.12.1' + })) +# https://github.com/PowerShell/PSScriptAnalyzer +[void]$modulesToInstall.Add(([PSCustomObject]@{ + ModuleName = 'PSScriptAnalyzer' + ModuleVersion = '1.23.0' + })) +# https://github.com/PowerShell/platyPS +# older version used due to: https://github.com/PowerShell/platyPS/issues/457 +[void]$modulesToInstall.Add(([PSCustomObject]@{ + ModuleName = 'platyPS' + ModuleVersion = '0.12.0' + })) + + + +'Installing PowerShell Modules' +foreach ($module in $modulesToInstall) { + $installSplat = @{ + Name = $module.ModuleName + RequiredVersion = $module.ModuleVersion + Repository = 'PSGallery' + SkipPublisherCheck = $true + Force = $true + ErrorAction = 'Stop' + } + try { + if ($module.ModuleName -eq 'Pester' -and ($IsWindows -or $PSVersionTable.PSVersion -le [version]'5.1')) { + # special case for Pester certificate mismatch with older Pester versions - https://github.com/pester/Pester/issues/2389 + # this only affects windows builds + Install-Module @installSplat -SkipPublisherCheck + } + else { + Install-Module @installSplat + } + Import-Module -Name $module.ModuleName -ErrorAction Stop + ' - Successfully installed {0}' -f $module.ModuleName + } + catch { + $message = 'Failed to install {0}' -f $module.ModuleName + " - $message" + throw + } +} diff --git a/docs/MIGRATION.md b/docs/MIGRATION.md index ecaa21c..3164a9e 100644 --- a/docs/MIGRATION.md +++ b/docs/MIGRATION.md @@ -1,208 +1,208 @@ -# Migration from MkDocs to Zensical - -This document outlines the migration process from MkDocs to Zensical for the PSReddit documentation. - -## Why Zensical? - -Zensical is a modern documentation site generator built by the creators of Material for MkDocs. Key benefits include: - -- **Better Performance**: Optimized for large documentation sites (10,000+ pages) -- **Simpler Configuration**: TOML-based configuration instead of YAML -- **Modern Architecture**: Built on the ZRX differential build engine -- **Maintained by Material Team**: From the creators of Material for MkDocs -- **Native Markdown**: No need for JSX or React knowledge -- **Built-in Material Design**: Modern, responsive theme out of the box - -## What Changed - -### New Files -- `zensical.toml` - Main Zensical configuration (replaces `mkdocs.yml`) -- `docs/package.json` - NPM scripts for running Zensical commands -- `docs/assets/stylesheets/custom.css` - Custom styling to hide Zensical branding - -### Updated Files -- `docs/requirements.txt` - Now uses `zensical>=0.0.4` instead of MkDocs packages -- `.github/workflows/psreddit-deploy-docs.yml` - Updated to use Zensical build process -- `.gitignore` - Added `docs/site/` and `.zensical/` to ignore build artifacts -- `.readthedocs.yaml` - Updated to use Zensical build commands - -### Preserved Files -- All markdown documentation files (`.md`) remain unchanged -- Documentation structure and navigation remain the same -- All content, examples, and code snippets are preserved - -## Configuration Mapping - -### MkDocs → Zensical Configuration - -**Site Information:** -```yaml -# mkdocs.yml -site_name: PSReddit -site_url: https://LukeEvansTech.github.io/PSReddit/ -``` - -```toml -# zensical.toml -[project] -site_name = "PSReddit" -site_url = "https://LukeEvansTech.github.io/PSReddit/" -``` - -**Theme Configuration:** -```yaml -# mkdocs.yml -theme: - name: material - palette: - - media: "(prefers-color-scheme: light)" - scheme: default - primary: blue -``` - -```toml -# zensical.toml -[project.theme] -name = "material" -variant = "modern" - -[[project.theme.palette]] -media = "(prefers-color-scheme: light)" -scheme = "default" -primary = "blue" -``` - -**Navigation:** -```yaml -# mkdocs.yml -nav: - - Home: index.md - - Cmdlets: - - Get-RedditOAuthToken: Get-RedditOAuthToken.md -``` - -```toml -# zensical.toml -[[nav]] -Home = "index.md" - -[[nav]] -Cmdlets = [ - { "Get-RedditOAuthToken" = "Get-RedditOAuthToken.md" } -] -``` - -## Development Workflow Changes - -### Before (MkDocs) -```bash -# Install dependencies -pip install -r docs/requirements.txt - -# Start dev server -mkdocs serve - -# Build documentation -mkdocs build - -# Deploy to GitHub Pages -mkdocs gh-deploy -``` - -### After (Zensical) -```bash -# Install dependencies -pip install -r docs/requirements.txt - -# Start dev server -cd docs -npm start -# or: zensical serve - -# Build documentation -npm run build -# or: zensical build - -# Deploy (handled automatically by GitHub Actions) -``` - -## Deployment Changes - -### GitHub Actions -The workflow now uses a two-job approach: -1. **Build Job**: Builds the site with Zensical and uploads artifact -2. **Deploy Job**: Deploys the artifact to GitHub Pages - -Benefits: -- Better separation of concerns -- Pip caching for faster builds -- Only triggers on changes to `docs/**` or workflow file -- Manual trigger support via `workflow_dispatch` - -### ReadTheDocs -ReadTheDocs configuration updated to use custom build commands: -```yaml -build: - commands: - - pip install -r docs/requirements.txt - - cd docs && zensical build - - mkdir -p $READTHEDOCS_OUTPUT/html - - cp -r docs/site/* $READTHEDOCS_OUTPUT/html/ -``` - -## Breaking Changes - -None! The migration is designed to be seamless: -- All URLs remain the same -- All documentation content is preserved -- Navigation structure unchanged -- Same Material Design theme - -## Rollback Instructions - -If you need to rollback to MkDocs: - -1. Restore the original `mkdocs.yml` file -2. Revert `docs/requirements.txt` to use MkDocs packages -3. Revert `.github/workflows/psreddit-deploy-docs.yml` -4. Delete `zensical.toml` and `docs/package.json` -5. Revert `.readthedocs.yaml` changes - -The original MkDocs configuration can be found in the git history before this migration commit. - -## Testing the Migration - -To verify the migration was successful: - -1. **Local Build Test:** - ```bash - cd docs - pip install -r requirements.txt - zensical build - ``` - -2. **Check Output:** - - Verify `docs/site/` directory was created - - Open `docs/site/index.html` in a browser - - Test all navigation links - - Verify code syntax highlighting works - - Check light/dark mode toggle - -3. **GitHub Actions Test:** - - Push changes to a branch - - Verify the workflow runs successfully - - Check GitHub Pages deployment - -## Resources - -- [Zensical Official Documentation](https://zensical.org/) -- [Material for MkDocs Documentation](https://squidfunk.github.io/mkdocs-material/) -- [github-infrastructure Migration Reference](https://github.com/LukeEvansTech/github-infrastructure/blob/main/docs/MIGRATION.md) - -## Questions or Issues? - -If you encounter any issues with the migration: -1. Check the build logs in GitHub Actions -2. Verify local build works correctly -3. Review the Zensical documentation -4. Compare with the github-infrastructure repository for reference +# Migration from MkDocs to Zensical + +This document outlines the migration process from MkDocs to Zensical for the PSReddit documentation. + +## Why Zensical? + +Zensical is a modern documentation site generator built by the creators of Material for MkDocs. Key benefits include: + +- **Better Performance**: Optimized for large documentation sites (10,000+ pages) +- **Simpler Configuration**: TOML-based configuration instead of YAML +- **Modern Architecture**: Built on the ZRX differential build engine +- **Maintained by Material Team**: From the creators of Material for MkDocs +- **Native Markdown**: No need for JSX or React knowledge +- **Built-in Material Design**: Modern, responsive theme out of the box + +## What Changed + +### New Files +- `zensical.toml` - Main Zensical configuration (replaces `mkdocs.yml`) +- `docs/package.json` - NPM scripts for running Zensical commands +- `docs/assets/stylesheets/custom.css` - Custom styling to hide Zensical branding + +### Updated Files +- `docs/requirements.txt` - Now uses `zensical>=0.0.4` instead of MkDocs packages +- `.github/workflows/psreddit-deploy-docs.yml` - Updated to use Zensical build process +- `.gitignore` - Added `docs/site/` and `.zensical/` to ignore build artifacts +- `.readthedocs.yaml` - Updated to use Zensical build commands + +### Preserved Files +- All markdown documentation files (`.md`) remain unchanged +- Documentation structure and navigation remain the same +- All content, examples, and code snippets are preserved + +## Configuration Mapping + +### MkDocs → Zensical Configuration + +**Site Information:** +```yaml +# mkdocs.yml +site_name: PSReddit +site_url: https://LukeEvansTech.github.io/PSReddit/ +``` + +```toml +# zensical.toml +[project] +site_name = "PSReddit" +site_url = "https://LukeEvansTech.github.io/PSReddit/" +``` + +**Theme Configuration:** +```yaml +# mkdocs.yml +theme: + name: material + palette: + - media: "(prefers-color-scheme: light)" + scheme: default + primary: blue +``` + +```toml +# zensical.toml +[project.theme] +name = "material" +variant = "modern" + +[[project.theme.palette]] +media = "(prefers-color-scheme: light)" +scheme = "default" +primary = "blue" +``` + +**Navigation:** +```yaml +# mkdocs.yml +nav: + - Home: index.md + - Cmdlets: + - Get-RedditOAuthToken: Get-RedditOAuthToken.md +``` + +```toml +# zensical.toml +[[nav]] +Home = "index.md" + +[[nav]] +Cmdlets = [ + { "Get-RedditOAuthToken" = "Get-RedditOAuthToken.md" } +] +``` + +## Development Workflow Changes + +### Before (MkDocs) +```bash +# Install dependencies +pip install -r docs/requirements.txt + +# Start dev server +mkdocs serve + +# Build documentation +mkdocs build + +# Deploy to GitHub Pages +mkdocs gh-deploy +``` + +### After (Zensical) +```bash +# Install dependencies +pip install -r docs/requirements.txt + +# Start dev server +cd docs +npm start +# or: zensical serve + +# Build documentation +npm run build +# or: zensical build + +# Deploy (handled automatically by GitHub Actions) +``` + +## Deployment Changes + +### GitHub Actions +The workflow now uses a two-job approach: +1. **Build Job**: Builds the site with Zensical and uploads artifact +2. **Deploy Job**: Deploys the artifact to GitHub Pages + +Benefits: +- Better separation of concerns +- Pip caching for faster builds +- Only triggers on changes to `docs/**` or workflow file +- Manual trigger support via `workflow_dispatch` + +### ReadTheDocs +ReadTheDocs configuration updated to use custom build commands: +```yaml +build: + commands: + - pip install -r docs/requirements.txt + - cd docs && zensical build + - mkdir -p $READTHEDOCS_OUTPUT/html + - cp -r docs/site/* $READTHEDOCS_OUTPUT/html/ +``` + +## Breaking Changes + +None! The migration is designed to be seamless: +- All URLs remain the same +- All documentation content is preserved +- Navigation structure unchanged +- Same Material Design theme + +## Rollback Instructions + +If you need to rollback to MkDocs: + +1. Restore the original `mkdocs.yml` file +2. Revert `docs/requirements.txt` to use MkDocs packages +3. Revert `.github/workflows/psreddit-deploy-docs.yml` +4. Delete `zensical.toml` and `docs/package.json` +5. Revert `.readthedocs.yaml` changes + +The original MkDocs configuration can be found in the git history before this migration commit. + +## Testing the Migration + +To verify the migration was successful: + +1. **Local Build Test:** + ```bash + cd docs + pip install -r requirements.txt + zensical build + ``` + +2. **Check Output:** + - Verify `docs/site/` directory was created + - Open `docs/site/index.html` in a browser + - Test all navigation links + - Verify code syntax highlighting works + - Check light/dark mode toggle + +3. **GitHub Actions Test:** + - Push changes to a branch + - Verify the workflow runs successfully + - Check GitHub Pages deployment + +## Resources + +- [Zensical Official Documentation](https://zensical.org/) +- [Material for MkDocs Documentation](https://squidfunk.github.io/mkdocs-material/) +- [github-infrastructure Migration Reference](https://github.com/LukeEvansTech/github-infrastructure/blob/main/docs/MIGRATION.md) + +## Questions or Issues? + +If you encounter any issues with the migration: +1. Check the build logs in GitHub Actions +2. Verify local build works correctly +3. Review the Zensical documentation +4. Compare with the github-infrastructure repository for reference diff --git a/docs/docs/Get-RedditOAuthToken.md b/docs/docs/Get-RedditOAuthToken.md index d43d1ee..69a394d 100644 --- a/docs/docs/Get-RedditOAuthToken.md +++ b/docs/docs/Get-RedditOAuthToken.md @@ -1,47 +1,47 @@ ---- -external help file: PSReddit-help.xml -Module Name: PSReddit -online version: -schema: 2.0.0 ---- - -# Get-RedditOAuthToken - -## SYNOPSIS - -Retrieves an OAuth2 bearer token from Reddit. - -## SYNTAX - -```powershell -Get-RedditOAuthToken [] -``` - -## DESCRIPTION - -Authenticates with Reddit using client credentials from environment variables (REDDIT_CLIENT_ID, REDDIT_CLIENT_SECRET). - -## EXAMPLES - -### EXAMPLE 1 - -```powershell -$token = Get-RedditOAuthToken -``` - -## PARAMETERS - -### CommonParameters - -This cmdlet supports the common parameters: -Debug, -ErrorAction, -ErrorVariable, -InformationAction, -InformationVariable, -OutBuffer, -OutVariable, -PipelineVariable, -Verbose, -WarningAction, -WarningVariable, and -ProgressAction. -For more information, see about_CommonParameters (http://go.microsoft.com/fwlink/?LinkID=113216). - -## INPUTS - -## OUTPUTS - -## NOTES - -Requires environment variables REDDIT_CLIENT_ID and REDDIT_CLIENT_SECRET to be set. - +--- +external help file: PSReddit-help.xml +Module Name: PSReddit +online version: +schema: 2.0.0 +--- + +# Get-RedditOAuthToken + +## SYNOPSIS + +Retrieves an OAuth2 bearer token from Reddit. + +## SYNTAX + +```powershell +Get-RedditOAuthToken [] +``` + +## DESCRIPTION + +Authenticates with Reddit using client credentials from environment variables (REDDIT_CLIENT_ID, REDDIT_CLIENT_SECRET). + +## EXAMPLES + +### EXAMPLE 1 + +```powershell +$token = Get-RedditOAuthToken +``` + +## PARAMETERS + +### CommonParameters + +This cmdlet supports the common parameters: -Debug, -ErrorAction, -ErrorVariable, -InformationAction, -InformationVariable, -OutBuffer, -OutVariable, -PipelineVariable, -Verbose, -WarningAction, -WarningVariable, and -ProgressAction. +For more information, see about_CommonParameters (http://go.microsoft.com/fwlink/?LinkID=113216). + +## INPUTS + +## OUTPUTS + +## NOTES + +Requires environment variables REDDIT_CLIENT_ID and REDDIT_CLIENT_SECRET to be set. + ## RELATED LINKS diff --git a/docs/docs/Get-RedditSubredditPost.md b/docs/docs/Get-RedditSubredditPost.md index d2135c4..9cacf73 100644 --- a/docs/docs/Get-RedditSubredditPost.md +++ b/docs/docs/Get-RedditSubredditPost.md @@ -1,222 +1,222 @@ ---- -external help file: PSReddit-help.xml -Module Name: PSReddit -online version: -schema: 2.0.0 ---- - -# Get-RedditSubredditPost - -## SYNOPSIS - -Retrieves posts from one or more subreddits. - -## SYNTAX - -```powershell -Get-RedditSubredditPost [-Subreddit] [[-Sort] ] [-LastHour] [-LastDay] [-LastWeek] - [-LastMonth] [-LastYear] [-AllTime] [[-Count] ] [-DebugApi] - [] -``` - -## DESCRIPTION - -Uses Reddit's API and OAuth2 to fetch posts from specified subreddit(s). -You can specify the sort type (Top, New, Rising, Hot, Controversial). -Timeframe switches (-LastHour, -LastDay, etc.) only apply to 'Top' and 'Controversial' sorts, per Reddit API rules. -Only one timeframe or sort can be used per call, as the Reddit API does not support combining them. -Use -Count to specify how many posts to retrieve (max 100). - -## EXAMPLES - -### EXAMPLE 1 - -```powershell -Get-RedditSubredditPost -Subreddit 'powershell' -Sort New -Count 10 -``` - -### EXAMPLE 2 - -```powershell -Get-RedditSubredditPost -Subreddit 'powershell' -Sort Top -LastWeek -``` - -## PARAMETERS - -### -Subreddit - -One or more subreddit names (without /r/). - -```yaml -Type: String[] -Parameter Sets: (All) -Aliases: - -Required: True -Position: 1 -Default value: None -Accept pipeline input: False -Accept wildcard characters: False -``` - -### -Sort - -Sort type: Top, New, Rising, Hot, or Controversial. -Default is Top. - -```yaml -Type: String -Parameter Sets: (All) -Aliases: - -Required: False -Position: 2 -Default value: Top -Accept pipeline input: False -Accept wildcard characters: False -``` - -### -LastHour - -Retrieve posts from the last hour (Top/Controversial only). - -```yaml -Type: SwitchParameter -Parameter Sets: (All) -Aliases: - -Required: False -Position: Named -Default value: False -Accept pipeline input: False -Accept wildcard characters: False -``` - -### -LastDay - -Retrieve posts from the last day (default; Top/Controversial only). - -```yaml -Type: SwitchParameter -Parameter Sets: (All) -Aliases: - -Required: False -Position: Named -Default value: False -Accept pipeline input: False -Accept wildcard characters: False -``` - -### -LastWeek - -Retrieve posts from the last week (Top/Controversial only). - -```yaml -Type: SwitchParameter -Parameter Sets: (All) -Aliases: - -Required: False -Position: Named -Default value: False -Accept pipeline input: False -Accept wildcard characters: False -``` - -### -LastMonth - -Retrieve posts from the last month (Top/Controversial only). - -```yaml -Type: SwitchParameter -Parameter Sets: (All) -Aliases: - -Required: False -Position: Named -Default value: False -Accept pipeline input: False -Accept wildcard characters: False -``` - -### -LastYear - -Retrieve posts from the last year (Top/Controversial only). - -```yaml -Type: SwitchParameter -Parameter Sets: (All) -Aliases: - -Required: False -Position: Named -Default value: False -Accept pipeline input: False -Accept wildcard characters: False -``` - -### -AllTime - -Retrieve posts from all time (Top/Controversial only). - -```yaml -Type: SwitchParameter -Parameter Sets: (All) -Aliases: - -Required: False -Position: Named -Default value: False -Accept pipeline input: False -Accept wildcard characters: False -``` - -### -Count - -Number of posts to retrieve per subreddit (max 100, default 25). - -```yaml -Type: Int32 -Parameter Sets: (All) -Aliases: - -Required: False -Position: 3 -Default value: 25 -Accept pipeline input: False -Accept wildcard characters: False -``` - -### -DebugApi - -If specified, outputs verbose debugging information about the API requests and responses. - -```yaml -Type: SwitchParameter -Parameter Sets: (All) -Aliases: - -Required: False -Position: Named -Default value: False -Accept pipeline input: False -Accept wildcard characters: False -``` - -### CommonParameters - -This cmdlet supports the common parameters: -Debug, -ErrorAction, -ErrorVariable, -InformationAction, -InformationVariable, -OutBuffer, -OutVariable, -PipelineVariable, -Verbose, -WarningAction, -WarningVariable, and -ProgressAction. -For more information, see about_CommonParameters (http://go.microsoft.com/fwlink/?LinkID=113216). - -## INPUTS - -## OUTPUTS - -## NOTES - -Reddit's API only allows one sort and (if applicable) one timeframe per request. -Timeframes only apply to 'Top' and 'Controversial' sorts. -Other sorts (New, Rising, Hot) ignore timeframe and always return the latest/rising/hot posts. - +--- +external help file: PSReddit-help.xml +Module Name: PSReddit +online version: +schema: 2.0.0 +--- + +# Get-RedditSubredditPost + +## SYNOPSIS + +Retrieves posts from one or more subreddits. + +## SYNTAX + +```powershell +Get-RedditSubredditPost [-Subreddit] [[-Sort] ] [-LastHour] [-LastDay] [-LastWeek] + [-LastMonth] [-LastYear] [-AllTime] [[-Count] ] [-DebugApi] + [] +``` + +## DESCRIPTION + +Uses Reddit's API and OAuth2 to fetch posts from specified subreddit(s). +You can specify the sort type (Top, New, Rising, Hot, Controversial). +Timeframe switches (-LastHour, -LastDay, etc.) only apply to 'Top' and 'Controversial' sorts, per Reddit API rules. +Only one timeframe or sort can be used per call, as the Reddit API does not support combining them. +Use -Count to specify how many posts to retrieve (max 100). + +## EXAMPLES + +### EXAMPLE 1 + +```powershell +Get-RedditSubredditPost -Subreddit 'powershell' -Sort New -Count 10 +``` + +### EXAMPLE 2 + +```powershell +Get-RedditSubredditPost -Subreddit 'powershell' -Sort Top -LastWeek +``` + +## PARAMETERS + +### -Subreddit + +One or more subreddit names (without /r/). + +```yaml +Type: String[] +Parameter Sets: (All) +Aliases: + +Required: True +Position: 1 +Default value: None +Accept pipeline input: False +Accept wildcard characters: False +``` + +### -Sort + +Sort type: Top, New, Rising, Hot, or Controversial. +Default is Top. + +```yaml +Type: String +Parameter Sets: (All) +Aliases: + +Required: False +Position: 2 +Default value: Top +Accept pipeline input: False +Accept wildcard characters: False +``` + +### -LastHour + +Retrieve posts from the last hour (Top/Controversial only). + +```yaml +Type: SwitchParameter +Parameter Sets: (All) +Aliases: + +Required: False +Position: Named +Default value: False +Accept pipeline input: False +Accept wildcard characters: False +``` + +### -LastDay + +Retrieve posts from the last day (default; Top/Controversial only). + +```yaml +Type: SwitchParameter +Parameter Sets: (All) +Aliases: + +Required: False +Position: Named +Default value: False +Accept pipeline input: False +Accept wildcard characters: False +``` + +### -LastWeek + +Retrieve posts from the last week (Top/Controversial only). + +```yaml +Type: SwitchParameter +Parameter Sets: (All) +Aliases: + +Required: False +Position: Named +Default value: False +Accept pipeline input: False +Accept wildcard characters: False +``` + +### -LastMonth + +Retrieve posts from the last month (Top/Controversial only). + +```yaml +Type: SwitchParameter +Parameter Sets: (All) +Aliases: + +Required: False +Position: Named +Default value: False +Accept pipeline input: False +Accept wildcard characters: False +``` + +### -LastYear + +Retrieve posts from the last year (Top/Controversial only). + +```yaml +Type: SwitchParameter +Parameter Sets: (All) +Aliases: + +Required: False +Position: Named +Default value: False +Accept pipeline input: False +Accept wildcard characters: False +``` + +### -AllTime + +Retrieve posts from all time (Top/Controversial only). + +```yaml +Type: SwitchParameter +Parameter Sets: (All) +Aliases: + +Required: False +Position: Named +Default value: False +Accept pipeline input: False +Accept wildcard characters: False +``` + +### -Count + +Number of posts to retrieve per subreddit (max 100, default 25). + +```yaml +Type: Int32 +Parameter Sets: (All) +Aliases: + +Required: False +Position: 3 +Default value: 25 +Accept pipeline input: False +Accept wildcard characters: False +``` + +### -DebugApi + +If specified, outputs verbose debugging information about the API requests and responses. + +```yaml +Type: SwitchParameter +Parameter Sets: (All) +Aliases: + +Required: False +Position: Named +Default value: False +Accept pipeline input: False +Accept wildcard characters: False +``` + +### CommonParameters + +This cmdlet supports the common parameters: -Debug, -ErrorAction, -ErrorVariable, -InformationAction, -InformationVariable, -OutBuffer, -OutVariable, -PipelineVariable, -Verbose, -WarningAction, -WarningVariable, and -ProgressAction. +For more information, see about_CommonParameters (http://go.microsoft.com/fwlink/?LinkID=113216). + +## INPUTS + +## OUTPUTS + +## NOTES + +Reddit's API only allows one sort and (if applicable) one timeframe per request. +Timeframes only apply to 'Top' and 'Controversial' sorts. +Other sorts (New, Rising, Hot) ignore timeframe and always return the latest/rising/hot posts. + ## RELATED LINKS diff --git a/docs/docs/Get-RedditUserPost.md b/docs/docs/Get-RedditUserPost.md index 5abfe66..308bc21 100644 --- a/docs/docs/Get-RedditUserPost.md +++ b/docs/docs/Get-RedditUserPost.md @@ -1,219 +1,219 @@ ---- -external help file: PSReddit-help.xml -Module Name: PSReddit -online version: -schema: 2.0.0 ---- - -# Get-RedditUserPost - -## SYNOPSIS - -Retrieves posts submitted by specific Reddit users. - -## SYNTAX - -```powershell -Get-RedditUserPost [-Username] [[-Sort] ] [-LastHour] [-LastDay] [-LastWeek] [-LastMonth] - [-LastYear] [-AllTime] [[-Count] ] [-DebugApi] - [] -``` - -## DESCRIPTION - -Uses Reddit's API and OAuth2 to fetch posts made by specified Reddit user(s). -You can specify the sort type (Top, New, Hot, Controversial) and limit the number of posts. -Timeframe switches (-LastHour, -LastDay, etc.) apply to 'Top' and 'Controversial' sorts, per Reddit API rules. - -## EXAMPLES - -### EXAMPLE 1 - -```powershell -Get-RedditUserPost -Username 'LukeEvansTech' -Sort New -Count 10 -``` - -### EXAMPLE 2 - -```powershell -Get-RedditUserPost -Username 'LukeEvansTech' -Sort Top -LastWeek -``` - -## PARAMETERS - -### -Username - -One or more Reddit usernames (without u/). - -```yaml -Type: String[] -Parameter Sets: (All) -Aliases: - -Required: True -Position: 1 -Default value: None -Accept pipeline input: False -Accept wildcard characters: False -``` - -### -Sort - -Sort type: Top, New, Hot, or Controversial. -Default is New. - -```yaml -Type: String -Parameter Sets: (All) -Aliases: - -Required: False -Position: 2 -Default value: New -Accept pipeline input: False -Accept wildcard characters: False -``` - -### -LastHour - -Retrieve posts from the last hour (Top/Controversial only). - -```yaml -Type: SwitchParameter -Parameter Sets: (All) -Aliases: - -Required: False -Position: Named -Default value: False -Accept pipeline input: False -Accept wildcard characters: False -``` - -### -LastDay - -Retrieve posts from the last day (Top/Controversial only). - -```yaml -Type: SwitchParameter -Parameter Sets: (All) -Aliases: - -Required: False -Position: Named -Default value: False -Accept pipeline input: False -Accept wildcard characters: False -``` - -### -LastWeek - -Retrieve posts from the last week (Top/Controversial only). - -```yaml -Type: SwitchParameter -Parameter Sets: (All) -Aliases: - -Required: False -Position: Named -Default value: False -Accept pipeline input: False -Accept wildcard characters: False -``` - -### -LastMonth - -Retrieve posts from the last month (Top/Controversial only). - -```yaml -Type: SwitchParameter -Parameter Sets: (All) -Aliases: - -Required: False -Position: Named -Default value: False -Accept pipeline input: False -Accept wildcard characters: False -``` - -### -LastYear - -Retrieve posts from the last year (Top/Controversial only). - -```yaml -Type: SwitchParameter -Parameter Sets: (All) -Aliases: - -Required: False -Position: Named -Default value: False -Accept pipeline input: False -Accept wildcard characters: False -``` - -### -AllTime - -Retrieve posts from all time (default; Top/Controversial only). - -```yaml -Type: SwitchParameter -Parameter Sets: (All) -Aliases: - -Required: False -Position: Named -Default value: False -Accept pipeline input: False -Accept wildcard characters: False -``` - -### -Count - -Number of posts to retrieve per user (max 100, default 25). - -```yaml -Type: Int32 -Parameter Sets: (All) -Aliases: - -Required: False -Position: 3 -Default value: 25 -Accept pipeline input: False -Accept wildcard characters: False -``` - -### -DebugApi - -If specified, outputs verbose debugging information about the API requests and responses. - -```yaml -Type: SwitchParameter -Parameter Sets: (All) -Aliases: - -Required: False -Position: Named -Default value: False -Accept pipeline input: False -Accept wildcard characters: False -``` - -### CommonParameters - -This cmdlet supports the common parameters: -Debug, -ErrorAction, -ErrorVariable, -InformationAction, -InformationVariable, -OutBuffer, -OutVariable, -PipelineVariable, -Verbose, -WarningAction, -WarningVariable, and -ProgressAction. -For more information, see about_CommonParameters (http://go.microsoft.com/fwlink/?LinkID=113216). - -## INPUTS - -## OUTPUTS - -## NOTES - -Reddit's API only allows one sort and (if applicable) one timeframe per request. -Timeframes only apply to 'Top' and 'Controversial' sorts. - +--- +external help file: PSReddit-help.xml +Module Name: PSReddit +online version: +schema: 2.0.0 +--- + +# Get-RedditUserPost + +## SYNOPSIS + +Retrieves posts submitted by specific Reddit users. + +## SYNTAX + +```powershell +Get-RedditUserPost [-Username] [[-Sort] ] [-LastHour] [-LastDay] [-LastWeek] [-LastMonth] + [-LastYear] [-AllTime] [[-Count] ] [-DebugApi] + [] +``` + +## DESCRIPTION + +Uses Reddit's API and OAuth2 to fetch posts made by specified Reddit user(s). +You can specify the sort type (Top, New, Hot, Controversial) and limit the number of posts. +Timeframe switches (-LastHour, -LastDay, etc.) apply to 'Top' and 'Controversial' sorts, per Reddit API rules. + +## EXAMPLES + +### EXAMPLE 1 + +```powershell +Get-RedditUserPost -Username 'LukeEvansTech' -Sort New -Count 10 +``` + +### EXAMPLE 2 + +```powershell +Get-RedditUserPost -Username 'LukeEvansTech' -Sort Top -LastWeek +``` + +## PARAMETERS + +### -Username + +One or more Reddit usernames (without u/). + +```yaml +Type: String[] +Parameter Sets: (All) +Aliases: + +Required: True +Position: 1 +Default value: None +Accept pipeline input: False +Accept wildcard characters: False +``` + +### -Sort + +Sort type: Top, New, Hot, or Controversial. +Default is New. + +```yaml +Type: String +Parameter Sets: (All) +Aliases: + +Required: False +Position: 2 +Default value: New +Accept pipeline input: False +Accept wildcard characters: False +``` + +### -LastHour + +Retrieve posts from the last hour (Top/Controversial only). + +```yaml +Type: SwitchParameter +Parameter Sets: (All) +Aliases: + +Required: False +Position: Named +Default value: False +Accept pipeline input: False +Accept wildcard characters: False +``` + +### -LastDay + +Retrieve posts from the last day (Top/Controversial only). + +```yaml +Type: SwitchParameter +Parameter Sets: (All) +Aliases: + +Required: False +Position: Named +Default value: False +Accept pipeline input: False +Accept wildcard characters: False +``` + +### -LastWeek + +Retrieve posts from the last week (Top/Controversial only). + +```yaml +Type: SwitchParameter +Parameter Sets: (All) +Aliases: + +Required: False +Position: Named +Default value: False +Accept pipeline input: False +Accept wildcard characters: False +``` + +### -LastMonth + +Retrieve posts from the last month (Top/Controversial only). + +```yaml +Type: SwitchParameter +Parameter Sets: (All) +Aliases: + +Required: False +Position: Named +Default value: False +Accept pipeline input: False +Accept wildcard characters: False +``` + +### -LastYear + +Retrieve posts from the last year (Top/Controversial only). + +```yaml +Type: SwitchParameter +Parameter Sets: (All) +Aliases: + +Required: False +Position: Named +Default value: False +Accept pipeline input: False +Accept wildcard characters: False +``` + +### -AllTime + +Retrieve posts from all time (default; Top/Controversial only). + +```yaml +Type: SwitchParameter +Parameter Sets: (All) +Aliases: + +Required: False +Position: Named +Default value: False +Accept pipeline input: False +Accept wildcard characters: False +``` + +### -Count + +Number of posts to retrieve per user (max 100, default 25). + +```yaml +Type: Int32 +Parameter Sets: (All) +Aliases: + +Required: False +Position: 3 +Default value: 25 +Accept pipeline input: False +Accept wildcard characters: False +``` + +### -DebugApi + +If specified, outputs verbose debugging information about the API requests and responses. + +```yaml +Type: SwitchParameter +Parameter Sets: (All) +Aliases: + +Required: False +Position: Named +Default value: False +Accept pipeline input: False +Accept wildcard characters: False +``` + +### CommonParameters + +This cmdlet supports the common parameters: -Debug, -ErrorAction, -ErrorVariable, -InformationAction, -InformationVariable, -OutBuffer, -OutVariable, -PipelineVariable, -Verbose, -WarningAction, -WarningVariable, and -ProgressAction. +For more information, see about_CommonParameters (http://go.microsoft.com/fwlink/?LinkID=113216). + +## INPUTS + +## OUTPUTS + +## NOTES + +Reddit's API only allows one sort and (if applicable) one timeframe per request. +Timeframes only apply to 'Top' and 'Controversial' sorts. + ## RELATED LINKS diff --git a/docs/docs/assets/stylesheets/custom.css b/docs/docs/assets/stylesheets/custom.css index df2845a..6573741 100644 --- a/docs/docs/assets/stylesheets/custom.css +++ b/docs/docs/assets/stylesheets/custom.css @@ -1,14 +1,14 @@ -/* Custom styles for PSReddit documentation */ - -/* Hide Zensical branding in footer */ -.md-footer-meta__inner .md-copyright a[href*="zensical"] { - display: none !important; -} - -.md-footer-meta__inner .md-copyright > br { - display: none !important; -} - -.md-footer-meta__inner .md-copyright > a[href*="zensical"] ~ * { - display: none !important; -} +/* Custom styles for PSReddit documentation */ + +/* Hide Zensical branding in footer */ +.md-footer-meta__inner .md-copyright a[href*="zensical"] { + display: none !important; +} + +.md-footer-meta__inner .md-copyright > br { + display: none !important; +} + +.md-footer-meta__inner .md-copyright > a[href*="zensical"] ~ * { + display: none !important; +} diff --git a/docs/docs/module-manifest.md b/docs/docs/module-manifest.md index c3d30e5..99c429d 100644 --- a/docs/docs/module-manifest.md +++ b/docs/docs/module-manifest.md @@ -1,21 +1,21 @@ ---- -Module Name: PSReddit -Module Guid: b8f3e5d1-2b7b-4e8b-9c6e-123456789abc -Download Help Link: NA -Help Version: 0.1.0 -Locale: en-US ---- - -# PSReddit Module -## Description -A PowerShell module (PSReddit) for authenticating with Reddit and retrieving posts. - -## PSReddit Cmdlets -### [Get-RedditOAuthToken](Get-RedditOAuthToken.md) -Retrieves an OAuth2 bearer token from Reddit. - -### [Get-RedditSubredditPost](Get-RedditSubredditPost.md) -Retrieves posts from one or more subreddits. - -### [Get-RedditUserPost](Get-RedditUserPost.md) +--- +Module Name: PSReddit +Module Guid: b8f3e5d1-2b7b-4e8b-9c6e-123456789abc +Download Help Link: NA +Help Version: 0.1.0 +Locale: en-US +--- + +# PSReddit Module +## Description +A PowerShell module (PSReddit) for authenticating with Reddit and retrieving posts. + +## PSReddit Cmdlets +### [Get-RedditOAuthToken](Get-RedditOAuthToken.md) +Retrieves an OAuth2 bearer token from Reddit. + +### [Get-RedditSubredditPost](Get-RedditSubredditPost.md) +Retrieves posts from one or more subreddits. + +### [Get-RedditUserPost](Get-RedditUserPost.md) Retrieves posts submitted by specific Reddit users. diff --git a/docs/package.json b/docs/package.json index 36e0f5e..f14b689 100644 --- a/docs/package.json +++ b/docs/package.json @@ -1,14 +1,14 @@ -{ - "name": "psreddit-docs", - "version": "0.0.0", - "private": true, - "scripts": { - "start": "zensical serve", - "build": "zensical build", - "serve": "zensical serve", - "clean": "zensical build --clean" - }, - "engines": { - "node": ">=20.0" - } -} +{ + "name": "psreddit-docs", + "version": "0.0.0", + "private": true, + "scripts": { + "start": "zensical serve", + "build": "zensical build", + "serve": "zensical serve", + "clean": "zensical build --clean" + }, + "engines": { + "node": ">=20.0" + } +} diff --git a/src/MarkdownRepair.ps1 b/src/MarkdownRepair.ps1 index cedfac8..c973c64 100644 --- a/src/MarkdownRepair.ps1 +++ b/src/MarkdownRepair.ps1 @@ -1,136 +1,136 @@ -<# -.SYNOPSIS - Repair PlatyPS generated markdown files. -.NOTES - This file is temporarily required to handle platyPS help generation. - https://github.com/PowerShell/platyPS/issues/595 - This is a result of a breaking change introduced in PowerShell 7.4.0: - https://learn.microsoft.com/en-us/powershell/scripting/whats-new/what-s-new-in-powershell-74?view=powershell-7.4 - Breaking Changes: Added the ProgressAction parameter to the Common Parameters - modified from source: https://github.com/PowerShell/platyPS/issues/595#issuecomment-1820971702 -#> - -function Remove-CommonParameterFromMarkdown { - [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseShouldProcessForStateChangingFunctions', '')] - <# - .SYNOPSIS - Remove a PlatyPS generated parameter block. - .DESCRIPTION - Removes parameter block for the provided parameter name from the markdown file provided. - #> - param( - [Parameter(Mandatory)] - [string[]] - $Path, - - [Parameter(Mandatory = $false)] - [string[]] - $ParameterName = @('ProgressAction') - ) - $ErrorActionPreference = 'Stop' - foreach ($p in $Path) { - $content = (Get-Content -Path $p -Raw).TrimEnd() - $updateFile = $false - foreach ($param in $ParameterName) { - if (-not ($Param.StartsWith('-'))) { - $param = "-$($param)" - } - # Remove the parameter block - $pattern = "(?m)^### $param\r?\n[\S\s]*?(?=#{2,3}?)" - $newContent = $content -replace $pattern, '' - # Remove the parameter from the syntax block - $pattern = " \[$param\s?.*?]" - $newContent = $newContent -replace $pattern, '' - if ($null -ne (Compare-Object -ReferenceObject $content -DifferenceObject $newContent)) { - Write-Verbose "Added $param to $p" - # Update file content - $content = $newContent - $updateFile = $true - } - } - # Save file if content has changed - if ($updateFile) { - $newContent | Out-File -Encoding utf8 -FilePath $p - Write-Verbose "Updated file: $p" - } - } - return -} - -function Add-MissingCommonParameterToMarkdown { - param( - [Parameter(Mandatory)] - [string[]] - $Path, - - [Parameter(Mandatory = $false)] - [string[]] - $ParameterName = @('ProgressAction') - ) - $ErrorActionPreference = 'Stop' - foreach ($p in $Path) { - $content = (Get-Content -Path $p -Raw).TrimEnd() - $updateFile = $false - foreach ($NewParameter in $ParameterName) { - if (-not ($NewParameter.StartsWith('-'))) { - $NewParameter = "-$($NewParameter)" - } - $pattern = '(?m)^This cmdlet supports the common parameters:(.+?)\.' - $replacement = { - $Params = $_.Groups[1].Captures[0].ToString() -split ' ' - $CommonParameters = @() - foreach ($CommonParameter in $Params) { - if ($CommonParameter.StartsWith('-')) { - if ($CommonParameter.EndsWith(',')) { - $CleanParam = $CommonParameter.Substring(0, $CommonParameter.Length - 1) - } - elseif ($p.EndsWith('.')) { - $CleanParam = $CommonParameter.Substring(0, $CommonParameter.Length - 1) - } - else { - $CleanParam = $CommonParameter - } - $CommonParameters += $CleanParam - } - } - if ($NewParameter -notin $CommonParameters) { - $CommonParameters += $NewParameter - } - $CommonParameters[-1] = "and $($CommonParameters[-1]). " - return "This cmdlet supports the common parameters: " + (($CommonParameters | Sort-Object) -join ', ') - } - $newContent = $content -replace $pattern, $replacement - if ($null -ne (Compare-Object -ReferenceObject $content -DifferenceObject $newContent)) { - Write-Verbose "Added $NewParameter to $p" - $updateFile = $true - $content = $newContent - } - } - # Save file if content has changed - if ($updateFile) { - $newContent | Out-File -Encoding utf8 -FilePath $p - Write-Verbose "Updated file: $p" - } - } - return -} - -function Repair-PlatyPSMarkdown { - param( - [Parameter(Mandatory)] - [string[]] - $Path, - - [Parameter()] - [string[]] - $ParameterName = @('ProgressAction') - ) - $ErrorActionPreference = 'Stop' - $Parameters = @{ - Path = $Path - ParameterName = $ParameterName - } - $null = Remove-CommonParameterFromMarkdown @Parameters - $null = Add-MissingCommonParameterToMarkdown @Parameters - return -} +<# +.SYNOPSIS + Repair PlatyPS generated markdown files. +.NOTES + This file is temporarily required to handle platyPS help generation. + https://github.com/PowerShell/platyPS/issues/595 + This is a result of a breaking change introduced in PowerShell 7.4.0: + https://learn.microsoft.com/en-us/powershell/scripting/whats-new/what-s-new-in-powershell-74?view=powershell-7.4 + Breaking Changes: Added the ProgressAction parameter to the Common Parameters + modified from source: https://github.com/PowerShell/platyPS/issues/595#issuecomment-1820971702 +#> + +function Remove-CommonParameterFromMarkdown { + [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseShouldProcessForStateChangingFunctions', '')] + <# + .SYNOPSIS + Remove a PlatyPS generated parameter block. + .DESCRIPTION + Removes parameter block for the provided parameter name from the markdown file provided. + #> + param( + [Parameter(Mandatory)] + [string[]] + $Path, + + [Parameter(Mandatory = $false)] + [string[]] + $ParameterName = @('ProgressAction') + ) + $ErrorActionPreference = 'Stop' + foreach ($p in $Path) { + $content = (Get-Content -Path $p -Raw).TrimEnd() + $updateFile = $false + foreach ($param in $ParameterName) { + if (-not ($Param.StartsWith('-'))) { + $param = "-$($param)" + } + # Remove the parameter block + $pattern = "(?m)^### $param\r?\n[\S\s]*?(?=#{2,3}?)" + $newContent = $content -replace $pattern, '' + # Remove the parameter from the syntax block + $pattern = " \[$param\s?.*?]" + $newContent = $newContent -replace $pattern, '' + if ($null -ne (Compare-Object -ReferenceObject $content -DifferenceObject $newContent)) { + Write-Verbose "Added $param to $p" + # Update file content + $content = $newContent + $updateFile = $true + } + } + # Save file if content has changed + if ($updateFile) { + $newContent | Out-File -Encoding utf8 -FilePath $p + Write-Verbose "Updated file: $p" + } + } + return +} + +function Add-MissingCommonParameterToMarkdown { + param( + [Parameter(Mandatory)] + [string[]] + $Path, + + [Parameter(Mandatory = $false)] + [string[]] + $ParameterName = @('ProgressAction') + ) + $ErrorActionPreference = 'Stop' + foreach ($p in $Path) { + $content = (Get-Content -Path $p -Raw).TrimEnd() + $updateFile = $false + foreach ($NewParameter in $ParameterName) { + if (-not ($NewParameter.StartsWith('-'))) { + $NewParameter = "-$($NewParameter)" + } + $pattern = '(?m)^This cmdlet supports the common parameters:(.+?)\.' + $replacement = { + $Params = $_.Groups[1].Captures[0].ToString() -split ' ' + $CommonParameters = @() + foreach ($CommonParameter in $Params) { + if ($CommonParameter.StartsWith('-')) { + if ($CommonParameter.EndsWith(',')) { + $CleanParam = $CommonParameter.Substring(0, $CommonParameter.Length - 1) + } + elseif ($p.EndsWith('.')) { + $CleanParam = $CommonParameter.Substring(0, $CommonParameter.Length - 1) + } + else { + $CleanParam = $CommonParameter + } + $CommonParameters += $CleanParam + } + } + if ($NewParameter -notin $CommonParameters) { + $CommonParameters += $NewParameter + } + $CommonParameters[-1] = "and $($CommonParameters[-1]). " + return "This cmdlet supports the common parameters: " + (($CommonParameters | Sort-Object) -join ', ') + } + $newContent = $content -replace $pattern, $replacement + if ($null -ne (Compare-Object -ReferenceObject $content -DifferenceObject $newContent)) { + Write-Verbose "Added $NewParameter to $p" + $updateFile = $true + $content = $newContent + } + } + # Save file if content has changed + if ($updateFile) { + $newContent | Out-File -Encoding utf8 -FilePath $p + Write-Verbose "Updated file: $p" + } + } + return +} + +function Repair-PlatyPSMarkdown { + param( + [Parameter(Mandatory)] + [string[]] + $Path, + + [Parameter()] + [string[]] + $ParameterName = @('ProgressAction') + ) + $ErrorActionPreference = 'Stop' + $Parameters = @{ + Path = $Path + ParameterName = $ParameterName + } + $null = Remove-CommonParameterFromMarkdown @Parameters + $null = Add-MissingCommonParameterToMarkdown @Parameters + return +} diff --git a/src/PSReddit.Settings.ps1 b/src/PSReddit.Settings.ps1 index a0e647a..dc0e7d9 100644 --- a/src/PSReddit.Settings.ps1 +++ b/src/PSReddit.Settings.ps1 @@ -1,2 +1,2 @@ -# specify the minimum required major PowerShell version that the build script should validate -[version]$script:requiredPSVersion = '5.1.0' +# specify the minimum required major PowerShell version that the build script should validate +[version]$script:requiredPSVersion = '5.1.0' diff --git a/src/PSReddit.build.ps1 b/src/PSReddit.build.ps1 index 0a2e952..c6c064f 100644 --- a/src/PSReddit.build.ps1 +++ b/src/PSReddit.build.ps1 @@ -1,618 +1,618 @@ -<# -.SYNOPSIS - An Invoke-Build Build file. -.DESCRIPTION - Build steps can include: - - ValidateRequirements - - ImportModuleManifest - - Clean - - Analyze - - FormattingCheck - - Test - - DevCC - - CreateHelpStart - - Build - - IntegrationTest - - Archive -.EXAMPLE - Invoke-Build - - This will perform the default build Add-BuildTasks: see below for the default Add-BuildTask execution -.EXAMPLE - Invoke-Build -Add-BuildTask Analyze,Test - - This will perform only the Analyze and Test Add-BuildTasks. -.NOTES - This build file by Catesta will pull in configurations from the ".Settings.ps1" file as well, where users can more easily customize the build process if required. - https://github.com/nightroman/Invoke-Build - https://github.com/nightroman/Invoke-Build/wiki/Build-Scripts-Guidelines - If using VSCode you can use the generated tasks.json to execute the various tasks in this build file. - Ctrl + P | then type task (add space) - you will then be presented with a list of available tasks to run - The 'InstallDependencies' Add-BuildTask isn't present here. - Module dependencies are installed at a previous step in the pipeline. - If your manifest has module dependencies include all required modules in your CI/CD bootstrap file: - AWS - install_modules.ps1 - Azure - actions_bootstrap.ps1 - GitHub Actions - actions_bootstrap.ps1 - AppVeyor - actions_bootstrap.ps1 -#> - -#Include: Settings -$ModuleName = [regex]::Match((Get-Item $BuildFile).Name, '^(.*)\.build\.ps1$').Groups[1].Value -. "./$ModuleName.Settings.ps1" - -function Test-ManifestBool ($Path) { - Get-ChildItem $Path | Test-ModuleManifest -ErrorAction SilentlyContinue | Out-Null; $? -} - -#Default Build -$str = @() -$str = 'Clean', 'ValidateRequirements', 'ImportModuleManifest' -$str += 'FormattingCheck' -$str += 'Analyze', 'Test' -$str += 'CreateHelpStart' -$str2 = $str -$str2 += 'Build', 'Archive' -$str += 'Build', 'IntegrationTest', 'Archive' -Add-BuildTask -Name . -Jobs $str - -#Local testing build process -Add-BuildTask TestLocal Clean, ImportModuleManifest, Analyze, Test - -#Local help file creation process -Add-BuildTask HelpLocal Clean, ImportModuleManifest, CreateHelpStart - -#Full build sans integration tests -Add-BuildTask BuildNoIntegration -Jobs $str2 - -# Pre-build variables to be used by other portions of the script -Enter-Build { - $script:ModuleName = [regex]::Match((Get-Item $BuildFile).Name, '^(.*)\.build\.ps1$').Groups[1].Value - - # Identify other required paths - $script:ModuleSourcePath = Join-Path -Path $BuildRoot -ChildPath $script:ModuleName - $script:ModuleFiles = Join-Path -Path $script:ModuleSourcePath -ChildPath '*' - - $script:ModuleManifestFile = Join-Path -Path $script:ModuleSourcePath -ChildPath "$($script:ModuleName).psd1" - - $manifestInfo = Import-PowerShellDataFile -Path $script:ModuleManifestFile - $script:ModuleVersion = $manifestInfo.ModuleVersion - $script:ModuleDescription = $manifestInfo.Description - $script:FunctionsToExport = $manifestInfo.FunctionsToExport - - $script:TestsPath = Join-Path -Path $BuildRoot -ChildPath 'Tests' - $script:UnitTestsPath = Join-Path -Path $script:TestsPath -ChildPath 'Unit' - $script:IntegrationTestsPath = Join-Path -Path $script:TestsPath -ChildPath 'Integration' - - $script:ArtifactsPath = Join-Path -Path $BuildRoot -ChildPath 'Artifacts' - $script:ArchivePath = Join-Path -Path $BuildRoot -ChildPath 'Archive' - - $script:BuildModuleRootFile = Join-Path -Path $script:ArtifactsPath -ChildPath "$($script:ModuleName).psm1" - - # Ensure our builds fail until if below a minimum defined code test coverage threshold - $script:coverageThreshold = 30 - - [version]$script:MinPesterVersion = '5.2.2' - [version]$script:MaxPesterVersion = '5.99.99' - $script:testOutputFormat = 'NUnitXML' -} #Enter-Build - -# Define headers as separator, task path, synopsis, and location, e.g. for Ctrl+Click in VSCode. -# Also change the default color to Green. If you need task start times, use `$Task.Started`. -Set-BuildHeader { - param($Path) - # separator line - Write-Build DarkMagenta ('=' * 79) - # default header + synopsis - Write-Build DarkGray "Task $Path : $(Get-BuildSynopsis $Task)" - # task location in a script - Write-Build DarkGray "At $($Task.InvocationInfo.ScriptName):$($Task.InvocationInfo.ScriptLineNumber)" - Write-Build Yellow "Manifest File: $script:ModuleManifestFile" - Write-Build Yellow "Manifest Version: $($manifestInfo.ModuleVersion)" -} #Set-BuildHeader - -# Define footers similar to default but change the color to DarkGray. -Set-BuildFooter { - param($Path) - Write-Build DarkGray "Done $Path, $($Task.Elapsed)" - # # separator line - # Write-Build Gray ('=' * 79) -} #Set-BuildFooter - -#Synopsis: Validate system requirements are met -Add-BuildTask ValidateRequirements { - # this setting comes from the *.Settings.ps1 - Write-Build White " Verifying at least PowerShell $script:requiredPSVersion..." - Assert-Build ($PSVersionTable.PSVersion -ge $script:requiredPSVersion) "At least Powershell $script:requiredPSVersion is required for this build to function properly" - Write-Build Green ' ...Verification Complete!' -} #ValidateRequirements - -# Synopsis: Import the current module manifest file for processing -Add-BuildTask TestModuleManifest -Before ImportModuleManifest { - Write-Build White ' Running module manifest tests...' - Assert-Build (Test-Path $script:ModuleManifestFile) 'Unable to locate the module manifest file.' - Assert-Build (Test-ManifestBool -Path $script:ModuleManifestFile) 'Module Manifest test did not pass verification.' - Write-Build Green ' ...Module Manifest Verification Complete!' -} #f5b33218-bde4-4028-b2a1-9c206f089503 - -# Synopsis: Load the module project -Add-BuildTask ImportModuleManifest { - Write-Build White ' Attempting to load the project module.' - try { - Import-Module $script:ModuleManifestFile -Force -PassThru -ErrorAction Stop - } catch { - Write-Build Red " ...$_`n" - throw 'Unable to load the project module' - } - Write-Build Green " ...$script:ModuleName imported successfully" -} - -#Synopsis: Clean and reset Artifacts/Archive Directory -Add-BuildTask Clean { - Write-Build White ' Clean up our Artifacts/Archive directory...' - - $null = Remove-Item $script:ArtifactsPath -Force -Recurse -ErrorAction 0 - $null = New-Item $script:ArtifactsPath -ItemType:Directory - $null = Remove-Item $script:ArchivePath -Force -Recurse -ErrorAction 0 - $null = New-Item $script:ArchivePath -ItemType:Directory - - Write-Build Green ' ...Clean Complete!' -} #Clean - -#Synopsis: Invokes PSScriptAnalyzer against the Module source path -Add-BuildTask Analyze { - - $scriptAnalyzerParams = @{ - Path = $script:ModuleSourcePath - Setting = 'PSScriptAnalyzerSettings.psd1' - Recurse = $true - Verbose = $false - } - - Write-Build White ' Performing Module ScriptAnalyzer checks...' - $scriptAnalyzerResults = Invoke-ScriptAnalyzer @scriptAnalyzerParams - - if ($scriptAnalyzerResults) { - $scriptAnalyzerResults | Format-Table - throw ' One or more PSScriptAnalyzer errors/warnings where found.' - } else { - Write-Build Green ' ...Module Analyze Complete!' - } -} #Analyze - -#Synopsis: Invokes Script Analyzer against the Tests path if it exists -Add-BuildTask AnalyzeTests -After Analyze { - if (Test-Path -Path $script:TestsPath) { - - $scriptAnalyzerParams = @{ - Path = $script:TestsPath - Setting = 'PSScriptAnalyzerSettings.psd1' - ExcludeRule = 'PSUseDeclaredVarsMoreThanAssignments' - Recurse = $true - Verbose = $false - } - - Write-Build White ' Performing Test ScriptAnalyzer checks...' - $scriptAnalyzerResults = Invoke-ScriptAnalyzer @scriptAnalyzerParams - - if ($scriptAnalyzerResults) { - $scriptAnalyzerResults | Format-Table - throw ' One or more PSScriptAnalyzer errors/warnings where found.' - } else { - Write-Build Green ' ...Test Analyze Complete!' - } - } -} #AnalyzeTests - -#Synopsis: Analyze scripts to verify if they adhere to desired coding format (Stroustrup / OTBS / Allman) -Add-BuildTask FormattingCheck { - - - $scriptAnalyzerParams = @{ - Setting = 'CodeFormattingOTBS' - ExcludeRule = 'PSUseConsistentWhitespace' - Recurse = $true - Verbose = $false - } - - - Write-Build White ' Performing script formatting checks...' - $scriptAnalyzerResults = Get-ChildItem -Path $script:ModuleSourcePath -Exclude "*.psd1" | Invoke-ScriptAnalyzer @scriptAnalyzerParams - - if ($scriptAnalyzerResults) { - $scriptAnalyzerResults | Format-Table - throw ' PSScriptAnalyzer code formatting check did not adhere to {0} standards' -f $scriptAnalyzerParams.Setting - } else { - Write-Build Green ' ...Formatting Analyze Complete!' - } -} #FormattingCheck - -#Synopsis: Invokes all Pester Unit Tests in the Tests\Unit folder (if it exists) -Add-BuildTask Test { - - Write-Build White " Importing desired Pester version. Min: $script:MinPesterVersion Max: $script:MaxPesterVersion" - Remove-Module -Name Pester -Force -ErrorAction 'SilentlyContinue'# there are instances where some containers have Pester already in the session - Import-Module -Name Pester -MinimumVersion $script:MinPesterVersion -MaximumVersion $script:MaxPesterVersion -ErrorAction 'Stop' - - $codeCovPath = "$script:ArtifactsPath\ccReport\" - $testOutPutPath = "$script:ArtifactsPath\testOutput\" - if (-not(Test-Path $codeCovPath)) { - New-Item -Path $codeCovPath -ItemType Directory | Out-Null - } - if (-not(Test-Path $testOutPutPath)) { - New-Item -Path $testOutPutPath -ItemType Directory | Out-Null - } - if (Test-Path -Path $script:UnitTestsPath) { - $pesterConfiguration = New-PesterConfiguration - $pesterConfiguration.run.Path = $script:UnitTestsPath - $pesterConfiguration.Run.PassThru = $true - $pesterConfiguration.Run.Exit = $false - $pesterConfiguration.CodeCoverage.Enabled = $true - $pesterConfiguration.CodeCoverage.Path = "$script:ModuleSourcePath\*\*.ps1" - $pesterConfiguration.CodeCoverage.CoveragePercentTarget = $script:coverageThreshold - $pesterConfiguration.CodeCoverage.OutputPath = "$codeCovPath\CodeCoverage.xml" - $pesterConfiguration.CodeCoverage.OutputFormat = 'JaCoCo' - $pesterConfiguration.TestResult.Enabled = $true - $pesterConfiguration.TestResult.OutputPath = "$testOutPutPath\PesterTests.xml" - $pesterConfiguration.TestResult.OutputFormat = $script:testOutputFormat - $pesterConfiguration.Output.Verbosity = 'Detailed' - - Write-Build White ' Performing Pester Unit Tests...' - # Publish Test Results - $testResults = Invoke-Pester -Configuration $pesterConfiguration - - # This will output a nice json for each failed test (if running in CodeBuild) - if ($env:CODEBUILD_BUILD_ARN) { - $testResults.TestResult | ForEach-Object { - if ($_.Result -ne 'Passed') { - ConvertTo-Json -InputObject $_ -Compress - } - } - } - - $numberFails = $testResults.FailedCount - Assert-Build($numberFails -eq 0) ('Failed "{0}" unit tests.' -f $numberFails) - - Write-Build Gray (' ...CODE COVERAGE - CommandsExecutedCount: {0}' -f $testResults.CodeCoverage.CommandsExecutedCount) - Write-Build Gray (' ...CODE COVERAGE - CommandsAnalyzedCount: {0}' -f $testResults.CodeCoverage.CommandsAnalyzedCount) - - if ($testResults.CodeCoverage.NumberOfCommandsExecuted -ne 0) { - $coveragePercent = '{0:N2}' -f ($testResults.CodeCoverage.CommandsExecutedCount / $testResults.CodeCoverage.CommandsAnalyzedCount * 100) - - <# - if ($testResults.CodeCoverage.NumberOfCommandsMissed -gt 0) { - 'Failed to analyze "{0}" commands' -f $testResults.CodeCoverage.NumberOfCommandsMissed - } - Write-Host "PowerShell Commands not tested:`n$(ConvertTo-Json -InputObject $testResults.CodeCoverage.MissedCommands)" - #> - if ([Int]$coveragePercent -lt $coverageThreshold) { - throw ('Failed to meet code coverage threshold of {0}% with only {1}% coverage' -f $coverageThreshold, $coveragePercent) - } else { - Write-Build Cyan " $('Covered {0}% of {1} analyzed commands in {2} files.' -f $coveragePercent,$testResults.CodeCoverage.CommandsAnalyzedCount,$testResults.CodeCoverage.FilesAnalyzedCount)" - Write-Build Green ' ...Pester Unit Tests Complete!' - } - } else { - # account for new module build condition - Write-Build Yellow ' Code coverage check skipped. No commands to execute...' - } - - } -} #Test - -#Synopsis: Used primarily during active development to generate xml file to graphically display code coverage in VSCode using Coverage Gutters -Add-BuildTask DevCC { - Write-Build White ' Generating code coverage report at root...' - Write-Build White " Importing desired Pester version. Min: $script:MinPesterVersion Max: $script:MaxPesterVersion" - Remove-Module -Name Pester -Force -ErrorAction SilentlyContinue # there are instances where some containers have Pester already in the session - Import-Module -Name Pester -MinimumVersion $script:MinPesterVersion -MaximumVersion $script:MaxPesterVersion -ErrorAction 'Stop' - $pesterConfiguration = New-PesterConfiguration - $pesterConfiguration.run.Path = $script:UnitTestsPath - $pesterConfiguration.CodeCoverage.Enabled = $true - $pesterConfiguration.CodeCoverage.Path = "$PSScriptRoot\$ModuleName\*\*.ps1" - $pesterConfiguration.CodeCoverage.CoveragePercentTarget = $script:coverageThreshold - $pesterConfiguration.CodeCoverage.OutputPath = '..\..\..\cov.xml' - $pesterConfiguration.CodeCoverage.OutputFormat = 'CoverageGutters' - - Invoke-Pester -Configuration $pesterConfiguration - Write-Build Green ' ...Code Coverage report generated!' -} #DevCC - -# Synopsis: Build help for module -Add-BuildTask CreateHelpStart { - Write-Build White ' Performing all help related actions.' - - Write-Build Gray ' Importing platyPS v0.12.0 ...' - Import-Module platyPS -RequiredVersion 0.12.0 -ErrorAction Stop - Write-Build Gray ' ...platyPS imported successfully.' -} #CreateHelpStart - -# Synopsis: Build markdown help files for module and fail if help information is missing -Add-BuildTask CreateMarkdownHelp -After CreateHelpStart { - $ModulePage = "$script:ArtifactsPath\docs\$($ModuleName).md" - - $markdownParams = @{ - Module = $ModuleName - OutputFolder = "$script:ArtifactsPath\docs\" - Force = $true - WithModulePage = $true - Locale = 'en-US' - FwLink = "NA" - HelpVersion = $script:ModuleVersion - } - - Write-Build Gray ' Generating markdown files...' - $null = New-MarkdownHelp @markdownParams - Write-Build Gray ' ...Markdown generation completed.' - - Write-Build Gray ' Replacing markdown elements...' - Write-Build DarkGray ' Replace multi-line EXAMPLES' - $OutputDir = "$script:ArtifactsPath\docs\" - $OutputDir | Get-ChildItem -File | ForEach-Object { - # fix formatting in multiline examples - $content = Get-Content $_.FullName -Raw - $newContent = $content -replace '(## EXAMPLE [^`]+?```\r\n[^`\r\n]+?\r\n)(```\r\n\r\n)([^#]+?\r\n)(\r\n)([^#]+)(#)', '$1$3$2$4$5$6' - if ($newContent -ne $content) { - Set-Content -Path $_.FullName -Value $newContent -Force - } - } - Write-Build DarkGray ' Replace each missing element we need for a proper generic module page .md file' - $ModulePageFileContent = Get-Content -Raw $ModulePage - $ModulePageFileContent = $ModulePageFileContent -replace '{{Manually Enter Description Here}}', $script:ModuleDescription - $script:FunctionsToExport | ForEach-Object { - Write-Build DarkGray " Updating definition for the following function: $($_)" - $TextToReplace = "{{Manually Enter $($_) Description Here}}" - $ReplacementText = (Get-Help -Detailed $_).Synopsis - $ModulePageFileContent = $ModulePageFileContent -replace $TextToReplace, $ReplacementText - } - Write-Build DarkGray ' Evaluating if running 7.4.0 or higher...' - # https://github.com/PowerShell/platyPS/issues/595 - if ($PSVersionTable.PSVersion -ge [version]'7.4.0') { - Write-Build DarkGray ' Performing Markdown repair' - # dot source markdown repair - . $BuildRoot\MarkdownRepair.ps1 - $OutputDir | Get-ChildItem -File | ForEach-Object { - Repair-PlatyPSMarkdown -Path $_.FullName - } - } - Write-Build DarkGray ' Add blank line after headers.' - $OutputDir | Get-ChildItem -File | ForEach-Object { - $content = Get-Content $_.FullName -Raw - $newContent = $content -replace '(?m)^(#{1,6}\s+.*)$\r?\n(?!\r?\n)', "`$1`r`n" - # $newContent = $content -replace '(?m)^(#{1,6}\s+.+?)$\r?\n(?!\r?\n)', "`$1`n" - # $newContent = $content -replace '(?m)^(#{1,6}\s+.*)$\r?\n(?!\r?\n)', "`$1`n" - if ($newContent -ne $content) { - Set-Content -Path $_.FullName -Value $newContent -Force - } - } - # *NOTE: it is not possible to adjust fenced code block at this location as conversion to MAML does not support language tags. - - $ModulePageFileContent | Out-File $ModulePage -Force -Encoding:utf8 - Write-Build Gray ' ...Markdown replacements complete.' - - Write-Build Gray ' Verifying GUID...' - $MissingGUID = Select-String -Path "$script:ArtifactsPath\docs\*.md" -Pattern "(00000000-0000-0000-0000-000000000000)" - if ($MissingGUID.Count -gt 0) { - Write-Build Yellow ' The documentation that got generated resulted in a generic GUID. Check the GUID entry of your module manifest.' - throw 'Missing GUID. Please review and rebuild.' - } - - Write-Build Gray ' Checking for missing documentation in md files...' - $MissingDocumentation = Select-String -Path "$script:ArtifactsPath\docs\*.md" -Pattern "({{.*}})" - if ($MissingDocumentation.Count -gt 0) { - Write-Build Yellow ' The documentation that got generated resulted in missing sections which should be filled out.' - Write-Build Yellow ' Please review the following sections in your comment based help, fill out missing information and rerun this build:' - Write-Build Yellow ' (Note: This can happen if the .EXTERNALHELP CBH is defined for a function before running this build.)' - Write-Build Yellow " Path of files with issues: $script:ArtifactsPath\docs\" - $MissingDocumentation | Select-Object FileName, LineNumber, Line | Format-Table -AutoSize - throw 'Missing documentation. Please review and rebuild.' - } - - Write-Build Gray ' Checking for missing SYNOPSIS in md files...' - $fSynopsisOutput = @() - # $synopsisEval = Select-String -Path "$script:ArtifactsPath\docs\*.md" -Pattern "^## SYNOPSIS$" -Context 0, 1 - $synopsisEval = Select-String -Path "$script:ArtifactsPath\docs\*.md" -Pattern "^## SYNOPSIS$\r?\n$" -Context 0, 2 - $synopsisEval | ForEach-Object { - $chAC = $_.Context.DisplayPostContext.ToCharArray() - if ($null -eq $chAC) { - $fSynopsisOutput += $_.FileName - } - } - if ($fSynopsisOutput) { - Write-Build Yellow " The following files are missing SYNOPSIS:" - $fSynopsisOutput - throw 'SYNOPSIS information missing. Please review.' - } - - Write-Build Gray ' ...Markdown generation complete.' -} #CreateMarkdownHelp - -# Synopsis: Build the external xml help file from markdown help files with PlatyPS -Add-BuildTask CreateExternalHelp -After CreateMarkdownHelp { - Write-Build Gray ' Creating external xml help file...' - $null = New-ExternalHelp "$script:ArtifactsPath\docs" -OutputPath "$script:ArtifactsPath\en-US\" -Force - Write-Build Gray ' ...External xml help file created!' -} #CreateExternalHelp - -Add-BuildTask CreateHelpComplete -After CreateExternalHelp { - Write-Build Gray ' Finalizing markdown documentation now that external help has been created...' - Write-Build DarkGray ' Add powershell language to unspecified fenced code blocks.' - - $OutputDir = "$script:ArtifactsPath\docs\" - - Get-ChildItem -Path $OutputDir -File | ForEach-Object { - $lines = Get-Content -Path $_.FullName - $insideCodeBlock = $false - - for ($i = 0; $i -lt $lines.Count; $i++) { - # Regex captures everything after triple backticks (if present). - # e.g. ```yaml => captured group = "yaml" - # ``` => captured group = "" - if ($lines[$i] -match '^\s*```(\S*)\s*$') { - $lang = $Matches[1] - - if (-not $insideCodeBlock) { - # We found an opening fence - if ([string]::IsNullOrWhiteSpace($lang)) { - # Bare triple backticks => add powershell - $lines[$i] = '```powershell' - } - # Toggle "inside code block" on - $insideCodeBlock = $true - } else { - # We found the closing fence -> set $insideCodeBlock off - $insideCodeBlock = $false - # Do *not* modify closing fence, leave it exactly as it is - } - } - } - - Set-Content -Path $_.FullName -Value $lines - } - Write-Build DarkGray ' Ensuring exactly one trailing newline in final markdown file.' - Get-ChildItem -Path $OutputDir -File -Filter *.md | ForEach-Object { - # Read the file as an array of lines - $lines = Get-Content -Path $_.FullName - - # Remove all blank lines at the end, but do not remove actual content - while ($lines.Count -gt 0 -and $lines[-1] -match '^\s*$') { - $lines = $lines[0..($lines.Count - 2)] - } - - # Re-join with Windows line endings and add exactly one trailing newline - $content = ($lines -join "`r`n") - Set-Content -Path $_.FullName -Value $content -Force - } - Write-Build Gray ' ...Markdown documentation finalized.' - - Write-Build Green ' ...CreateHelp Complete!' -} #CreateHelpStart - -# Synopsis: Replace comment based help (CBH) with external help in all public functions for this project -Add-BuildTask UpdateCBH -After AssetCopy { - $ExternalHelp = @" -<# -.EXTERNALHELP $($ModuleName)-help.xml -#> -"@ - - $CBHPattern = "(?ms)(\<#.*\.SYNOPSIS.*?#>)" - Get-ChildItem -Path "$script:ArtifactsPath\Public\*.ps1" -File | ForEach-Object { - $FormattedOutFile = $_.FullName - Write-Output " Replacing CBH in file: $($FormattedOutFile)" - $UpdatedFile = (Get-Content $FormattedOutFile -Raw) -replace $CBHPattern, $ExternalHelp - $UpdatedFile | Out-File -FilePath $FormattedOutFile -Force -Encoding:utf8 - } -} #UpdateCBH - -# Synopsis: Copies module assets to Artifacts folder -Add-BuildTask AssetCopy -Before Build { - Write-Build Gray ' Copying assets to Artifacts...' - Copy-Item -Path "$script:ModuleSourcePath\*" -Destination $script:ArtifactsPath -Exclude *.psd1, *.psm1 -Recurse -ErrorAction Stop - Write-Build Gray ' ...Assets copy complete.' -} #AssetCopy - -# Synopsis: Builds the Module to the Artifacts folder -Add-BuildTask Build { - Write-Build White ' Performing Module Build' - - Write-Build Gray ' Copying manifest file to Artifacts...' - Copy-Item -Path $script:ModuleManifestFile -Destination $script:ArtifactsPath -Recurse -ErrorAction Stop - #Copy-Item -Path $script:ModuleSourcePath\bin -Destination $script:ArtifactsPath -Recurse -ErrorAction Stop - Write-Build Gray ' ...manifest copy complete.' - - Write-Build Gray ' Merging Public and Private functions to one module file...' - #$private = "$script:ModuleSourcePath\Private" - $scriptContent = [System.Text.StringBuilder]::new() - #$powerShellScripts = Get-ChildItem -Path $script:ModuleSourcePath -Filter '*.ps1' -Recurse - $powerShellScripts = Get-ChildItem -Path $script:ArtifactsPath -Recurse | Where-Object { $_.Name -match '^*.ps1$' } - foreach ($script in $powerShellScripts) { - $null = $scriptContent.Append((Get-Content -Path $script.FullName -Raw)) - $null = $scriptContent.AppendLine('') - $null = $scriptContent.AppendLine('') - } - $scriptContent.ToString() | Out-File -FilePath $script:BuildModuleRootFile -Encoding utf8 -Force - # Cleanup the combined root module and remove extra trailing lines at the end of the file. - Invoke-Formatter $script:BuildModuleRootFile -ErrorAction SilentlyContinue - Write-Build Gray ' ...Module creation complete.' - - Write-Build Gray ' Cleaning up leftover artifacts...' - #cleanup artifacts that are no longer required - if (Test-Path "$script:ArtifactsPath\Public") { - Remove-Item "$script:ArtifactsPath\Public" -Recurse -Force -ErrorAction Stop - } - if (Test-Path "$script:ArtifactsPath\Private") { - Remove-Item "$script:ArtifactsPath\Private" -Recurse -Force -ErrorAction Stop - } - if (Test-Path "$script:ArtifactsPath\Imports.ps1") { - Remove-Item "$script:ArtifactsPath\Imports.ps1" -Force -ErrorAction SilentlyContinue - } - - if (Test-Path "$script:ArtifactsPath\docs") { - #here we update the parent level docs. If you would prefer not to update them, comment out this section. - Write-Build Gray ' Overwriting docs output...' - if (-not (Test-Path '..\docs\')) { - New-Item -Path '..\docs\' -ItemType Directory -Force | Out-Null - } - Move-Item "$script:ArtifactsPath\docs\*.md" -Destination '..\docs\' -Force - Remove-Item "$script:ArtifactsPath\docs" -Recurse -Force -ErrorAction Stop - Write-Build Gray ' ...Docs output completed.' - } - - Write-Build Green ' ...Build Complete!' -} #Build - -#Synopsis: Invokes all Pester Integration Tests in the Tests\Integration folder (if it exists) -Add-BuildTask IntegrationTest { - if (Test-Path -Path $script:IntegrationTestsPath) { - Write-Build White " Importing desired Pester version. Min: $script:MinPesterVersion Max: $script:MaxPesterVersion" - Remove-Module -Name Pester -Force -ErrorAction SilentlyContinue # there are instances where some containers have Pester already in the session - Import-Module -Name Pester -MinimumVersion $script:MinPesterVersion -MaximumVersion $script:MaxPesterVersion -ErrorAction 'Stop' - - Write-Build White " Performing Pester Integration Tests in $($invokePesterParams.path)" - - $pesterConfiguration = New-PesterConfiguration - $pesterConfiguration.run.Path = $script:IntegrationTestsPath - $pesterConfiguration.Run.PassThru = $true - $pesterConfiguration.Run.Exit = $false - $pesterConfiguration.CodeCoverage.Enabled = $false - $pesterConfiguration.TestResult.Enabled = $false - $pesterConfiguration.Output.Verbosity = 'Detailed' - - $testResults = Invoke-Pester -Configuration $pesterConfiguration - # This will output a nice json for each failed test (if running in CodeBuild) - if ($env:CODEBUILD_BUILD_ARN) { - $testResults.TestResult | ForEach-Object { - if ($_.Result -ne 'Passed') { - ConvertTo-Json -InputObject $_ -Compress - } - } - } - - $numberFails = $testResults.FailedCount - Assert-Build($numberFails -eq 0) ('Failed "{0}" unit tests.' -f $numberFails) - Write-Build Green ' ...Pester Integration Tests Complete!' - } -} #IntegrationTest - -#Synopsis: Creates an archive of the built Module -Add-BuildTask Archive { - Write-Build White ' Performing Archive...' - - $archivePath = Join-Path -Path $BuildRoot -ChildPath 'Archive' - if (Test-Path -Path $archivePath) { - $null = Remove-Item -Path $archivePath -Recurse -Force - } - - $null = New-Item -Path $archivePath -ItemType Directory -Force - - $zipFileName = '{0}_{1}_{2}.{3}.zip' -f $script:ModuleName, $script:ModuleVersion, ([DateTime]::UtcNow.ToString("yyyyMMdd")), ([DateTime]::UtcNow.ToString("hhmmss")) - $zipFile = Join-Path -Path $archivePath -ChildPath $zipFileName - - if ($PSEdition -eq 'Desktop') { - Add-Type -AssemblyName 'System.IO.Compression.FileSystem' - } - [System.IO.Compression.ZipFile]::CreateFromDirectory($script:ArtifactsPath, $zipFile) - - Write-Build Green ' ...Archive Complete!' -} #Archive - +<# +.SYNOPSIS + An Invoke-Build Build file. +.DESCRIPTION + Build steps can include: + - ValidateRequirements + - ImportModuleManifest + - Clean + - Analyze + - FormattingCheck + - Test + - DevCC + - CreateHelpStart + - Build + - IntegrationTest + - Archive +.EXAMPLE + Invoke-Build + + This will perform the default build Add-BuildTasks: see below for the default Add-BuildTask execution +.EXAMPLE + Invoke-Build -Add-BuildTask Analyze,Test + + This will perform only the Analyze and Test Add-BuildTasks. +.NOTES + This build file by Catesta will pull in configurations from the ".Settings.ps1" file as well, where users can more easily customize the build process if required. + https://github.com/nightroman/Invoke-Build + https://github.com/nightroman/Invoke-Build/wiki/Build-Scripts-Guidelines + If using VSCode you can use the generated tasks.json to execute the various tasks in this build file. + Ctrl + P | then type task (add space) - you will then be presented with a list of available tasks to run + The 'InstallDependencies' Add-BuildTask isn't present here. + Module dependencies are installed at a previous step in the pipeline. + If your manifest has module dependencies include all required modules in your CI/CD bootstrap file: + AWS - install_modules.ps1 + Azure - actions_bootstrap.ps1 + GitHub Actions - actions_bootstrap.ps1 + AppVeyor - actions_bootstrap.ps1 +#> + +#Include: Settings +$ModuleName = [regex]::Match((Get-Item $BuildFile).Name, '^(.*)\.build\.ps1$').Groups[1].Value +. "./$ModuleName.Settings.ps1" + +function Test-ManifestBool ($Path) { + Get-ChildItem $Path | Test-ModuleManifest -ErrorAction SilentlyContinue | Out-Null; $? +} + +#Default Build +$str = @() +$str = 'Clean', 'ValidateRequirements', 'ImportModuleManifest' +$str += 'FormattingCheck' +$str += 'Analyze', 'Test' +$str += 'CreateHelpStart' +$str2 = $str +$str2 += 'Build', 'Archive' +$str += 'Build', 'IntegrationTest', 'Archive' +Add-BuildTask -Name . -Jobs $str + +#Local testing build process +Add-BuildTask TestLocal Clean, ImportModuleManifest, Analyze, Test + +#Local help file creation process +Add-BuildTask HelpLocal Clean, ImportModuleManifest, CreateHelpStart + +#Full build sans integration tests +Add-BuildTask BuildNoIntegration -Jobs $str2 + +# Pre-build variables to be used by other portions of the script +Enter-Build { + $script:ModuleName = [regex]::Match((Get-Item $BuildFile).Name, '^(.*)\.build\.ps1$').Groups[1].Value + + # Identify other required paths + $script:ModuleSourcePath = Join-Path -Path $BuildRoot -ChildPath $script:ModuleName + $script:ModuleFiles = Join-Path -Path $script:ModuleSourcePath -ChildPath '*' + + $script:ModuleManifestFile = Join-Path -Path $script:ModuleSourcePath -ChildPath "$($script:ModuleName).psd1" + + $manifestInfo = Import-PowerShellDataFile -Path $script:ModuleManifestFile + $script:ModuleVersion = $manifestInfo.ModuleVersion + $script:ModuleDescription = $manifestInfo.Description + $script:FunctionsToExport = $manifestInfo.FunctionsToExport + + $script:TestsPath = Join-Path -Path $BuildRoot -ChildPath 'Tests' + $script:UnitTestsPath = Join-Path -Path $script:TestsPath -ChildPath 'Unit' + $script:IntegrationTestsPath = Join-Path -Path $script:TestsPath -ChildPath 'Integration' + + $script:ArtifactsPath = Join-Path -Path $BuildRoot -ChildPath 'Artifacts' + $script:ArchivePath = Join-Path -Path $BuildRoot -ChildPath 'Archive' + + $script:BuildModuleRootFile = Join-Path -Path $script:ArtifactsPath -ChildPath "$($script:ModuleName).psm1" + + # Ensure our builds fail until if below a minimum defined code test coverage threshold + $script:coverageThreshold = 30 + + [version]$script:MinPesterVersion = '5.2.2' + [version]$script:MaxPesterVersion = '5.99.99' + $script:testOutputFormat = 'NUnitXML' +} #Enter-Build + +# Define headers as separator, task path, synopsis, and location, e.g. for Ctrl+Click in VSCode. +# Also change the default color to Green. If you need task start times, use `$Task.Started`. +Set-BuildHeader { + param($Path) + # separator line + Write-Build DarkMagenta ('=' * 79) + # default header + synopsis + Write-Build DarkGray "Task $Path : $(Get-BuildSynopsis $Task)" + # task location in a script + Write-Build DarkGray "At $($Task.InvocationInfo.ScriptName):$($Task.InvocationInfo.ScriptLineNumber)" + Write-Build Yellow "Manifest File: $script:ModuleManifestFile" + Write-Build Yellow "Manifest Version: $($manifestInfo.ModuleVersion)" +} #Set-BuildHeader + +# Define footers similar to default but change the color to DarkGray. +Set-BuildFooter { + param($Path) + Write-Build DarkGray "Done $Path, $($Task.Elapsed)" + # # separator line + # Write-Build Gray ('=' * 79) +} #Set-BuildFooter + +#Synopsis: Validate system requirements are met +Add-BuildTask ValidateRequirements { + # this setting comes from the *.Settings.ps1 + Write-Build White " Verifying at least PowerShell $script:requiredPSVersion..." + Assert-Build ($PSVersionTable.PSVersion -ge $script:requiredPSVersion) "At least Powershell $script:requiredPSVersion is required for this build to function properly" + Write-Build Green ' ...Verification Complete!' +} #ValidateRequirements + +# Synopsis: Import the current module manifest file for processing +Add-BuildTask TestModuleManifest -Before ImportModuleManifest { + Write-Build White ' Running module manifest tests...' + Assert-Build (Test-Path $script:ModuleManifestFile) 'Unable to locate the module manifest file.' + Assert-Build (Test-ManifestBool -Path $script:ModuleManifestFile) 'Module Manifest test did not pass verification.' + Write-Build Green ' ...Module Manifest Verification Complete!' +} #f5b33218-bde4-4028-b2a1-9c206f089503 + +# Synopsis: Load the module project +Add-BuildTask ImportModuleManifest { + Write-Build White ' Attempting to load the project module.' + try { + Import-Module $script:ModuleManifestFile -Force -PassThru -ErrorAction Stop + } catch { + Write-Build Red " ...$_`n" + throw 'Unable to load the project module' + } + Write-Build Green " ...$script:ModuleName imported successfully" +} + +#Synopsis: Clean and reset Artifacts/Archive Directory +Add-BuildTask Clean { + Write-Build White ' Clean up our Artifacts/Archive directory...' + + $null = Remove-Item $script:ArtifactsPath -Force -Recurse -ErrorAction 0 + $null = New-Item $script:ArtifactsPath -ItemType:Directory + $null = Remove-Item $script:ArchivePath -Force -Recurse -ErrorAction 0 + $null = New-Item $script:ArchivePath -ItemType:Directory + + Write-Build Green ' ...Clean Complete!' +} #Clean + +#Synopsis: Invokes PSScriptAnalyzer against the Module source path +Add-BuildTask Analyze { + + $scriptAnalyzerParams = @{ + Path = $script:ModuleSourcePath + Setting = 'PSScriptAnalyzerSettings.psd1' + Recurse = $true + Verbose = $false + } + + Write-Build White ' Performing Module ScriptAnalyzer checks...' + $scriptAnalyzerResults = Invoke-ScriptAnalyzer @scriptAnalyzerParams + + if ($scriptAnalyzerResults) { + $scriptAnalyzerResults | Format-Table + throw ' One or more PSScriptAnalyzer errors/warnings where found.' + } else { + Write-Build Green ' ...Module Analyze Complete!' + } +} #Analyze + +#Synopsis: Invokes Script Analyzer against the Tests path if it exists +Add-BuildTask AnalyzeTests -After Analyze { + if (Test-Path -Path $script:TestsPath) { + + $scriptAnalyzerParams = @{ + Path = $script:TestsPath + Setting = 'PSScriptAnalyzerSettings.psd1' + ExcludeRule = 'PSUseDeclaredVarsMoreThanAssignments' + Recurse = $true + Verbose = $false + } + + Write-Build White ' Performing Test ScriptAnalyzer checks...' + $scriptAnalyzerResults = Invoke-ScriptAnalyzer @scriptAnalyzerParams + + if ($scriptAnalyzerResults) { + $scriptAnalyzerResults | Format-Table + throw ' One or more PSScriptAnalyzer errors/warnings where found.' + } else { + Write-Build Green ' ...Test Analyze Complete!' + } + } +} #AnalyzeTests + +#Synopsis: Analyze scripts to verify if they adhere to desired coding format (Stroustrup / OTBS / Allman) +Add-BuildTask FormattingCheck { + + + $scriptAnalyzerParams = @{ + Setting = 'CodeFormattingOTBS' + ExcludeRule = 'PSUseConsistentWhitespace' + Recurse = $true + Verbose = $false + } + + + Write-Build White ' Performing script formatting checks...' + $scriptAnalyzerResults = Get-ChildItem -Path $script:ModuleSourcePath -Exclude "*.psd1" | Invoke-ScriptAnalyzer @scriptAnalyzerParams + + if ($scriptAnalyzerResults) { + $scriptAnalyzerResults | Format-Table + throw ' PSScriptAnalyzer code formatting check did not adhere to {0} standards' -f $scriptAnalyzerParams.Setting + } else { + Write-Build Green ' ...Formatting Analyze Complete!' + } +} #FormattingCheck + +#Synopsis: Invokes all Pester Unit Tests in the Tests\Unit folder (if it exists) +Add-BuildTask Test { + + Write-Build White " Importing desired Pester version. Min: $script:MinPesterVersion Max: $script:MaxPesterVersion" + Remove-Module -Name Pester -Force -ErrorAction 'SilentlyContinue'# there are instances where some containers have Pester already in the session + Import-Module -Name Pester -MinimumVersion $script:MinPesterVersion -MaximumVersion $script:MaxPesterVersion -ErrorAction 'Stop' + + $codeCovPath = "$script:ArtifactsPath\ccReport\" + $testOutPutPath = "$script:ArtifactsPath\testOutput\" + if (-not(Test-Path $codeCovPath)) { + New-Item -Path $codeCovPath -ItemType Directory | Out-Null + } + if (-not(Test-Path $testOutPutPath)) { + New-Item -Path $testOutPutPath -ItemType Directory | Out-Null + } + if (Test-Path -Path $script:UnitTestsPath) { + $pesterConfiguration = New-PesterConfiguration + $pesterConfiguration.run.Path = $script:UnitTestsPath + $pesterConfiguration.Run.PassThru = $true + $pesterConfiguration.Run.Exit = $false + $pesterConfiguration.CodeCoverage.Enabled = $true + $pesterConfiguration.CodeCoverage.Path = "$script:ModuleSourcePath\*\*.ps1" + $pesterConfiguration.CodeCoverage.CoveragePercentTarget = $script:coverageThreshold + $pesterConfiguration.CodeCoverage.OutputPath = "$codeCovPath\CodeCoverage.xml" + $pesterConfiguration.CodeCoverage.OutputFormat = 'JaCoCo' + $pesterConfiguration.TestResult.Enabled = $true + $pesterConfiguration.TestResult.OutputPath = "$testOutPutPath\PesterTests.xml" + $pesterConfiguration.TestResult.OutputFormat = $script:testOutputFormat + $pesterConfiguration.Output.Verbosity = 'Detailed' + + Write-Build White ' Performing Pester Unit Tests...' + # Publish Test Results + $testResults = Invoke-Pester -Configuration $pesterConfiguration + + # This will output a nice json for each failed test (if running in CodeBuild) + if ($env:CODEBUILD_BUILD_ARN) { + $testResults.TestResult | ForEach-Object { + if ($_.Result -ne 'Passed') { + ConvertTo-Json -InputObject $_ -Compress + } + } + } + + $numberFails = $testResults.FailedCount + Assert-Build($numberFails -eq 0) ('Failed "{0}" unit tests.' -f $numberFails) + + Write-Build Gray (' ...CODE COVERAGE - CommandsExecutedCount: {0}' -f $testResults.CodeCoverage.CommandsExecutedCount) + Write-Build Gray (' ...CODE COVERAGE - CommandsAnalyzedCount: {0}' -f $testResults.CodeCoverage.CommandsAnalyzedCount) + + if ($testResults.CodeCoverage.NumberOfCommandsExecuted -ne 0) { + $coveragePercent = '{0:N2}' -f ($testResults.CodeCoverage.CommandsExecutedCount / $testResults.CodeCoverage.CommandsAnalyzedCount * 100) + + <# + if ($testResults.CodeCoverage.NumberOfCommandsMissed -gt 0) { + 'Failed to analyze "{0}" commands' -f $testResults.CodeCoverage.NumberOfCommandsMissed + } + Write-Host "PowerShell Commands not tested:`n$(ConvertTo-Json -InputObject $testResults.CodeCoverage.MissedCommands)" + #> + if ([Int]$coveragePercent -lt $coverageThreshold) { + throw ('Failed to meet code coverage threshold of {0}% with only {1}% coverage' -f $coverageThreshold, $coveragePercent) + } else { + Write-Build Cyan " $('Covered {0}% of {1} analyzed commands in {2} files.' -f $coveragePercent,$testResults.CodeCoverage.CommandsAnalyzedCount,$testResults.CodeCoverage.FilesAnalyzedCount)" + Write-Build Green ' ...Pester Unit Tests Complete!' + } + } else { + # account for new module build condition + Write-Build Yellow ' Code coverage check skipped. No commands to execute...' + } + + } +} #Test + +#Synopsis: Used primarily during active development to generate xml file to graphically display code coverage in VSCode using Coverage Gutters +Add-BuildTask DevCC { + Write-Build White ' Generating code coverage report at root...' + Write-Build White " Importing desired Pester version. Min: $script:MinPesterVersion Max: $script:MaxPesterVersion" + Remove-Module -Name Pester -Force -ErrorAction SilentlyContinue # there are instances where some containers have Pester already in the session + Import-Module -Name Pester -MinimumVersion $script:MinPesterVersion -MaximumVersion $script:MaxPesterVersion -ErrorAction 'Stop' + $pesterConfiguration = New-PesterConfiguration + $pesterConfiguration.run.Path = $script:UnitTestsPath + $pesterConfiguration.CodeCoverage.Enabled = $true + $pesterConfiguration.CodeCoverage.Path = "$PSScriptRoot\$ModuleName\*\*.ps1" + $pesterConfiguration.CodeCoverage.CoveragePercentTarget = $script:coverageThreshold + $pesterConfiguration.CodeCoverage.OutputPath = '..\..\..\cov.xml' + $pesterConfiguration.CodeCoverage.OutputFormat = 'CoverageGutters' + + Invoke-Pester -Configuration $pesterConfiguration + Write-Build Green ' ...Code Coverage report generated!' +} #DevCC + +# Synopsis: Build help for module +Add-BuildTask CreateHelpStart { + Write-Build White ' Performing all help related actions.' + + Write-Build Gray ' Importing platyPS v0.12.0 ...' + Import-Module platyPS -RequiredVersion 0.12.0 -ErrorAction Stop + Write-Build Gray ' ...platyPS imported successfully.' +} #CreateHelpStart + +# Synopsis: Build markdown help files for module and fail if help information is missing +Add-BuildTask CreateMarkdownHelp -After CreateHelpStart { + $ModulePage = "$script:ArtifactsPath\docs\$($ModuleName).md" + + $markdownParams = @{ + Module = $ModuleName + OutputFolder = "$script:ArtifactsPath\docs\" + Force = $true + WithModulePage = $true + Locale = 'en-US' + FwLink = "NA" + HelpVersion = $script:ModuleVersion + } + + Write-Build Gray ' Generating markdown files...' + $null = New-MarkdownHelp @markdownParams + Write-Build Gray ' ...Markdown generation completed.' + + Write-Build Gray ' Replacing markdown elements...' + Write-Build DarkGray ' Replace multi-line EXAMPLES' + $OutputDir = "$script:ArtifactsPath\docs\" + $OutputDir | Get-ChildItem -File | ForEach-Object { + # fix formatting in multiline examples + $content = Get-Content $_.FullName -Raw + $newContent = $content -replace '(## EXAMPLE [^`]+?```\r\n[^`\r\n]+?\r\n)(```\r\n\r\n)([^#]+?\r\n)(\r\n)([^#]+)(#)', '$1$3$2$4$5$6' + if ($newContent -ne $content) { + Set-Content -Path $_.FullName -Value $newContent -Force + } + } + Write-Build DarkGray ' Replace each missing element we need for a proper generic module page .md file' + $ModulePageFileContent = Get-Content -Raw $ModulePage + $ModulePageFileContent = $ModulePageFileContent -replace '{{Manually Enter Description Here}}', $script:ModuleDescription + $script:FunctionsToExport | ForEach-Object { + Write-Build DarkGray " Updating definition for the following function: $($_)" + $TextToReplace = "{{Manually Enter $($_) Description Here}}" + $ReplacementText = (Get-Help -Detailed $_).Synopsis + $ModulePageFileContent = $ModulePageFileContent -replace $TextToReplace, $ReplacementText + } + Write-Build DarkGray ' Evaluating if running 7.4.0 or higher...' + # https://github.com/PowerShell/platyPS/issues/595 + if ($PSVersionTable.PSVersion -ge [version]'7.4.0') { + Write-Build DarkGray ' Performing Markdown repair' + # dot source markdown repair + . $BuildRoot\MarkdownRepair.ps1 + $OutputDir | Get-ChildItem -File | ForEach-Object { + Repair-PlatyPSMarkdown -Path $_.FullName + } + } + Write-Build DarkGray ' Add blank line after headers.' + $OutputDir | Get-ChildItem -File | ForEach-Object { + $content = Get-Content $_.FullName -Raw + $newContent = $content -replace '(?m)^(#{1,6}\s+.*)$\r?\n(?!\r?\n)', "`$1`r`n" + # $newContent = $content -replace '(?m)^(#{1,6}\s+.+?)$\r?\n(?!\r?\n)', "`$1`n" + # $newContent = $content -replace '(?m)^(#{1,6}\s+.*)$\r?\n(?!\r?\n)', "`$1`n" + if ($newContent -ne $content) { + Set-Content -Path $_.FullName -Value $newContent -Force + } + } + # *NOTE: it is not possible to adjust fenced code block at this location as conversion to MAML does not support language tags. + + $ModulePageFileContent | Out-File $ModulePage -Force -Encoding:utf8 + Write-Build Gray ' ...Markdown replacements complete.' + + Write-Build Gray ' Verifying GUID...' + $MissingGUID = Select-String -Path "$script:ArtifactsPath\docs\*.md" -Pattern "(00000000-0000-0000-0000-000000000000)" + if ($MissingGUID.Count -gt 0) { + Write-Build Yellow ' The documentation that got generated resulted in a generic GUID. Check the GUID entry of your module manifest.' + throw 'Missing GUID. Please review and rebuild.' + } + + Write-Build Gray ' Checking for missing documentation in md files...' + $MissingDocumentation = Select-String -Path "$script:ArtifactsPath\docs\*.md" -Pattern "({{.*}})" + if ($MissingDocumentation.Count -gt 0) { + Write-Build Yellow ' The documentation that got generated resulted in missing sections which should be filled out.' + Write-Build Yellow ' Please review the following sections in your comment based help, fill out missing information and rerun this build:' + Write-Build Yellow ' (Note: This can happen if the .EXTERNALHELP CBH is defined for a function before running this build.)' + Write-Build Yellow " Path of files with issues: $script:ArtifactsPath\docs\" + $MissingDocumentation | Select-Object FileName, LineNumber, Line | Format-Table -AutoSize + throw 'Missing documentation. Please review and rebuild.' + } + + Write-Build Gray ' Checking for missing SYNOPSIS in md files...' + $fSynopsisOutput = @() + # $synopsisEval = Select-String -Path "$script:ArtifactsPath\docs\*.md" -Pattern "^## SYNOPSIS$" -Context 0, 1 + $synopsisEval = Select-String -Path "$script:ArtifactsPath\docs\*.md" -Pattern "^## SYNOPSIS$\r?\n$" -Context 0, 2 + $synopsisEval | ForEach-Object { + $chAC = $_.Context.DisplayPostContext.ToCharArray() + if ($null -eq $chAC) { + $fSynopsisOutput += $_.FileName + } + } + if ($fSynopsisOutput) { + Write-Build Yellow " The following files are missing SYNOPSIS:" + $fSynopsisOutput + throw 'SYNOPSIS information missing. Please review.' + } + + Write-Build Gray ' ...Markdown generation complete.' +} #CreateMarkdownHelp + +# Synopsis: Build the external xml help file from markdown help files with PlatyPS +Add-BuildTask CreateExternalHelp -After CreateMarkdownHelp { + Write-Build Gray ' Creating external xml help file...' + $null = New-ExternalHelp "$script:ArtifactsPath\docs" -OutputPath "$script:ArtifactsPath\en-US\" -Force + Write-Build Gray ' ...External xml help file created!' +} #CreateExternalHelp + +Add-BuildTask CreateHelpComplete -After CreateExternalHelp { + Write-Build Gray ' Finalizing markdown documentation now that external help has been created...' + Write-Build DarkGray ' Add powershell language to unspecified fenced code blocks.' + + $OutputDir = "$script:ArtifactsPath\docs\" + + Get-ChildItem -Path $OutputDir -File | ForEach-Object { + $lines = Get-Content -Path $_.FullName + $insideCodeBlock = $false + + for ($i = 0; $i -lt $lines.Count; $i++) { + # Regex captures everything after triple backticks (if present). + # e.g. ```yaml => captured group = "yaml" + # ``` => captured group = "" + if ($lines[$i] -match '^\s*```(\S*)\s*$') { + $lang = $Matches[1] + + if (-not $insideCodeBlock) { + # We found an opening fence + if ([string]::IsNullOrWhiteSpace($lang)) { + # Bare triple backticks => add powershell + $lines[$i] = '```powershell' + } + # Toggle "inside code block" on + $insideCodeBlock = $true + } else { + # We found the closing fence -> set $insideCodeBlock off + $insideCodeBlock = $false + # Do *not* modify closing fence, leave it exactly as it is + } + } + } + + Set-Content -Path $_.FullName -Value $lines + } + Write-Build DarkGray ' Ensuring exactly one trailing newline in final markdown file.' + Get-ChildItem -Path $OutputDir -File -Filter *.md | ForEach-Object { + # Read the file as an array of lines + $lines = Get-Content -Path $_.FullName + + # Remove all blank lines at the end, but do not remove actual content + while ($lines.Count -gt 0 -and $lines[-1] -match '^\s*$') { + $lines = $lines[0..($lines.Count - 2)] + } + + # Re-join with Windows line endings and add exactly one trailing newline + $content = ($lines -join "`r`n") + Set-Content -Path $_.FullName -Value $content -Force + } + Write-Build Gray ' ...Markdown documentation finalized.' + + Write-Build Green ' ...CreateHelp Complete!' +} #CreateHelpStart + +# Synopsis: Replace comment based help (CBH) with external help in all public functions for this project +Add-BuildTask UpdateCBH -After AssetCopy { + $ExternalHelp = @" +<# +.EXTERNALHELP $($ModuleName)-help.xml +#> +"@ + + $CBHPattern = "(?ms)(\<#.*\.SYNOPSIS.*?#>)" + Get-ChildItem -Path "$script:ArtifactsPath\Public\*.ps1" -File | ForEach-Object { + $FormattedOutFile = $_.FullName + Write-Output " Replacing CBH in file: $($FormattedOutFile)" + $UpdatedFile = (Get-Content $FormattedOutFile -Raw) -replace $CBHPattern, $ExternalHelp + $UpdatedFile | Out-File -FilePath $FormattedOutFile -Force -Encoding:utf8 + } +} #UpdateCBH + +# Synopsis: Copies module assets to Artifacts folder +Add-BuildTask AssetCopy -Before Build { + Write-Build Gray ' Copying assets to Artifacts...' + Copy-Item -Path "$script:ModuleSourcePath\*" -Destination $script:ArtifactsPath -Exclude *.psd1, *.psm1 -Recurse -ErrorAction Stop + Write-Build Gray ' ...Assets copy complete.' +} #AssetCopy + +# Synopsis: Builds the Module to the Artifacts folder +Add-BuildTask Build { + Write-Build White ' Performing Module Build' + + Write-Build Gray ' Copying manifest file to Artifacts...' + Copy-Item -Path $script:ModuleManifestFile -Destination $script:ArtifactsPath -Recurse -ErrorAction Stop + #Copy-Item -Path $script:ModuleSourcePath\bin -Destination $script:ArtifactsPath -Recurse -ErrorAction Stop + Write-Build Gray ' ...manifest copy complete.' + + Write-Build Gray ' Merging Public and Private functions to one module file...' + #$private = "$script:ModuleSourcePath\Private" + $scriptContent = [System.Text.StringBuilder]::new() + #$powerShellScripts = Get-ChildItem -Path $script:ModuleSourcePath -Filter '*.ps1' -Recurse + $powerShellScripts = Get-ChildItem -Path $script:ArtifactsPath -Recurse | Where-Object { $_.Name -match '^*.ps1$' } + foreach ($script in $powerShellScripts) { + $null = $scriptContent.Append((Get-Content -Path $script.FullName -Raw)) + $null = $scriptContent.AppendLine('') + $null = $scriptContent.AppendLine('') + } + $scriptContent.ToString() | Out-File -FilePath $script:BuildModuleRootFile -Encoding utf8 -Force + # Cleanup the combined root module and remove extra trailing lines at the end of the file. + Invoke-Formatter $script:BuildModuleRootFile -ErrorAction SilentlyContinue + Write-Build Gray ' ...Module creation complete.' + + Write-Build Gray ' Cleaning up leftover artifacts...' + #cleanup artifacts that are no longer required + if (Test-Path "$script:ArtifactsPath\Public") { + Remove-Item "$script:ArtifactsPath\Public" -Recurse -Force -ErrorAction Stop + } + if (Test-Path "$script:ArtifactsPath\Private") { + Remove-Item "$script:ArtifactsPath\Private" -Recurse -Force -ErrorAction Stop + } + if (Test-Path "$script:ArtifactsPath\Imports.ps1") { + Remove-Item "$script:ArtifactsPath\Imports.ps1" -Force -ErrorAction SilentlyContinue + } + + if (Test-Path "$script:ArtifactsPath\docs") { + #here we update the parent level docs. If you would prefer not to update them, comment out this section. + Write-Build Gray ' Overwriting docs output...' + if (-not (Test-Path '..\docs\')) { + New-Item -Path '..\docs\' -ItemType Directory -Force | Out-Null + } + Move-Item "$script:ArtifactsPath\docs\*.md" -Destination '..\docs\' -Force + Remove-Item "$script:ArtifactsPath\docs" -Recurse -Force -ErrorAction Stop + Write-Build Gray ' ...Docs output completed.' + } + + Write-Build Green ' ...Build Complete!' +} #Build + +#Synopsis: Invokes all Pester Integration Tests in the Tests\Integration folder (if it exists) +Add-BuildTask IntegrationTest { + if (Test-Path -Path $script:IntegrationTestsPath) { + Write-Build White " Importing desired Pester version. Min: $script:MinPesterVersion Max: $script:MaxPesterVersion" + Remove-Module -Name Pester -Force -ErrorAction SilentlyContinue # there are instances where some containers have Pester already in the session + Import-Module -Name Pester -MinimumVersion $script:MinPesterVersion -MaximumVersion $script:MaxPesterVersion -ErrorAction 'Stop' + + Write-Build White " Performing Pester Integration Tests in $($invokePesterParams.path)" + + $pesterConfiguration = New-PesterConfiguration + $pesterConfiguration.run.Path = $script:IntegrationTestsPath + $pesterConfiguration.Run.PassThru = $true + $pesterConfiguration.Run.Exit = $false + $pesterConfiguration.CodeCoverage.Enabled = $false + $pesterConfiguration.TestResult.Enabled = $false + $pesterConfiguration.Output.Verbosity = 'Detailed' + + $testResults = Invoke-Pester -Configuration $pesterConfiguration + # This will output a nice json for each failed test (if running in CodeBuild) + if ($env:CODEBUILD_BUILD_ARN) { + $testResults.TestResult | ForEach-Object { + if ($_.Result -ne 'Passed') { + ConvertTo-Json -InputObject $_ -Compress + } + } + } + + $numberFails = $testResults.FailedCount + Assert-Build($numberFails -eq 0) ('Failed "{0}" unit tests.' -f $numberFails) + Write-Build Green ' ...Pester Integration Tests Complete!' + } +} #IntegrationTest + +#Synopsis: Creates an archive of the built Module +Add-BuildTask Archive { + Write-Build White ' Performing Archive...' + + $archivePath = Join-Path -Path $BuildRoot -ChildPath 'Archive' + if (Test-Path -Path $archivePath) { + $null = Remove-Item -Path $archivePath -Recurse -Force + } + + $null = New-Item -Path $archivePath -ItemType Directory -Force + + $zipFileName = '{0}_{1}_{2}.{3}.zip' -f $script:ModuleName, $script:ModuleVersion, ([DateTime]::UtcNow.ToString("yyyyMMdd")), ([DateTime]::UtcNow.ToString("hhmmss")) + $zipFile = Join-Path -Path $archivePath -ChildPath $zipFileName + + if ($PSEdition -eq 'Desktop') { + Add-Type -AssemblyName 'System.IO.Compression.FileSystem' + } + [System.IO.Compression.ZipFile]::CreateFromDirectory($script:ArtifactsPath, $zipFile) + + Write-Build Green ' ...Archive Complete!' +} #Archive + diff --git a/src/PSReddit/Imports.ps1 b/src/PSReddit/Imports.ps1 index 45056b1..ad7b7d9 100644 --- a/src/PSReddit/Imports.ps1 +++ b/src/PSReddit/Imports.ps1 @@ -1,6 +1,6 @@ -# This is a locally sourced Imports file for local development. -# It can be imported by the psm1 in local development to add script level variables. -# It will merged in the build process. This is for local development only. - -# region script variables -# $script:resourcePath = "$PSScriptRoot\Resources" +# This is a locally sourced Imports file for local development. +# It can be imported by the psm1 in local development to add script level variables. +# It will merged in the build process. This is for local development only. + +# region script variables +# $script:resourcePath = "$PSScriptRoot\Resources" diff --git a/src/PSReddit/PSReddit.psm1 b/src/PSReddit/PSReddit.psm1 index d9013f8..9babdcb 100644 --- a/src/PSReddit/PSReddit.psm1 +++ b/src/PSReddit/PSReddit.psm1 @@ -1,31 +1,31 @@ -# this psm1 is for local testing and development use only - -# dot source the parent import for local development variables -. $PSScriptRoot\Imports.ps1 - -# discover all ps1 file(s) in Public and Private paths - -$itemSplat = @{ - Filter = '*.ps1' - Recurse = $true - ErrorAction = 'Stop' -} -try { - $public = @(Get-ChildItem -Path "$PSScriptRoot\Public" @itemSplat) - $private = @(Get-ChildItem -Path "$PSScriptRoot\Private" @itemSplat) -} catch { - Write-Error $_ - throw 'Unable to get get file information from Public & Private src.' -} - -# dot source all .ps1 file(s) found -foreach ($file in @($public + $private)) { - try { - . $file.FullName - } catch { - throw ('Unable to dot source {0}' -f $file.FullName) - } -} - -# export all public functions -Export-ModuleMember -Function $public.Basename +# this psm1 is for local testing and development use only + +# dot source the parent import for local development variables +. $PSScriptRoot\Imports.ps1 + +# discover all ps1 file(s) in Public and Private paths + +$itemSplat = @{ + Filter = '*.ps1' + Recurse = $true + ErrorAction = 'Stop' +} +try { + $public = @(Get-ChildItem -Path "$PSScriptRoot\Public" @itemSplat) + $private = @(Get-ChildItem -Path "$PSScriptRoot\Private" @itemSplat) +} catch { + Write-Error $_ + throw 'Unable to get get file information from Public & Private src.' +} + +# dot source all .ps1 file(s) found +foreach ($file in @($public + $private)) { + try { + . $file.FullName + } catch { + throw ('Unable to dot source {0}' -f $file.FullName) + } +} + +# export all public functions +Export-ModuleMember -Function $public.Basename diff --git a/src/PSScriptAnalyzerSettings.psd1 b/src/PSScriptAnalyzerSettings.psd1 index 26e4277..e204206 100644 --- a/src/PSScriptAnalyzerSettings.psd1 +++ b/src/PSScriptAnalyzerSettings.psd1 @@ -1,61 +1,61 @@ -@{ - #________________________________________ - #IncludeDefaultRules - IncludeDefaultRules = $true - #________________________________________ - #Severity - #Specify Severity when you want to limit generated diagnostic records to a specific subset: [ Error | Warning | Information ] - Severity = @('Error', 'Warning') - #________________________________________ - #CustomRulePath - #Specify CustomRulePath when you have a large set of custom rules you'd like to reference - #CustomRulePath = "Module\InjectionHunter\1.0.0\InjectionHunter.psd1" - #________________________________________ - #IncludeRules - #Specify IncludeRules when you only want to run specific subset of rules instead of the default rule set. - #IncludeRules = @('PSShouldProcess', - # 'PSUseApprovedVerbs') - #________________________________________ - #ExcludeRules - #Specify ExcludeRules when you want to exclude a certain rule from the the default set of rules. - # PSUseDeclaredVarsMoreThanAssignments excluded due to false positives with Pester v5 scoping - ExcludeRules = @( - 'PSUseDeclaredVarsMoreThanAssignments' - ) - #________________________________________ - #Rules - #Here you can specify customizations for particular rules. Several examples are included below: - #Rules = @{ - # PSUseCompatibleCmdlets = @{ - # compatibility = @('core-6.1.0-windows', 'desktop-4.0-windows') - # } - # PSUseCompatibleSyntax = @{ - # Enable = $true - # TargetVersions = @( - # '3.0', - # '5.1', - # '6.2' - # ) - # } - # PSUseCompatibleCommands = @{ - # Enable = $true - # TargetProfiles = @( - # 'win-8_x64_10.0.14393.0_6.1.3_x64_4.0.30319.42000_core', # PS 6.1 on WinServer-2019 - # 'win-8_x64_10.0.17763.0_5.1.17763.316_x64_4.0.30319.42000_framework', # PS 5.1 on WinServer-2019 - # 'win-8_x64_6.2.9200.0_3.0_x64_4.0.30319.42000_framework' # PS 3 on WinServer-2012 - # ) - # } - # PSUseCompatibleTypes = @{ - # Enable = $true - # TargetProfiles = @( - # 'ubuntu_x64_18.04_6.1.3_x64_4.0.30319.42000_core', - # 'win-48_x64_10.0.17763.0_5.1.17763.316_x64_4.0.30319.42000_framework' - # ) - # # You can specify types to not check like this, which will also ignore methods and members on it: - # IgnoreTypes = @( - # 'System.IO.Compression.ZipFile' - # ) - # } - #} - #________________________________________ -} +@{ + #________________________________________ + #IncludeDefaultRules + IncludeDefaultRules = $true + #________________________________________ + #Severity + #Specify Severity when you want to limit generated diagnostic records to a specific subset: [ Error | Warning | Information ] + Severity = @('Error', 'Warning') + #________________________________________ + #CustomRulePath + #Specify CustomRulePath when you have a large set of custom rules you'd like to reference + #CustomRulePath = "Module\InjectionHunter\1.0.0\InjectionHunter.psd1" + #________________________________________ + #IncludeRules + #Specify IncludeRules when you only want to run specific subset of rules instead of the default rule set. + #IncludeRules = @('PSShouldProcess', + # 'PSUseApprovedVerbs') + #________________________________________ + #ExcludeRules + #Specify ExcludeRules when you want to exclude a certain rule from the the default set of rules. + # PSUseDeclaredVarsMoreThanAssignments excluded due to false positives with Pester v5 scoping + ExcludeRules = @( + 'PSUseDeclaredVarsMoreThanAssignments' + ) + #________________________________________ + #Rules + #Here you can specify customizations for particular rules. Several examples are included below: + #Rules = @{ + # PSUseCompatibleCmdlets = @{ + # compatibility = @('core-6.1.0-windows', 'desktop-4.0-windows') + # } + # PSUseCompatibleSyntax = @{ + # Enable = $true + # TargetVersions = @( + # '3.0', + # '5.1', + # '6.2' + # ) + # } + # PSUseCompatibleCommands = @{ + # Enable = $true + # TargetProfiles = @( + # 'win-8_x64_10.0.14393.0_6.1.3_x64_4.0.30319.42000_core', # PS 6.1 on WinServer-2019 + # 'win-8_x64_10.0.17763.0_5.1.17763.316_x64_4.0.30319.42000_framework', # PS 5.1 on WinServer-2019 + # 'win-8_x64_6.2.9200.0_3.0_x64_4.0.30319.42000_framework' # PS 3 on WinServer-2012 + # ) + # } + # PSUseCompatibleTypes = @{ + # Enable = $true + # TargetProfiles = @( + # 'ubuntu_x64_18.04_6.1.3_x64_4.0.30319.42000_core', + # 'win-48_x64_10.0.17763.0_5.1.17763.316_x64_4.0.30319.42000_framework' + # ) + # # You can specify types to not check like this, which will also ignore methods and members on it: + # IgnoreTypes = @( + # 'System.IO.Compression.ZipFile' + # ) + # } + #} + #________________________________________ +} diff --git a/src/Tests/Unit/ExportedFunctions.Tests.ps1 b/src/Tests/Unit/ExportedFunctions.Tests.ps1 index 5260252..39c7e1c 100644 --- a/src/Tests/Unit/ExportedFunctions.Tests.ps1 +++ b/src/Tests/Unit/ExportedFunctions.Tests.ps1 @@ -1,154 +1,154 @@ -BeforeAll { - Set-Location -Path $PSScriptRoot - $ModuleName = 'PSReddit' - $PathToManifest = [System.IO.Path]::Combine('..', '..', $ModuleName, "$ModuleName.psd1") - Get-Module $ModuleName -ErrorAction SilentlyContinue | Remove-Module -Force - Import-Module $PathToManifest -Force - $manifestContent = Test-ModuleManifest -Path $PathToManifest - # Get all exported functions - $moduleExported = Get-Command -Module $ModuleName -CommandType Function | Select-Object -ExpandProperty Name - $manifestExported = ($manifestContent.ExportedFunctions).Keys -} -BeforeDiscovery { - Set-Location -Path $PSScriptRoot - $ModuleName = 'PSReddit' - $PathToManifest = [System.IO.Path]::Combine('..', '..', $ModuleName, "$ModuleName.psd1") - $manifestContent = Test-ModuleManifest -Path $PathToManifest - # Get all exported functions - $moduleExported = Get-Command -Module $ModuleName -CommandType Function | Select-Object -ExpandProperty Name - $manifestExported = ($manifestContent.ExportedFunctions).Keys -} -Describe $ModuleName { - - Context 'Exported Commands' -Fixture { - - Context 'Number of commands' -Fixture { - - It 'Exports the same number of public functions as what is listed in the Module Manifest' { - $manifestExported.Count | Should -BeExactly $moduleExported.Count - } - - - - } - - Context 'Explicitly exported commands' { - - It 'Includes <_> in the Module Manifest ExportedFunctions' -ForEach $moduleExported { - $manifestExported -contains $_ | Should -BeTrue - } - - - - } - } #context_ExportedCommands - - Context 'Command Help' -Fixture { - Context '<_>' -ForEach $moduleExported { - - BeforeEach { - $help = Get-Help -Name $_ -Full - } - - It -Name 'Includes a Synopsis' -Test { - $help.Synopsis | Should -Not -BeNullOrEmpty - } - - It -Name 'Includes a Description' -Test { - $help.description.Text | Should -Not -BeNullOrEmpty - } - - It -Name 'Includes an Example' -Test { - $help.examples.example | Should -Not -BeNullOrEmpty - } - } - } #context_CommandHelp - Context 'Function Logic' { - - # Mock Invoke-RestMethod for Get-RedditOAuthToken - # Mocks moved inside individual 'It' blocks for better scoping - It 'Get-RedditOAuthToken should return a token when credentials are present (mocked)' { - # Mock Invoke-RestMethod specifically for the token endpoint for this test - Mock Invoke-RestMethod -ModuleName PSReddit -ParameterFilter { $Uri -like '*api/v1/access_token*' -and $Method -eq 'Post' } -MockWith { - Write-Verbose "Mocking Invoke-RestMethod for Token Request: $Uri" - return @{ access_token = 'mock_token_123' } - } - - # Set dummy environment variables for the test scope - $env:REDDIT_CLIENT_ID = 'mock_client_id' - $env:REDDIT_CLIENT_SECRET = 'mock_client_secret' - - $token = Get-RedditOAuthToken - $token | Should -Be 'mock_token_123' - - # Clean up environment variables - Remove-Item Env:\REDDIT_CLIENT_ID - Remove-Item Env:\REDDIT_CLIENT_SECRET - } - - It 'Get-RedditSubredditPost should return posts (mocked)' { - # Mock Invoke-RestMethod specifically for the posts endpoint for this test - Mock Invoke-RestMethod -ModuleName PSReddit -ParameterFilter { $Uri -like '*oauth.reddit.com*' } -MockWith { - Write-Verbose "Mocking Invoke-RestMethod for Posts Request: $Uri" - # Simulate successful post retrieval - return @{ - data = @{ - children = @( - @{ data = @{ title = 'Mock Post 1'; score = 10; id = 'mp1' } } - @{ data = @{ title = 'Mock Post 2'; score = 20; id = 'mp2' } } - ) - } - } - } - - # Mock Invoke-RestMethod specifically for the posts endpoint for this test - Mock Invoke-RestMethod -ModuleName PSReddit -ParameterFilter { $Uri -like '*oauth.reddit.com*' } -MockWith { - Write-Verbose "Mocking Invoke-RestMethod for Posts Request: $Uri" - # Simulate successful post retrieval - return @{ - data = @{ - children = @( - @{ data = @{ title = 'Mock Post 1'; score = 10; id = 'mp1' } } - @{ data = @{ title = 'Mock Post 2'; score = 20; id = 'mp2' } } - ) - } - } - } - - # Mock Get-RedditOAuthToken specifically for this test - Mock Get-RedditOAuthToken -ModuleName PSReddit -MockWith { return 'mock_token_for_getpost' } - - $posts = Get-RedditSubredditPost -Subreddit 'powershell' -Count 2 - $posts | Should -Not -BeNullOrEmpty - $posts.Count | Should -Be 2 - $posts[0].title | Should -Be 'Mock Post 1' - $posts[1].score | Should -Be 20 - } - - It 'Get-RedditUserPost should return user posts (mocked)' { - # Mock Invoke-RestMethod specifically for the user posts endpoint - Mock Invoke-RestMethod -ModuleName PSReddit -ParameterFilter { $Uri -like '*oauth.reddit.com/user/*' } -MockWith { - Write-Verbose "Mocking Invoke-RestMethod for User Posts Request: $Uri" - # Simulate successful user post retrieval - return @{ - data = @{ - children = @( - @{ data = @{ title = 'User Post 1'; score = 15; id = 'up1' } } - @{ data = @{ title = 'User Post 2'; score = 25; id = 'up2' } } - ) - } - } - } - - # Mock Get-RedditOAuthToken specifically for this test - Mock Get-RedditOAuthToken -ModuleName PSReddit -MockWith { return 'mock_token_for_userpost' } - - $posts = Get-RedditUserPost -Username 'LukeEvansTech' -Count 2 - $posts | Should -Not -BeNullOrEmpty - $posts.Count | Should -Be 2 - $posts[0].title | Should -Be 'User Post 1' - $posts[1].score | Should -Be 25 - } - } #context_FunctionLogic -} - +BeforeAll { + Set-Location -Path $PSScriptRoot + $ModuleName = 'PSReddit' + $PathToManifest = [System.IO.Path]::Combine('..', '..', $ModuleName, "$ModuleName.psd1") + Get-Module $ModuleName -ErrorAction SilentlyContinue | Remove-Module -Force + Import-Module $PathToManifest -Force + $manifestContent = Test-ModuleManifest -Path $PathToManifest + # Get all exported functions + $moduleExported = Get-Command -Module $ModuleName -CommandType Function | Select-Object -ExpandProperty Name + $manifestExported = ($manifestContent.ExportedFunctions).Keys +} +BeforeDiscovery { + Set-Location -Path $PSScriptRoot + $ModuleName = 'PSReddit' + $PathToManifest = [System.IO.Path]::Combine('..', '..', $ModuleName, "$ModuleName.psd1") + $manifestContent = Test-ModuleManifest -Path $PathToManifest + # Get all exported functions + $moduleExported = Get-Command -Module $ModuleName -CommandType Function | Select-Object -ExpandProperty Name + $manifestExported = ($manifestContent.ExportedFunctions).Keys +} +Describe $ModuleName { + + Context 'Exported Commands' -Fixture { + + Context 'Number of commands' -Fixture { + + It 'Exports the same number of public functions as what is listed in the Module Manifest' { + $manifestExported.Count | Should -BeExactly $moduleExported.Count + } + + + + } + + Context 'Explicitly exported commands' { + + It 'Includes <_> in the Module Manifest ExportedFunctions' -ForEach $moduleExported { + $manifestExported -contains $_ | Should -BeTrue + } + + + + } + } #context_ExportedCommands + + Context 'Command Help' -Fixture { + Context '<_>' -ForEach $moduleExported { + + BeforeEach { + $help = Get-Help -Name $_ -Full + } + + It -Name 'Includes a Synopsis' -Test { + $help.Synopsis | Should -Not -BeNullOrEmpty + } + + It -Name 'Includes a Description' -Test { + $help.description.Text | Should -Not -BeNullOrEmpty + } + + It -Name 'Includes an Example' -Test { + $help.examples.example | Should -Not -BeNullOrEmpty + } + } + } #context_CommandHelp + Context 'Function Logic' { + + # Mock Invoke-RestMethod for Get-RedditOAuthToken + # Mocks moved inside individual 'It' blocks for better scoping + It 'Get-RedditOAuthToken should return a token when credentials are present (mocked)' { + # Mock Invoke-RestMethod specifically for the token endpoint for this test + Mock Invoke-RestMethod -ModuleName PSReddit -ParameterFilter { $Uri -like '*api/v1/access_token*' -and $Method -eq 'Post' } -MockWith { + Write-Verbose "Mocking Invoke-RestMethod for Token Request: $Uri" + return @{ access_token = 'mock_token_123' } + } + + # Set dummy environment variables for the test scope + $env:REDDIT_CLIENT_ID = 'mock_client_id' + $env:REDDIT_CLIENT_SECRET = 'mock_client_secret' + + $token = Get-RedditOAuthToken + $token | Should -Be 'mock_token_123' + + # Clean up environment variables + Remove-Item Env:\REDDIT_CLIENT_ID + Remove-Item Env:\REDDIT_CLIENT_SECRET + } + + It 'Get-RedditSubredditPost should return posts (mocked)' { + # Mock Invoke-RestMethod specifically for the posts endpoint for this test + Mock Invoke-RestMethod -ModuleName PSReddit -ParameterFilter { $Uri -like '*oauth.reddit.com*' } -MockWith { + Write-Verbose "Mocking Invoke-RestMethod for Posts Request: $Uri" + # Simulate successful post retrieval + return @{ + data = @{ + children = @( + @{ data = @{ title = 'Mock Post 1'; score = 10; id = 'mp1' } } + @{ data = @{ title = 'Mock Post 2'; score = 20; id = 'mp2' } } + ) + } + } + } + + # Mock Invoke-RestMethod specifically for the posts endpoint for this test + Mock Invoke-RestMethod -ModuleName PSReddit -ParameterFilter { $Uri -like '*oauth.reddit.com*' } -MockWith { + Write-Verbose "Mocking Invoke-RestMethod for Posts Request: $Uri" + # Simulate successful post retrieval + return @{ + data = @{ + children = @( + @{ data = @{ title = 'Mock Post 1'; score = 10; id = 'mp1' } } + @{ data = @{ title = 'Mock Post 2'; score = 20; id = 'mp2' } } + ) + } + } + } + + # Mock Get-RedditOAuthToken specifically for this test + Mock Get-RedditOAuthToken -ModuleName PSReddit -MockWith { return 'mock_token_for_getpost' } + + $posts = Get-RedditSubredditPost -Subreddit 'powershell' -Count 2 + $posts | Should -Not -BeNullOrEmpty + $posts.Count | Should -Be 2 + $posts[0].title | Should -Be 'Mock Post 1' + $posts[1].score | Should -Be 20 + } + + It 'Get-RedditUserPost should return user posts (mocked)' { + # Mock Invoke-RestMethod specifically for the user posts endpoint + Mock Invoke-RestMethod -ModuleName PSReddit -ParameterFilter { $Uri -like '*oauth.reddit.com/user/*' } -MockWith { + Write-Verbose "Mocking Invoke-RestMethod for User Posts Request: $Uri" + # Simulate successful user post retrieval + return @{ + data = @{ + children = @( + @{ data = @{ title = 'User Post 1'; score = 15; id = 'up1' } } + @{ data = @{ title = 'User Post 2'; score = 25; id = 'up2' } } + ) + } + } + } + + # Mock Get-RedditOAuthToken specifically for this test + Mock Get-RedditOAuthToken -ModuleName PSReddit -MockWith { return 'mock_token_for_userpost' } + + $posts = Get-RedditUserPost -Username 'LukeEvansTech' -Count 2 + $posts | Should -Not -BeNullOrEmpty + $posts.Count | Should -Be 2 + $posts[0].title | Should -Be 'User Post 1' + $posts[1].score | Should -Be 25 + } + } #context_FunctionLogic +} + diff --git a/src/Tests/Unit/PSReddit-Module.Tests.ps1 b/src/Tests/Unit/PSReddit-Module.Tests.ps1 index bec858c..78bdb3b 100644 --- a/src/Tests/Unit/PSReddit-Module.Tests.ps1 +++ b/src/Tests/Unit/PSReddit-Module.Tests.ps1 @@ -1,49 +1,49 @@ -BeforeAll { - #------------------------------------------------------------------------- - Set-Location -Path $PSScriptRoot - #------------------------------------------------------------------------- - $ModuleName = 'PSReddit' - $PathToManifest = [System.IO.Path]::Combine('..', '..', $ModuleName, "$ModuleName.psd1") - $PathToModule = [System.IO.Path]::Combine('..', '..', $ModuleName, "$ModuleName.psm1") - #------------------------------------------------------------------------- -} -Describe 'Module Tests' -Tag Unit { - Context "Module Tests" { - $script:manifestEval = $null - It 'Passes Test-ModuleManifest' { - { $script:manifestEval = Test-ModuleManifest -Path $PathToManifest } | Should -Not -Throw - $? | Should -BeTrue - } #manifestTest - It 'root module PSReddit.psm1 should exist' { - $PathToModule | Should -Exist - $? | Should -BeTrue - } #psm1Exists - It 'manifest should contain PSReddit.psm1' { - $PathToManifest | - Should -FileContentMatchExactly "PSReddit.psm1" - } #validPSM1 - It 'should have a matching module name in the manifest' { - $script:manifestEval.Name | Should -BeExactly $ModuleName - } #name - It 'should have a valid description in the manifest' { - $script:manifestEval.Description | Should -Not -BeNullOrEmpty - } #description - It 'should have a valid author in the manifest' { - $script:manifestEval.Author | Should -Not -BeNullOrEmpty - } #author - It 'should have a valid version in the manifest' { - $script:manifestEval.Version -as [Version] | Should -Not -BeNullOrEmpty - } #version - It 'should have a valid guid in the manifest' { - { [guid]::Parse($script:manifestEval.Guid) } | Should -Not -Throw - } #guid - It 'should not have any spaces in the tags' { - foreach ($tag in $script:manifestEval.Tags) { - $tag | Should -Not -Match '\s' - } - } #tagSpaces - It 'should have a valid project Uri' { - $script:manifestEval.ProjectUri | Should -Not -BeNullOrEmpty - } #uri - } #context_ModuleTests +BeforeAll { + #------------------------------------------------------------------------- + Set-Location -Path $PSScriptRoot + #------------------------------------------------------------------------- + $ModuleName = 'PSReddit' + $PathToManifest = [System.IO.Path]::Combine('..', '..', $ModuleName, "$ModuleName.psd1") + $PathToModule = [System.IO.Path]::Combine('..', '..', $ModuleName, "$ModuleName.psm1") + #------------------------------------------------------------------------- +} +Describe 'Module Tests' -Tag Unit { + Context "Module Tests" { + $script:manifestEval = $null + It 'Passes Test-ModuleManifest' { + { $script:manifestEval = Test-ModuleManifest -Path $PathToManifest } | Should -Not -Throw + $? | Should -BeTrue + } #manifestTest + It 'root module PSReddit.psm1 should exist' { + $PathToModule | Should -Exist + $? | Should -BeTrue + } #psm1Exists + It 'manifest should contain PSReddit.psm1' { + $PathToManifest | + Should -FileContentMatchExactly "PSReddit.psm1" + } #validPSM1 + It 'should have a matching module name in the manifest' { + $script:manifestEval.Name | Should -BeExactly $ModuleName + } #name + It 'should have a valid description in the manifest' { + $script:manifestEval.Description | Should -Not -BeNullOrEmpty + } #description + It 'should have a valid author in the manifest' { + $script:manifestEval.Author | Should -Not -BeNullOrEmpty + } #author + It 'should have a valid version in the manifest' { + $script:manifestEval.Version -as [Version] | Should -Not -BeNullOrEmpty + } #version + It 'should have a valid guid in the manifest' { + { [guid]::Parse($script:manifestEval.Guid) } | Should -Not -Throw + } #guid + It 'should not have any spaces in the tags' { + foreach ($tag in $script:manifestEval.Tags) { + $tag | Should -Not -Match '\s' + } + } #tagSpaces + It 'should have a valid project Uri' { + $script:manifestEval.ProjectUri | Should -Not -BeNullOrEmpty + } #uri + } #context_ModuleTests } #describe_ModuleTests From d6be8c9ada504b8d368e0c5ea2f71a115ac6bd0e Mon Sep 17 00:00:00 2001 From: Luke Evans Date: Fri, 24 Jul 2026 08:26:30 +0100 Subject: [PATCH 2/3] chore(lint): clear pre-existing lint debt surfaced by renormalization MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The line-ending renormalization touched 28 files, and super-linter (soft-launch:false, changed-files-only) then lints all of them — surfacing pre-existing style debt that was dormant only because those files hadn't changed since the linters tightened. None of it is caused by the line-ending change; this commit clears it so the lint gate stays green. Content fixes (super-linter's own prettier/markdownlint/textlint toolchain, plus manual heading levels): - Markdown formatting: blanks around headings/lists/fences, trailing spaces, single trailing newline, bare URLs wrapped (MD009/022/031/032/034/047). - MD001: issue-template sections promoted h3 -> h2 (they had no parent heading). - Prettier reflow of the docs/.github markdown. - Terminology: e-mail->email, repo->repository, NPM->npm, markdown->Markdown. Config (honoring existing/appropriate policy, not loosening standards): - .github/linters/.powershell-psscriptanalyzer.psd1 mirrors the repo's own src/PSScriptAnalyzerSettings.psd1 (super-linter reads its PSSA settings from .github/linters/, not src/), so the Pester-scoping false positive PSUseDeclaredVarsMoreThanAssignments is excluded consistently. - .github/linters/.stylelintrc.json extends super-linter's default and disables selector-class-pattern: custom.css styles Material-theme BEM classes (.md-footer-meta__inner) we don't control and can't rename. - lint.yml excludes .vscode/*.json (JSONC — comments are valid there) from the JSON linter via filter-regex-exclude. Claude-Session: https://claude.ai/code/session_01RmUNAvT3AenY6hrt4D9GAx --- .github/CODE_OF_CONDUCT.md | 28 +++++++++---------- .github/CONTRIBUTING.md | 1 - .github/ISSUE_TEMPLATE/bug-report.md | 28 +++++++++++-------- .github/ISSUE_TEMPLATE/feature_request.md | 19 +++++++------ .github/SECURITY.md | 4 +-- .../linters/.powershell-psscriptanalyzer.psd1 | 13 +++++++++ .github/linters/.stylelintrc.json | 13 +++++++++ .github/workflows/lint.yml | 3 ++ README.md | 2 ++ docs/MIGRATION.md | 22 +++++++++++++-- docs/docs/Get-RedditOAuthToken.md | 4 +-- docs/docs/Get-RedditSubredditPost.md | 4 +-- docs/docs/Get-RedditUserPost.md | 4 +-- docs/docs/module-manifest.md | 6 ++++ 14 files changed, 106 insertions(+), 45 deletions(-) create mode 100644 .github/linters/.powershell-psscriptanalyzer.psd1 create mode 100644 .github/linters/.stylelintrc.json diff --git a/.github/CODE_OF_CONDUCT.md b/.github/CODE_OF_CONDUCT.md index 7f8bc3d..1b4eaf0 100644 --- a/.github/CODE_OF_CONDUCT.md +++ b/.github/CODE_OF_CONDUCT.md @@ -14,22 +14,22 @@ appearance, race, religion, or sexual identity and orientation. Examples of behavior that contributes to creating a positive environment include: -* Using welcoming and inclusive language -* Being respectful of differing viewpoints and experiences -* Gracefully accepting constructive criticism -* Focusing on what is best for the community -* Showing empathy towards other community members +- Using welcoming and inclusive language +- Being respectful of differing viewpoints and experiences +- Gracefully accepting constructive criticism +- Focusing on what is best for the community +- Showing empathy towards other community members Examples of unacceptable behavior by participants include: -* The use of sexualized language or imagery and unwelcome sexual attention or - advances -* Trolling, insulting/derogatory comments, and personal or political attacks -* Public or private harassment -* Publishing others' private information, such as a physical or electronic - address, without explicit permission -* Other conduct which could reasonably be considered inappropriate in a - professional setting +- The use of sexualized language or imagery and unwelcome sexual attention or + advances +- Trolling, insulting/derogatory comments, and personal or political attacks +- Public or private harassment +- Publishing others' private information, such as a physical or electronic + address, without explicit permission +- Other conduct which could reasonably be considered inappropriate in a + professional setting ## Our Responsibilities @@ -47,7 +47,7 @@ threatening, offensive, or harmful. This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of -representing a project or community include using an official project e-mail +representing a project or community include using an official project email address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers. diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md index e3bacb9..ddca9ed 100644 --- a/.github/CONTRIBUTING.md +++ b/.github/CONTRIBUTING.md @@ -21,4 +21,3 @@ This project has a [Code of Conduct](CODE_OF_CONDUCT.md). ## Licensing See the [LICENSE](LICENSE.txt) file for our project's licensing. - diff --git a/.github/ISSUE_TEMPLATE/bug-report.md b/.github/ISSUE_TEMPLATE/bug-report.md index 40812c7..0960be4 100644 --- a/.github/ISSUE_TEMPLATE/bug-report.md +++ b/.github/ISSUE_TEMPLATE/bug-report.md @@ -2,23 +2,26 @@ name: Bug report about: Submit a new bug title: Bug report -labels: 'bug' -assignees: '' - +labels: "bug" +assignees: "" --- -### Expected Behavior +## Expected Behavior + -### Current Behavior +## Current Behavior + -### Possible Solution +## Possible Solution + -### Steps to Reproduce +## Steps to Reproduce + @@ -27,13 +30,16 @@ assignees: '' 3. 4. -### Context (Environment) +## Context (Environment) + -* Operating System and version as reported by `$PSVersionTable.OS`: -* PowerShell versions as reported by `$PSVersionTable.PSEdition`: + +- Operating System and version as reported by `$PSVersionTable.OS`: +- PowerShell versions as reported by `$PSVersionTable.PSEdition`: -### Detailed Description +## Detailed Description + diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md index a3a401e..239d905 100644 --- a/.github/ISSUE_TEMPLATE/feature_request.md +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -1,20 +1,23 @@ --- name: Feature request about: Suggest an idea for this project -title: 'Feature request' -labels: 'enhancement' -assignees: '' - +title: "Feature request" +labels: "enhancement" +assignees: "" --- -### Description +## Description + -### Describe the solution you'd like +## Describe the solution you'd like + -### Describe any alternatives you've considered +## Describe any alternatives you've considered + -### Additional context +## Additional context + diff --git a/.github/SECURITY.md b/.github/SECURITY.md index c782d6c..0d935ce 100644 --- a/.github/SECURITY.md +++ b/.github/SECURITY.md @@ -10,9 +10,9 @@ If you discover a vulnerability in PSReddit, please follow the _following proces 1. Open a generic bug issue advising you have discovered a vulnerability. - Avoid sharing specifics or details of the vulnerability in an open GitHub issue. -2. A repo owner will reach out to you to establish a private form of communication. +2. A repository owner will reach out to you to establish a private form of communication. 3. We will evaluate the vulnerability and, if necessary, release a fix or mitigating steps to address it. We will contact you to let you know the outcome, and will credit you in the report. Please **do not disclose the vulnerability publicly** until a fix is released! -4. Once we have either a) published a fix, or b) declined to address the vulnerability for whatever reason, you are free to publicly disclose it. \ No newline at end of file +4. Once we have either a) published a fix, or b) declined to address the vulnerability for whatever reason, you are free to publicly disclose it. diff --git a/.github/linters/.powershell-psscriptanalyzer.psd1 b/.github/linters/.powershell-psscriptanalyzer.psd1 new file mode 100644 index 0000000..d52cf3e --- /dev/null +++ b/.github/linters/.powershell-psscriptanalyzer.psd1 @@ -0,0 +1,13 @@ +@{ + # Mirrors src/PSScriptAnalyzerSettings.psd1 so super-linter applies the same + # rules the repo already uses locally (super-linter reads its PSScriptAnalyzer + # settings from .github/linters/, not from src/). + IncludeDefaultRules = $true + Severity = @('Error', 'Warning') + # PSUseDeclaredVarsMoreThanAssignments excluded due to false positives with + # Pester v5 scoping — assignments in a test file are consumed inside It/Should + # blocks that PSScriptAnalyzer cannot see across scope. + ExcludeRules = @( + 'PSUseDeclaredVarsMoreThanAssignments' + ) +} diff --git a/.github/linters/.stylelintrc.json b/.github/linters/.stylelintrc.json new file mode 100644 index 0000000..297131f --- /dev/null +++ b/.github/linters/.stylelintrc.json @@ -0,0 +1,13 @@ +{ + "extends": ["stylelint-config-standard"], + "overrides": [ + { + "extends": ["stylelint-config-recommended-scss"], + "files": ["*.scss", "**/*.scss"], + "customSyntax": "postcss-scss" + } + ], + "rules": { + "selector-class-pattern": null + } +} diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 0409790..2f42893 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -18,3 +18,6 @@ jobs: uses: LukeEvansTech/shared-workflows/.github/workflows/super-linter.yml@3771e1f9da1b3f6132eec9d63aacc0acb74660bc # v1 with: soft-launch: false + # .vscode/*.json are JSONC (comments are valid there); exclude them so the + # JSON linter's jsonc/no-comments rule doesn't fail on editor config. + filter-regex-exclude: .*/\.vscode/.* diff --git a/README.md b/README.md index bf25994..d293824 100644 --- a/README.md +++ b/README.md @@ -59,9 +59,11 @@ $posts.title ``` ## Docs + See the [documentation](./docs/index.md) for full usage, installation, and development information. ## Development + - Run tests: `Invoke-Pester ./src/Tests -CI` (Ensure Pester module is installed) - Style Guides: Refer to project configuration files (e.g., `.editorconfig`, `PSScriptAnalyzerSettings.psd1`). - Contributions welcome! Please follow standard fork & pull request workflow. diff --git a/docs/MIGRATION.md b/docs/MIGRATION.md index 3164a9e..bdd2024 100644 --- a/docs/MIGRATION.md +++ b/docs/MIGRATION.md @@ -16,18 +16,21 @@ Zensical is a modern documentation site generator built by the creators of Mater ## What Changed ### New Files + - `zensical.toml` - Main Zensical configuration (replaces `mkdocs.yml`) -- `docs/package.json` - NPM scripts for running Zensical commands +- `docs/package.json` - npm scripts for running Zensical commands - `docs/assets/stylesheets/custom.css` - Custom styling to hide Zensical branding ### Updated Files + - `docs/requirements.txt` - Now uses `zensical>=0.0.4` instead of MkDocs packages - `.github/workflows/psreddit-deploy-docs.yml` - Updated to use Zensical build process - `.gitignore` - Added `docs/site/` and `.zensical/` to ignore build artifacts - `.readthedocs.yaml` - Updated to use Zensical build commands ### Preserved Files -- All markdown documentation files (`.md`) remain unchanged + +- All Markdown documentation files (`.md`) remain unchanged - Documentation structure and navigation remain the same - All content, examples, and code snippets are preserved @@ -36,6 +39,7 @@ Zensical is a modern documentation site generator built by the creators of Mater ### MkDocs → Zensical Configuration **Site Information:** + ```yaml # mkdocs.yml site_name: PSReddit @@ -50,6 +54,7 @@ site_url = "https://LukeEvansTech.github.io/PSReddit/" ``` **Theme Configuration:** + ```yaml # mkdocs.yml theme: @@ -73,6 +78,7 @@ primary = "blue" ``` **Navigation:** + ```yaml # mkdocs.yml nav: @@ -95,6 +101,7 @@ Cmdlets = [ ## Development Workflow Changes ### Before (MkDocs) + ```bash # Install dependencies pip install -r docs/requirements.txt @@ -110,6 +117,7 @@ mkdocs gh-deploy ``` ### After (Zensical) + ```bash # Install dependencies pip install -r docs/requirements.txt @@ -129,18 +137,23 @@ npm run build ## Deployment Changes ### GitHub Actions + The workflow now uses a two-job approach: + 1. **Build Job**: Builds the site with Zensical and uploads artifact 2. **Deploy Job**: Deploys the artifact to GitHub Pages Benefits: + - Better separation of concerns - Pip caching for faster builds - Only triggers on changes to `docs/**` or workflow file - Manual trigger support via `workflow_dispatch` ### ReadTheDocs + ReadTheDocs configuration updated to use custom build commands: + ```yaml build: commands: @@ -153,6 +166,7 @@ build: ## Breaking Changes None! The migration is designed to be seamless: + - All URLs remain the same - All documentation content is preserved - Navigation structure unchanged @@ -168,13 +182,14 @@ If you need to rollback to MkDocs: 4. Delete `zensical.toml` and `docs/package.json` 5. Revert `.readthedocs.yaml` changes -The original MkDocs configuration can be found in the git history before this migration commit. +The original MkDocs configuration can be found in the Git history before this migration commit. ## Testing the Migration To verify the migration was successful: 1. **Local Build Test:** + ```bash cd docs pip install -r requirements.txt @@ -202,6 +217,7 @@ To verify the migration was successful: ## Questions or Issues? If you encounter any issues with the migration: + 1. Check the build logs in GitHub Actions 2. Verify local build works correctly 3. Review the Zensical documentation diff --git a/docs/docs/Get-RedditOAuthToken.md b/docs/docs/Get-RedditOAuthToken.md index 69a394d..ee79366 100644 --- a/docs/docs/Get-RedditOAuthToken.md +++ b/docs/docs/Get-RedditOAuthToken.md @@ -33,8 +33,8 @@ $token = Get-RedditOAuthToken ### CommonParameters -This cmdlet supports the common parameters: -Debug, -ErrorAction, -ErrorVariable, -InformationAction, -InformationVariable, -OutBuffer, -OutVariable, -PipelineVariable, -Verbose, -WarningAction, -WarningVariable, and -ProgressAction. -For more information, see about_CommonParameters (http://go.microsoft.com/fwlink/?LinkID=113216). +This cmdlet supports the common parameters: -Debug, -ErrorAction, -ErrorVariable, -InformationAction, -InformationVariable, -OutBuffer, -OutVariable, -PipelineVariable, -Verbose, -WarningAction, -WarningVariable, and -ProgressAction. +For more information, see about_CommonParameters (). ## INPUTS diff --git a/docs/docs/Get-RedditSubredditPost.md b/docs/docs/Get-RedditSubredditPost.md index 9cacf73..50d637c 100644 --- a/docs/docs/Get-RedditSubredditPost.md +++ b/docs/docs/Get-RedditSubredditPost.md @@ -206,8 +206,8 @@ Accept wildcard characters: False ### CommonParameters -This cmdlet supports the common parameters: -Debug, -ErrorAction, -ErrorVariable, -InformationAction, -InformationVariable, -OutBuffer, -OutVariable, -PipelineVariable, -Verbose, -WarningAction, -WarningVariable, and -ProgressAction. -For more information, see about_CommonParameters (http://go.microsoft.com/fwlink/?LinkID=113216). +This cmdlet supports the common parameters: -Debug, -ErrorAction, -ErrorVariable, -InformationAction, -InformationVariable, -OutBuffer, -OutVariable, -PipelineVariable, -Verbose, -WarningAction, -WarningVariable, and -ProgressAction. +For more information, see about_CommonParameters (). ## INPUTS diff --git a/docs/docs/Get-RedditUserPost.md b/docs/docs/Get-RedditUserPost.md index 308bc21..edc8601 100644 --- a/docs/docs/Get-RedditUserPost.md +++ b/docs/docs/Get-RedditUserPost.md @@ -204,8 +204,8 @@ Accept wildcard characters: False ### CommonParameters -This cmdlet supports the common parameters: -Debug, -ErrorAction, -ErrorVariable, -InformationAction, -InformationVariable, -OutBuffer, -OutVariable, -PipelineVariable, -Verbose, -WarningAction, -WarningVariable, and -ProgressAction. -For more information, see about_CommonParameters (http://go.microsoft.com/fwlink/?LinkID=113216). +This cmdlet supports the common parameters: -Debug, -ErrorAction, -ErrorVariable, -InformationAction, -InformationVariable, -OutBuffer, -OutVariable, -PipelineVariable, -Verbose, -WarningAction, -WarningVariable, and -ProgressAction. +For more information, see about_CommonParameters (). ## INPUTS diff --git a/docs/docs/module-manifest.md b/docs/docs/module-manifest.md index 99c429d..5e7d64d 100644 --- a/docs/docs/module-manifest.md +++ b/docs/docs/module-manifest.md @@ -7,15 +7,21 @@ Locale: en-US --- # PSReddit Module + ## Description + A PowerShell module (PSReddit) for authenticating with Reddit and retrieving posts. ## PSReddit Cmdlets + ### [Get-RedditOAuthToken](Get-RedditOAuthToken.md) + Retrieves an OAuth2 bearer token from Reddit. ### [Get-RedditSubredditPost](Get-RedditSubredditPost.md) + Retrieves posts from one or more subreddits. ### [Get-RedditUserPost](Get-RedditUserPost.md) + Retrieves posts submitted by specific Reddit users. From 69fece05a6329b77e5cfe6801bbb388b0d8340d7 Mon Sep 17 00:00:00 2001 From: Luke Evans Date: Fri, 24 Jul 2026 08:32:28 +0100 Subject: [PATCH 3/3] chore(lint): keep the PSSA settings file ASCII-only The em-dash in a comment tripped PSUseBOMForUnicodeEncodedFile (PSScriptAnalyzer lints the settings file itself as a changed .psd1). Replaced with ASCII. Claude-Session: https://claude.ai/code/session_01RmUNAvT3AenY6hrt4D9GAx --- .github/linters/.powershell-psscriptanalyzer.psd1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/linters/.powershell-psscriptanalyzer.psd1 b/.github/linters/.powershell-psscriptanalyzer.psd1 index d52cf3e..4bb30ee 100644 --- a/.github/linters/.powershell-psscriptanalyzer.psd1 +++ b/.github/linters/.powershell-psscriptanalyzer.psd1 @@ -5,7 +5,7 @@ IncludeDefaultRules = $true Severity = @('Error', 'Warning') # PSUseDeclaredVarsMoreThanAssignments excluded due to false positives with - # Pester v5 scoping — assignments in a test file are consumed inside It/Should + # Pester v5 scoping: assignments in a test file are consumed inside It/Should # blocks that PSScriptAnalyzer cannot see across scope. ExcludeRules = @( 'PSUseDeclaredVarsMoreThanAssignments'