Public/New-PstScript.ps1
|
function New-PstScript { <# .SYNOPSIS Creates a new script file from a template or using intelligent code generation. .DESCRIPTION This function takes a name parameter and creates a new PowerShell script based on the specified complexity level. It supports two modes: 1. Template Mode (default): Copies a script template 2. Generator Mode (-UseGenerator): Uses the intelligent code generation system to create customized scripts with proper structure based on complexity level Complexity Levels: - Basic: Simple script with minimal structure (no CmdletBinding, simple execution) - Standard: Script with CmdletBinding, parameter validation, and error handling - Advanced: Full script features including ShouldProcess, comprehensive error handling Legacy ComplexityLevel values (Basic, Moderate, Complex) are automatically mapped to the new system. .PARAMETER Name (Required) The name for the new script file. .PARAMETER Complexity (Optional) The complexity level for code generation. Valid values: Basic, Standard, Advanced. Defaults to Standard. Only used with -UseGenerator switch. .PARAMETER ComplexityLevel (Optional) Legacy parameter for template mode. Valid values: Basic, Moderate, Complex. Defaults to Moderate for backward compatibility. Ignored when -UseGenerator is specified. .PARAMETER Parameters (Optional) Array of parameter definitions for the generated script. Each can be: - A string (parameter name, defaults to [string] type) - A hashtable with Name, Type, Mandatory, Pipeline, DefaultValue, etc. Only used with -UseGenerator switch. .PARAMETER Synopsis (Optional) Brief description for the script's help. Only used with -UseGenerator. .PARAMETER UseGenerator (Optional) Switch to use the intelligent code generation system instead of templates. .PARAMETER Force (Optional) Forces creation even if file exists, useful in automation scenarios. .PARAMETER IncludeProjectLayout (Optional) Creates a complete project structure including src/, tests/, docs/ directories. .EXAMPLE New-PstScript -Name MyScript Creates a new script using the Moderate template (legacy mode). .EXAMPLE New-PstScript -Name DataProcessor -UseGenerator -Complexity Standard Creates a Standard complexity script using the intelligent code generator. .EXAMPLE New-PstScript -Name BatchJob -UseGenerator -Complexity Advanced -Parameters @('InputPath', 'OutputPath') Creates an Advanced script with custom parameters using the code generator. .NOTES Template mode requires template files in Resources/Samples directory. Generator mode uses the new complexity schema system for dynamic code creation. .LINK https://docs.microsoft.com/en-us/powershell/scripting/overview #> [CmdletBinding(SupportsShouldProcess)] param ( [Parameter(Mandatory = $true, Position = 0, HelpMessage = "The name for the new script file.")] [ValidatePattern('^[A-Za-z][A-Za-z0-9\-_]*$')] [string]$Name, [Parameter(Mandatory = $false, HelpMessage = "Complexity level for code generation (Basic, Standard, Advanced).")] [ValidateSet("Basic", "Standard", "Advanced")] [string]$Complexity = "Standard", [Parameter(Mandatory = $false, HelpMessage = "Legacy complexity level for template mode.")] [ValidateSet("Basic", "Moderate", "Complex")] [string]$ComplexityLevel = "Moderate", [Parameter(Mandatory = $false, HelpMessage = "Parameter definitions for the generated script.")] [object[]]$Parameters = @(), [Parameter(Mandatory = $false, HelpMessage = "Brief description for the script's help.")] [string]$Synopsis, [Parameter(Mandatory = $false, HelpMessage = "Use the intelligent code generation system instead of templates.")] [switch]$UseGenerator, [Parameter(Mandatory = $false)] [switch]$Force, [Parameter(Mandatory = $false)] [switch]$IncludeProjectLayout ) begin { Write-Debug -Message "Begin '$($MyInvocation.MyCommand.Name)' at '$(Get-Date)'" # Determine file path based on project layout option if ($IncludeProjectLayout) { $projectRoot = $Name $newFilePath = Join-Path $projectRoot "src" "$Name.ps1" } else { $newFilePath = "$Name.ps1" } if ($UseGenerator) { # Using new code generation system Write-Verbose "Using intelligent code generation with complexity: $Complexity" } else { # Using legacy template mode # Select template based on complexity level $templateFileName = switch ($ComplexityLevel) { "Basic" { "Script-Basic.ps1" } "Moderate" { "Script-Moderate.ps1" } "Complex" { "Script-Complex.ps1" } default { "Script-Moderate.ps1" } # Fallback to Moderate } $templatePath = $Samples[$templateFileName] Write-Verbose "Using template mode with complexity level: $ComplexityLevel" Write-Verbose "Selected template: $templateFileName" if (-not $templatePath) { throw "Template '$templateFileName' not found in Samples hashtable. Available templates: $($Samples.Keys -join ', ')" } if (-not (Test-Path $templatePath)) { throw "Template file not found: $templatePath" } else { Write-Verbose "Template file found: $templatePath" } } } process { try { # Check if we're in automation mode - inline check to avoid scope issues $isAutomationMode = $Force -or ($Global:PstAutomationMode -eq $true) -or ($env:CI -eq 'true') -or ($env:GITHUB_ACTIONS -eq 'true') -or ($Host.Name -eq 'ServerRemoteHost') -or (-not [Environment]::UserInteractive) -or ($global:ConfirmPreference -eq 'None') # Create project structure if IncludeProjectLayout is specified if ($IncludeProjectLayout) { $projectRoot = $Name # Create directory structure $directories = @( $projectRoot, (Join-Path $projectRoot "src"), (Join-Path $projectRoot "tests"), (Join-Path $projectRoot "tests" "Unit"), (Join-Path $projectRoot "tests" "Integration"), (Join-Path $projectRoot "docs") ) foreach ($dir in $directories) { if (-not (Test-Path $dir)) { if ($isAutomationMode -or $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 = @" # $Name PowerShell script for [brief description]. ## Installation ``````powershell .\src\$Name.ps1 `````` ## Usage ``````powershell .\src\$Name.ps1 -Parameter Value `````` ## Testing ``````powershell Invoke-Pester -Path .\tests\ `````` ## License [Specify license] "@ $readmePath = Join-Path $projectRoot "README.md" if ($isAutomationMode -or $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 $projectRoot ".gitignore" if ($isAutomationMode -or $PSCmdlet.ShouldProcess($gitignorePath, "Create .gitignore")) { $gitignoreContent | Set-Content -Path $gitignorePath Write-Verbose "Created: $gitignorePath" } # Create docs README.md $docsReadmeContent = @" # $Name Documentation ## Overview [Script overview and purpose] ## Parameters [Parameter documentation] ## Examples [Usage examples] ## Notes [Additional notes] "@ $docsReadmePath = Join-Path $projectRoot "docs" "README.md" if ($isAutomationMode -or $PSCmdlet.ShouldProcess($docsReadmePath, "Create docs README.md")) { $docsReadmeContent | Set-Content -Path $docsReadmePath Write-Verbose "Created: $docsReadmePath" } # Create test file $testContent = @" BeforeAll { `$scriptPath = "`$PSScriptRoot\..\..\src\$Name.ps1" } Describe "$Name" { Context "When executed with valid parameters" { It "Should execute without errors" { # Arrange # Act # Assert `$true | Should -Be `$true } } Context "When executed with invalid parameters" { It "Should handle errors gracefully" { # Arrange & Act & Assert `$true | Should -Be `$true } } } "@ $testFilePath = Join-Path $projectRoot "tests" "Unit" "$Name.Tests.ps1" if ($isAutomationMode -or $PSCmdlet.ShouldProcess($testFilePath, "Create test file")) { $testContent | Set-Content -Path $testFilePath Write-Verbose "Created: $testFilePath" } } # Check if file already exists if ((Test-Path $newFilePath) -and -not $isAutomationMode) { if (-not $PSCmdlet.ShouldProcess($newFilePath, "Overwrite existing script file")) { Write-Warning "Operation cancelled by user." return } } elseif ((Test-Path $newFilePath) -and $isAutomationMode -and -not $Force) { Write-Warning "File '$newFilePath' already exists. Use -Force to overwrite in automation mode." return } # Proceed with file creation - skip ShouldProcess in automation mode if ($isAutomationMode -or $PSCmdlet.ShouldProcess($newFilePath, "Create new script file")) { # Create parent directory if it doesn't exist (for project layout) $parentDir = Split-Path -Path $newFilePath -Parent if ($parentDir -and -not (Test-Path $parentDir)) { New-Item -Path $parentDir -ItemType Directory -Force | Out-Null } if ($UseGenerator) { # Use intelligent code generation Write-Verbose "Generating script code with complexity: $Complexity" $generatorParams = @{ FunctionName = $Name Type = 'Script' Complexity = $Complexity } if ($Parameters.Count -gt 0) { $generatorParams.Parameters = $Parameters } if ($Synopsis) { $generatorParams.Synopsis = $Synopsis } $newContent = New-PstGeneratedCode @generatorParams } else { # Use template mode Write-Information -Message "Copy-Item -Path '$($templatePath)' -Destination '$(Join-Path (Get-Location) $newFilePath)'" Copy-Item -Path $templatePath -Destination $newFilePath $newContent = $null # Content already copied } # Write content if generated (template mode uses Copy-Item) if ($newContent) { $newContent | Set-Content -Path $newFilePath } if ($IncludeProjectLayout) { Write-Output "New script project created: $projectRoot" Write-Output " - Script file: $newFilePath" Write-Output " - Test file: tests\Unit\$Name.Tests.ps1" Write-Output " - Documentation: docs\README.md" if ($UseGenerator) { Write-Output " - Generated with: $Complexity complexity" } } else { if ($UseGenerator) { Write-Output "New script file created: $newFilePath (Generated: $Complexity complexity)" } else { Write-Output "New script file created: $newFilePath" } } } else { Write-Verbose "Operation cancelled or skipped." } } catch { if ($_.Exception -and $_.Exception.Message) { Write-Error "An error occurred: $($_.Exception.Message)" } else { Write-Error "An error occurred, but no additional information is available." } } } end { if ($?) { Write-Debug -Message "End '$($MyInvocation.MyCommand.Name)' at '$(Get-Date)'" } } } |