✨feature: Initial commit
This commit is contained in:
3
.gitignore
vendored
Normal file
3
.gitignore
vendored
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
.vscode/
|
||||||
|
notes
|
||||||
|
.wp-bootstraprc
|
||||||
67
README.md
Normal file
67
README.md
Normal file
@@ -0,0 +1,67 @@
|
|||||||
|
# Easy WordPress Bootstrap
|
||||||
|
|
||||||
|
Easily set up a new WordPress dev environment utilizing Laravel Herd and DBngin (or other MySQL/MariaDB solutions).
|
||||||
|
|
||||||
|
One command to spin up a new WordPress project locally:
|
||||||
|
|
||||||
|
- Creates a project folder in your Herd workspace
|
||||||
|
- Provisions MySQL DB + user via DBngin or other MySQL/MariaDB solution.
|
||||||
|
- Generates wp-config.php with salts
|
||||||
|
- Installs WP and sets default options (Home/News, other useful pages, permalinks)
|
||||||
|
- Installs/activates plugins from `plugins.json`
|
||||||
|
- Clones theme starter (with fresh history), activates it, and initializes a clean repo
|
||||||
|
|
||||||
|
## Prerequisites
|
||||||
|
|
||||||
|
- macOS or Windows
|
||||||
|
- Herd installed and a workspace directory (default: `~/Herd`)
|
||||||
|
- DBngin or other MySQL/MariaDB server
|
||||||
|
- CLI tools in PATH: `wp`, `git`, `curl`, `openssl`, `mysql`, and optionally `jq`
|
||||||
|
|
||||||
|
## Quick Start
|
||||||
|
|
||||||
|
1. Copy `wp-bootstraprc.example` to `~/.wp-bootstraprc` and edit values (one-time setup):
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cp wp-bootstraprc.example ~/.wp-bootstraprc
|
||||||
|
```
|
||||||
|
|
||||||
|
2. (Optional) Edit `plugins.json`.
|
||||||
|
|
||||||
|
3. Run the bootstrap:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
chmod +x wp-bootstrap.sh
|
||||||
|
./wp-bootstrap.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
4. Answer prompts. When finished, the script prints a summary and writes `bootstrap-summary.txt`.
|
||||||
|
|
||||||
|
## Configuration
|
||||||
|
|
||||||
|
- **Herd workspace**: set `HERD_WORKSPACE` in `~/.wp-bootstraprc`.
|
||||||
|
- **Local TLD**: set `LOCAL_TLD` (`test` by default).
|
||||||
|
- **DBngin**: set `MYSQL_HOST`, `MYSQL_PORT`, `MYSQL_ROOT_USER`, `MYSQL_ROOT_PASS`.
|
||||||
|
- **Theme starter**: set `THEME_STARTER_REPO`. The script clones shallow, removes `.git`, then re-initializes the theme repo.
|
||||||
|
- **Theme remote**: set `DEFAULT_THEME_REMOTE_ORIGIN` if you want the script to push the new theme repo automatically.
|
||||||
|
|
||||||
|
## Plugins
|
||||||
|
|
||||||
|
Baseline plugins are defined in `plugins.json`. Each entry accepts either a WordPress.org **slug** or a direct **zip** URL.
|
||||||
|
|
||||||
|
> Premium/private zips: use signed/internal URLs or manually download and point to a local `/tmp/file.zip` (adjust the script to `wp plugin install /tmp/file.zip`).
|
||||||
|
|
||||||
|
## Notes
|
||||||
|
|
||||||
|
- The script sets friendly permalinks (`/%postname%/`) and creates Home/News pages by default.
|
||||||
|
- Sample content is removed if present.
|
||||||
|
- A plugin install log is written to `wp-content/plugin-bootstrap.log`.
|
||||||
|
|
||||||
|
## Troubleshooting
|
||||||
|
|
||||||
|
- If `wp` is not found, ensure WP-CLI is installed and available in `PATH`.
|
||||||
|
- If MySQL connection fails, confirm DBngin is running and credentials are correct in `~/.wp-bootstraprc`.
|
||||||
|
- On Windows, prefer WSL or Git Bash for best results.
|
||||||
|
|
||||||
|
--
|
||||||
|
DIY-first policy: keep `plugins.json` minimal. Add big off-the-shelf stacks (e.g., ecomm) only when warranted.
|
||||||
27
plugins.json
Normal file
27
plugins.json
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
[
|
||||||
|
{
|
||||||
|
"zip": "https://docs.vincentdevelopment.ca/files/advanced-custom-fields-pro.zip",
|
||||||
|
"activate": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"zip": "https://docs.vincentdevelopment.ca/files/gravity-forms.zip",
|
||||||
|
"activate": true
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
"slug": "autodescription",
|
||||||
|
"activate": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"slug": "better-search-replace",
|
||||||
|
"activate": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"slug": "google-site-kit",
|
||||||
|
"activate": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"slug": "simple-history",
|
||||||
|
"activate": true
|
||||||
|
}
|
||||||
|
]
|
||||||
67
windows/README-Windows.md
Normal file
67
windows/README-Windows.md
Normal file
@@ -0,0 +1,67 @@
|
|||||||
|
# Easy WordPress Bootstrap (Herd + DBngin) — Windows Edition
|
||||||
|
|
||||||
|
This kit lets Windows devs spin up a new local WordPress project using **PowerShell only**.
|
||||||
|
|
||||||
|
## Includes
|
||||||
|
|
||||||
|
- `wp-bootstrap.ps1` — main PowerShell script
|
||||||
|
- `bootstrap.bat` — one-click launcher
|
||||||
|
- `plugins.json` — minimal default plugin list (use file from repo root)
|
||||||
|
|
||||||
|
## Prerequisites
|
||||||
|
|
||||||
|
- **PHP** installed and in PATH
|
||||||
|
- **MySQL client** (`mysql.exe`) in PATH (DBngin/MySQL installed and running)
|
||||||
|
- **Git** installed (for cloning the starter theme)
|
||||||
|
- **WP-CLI**: either installed globally as `wp`, **or** put `wp-cli.phar` next to this script and call with:
|
||||||
|
- `-WpCliPath "php .\wp-cli.phar"`
|
||||||
|
|
||||||
|
## Quick Start
|
||||||
|
|
||||||
|
1. Right-click **PowerShell** → *Run as Administrator* (first run only):
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
Set-ExecutionPolicy RemoteSigned -Scope CurrentUser
|
||||||
|
```
|
||||||
|
|
||||||
|
2. Open a normal PowerShell in this folder and run:
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
.\wp-bootstrap.ps1 -ProjectName "Client Site"
|
||||||
|
```
|
||||||
|
|
||||||
|
or double-click `bootstrap.bat` and enter the project name when prompted.
|
||||||
|
|
||||||
|
### Optional parameters
|
||||||
|
|
||||||
|
- `-AdminUser` (default: `vdidev`)
|
||||||
|
- `-AdminEmail` (default: `dev@vincentdesign.ca`)
|
||||||
|
- `-ThemeStarterRepo` (default: VDI starter theme)
|
||||||
|
- `-HerdWorkspace` (default: `"$HOME\Herd"`)
|
||||||
|
- `-LocalTld` (default: `test`)
|
||||||
|
- `-MysqlHost` / `-MysqlPort` / `-MysqlRootUser` / `-MysqlRootPass`
|
||||||
|
- `-WpCliPath` (set to `"php .\wp-cli.phar"` if not installed globally)
|
||||||
|
|
||||||
|
## What it does
|
||||||
|
|
||||||
|
- Creates project folder under your Herd workspace
|
||||||
|
- Downloads WordPress core
|
||||||
|
- Creates database + user with random password
|
||||||
|
- Generates `wp-config.php`, shuffles salts
|
||||||
|
- Installs WordPress and sets the site/home URLs
|
||||||
|
- Creates **Home** and **News** pages, sets **front page**/**posts page**
|
||||||
|
- Sets permalinks to `/%postname%/` and flushes
|
||||||
|
- Clones the starter theme, strips history, re-initializes a clean repo, and activates it
|
||||||
|
- Installs and (optionally) activates plugins from `plugins.json`
|
||||||
|
- Prints and saves a summary (`bootstrap-summary.txt`)
|
||||||
|
|
||||||
|
## Troubleshooting
|
||||||
|
|
||||||
|
- **WP-CLI not found**: pass `-WpCliPath "php .\wp-cli.phar"` (place `wp-cli.phar` beside this script).
|
||||||
|
- **MySQL not found**: ensure `mysql.exe` is in PATH. With DBngin, add the MySQL bin folder to PATH.
|
||||||
|
- **Access denied creating DB**: verify `-MysqlRootUser`/`-MysqlRootPass`, or create a dev-only MySQL user with `CREATE` privileges.
|
||||||
|
- **Herd not serving**: Add/link the folder in Herd and browse to `http://<slug>.test` (or your chosen TLD).
|
||||||
|
|
||||||
|
## Philosophy
|
||||||
|
|
||||||
|
DIY-first: keep `plugins.json` minimal. Only add big off‑the‑shelf stacks (e.g., ecomm) when warranted.
|
||||||
4
windows/bootstrap.bat
Normal file
4
windows/bootstrap.bat
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
@echo off
|
||||||
|
REM One-click launcher for the Windows PowerShell bootstrap.
|
||||||
|
REM Usage: bootstrap "Client Site Name"
|
||||||
|
PowerShell -ExecutionPolicy Bypass -File "%~dp0wp-bootstrap.ps1" -ProjectName "%*"
|
||||||
169
windows/wp-bootstrap.ps1
Normal file
169
windows/wp-bootstrap.ps1
Normal file
@@ -0,0 +1,169 @@
|
|||||||
|
|
||||||
|
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"
|
||||||
284
wp-bootstrap.sh
Normal file
284
wp-bootstrap.sh
Normal file
@@ -0,0 +1,284 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
# Load machine/user defaults
|
||||||
|
if [[ -f "$HOME/.vdi-wp-bootstraprc" ]]; then
|
||||||
|
# shellcheck disable=SC1090
|
||||||
|
source "$HOME/.vdi-wp-bootstraprc"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Sensible fallbacks if config missing
|
||||||
|
HERD_WORKSPACE="${HERD_WORKSPACE:-$HOME/Herd}"
|
||||||
|
MYSQL_HOST="${MYSQL_HOST:-127.0.0.1}"
|
||||||
|
MYSQL_PORT="${MYSQL_PORT:-3306}"
|
||||||
|
MYSQL_ROOT_USER="${MYSQL_ROOT_USER:-root}"
|
||||||
|
MYSQL_ROOT_PASS="${MYSQL_ROOT_PASS:-}"
|
||||||
|
DEFAULT_ADMIN_EMAIL="${DEFAULT_ADMIN_EMAIL:-dev@vincentdesign.ca}"
|
||||||
|
DEFAULT_ADMIN_USER="${DEFAULT_ADMIN_USER:-vdidev}"
|
||||||
|
THEME_STARTER_REPO="${THEME_STARTER_REPO:-https://github.com/WordPress/twentytwentyfour.git}"
|
||||||
|
DEFAULT_THEME_REMOTE_ORIGIN="${DEFAULT_THEME_REMOTE_ORIGIN:-}"
|
||||||
|
LOCAL_TLD="${LOCAL_TLD:-test}"
|
||||||
|
|
||||||
|
require() {
|
||||||
|
command -v "$1" >/dev/null 2>&1 || { echo "Missing dependency: $1"; exit 1; }
|
||||||
|
}
|
||||||
|
|
||||||
|
for bin in wp git curl openssl mysql; do
|
||||||
|
require "$bin"
|
||||||
|
done
|
||||||
|
|
||||||
|
slugify() {
|
||||||
|
# lower, spaces->-, strip non [a-z0-9-]
|
||||||
|
echo "$1" | tr '[:upper:]' '[:lower:]' | sed -E 's/[^a-z0-9]+/-/g;s/^-+|-+$//g'
|
||||||
|
}
|
||||||
|
|
||||||
|
randpass() {
|
||||||
|
# 24 char base64, strip non-url-safe chars
|
||||||
|
openssl rand -base64 24 | tr -d '\n' | tr -d '=/+' | cut -c1-24
|
||||||
|
}
|
||||||
|
|
||||||
|
prompt() {
|
||||||
|
local q def ans
|
||||||
|
q="$1"; def="${2:-}"
|
||||||
|
if [[ -n "$def" ]]; then
|
||||||
|
read -r -p "$q [$def]: " ans || true
|
||||||
|
echo "${ans:-$def}"
|
||||||
|
else
|
||||||
|
read -r -p "$q: " ans || true
|
||||||
|
echo "$ans"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
setup_pages_and_reading() {
|
||||||
|
echo "Setting up default pages and reading options…"
|
||||||
|
|
||||||
|
HOME_ID=$(wp post list --post_type=page --name='home' --field=ID --format=ids)
|
||||||
|
if [[ -z "$HOME_ID" ]]; then
|
||||||
|
HOME_ID=$(wp post create --post_type=page --post_status=publish --post_title="Home" --porcelain)
|
||||||
|
fi
|
||||||
|
|
||||||
|
NEWS_ID=$(wp post list --post_type=page --name='news' --field=ID --format=ids)
|
||||||
|
if [[ -z "$NEWS_ID" ]]; then
|
||||||
|
NEWS_ID=$(wp post create --post_type=page --post_status=publish --post_title="News" --porcelain)
|
||||||
|
fi
|
||||||
|
|
||||||
|
for TITLE in "Page Not Found (Error 404)" "Contact Us"; do
|
||||||
|
SLUG="$(echo "$TITLE" | tr '[:upper:]' '[:lower:]' | sed -E 's/[^a-z0-9]+/-/g;s/^-+|-+$//g')"
|
||||||
|
ID=$(wp post list --post_type=page --name="$SLUG" --field=ID --format=ids)
|
||||||
|
[[ -z "$ID" ]] && wp post create --post_type=page --post_status=publish --post_title="$TITLE" --porcelain >/dev/null
|
||||||
|
done
|
||||||
|
|
||||||
|
wp option update show_on_front 'page'
|
||||||
|
wp option update page_on_front "$HOME_ID"
|
||||||
|
wp option update page_for_posts "$NEWS_ID"
|
||||||
|
|
||||||
|
wp post delete 1 --force >/dev/null 2>&1 || true
|
||||||
|
wp post delete 2 --force >/dev/null 2>&1 || true
|
||||||
|
|
||||||
|
wp rewrite structure '/%postname%/'
|
||||||
|
wp rewrite flush --hard
|
||||||
|
echo "Default pages and reading options configured."
|
||||||
|
}
|
||||||
|
|
||||||
|
install_and_activate_plugins() {
|
||||||
|
local LOG="wp-content/plugin-bootstrap.log"
|
||||||
|
echo "=== $(date -u '+%F %T') :: Plugin bootstrap start ===" | tee -a "$LOG"
|
||||||
|
|
||||||
|
if [[ -f "plugins.json" ]]; then
|
||||||
|
if command -v jq >/dev/null 2>&1; then
|
||||||
|
jq -c '.[]' plugins.json | while read -r item; do
|
||||||
|
ZIP=$(echo "$item" | jq -r '.zip // empty')
|
||||||
|
SLUG=$(echo "$item" | jq -r '.slug // empty')
|
||||||
|
VER=$(echo "$item" | jq -r '.version // empty')
|
||||||
|
ACT=$(echo "$item" | jq -r '.activate // false')
|
||||||
|
|
||||||
|
if [[ -n "$ZIP" ]]; then
|
||||||
|
echo "Installing from zip: $ZIP" | tee -a "$LOG"
|
||||||
|
if wp plugin install "$ZIP" --force $( [[ "$ACT" == "true" ]] && echo --activate ); then
|
||||||
|
echo "OK zip: $ZIP (activate=$ACT)" | tee -a "$LOG"
|
||||||
|
else
|
||||||
|
echo "FAIL zip: $ZIP" | tee -a "$LOG"
|
||||||
|
fi
|
||||||
|
elif [[ -n "$SLUG" ]]; then
|
||||||
|
ARGS=(plugin install "$SLUG" --force)
|
||||||
|
[[ -n "$VER" ]] && ARGS+=("--version=$VER")
|
||||||
|
[[ "$ACT" == "true" ]] && ARGS+=("--activate")
|
||||||
|
echo "Installing slug: $SLUG ${VER:+(v$VER)}" | tee -a "$LOG"
|
||||||
|
if wp "${ARGS[@]}"; then
|
||||||
|
echo "OK slug: $SLUG (activate=$ACT)" | tee -a "$LOG"
|
||||||
|
else
|
||||||
|
echo "FAIL slug: $SLUG" | tee -a "$LOG"
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
echo "SKIP item with no slug/zip: $item" | tee -a "$LOG"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
else
|
||||||
|
echo "plugins.json found but jq is not installed; skipping plugin install." | tee -a "$LOG"
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
echo "No plugins.json present; skipping plugin install." | tee -a "$LOG"
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "=== $(date -u '+%F %T') :: Plugin bootstrap end ===" | tee -a "$LOG"
|
||||||
|
}
|
||||||
|
|
||||||
|
echo "— VDI WP Bootstrap (Herd + DBngin) —"
|
||||||
|
PROJECT_NAME="$(prompt 'Project name (Human-readable)')"
|
||||||
|
[[ -z "$PROJECT_NAME" ]] && { echo "Project name is required."; exit 1; }
|
||||||
|
|
||||||
|
PROJECT_SLUG="$(slugify "$PROJECT_NAME")"
|
||||||
|
FOLDER_NAME="$PROJECT_SLUG"
|
||||||
|
PROJECT_DIR="$HERD_WORKSPACE/$FOLDER_NAME"
|
||||||
|
LOCAL_URL="http://$PROJECT_SLUG.$LOCAL_TLD"
|
||||||
|
|
||||||
|
echo "Derived:"
|
||||||
|
echo " slug: $PROJECT_SLUG"
|
||||||
|
echo " path: $PROJECT_DIR"
|
||||||
|
echo " local URL: $LOCAL_URL"
|
||||||
|
echo
|
||||||
|
|
||||||
|
ADMIN_USER="$(prompt 'Admin username' "$DEFAULT_ADMIN_USER")"
|
||||||
|
ADMIN_EMAIL="$(prompt 'Admin email' "$DEFAULT_ADMIN_EMAIL")"
|
||||||
|
ADMIN_PASS="$(randpass)"
|
||||||
|
|
||||||
|
DB_NAME="vdi_${PROJECT_SLUG}"
|
||||||
|
DB_USER="$DB_NAME"
|
||||||
|
DB_PASS="$(randpass)"
|
||||||
|
|
||||||
|
THEME_REPO_URL="$(prompt 'Theme starter repo URL' "$THEME_STARTER_REPO")"
|
||||||
|
THEME_DIR="wp-content/themes/${PROJECT_SLUG}-theme"
|
||||||
|
THEME_REMOTE_ORIGIN="$(prompt 'New theme repo remote (origin) URL (leave blank to skip push)' "$DEFAULT_THEME_REMOTE_ORIGIN")"
|
||||||
|
|
||||||
|
echo
|
||||||
|
echo "Creating project directory…"
|
||||||
|
mkdir -p "$PROJECT_DIR"
|
||||||
|
cd "$PROJECT_DIR"
|
||||||
|
|
||||||
|
echo "Downloading WordPress core…"
|
||||||
|
wp core download --force
|
||||||
|
|
||||||
|
echo "Creating database and user in MySQL ($MYSQL_HOST:$MYSQL_PORT)…"
|
||||||
|
MYSQL_AUTH=(-h "$MYSQL_HOST" -P "$MYSQL_PORT" -u "$MYSQL_ROOT_USER")
|
||||||
|
if [[ -n "$MYSQL_ROOT_PASS" ]]; then
|
||||||
|
MYSQL_AUTH+=(-p"$MYSQL_ROOT_PASS")
|
||||||
|
fi
|
||||||
|
|
||||||
|
mysql "${MYSQL_AUTH[@]}" <<SQL
|
||||||
|
CREATE DATABASE IF NOT EXISTS \`$DB_NAME\` CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
|
||||||
|
CREATE USER IF NOT EXISTS '$DB_USER'@'%' IDENTIFIED BY '$DB_PASS';
|
||||||
|
GRANT ALL PRIVILEGES ON \`$DB_NAME\`.* TO '$DB_USER'@'%';
|
||||||
|
FLUSH PRIVILEGES;
|
||||||
|
SQL
|
||||||
|
|
||||||
|
echo "Generating wp-config.php…"
|
||||||
|
wp config create \
|
||||||
|
--dbname="$DB_NAME" \
|
||||||
|
--dbuser="$DB_USER" \
|
||||||
|
--dbpass="$DB_PASS" \
|
||||||
|
--dbhost="${MYSQL_HOST}:${MYSQL_PORT}" \
|
||||||
|
--force
|
||||||
|
|
||||||
|
if wp config shuffle-salts >/dev/null 2>&1; then
|
||||||
|
echo "Salt keys shuffled."
|
||||||
|
else
|
||||||
|
echo "Fetching salts from api.wordpress.org…"
|
||||||
|
SALTS="$(curl -fsSL https://api.wordpress.org/secret-key/1.1/salt/)"
|
||||||
|
wp config set AUTH_KEY "dummy" --type=constant --raw >/dev/null 2>&1 || true
|
||||||
|
php -r 'file_put_contents("wp-config.php", preg_replace("/\\?>\\s*$/","",file_get_contents("wp-config.php"))."\n".'"'"$SALTS"'"'."\n");'
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "Installing WordPress…"
|
||||||
|
wp core install \
|
||||||
|
--url="$LOCAL_URL" \
|
||||||
|
--title="$PROJECT_NAME" \
|
||||||
|
--admin_user="$ADMIN_USER" \
|
||||||
|
--admin_password="$ADMIN_PASS" \
|
||||||
|
--admin_email="$ADMIN_EMAIL"
|
||||||
|
|
||||||
|
wp option update siteurl "$LOCAL_URL"
|
||||||
|
wp option update home "$LOCAL_URL"
|
||||||
|
|
||||||
|
setup_pages_and_reading
|
||||||
|
|
||||||
|
echo "Cloning theme starter (history will be stripped)…"
|
||||||
|
TMP_DIR=".starter-tmp"
|
||||||
|
rm -rf "$TMP_DIR"
|
||||||
|
git clone --depth=1 "$THEME_REPO_URL" "$TMP_DIR"
|
||||||
|
|
||||||
|
mkdir -p "$(dirname "$THEME_DIR")"
|
||||||
|
rm -rf "$THEME_DIR"
|
||||||
|
mv "$TMP_DIR" "$THEME_DIR"
|
||||||
|
rm -rf "$THEME_DIR/.git"
|
||||||
|
|
||||||
|
if [[ -f "$THEME_DIR/style.css" ]]; then
|
||||||
|
sed -i.bak "s/Theme Name:.*/Theme Name: ${PROJECT_NAME}/" "$THEME_DIR/style.css" || true
|
||||||
|
sed -i.bak "s/Text Domain:.*/Text Domain: ${PROJECT_SLUG}-theme/" "$THEME_DIR/style.css" || true
|
||||||
|
rm -f "$THEME_DIR/style.css.bak"
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "Activating theme…"
|
||||||
|
wp theme activate "${PROJECT_SLUG}-theme" || {
|
||||||
|
echo "Activation failed (maybe theme slug mismatch). Listing themes:"
|
||||||
|
wp theme list
|
||||||
|
}
|
||||||
|
|
||||||
|
install_and_activate_plugins
|
||||||
|
|
||||||
|
echo "Initializing theme repo…"
|
||||||
|
pushd "$THEME_DIR" >/dev/null
|
||||||
|
git init -b main
|
||||||
|
git add -A
|
||||||
|
git commit -m "feat: bootstrap ${PROJECT_NAME} theme from starter"
|
||||||
|
if [[ -n "$THEME_REMOTE_ORIGIN" ]]; then
|
||||||
|
git remote add origin "$THEME_REMOTE_ORIGIN"
|
||||||
|
git push -u origin main
|
||||||
|
fi
|
||||||
|
popd >/dev/null
|
||||||
|
|
||||||
|
create_wpengine_staging() {
|
||||||
|
if [[ -z "${WPE_API_TOKEN:-}" ]]; then
|
||||||
|
echo "WPE_API_TOKEN not set; skipping staging creation."
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
echo "Stub: Add WPE API call here to create staging for ${PROJECT_SLUG}."
|
||||||
|
}
|
||||||
|
create_wpengine_staging
|
||||||
|
|
||||||
|
SUMMARY_FILE="$PROJECT_DIR/bootstrap-summary.txt"
|
||||||
|
cat > "$SUMMARY_FILE" <<TXT
|
||||||
|
VDI WP Bootstrap — Summary
|
||||||
|
===========================
|
||||||
|
|
||||||
|
Project: $PROJECT_NAME
|
||||||
|
Slug: $PROJECT_SLUG
|
||||||
|
Folder: $PROJECT_DIR
|
||||||
|
Local URL: $LOCAL_URL
|
||||||
|
|
||||||
|
DB Host: $MYSQL_HOST
|
||||||
|
DB Port: $MYSQL_PORT
|
||||||
|
DB Name: $DB_NAME
|
||||||
|
DB User: $DB_USER
|
||||||
|
DB Pass: $DB_PASS
|
||||||
|
|
||||||
|
WP Admin User: $ADMIN_USER
|
||||||
|
WP Admin Pass: $ADMIN_PASS
|
||||||
|
WP Admin Email: $ADMIN_EMAIL
|
||||||
|
|
||||||
|
Theme Dir: $THEME_DIR
|
||||||
|
Theme Remote: ${THEME_REMOTE_ORIGIN:-<none>}
|
||||||
|
|
||||||
|
Log files:
|
||||||
|
- Plugin install log: wp-content/plugin-bootstrap.log
|
||||||
|
|
||||||
|
Next steps:
|
||||||
|
- If Herd doesn’t auto-serve the folder, link it via Herd UI/CLI and open $LOCAL_URL
|
||||||
|
- Remove legacy activation.php from the starter theme if present (now handled by bootstrap)
|
||||||
|
- Wire CI/CD as needed (never push DB to production)
|
||||||
|
TXT
|
||||||
|
|
||||||
|
echo
|
||||||
|
echo "✅ Bootstrap complete."
|
||||||
|
echo "Summary saved to: $SUMMARY_FILE"
|
||||||
|
echo
|
||||||
|
cat "$SUMMARY_FILE"
|
||||||
21
wp-bootstraprc.example
Normal file
21
wp-bootstraprc.example
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
# Herd workspace (adjust if yours differs)
|
||||||
|
HERD_WORKSPACE="$HOME/Herd" # e.g., "$HOME/Sites" or "$HOME/Projects"
|
||||||
|
|
||||||
|
# DBngin/MySQL connection
|
||||||
|
MYSQL_HOST="127.0.0.1"
|
||||||
|
MYSQL_PORT="3306"
|
||||||
|
MYSQL_ROOT_USER="root"
|
||||||
|
MYSQL_ROOT_PASS=""
|
||||||
|
|
||||||
|
# Defaults for project bootstrap
|
||||||
|
DEFAULT_ADMIN_EMAIL="dev@vincentdesign.ca"
|
||||||
|
DEFAULT_ADMIN_USER="vdidev"
|
||||||
|
|
||||||
|
# Theme starter (public or private – ensure your git auth is set up for private repos)
|
||||||
|
THEME_STARTER_REPO="https://github.com/vincent-design-inc/starter-theme-3.git"
|
||||||
|
|
||||||
|
# Optional: default remote you often use for new theme repos (leave blank to skip pushing)
|
||||||
|
DEFAULT_THEME_REMOTE_ORIGIN=""
|
||||||
|
|
||||||
|
# Default local domain suffix used by Herd/Valet
|
||||||
|
LOCAL_TLD="test"
|
||||||
Reference in New Issue
Block a user