Public/New-PstModule.ps1

function New-PstModule {
    <#
.SYNOPSIS
    Creates a new PowerShell module from a template zip file.
.DESCRIPTION
    This function unpacks a zip file containing a base module template, renames the `.psm1` and `.psd1` template files
    according to the module name (derived from the leaf of the Path parameter), and performs error handling.

    Module templates are created with Complex-level features including proper folder structure,
    manifest files, and PowerShell best practices for production-ready modules.
.PARAMETER Path
    (Optional) The destination path where the new module will be created. Defaults to the current directory.
.PARAMETER IncludeProjectLayout
    (Optional) Creates a complete project structure with src/, tests/, docs/ directories and supporting files.
.EXAMPLE
    New-PstModule -Path "C:\Modules\MyNewModule"
    This example creates a new module named `MyNewModule` in the `C:\Modules` directory with complex-level features.
.EXAMPLE
    New-PstModule -Path "C:\Modules\MyAwesomeModule" -IncludeProjectLayout
    Creates a complete project structure with the module in src/, test directories, documentation, and .gitignore.
.NOTES
    This function creates production-ready PowerShell modules with proper structure and best practices.
    Module templates always use Complex-level patterns for reliability and maintainability.
.LINK
    No links available.
#>

    [CmdletBinding(SupportsShouldProcess)]
    param (
        [Parameter(Mandatory = $false, Position = 0, HelpMessage = "The destination path where the new module will be created.")]
        [string]$Path = (Get-Location),

        [Parameter(Mandatory = $false)]
        [switch]$IncludeProjectLayout
    )
    begin {
        Write-Debug -Message "Begin '$($MyInvocation.MyCommand.Name)' at '$(Get-Date)'"
        Write-Verbose -Message "Path: '$($Path)'"
        # Check if Path is empty
        if (-not $Path) {
            throw "Path cannot be empty."
        }

        # Determine actual module destination based on project layout option
        if ($IncludeProjectLayout) {
            $ModuleName = Split-Path -Path $Path -Leaf
            $ModuleDestination = Join-Path $Path "src" $ModuleName
        } else {
            $ModuleDestination = $Path
            $ModuleName = Split-Path -Path $Path -Leaf
        }

        # Check if the module destination is not empty (contains any files or folders)
        if (Test-Path -Path $ModuleDestination) {
            $existingItems = @(Get-ChildItem -Path $ModuleDestination -Force)
            if ($existingItems.Count -gt 0) {
                throw "The path '$ModuleDestination' is not empty. Please choose an empty directory."
            }
        }

        Write-Verbose -Message "ModuleName: '$($ModuleName)'"
        Write-Verbose -Message "ModuleDestination: '$($ModuleDestination)'"

        # Use the predefined file path from the $Samples hashtable
        if (-not $Samples.ContainsKey("Module.zip")) {
            throw "The file 'Module.zip' was not found in the Samples hashtable."
        }
        $ZipFilePath = $Samples["Module.zip"]
        Write-Verbose -Message "ZipFilePath: '$($ZipFilePath)'"
        $result = @{}
    }
    process {
        try {
            # Create project structure if IncludeProjectLayout is specified
            if ($IncludeProjectLayout) {
                # Create directory structure
                $directories = @(
                    $Path,
                    (Join-Path $Path "src"),
                    (Join-Path $Path "tests"),
                    (Join-Path $Path "tests" "Unit"),
                    (Join-Path $Path "tests" "Integration"),
                    (Join-Path $Path "docs")
                )

                foreach ($dir in $directories) {
                    if (-not (Test-Path $dir)) {
                        if ($PSCmdlet.ShouldProcess($dir, "Create directory")) {
                            New-Item -Path $dir -ItemType Directory -Force | Out-Null
                            Write-Verbose "Created directory: $dir"
                        }
                    }
                }

                # Create README.md in root
                $readmeContent = @"
# $ModuleName

PowerShell module for [brief description].

## Installation

``````powershell
Import-Module .\src\$ModuleName
``````

## Usage

``````powershell
Import-Module .\src\$ModuleName
Get-Command -Module $ModuleName
``````

## Testing

``````powershell
Invoke-Pester -Path .\tests\
``````

## License

[Specify license]
"@

                $readmePath = Join-Path $Path "README.md"
                if ($PSCmdlet.ShouldProcess($readmePath, "Create README.md")) {
                    $readmeContent | Set-Content -Path $readmePath
                    Write-Verbose "Created: $readmePath"
                }

                # Create .gitignore
                $gitignoreContent = @"
# Build artifacts
build/
*.nupkg

# Test results
TestResults*.xml
coverage*.xml

# Temporary files
temp/
*.tmp
*.log
"@

                $gitignorePath = Join-Path $Path ".gitignore"
                if ($PSCmdlet.ShouldProcess($gitignorePath, "Create .gitignore")) {
                    $gitignoreContent | Set-Content -Path $gitignorePath
                    Write-Verbose "Created: $gitignorePath"
                }

                # Create docs README.md
                $docsReadmeContent = @"
# $ModuleName Documentation

## Overview

[Module overview and purpose]

## Cmdlets

[List of exported cmdlets and their usage]

## Examples

[Usage examples]

## Notes

[Additional notes]
"@

                $docsReadmePath = Join-Path $Path "docs" "README.md"
                if ($PSCmdlet.ShouldProcess($docsReadmePath, "Create docs README.md")) {
                    $docsReadmeContent | Set-Content -Path $docsReadmePath
                    Write-Verbose "Created: $docsReadmePath"
                }
            }

            Write-Information -Message "Unpacking zip file to '$($ModuleDestination)'"
            # Ensure the destination path exists
            if (-not (Test-Path -Path $ModuleDestination)) {
                if ($PSCmdlet.ShouldProcess($ModuleDestination, "Create module directory")) {
                    New-Item -Path $ModuleDestination -ItemType Directory -Force | Out-Null
                }
            }
            # Extract the contents of the zip file to the destination path
            if ($PSCmdlet.ShouldProcess($ModuleDestination, "Extract module template")) {
                Expand-Archive -Path $ZipFilePath -DestinationPath $ModuleDestination -Force

                # Get the .psm1 file from the extracted contents
                $Psm1File = Get-ChildItem -Path $ModuleDestination -Filter "*.psm1" | Select-Object -First 1
                if ($Psm1File) {
                    # Rename the .psm1 file to match the module name
                    $NewPsm1Name = Join-Path -Path $ModuleDestination -ChildPath "$ModuleName.psm1"
                    if ($PSCmdlet.ShouldProcess($NewPsm1Name, "Rename .psm1 file")) {
                        Rename-Item -Path $Psm1File.FullName -NewName $NewPsm1Name -Force
                        Write-Information -Message "Renamed .psm1 file to '$NewPsm1Name'"
                    }
                } else {
                    throw "No .psm1 file found in the extracted template."
                }
                # Get the .psd1 file from the extracted contents
                $Psd1File = Get-ChildItem -Path $ModuleDestination -Filter "*.psd1" | Select-Object -First 1
                if ($Psd1File) {
                    # Rename the .psd1 file to match the module name
                    $NewPsd1Name = Join-Path -Path $ModuleDestination -ChildPath "$ModuleName.psd1"
                    if ($PSCmdlet.ShouldProcess($NewPsd1Name, "Rename .psd1 file")) {
                        Rename-Item -Path $Psd1File.FullName -NewName $NewPsd1Name -Force
                        Write-Information -Message "Renamed .psd1 file to '$NewPsd1Name'"
                    }
                } else {
                    Write-Warning -Message "No .psd1 file found in the extracted template."
                }
            }

            if ($IncludeProjectLayout) {
                Write-Output "New module project created: $Path"
                Write-Output " - Module: $ModuleDestination"
                Write-Output " - Tests: $(Join-Path $Path 'tests')"
                Write-Output " - Documentation: $(Join-Path $Path 'docs' 'README.md')"
            } else {
                Write-Output $result
            }
        }
        catch {
            Write-Error -Message "An error occurred: $($_.Exception.Message)"
        }
    }
    end {
        if ($?) {
            Write-Debug -Message "End '$($MyInvocation.MyCommand.Name)' at '$(Get-Date)'"
            if ($IncludeProjectLayout) {
                Write-Information -Message "Module project '$ModuleName' created successfully at '$Path'."
            } else {
                Write-Information -Message "Module '$ModuleName' created successfully at '$Path'."
            }
        }
    }
}