scripts/modules/project/initialize-project.ps1
|
# strangeloop Setup - Project Initialization Module # Version: 1.0.0 param( [string]${project-name}, [string]${loop-name}, [string]${project-path}, [string]$Platform, [switch]${requires-wsl}, [string]${base-directory}, [switch]${test-only}, [switch]${what-if} ) # Import shared modules $SharedPath = Split-Path $PSScriptRoot -Parent | Join-Path -ChildPath "shared" . "$SharedPath\write-functions.ps1" . "$SharedPath\test-functions.ps1" # Global context object for passing information between phases $Global:GitContext = @{ IsGitControlled = $false WillInitializeGit = $false SkipGit = $false LocalBranch = $null RemoteUrl = $null RemotePushSuccess = $false ProjectPath = $null } # Global phase skip flags $Global:PhaseSkips = @{ Git = $false Pipeline = $false } function Get-ProjectPath { <# .SYNOPSIS Gets project parent directory from user with intelligent defaults .PARAMETER LoopName Name of the selected loop for intelligent path suggestion .PARAMETER ProjectName Project name (used for context, but not included in returned path) .PARAMETER Platform Target platform (WSL or Windows) .PARAMETER RequiresWSL Whether the loop requires WSL environment .OUTPUTS String containing the parent directory path where project folder will be created #> param( [string]$LoopName, [string]$ProjectName, [string]$Platform, [switch]$RequiresWSL ) try { # Determine if this is a WSL loop $isWindowsLoop = $LoopName -in @('asp-dotnet-framework-api', 'ads-snr-basic', 'flask-windows') $isWSLLoop = -not $isWindowsLoop $effectiveRequiresWSL = $RequiresWSL -or $isWSLLoop # Generate intelligent default parent directory based on platform and loop requirements $defaultParentPath = $null if ($effectiveRequiresWSL -or $Platform -eq "WSL") { # WSL parent path try { $wslUser = & wsl -- whoami 2>&1 if ($wslUser) { $defaultParentPath = "/home/$($wslUser.Trim())/AdsSnR_Containers/services" } else { $defaultParentPath = "/home/\$USER/AdsSnR_Containers/services" } } catch { $defaultParentPath = "/home/\$USER/AdsSnR_Containers/services" } } else { # Windows parent path $defaultParentPath = "Q:\src\AdsSnR_Containers\services" } Write-Host "" Write-Host "📁 Project Parent Directory Selection" -ForegroundColor Cyan Write-Host "Platform: $(if ($effectiveRequiresWSL) { 'WSL' } else { 'Windows' })" -ForegroundColor Gray Write-Host "Loop: $LoopName" -ForegroundColor Gray Write-Host "Project '$ProjectName' will be created in this directory" -ForegroundColor Gray Write-Host "" $parentPath = Read-UserPrompt -Prompt "Parent directory" -DefaultValue $defaultParentPath if ([string]::IsNullOrWhiteSpace($parentPath)) { $parentPath = $defaultParentPath } Write-Info "Selected parent directory: $parentPath" Write-Info "Project will be created at: $parentPath/$ProjectName" return $parentPath } catch { Write-Warning "Error determining project parent path: $($_.Exception.Message)" return $null } } function Test-GitRepository { <# .SYNOPSIS Tests if a path is git-controlled and gets repository information .PARAMETER Path Path to test for git repository .OUTPUTS Hashtable with git repository information #> param( [string]$Path ) try { $result = @{ IsGitRepo = $false CurrentBranch = $null HasRemote = $false RemoteUrl = $null GitRepositoryPath = $null } # Check if path exists and is a directory $isWSLPath = $Path.StartsWith('/') -or $Path.Contains('/home/') if ($isWSLPath) { # Test WSL path - check for git repository, handling non-existent paths by checking parents $pathToTest = $Path # If the target path doesn't exist, check parent directories for git repositories $pathExists = & wsl -- bash -c "test -d '$pathToTest' && echo 'exists' || echo 'missing'" 2>&1 if ($pathExists -ne 'exists') { # Path doesn't exist, check if any parent directory is a git repository $parentPath = $pathToTest $maxDepth = 5 # Prevent infinite loops $depth = 0 while ($parentPath -ne '/' -and $parentPath -ne '' -and $depth -lt $maxDepth) { # Use Unix-style path manipulation for WSL paths if ($parentPath.EndsWith('/')) { $parentPath = $parentPath.TrimEnd('/') } $lastSlash = $parentPath.LastIndexOf('/') if ($lastSlash -gt 0) { $parentPath = $parentPath.Substring(0, $lastSlash) } else { $parentPath = '/' } if ($parentPath -eq '' -or $parentPath -eq '/') { break } $parentExists = & wsl -- bash -c "test -d '$parentPath' && echo 'exists' || echo 'missing'" 2>&1 if ($parentExists -eq 'exists') { $pathToTest = $parentPath break } $depth++ } } # Now check if the determined path is in a git repository # Check if this is a git repository using a simpler approach $gitCheck = & wsl -- bash -c "cd '$pathToTest' && git rev-parse --git-dir && echo 'git-success'" 2>&1 $result.IsGitRepo = ($gitCheck -like '*git-success*') if ($result.IsGitRepo) { # Store the git repository path (the path where git commands should be run) $result.GitRepositoryPath = $pathToTest # Get current branch $branchResult = & wsl -- bash -c "cd '$pathToTest' && git branch --show-current" 2>&1 if ($branchResult -and $branchResult.Trim()) { $result.CurrentBranch = $branchResult.Trim() } # Check for remote $remoteCheck = & wsl -- bash -c "cd '$pathToTest' && git remote -v" 2>&1 if ($remoteCheck) { $result.HasRemote = $true $result.RemoteUrl = ($remoteCheck | Where-Object { $_ -match "origin.*\(push\)" } | ForEach-Object { ($_ -split "\s+")[1] }) | Select-Object -First 1 } } } else { # Test Windows path if (Test-Path $Path) { Push-Location $Path try { $gitCheck = git rev-parse --git-dir 2>&1 $result.IsGitRepo = ($LASTEXITCODE -eq 0) if ($result.IsGitRepo) { # Store the git repository path (the path where git commands should be run) $result.GitRepositoryPath = $Path # Get current branch $branchResult = git branch --show-current 2>&1 if ($LASTEXITCODE -eq 0) { $result.CurrentBranch = $branchResult.Trim() } # Check for remote $remoteCheck = git remote -v 2>&1 if ($LASTEXITCODE -eq 0 -and $remoteCheck) { $result.HasRemote = $true $result.RemoteUrl = ($remoteCheck | Where-Object { $_ -match "origin.*\(push\)" } | ForEach-Object { ($_ -split "\s+")[1] }) | Select-Object -First 1 } } } finally { Pop-Location } } } return $result } catch { Write-Warning "Error testing git repository: $($_.Exception.Message)" return @{ IsGitRepo = $false CurrentBranch = $null HasRemote = $false RemoteUrl = $null } } } function Initialize-GitWorkflow { <# .SYNOPSIS Handles the initial git workflow setup based on path analysis .PARAMETER ProjectPath Path to the project directory .PARAMETER ProjectName Name of the project .PARAMETER what-if Shows what would be done without actually performing the actions .OUTPUTS Boolean indicating success #> param( [string]$ProjectPath, [string]$ProjectName, [switch]${what-if} ) try { Write-Host "" Write-Host "🔍 Git Repository Analysis" -ForegroundColor Cyan # For git detection, we need to check the parent directory where the project will be created # Extract the parent directory from the full project path, handling WSL and Windows paths if ($ProjectPath -match '^(\/home\/|\/mnt\/)' ) { # WSL-style path: use string manipulation to preserve leading slash $parentPath = ($ProjectPath -replace '/+$','') -replace '/[^/]+$','' if ([string]::IsNullOrWhiteSpace($parentPath)) { $parentPath = '/' } } else { # Windows path: use Split-Path $parentPath = Split-Path -Parent $ProjectPath } Write-Info "Checking git repository at parent path: $parentPath" # Test if parent path is git-controlled # No path conversion - use the exact path format provided by user $gitInfo = Test-GitRepository -Path $parentPath $useWSLForGit = $effectiveRequiresWSL -or $Platform -eq "WSL" -or $ProjectPath -match '^/' if ($gitInfo.IsGitRepo) { Write-Success "Project path is already git-controlled" Write-Info "Current branch: $($gitInfo.CurrentBranch)" if ($gitInfo.HasRemote) { Write-Info "Remote repository: $($gitInfo.RemoteUrl)" } # Handle git-controlled path Write-Host "" Write-Host "🌿 Branch Selection" -ForegroundColor Yellow # Generate a branch name based on the project name $isWSLPath = $ProjectPath.StartsWith('/') -or $ProjectPath.Contains('/home/') if ($isWSLPath) { # Get WSL username $username = & wsl -- whoami 2>&1 if (-not $username) { $username = "user" } $username = $username.Trim() } else { # Get Windows username $username = if ($env:USERNAME) { $env:USERNAME } else { "user" } } $suggestedBranch = "$username/$ProjectName" # Prompt user for branch name with project-based default $localBranch = Read-UserPrompt -Prompt "Local working branch" -DefaultValue $suggestedBranch if ([string]::IsNullOrWhiteSpace($localBranch)) { $localBranch = $suggestedBranch } # CRITICAL: Create and switch to project-specific branch first - only continue if successful Write-Progress "Creating project-specific branch '$localBranch'..." $branchCreationSuccess = $false $gitRepoPath = $parentPath try { if (${what-if}) { Write-Host "🔍 (what-if) Would create/switch to branch '$localBranch'" -ForegroundColor Yellow $branchCreationSuccess = $true } else { # First, check for uncommitted changes $hasUncommittedChanges = $false if ($useWSLForGit -or $effectiveRequiresWSL -or $Platform -eq "WSL" -or $ProjectPath -match '^/') { $statusResult = wsl -- bash -c "cd '$gitRepoPath' && git status --porcelain" 2>&1 $hasUncommittedChanges = ($LASTEXITCODE -eq 0 -and $statusResult -and $statusResult.Trim()) } else { Push-Location $gitRepoPath try { $statusResult = git status --porcelain 2>&1 $hasUncommittedChanges = ($LASTEXITCODE -eq 0 -and $statusResult -and $statusResult.Trim()) } finally { Pop-Location } } # Handle uncommitted changes before branch operations if ($hasUncommittedChanges) { Write-Warning "Found uncommitted changes in the repository" Write-Host "" Write-Host "📋 Uncommitted Changes Detected" -ForegroundColor Yellow Write-Host "The following files have uncommitted changes:" -ForegroundColor Gray $statusResult | ForEach-Object { if ($_ -and $_.Trim()) { Write-Host " $_" -ForegroundColor Gray } } Write-Host "" Write-Host "Options to handle uncommitted changes:" -ForegroundColor Cyan Write-Host " [s] Stash changes (recommended)" -ForegroundColor White Write-Host " [c] Commit changes to current branch" -ForegroundColor White Write-Host " [d] Discard changes (WARNING: permanent)" -ForegroundColor Yellow Write-Host " [a] Abort branch creation" -ForegroundColor Gray Write-Host "" $changeOption = Read-UserPrompt -Prompt "How to handle uncommitted changes?" -ValidValues @("s","c","d","a") if ($changeOption -eq "a") { Write-Info "Branch creation aborted by user" return $false } elseif ($changeOption -eq "s") { # Stash changes Write-Progress "Stashing uncommitted changes..." if ($useWSLForGit -or $effectiveRequiresWSL -or $Platform -eq "WSL" -or $ProjectPath -match '^/') { $stashResult = wsl -- bash -c "cd '$gitRepoPath' && git stash push -m 'strangeloop-bootstrap: auto-stash before branch switch'" 2>&1 if ($LASTEXITCODE -eq 0) { Write-Success "Changes stashed successfully" Write-Info "You can restore them later with: git stash pop" } else { Write-Error "Failed to stash changes: $($stashResult -join ' ')" return $false } } else { Push-Location $gitRepoPath try { $stashResult = git stash push -m "strangeloop-bootstrap: auto-stash before branch switch" 2>&1 if ($LASTEXITCODE -eq 0) { Write-Success "Changes stashed successfully" Write-Info "You can restore them later with: git stash pop" } else { Write-Error "Failed to stash changes: $($stashResult -join ' ')" return $false } } finally { Pop-Location } } } elseif ($changeOption -eq "c") { # Commit changes Write-Progress "Committing uncommitted changes..." $commitMessage = "strangeloop-bootstrap: auto-commit before branch switch" if ($useWSLForGit -or $effectiveRequiresWSL -or $Platform -eq "WSL" -or $ProjectPath -match '^/') { $addResult = wsl -- bash -c "cd '$gitRepoPath' && git add -A" 2>&1 if ($LASTEXITCODE -eq 0) { $commitResult = wsl -- bash -c "cd '$gitRepoPath' && git commit -m '$commitMessage'" 2>&1 if ($LASTEXITCODE -eq 0) { Write-Success "Changes committed successfully" } else { Write-Error "Failed to commit changes: $($commitResult -join ' ')" return $false } } else { Write-Error "Failed to stage changes: $($addResult -join ' ')" return $false } } else { Push-Location $gitRepoPath try { $addResult = git add -A 2>&1 if ($LASTEXITCODE -eq 0) { $commitResult = git commit -m $commitMessage 2>&1 if ($LASTEXITCODE -eq 0) { Write-Success "Changes committed successfully" } else { Write-Error "Failed to commit changes: $($commitResult -join ' ')" return $false } } else { Write-Error "Failed to stage changes: $($addResult -join ' ')" return $false } } finally { Pop-Location } } } elseif ($changeOption -eq "d") { # Discard changes Write-Host "" Write-Warning "⚠️ DANGER: This will permanently discard all uncommitted changes!" $confirmDiscard = Read-UserPrompt -Prompt "Are you sure you want to discard all changes?" -ValidValues @("y","n") if (Test-YesResponse $confirmDiscard) { Write-Progress "Discarding uncommitted changes..." if ($useWSLForGit -or $effectiveRequiresWSL -or $Platform -eq "WSL" -or $ProjectPath -match '^/') { $resetResult = wsl -- bash -c "cd '$gitRepoPath' && git reset --hard HEAD && git clean -fd" 2>&1 if ($LASTEXITCODE -eq 0) { Write-Success "Changes discarded successfully" } else { Write-Error "Failed to discard changes: $($resetResult -join ' ')" return $false } } else { Push-Location $gitRepoPath try { $resetResult = git reset --hard HEAD 2>&1 $cleanResult = git clean -fd 2>&1 if ($LASTEXITCODE -eq 0) { Write-Success "Changes discarded successfully" } else { Write-Error "Failed to discard changes" return $false } } finally { Pop-Location } } } else { Write-Info "Discard operation cancelled" return $false } } } # Now proceed with branch creation/switch (working directory should be clean) if ($useWSLForGit -or $effectiveRequiresWSL -or $Platform -eq "WSL" -or $ProjectPath -match '^/') { # Check if branch already exists $branchExists = wsl -- bash -c "cd '$gitRepoPath' && git branch --list '$localBranch'" 2>&1 if ($branchExists -and $branchExists.Trim()) { # Branch exists, switch to it $checkoutResult = wsl -- bash -c "cd '$gitRepoPath' && git checkout '$localBranch'" 2>&1 $branchCreationSuccess = ($LASTEXITCODE -eq 0) if ($branchCreationSuccess) { Write-Success "Switched to existing branch '$localBranch'" } else { Write-Error "Failed to switch to existing branch '$localBranch': $($checkoutResult -join ' ')" } } else { # Branch doesn't exist, create it $createResult = wsl -- bash -c "cd '$gitRepoPath' && git checkout -b '$localBranch'" 2>&1 $branchCreationSuccess = ($LASTEXITCODE -eq 0) if ($branchCreationSuccess) { Write-Success "Created and switched to new branch '$localBranch'" } else { Write-Error "Failed to create branch '$localBranch': $($createResult -join ' ')" } } } else { Push-Location $gitRepoPath try { # Check if branch already exists $branchExists = git branch --list $localBranch 2>&1 if ($branchExists -and $branchExists.Trim()) { # Branch exists, switch to it $checkoutResult = git checkout $localBranch 2>&1 $branchCreationSuccess = ($LASTEXITCODE -eq 0) if ($branchCreationSuccess) { Write-Success "Switched to existing branch '$localBranch'" } else { Write-Error "Failed to switch to existing branch '$localBranch': $($checkoutResult -join ' ')" } } else { # Branch doesn't exist, create it $createResult = git checkout -b $localBranch 2>&1 $branchCreationSuccess = ($LASTEXITCODE -eq 0) if ($branchCreationSuccess) { Write-Success "Created and switched to new branch '$localBranch'" } else { Write-Error "Failed to create branch '$localBranch': $($createResult -join ' ')" } } } finally { Pop-Location } } } } catch { Write-Error "Error during branch creation/switch: $($_.Exception.Message)" $branchCreationSuccess = $false } # ONLY continue if branch creation/switch was successful if (-not $branchCreationSuccess) { Write-Error "Failed to create or switch to project-specific branch. Cannot continue with git workflow." return $false } # Now that we're on the correct branch, sync with remote if available if ($gitInfo.HasRemote) { if (${what-if}) { Write-Host "🔍 (what-if) Would sync latest changes from main branch..." -ForegroundColor Yellow Write-Host "🔍 (what-if) Would merge/pull changes from origin/main" -ForegroundColor Yellow } else { Write-Progress "Syncing latest changes from main branch..." try { if ($useWSLForGit -or $effectiveRequiresWSL -or $Platform -eq "WSL" -or $ProjectPath -match '^/') { # Fetch latest changes and merge/pull from main wsl -- bash -c "cd '$gitRepoPath' && git fetch origin main >/dev/null 2>&1" $syncResult = wsl -- bash -c "cd '$gitRepoPath' && (git merge origin/main >/dev/null 2>&1 || git pull origin main >/dev/null 2>&1)" if ($LASTEXITCODE -eq 0) { Write-Success "Branch '$localBranch' synced with latest changes from main" } else { Write-Warning "Could not sync with main branch, but continuing on branch '$localBranch'" } } else { Push-Location $gitRepoPath try { $null = git fetch origin main 2>&1 $null = git merge origin/main 2>&1 if ($LASTEXITCODE -ne 0) { $null = git pull origin main 2>&1 } if ($LASTEXITCODE -eq 0) { Write-Success "Branch '$localBranch' synced with latest changes from main" } else { Write-Warning "Could not sync with main branch, but continuing on branch '$localBranch'" } } finally { Pop-Location } } } catch { Write-Warning "Could not sync with remote: $($_.Exception.Message)" Write-Info "Continuing on branch '$localBranch' without sync" } } } # Set git context $Global:GitContext.IsGitControlled = $true $Global:GitContext.LocalBranch = $localBranch $Global:GitContext.ProjectPath = $ProjectPath if ($gitInfo.HasRemote) { $Global:GitContext.RemoteUrl = $gitInfo.RemoteUrl } } else { # Handle non-git-controlled path Write-Warning "Project path is not git-controlled" Write-Host "" Write-Host "⚠️ Source Control Setup" -ForegroundColor Yellow Write-Host "This project will not be version controlled without git setup." -ForegroundColor Gray Write-Host "" $includeSourceControl = Read-UserPrompt -Prompt "Include project in source control?" -ValidValues @("y","n") if (Test-YesResponse $includeSourceControl) { # User wants source control $defaultRemoteUrl = "https://msasg.visualstudio.com/DefaultCollection/Bing_Ads/_git/AdsSnR_Containers" Write-Host "" $remoteUrl = Read-UserPrompt -Prompt "Remote repository URL" -DefaultValue $defaultRemoteUrl if ([string]::IsNullOrWhiteSpace($remoteUrl)) { $remoteUrl = $defaultRemoteUrl } # Set git context for initialization $Global:GitContext.WillInitializeGit = $true $Global:GitContext.RemoteUrl = $remoteUrl $Global:GitContext.ProjectPath = $ProjectPath Write-Success "Git repository will be initialized with remote: $remoteUrl" } else { # User doesn't want source control Write-Info "⏭️ Skipping source control setup" $Global:GitContext.SkipGit = $true $Global:PhaseSkips.Git = $true $Global:PhaseSkips.Pipeline = $true Write-Warning "Git and Pipeline phases will be skipped" } } return $true } catch { Write-Error "Git workflow initialization failed: $($_.Exception.Message)" return $false } } function Show-LoopSelection { <# .SYNOPSIS Shows available loops and allows user selection .DESCRIPTION Display available strangeloop templates and prompts user to select one .PARAMETER AvailableLoops Array of available loop objects .OUTPUTS Selected loop object or null #> param( [Parameter(Mandatory)] [array]$AvailableLoops ) if ($false) { return @{ Success = $false ProjectPath = $null ProjectName = $null LoopName = $null } } try { Write-Host "" Write-Step "Available strangeloop Templates" Write-Host "" # Group loops by platform $groupedLoops = $AvailableLoops | Group-Object { if ($_.platform) { $_.platform } else { 'Windows' # Default platform } } $index = 1 $loopIndex = @{} foreach ($group in $groupedLoops) { Write-Host " $($group.Name):" -ForegroundColor Cyan foreach ($loop in $group.Group) { $displayName = $loop.name $description = if ($loop.description) { " - $($loop.description)" } else { "" } Write-Host " [$index] $displayName$description" -ForegroundColor White $loopIndex[$index] = $loop $index++ } Write-Host "" } do { $selection = Read-UserPrompt -Prompt "Select a template (1-$($AvailableLoops.Count)) or 'q' to quit" if ($selection -eq 'q' -or $selection -eq 'quit') { return @{ Success = $false ProjectPath = $null ProjectName = $null LoopName = $null } } $selectedIndex = $null if ([int]::TryParse($selection, [ref]$selectedIndex) -and $loopIndex.ContainsKey($selectedIndex)) { $selectedLoop = $loopIndex[$selectedIndex] Write-Host "" Write-Info "Selected: $($selectedLoop.name)" if ($selectedLoop.description) { Write-Host " Description: $($selectedLoop.description)" -ForegroundColor Gray } return @{ Success = $true ProjectPath = $null ProjectName = $null LoopName = $selectedLoop.name SelectedLoop = $selectedLoop } } else { Write-Warning "Invalid selection. Please enter a number between 1 and $($AvailableLoops.Count) or 'q' to quit." } } while ($true) } catch { Write-Warning "Error in loop selection: $($_.Exception.Message)" return @{ Success = $false ProjectPath = $null ProjectName = $null LoopName = $null } } } function Get-ProjectDetails { <# .SYNOPSIS Collects project details from user input .DESCRIPTION Prompts user for project name and parent path, with validation .PARAMETER DefaultProjectName Default project name to suggest .PARAMETER DefaultProjectPath Default parent directory path to suggest (project folder will be created within this) .PARAMETER LoopName Name of the loop template being used .PARAMETER BaseDirectory Base directory for project path suggestions .PARAMETER RequiresWSL Indicates if this is a WSL project, affects path suggestions .PARAMETER SkipProjectName Skip prompting for project name (already provided) .PARAMETER SkipProjectPath Skip prompting for project parent path (already provided) .OUTPUTS Hashtable with ProjectName and ProjectPath (full path to project directory) #> param( [string]$DefaultProjectName, [string]$DefaultProjectPath, [string]${loop-name}, [string]${base-directory}, [switch]${requires-wsl}, [switch]$SkipProjectName, [switch]$SkipProjectPath ) if ($false) { # Generate defaults if not provided $projectName = if ($DefaultProjectName) { $DefaultProjectName } else { "test-strangeloop-app" } $projectPath = if ($DefaultProjectPath) { $DefaultProjectPath } else { # Generate default path based on loop name and platform if (${loop-name}) { # Define Windows-only loops (everything else defaults to WSL) $isWindowsLoop = ${loop-name} -in @('asp-dotnet-framework-api', 'ads-snr-basic', 'flask-windows') $isWSLLoop = -not $isWindowsLoop if ($isWindowsLoop) { "Q:\src\AdsSnR_Containers\services\$projectName" } elseif (${requires-wsl} -or $isWSLLoop) { # Use WSL Linux path for WSL/Linux loops or when explicitly requiring WSL try { $wslUser = & wsl -- whoami 2>&1 if ($wslUser) { "/home/$($wslUser.Trim())/AdsSnR_Containers/services/$projectName" } else { "/home/\$USER/AdsSnR_Containers/services/$projectName" } } catch { "/home/\$USER/AdsSnR_Containers/services/$projectName" } } else { $baseDir = if (${base-directory}) { ${base-directory} } else { Get-Location } Join-Path $baseDir "AdsSnR_Containers\services\$projectName" } } else { if (${requires-wsl}) { "/home/\$USER/AdsSnR_Containers/services/$projectName" } else { "Q:\src\AdsSnR_Containers\services\$projectName" } } } return @{ ProjectName = $projectName ProjectPath = $projectPath } } try { Write-Host "" Write-Step "Project Configuration" # Get project name if ($SkipProjectName) { # Use the provided default project name $projectName = $DefaultProjectName Write-Info "Using provided project name: $projectName" } else { do { if ($DefaultProjectName) { $inputName = Read-UserPrompt -Prompt "Project name" -DefaultValue $DefaultProjectName } else { $inputName = Read-UserPrompt -Prompt "Project name" } if ([string]::IsNullOrWhiteSpace($inputName) -and $DefaultProjectName) { $projectName = $DefaultProjectName } elseif ([string]::IsNullOrWhiteSpace($inputName)) { Write-Warning "Project name cannot be empty" continue } else { $projectName = $inputName.Trim() } # Validate project name if ($projectName -match '[<>:"/\\|?*]') { Write-Warning "Project name contains invalid characters" continue } break } while ($true) } # Get project path if ($SkipProjectPath) { # Use the provided default project path as parent directory $projectParentPath = $DefaultProjectPath Write-Info "Using provided project parent directory: $projectParentPath" # Construct full project path by combining parent path with project name if ($projectParentPath.StartsWith("/") -or $projectParentPath.Contains("/home/")) { # WSL path - use forward slash $projectPath = "$projectParentPath/$projectName" } else { # Windows path - use Join-Path for proper path handling $projectPath = Join-Path $projectParentPath $projectName } Write-Info "Full project path: $projectPath" } else { do { # Always recalculate suggested path with current project name # Define Windows-only loops (everything else defaults to WSL) $isWindowsLoop = ${loop-name} -in @('asp-dotnet-framework-api', 'ads-snr-basic', 'flask-windows') # All other loops are WSL loops (including all python, csharp-mcp-server, csharp-semantic-kernel-agent, dotnet-aspire, etc.) $isWSLLoop = -not $isWindowsLoop if ($isWindowsLoop) { # Use Q:\src\AdsSnR_Containers\services for Windows loops $suggestedPath = "Q:\src\AdsSnR_Containers\services" } elseif (${requires-wsl} -or $isWSLLoop) { # Use WSL Linux path for WSL/Linux loops try { $wslUser = & wsl -- whoami 2>&1 if ($wslUser) { $suggestedPath = "/home/$($wslUser.Trim())/AdsSnR_Containers/services" } else { $suggestedPath = "/home/\$USER/AdsSnR_Containers/services" } } catch { $suggestedPath = "/home/\$USER/AdsSnR_Containers/services" } } else { # Use relative path as fallback $baseDir = if (${base-directory}) { ${base-directory} } else { Get-Location } Write-Verbose "Using BaseDirectory: $baseDir" $suggestedPath = Join-Path $baseDir "AdsSnR_Containers\services" } $inputPath = Read-UserPrompt -Prompt "Project parent directory" -DefaultValue $suggestedPath if ([string]::IsNullOrWhiteSpace($inputPath)) { $projectParentPath = $suggestedPath } else { $projectParentPath = $inputPath.Trim() } # Expand environment variables $projectParentPath = [Environment]::ExpandEnvironmentVariables($projectParentPath) # Convert to absolute path (handle WSL paths differently) if ($projectParentPath.StartsWith("/") -or $projectParentPath.Contains("/home/")) { # This is a WSL/Linux path, don't try to convert it using Windows logic Write-Verbose "Using WSL/Linux parent path: $projectParentPath" } elseif (-not [System.IO.Path]::IsPathRooted($projectParentPath)) { $projectParentPath = Join-Path $PWD $projectParentPath } # Construct full project path by combining parent path with project name if ($projectParentPath.StartsWith("/") -or $projectParentPath.Contains("/home/")) { # WSL path - use forward slash $projectPath = "$projectParentPath/$projectName" } else { # Windows path - use Join-Path for proper path handling $projectPath = Join-Path $projectParentPath $projectName } # Check if path exists and is not empty (handle WSL paths) $pathExists = $false $pathHasItems = $false if ($projectPath.StartsWith("/") -or $projectPath.Contains("/home/")) { # WSL path - check using WSL commands try { $null = & wsl test -d "$projectPath" 2>&1 $pathExists = ($LASTEXITCODE -eq 0) if ($pathExists) { $items = & wsl ls -la "$projectPath" 2>&1 $pathHasItems = ($items -and ($items | Where-Object { $_ -and $_ -notmatch "^total" -and $_ -notmatch "^\.\s" -and $_ -notmatch "^\.\.\s" }).Count -gt 0) } } catch { Write-Verbose "Could not check WSL path existence: $($_.Exception.Message)" } } else { # Windows path - use normal Test-Path $pathExists = Test-Path $projectPath if ($pathExists) { $items = Get-ChildItem $projectPath -ErrorAction SilentlyContinue $pathHasItems = ($items -and $items.Count -gt 0) } } if ($pathExists -and $pathHasItems) { # Check if it's already a strangeloop project $isstrangeloopProject = $false if ($projectPath.StartsWith("/") -or $projectPath.Contains("/home/")) { # WSL path - check using WSL commands try { $null = & wsl test -d "$projectPath/strangeloop" 2>&1 $strangeloopExists = ($LASTEXITCODE -eq 0) $null = & wsl test -f "$projectPath/strangeloop/settings.yaml" 2>&1 $settingsExists = ($LASTEXITCODE -eq 0) $isstrangeloopProject = $strangeloopExists -and $settingsExists } catch { Write-Verbose "Could not check WSL strangeloop project structure: $($_.Exception.Message)" } } else { # Windows path - use normal Test-Path $strangeLoopDir = Join-Path $projectPath "strangeloop" $settingsPath = Join-Path $strangeLoopDir "settings.yaml" $isstrangeloopProject = (Test-Path $strangeLoopDir) -and (Test-Path $settingsPath) } if ($isstrangeloopProject) { Write-Info "strangeloop project already exists at: $projectPath" Write-Info "Using existing project" break # Use existing strangeloop project } else { # Directory exists but not a strangeloop project Write-Warning "Directory '$projectPath' already exists and is not empty" $confirm = Read-UserPrompt -Prompt "Continue anyway?" -ValidValues @("y","n") if (-not (Test-YesResponse $confirm)) { continue } } } break } while ($true) } return @{ Success = $true ProjectName = $projectName ProjectPath = $projectPath } } catch { Write-Warning "Error collecting project details: $($_.Exception.Message)" return @{ Success = $false ProjectPath = $null ProjectName = $null LoopName = ${loop-name} } } } function New-strangeloopProject { <# .SYNOPSIS Creates a new strangeloop project from a template .DESCRIPTION Initializes a new project using the strangeloop CLI with the specified template .PARAMETER ProjectName Name of the project to create .PARAMETER LoopName Name of the loop template to use .PARAMETER ProjectPath Path where the project should be created .OUTPUTS Boolean indicating success #> param( [Parameter(Mandatory)] [string]${project-name}, [Parameter(Mandatory)] [string]${loop-name}, [Parameter(Mandatory)] [string]${project-path}, [switch]${what-if} ) try { Write-Step "Creating strangeloop Project" Write-Info "Project: ${project-name}" Write-Info "Template: ${loop-name}" Write-Info "Path: ${project-path}" # Ensure parent directory exists $parentDir = Split-Path ${project-path} -Parent if (-not (Test-Path $parentDir)) { Write-Info "Creating parent directory: $parentDir" New-Item -Path $parentDir -ItemType Directory -Force | Out-Null } # Check if project directory already exists (strangeloop will create it with --name parameter) if (Test-Path ${project-path}) { # Check if it's already a strangeloop project $strangeLoopDir = Join-Path ${project-path} "strangeloop" $settingsPath = Join-Path $strangeLoopDir "settings.yaml" if ((Test-Path $strangeLoopDir) -and (Test-Path $settingsPath)) { # It's an existing strangeloop project Write-Warning "strangeloop project already exists at: ${project-path}" $response = Read-UserPrompt -Prompt "Overwrite existing strangeloop project?" -ValidValues @("y","n") if (-not (Test-YesResponse $response)) { Write-Info "Using existing strangeloop project without overwriting" # Return success but indicate it's an existing project # Don't return early - let the workflow continue for git and pipeline setup Write-Success "Project will use existing strangeloop configuration" return @{ Success = $true ProjectPath = ${project-path} ProjectName = ${project-name} LoopName = ${loop-name} ExistingProject = $true SkipProjectCreation = $true } } # User confirmed to overwrite Write-Info "Removing existing strangeloop project" # Reset any Git changes to avoid "git checkout is not clean" errors if ($true) { # Always try to reset git changes Write-Progress "Resetting any Git changes in existing project..." try { & git -C ${project-path} reset --hard HEAD if ($LASTEXITCODE -eq 0) { Write-Info "Git changes reset successfully" } else { Write-Info "No Git repository found or reset not needed (exit code: $LASTEXITCODE)" } } catch { Write-Info "Git reset skipped (no Git repository or other issue)" Write-Host "Exception: $($_.Exception.Message)" -ForegroundColor Yellow } } Remove-Item ${project-path} -Recurse -Force -ErrorAction SilentlyContinue } else { # Directory exists but is not a strangeloop project, check if it has content $dirContent = Get-ChildItem ${project-path} -ErrorAction SilentlyContinue $hasContent = ($dirContent -and $dirContent.Count -gt 0) if ($hasContent) { # Directory has content, ask user if they want to overwrite Write-Warning "Directory already exists and contains files: ${project-path}" $response = Read-UserPrompt -Prompt "Overwrite existing directory?" -ValidValues @("y","n") if (-not (Test-YesResponse $response)) { Write-Warning "Project creation cancelled by user" return @{ Success = $false ProjectPath = $null ProjectName = $null LoopName = $null } } # User confirmed to overwrite Write-Info "Removing existing directory" # Reset any Git changes to avoid "git checkout is not clean" errors if ($true) { # Always try to reset git changes Write-Progress "Resetting any Git changes in existing directory..." try { & git -C ${project-path} reset --hard HEAD if ($LASTEXITCODE -eq 0) { Write-Info "Git changes reset successfully" } else { Write-Info "No Git repository found or reset not needed (exit code: $LASTEXITCODE)" } } catch { Write-Info "Git reset skipped (no Git repository or other issue)" } } Remove-Item ${project-path} -Recurse -Force -ErrorAction SilentlyContinue } else { # Directory is empty, remove it so strangeloop can create it fresh Write-Info "Directory exists but is empty, removing for fresh creation" Remove-Item ${project-path} -Recurse -Force -ErrorAction SilentlyContinue } } } # Create project using strangeloop CLI Write-Progress "Creating project from template..." # Ensure parent directory exists (strangeloop will create the project directory with the --name parameter) $parentDir = Split-Path ${project-path} -Parent if (-not (Test-Path $parentDir)) { Write-Info "Creating parent directory: $parentDir" New-Item -Path $parentDir -ItemType Directory -Force | Out-Null } # Execute strangeloop CLI from the parent directory try { # Clear cache before initialization if (${what-if}) { Write-Host "🔍 (what-if) Would clear strangeloop cache" -ForegroundColor Yellow Write-Host "🔍 (what-if) Would run: strangeloop library-registry clear-cache" -ForegroundColor Yellow } else { Write-Info "Clearing strangeloop cache..." $clearProcess = Start-Process -FilePath "strangeloop" -ArgumentList @("library-registry", "clear-cache") -Wait -PassThru -NoNewWindow if ($clearProcess.ExitCode -ne 0) { Write-Warning "Failed to clear strangeloop cache, but continuing with initialization..." } } # Build CLI command - use init with --name parameter (run from parent directory) $cliArgs = @( "--force", "init", "--loop", ${loop-name}, "--name", ${project-name} ) if (${what-if}) { Write-Host "🔍 (what-if) Would create strangeloop project" -ForegroundColor Yellow Write-Host "🔍 (what-if) Would run: strangeloop $($cliArgs -join ' ')" -ForegroundColor Yellow Write-Host "🔍 (what-if) Working directory: $parentDir" -ForegroundColor Yellow Write-Success "(what-if) Project would be created with name: ${project-name}" } else { # Use Start-Process to run with real-time console output from parent directory $process = Start-Process -FilePath "strangeloop" -ArgumentList $cliArgs -WorkingDirectory $parentDir -Wait -PassThru -NoNewWindow $exitCode = $process.ExitCode if ($exitCode -ne 0) { Write-Warning "strangeloop project creation failed with exit code: $exitCode" return @{ Success = $false ProjectPath = $null ProjectName = $null LoopName = $null } } Write-Success "Project created with name: ${project-name}" } } catch { Write-Warning "Error executing strangeloop CLI: $($_.Exception.Message)" return @{ Success = $false ProjectPath = $null ProjectName = $null LoopName = $null } } # Verify project was created if (-not (Test-Path ${project-path})) { Write-Warning "Project directory was not created: ${project-path}" return @{ Success = $false ProjectPath = $null ProjectName = $null LoopName = $null } } Write-Success "Project created successfully at: ${project-path}" return @{ Success = $true ProjectPath = ${project-path} ProjectName = ${project-name} LoopName = ${loop-name} ExistingProject = $false } } catch { Write-Warning "Error creating strangeloop project: $($_.Exception.Message)" return @{ Success = $false ProjectPath = $null ProjectName = $null LoopName = $null } } } function New-strangeloopProjectWSL { <# .SYNOPSIS Creates a new strangeloop project in WSL environment .DESCRIPTION Initializes a new project using the strangeloop CLI in WSL with the specified template .PARAMETER ProjectName Name of the project to create .PARAMETER LoopName Name of the loop template to use .PARAMETER ProjectPath Path where the project should be created (WSL path) .OUTPUTS Boolean indicating success #> param( [Parameter(Mandatory)] [string]${project-name}, [Parameter(Mandatory)] [string]${loop-name}, [Parameter(Mandatory)] [string]${project-path}, [switch]${what-if} ) try { Write-Step "Creating strangeloop Project in WSL" Write-Info "Project: ${project-name}" Write-Info "Template: ${loop-name}" Write-Info "WSL Path: ${project-path}" # Check if WSL is available if (-not (Get-Command wsl -ErrorAction SilentlyContinue)) { Write-Warning "WSL is required but not available" return @{ Success = $false ProjectPath = $null ProjectName = $null LoopName = $null } } # Use WSL path format $wslProjectPath = if (${project-path}.StartsWith("/")) { ${project-path} } else { try { $wslUser = & wsl -- whoami 2>&1 if ($wslUser) { "/home/$($wslUser.Trim())/AdsSnR_Containers/services/${project-name}" } else { "/home/\$USER/AdsSnR_Containers/services/${project-name}" } } catch { "/home/\$USER/AdsSnR_Containers/services/${project-name}" } } # Check if project already exists in WSL (strangeloop will create the directory with --name parameter) $null = wsl -- test -d $wslProjectPath if ($LASTEXITCODE -eq 0) { # Directory exists, check if it has strangeloop project $null = wsl -- test -d "$wslProjectPath/strangeloop" if ($LASTEXITCODE -eq 0) { # It's a strangeloop project Write-Warning "strangeloop project already exists in WSL at $wslProjectPath" $response = Read-UserPrompt -Prompt "Do you want to overwrite it?" -ValidValues @("y","n") if (-not (Test-YesResponse $response)) { Write-Info "Using existing strangeloop project without overwriting" # Return success but indicate it's an existing project # Don't return early - let the workflow continue for git and pipeline setup Write-Success "Project will use existing strangeloop configuration" return @{ Success = $true ProjectPath = ${project-path} ProjectName = ${project-name} LoopName = ${loop-name} ExistingProject = $true SkipProjectCreation = $true } } Write-Info "Removing existing project..." # Reset any Git changes to avoid "git checkout is not clean" errors if ($true) { # Always try to reset git changes Write-Progress "Resetting any Git changes in existing project..." try { wsl -- bash -c "cd '$wslProjectPath' && git reset --hard HEAD" if ($LASTEXITCODE -eq 0) { Write-Info "Git changes reset successfully" } else { Write-Info "No Git repository found or reset not needed (exit code: $LASTEXITCODE)" } } catch { Write-Info "Git reset skipped (no Git repository or other issue)" } } $removeResult = wsl -- rm -rf $wslProjectPath if ($LASTEXITCODE -ne 0) { Write-Warning "Failed to remove existing project: $removeResult" return @{ Success = $false ProjectPath = $null ProjectName = $null LoopName = $null } } } else { # Directory exists but not a strangeloop project, check if it has content $dirContent = wsl -- ls -la "$wslProjectPath" 2>&1 $hasContent = $false if ($LASTEXITCODE -eq 0 -and $dirContent) { # Count actual files (excluding . and .. and total line) $fileLines = $dirContent | Where-Object { $_ -and $_ -notmatch '^\s*total\s+' -and $_ -notmatch '\s+\.$' -and $_ -notmatch '\s+\.\.$' } $fileCount = if ($fileLines) { if ($fileLines.Count) { $fileLines.Count } else { 1 } } else { 0 } $hasContent = ($fileCount -gt 0) Write-Verbose "Directory content check: Found $fileCount actual files/directories" } if ($hasContent) { # Directory has content, ask user if they want to overwrite Write-Warning "Directory already exists in WSL and contains files: $wslProjectPath" $response = Read-UserPrompt -Prompt "Do you want to overwrite this directory?" -ValidValues @("y","n") if (-not (Test-YesResponse $response)) { Write-Info "Project creation cancelled by user" return @{ Success = $false ProjectPath = $null ProjectName = $null LoopName = $null } } Write-Info "Removing existing directory..." # Reset any Git changes to avoid "git checkout is not clean" errors Write-Progress "Resetting any Git changes in existing directory..." try { Write-Host "Executing: git reset --hard HEAD in $wslProjectPath" -ForegroundColor Cyan wsl -- bash -c "cd '$wslProjectPath' && git reset --hard HEAD" if ($LASTEXITCODE -eq 0) { Write-Info "Git changes reset successfully" } else { Write-Info "No Git repository found or reset not needed" } } catch { Write-Info "Git reset skipped (no Git repository or other issue)" } $removeResult = wsl -- rm -rf $wslProjectPath if ($LASTEXITCODE -ne 0) { Write-Warning "Failed to remove existing directory: $removeResult" return @{ Success = $false ProjectPath = $null ProjectName = $null LoopName = $null } } } else { # Directory is empty, remove it so strangeloop can create it fresh Write-Info "Directory exists but is empty, removing for fresh creation" $removeResult = wsl -- rm -rf $wslProjectPath if ($LASTEXITCODE -ne 0) { Write-Warning "Failed to remove existing directory: $removeResult" return @{ Success = $false ProjectPath = $null ProjectName = $null LoopName = $null } } } } } # Ensure parent directory exists in WSL $parentDirPath = if ($wslProjectPath.Contains("/")) { # Unix path - handle manually to avoid Windows path semantics $lastSlashIndex = $wslProjectPath.LastIndexOf('/') if ($lastSlashIndex -gt 0) { $wslProjectPath.Substring(0, $lastSlashIndex) } else { "/" } } else { Split-Path $wslProjectPath -Parent } $mkdirResult = wsl -- mkdir -p $parentDirPath if ($LASTEXITCODE -ne 0) { Write-Warning "Failed to create parent directory in WSL: $mkdirResult" return @{ Success = $false ProjectPath = $null ProjectName = $null LoopName = $null } } Write-Progress "Initializing strangeloop project in WSL..." # Clear cache before initialization Write-Info "Clearing strangeloop cache in WSL..." $clearProcess = Start-Process -FilePath "wsl" -ArgumentList @("--", "strangeloop", "library-registry", "clear-cache") -Wait -PassThru -NoNewWindow if ($clearProcess.ExitCode -ne 0) { Write-Warning "Failed to clear strangeloop cache in WSL, but continuing with initialization..." } # Execute strangeloop CLI from parent directory with --name parameter $parentDirPath = if ($wslProjectPath.Contains("/")) { # Unix path - handle manually to avoid Windows path semantics $lastSlashIndex = $wslProjectPath.LastIndexOf('/') if ($lastSlashIndex -gt 0) { $wslProjectPath.Substring(0, $lastSlashIndex) } else { "/" } } else { Split-Path $wslProjectPath -Parent } # Execute all commands in a single WSL bash call from parent directory $combinedCommand = "cd `"$parentDirPath`" && strangeloop --force init --loop '${loop-name}' --name '${project-name}'" if (${what-if}) { Write-Host "🔍 (what-if) Would create strangeloop project in WSL" -ForegroundColor Yellow Write-Host "🔍 (what-if) Would execute in WSL: $combinedCommand" -ForegroundColor Yellow } else { Write-Info "Executing in WSL: $combinedCommand" # Use Start-Process to show real-time output but with proper command construction Write-Progress "Initializing strangeloop project (this may take a moment)..." try { # Execute the init command from parent directory with --name parameter $initCommand = "cd `"$parentDirPath`" && strangeloop --force init --loop '${loop-name}' --name '${project-name}'" # Use direct WSL execution instead of System.Diagnostics.Process for better compatibility $null = & wsl.exe -- bash -c $initCommand $exitCode = $LASTEXITCODE } catch { Write-Warning "Error during project initialization: $($_.Exception.Message)" $exitCode = 1 } } if ($exitCode -ne 0) { Write-Warning "strangeloop project creation failed in WSL with exit code: $exitCode" return @{ Success = $false ProjectPath = $null ProjectName = $null LoopName = $null } } Write-Success "Project created with name: ${project-name}" # Verify project was created $null = wsl -- test -d "$wslProjectPath/strangeloop" if ($LASTEXITCODE -ne 0) { Write-Warning "Project verification failed - strangeloop directory not found" return @{ Success = $false ProjectPath = $null ProjectName = $null LoopName = $null } } Write-Success "strangeloop project created successfully in WSL at: $wslProjectPath" return @{ Success = $true ProjectPath = ${project-path} ProjectName = ${project-name} LoopName = ${loop-name} ExistingProject = $false } } catch { Write-Warning "Error creating strangeloop project in WSL: $($_.Exception.Message)" return @{ Success = $false ProjectPath = $null ProjectName = $null LoopName = $null } } } function initialize-projectWorkspace { <# .SYNOPSIS Initializes the project workspace with development tools .DESCRIPTION Sets up the created project with additional development tools and configurations .PARAMETER ProjectPath Path to the project directory .OUTPUTS Boolean indicating success #> param( [Parameter(Mandatory)] [string]${project-path} ) try { Write-Info "Initializing project workspace..." # Determine if this is a WSL path $isWSLPath = ${project-path}.StartsWith("/") -or ${project-path}.Contains("/home/") if ($isWSLPath) { # Handle WSL project initialization Write-Info "Initializing WSL project workspace..." # Check for Python projects and install dependencies $pyprojectExists = wsl -- test -f "${project-path}/pyproject.toml" $requirementsExists = wsl -- test -f "${project-path}/requirements.txt" if ($LASTEXITCODE -eq 0 -or $pyprojectExists -eq 0) { Write-Progress "Installing Python dependencies in WSL..." if ($pyprojectExists -eq 0) { Write-Info "Found pyproject.toml - installing with Poetry..." $null = wsl -- bash -c "cd '${project-path}' && poetry install" if ($LASTEXITCODE -eq 0) { Write-Success "Poetry dependencies installed" } else { Write-Info "Poetry install completed with warnings" } } elseif ($requirementsExists -eq 0) { Write-Info "Found requirements.txt - installing with pip..." $null = wsl -- bash -c "cd '${project-path}' && pip install -r requirements.txt" if ($LASTEXITCODE -eq 0) { Write-Success "Python dependencies installed" } else { Write-Info "Pip install completed with warnings" } } } # Check for .NET projects and restore packages $csprojExists = wsl -- bash -c "find '${project-path}' -name '*.csproj' -type f | head -1" if ($LASTEXITCODE -eq 0 -and $csprojExists) { Write-Progress "Restoring .NET packages in WSL..." $null = wsl -- bash -c "cd '${project-path}' && dotnet restore" if ($LASTEXITCODE -eq 0) { Write-Success ".NET packages restored" } else { Write-Info "Dotnet restore completed with warnings" } } } else { # Handle Windows project initialization Write-Info "Initializing Windows project workspace..." # Check for Python projects and install dependencies $pyprojectPath = Join-Path ${project-path} "pyproject.toml" $requirementsPath = Join-Path ${project-path} "requirements.txt" if ((Test-Path $pyprojectPath) -or (Test-Path $requirementsPath)) { Write-Progress "Installing Python dependencies..." if (Test-Path $pyprojectPath) { $poetryProcess = New-Object System.Diagnostics.Process $poetryProcess.StartInfo.FileName = "poetry" $poetryProcess.StartInfo.Arguments = "install" $poetryProcess.StartInfo.WorkingDirectory = ${project-path} $poetryProcess.StartInfo.UseShellExecute = $false $poetryProcess.StartInfo.RedirectStandardOutput = $true $poetryProcess.StartInfo.RedirectStandardError = $true $poetryProcess.Start() | Out-Null $poetryProcess.WaitForExit() if ($poetryProcess.ExitCode -eq 0) { Write-Success "Poetry dependencies installed" } } elseif (Test-Path $requirementsPath) { $pipProcess = New-Object System.Diagnostics.Process $pipProcess.StartInfo.FileName = "pip" $pipProcess.StartInfo.Arguments = "install -r requirements.txt" $pipProcess.StartInfo.WorkingDirectory = ${project-path} $pipProcess.StartInfo.UseShellExecute = $false $pipProcess.StartInfo.RedirectStandardOutput = $true $pipProcess.StartInfo.RedirectStandardError = $true $pipProcess.Start() | Out-Null $pipProcess.WaitForExit() if ($pipProcess.ExitCode -eq 0) { Write-Success "Python dependencies installed" } } } # Check for .NET projects and restore packages $csprojFiles = Get-ChildItem -Path ${project-path} -Filter "*.csproj" -Recurse -ErrorAction SilentlyContinue if ($csprojFiles) { Write-Progress "Restoring .NET packages..." $dotnetProcess = New-Object System.Diagnostics.Process $dotnetProcess.StartInfo.FileName = "dotnet" $dotnetProcess.StartInfo.Arguments = "restore" $dotnetProcess.StartInfo.WorkingDirectory = ${project-path} $dotnetProcess.StartInfo.UseShellExecute = $false $dotnetProcess.StartInfo.RedirectStandardOutput = $true $dotnetProcess.StartInfo.RedirectStandardError = $true $dotnetProcess.Start() | Out-Null $dotnetProcess.WaitForExit() if ($dotnetProcess.ExitCode -eq 0) { Write-Success ".NET packages restored" } } } return @{ Success = $true ProjectPath = ${project-path} ProjectName = $null LoopName = $null } } catch { Write-Warning "Error initializing project workspace: $($_.Exception.Message)" return @{ Success = $false ProjectPath = $null ProjectName = $null LoopName = $null } } } function Start-ProjectInitialization { <# .SYNOPSIS Main function to start the project initialization process .DESCRIPTION Orchestrates the complete project creation workflow including loop selection and project setup .PARAMETER ProjectName Name of the project (optional, will prompt if not provided) .PARAMETER LoopName Name of the loop template (optional, will show selection if not provided) .PARAMETER ProjectPath Parent directory path where the project folder will be created (optional, will prompt if not provided) .PARAMETER Platform Target platform (Windows or Linux/WSL) .PARAMETER RequiresWSL Whether the project requires WSL environment .PARAMETER BaseDirectory Base directory for project path suggestions .PARAMETER test-only Only test the process without creating anything .PARAMETER what-if Shows what would be done without actually performing the actions .OUTPUTS Boolean indicating success #> param( [string]${project-name}, [string]${loop-name}, [string]${project-path}, [string]$Platform, [switch]${requires-wsl}, [string]${base-directory}, [switch]${test-only}, [switch]${what-if} ) try { Write-Step "Starting Project Initialization..." # Validate that we have a loop name (should come from Discovery phase or command line) if (-not ${loop-name}) { Write-Error "No loop template specified. This should have been selected during the Discovery phase." return @{ Success = $false ProjectPath = $null ProjectName = $null LoopName = $null GitContext = $Global:GitContext PhaseSkips = $Global:PhaseSkips } } # Get or validate project name $ProjectName = ${project-name} if (-not $ProjectName) { # Generate default name based on loop name $defaultName = "test-${loop-name}-app" $ProjectName = Read-UserPrompt -Prompt "Enter project name" -DefaultValue $defaultName if ([string]::IsNullOrWhiteSpace($ProjectName)) { $ProjectName = $defaultName } } # Validate project name if ($ProjectName -notmatch "^[a-zA-Z0-9-_]+$") { Write-Error "Invalid project name. Use only letters, numbers, hyphens, and underscores." return @{ Success = $false ProjectPath = $null ProjectName = $null LoopName = ${loop-name} GitContext = $Global:GitContext PhaseSkips = $Global:PhaseSkips } } Write-Info "Project Name: $ProjectName" Write-Info "Loop Template: ${loop-name}" # Determine if this is a WSL loop $isWindowsLoop = ${loop-name} -in @('asp-dotnet-framework-api', 'ads-snr-basic', 'flask-windows') $isWSLLoop = -not $isWindowsLoop $effectiveRequiresWSL = ${requires-wsl} -or $isWSLLoop Write-Info "Platform: $(if ($effectiveRequiresWSL) { 'WSL' } else { 'Windows' })" if (${test-only}) { Write-Success "Project initialization test completed successfully" Write-Info "Would initialize project '$ProjectName' using loop '${loop-name}'" return @{ Success = $true ProjectPath = "test-mode" ProjectName = $ProjectName LoopName = ${loop-name} TestMode = $true GitContext = $Global:GitContext PhaseSkips = $Global:PhaseSkips } } # Step 1: Get project parent directory with intelligent defaults $ProjectParentPath = ${project-path} if (-not $ProjectParentPath) { $ProjectParentPath = Get-ProjectPath -LoopName ${loop-name} -ProjectName $ProjectName -Platform $Platform -RequiresWSL:${requires-wsl} if (-not $ProjectParentPath) { Write-Error "Failed to determine project parent directory" return @{ Success = $false ProjectPath = $null ProjectName = $null LoopName = ${loop-name} GitContext = $Global:GitContext PhaseSkips = $Global:PhaseSkips } } } # Construct full project path by combining parent path with project name if ($ProjectParentPath.StartsWith("/") -or $ProjectParentPath.Contains("/home/")) { # WSL path - use forward slash $ProjectPath = "$ProjectParentPath/$ProjectName" } else { # Windows path - use Join-Path for proper path handling $ProjectPath = Join-Path $ProjectParentPath $ProjectName } # Step 2: Initialize git workflow (discovery and setup) - CRITICAL: Must succeed to continue $gitWorkflowResult = Initialize-GitWorkflow -ProjectPath $ProjectPath -ProjectName $ProjectName -what-if:${what-if} if (-not $gitWorkflowResult) { Write-Error "Git workflow initialization failed. Cannot continue with project creation to prevent corruption." Write-Error "Please resolve git issues and try again." return @{ Success = $false ProjectPath = $null ProjectName = $null LoopName = ${loop-name} GitContext = $Global:GitContext PhaseSkips = $Global:PhaseSkips Error = "Git workflow initialization failed" } } # Step 3: Create project directory if it doesn't exist $isWSLPath = $ProjectPath.StartsWith('/') -or $ProjectPath.Contains('/home/') if ($isWSLPath) { # WSL path handling $null = wsl -- test -d "$ProjectPath" $dirExistsResult = ($LASTEXITCODE -eq 0) if (-not $dirExistsResult) { if (${what-if}) { Write-Host "what if: Would create WSL project directory: $ProjectPath" -ForegroundColor Yellow } else { Write-Progress "Creating project directory in WSL..." $createResult = wsl -- mkdir -p "$ProjectPath" if ($LASTEXITCODE -ne 0) { Write-Error "Failed to create WSL project directory: $ProjectPath" return @{ Success = $false ProjectPath = $null ProjectName = $null LoopName = ${loop-name} GitContext = $Global:GitContext PhaseSkips = $Global:PhaseSkips } } } } } else { # Windows path handling if (-not (Test-Path $ProjectPath)) { if (${what-if}) { Write-Host "what if: Would create Windows project directory: $ProjectPath" -ForegroundColor Yellow } else { Write-Progress "Creating project directory..." try { New-Item -ItemType Directory -Path $ProjectPath -Force | Out-Null } catch { Write-Error "Failed to create project directory: $($_.Exception.Message)" return @{ Success = $false ProjectPath = $null ProjectName = $null LoopName = ${loop-name} GitContext = $Global:GitContext PhaseSkips = $Global:PhaseSkips } } } } } # Step 4: Initialize strangeloop project Write-Progress "Initializing strangeloop project..." # Use the proper project creation functions if ($isWSLPath) { $createResult = New-strangeloopProjectWSL -project-name $ProjectName -loop-name ${loop-name} -project-path $ProjectPath -what-if:${what-if} } else { $createResult = New-strangeloopProject -project-name $ProjectName -loop-name ${loop-name} -project-path $ProjectPath -what-if:${what-if} } # Handle case where createResult might be an array with mixed output if ($createResult -is [Object[]] -and $createResult.Count -gt 0) { # Find the hashtable in the array $hashtableResult = $createResult | Where-Object { $_ -is [hashtable] -and $_.ContainsKey('Success') } | Select-Object -First 1 if ($hashtableResult) { $createResult = $hashtableResult } } if (-not $createResult -or -not $createResult.Success) { Write-Error "strangeloop project creation failed" return @{ Success = $false ProjectPath = $null ProjectName = $null LoopName = ${loop-name} GitContext = $Global:GitContext PhaseSkips = $Global:PhaseSkips } } # Check if this was an existing project that user chose not to overwrite if ($createResult.ExistingProject -and $createResult.SkipProjectCreation) { Write-Success "Using existing strangeloop project" } else { Write-Success "strangeloop project created successfully" } # Update git context with final project path $Global:GitContext.ProjectPath = $ProjectPath Write-Success "Project initialization completed successfully" Write-Info "Project location: $ProjectPath" # Display git workflow summary if ($Global:GitContext.SkipGit) { Write-Info "📋 Git and Pipeline phases will be skipped (user choice)" } elseif ($Global:GitContext.IsGitControlled) { Write-Info "📋 Git repository detected - will commit changes in Git phase" Write-Info " Branch: $($Global:GitContext.LocalBranch)" if ($Global:GitContext.RemoteUrl) { Write-Info " Remote: $($Global:GitContext.RemoteUrl)" } } elseif ($Global:GitContext.WillInitializeGit) { Write-Info "📋 Git repository will be initialized in Git phase" Write-Info " Remote: $($Global:GitContext.RemoteUrl)" } return @{ Success = $true ProjectPath = $ProjectPath ProjectName = $ProjectName LoopName = ${loop-name} GitContext = $Global:GitContext PhaseSkips = $Global:PhaseSkips } } catch { Write-Error "Project initialization failed: $($_.Exception.Message)" return @{ Success = $false ProjectPath = $null ProjectName = $null LoopName = ${loop-name} GitContext = $Global:GitContext PhaseSkips = $Global:PhaseSkips } } } # Main execution if ($MyInvocation.InvocationName -ne '.') { $result = Start-ProjectInitialization -project-name ${project-name} -loop-name ${loop-name} -project-path ${project-path} -Platform $Platform -requires-wsl:${requires-wsl} -base-directory ${base-directory} -test-only:${test-only} -what-if:${what-if} if ($result -and $result.Success) { Write-Success "Project initialization completed successfully" } else { Write-Error "Project initialization failed" } # Return the result for Invoke-Phase to capture return $result } # Export functions for module usage if ($MyInvocation.MyCommand.ModuleName) { Export-ModuleMember -Function @( 'Show-LoopSelection', 'Get-ProjectDetails', 'New-strangeloopProject', 'New-strangeloopProjectWSL', 'initialize-projectWorkspace', 'Start-ProjectInitialization' ) } |