Param( [Parameter(Mandatory=$true)][string]$ProjectName, [string]$AdminUser = "vdidev", [string]$AdminEmail = "dev@vincentdesign.ca", [string]$ThemeStarterRepo = "https://github.com/vincent-design-inc/starter-theme-3.git", [string]$HerdWorkspace = "$HOME\Herd", [string]$LocalTld = "test", [string]$MysqlHost = "127.0.0.1", [string]$MysqlPort = "3306", [string]$MysqlRootUser = "root", [string]$MysqlRootPass = "", # If WP-CLI isn't globally installed, set this to 'php .\wp-cli.phar' [string]$WpCliPath = "wp" ) function Slugify([string]$s) { ($s.ToLower() -replace '[^a-z0-9]+','-').Trim('-') } function RandPass() { $bytes = New-Object 'System.Byte[]' 18 (New-Object System.Security.Cryptography.RNGCryptoServiceProvider).GetBytes($bytes) [Convert]::ToBase64String($bytes) -replace '[^a-zA-Z0-9]','' | ForEach-Object { $_.Substring(0,24) } } function Invoke-WP { param([string[]]$Args) # Support 'wp' OR 'php .\wp-cli.phar' if ($WpCliPath -match '\s') { $parts = $WpCliPath -split '\s+' & $parts[0] $parts[1..($parts.Count-1)] $Args 2>&1 | ForEach-Object { "$_" } } else { & $WpCliPath $Args 2>&1 | ForEach-Object { "$_" } } if ($LASTEXITCODE -ne 0) { throw "WP-CLI command failed: $($Args -join ' ')" } } function WP([string]$line) { # Convenience to pass a single-line string; splits on spaces $args = @() $token = "" $inQuote = $false foreach ($c in $line.ToCharArray()) { if ($c -eq '"') { $inQuote = -not $inQuote; continue } if (-not $inQuote -and [char]::IsWhiteSpace($c)) { if ($token.Length -gt 0) { $args += $token; $token = "" } } else { $token += $c } } if ($token.Length -gt 0) { $args += $token } Invoke-WP $args } # === Start === $Slug = Slugify $ProjectName $ProjectDir = Join-Path $HerdWorkspace $Slug $LocalUrl = "http://$Slug.$LocalTld" $DbName = "vdi_$Slug" $DbUser = $DbName $DbPass = RandPass $AdminPass = RandPass Write-Host "Creating project at $ProjectDir..." New-Item -ItemType Directory -Force -Path $ProjectDir | Out-Null Set-Location $ProjectDir # --- WordPress Core --- Write-Host "Downloading WordPress..." Invoke-WP @("core","download","--force") # --- Database setup --- Write-Host "Creating MySQL DB and user..." $mysqlArgs = @("-h", $MysqlHost, "-P", $MysqlPort, "-u", $MysqlRootUser) if ($MysqlRootPass -ne "") { $mysqlArgs += "-p$MysqlRootPass" } $sql = @" CREATE DATABASE IF NOT EXISTS `$DbName` CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; CREATE USER IF NOT EXISTS '$DbUser'@'%' IDENTIFIED BY '$DbPass'; GRANT ALL PRIVILEGES ON `$DbName`.* TO '$DbUser'@'%'; FLUSH PRIVILEGES; "@ try { $sql | & mysql.exe @mysqlArgs } catch { throw "MySQL client not found or connection failed. Ensure mysql.exe is in PATH and DBngin/MySQL is running." } # --- Config + Install --- Invoke-WP @("config","create","--dbname=$DbName","--dbuser=$DbUser","--dbpass=$DbPass","--dbhost=$MysqlHost`:$MysqlPort","--force") Invoke-WP @("config","shuffle-salts") Invoke-WP @("core","install","--url=$LocalUrl","--title=$ProjectName","--admin_user=$AdminUser","--admin_password=$AdminPass","--admin_email=$AdminEmail") Invoke-WP @("option","update","siteurl",$LocalUrl) Invoke-WP @("option","update","home",$LocalUrl) # --- Pages & Reading --- Write-Host "Creating Home/News pages and setting permalinks..." $homeId = (Invoke-WP @("post","create","--post_type=page","--post_status=publish","--post_title=Home","--porcelain") | Select-Object -Last 1).Trim() $newsId = (Invoke-WP @("post","create","--post_type=page","--post_status=publish","--post_title=News","--porcelain") | Select-Object -Last 1).Trim() Invoke-WP @("option","update","show_on_front","page") Invoke-WP @("option","update","page_on_front",$homeId) Invoke-WP @("option","update","page_for_posts",$newsId) Invoke-WP @("rewrite","structure","/%postname%/") Invoke-WP @("rewrite","flush","--hard") # --- Theme --- Write-Host "Cloning starter theme..." $tmp = ".starter-tmp" if (Test-Path $tmp) { Remove-Item -Recurse -Force $tmp } git clone --depth=1 $ThemeStarterRepo $tmp | Out-Null $ThemeDir = "wp-content\themes\$Slug-theme" if (Test-Path $ThemeDir) { Remove-Item -Recurse -Force $ThemeDir } New-Item -ItemType Directory -Force -Path (Split-Path $ThemeDir) | Out-Null Move-Item $tmp $ThemeDir if (Test-Path "$ThemeDir\.git") { Remove-Item -Recurse -Force "$ThemeDir\.git" } Invoke-WP @("theme","activate","$Slug-theme") Push-Location $ThemeDir git init -b main | Out-Null git add -A git commit -m "feat: bootstrap ${ProjectName} theme from starter" | Out-Null Pop-Location # --- Plugins --- if (Test-Path "plugins.json") { try { $plugins = Get-Content "plugins.json" | ConvertFrom-Json foreach ($plugin in $plugins) { if ($plugin.zip) { $args = @("plugin","install",$plugin.zip,"--force") if ($plugin.activate -eq $true) { $args += "--activate" } Invoke-WP $args } elseif ($plugin.slug) { $args = @("plugin","install",$plugin.slug,"--force") if ($plugin.version) { $args += "--version=$($plugin.version)" } if ($plugin.activate -eq $true) { $args += "--activate" } Invoke-WP $args } } } catch { Write-Host "Error reading plugins.json: $_" } } # --- Summary --- $Summary = @" VDI WP Bootstrap — Summary =========================== Project: $ProjectName Slug: $Slug Folder: $ProjectDir Local URL: $LocalUrl DB Host: $MysqlHost DB Port: $MysqlPort DB Name: $DbName DB User: $DbUser DB Pass: $DbPass WP Admin User: $AdminUser WP Admin Pass: $AdminPass WP Admin Email: $AdminEmail Theme Dir: $ThemeDir "@ $SummaryFile = Join-Path $ProjectDir "bootstrap-summary.txt" $Summary | Out-File -FilePath $SummaryFile -Encoding utf8 Write-Host $Summary Write-Host "Saved summary to $SummaryFile"