feature: Enhance bootstrap scripts with improved configuration handling and plugin management

This commit is contained in:
Keith Solomon
2025-11-01 11:01:57 -05:00
parent 06e4007b5e
commit 1f12dff4d2
3 changed files with 347 additions and 128 deletions

View File

@@ -1,67 +1,90 @@
# Easy WordPress Bootstrap # Easy WordPress Bootstrap
Easily set up a new WordPress dev environment utilizing Laravel Herd and DBngin (or other MySQL/MariaDB solutions). Spin up a complete WordPress development site on macOS with Laravel Herd and DBngin (or any MySQL/MariaDB server) in one pass. For the Windows/PowerShell workflow, see [`windows/README-Windows.md`](windows/README-Windows.md).
One command to spin up a new WordPress project locally: ## What You Get
- Creates a project folder in your Herd workspace - Project directory under your Herd workspace with sanitized slug and host mapping.
- Provisions MySQL DB + user via DBngin or other MySQL/MariaDB solution. - MySQL database + user created after verifying the supplied root credentials.
- Generates wp-config.php with salts - Latest WordPress core, `wp-config.php` with fresh salts, permalinks set to `/%postname%/`, and Home/News pages.
- Installs WP and sets default options (Home/News, other useful pages, permalinks) - Plugin install/activation from `plugins.json`, resolved from the project folder first then the repository default.
- Installs/activates plugins from `plugins.json` - Starter theme cloned with shallow history, `.git` stripped, repo reinitialized, remote optionally set, and Composer/NPM build steps run when available.
- Clones theme starter (with fresh history), activates it, and initializes a clean repo - `.htaccess` seeded when Apache rewrites are missing, `bootstrap-summary.txt` written, and plugin install logs stored at `wp-content/plugin-bootstrap.log`.
- Automatic WP-CLI detection with a bundled `wp-cli.phar` fallback (`WP_CLI_PHAR` env/config override).
## Prerequisites ## Prerequisites
- macOS or [Windows](windows/README-Windows.md) (PowerShell) - macOS with [Laravel Herd](https://herd.laravel.com/) configured (default workspace `~/Herd`).
- Herd installed and a workspace directory (default: `~/Herd`) - MySQL or MariaDB available locally (DBngin recommended) and the `mysql` client in `PATH`.
- DBngin or other MySQL/MariaDB server - CLI tools: `php`, `git`, `curl`, `openssl`, `composer`, `npm`, `mysql`, and optionally `jq` for pretty plugin output.
- CLI tools in PATH: `wp`, `git`, `curl`, `openssl`, `mysql`, and optionally `jq` - Either `wp` in `PATH` or a downloadable `wp-cli.phar` placed beside the script (or in `windows/wp-cli.phar`).
## Quick Start ## Quick Start (Interactive)
1. Copy `wp-bootstraprc.example` to `~/.wp-bootstraprc` and edit values (one-time setup): 1. Copy the sample configuration (one time):
```bash ```bash
cp wp-bootstraprc.example ~/.wp-bootstraprc cp wp-bootstraprc.example ~/.wp-bootstraprc
``` ```
2. (Optional) Edit `plugins.json`. 2. Review `plugins.json` (optional override per project; place your own copy in the project root if needed).
3. Make the script executable, then run it and answer prompts:
3. Run the bootstrap:
```bash ```bash
chmod +x wp-bootstrap.sh chmod +x wp-bootstrap.sh
./wp-bootstrap.sh ./wp-bootstrap.sh
``` ```
4. Answer prompts. When finished, the script prints a summary and writes `bootstrap-summary.txt`. When the bootstrap completes, open the URL printed in the summary and check `bootstrap-summary.txt` under the project root.
## Configuration ## Non-Interactive Usage
- **Herd workspace**: set `HERD_WORKSPACE` in `~/.wp-bootstraprc`. Pass values via flags (help output lists every option):
- **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 ```bash
./wp-bootstrap.sh \
--project-name "Client Site" \
--admin-user admin \
--admin-email dev@example.com \
--theme-starter-repo git@github.com:vendor/theme.git \
--herd-workspace "$HOME/Herd" \
--local-tld test \
--mysql-host 127.0.0.1 \
--mysql-port 3306 \
--mysql-root-user root \
--mysql-root-pass secret
```
Baseline plugins are defined in `plugins.json`. Each entry accepts either a WordPress.org **slug** or a direct **zip** URL. Any value omitted from the CLI falls back to `~/.wp-bootstraprc` or the script defaults. Supply `--help` to print the full usage guide.
> 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`). ## Configuration with `.wp-bootstraprc`
## Notes The script sources `~/.wp-bootstraprc` on start. Key entries include:
- The script sets friendly permalinks (`/%postname%/`) and creates Home/News pages by default. - `HERD_WORKSPACE`, `LOCAL_TLD`
- Sample content is removed if present. - `MYSQL_HOST`, `MYSQL_PORT`, `MYSQL_ROOT_USER`, `MYSQL_ROOT_PASS`
- A plugin install log is written to `wp-content/plugin-bootstrap.log`. - `DEFAULT_ADMIN_USER`, `DEFAULT_ADMIN_EMAIL`
- `THEME_STARTER_REPO`, `DEFAULT_THEME_REMOTE_ORIGIN`
- `WP_CLI_PHAR` to point at a custom `wp-cli.phar`
Environment variables exported before running the script take precedence over both CLI flags and rc defaults when they share the same name.
## Plugin Manifest
`plugins.json` accepts WordPress.org slugs or zip URLs. The script first looks for `./plugins.json` inside the target project (useful for site-specific manifests) and falls back to the repository version when absent. Premium/private zips can point to signed URLs or local files; make sure the executing user has access.
## Logs & Files Created
- `bootstrap-summary.txt` high-level run output, stored in the project root.
- `wp-content/plugin-bootstrap.log` detailed plugin installation results.
- `.htaccess` created only when missing, populated with standard WordPress rewrite rules.
## Troubleshooting ## Troubleshooting
- If `wp` is not found, ensure WP-CLI is installed and available in `PATH`. - **WP-CLI missing** install `wp` globally, or download `wp-cli.phar` into the script directory and set `WP_CLI_PHAR=/path/to/wp-cli.phar`.
- If MySQL connection fails, confirm DBngin is running and credentials are correct in `~/.wp-bootstraprc`. - **MySQL connection failures** confirm DBngin (or your server) is running, credentials match your `.wp-bootstraprc`, and the root user can create databases/users.
- On Windows, prefer WSL or Git Bash for best results. - **Composer/NPM not installed** the script skips dependency installation but leaves a warning in the output; install the tooling and rerun if theme assets are required.
- **Permissions** ensure the Herd workspace and MySQL socket/port are accessible to your user account.
-- ---
DIY-first policy: keep `plugins.json` minimal. Add big off-the-shelf stacks (e.g., ecomm) only when warranted. DIY-first policy: keep `plugins.json` minimal; add heavier stacks only when a project truly needs them.

View File

@@ -1,67 +1,80 @@
# Easy WordPress Bootstrap (Herd + DBngin) — Windows Edition # Easy WordPress Bootstrap (Windows + Herd)
This kit lets Windows devs spin up a new local WordPress project using **PowerShell only**. PowerShell companion to the macOS bootstrap script. It provisions a full WordPress development site backed by Laravel Herd and DBngin (or any local MySQL/MariaDB server) with a single command.
## Includes ## Included Files
- `wp-bootstrap.ps1` main PowerShell script - `wp-bootstrap.ps1` main PowerShell script.
- `bootstrap.bat` — one-click launcher - `wp-cli.phar` bundled WP-CLI used by the script when `wp` is not installed globally.
- `plugins.json` — minimal default plugin list (use file from repo root) - `plugins.json` default plugin manifest; copy/override per project if you need a different set.
## Prerequisites ## Prerequisites
- **PHP** installed and in PATH - Windows 10/11 with PowerShell 7 (`pwsh`) recommended.
- **MySQL client** (`mysql.exe`) in PATH (DBngin/MySQL installed and running) - [Laravel Herd for Windows](https://herd.laravel.com/) installed so PHP is available (the script auto-detects Herds `php.exe`).
- **Git** installed (for cloning the starter theme) - DBngin or another MySQL/MariaDB server running locally, with `mysql.exe` available in `PATH`.
- **WP-CLI**: either installed globally as `wp`, **or** put `wp-cli.phar` next to this script and call with: - Git for cloning the starter theme.
- `-WpCliPath "php .\wp-cli.phar"` - Composer and npm in `PATH` so theme dependencies can be installed and built (the script warns and skips if they are missing).
## Quick Start ## First-Time Setup
1. Right-click **PowerShell***Run as Administrator* (first run only): 1. Allow local PowerShell scripts (only needs to be done once for your profile):
```powershell ```powershell
Set-ExecutionPolicy RemoteSigned -Scope CurrentUser Set-ExecutionPolicy -Scope CurrentUser RemoteSigned
``` ```
2. Open a normal PowerShell in this folder and run: 2. Copy the sample rc file and adjust values that should become your defaults:
```powershell ```powershell
.\wp-bootstrap.ps1 -ProjectName "Client Site" Copy-Item ..\wp-bootstraprc.example $HOME\.wp-bootstraprc
``` ```
or double-click `bootstrap.bat` and enter the project name when prompted. 3. Leave `wp-cli.phar` in the same folder as `wp-bootstrap.ps1` (or update the `WP_CLI_PHAR` entry in your rc file to point elsewhere).
### Optional parameters ## Running a Bootstrap
- `-AdminUser` (default: `vdidev`) From this directory, run:
- `-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 ```powershell
pwsh -File .\wp-bootstrap.ps1 -ProjectName "Client Site"
```
- Creates project folder under your Herd workspace Optional parameters (all may also be supplied via `~\.wp-bootstraprc`):
- Downloads WordPress core
- Creates database + user with random password - `-AdminUser`
- Generates `wp-config.php`, shuffles salts - `-AdminEmail`
- Installs WordPress and sets the site/home URLs - `-ThemeStarterRepo`
- Creates **Home** and **News** pages, sets **front page**/**posts page** - `-HerdWorkspace`
- Sets permalinks to `/%postname%/` and flushes - `-LocalTld`
- Clones the starter theme, strips history, re-initializes a clean repo, and activates it - `-MysqlHost`
- Installs and (optionally) activates plugins from `plugins.json` - `-MysqlPort`
- Prints and saves a summary (`bootstrap-summary.txt`) - `-MysqlRootUser`
- `-MysqlRootPass`
Omit parameters to fall back to your rc defaults. The script prints the detected configuration before it runs and exits early if required inputs are still missing.
## What the Script Does
- Creates a sanitized project folder in your Herd workspace and reports the local URL.
- Validates MySQL root credentials, creates the database/user, and confirms the new user can connect.
- Downloads WordPress core, generates `wp-config.php`, shuffles salts, sets permalinks, creates Home/News pages, and assigns them.
- Ensures a standard `.htaccess` exists for Apache-friendly rewrites.
- Clones the starter theme with shallow history, strips `.git`, reinitializes the repo, sets an optional remote, and activates the theme.
- Installs/activates plugins pulled from `plugins.json`, preferring a project-local manifest when present.
- Runs `composer install`, `npm install`, and `npm run build` inside the theme when those tools are available.
- Writes a `bootstrap-summary.txt` to the project root and logs plugin installs to `wp-content/plugin-bootstrap.log`.
## Plugin Manifest Details
`plugins.json` entries can be WordPress.org slugs or zip URLs (including private artifacts). Place a tailored `plugins.json` in your project directory to override the repository default for a single site.
## Troubleshooting ## Troubleshooting
- **WP-CLI not found**: pass `-WpCliPath "php .\wp-cli.phar"` (place `wp-cli.phar` beside this script). - **WP-CLI** ensure `wp-cli.phar` lives beside the script or set `WP_CLI_PHAR` in `~\.wp-bootstraprc` to an absolute path.
- **MySQL not found**: ensure `mysql.exe` is in PATH. With DBngin, add the MySQL bin folder to PATH. - **MySQL connectivity** verify the server is running, your root user matches the rc values, and Windows Firewall allows the connection.
- **Access denied creating DB**: verify `-MysqlRootUser`/`-MysqlRootPass`, or create a dev-only MySQL user with `CREATE` privileges. - **Composer/NPM missing** install them via Scoop/winget/Homebrew on Windows, or remove their commands from the script if you do not build theme assets.
- **Herd not serving**: Add/link the folder in Herd and browse to `http://<slug>.test` (or your chosen TLD). - **Permissions** confirm Herd has access to the project folder and that the execution policy change succeeded.
## Philosophy ---
DIY-first policy: keep `plugins.json` lean; add heavier stacks only when the project truly needs them.
DIY-first: keep `plugins.json` minimal. Only add big offtheshelf stacks (e.g., ecomm) when warranted.

View File

@@ -1,10 +1,13 @@
#!/usr/bin/env bash #!/usr/bin/env bash
set -euo pipefail set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
export WP_CLI_STRICT_ARGS_MODE=1
# Load machine/user defaults # Load machine/user defaults
if [[ -f "$HOME/.vdi-wp-bootstraprc" ]]; then if [[ -f "$HOME/.wp-bootstraprc" ]]; then
# shellcheck disable=SC1090 # shellcheck disable=SC1090
source "$HOME/.vdi-wp-bootstraprc" source "$HOME/.wp-bootstraprc"
fi fi
# Sensible fallbacks if config missing # Sensible fallbacks if config missing
@@ -23,9 +26,52 @@ require() {
command -v "$1" >/dev/null 2>&1 || { echo "Missing dependency: $1"; exit 1; } command -v "$1" >/dev/null 2>&1 || { echo "Missing dependency: $1"; exit 1; }
} }
for bin in wp git curl openssl mysql; do PHP_BIN="${WP_PHP_BIN:-php}"
for bin in git curl openssl mysql composer npm; do
require "$bin" require "$bin"
done done
require "$PHP_BIN"
if command -v wp >/dev/null 2>&1; then
WP_CLI_CMD=(wp)
else
WP_CLI_PHAR_DEFAULT="$SCRIPT_DIR/wp-cli.phar"
if [[ ! -f "$WP_CLI_PHAR_DEFAULT" && -f "$SCRIPT_DIR/windows/wp-cli.phar" ]]; then
WP_CLI_PHAR_DEFAULT="$SCRIPT_DIR/windows/wp-cli.phar"
fi
WP_CLI_PHAR="${WP_CLI_PHAR:-$WP_CLI_PHAR_DEFAULT}"
if [[ -f "$WP_CLI_PHAR" ]]; then
WP_CLI_CMD=("$PHP_BIN" -f "$WP_CLI_PHAR" --)
else
echo "Missing dependency: wp (command) or wp-cli.phar (set WP_CLI_PHAR)." >&2
exit 1
fi
fi
invoke_wp() {
local args=("$@")
WP_CLI_STRICT_ARGS_MODE=1 "${WP_CLI_CMD[@]}" --path="$PROJECT_DIR" "${args[@]}"
}
usage() {
cat <<'USAGE'
Usage: wp-bootstrap.sh --project-name "Example Site" [options]
Options:
--project-name NAME Human-friendly project name (required)
--admin-user USER WordPress admin username
--admin-email EMAIL WordPress admin email
--theme-starter-repo URL Git URL for the starter theme
--herd-workspace PATH Herd workspace directory
--local-tld TLD Local development TLD (e.g., test)
--mysql-host HOST MySQL host
--mysql-port PORT MySQL port
--mysql-root-user USER MySQL root username
--mysql-root-pass PASS MySQL root password
--help Show this help and exit
USAGE
}
slugify() { slugify() {
# lower, spaces->-, strip non [a-z0-9-] # lower, spaces->-, strip non [a-z0-9-]
@@ -50,33 +96,33 @@ prompt() {
} }
setup_pages_and_reading() { setup_pages_and_reading() {
echo "Setting up default pages and reading options" echo "Setting up default pages and reading options..."
HOME_ID=$(wp post list --post_type=page --name='home' --field=ID --format=ids) HOME_ID=$(invoke_wp post list --post_type=page --name='home' --field=ID --format=ids)
if [[ -z "$HOME_ID" ]]; then if [[ -z "$HOME_ID" ]]; then
HOME_ID=$(wp post create --post_type=page --post_status=publish --post_title="Home" --porcelain) HOME_ID=$(invoke_wp post create --post_type=page --post_status=publish --post_title="Home" --porcelain)
fi fi
NEWS_ID=$(wp post list --post_type=page --name='news' --field=ID --format=ids) NEWS_ID=$(invoke_wp post list --post_type=page --name='news' --field=ID --format=ids)
if [[ -z "$NEWS_ID" ]]; then if [[ -z "$NEWS_ID" ]]; then
NEWS_ID=$(wp post create --post_type=page --post_status=publish --post_title="News" --porcelain) NEWS_ID=$(invoke_wp post create --post_type=page --post_status=publish --post_title="News" --porcelain)
fi fi
for TITLE in "Page Not Found (Error 404)" "Contact Us"; do 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')" 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) ID=$(invoke_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 [[ -z "$ID" ]] && invoke_wp post create --post_type=page --post_status=publish --post_title="$TITLE" --porcelain >/dev/null
done done
wp option update show_on_front 'page' invoke_wp option update show_on_front 'page'
wp option update page_on_front "$HOME_ID" invoke_wp option update page_on_front "$HOME_ID"
wp option update page_for_posts "$NEWS_ID" invoke_wp option update page_for_posts "$NEWS_ID"
wp post delete 1 --force >/dev/null 2>&1 || true invoke_wp post delete 1 --force >/dev/null 2>&1 || true
wp post delete 2 --force >/dev/null 2>&1 || true invoke_wp post delete 2 --force >/dev/null 2>&1 || true
wp rewrite structure '/%postname%/' invoke_wp rewrite structure '/%postname%/'
wp rewrite flush --hard invoke_wp rewrite flush --hard
echo "Default pages and reading options configured." echo "Default pages and reading options configured."
} }
@@ -84,9 +130,19 @@ install_and_activate_plugins() {
local LOG="wp-content/plugin-bootstrap.log" local LOG="wp-content/plugin-bootstrap.log"
echo "=== $(date -u '+%F %T') :: Plugin bootstrap start ===" | tee -a "$LOG" echo "=== $(date -u '+%F %T') :: Plugin bootstrap start ===" | tee -a "$LOG"
if [[ -f "plugins.json" ]]; then local plugins_path=""
if [[ -f "$PROJECT_DIR/plugins.json" ]]; then
plugins_path="$PROJECT_DIR/plugins.json"
elif [[ -f "$SCRIPT_DIR/plugins.json" ]]; then
plugins_path="$SCRIPT_DIR/plugins.json"
fi
if [[ -n "$plugins_path" ]]; then
local pretty_path="$plugins_path"
[[ "$pretty_path" == "$PROJECT_DIR/"* ]] && pretty_path="plugins.json"
echo "Installing plugins from $pretty_path..." | tee -a "$LOG"
if command -v jq >/dev/null 2>&1; then if command -v jq >/dev/null 2>&1; then
jq -c '.[]' plugins.json | while read -r item; do jq -c '.[]' "$plugins_path" | while read -r item; do
ZIP=$(echo "$item" | jq -r '.zip // empty') ZIP=$(echo "$item" | jq -r '.zip // empty')
SLUG=$(echo "$item" | jq -r '.slug // empty') SLUG=$(echo "$item" | jq -r '.slug // empty')
VER=$(echo "$item" | jq -r '.version // empty') VER=$(echo "$item" | jq -r '.version // empty')
@@ -94,17 +150,19 @@ install_and_activate_plugins() {
if [[ -n "$ZIP" ]]; then if [[ -n "$ZIP" ]]; then
echo "Installing from zip: $ZIP" | tee -a "$LOG" echo "Installing from zip: $ZIP" | tee -a "$LOG"
if wp plugin install "$ZIP" --force $( [[ "$ACT" == "true" ]] && echo --activate ); then local args=(plugin install "$ZIP" --force)
[[ "$ACT" == "true" ]] && args+=(--activate)
if invoke_wp "${args[@]}"; then
echo "OK zip: $ZIP (activate=$ACT)" | tee -a "$LOG" echo "OK zip: $ZIP (activate=$ACT)" | tee -a "$LOG"
else else
echo "FAIL zip: $ZIP" | tee -a "$LOG" echo "FAIL zip: $ZIP" | tee -a "$LOG"
fi fi
elif [[ -n "$SLUG" ]]; then elif [[ -n "$SLUG" ]]; then
ARGS=(plugin install "$SLUG" --force) local args=(plugin install "$SLUG" --force)
[[ -n "$VER" ]] && ARGS+=("--version=$VER") [[ -n "$VER" ]] && args+=("--version=$VER")
[[ "$ACT" == "true" ]] && ARGS+=("--activate") [[ "$ACT" == "true" ]] && args+=(--activate)
echo "Installing slug: $SLUG ${VER:+(v$VER)}" | tee -a "$LOG" echo "Installing slug: $SLUG ${VER:+(v$VER)}" | tee -a "$LOG"
if wp "${ARGS[@]}"; then if invoke_wp "${args[@]}"; then
echo "OK slug: $SLUG (activate=$ACT)" | tee -a "$LOG" echo "OK slug: $SLUG (activate=$ACT)" | tee -a "$LOG"
else else
echo "FAIL slug: $SLUG" | tee -a "$LOG" echo "FAIL slug: $SLUG" | tee -a "$LOG"
@@ -114,7 +172,7 @@ install_and_activate_plugins() {
fi fi
done done
else else
echo "plugins.json found but jq is not installed; skipping plugin install." | tee -a "$LOG" echo "$pretty_path found but jq is not installed; skipping plugin install." | tee -a "$LOG"
fi fi
else else
echo "No plugins.json present; skipping plugin install." | tee -a "$LOG" echo "No plugins.json present; skipping plugin install." | tee -a "$LOG"
@@ -123,8 +181,81 @@ install_and_activate_plugins() {
echo "=== $(date -u '+%F %T') :: Plugin bootstrap end ===" | tee -a "$LOG" echo "=== $(date -u '+%F %T') :: Plugin bootstrap end ===" | tee -a "$LOG"
} }
echo "— VDI WP Bootstrap (Herd + DBngin) —" PROJECT_NAME_CLI=""
PROJECT_NAME="$(prompt 'Project name (Human-readable)')" ADMIN_USER_CLI=""
ADMIN_EMAIL_CLI=""
THEME_STARTER_REPO_FLAG=0
while [[ $# -gt 0 ]]; do
case "$1" in
--project-name)
[[ $# -lt 2 ]] && { echo "Missing value for --project-name" >&2; usage >&2; exit 1; }
PROJECT_NAME_CLI="$2"
shift 2
;;
--admin-user)
[[ $# -lt 2 ]] && { echo "Missing value for --admin-user" >&2; usage >&2; exit 1; }
ADMIN_USER_CLI="$2"
shift 2
;;
--admin-email)
[[ $# -lt 2 ]] && { echo "Missing value for --admin-email" >&2; usage >&2; exit 1; }
ADMIN_EMAIL_CLI="$2"
shift 2
;;
--theme-starter-repo)
[[ $# -lt 2 ]] && { echo "Missing value for --theme-starter-repo" >&2; usage >&2; exit 1; }
THEME_STARTER_REPO="$2"
THEME_STARTER_REPO_FLAG=1
shift 2
;;
--herd-workspace)
[[ $# -lt 2 ]] && { echo "Missing value for --herd-workspace" >&2; usage >&2; exit 1; }
HERD_WORKSPACE="$2"
shift 2
;;
--local-tld)
[[ $# -lt 2 ]] && { echo "Missing value for --local-tld" >&2; usage >&2; exit 1; }
LOCAL_TLD="$2"
shift 2
;;
--mysql-host)
[[ $# -lt 2 ]] && { echo "Missing value for --mysql-host" >&2; usage >&2; exit 1; }
MYSQL_HOST="$2"
shift 2
;;
--mysql-port)
[[ $# -lt 2 ]] && { echo "Missing value for --mysql-port" >&2; usage >&2; exit 1; }
MYSQL_PORT="$2"
shift 2
;;
--mysql-root-user)
[[ $# -lt 2 ]] && { echo "Missing value for --mysql-root-user" >&2; usage >&2; exit 1; }
MYSQL_ROOT_USER="$2"
shift 2
;;
--mysql-root-pass)
[[ $# -lt 2 ]] && { echo "Missing value for --mysql-root-pass" >&2; usage >&2; exit 1; }
MYSQL_ROOT_PASS="$2"
shift 2
;;
--help|-h)
usage
exit 0
;;
*)
echo "Unknown option: $1" >&2
usage >&2
exit 1
;;
esac
done
echo "VDI WP Bootstrap (Herd + DBngin)"
PROJECT_NAME="${PROJECT_NAME_CLI:-}"
if [[ -z "$PROJECT_NAME" ]]; then
PROJECT_NAME="$(prompt 'Project name (Human-readable)')"
fi
[[ -z "$PROJECT_NAME" ]] && { echo "Project name is required."; exit 1; } [[ -z "$PROJECT_NAME" ]] && { echo "Project name is required."; exit 1; }
PROJECT_SLUG="$(slugify "$PROJECT_NAME")" PROJECT_SLUG="$(slugify "$PROJECT_NAME")"
@@ -138,70 +269,115 @@ echo " path: $PROJECT_DIR"
echo " local URL: $LOCAL_URL" echo " local URL: $LOCAL_URL"
echo echo
ADMIN_USER="$(prompt 'Admin username' "$DEFAULT_ADMIN_USER")" if [[ -n "$ADMIN_USER_CLI" ]]; then
ADMIN_EMAIL="$(prompt 'Admin email' "$DEFAULT_ADMIN_EMAIL")" ADMIN_USER="$ADMIN_USER_CLI"
else
ADMIN_USER="$(prompt 'Admin username' "$DEFAULT_ADMIN_USER")"
fi
if [[ -n "$ADMIN_EMAIL_CLI" ]]; then
ADMIN_EMAIL="$ADMIN_EMAIL_CLI"
else
ADMIN_EMAIL="$(prompt 'Admin email' "$DEFAULT_ADMIN_EMAIL")"
fi
ADMIN_PASS="$(randpass)" ADMIN_PASS="$(randpass)"
DB_NAME="vdi_${PROJECT_SLUG}" DB_NAME="vdi_${PROJECT_SLUG}"
DB_USER="$DB_NAME" DB_USER="$DB_NAME"
DB_PASS="$(randpass)" DB_PASS="$(randpass)"
THEME_REPO_URL="$(prompt 'Theme starter repo URL' "$THEME_STARTER_REPO")" if (( THEME_STARTER_REPO_FLAG )); then
THEME_REPO_URL="$THEME_STARTER_REPO"
else
THEME_REPO_URL="$(prompt 'Theme starter repo URL' "$THEME_STARTER_REPO")"
fi
THEME_DIR="wp-content/themes/${PROJECT_SLUG}-theme" 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")" THEME_REMOTE_ORIGIN="$(prompt 'New theme repo remote (origin) URL (leave blank to skip push)' "$DEFAULT_THEME_REMOTE_ORIGIN")"
echo echo
echo "Creating project directory" echo "Creating project directory..."
mkdir -p "$PROJECT_DIR" mkdir -p "$PROJECT_DIR"
cd "$PROJECT_DIR" cd "$PROJECT_DIR"
echo "Downloading WordPress core" echo "Downloading WordPress core..."
wp core download --force invoke_wp core download --force
echo "Creating database and user in MySQL ($MYSQL_HOST:$MYSQL_PORT)" echo "Creating database and user in MySQL ($MYSQL_HOST:$MYSQL_PORT)..."
MYSQL_AUTH=(-h "$MYSQL_HOST" -P "$MYSQL_PORT" -u "$MYSQL_ROOT_USER") MYSQL_AUTH=(-h "$MYSQL_HOST" -P "$MYSQL_PORT" -u "$MYSQL_ROOT_USER")
if [[ -n "$MYSQL_ROOT_PASS" ]]; then if [[ -n "$MYSQL_ROOT_PASS" ]]; then
MYSQL_AUTH+=(-p"$MYSQL_ROOT_PASS") MYSQL_AUTH+=(-p"$MYSQL_ROOT_PASS")
fi fi
if ! mysql "${MYSQL_AUTH[@]}" -e "SELECT VERSION();" >/dev/null 2>&1; then
echo "Cannot connect to MySQL as root (${MYSQL_ROOT_USER}@${MYSQL_HOST}:${MYSQL_PORT})." >&2
exit 1
fi
mysql "${MYSQL_AUTH[@]}" <<SQL mysql "${MYSQL_AUTH[@]}" <<SQL
CREATE DATABASE IF NOT EXISTS \`$DB_NAME\` CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; CREATE DATABASE IF NOT EXISTS \`$DB_NAME\` CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
CREATE USER IF NOT EXISTS '$DB_USER'@'localhost' IDENTIFIED BY '$DB_PASS';
CREATE USER IF NOT EXISTS '$DB_USER'@'127.0.0.1' IDENTIFIED BY '$DB_PASS';
CREATE USER IF NOT EXISTS '$DB_USER'@'%' IDENTIFIED BY '$DB_PASS'; CREATE USER IF NOT EXISTS '$DB_USER'@'%' IDENTIFIED BY '$DB_PASS';
GRANT ALL PRIVILEGES ON \`$DB_NAME\`.* TO '$DB_USER'@'localhost';
GRANT ALL PRIVILEGES ON \`$DB_NAME\`.* TO '$DB_USER'@'127.0.0.1';
GRANT ALL PRIVILEGES ON \`$DB_NAME\`.* TO '$DB_USER'@'%'; GRANT ALL PRIVILEGES ON \`$DB_NAME\`.* TO '$DB_USER'@'%';
FLUSH PRIVILEGES; FLUSH PRIVILEGES;
SQL SQL
echo "Generating wp-config.php…" if ! mysql -h "$MYSQL_HOST" -P "$MYSQL_PORT" -u "$DB_USER" -p"$DB_PASS" -D "$DB_NAME" -e "SELECT 1;" >/dev/null 2>&1; then
wp config create \ echo "New MySQL user '$DB_USER' cannot access database '$DB_NAME' at ${MYSQL_HOST}:${MYSQL_PORT}." >&2
exit 1
fi
echo "Generating wp-config.php..."
invoke_wp config create \
--dbname="$DB_NAME" \ --dbname="$DB_NAME" \
--dbuser="$DB_USER" \ --dbuser="$DB_USER" \
--dbpass="$DB_PASS" \ --dbpass="$DB_PASS" \
--dbhost="${MYSQL_HOST}:${MYSQL_PORT}" \ --dbhost="${MYSQL_HOST}:${MYSQL_PORT}" \
--force --force
if wp config shuffle-salts >/dev/null 2>&1; then if invoke_wp config shuffle-salts >/dev/null 2>&1; then
echo "Salt keys shuffled." echo "Salt keys shuffled."
else else
echo "Fetching salts from api.wordpress.org" echo "Fetching salts from api.wordpress.org..."
SALTS="$(curl -fsSL https://api.wordpress.org/secret-key/1.1/salt/)" 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 invoke_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");' "$PHP_BIN" -r 'file_put_contents("wp-config.php", preg_replace("/\\?>\\s*$/","",file_get_contents("wp-config.php"))."\n".'"'"$SALTS"'"'."\n");'
fi fi
echo "Installing WordPress" echo "Installing WordPress..."
wp core install \ invoke_wp core install \
--url="$LOCAL_URL" \ --url="$LOCAL_URL" \
--title="$PROJECT_NAME" \ --title="$PROJECT_NAME" \
--admin_user="$ADMIN_USER" \ --admin_user="$ADMIN_USER" \
--admin_password="$ADMIN_PASS" \ --admin_password="$ADMIN_PASS" \
--admin_email="$ADMIN_EMAIL" --admin_email="$ADMIN_EMAIL"
wp option update siteurl "$LOCAL_URL" invoke_wp option update siteurl "$LOCAL_URL"
wp option update home "$LOCAL_URL" invoke_wp option update home "$LOCAL_URL"
setup_pages_and_reading setup_pages_and_reading
echo "Cloning theme starter (history will be stripped)…" HTACCESS_FILE="$PROJECT_DIR/.htaccess"
if [[ ! -f "$HTACCESS_FILE" ]]; then
cat >"$HTACCESS_FILE" <<'HTACCESS'
# BEGIN WordPress
<IfModule mod_rewrite.c>
RewriteEngine On
RewriteBase /
RewriteRule ^index\.php$ - [L]
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule . /index.php [L]
</IfModule>
# END WordPress
HTACCESS
echo "Created default .htaccess"
fi
echo "Cloning theme starter (history will be stripped)..."
TMP_DIR=".starter-tmp" TMP_DIR=".starter-tmp"
rm -rf "$TMP_DIR" rm -rf "$TMP_DIR"
git clone --depth=1 "$THEME_REPO_URL" "$TMP_DIR" git clone --depth=1 "$THEME_REPO_URL" "$TMP_DIR"
@@ -217,15 +393,22 @@ if [[ -f "$THEME_DIR/style.css" ]]; then
rm -f "$THEME_DIR/style.css.bak" rm -f "$THEME_DIR/style.css.bak"
fi fi
echo "Activating theme" echo "Installing theme dependencies and building assets..."
wp theme activate "${PROJECT_SLUG}-theme" || { pushd "$THEME_DIR" >/dev/null
composer install
npm install
npm run build
popd >/dev/null
echo "Activating theme..."
invoke_wp theme activate "${PROJECT_SLUG}-theme" || {
echo "Activation failed (maybe theme slug mismatch). Listing themes:" echo "Activation failed (maybe theme slug mismatch). Listing themes:"
wp theme list invoke_wp theme list
} }
install_and_activate_plugins install_and_activate_plugins
echo "Initializing theme repo" echo "Initializing theme repo..."
pushd "$THEME_DIR" >/dev/null pushd "$THEME_DIR" >/dev/null
git init -b main git init -b main
git add -A git add -A