Set-StrictMode -Version Latest $ErrorActionPreference = 'Stop' $script:LoaderRoot = Split-Path -Parent $MyInvocation.MyCommand.Path $script:ConfigPath = Join-Path -Path $script:LoaderRoot -ChildPath 'config.json' $script:ConfigCache = $null function Get-ProjectStructureConfig { if ($null -ne $script:ConfigCache) { return $script:ConfigCache } if (Test-Path -LiteralPath $script:ConfigPath) { try { $raw = Get-Content -LiteralPath $script:ConfigPath -Raw -ErrorAction Stop if ($raw.Trim().Length -gt 0) { $script:ConfigCache = $raw | ConvertFrom-Json return $script:ConfigCache } } catch { Write-Warning "Failed to parse config.json: $($_.Exception.Message)" } } $script:ConfigCache = [pscustomobject]@{} return $script:ConfigCache } function Get-ConfigValue { param( [Parameter(Mandatory)] [string]$Name, $Default = $null ) $config = Get-ProjectStructureConfig if ($config.PSObject.Properties.Name -contains $Name) { $value = $config.$Name if ($null -ne $value -and ($value -isnot [string] -or $value.Trim().Length -gt 0)) { return $value } } return $Default } function Get-StructDirectory { $value = Get-ConfigValue -Name 'structDir' if ($null -eq $value -or [string]::IsNullOrWhiteSpace($value)) { return $script:LoaderRoot } if ([System.IO.Path]::IsPathRooted($value)) { $resolved = Resolve-Path -LiteralPath $value -ErrorAction SilentlyContinue if ($null -ne $resolved) { return $resolved.Path } return $value } $candidate = Join-Path -Path $script:LoaderRoot -ChildPath $value $resolvedCandidate = Resolve-Path -LiteralPath $candidate -ErrorAction SilentlyContinue if ($null -ne $resolvedCandidate) { return $resolvedCandidate.Path } return $candidate } function Get-ProjectsRoot { $value = Get-ConfigValue -Name 'projectsRoot' if ($null -eq $value -or [string]::IsNullOrWhiteSpace($value)) { $structDir = Get-StructDirectory $parent = Split-Path -Parent $structDir if ($null -eq $parent -or $parent.Length -eq 0 -or $parent -eq $structDir) { return $structDir } return $parent } if ([System.IO.Path]::IsPathRooted($value)) { $resolved = Resolve-Path -LiteralPath $value -ErrorAction SilentlyContinue if ($null -ne $resolved) { return $resolved.Path } return $value } $candidate = Join-Path -Path $script:LoaderRoot -ChildPath $value $resolvedCandidate = Resolve-Path -LiteralPath $candidate -ErrorAction SilentlyContinue if ($null -ne $resolvedCandidate) { return $resolvedCandidate.Path } return $candidate } function Get-ProjectPathFromUser { param( [string]$Prompt = "Paste the project path to deploy in" ) do { $inputPath = Read-Host -Prompt $Prompt if ([string]::IsNullOrWhiteSpace($inputPath)) { Write-Warning "Path cannot be empty. Please try again." continue } # Remove quotes if present $inputPath = $inputPath.Trim('"', "'") # Try to resolve the path try { if (Test-Path -LiteralPath $inputPath -PathType Container) { $resolved = Resolve-Path -LiteralPath $inputPath -ErrorAction Stop return $resolved.Path } else { Write-Warning "Path does not exist or is not a directory: $inputPath" $retry = Read-Host "Try again? (Y/N)" if ($retry -notmatch '^[Yy]') { return $null } } } catch { Write-Warning "Invalid path: $($_.Exception.Message)" $retry = Read-Host "Try again? (Y/N)" if ($retry -notmatch '^[Yy]') { return $null } } } while ($true) } function Use-IsoDailyFormat { $dailyFormat = Get-ConfigValue -Name 'dailyFormat' -Default $true # Handle backward compatibility with boolean values if ($dailyFormat -is [bool]) { return $dailyFormat } # Handle string values if ($dailyFormat -is [string]) { $formatStr = $dailyFormat.Trim() # ISO format contains YYYY-MM-DD pattern if ($formatStr -match 'YYYY-MM-DD' -or $formatStr -match '\d{4}-\d{2}-\d{2}') { return $true } # daily_YYMMDD or other formats are not ISO return $false } # Default to false (not ISO format) for unknown types return $false } function Use-7Zip { $zipper = Get-ConfigValue -Name 'zipper' -Default $true # Handle backward compatibility with boolean values if ($zipper -is [bool]) { return $zipper } # Handle string values if ($zipper -is [string]) { $zipperStr = $zipper.Trim().ToLower() return ($zipperStr -eq '7z') } # Default to false (use zip) for unknown types return $false } function Get-ZipCompressionLevel { $value = Get-ConfigValue -Name 'compression' -Default 9 if ($value -is [string]) { $parsed = 0 if ([int]::TryParse($value, [ref]$parsed)) { $value = $parsed } } if ($value -isnot [int]) { return 9 } return [Math]::Min(9, [Math]::Max(0, $value)) } # If script is run directly (not dot-sourced), prompt for project path and deploy # When dot-sourced, InvocationName is '.'; when run directly, it's the script path or name if ($MyInvocation.InvocationName -ne '.') { $projectPath = Get-ProjectPathFromUser if ($null -ne $projectPath) { # Deploy batch files and config to the project $structDir = Get-StructDirectory if (-not (Test-Path -LiteralPath $structDir -PathType Container)) { Write-Error "Configured structDir not found: $structDir" exit 1 } try { $resolvedProject = (Resolve-Path -LiteralPath $projectPath -ErrorAction Stop).Path } catch { Write-Error "Unable to resolve project directory: $($_.Exception.Message)" exit 1 } if (-not (Test-Path -LiteralPath $resolvedProject -PathType Container)) { Write-Error "Project path is not a directory: $resolvedProject" exit 1 } Write-Host "`nDeploying to: $resolvedProject" -ForegroundColor Cyan Write-Host "Struct directory: $structDir" -ForegroundColor Cyan $specs = @( @{ Name = 'UpdateSequences.bat'; Source = Join-Path -Path $structDir -ChildPath 'UpdateSequences.bat' }, @{ Name = 'UpdateAllSequences.bat'; Source = Join-Path -Path $structDir -ChildPath 'UpdateAllSequences.bat' }, @{ Name = 'ZipSeqArchv.bat'; Source = Join-Path -Path $structDir -ChildPath 'ZipSeqArchv.bat' }, @{ Name = 'UnzipSeqArchv.bat'; Source = Join-Path -Path $structDir -ChildPath 'UnzipSeqArchv.bat' }, @{ Name = 'NewDaily.bat'; Source = Join-Path -Path $structDir -ChildPath 'NewDaily.bat' }, @{ Name = '.gitattributes'; Source = Join-Path -Path $structDir -ChildPath 'components\gitattributes' }, @{ Name = '.gitignore'; Source = Join-Path -Path $structDir -ChildPath 'components\gitignore' } ) # Config files to deploy to projectroot\.config\ $configAssets = @( @{ Name = 'config.json'; Source = Join-Path -Path $structDir -ChildPath 'config.json' }, @{ Name = 'GetStructDir.ps1'; Source = Join-Path -Path $structDir -ChildPath 'GetStructDir.ps1' } ) foreach ($spec in $specs) { if (-not (Test-Path -LiteralPath $spec.Source -PathType Leaf)) { Write-Error "Source file not found: $($spec.Source)" exit 1 } } foreach ($asset in $configAssets) { if (-not (Test-Path -LiteralPath $asset.Source -PathType Leaf)) { Write-Error "Config asset not found: $($asset.Source)" exit 1 } } # Ensure .config directory exists in project root and is hidden $projectConfigDir = Join-Path -Path $resolvedProject -ChildPath '.config' if (-not (Test-Path -LiteralPath $projectConfigDir -PathType Container)) { New-Item -Path $projectConfigDir -ItemType Directory -Force | Out-Null Write-Host "Created .config directory: $projectConfigDir" -ForegroundColor Cyan } # Set hidden attribute on .config directory $folder = Get-Item -LiteralPath $projectConfigDir -Force $folder.Attributes = $folder.Attributes -bor [System.IO.FileAttributes]::Hidden $touchedDirs = @{} $summary = @() foreach ($spec in $specs) { Write-Host "`n=== Updating $($spec.Name) ===" -ForegroundColor Magenta $targets = Get-ChildItem -LiteralPath $resolvedProject -Recurse -Filter $spec.Name -File -Force -ErrorAction SilentlyContinue $targets = $targets | Where-Object { $_.FullName -ne $spec.Source } if (-not $targets) { Write-Host "No targets found." -ForegroundColor Yellow $summary += [pscustomobject]@{ Name = $spec.Name Updated = 0 Failed = 0 Skipped = 0 Total = 0 } continue } $updated = 0 $failed = 0 foreach ($target in $targets) { try { Copy-Item -Path $spec.Source -Destination $target.FullName -Force # Set hidden attribute for .gitattributes and .gitignore files if ($spec.Name -eq '.gitattributes' -or $spec.Name -eq '.gitignore') { $file = Get-Item -LiteralPath $target.FullName -Force $file.Attributes = $file.Attributes -bor [System.IO.FileAttributes]::Hidden } Write-Host "[OK] $($target.FullName)" -ForegroundColor Green $updated++ $targetDir = $target.Directory.FullName if (-not $touchedDirs.ContainsKey($targetDir)) { $touchedDirs[$targetDir] = $true } } catch { Write-Host "[FAIL] $($target.FullName)" -ForegroundColor Red Write-Host " $($_.Exception.Message)" -ForegroundColor DarkRed $failed++ } } $summary += [pscustomobject]@{ Name = $spec.Name Updated = $updated Failed = $failed Skipped = 0 Total = @($targets).Count } } Write-Host "`n=== Summary ===" -ForegroundColor Cyan foreach ($item in $summary) { Write-Host ("{0,-22} Updated: {1,3} Failed: {2,3} Total: {3,3}" -f $item.Name, $item.Updated, $item.Failed, $item.Total) } if (($summary | Measure-Object -Property Failed -Sum).Sum -gt 0) { Write-Host "Completed with errors." -ForegroundColor Yellow exit 1 } Write-Host "All batch files refreshed successfully." -ForegroundColor Green # Deploy .gitattributes and .gitignore to project root if they don't exist Write-Host "`n=== Deploying .gitattributes and .gitignore to project root ===" -ForegroundColor Magenta $gitFiles = @( @{ Name = '.gitattributes'; Source = Join-Path -Path $structDir -ChildPath 'components\gitattributes' }, @{ Name = '.gitignore'; Source = Join-Path -Path $structDir -ChildPath 'components\gitignore' } ) foreach ($gitFile in $gitFiles) { $targetPath = Join-Path -Path $resolvedProject -ChildPath $gitFile.Name try { Copy-Item -Path $gitFile.Source -Destination $targetPath -Force # Set hidden attribute on the file $file = Get-Item -LiteralPath $targetPath -Force $file.Attributes = $file.Attributes -bor [System.IO.FileAttributes]::Hidden Write-Host "[OK] $targetPath" -ForegroundColor Green } catch { Write-Host "[FAIL] $targetPath" -ForegroundColor Red Write-Host " $($_.Exception.Message)" -ForegroundColor DarkRed exit 1 } } # Deploy config files to projectroot\.config\ Write-Host "`n=== Deploying config files to .config\ ===" -ForegroundColor Magenta foreach ($asset in $configAssets) { $targetPath = Join-Path -Path $projectConfigDir -ChildPath $asset.Name try { Copy-Item -Path $asset.Source -Destination $targetPath -Force Write-Host "[OK] $targetPath" -ForegroundColor Green } catch { Write-Host "[FAIL] $targetPath" -ForegroundColor Red Write-Host " $($_.Exception.Message)" -ForegroundColor DarkRed exit 1 } } Write-Host "`nConfig files deployed successfully." -ForegroundColor Green Write-Host "Deployment completed successfully." -ForegroundColor Green exit 0 } else { Write-Host "No project path provided." -ForegroundColor Yellow exit 1 } }