MetaNull.LaravelUtils.psm1
# Module Constants and Initialization # Icon configuration based on PowerShell version $script:ModuleIcons = @{} $script:UseEmojis = $false # Detect PowerShell version and set icon preference if ($PSVersionTable.PSVersion.Major -ge 7) { # PowerShell 7+ supports Unicode emojis better $script:UseEmojis = $true $script:ModuleIcons = @{ # Status Icons Rocket = "🚀" CheckMark = "✅" Warning = "⚠️" Info = "ℹ️" Error = "❌" # Application Icons Celebration = "🎉" MobilePhone = "📱" Satellite = "📡" Lightning = "⚡" # Tool Icons Wrench = "🔧" Books = "📚" GreenHeart = "💚" Key = "🔑" FloppyDisk = "💾" # Fallback plain text versions PlainText = @{ Rocket = "[START]" CheckMark = "[OK]" Warning = "[WARN]" Info = "[INFO]" Error = "[ERROR]" Celebration = "[SUCCESS]" MobilePhone = "[APP]" Satellite = "[API]" Lightning = "[HMR]" Wrench = "[TOOLS]" Books = "[DOCS]" GreenHeart = "[HEALTH]" Key = "[DASH]" FloppyDisk = "[DEV]" } } } else { # PowerShell 5.1 and earlier - use plain text icons $script:UseEmojis = $false $script:ModuleIcons = @{ PlainText = @{ Rocket = "[START]" CheckMark = "[OK]" Warning = "[WARN]" Info = "[INFO]" Error = "[ERROR]" Celebration = "[SUCCESS]" MobilePhone = "[APP]" Satellite = "[API]" Lightning = "[HMR]" Wrench = "[TOOLS]" Books = "[DOCS]" GreenHeart = "[HEALTH]" Key = "[DASH]" FloppyDisk = "[DEV]" } } } # Color constants for consistent output Set-Variable -Name "ModuleColorSuccess" -Value "Green" -Option Constant Set-Variable -Name "ModuleColorWarning" -Value "Yellow" -Option Constant Set-Variable -Name "ModuleColorError" -Value "Red" -Option Constant Set-Variable -Name "ModuleColorInfo" -Value "Cyan" -Option Constant Set-Variable -Name "ModuleColorStep" -Value "Magenta" -Option Constant Function Get-DevIcon { <# .SYNOPSIS Gets an icon for display based on PowerShell version capabilities .DESCRIPTION Returns either an emoji icon (PowerShell 7+) or plain text fallback (PowerShell 5.1). This is a public wrapper around the private Get-ModuleIcon function. .PARAMETER IconName The name of the icon to retrieve. Valid values include: Rocket, CheckMark, Warning, Info, Error, Celebration, MobilePhone, Satellite, Lightning, Wrench, Books, GreenHeart, Key, FloppyDisk .OUTPUTS String - The icon character or text representation .EXAMPLE Get-DevIcon "Rocket" Returns "🚀" on PowerShell 7+ or "[START]" on PowerShell 5.1 .EXAMPLE $icon = Get-DevIcon "CheckMark" Write-Host "$icon Operation completed successfully!" #> [CmdletBinding()] [OutputType([string])] param( [Parameter(Mandatory = $true, Position = 0)] [ValidateSet("Rocket", "CheckMark", "Warning", "Info", "Error", "Celebration", "MobilePhone", "Satellite", "Lightning", "Wrench", "Books", "GreenHeart", "Key", "FloppyDisk")] [string]$IconName ) End { return Get-ModuleIcon -IconName $IconName } } Function Get-ModuleIcon { <# .SYNOPSIS Gets an icon for display based on PowerShell version capabilities .DESCRIPTION Returns either an emoji icon (PowerShell 7+) or plain text fallback (PowerShell 5.1) .PARAMETER IconName The name of the icon to retrieve .EXAMPLE Get-ModuleIcon "Rocket" Returns "`u{1F680}" on PowerShell 7+ or "[START]" on PowerShell 5.1 #> [CmdletBinding()] [OutputType([string])] param( [Parameter(Mandatory = $true, Position = 0)] [string]$IconName, [Parameter()] [ValidateSet("plaintext", "unicode", "auto")] [string]$Mode = "auto" ) End { if ($Mode -eq "plaintext" -or ($Mode -eq "auto" -and -not $script:UseEmojis)) { $PlainText = $true } elseif ($Mode -eq "unicode") { $PlainText = $false } else { $PlainText = $script:UseEmojis -eq $false } if ($PlainText) { $IconSet = $script:ModuleIcons.PlainText } else { $IconSet = $script:ModuleIcons } if ($IconSet.ContainsKey($IconName)) { return $IconSet[$IconName] } else { Write-Warning "Icon '$IconName' not found." } return "?" } } Function Stop-DevProcessOnPort { <# .SYNOPSIS Stops processes running on a specific port .DESCRIPTION Finds and forcefully stops all processes that are listening on the specified port. Useful for freeing up ports before starting development servers. .PARAMETER Port The port number to free up .EXAMPLE Stop-DevProcessOnPort -Port 8000 Stops all processes using port 8000 #> [CmdletBinding()] param( [Parameter(Mandatory = $true)] [int]$Port ) End { try { $processes = Get-NetTCPConnection -LocalPort $Port -ErrorAction SilentlyContinue | Select-Object -ExpandProperty OwningProcess -Unique foreach ($processId in $processes) { if ($processId -and $processId -ne 0) { $process = Get-Process -Id $processId -ErrorAction SilentlyContinue if ($process) { Write-DevWarning "Stopping process $($process.Name) (PID: $processId) on port $Port" Stop-Process -Id $processId -Force Start-Sleep -Seconds 1 } } } } catch { # Ignore errors when stopping processes Write-DevWarning "Could not stop processes on port $Port - they may have already been stopped" } } } Function Test-DevCommand { <# .SYNOPSIS Tests if a command is available in the current session .DESCRIPTION Checks if a command exists and can be executed in the current PowerShell session .PARAMETER Command The command name to test .OUTPUTS Boolean - True if command exists, False otherwise .EXAMPLE Test-DevCommand "php" Returns $true if PHP is available in PATH #> [CmdletBinding()] [OutputType([bool])] param( [Parameter(Mandatory = $true)] [string]$Command ) End { try { $null = Get-Command $Command -ErrorAction Stop return $true } catch { return $false } } } Function Test-DevPort { <# .SYNOPSIS Tests if a TCP port is open and accepting connections .DESCRIPTION Uses a TCP client to test if a port is available on localhost (127.0.0.1). This approach avoids IPv6 warnings that can occur with Test-NetConnection. .PARAMETER Port The port number to test .OUTPUTS Boolean - True if port is in use, False if available .EXAMPLE Test-DevPort 8000 Returns $true if port 8000 is in use, $false if available #> [CmdletBinding()] [OutputType([bool])] param( [Parameter(Mandatory = $true)] [int]$Port ) End { try { # Use a TCP client approach which is more reliable and doesn't show IPv6 warnings $tcpClient = New-Object System.Net.Sockets.TcpClient $tcpClient.ReceiveTimeout = 1000 $tcpClient.SendTimeout = 1000 $result = $tcpClient.BeginConnect("127.0.0.1", $Port, $null, $null) $success = $result.AsyncWaitHandle.WaitOne(1000, $false) if ($success) { try { $tcpClient.EndConnect($result) $tcpClient.Close() return $true } catch { $tcpClient.Close() return $false } } else { $tcpClient.Close() return $false } } catch { return $false } } } Function Wait-ForDevPort { <# .SYNOPSIS Waits for a port to become available (in use) with timeout .DESCRIPTION Continuously tests a port until it becomes available or timeout is reached. Useful for waiting for servers to start up. .PARAMETER Port The port number to wait for .PARAMETER TimeoutSeconds Maximum time to wait in seconds (default: 10) .PARAMETER IntervalSeconds Time between checks in seconds (default: 1) .OUTPUTS Boolean - True if port became available within timeout, False otherwise .EXAMPLE Wait-ForDevPort -Port 8000 -TimeoutSeconds 30 Waits up to 30 seconds for port 8000 to become available #> [CmdletBinding()] [OutputType([bool])] param( [Parameter(Mandatory = $true)] [int]$Port, [Parameter(Mandatory = $false)] [int]$TimeoutSeconds = 10, [Parameter(Mandatory = $false)] [int]$IntervalSeconds = 1 ) End { $elapsed = 0 while ($elapsed -lt $TimeoutSeconds) { if (Test-DevPort -Port $Port) { return $true } Start-Sleep -Seconds $IntervalSeconds $elapsed += $IntervalSeconds } return $false } } Function Write-DevError { <# .SYNOPSIS Writes an error message with appropriate icon and color .DESCRIPTION Displays an error message with error icon and red color .PARAMETER Message The message to display .EXAMPLE Write-DevError "Failed to start server" #> [CmdletBinding()] [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSAvoidUsingWriteHost', '', Justification = 'Write-Host is appropriate for colored user output in development utilities')] param( [Parameter(Mandatory = $true)] [string]$Message ) End { $icon = Get-ModuleIcon "Error" Write-Host "$icon $Message" -ForegroundColor $ModuleColorError } } Function Write-DevHeader { <# .SYNOPSIS Writes a header message with appropriate icon and color .DESCRIPTION Displays a header message with celebration icon and white color for section headers .PARAMETER Message The message to display .EXAMPLE Write-DevHeader "Starting Laravel Development Environment" #> [CmdletBinding()] [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSAvoidUsingWriteHost', '', Justification = 'Write-Host is appropriate for colored user output in development utilities')] param( [Parameter(Mandatory = $true)] [string]$Message ) End { $icon = Get-ModuleIcon "Celebration" Write-Host "" Write-Host "$icon $Message" -ForegroundColor White Write-Host "" } } Function Write-DevInfo { <# .SYNOPSIS Writes an info message with appropriate icon and color .DESCRIPTION Displays an informational message with info icon and cyan color .PARAMETER Message The message to display .EXAMPLE Write-DevInfo "Found PHP version: 8.2.12" #> [CmdletBinding()] [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSAvoidUsingWriteHost', '', Justification = 'Write-Host is appropriate for colored user output in development utilities')] param( [Parameter(Mandatory = $true)] [string]$Message ) End { $icon = Get-ModuleIcon "Info" Write-Host "$icon $Message" -ForegroundColor $ModuleColorInfo } } Function Write-DevStep { <# .SYNOPSIS Writes a step message with appropriate icon and color .DESCRIPTION Displays a step message in the development process with rocket icon and magenta color .PARAMETER Message The message to display .EXAMPLE Write-DevStep "Starting Laravel server..." #> [CmdletBinding()] [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSAvoidUsingWriteHost', '', Justification = 'Write-Host is appropriate for colored user output in development utilities')] param( [Parameter(Mandatory = $true)] [string]$Message ) End { $icon = Get-ModuleIcon "Rocket" Write-Host "$icon $Message" -ForegroundColor $ModuleColorStep } } Function Write-DevSuccess { <# .SYNOPSIS Writes a success message with appropriate icon and color .DESCRIPTION Displays a success message with checkmark icon and green color .PARAMETER Message The message to display .EXAMPLE Write-DevSuccess "Server started successfully!" #> [CmdletBinding()] [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSAvoidUsingWriteHost', '', Justification = 'Write-Host is appropriate for colored user output in development utilities')] param( [Parameter(Mandatory = $true)] [string]$Message ) End { $icon = Get-ModuleIcon "CheckMark" Write-Host "$icon $Message" -ForegroundColor $ModuleColorSuccess } } Function Write-DevWarning { <# .SYNOPSIS Writes a warning message with appropriate icon and color .DESCRIPTION Displays a warning message with warning icon and yellow color .PARAMETER Message The message to display .EXAMPLE Write-DevWarning "Port is already in use" #> [CmdletBinding()] [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSAvoidUsingWriteHost', '', Justification = 'Write-Host is appropriate for colored user output in development utilities')] param( [Parameter(Mandatory = $true)] [string]$Message ) End { $icon = Get-ModuleIcon "Warning" Write-Host "$icon $Message" -ForegroundColor $ModuleColorWarning } } Function Start-Laravel { <# .SYNOPSIS Starts the complete Laravel development environment .DESCRIPTION Starts all Laravel development components: Web server, Vite, and Queue workers .PARAMETER Path The root directory of the Laravel application .PARAMETER WebPort Port for the Laravel web server (default: 8000) .PARAMETER VitePort Port for the Vite development server (default: 5173) .PARAMETER Queue Queue name for Laravel queue workers (default: "default") .PARAMETER TimeoutSeconds Timeout in seconds for server startup checks (default: 30) .PARAMETER SkipChecks Skip port availability checks .PARAMETER Force Force stop any existing processes on the specified ports .EXAMPLE Start-Laravel -Path "C:\path\to\laravel" Starts Laravel with default settings (web on 8000, vite on 5173, default queue) .EXAMPLE Start-Laravel -Path "C:\path\to\laravel" -WebPort 8080 -VitePort 3000 -Queue "emails" -Force Starts Laravel with custom ports and queue, forcing stop of existing processes #> [CmdletBinding()] param( [Parameter(Mandatory = $true)] [ValidateScript({ Test-Path $_ -PathType Container })] [string]$Path, [Parameter()] [int]$WebPort = 8000, [Parameter()] [int]$VitePort = 5173, [Parameter()] [string]$Queue = "default", [Parameter()] [int]$TimeoutSeconds = 30, [Parameter()] [switch]$SkipChecks, [Parameter()] [switch]$Force ) Begin { Write-DevHeader "Starting Laravel Development Environment" $success = $true try { # Start Laravel Web Server Write-DevInfo "Starting Laravel web server..." $webResult = Start-LaravelWeb -Path $Path -Port $WebPort -TimeoutSeconds $TimeoutSeconds -SkipChecks:$SkipChecks -Force:$Force if (-not $webResult) { Write-DevError "Failed to start Laravel web server" $success = $false } # Start Vite Development Server Write-DevInfo "Starting Vite development server..." $viteResult = Start-LaravelVite -Path $Path -Port $VitePort -LaravelPort $WebPort -TimeoutSeconds $TimeoutSeconds -SkipChecks:$SkipChecks -Force:$Force if (-not $viteResult) { Write-DevError "Failed to start Vite development server" $success = $false } # Start Laravel Queue Worker Write-DevInfo "Starting Laravel queue worker..." $queueResult = Start-LaravelQueue -Path $Path -Queue $Queue -Force:$Force if (-not $queueResult) { Write-DevError "Failed to start Laravel queue worker" $success = $false } if ($success) { Write-DevSuccess "Laravel development environment started successfully!" Write-DevInfo "Services:" Write-DevInfo " - Web Server: http://localhost:$WebPort" Write-DevInfo " - Vite Server: http://localhost:$VitePort" Write-DevInfo " - Queue Worker: $Queue queue" } else { Write-DevWarning "Some Laravel services failed to start. Check the logs above." } return $success } catch { Write-DevError "Failed to start Laravel development environment: $($_.Exception.Message)" return $false } } } Function Start-LaravelQueue { <# .SYNOPSIS Starts the Laravel queue worker .DESCRIPTION Starts Laravel's queue worker to process background jobs .PARAMETER Path The root directory of the Laravel application .PARAMETER Queue The queue name to process (default: default) .PARAMETER ConnectionName The queue connection to use (default: uses Laravel's default) .PARAMETER MaxJobs Maximum number of jobs to process before restarting (default: 1000) .PARAMETER MaxTime Maximum time in seconds the worker should run (default: 3600) .PARAMETER Sleep Number of seconds to sleep when no jobs are available (default: 3) .PARAMETER Timeout Number of seconds a child process can run (default: 60) .PARAMETER Force Force stop any existing queue workers .EXAMPLE Start-LaravelQueue -Path "C:\path\to\laravel" Starts Laravel queue worker with default settings .EXAMPLE Start-LaravelQueue -Path "C:\path\to\laravel" -Queue "emails" -MaxJobs 500 Starts Laravel queue worker for the "emails" queue with max 500 jobs #> [CmdletBinding()] [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSAvoidUsingWriteHost', '', Justification = 'Write-Host used for debugging output display in development utility')] param( [Parameter(Mandatory = $true)] [ValidateScript({ Test-Path $_ -PathType Container })] [string]$Path, [Parameter()] [string]$Queue = "default", [Parameter()] [string]$ConnectionName, [Parameter()] [int]$MaxJobs = 1000, [Parameter()] [int]$MaxTime = 3600, [Parameter()] [int]$Sleep = 3, [Parameter()] [int]$Timeout = 60, [Parameter()] [switch]$Force ) Begin { Write-DevStep "Starting Laravel queue worker for queue '$Queue'..." if ($Force) { Write-DevInfo "Stopping any existing queue workers..." Stop-LaravelQueue -Path $Path -Queue $Queue -Force } # Build the artisan command $queueCommand = "queue:work" $queueArgs = @() if ($ConnectionName) { $queueArgs += $ConnectionName } $queueArgs += "--queue=$Queue" $queueArgs += "--max-jobs=$MaxJobs" $queueArgs += "--max-time=$MaxTime" $queueArgs += "--sleep=$Sleep" $queueArgs += "--timeout=$Timeout" $queueArgs += "--verbose" # Start queue worker $queueJob = Start-Job -ScriptBlock { Set-Location $Using:Path & php artisan $Using:queueCommand $Using:queueArgs } # Give it a moment to start Start-Sleep -Seconds 2 # Check if job is running if ($queueJob.State -eq "Running") { Write-DevSuccess "Laravel queue worker started successfully for queue '$Queue'" Write-DevInfo "Worker will process up to $MaxJobs jobs or run for $MaxTime seconds" return $queueJob } else { Write-DevError "Failed to start Laravel queue worker" # Get job output for debugging $jobOutput = Receive-Job $queueJob -ErrorAction SilentlyContinue if ($jobOutput) { Write-DevError "Queue worker output:" Write-Host $jobOutput -ForegroundColor Red } Remove-Job $queueJob -ErrorAction SilentlyContinue return $null } } } Function Start-LaravelVite { <# .SYNOPSIS Starts the Laravel Vite development server .DESCRIPTION Starts the Vite development server for Laravel frontend assets .PARAMETER Path The root directory of the Laravel application .PARAMETER Port The port number to start the Vite server on (default: 5173) .PARAMETER LaravelPort The Laravel web server port for proper integration (default: 8000) .PARAMETER TimeoutSeconds How long to wait for the server to start (default: 15) .PARAMETER SkipChecks Skip port availability checks and start immediately .PARAMETER Force Force stop any existing processes on the specified port .EXAMPLE Start-LaravelVite -Path "C:\path\to\laravel" -Port 5173 -LaravelPort 8000 Starts Vite server on port 5173 integrated with Laravel on port 8000 #> [CmdletBinding()] [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSReviewUnusedParameter', 'Path', Justification = 'Used via $Using:Path in Start-Job ScriptBlock')] [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSAvoidUsingWriteHost', '', Justification = 'Write-Host used for error output display in development utility')] param( [Parameter(Mandatory = $true)] [ValidateScript({ Test-Path $_ -PathType Container })] [string]$Path, [Parameter()] [int]$Port = 5173, [Parameter()] [int]$LaravelPort = 8000, [Parameter()] [int]$TimeoutSeconds = 15, [Parameter()] [switch]$SkipChecks, [Parameter()] [switch]$Force ) Begin { Write-DevStep "Starting Laravel Vite server on port $Port..." if (-not $SkipChecks) { # Check if port is available if (Test-DevPort -Port $Port) { if ($Force) { Write-DevWarning "Port $Port is already in use. Force stopping processes..." Stop-DevProcessOnPort -Port $Port Start-Sleep -Seconds 2 } else { Write-DevWarning "Port $Port is already in use. Attempting to free it..." Stop-DevProcessOnPort -Port $Port Start-Sleep -Seconds 2 } if (Test-DevPort -Port $Port) { Write-DevError "Unable to free port $Port. Please check what's using it and try again." return $null } } } # Start Vite server with output redirection to prevent job termination $viteJob = Start-Job -ScriptBlock { Set-Location $Using:Path # Set environment variables for Vite to ensure IPv4 binding $env:VITE_PORT = $Using:Port $env:VITE_HOST = "127.0.0.1" $env:VITE_DEV_SERVER_URL = "http://127.0.0.1:$($Using:Port)" # Start Vite with port specification using npx directly for proper argument handling npx vite --host 127.0.0.1 --port $Using:Port --strictPort 2>&1 } if ($SkipChecks) { Write-DevInfo "Laravel Vite server started (checks skipped)" Write-DevInfo "Note: Access your Vue app via Laravel at http://127.0.0.1:$LaravelPort/" return $viteJob } # Wait longer for Vite to start (it takes more time than Laravel) Write-DevInfo "Waiting for Laravel Vite server to start (timeout: $TimeoutSeconds seconds)..." if (Wait-ForDevPort -Port $Port -TimeoutSeconds $TimeoutSeconds) { Write-DevSuccess "Laravel Vite server running at http://127.0.0.1:$Port" Write-DevInfo "Note: Access your Vue app via Laravel at http://127.0.0.1:$LaravelPort/" return $viteJob } else { Write-DevError "Failed to start Laravel Vite server within $TimeoutSeconds seconds" if ($viteJob) { # Get job output for debugging Start-Sleep -Seconds 2 # Give job time to produce output $jobOutput = Receive-Job $viteJob -ErrorAction SilentlyContinue if ($jobOutput) { Write-DevError "Vite job output:" Write-Host $jobOutput -ForegroundColor Red } # Also check job state and errors if ($viteJob.State -eq "Failed") { $jobErrors = $viteJob.ChildJobs[0].Error if ($jobErrors) { Write-DevError "Vite job errors:" foreach ($err in $jobErrors) { Write-Host $err -ForegroundColor Red } } } Stop-Job $viteJob -ErrorAction SilentlyContinue Remove-Job $viteJob -ErrorAction SilentlyContinue } return $null } } } Function Start-LaravelWeb { <# .SYNOPSIS Starts the Laravel web development server .DESCRIPTION Starts the Laravel artisan serve command on the specified port with proper error handling .PARAMETER Path The root directory of the Laravel application .PARAMETER Port The port number to start the Laravel server on (default: 8000) .PARAMETER TimeoutSeconds How long to wait for the server to start (default: 10) .PARAMETER SkipChecks Skip port availability checks and start immediately .PARAMETER Force Force stop any existing processes on the specified port .EXAMPLE Start-LaravelWeb -Path "C:\path\to\laravel" -Port 8000 Starts Laravel web server on port 8000 .EXAMPLE Start-LaravelWeb -Path "C:\path\to\laravel" -Port 8001 -TimeoutSeconds 15 -SkipChecks Starts Laravel web server on port 8001 with 15 second timeout, skipping checks #> [CmdletBinding()] [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSReviewUnusedParameter', 'Path', Justification = 'Used via $Using:Path in Start-Job ScriptBlock')] [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSAvoidUsingWriteHost', '', Justification = 'Write-Host used for error output display in development utility')] param( [Parameter(Mandatory = $true)] [ValidateScript({ Test-Path $_ -PathType Container })] [string]$Path, [Parameter()] [int]$Port = 8000, [Parameter()] [int]$TimeoutSeconds = 10, [Parameter()] [switch]$SkipChecks, [Parameter()] [switch]$Force ) Begin { Write-DevStep "Starting Laravel web server on port $Port..." if ($Force -or -not $SkipChecks) { # Check if port is available if (Test-DevPort -Port $Port) { Write-DevWarning "Port $Port is already in use. Attempting to free it..." Stop-DevProcessOnPort -Port $Port Start-Sleep -Seconds 2 if (Test-DevPort -Port $Port) { Write-DevError "Unable to free port $Port. Please check what's using it and try again." return $null } } } # Start Laravel server $laravelJob = Start-Job -ScriptBlock { Set-Location $Using:Path php artisan serve --port=$Using:Port --host=127.0.0.1 } if ($SkipChecks) { Write-DevInfo "Laravel web server started (checks skipped)" return $laravelJob } # Wait for server to start with configurable timeout Write-DevInfo "Waiting for Laravel web server to start (timeout: $TimeoutSeconds seconds)..." if (Wait-ForDevPort -Port $Port -TimeoutSeconds $TimeoutSeconds) { Write-DevSuccess "Laravel web server running at http://127.0.0.1:$Port" return $laravelJob } else { Write-DevError "Failed to start Laravel web server within $TimeoutSeconds seconds" # Get job output for debugging Start-Sleep -Seconds 1 $jobOutput = Receive-Job $laravelJob -ErrorAction SilentlyContinue if ($jobOutput) { Write-DevError "Laravel job output:" Write-Host $jobOutput -ForegroundColor Red } if ($laravelJob) { Stop-Job $laravelJob -ErrorAction SilentlyContinue Remove-Job $laravelJob -ErrorAction SilentlyContinue } return $null } } } Function Stop-Laravel { <# .SYNOPSIS Stops the complete Laravel development environment .DESCRIPTION Stops all Laravel development components: Web server, Vite, and Queue workers .PARAMETER Path The root directory of the Laravel application .PARAMETER WebPort Port for the Laravel web server (default: 8000) .PARAMETER VitePort Port for the Vite development server (default: 5173) .PARAMETER Queue Queue name for Laravel queue workers (optional - stops all if not specified) .PARAMETER Force Force stop processes without graceful shutdown .EXAMPLE Stop-Laravel -Path "C:\path\to\laravel" Stops all Laravel services with default settings .EXAMPLE Stop-Laravel -Path "C:\path\to\laravel" -WebPort 8080 -VitePort 3000 -Queue "emails" -Force Forcefully stops Laravel services with custom ports and specific queue #> [CmdletBinding()] param( [Parameter(Mandatory = $true)] [ValidateScript({ Test-Path $_ -PathType Container })] [string]$Path, [Parameter()] [int]$WebPort = 8000, [Parameter()] [int]$VitePort = 5173, [Parameter()] [string]$Queue, [Parameter()] [switch]$Force ) Begin { Write-DevHeader "Stopping Laravel Development Environment" $success = $true try { # Stop Laravel Web Server Write-DevInfo "Stopping Laravel web server..." $webResult = Stop-LaravelWeb -Path $Path -Port $WebPort -Force:$Force if (-not $webResult) { Write-DevWarning "Issues stopping Laravel web server" $success = $false } # Stop Vite Development Server Write-DevInfo "Stopping Vite development server..." $viteResult = Stop-LaravelVite -Path $Path -Port $VitePort -Force:$Force if (-not $viteResult) { Write-DevWarning "Issues stopping Vite development server" $success = $false } # Stop Laravel Queue Worker Write-DevInfo "Stopping Laravel queue worker..." if ($Queue) { $queueResult = Stop-LaravelQueue -Path $Path -Queue $Queue -Force:$Force } else { $queueResult = Stop-LaravelQueue -Path $Path -Force:$Force } if (-not $queueResult) { Write-DevWarning "Issues stopping Laravel queue worker" $success = $false } if ($success) { Write-DevSuccess "Laravel development environment stopped successfully!" } else { Write-DevWarning "Some Laravel services may still be running. Check the logs above." } return $success } catch { Write-DevError "Failed to stop Laravel development environment: $($_.Exception.Message)" return $false } } } Function Stop-LaravelQueue { <# .SYNOPSIS Stops the Laravel queue worker .DESCRIPTION Stops Laravel queue worker processes gracefully .PARAMETER Path The root directory of the Laravel application .PARAMETER Force Force stop without confirmation .PARAMETER Queue Specific queue name to target (optional) .EXAMPLE Stop-LaravelQueue -Path "C:\path\to\laravel" Stops all Laravel queue workers .EXAMPLE Stop-LaravelQueue -Path "C:\path\to\laravel" -Queue "emails" -Force Force stops Laravel queue workers for the "emails" queue #> [CmdletBinding()] param( [Parameter(Mandatory = $true)] [ValidateScript({ Test-Path $_ -PathType Container })] [string]$Path, [Parameter()] [switch]$Force, [Parameter()] [string]$Queue ) Begin { Write-DevStep "Stopping Laravel queue worker$(if($Queue) { " for queue '$Queue'" })..." # Validate Laravel path if (-not (Test-Path $Path -PathType Container)) { Write-DevError "Laravel path does not exist: $Path" return $false } try { # Find queue worker processes $queueProcesses = Get-Process | Where-Object { $_.ProcessName -match "php" -and $_.CommandLine -match "artisan.*queue:work" } if ($Queue) { $queueProcesses = $queueProcesses | Where-Object { $_.CommandLine -match "queue.*$Queue" } } if (-not $queueProcesses) { Write-DevInfo "No Laravel queue worker processes found$(if($Queue) { " for queue '$Queue'" })" return $true } $stoppedAny = $false foreach ($process in $queueProcesses) { if (-not $Force) { $confirmation = Read-Host "Stop Laravel queue worker process (PID: $($process.Id))? [Y/n]" if ($confirmation -eq "n" -or $confirmation -eq "N") { Write-DevInfo "Skipping process $($process.Id)" continue } } Write-DevStep "Stopping Laravel queue worker process (PID: $($process.Id))" try { # Try graceful stop first $process.CloseMainWindow() Start-Sleep -Seconds 3 # Check if process is still running $runningProcess = Get-Process -Id $process.Id -ErrorAction SilentlyContinue if ($runningProcess) { # Force kill if still running Stop-Process -Id $process.Id -Force Start-Sleep -Seconds 1 } Write-DevSuccess "Stopped Laravel queue worker (PID: $($process.Id))" $stoppedAny = $true } catch { Write-DevError "Failed to stop process $($process.Id)`: $($_.Exception.Message)" } } if ($stoppedAny) { Write-DevSuccess "Laravel queue worker$(if($Queue) { "s for queue '$Queue'" }) stopped successfully" return $true } else { Write-DevWarning "No Laravel queue workers were stopped" return $false } } catch { Write-DevError "Failed to stop Laravel queue worker: $($_.Exception.Message)" return $false } } } Function Stop-LaravelVite { <# .SYNOPSIS Stops the Laravel Vite development server .DESCRIPTION Stops processes running on the Laravel Vite server port .PARAMETER Path The root directory of the Laravel application .PARAMETER Port The port number where Laravel Vite server is running (default: 5173) .PARAMETER Force Force stop without confirmation .EXAMPLE Stop-LaravelVite -Path "C:\path\to\laravel" -Port 5173 Stops Laravel Vite server on port 5173 .EXAMPLE Stop-LaravelVite -Path "C:\path\to\laravel" -Force Force stops Laravel Vite server with default port #> [CmdletBinding()] param( [Parameter(Mandatory = $true)] [ValidateScript({ Test-Path $_ -PathType Container })] [string]$Path, [Parameter()] [int]$Port = 5173, [Parameter()] [switch]$Force ) Begin { Write-DevStep "Stopping Laravel Vite server on port $Port..." # Validate Laravel path if (-not (Test-Path $Path -PathType Container)) { Write-DevError "Laravel path does not exist: $Path" return $false } try { if (-not (Test-DevPort -Port $Port)) { Write-DevInfo "Laravel Vite server is not running on port $Port" return $true } # Get processes using the port $processes = Get-NetTCPConnection -LocalPort $Port -ErrorAction SilentlyContinue | Select-Object -ExpandProperty OwningProcess -Unique if (-not $processes) { Write-DevWarning "Port $Port appears to be in use but no processes found" return $false } $stoppedAny = $false foreach ($processId in $processes) { if ($processId -and $processId -ne 0) { $process = Get-Process -Id $processId -ErrorAction SilentlyContinue if ($process) { # Verify this is likely a Vite server process $isViteProcess = $process.ProcessName -match "(node|npm)" -or $process.CommandLine -match "(vite|npm.*dev)" if ($isViteProcess -or $Force) { if (-not $Force) { $confirmation = Read-Host "Stop Laravel Vite process '$($process.Name)' (PID: $processId)? [Y/n]" if ($confirmation -eq "n" -or $confirmation -eq "N") { Write-DevInfo "Skipping process $processId" continue } } Write-DevStep "Stopping Laravel Vite process '$($process.Name)' (PID: $processId)" try { # Try graceful stop first $process.CloseMainWindow() Start-Sleep -Seconds 2 # Check if process is still running $runningProcess = Get-Process -Id $processId -ErrorAction SilentlyContinue if ($runningProcess) { # Force kill if still running Stop-Process -Id $processId -Force Start-Sleep -Seconds 1 } Write-DevSuccess "Stopped Laravel Vite server (PID: $processId)" $stoppedAny = $true } catch { Write-DevError "Failed to stop process $processId`: $($_.Exception.Message)" } } else { Write-DevWarning "Process '$($process.Name)' (PID: $processId) on port $Port doesn't appear to be a Laravel Vite server" Write-DevInfo "Use -Force to stop it anyway" } } } } # Verify port is now free Start-Sleep -Seconds 1 if (-not (Test-DevPort -Port $Port)) { if ($stoppedAny) { Write-DevSuccess "Laravel Vite server stopped successfully" } return $true } else { Write-DevWarning "Laravel Vite port $Port is still in use after attempting to stop processes" return $false } } catch { Write-DevError "Failed to stop Laravel Vite server on port $Port`: $($_.Exception.Message)" return $false } } } Function Stop-LaravelWeb { <# .SYNOPSIS Stops the Laravel web development server .DESCRIPTION Stops processes running on the Laravel web server port .PARAMETER Path The root directory of the Laravel application .PARAMETER Port The port number where Laravel web server is running (default: 8000) .PARAMETER Force Force stop without confirmation .EXAMPLE Stop-LaravelWeb -Path "C:\path\to\laravel" -Port 8000 Stops Laravel web server on port 8000 .EXAMPLE Stop-LaravelWeb -Path "C:\path\to\laravel" -Port 8001 -Force Force stops Laravel web server on port 8001 #> [CmdletBinding()] param( [Parameter(Mandatory = $true)] [ValidateScript({ Test-Path $_ -PathType Container })] [string]$Path, [Parameter()] [int]$Port = 8000, [Parameter()] [switch]$Force ) Begin { Write-DevStep "Stopping Laravel web server on port $Port..." # Validate Laravel path if (-not (Test-Path $Path -PathType Container)) { Write-DevError "Laravel path does not exist: $Path" return $false } try { if (-not (Test-DevPort -Port $Port)) { Write-DevInfo "Laravel web server is not running on port $Port" return $true } # Get processes using the port $processes = Get-NetTCPConnection -LocalPort $Port -ErrorAction SilentlyContinue | Select-Object -ExpandProperty OwningProcess -Unique if (-not $processes) { Write-DevWarning "Port $Port appears to be in use but no processes found" return $false } $stoppedAny = $false foreach ($processId in $processes) { if ($processId -and $processId -ne 0) { $process = Get-Process -Id $processId -ErrorAction SilentlyContinue if ($process) { # Verify this is likely a Laravel web server process $isLaravelProcess = $process.ProcessName -match "(php|artisan)" -or $process.CommandLine -match "artisan serve" if ($isLaravelProcess -or $Force) { if (-not $Force) { $confirmation = Read-Host "Stop Laravel web process '$($process.Name)' (PID: $processId)? [Y/n]" if ($confirmation -eq "n" -or $confirmation -eq "N") { Write-DevInfo "Skipping process $processId" continue } } Write-DevStep "Stopping Laravel web process '$($process.Name)' (PID: $processId)" try { # Try graceful stop first $process.CloseMainWindow() Start-Sleep -Seconds 2 # Check if process is still running $runningProcess = Get-Process -Id $processId -ErrorAction SilentlyContinue if ($runningProcess) { # Force kill if still running Stop-Process -Id $processId -Force Start-Sleep -Seconds 1 } Write-DevSuccess "Stopped Laravel web server (PID: $processId)" $stoppedAny = $true } catch { Write-DevError "Failed to stop process $processId`: $($_.Exception.Message)" } } else { Write-DevWarning "Process '$($process.Name)' (PID: $processId) on port $Port doesn't appear to be a Laravel web server" Write-DevInfo "Use -Force to stop it anyway" } } } } # Verify port is now free Start-Sleep -Seconds 1 if (-not (Test-DevPort -Port $Port)) { if ($stoppedAny) { Write-DevSuccess "Laravel web server stopped successfully" } return $true } else { Write-DevWarning "Laravel web port $Port is still in use after attempting to stop processes" return $false } } catch { Write-DevError "Failed to stop Laravel web server on port $Port`: $($_.Exception.Message)" return $false } } } Function Test-Laravel { <# .SYNOPSIS Tests if the complete Laravel development environment is running .DESCRIPTION Checks if all Laravel development components are active: Web server, Vite, and Queue workers .PARAMETER Path The root directory of the Laravel application .PARAMETER WebPort Port for the Laravel web server (default: 8000) .PARAMETER VitePort Port for the Vite development server (default: 5173) .PARAMETER Queue Queue name for Laravel queue workers (optional - checks all if not specified) .EXAMPLE Test-Laravel -Path "C:\path\to\laravel" Tests all Laravel services with default settings .EXAMPLE Test-Laravel -Path "C:\path\to\laravel" -WebPort 8080 -VitePort 3000 -Queue "emails" Tests Laravel services with custom ports and specific queue #> [CmdletBinding()] [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSAvoidUsingWriteHost', '', Justification = 'Write-Host used for status output formatting with spacing in development utility')] param( [Parameter(Mandatory = $true)] [ValidateScript({ Test-Path $_ -PathType Container })] [string]$Path, [Parameter()] [int]$WebPort = 8000, [Parameter()] [int]$VitePort = 5173, [Parameter()] [string]$Queue ) Begin { Write-DevHeader "Testing Laravel Development Environment" $webStatus = $false $viteStatus = $false $queueStatus = $false try { # Test Laravel Web Server Write-DevInfo "Testing Laravel web server..." $webStatus = Test-LaravelWeb -Path $Path -Port $WebPort # Test Vite Development Server Write-DevInfo "Testing Vite development server..." $viteStatus = Test-LaravelVite -Path $Path -Port $VitePort # Test Laravel Queue Worker Write-DevInfo "Testing Laravel queue worker..." if ($Queue) { $queueStatus = Test-LaravelQueue -Path $Path -Queue $Queue } else { $queueStatus = Test-LaravelQueue -Path $Path } # Summary Write-Host "" Write-DevInfo "Laravel Development Environment Status:" Write-DevInfo " - Web Server (port $WebPort): $(if($webStatus) { 'Running' } else { 'Stopped' })" Write-DevInfo " - Vite Server (port $VitePort): $(if($viteStatus) { 'Running' } else { 'Stopped' })" Write-DevInfo " - Queue Worker$(if($Queue) { " ($Queue)" }): $(if($queueStatus) { 'Running' } else { 'Stopped' })" $allRunning = $webStatus -and $viteStatus -and $queueStatus if ($allRunning) { Write-DevSuccess "All Laravel services are running!" } else { $runningCount = @($webStatus, $viteStatus, $queueStatus) | Where-Object { $_ } | Measure-Object | Select-Object -ExpandProperty Count Write-DevWarning "$runningCount of 3 Laravel services are running" } return @{ Web = $webStatus Vite = $viteStatus Queue = $queueStatus All = $allRunning } } catch { Write-DevError "Failed to test Laravel development environment: $($_.Exception.Message)" return @{ Web = $false Vite = $false Queue = $false All = $false } } } } Function Test-LaravelQueue { <# .SYNOPSIS Tests if the Laravel queue worker is running .DESCRIPTION Checks if Laravel queue worker processes are active .PARAMETER Path The root directory of the Laravel application .PARAMETER Queue Specific queue name to check (optional) .EXAMPLE Test-LaravelQueue -Path "C:\path\to\laravel" Tests if any Laravel queue workers are running .EXAMPLE Test-LaravelQueue -Path "C:\path\to\laravel" -Queue "emails" Tests if Laravel queue workers are running for the "emails" queue #> [CmdletBinding()] param( [Parameter(Mandatory = $true)] [ValidateScript({ Test-Path $_ -PathType Container })] [string]$Path, [Parameter()] [string]$Queue ) Begin { Write-DevInfo "Testing Laravel queue worker$(if($Queue) { " for queue '$Queue'" })..." # Validate Laravel path if (-not (Test-Path $Path -PathType Container)) { Write-DevError "Laravel path does not exist: $Path" return $false } try { # Find queue worker processes using WMI for more reliable command line detection # Note: Using WMI for compatibility with existing tests and broad system support $queueProcesses = Get-WmiObject Win32_Process | Where-Object { $_.Name -match "php" -and $_.CommandLine -match "artisan.*queue:work" } if ($Queue) { $queueProcesses = $queueProcesses | Where-Object { $_.CommandLine -match "queue.*$Queue" } } if ($queueProcesses) { $processCount = $queueProcesses.Count Write-DevSuccess "Found $processCount Laravel queue worker process$(if($processCount -gt 1) { 'es' })$(if($Queue) { " for queue '$Queue'" })" foreach ($process in $queueProcesses) { Write-DevInfo " - PID: $($process.ProcessId), Started: $($process.CreationDate)" } return $true } else { Write-DevInfo "No Laravel queue worker processes found$(if($Queue) { " for queue '$Queue'" })" return $false } } catch { Write-DevError "Failed to test Laravel queue worker: $($_.Exception.Message)" return $false } } } Function Test-LaravelVite { <# .SYNOPSIS Tests if the Laravel Vite development server is running .DESCRIPTION Checks if the Laravel Vite server is responding on the specified port .PARAMETER Port The port number to test (default: 5173) .EXAMPLE Test-LaravelVite -Port 5173 Tests if Laravel Vite server is running on port 5173 #> [CmdletBinding()] param( [Parameter()] [int]$Port = 5173 ) Begin { Write-DevInfo "Testing Laravel Vite server on port $Port..." if (Test-DevPort -Port $Port) { try { # Try to make a simple HTTP request to verify it's actually Vite $response = Invoke-WebRequest -Uri "http://127.0.0.1:$Port" -Method HEAD -TimeoutSec 5 -ErrorAction SilentlyContinue if ($response) { Write-DevSuccess "Laravel Vite server is running and responding on port $Port" return $true } else { Write-DevWarning "Port $Port is in use but not responding to HTTP requests" return $false } } catch { Write-DevWarning "Port $Port is in use but HTTP test failed: $($_.Exception.Message)" return $false } } else { Write-DevInfo "Laravel Vite server is not running on port $Port" return $false } } } Function Test-LaravelWeb { <# .SYNOPSIS Tests if the Laravel web development server is running .DESCRIPTION Checks if the Laravel web server is responding on the specified port .PARAMETER Path The root directory of the Laravel application .PARAMETER Port The port number to test (default: 8000) .EXAMPLE Test-LaravelWeb -Path "C:\path\to\laravel" -Port 8000 Tests if Laravel web server is running on port 8000 #> [CmdletBinding()] param( [Parameter(Mandatory = $true)] [ValidateScript({ Test-Path $_ -PathType Container })] [string]$Path, [Parameter()] [int]$Port = 8000 ) Begin { Write-DevInfo "Testing Laravel web server on port $Port..." # Validate Laravel path if (-not (Test-Path $Path -PathType Container)) { Write-DevError "Laravel path does not exist: $Path" return $false } if (Test-DevPort -Port $Port) { try { # Try to make a simple HTTP request to verify it's actually Laravel $response = Invoke-WebRequest -Uri "http://127.0.0.1:$Port" -Method HEAD -TimeoutSec 5 -ErrorAction SilentlyContinue if ($response) { Write-DevSuccess "Laravel web server is running and responding on port $Port" return $true } else { Write-DevWarning "Port $Port is in use but not responding to HTTP requests" return $false } } catch { Write-DevWarning "Port $Port is in use but HTTP test failed: $($_.Exception.Message)" return $false } } else { Write-DevInfo "Laravel web server is not running on port $Port" return $false } } } |