366 lines
13 KiB
PowerShell
366 lines
13 KiB
PowerShell
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
|
|
}
|
|
}
|