Compare commits
10 Commits
523a29d20e
...
e73abc1100
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e73abc1100 | ||
|
|
1f12dff4d2 | ||
|
|
06e4007b5e | ||
|
|
e100a2a709 | ||
|
|
605b14f169 | ||
|
|
fd3d14e37b | ||
|
|
0cdb5eb420 | ||
|
|
3c5d13cb24 | ||
|
|
f19c016a09 | ||
|
|
65213b8962 |
1
.gitignore
vendored
1
.gitignore
vendored
@@ -1,3 +1,4 @@
|
||||
.vscode/
|
||||
notes
|
||||
.wp-bootstraprc
|
||||
windows/plugins.json
|
||||
|
||||
95
README.md
95
README.md
@@ -1,67 +1,90 @@
|
||||
# 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
|
||||
- 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
|
||||
- Project directory under your Herd workspace with sanitized slug and host mapping.
|
||||
- MySQL database + user created after verifying the supplied root credentials.
|
||||
- Latest WordPress core, `wp-config.php` with fresh salts, permalinks set to `/%postname%/`, and Home/News pages.
|
||||
- Plugin install/activation from `plugins.json`, resolved from the project folder first then the repository default.
|
||||
- Starter theme cloned with shallow history, `.git` stripped, repo reinitialized, remote optionally set, and Composer/NPM build steps run when available.
|
||||
- `.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
|
||||
|
||||
- macOS, Linux, or [Windows](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`
|
||||
- macOS with [Laravel Herd](https://herd.laravel.com/) configured (default workspace `~/Herd`).
|
||||
- MySQL or MariaDB available locally (DBngin recommended) and the `mysql` client in `PATH`.
|
||||
- CLI tools: `php`, `git`, `curl`, `openssl`, `composer`, `npm`, `mysql`, and optionally `jq` for pretty plugin output.
|
||||
- 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
|
||||
cp wp-bootstraprc.example ~/.wp-bootstraprc
|
||||
```
|
||||
|
||||
2. (Optional) Edit `plugins.json`.
|
||||
|
||||
3. Run the bootstrap:
|
||||
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:
|
||||
|
||||
```bash
|
||||
chmod +x 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`.
|
||||
- **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.
|
||||
Pass values via flags (help output lists every option):
|
||||
|
||||
## 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.
|
||||
- Sample content is removed if present.
|
||||
- A plugin install log is written to `wp-content/plugin-bootstrap.log`.
|
||||
- `HERD_WORKSPACE`, `LOCAL_TLD`
|
||||
- `MYSQL_HOST`, `MYSQL_PORT`, `MYSQL_ROOT_USER`, `MYSQL_ROOT_PASS`
|
||||
- `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
|
||||
|
||||
- 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.
|
||||
- **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`.
|
||||
- **MySQL connection failures** – confirm DBngin (or your server) is running, credentials match your `.wp-bootstraprc`, and the root user can create databases/users.
|
||||
- **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.
|
||||
|
||||
@@ -7,10 +7,9 @@
|
||||
"zip": "https://docs.vincentdevelopment.ca/files/gravity-forms.zip",
|
||||
"activate": true
|
||||
},
|
||||
|
||||
{
|
||||
"slug": "autodescription",
|
||||
"activate": true
|
||||
"activate": false
|
||||
},
|
||||
{
|
||||
"slug": "better-search-replace",
|
||||
|
||||
@@ -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
|
||||
- `bootstrap.bat` — one-click launcher
|
||||
- `plugins.json` — minimal default plugin list (use file from repo root)
|
||||
- `wp-bootstrap.ps1` – main PowerShell script.
|
||||
- `wp-cli.phar` – bundled WP-CLI used by the script when `wp` is not installed globally.
|
||||
- `plugins.json` – default plugin manifest; copy/override per project if you need a different set.
|
||||
|
||||
## 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"`
|
||||
- Windows 10/11 with PowerShell 7 (`pwsh`) recommended.
|
||||
- [Laravel Herd for Windows](https://herd.laravel.com/) installed so PHP is available (the script auto-detects Herd’s `php.exe`).
|
||||
- DBngin or another MySQL/MariaDB server running locally, with `mysql.exe` available in `PATH`.
|
||||
- Git for cloning the starter theme.
|
||||
- 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
|
||||
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
|
||||
.\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`)
|
||||
- `-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)
|
||||
From this directory, run:
|
||||
|
||||
## What it does
|
||||
```powershell
|
||||
pwsh -File .\wp-bootstrap.ps1 -ProjectName "Client Site"
|
||||
```
|
||||
|
||||
- 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`)
|
||||
Optional parameters (all may also be supplied via `~\.wp-bootstraprc`):
|
||||
|
||||
- `-AdminUser`
|
||||
- `-AdminEmail`
|
||||
- `-ThemeStarterRepo`
|
||||
- `-HerdWorkspace`
|
||||
- `-LocalTld`
|
||||
- `-MysqlHost`
|
||||
- `-MysqlPort`
|
||||
- `-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
|
||||
|
||||
- **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).
|
||||
- **WP-CLI** – ensure `wp-cli.phar` lives beside the script or set `WP_CLI_PHAR` in `~\.wp-bootstraprc` to an absolute path.
|
||||
- **MySQL connectivity** – verify the server is running, your root user matches the rc values, and Windows Firewall allows the connection.
|
||||
- **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.
|
||||
- **Permissions** – confirm Herd has access to the project folder and that the execution policy change succeeded.
|
||||
|
||||
## Philosophy
|
||||
|
||||
DIY-first: keep `plugins.json` minimal. Only add big off‑the‑shelf stacks (e.g., ecomm) when warranted.
|
||||
---
|
||||
DIY-first policy: keep `plugins.json` lean; add heavier stacks only when the project truly needs them.
|
||||
|
||||
@@ -1,4 +0,0 @@
|
||||
@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 "%*"
|
||||
@@ -1,155 +1,346 @@
|
||||
# Easy WP Bootstrap — Windows (PowerShell + Herd) — local wp-cli.phar via php.exe
|
||||
|
||||
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"
|
||||
[string]$AdminUser,
|
||||
[string]$AdminEmail,
|
||||
[string]$ThemeStarterRepo,
|
||||
[string]$HerdWorkspace,
|
||||
[string]$LocalTld,
|
||||
[string]$MysqlHost,
|
||||
[string]$MysqlPort,
|
||||
[string]$MysqlRootUser,
|
||||
[string]$MysqlRootPass
|
||||
)
|
||||
|
||||
$ErrorActionPreference = 'Stop'
|
||||
$env:WP_CLI_STRICT_ARGS_MODE = '1'
|
||||
|
||||
# ---- Load rc (CLI > rc > hard default) ----
|
||||
$RcFile = Join-Path $HOME ".wp-bootstraprc"
|
||||
$Rc = @{}
|
||||
if (Test-Path $RcFile) {
|
||||
Get-Content $RcFile | ForEach-Object {
|
||||
$line = $_.Trim()
|
||||
if ($line -match '^\s*#' -or $line -match '^\s*$') { return }
|
||||
if ($line -match '^\s*([^=]+)=(.+)$') {
|
||||
$key = $matches[1].Trim()
|
||||
$val = $matches[2].Trim('"'' ')
|
||||
$val = $val -replace '\$HOME', [Regex]::Escape($HOME)
|
||||
$val = $val -replace '^~', [Regex]::Escape($HOME)
|
||||
$val = $val -replace '\\\\','\'
|
||||
$Rc[$key] = $val
|
||||
}
|
||||
}
|
||||
}
|
||||
function RcOr([string]$Key,[string]$Fallback){ if($Rc.ContainsKey($Key) -and $Rc[$Key]){$Rc[$Key]}else{$Fallback} }
|
||||
|
||||
if (-not $AdminUser) { $AdminUser = RcOr "DEFAULT_ADMIN_USER" "vdidev" }
|
||||
if (-not $AdminEmail) { $AdminEmail = RcOr "DEFAULT_ADMIN_EMAIL" "dev@vincentdesign.ca" }
|
||||
if (-not $ThemeStarterRepo) { $ThemeStarterRepo = RcOr "THEME_STARTER_REPO" "https://github.com/WordPress/twentytwentyfour" }
|
||||
if (-not $HerdWorkspace) { $HerdWorkspace = RcOr "HERD_WORKSPACE" "$HOME\Herd" }
|
||||
if (-not $LocalTld) { $LocalTld = RcOr "LOCAL_TLD" "test" }
|
||||
if (-not $MysqlHost) { $MysqlHost = RcOr "MYSQL_HOST" "127.0.0.1" }
|
||||
if (-not $MysqlPort) { $MysqlPort = RcOr "MYSQL_PORT" "3306" }
|
||||
if (-not $MysqlRootUser) { $MysqlRootUser = RcOr "MYSQL_ROOT_USER" "root" }
|
||||
if (-not $MysqlRootPass) { $MysqlRootPass = RcOr "MYSQL_ROOT_PASS" "" }
|
||||
|
||||
# ---- Helpers ----
|
||||
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) }
|
||||
$b = New-Object 'System.Byte[]' 48
|
||||
(New-Object System.Security.Cryptography.RNGCryptoServiceProvider).GetBytes($b)
|
||||
$raw = [Convert]::ToBase64String($b) -replace '[^a-zA-Z0-9]',''
|
||||
while($raw.Length -lt 24){
|
||||
$b2 = New-Object 'System.Byte[]' 16
|
||||
(New-Object System.Security.Cryptography.RNGCryptoServiceProvider).GetBytes($b2)
|
||||
$raw += ([Convert]::ToBase64String($b2) -replace '[^a-zA-Z0-9]','')
|
||||
}
|
||||
$raw.Substring(0,24)
|
||||
}
|
||||
|
||||
# Resolve script folder + absolute phar
|
||||
$ScriptRoot = if ($PSScriptRoot) { $PSScriptRoot } else { Split-Path -Parent $MyInvocation.MyCommand.Definition }
|
||||
|
||||
$PharFullPath = Join-Path $ScriptRoot 'wp-cli.phar'
|
||||
|
||||
if (!(Test-Path $PharFullPath)) { throw "wp-cli.phar not found at: $PharFullPath" }
|
||||
|
||||
# Resolve Herd php.exe (parse php.bat or derive from php -v → $HOME\.config\herd\bin\<ver>\php.exe)
|
||||
function Resolve-PhpExe {
|
||||
$phpCmd = Get-Command php -ErrorAction SilentlyContinue
|
||||
if ($phpCmd -and ($phpCmd.Source -match '\.bat$') -and (Test-Path $phpCmd.Source)) {
|
||||
$bat = Get-Content -Raw $phpCmd.Source
|
||||
$m = [regex]::Match($bat, '(["'']?)(%[^"''\r\n]+%|[A-Za-z]:\\[^"''\r\n]+?\\php\.exe)\1')
|
||||
if ($m.Success) {
|
||||
$raw = $m.Groups[2].Value
|
||||
$exp = [Environment]::ExpandEnvironmentVariables($raw)
|
||||
if (Test-Path $exp) { return $exp }
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
$verOut = & cmd.exe /d /s /c 'php -v' 2>&1
|
||||
$verLine = ($verOut | Select-Object -First 1)
|
||||
$m2 = [regex]::Match($verLine, 'PHP\s+(\d+\.\d+\.\d+)')
|
||||
if ($m2.Success) {
|
||||
$ver = $m2.Groups[1].Value
|
||||
$cand = Join-Path $HOME ".config\herd\bin\$ver\php.exe"
|
||||
if (Test-Path $cand) { return $cand }
|
||||
}
|
||||
} catch {}
|
||||
$whereExe = (& where.exe php 2>$null) -split "`r?`n" | Where-Object { $_ -match '\.exe$' } | Select-Object -First 1
|
||||
if ($whereExe -and (Test-Path $whereExe)) { return $whereExe }
|
||||
throw "Could not resolve a real php.exe (expected like $HOME\.config\herd\bin\<ver>\php.exe)."
|
||||
}
|
||||
|
||||
$PhpExe = Resolve-PhpExe
|
||||
|
||||
# ---- WP-CLI wrapper: space-safe (runs php from script folder) ----
|
||||
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 { "$_" }
|
||||
[CmdletBinding(PositionalBinding=$false)]
|
||||
param(
|
||||
[Parameter(ValueFromRemainingArguments=$true)]
|
||||
[string[]]$WpArgs
|
||||
)
|
||||
|
||||
# WordPress installation directory (do not change)
|
||||
$wpPath = ((Get-Location).Path) -replace '\\','/'
|
||||
|
||||
# Always run PHP from the folder that contains wp-cli.phar to avoid spaces in the script path
|
||||
$orig = Get-Location
|
||||
try {
|
||||
Push-Location $ScriptRoot
|
||||
|
||||
# Use -f and -- to separate PHP options from WP-CLI args
|
||||
# Format executed: php -f wp-cli.phar -- --path=<project-dir> <wp-cli-args…>
|
||||
$all = @("-f", "wp-cli.phar", "--", "--path=$wpPath") + $WpArgs
|
||||
|
||||
# DEBUG (optional): uncomment to see exactly what's executed
|
||||
# Write-Host ">> $PhpExe $($all -join ' ') (cwd=$((Get-Location).Path))"
|
||||
|
||||
$out = & $PhpExe @all 2>&1
|
||||
|
||||
# DEBUG (optional): uncomment to see full output
|
||||
# $out | Out-Host
|
||||
|
||||
if ($LASTEXITCODE -ne 0) {
|
||||
throw "WP-CLI failed (exit $LASTEXITCODE): $PhpExe $($all -join ' ') (cwd=$((Get-Location).Path))"
|
||||
}
|
||||
return $out
|
||||
}
|
||||
finally {
|
||||
Pop-Location | Out-Null
|
||||
}
|
||||
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 ===
|
||||
# ---- Compute basics ----
|
||||
$Slug = Slugify $ProjectName
|
||||
$ProjectDir = Join-Path $HerdWorkspace $Slug
|
||||
$LocalUrl = "http://$Slug.$LocalTld"
|
||||
|
||||
$DbName = "vdi_$Slug"
|
||||
$DbName = "wp_$Slug"
|
||||
$DbUser = $DbName
|
||||
$DbPass = RandPass
|
||||
$AdminPass = RandPass
|
||||
|
||||
Write-Host "Creating project at $ProjectDir..."
|
||||
Write-Host "Creating project at $ProjectDir...`n"
|
||||
New-Item -ItemType Directory -Force -Path $ProjectDir | Out-Null
|
||||
Set-Location $ProjectDir
|
||||
|
||||
# --- WordPress Core ---
|
||||
Write-Host "Downloading WordPress..."
|
||||
Invoke-WP @("core","download","--force")
|
||||
# Sanity print
|
||||
$pathShown = ((Get-Location).Path -replace '\\','/')
|
||||
Write-Host "WP-CLI php: $PhpExe"
|
||||
Write-Host "WP-CLI phar: $PharFullPath"
|
||||
Write-Host "WP-CLI --path: --path=$pathShown"
|
||||
Invoke-WP "cli" "version"
|
||||
|
||||
# --- 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."
|
||||
# ---- WP Core ----
|
||||
Write-Host "`nDownloading WordPress core..."
|
||||
Invoke-WP "core" "download" "--force"
|
||||
|
||||
# ---------- Database (DBngin/MySQL) ----------
|
||||
Write-Host "`nCreating MySQL DB and user..."
|
||||
|
||||
# Build root arg list
|
||||
$mysqlArgsRoot = @("-h", $MysqlHost, "-P", $MysqlPort, "-u", $MysqlRootUser)
|
||||
if ($MysqlRootPass -ne "") { $mysqlArgsRoot += "-p$MysqlRootPass" }
|
||||
|
||||
# Sanity: confirm root can connect
|
||||
$null = & mysql.exe @mysqlArgsRoot -e "SELECT VERSION();" 2>$null
|
||||
if ($LASTEXITCODE -ne 0) {
|
||||
throw ("Cannot connect to MySQL as root ({0}@{1}:{2}). Check MYSQL_ROOT_* in your rc file." -f $MysqlRootUser, $MysqlHost, $MysqlPort)
|
||||
}
|
||||
|
||||
# --- 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)
|
||||
# Build SQL without any escaping sequences in PowerShell strings.
|
||||
# Use a real backtick char for MySQL identifiers:
|
||||
$bt = [char]96 # `
|
||||
$dbIdent = "$bt$DbName$bt"
|
||||
|
||||
# --- Pages & Reading ---
|
||||
$sqlParts = @(
|
||||
"CREATE DATABASE IF NOT EXISTS $dbIdent CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;",
|
||||
"",
|
||||
"CREATE USER IF NOT EXISTS '$DbUser'@'localhost' IDENTIFIED BY '$DbPass';",
|
||||
"CREATE USER IF NOT EXISTS '$DbUser'@'127.0.0.1' IDENTIFIED BY '$DbPass';",
|
||||
"CREATE USER IF NOT EXISTS '$DbUser'@'%' IDENTIFIED BY '$DbPass';",
|
||||
"",
|
||||
"GRANT ALL PRIVILEGES ON $dbIdent.* TO '$DbUser'@'localhost';",
|
||||
"GRANT ALL PRIVILEGES ON $dbIdent.* TO '$DbUser'@'127.0.0.1';",
|
||||
"GRANT ALL PRIVILEGES ON $dbIdent.* TO '$DbUser'@'%';",
|
||||
"FLUSH PRIVILEGES;"
|
||||
)
|
||||
|
||||
$tmpSql = [System.IO.Path]::GetTempFileName()
|
||||
[System.IO.File]::WriteAllLines($tmpSql, $sqlParts, [System.Text.Encoding]::UTF8)
|
||||
|
||||
# Feed the SQL file to mysql via cmd redirection (reliable on Windows)
|
||||
function Quote-ForCmd([string]$s){ '"' + ($s -replace '"','""') + '"' }
|
||||
|
||||
$cmdLine = "mysql.exe " + (
|
||||
($mysqlArgsRoot | ForEach-Object { Quote-ForCmd $_ }) -join ' '
|
||||
) + " < " + (Quote-ForCmd $tmpSql)
|
||||
|
||||
# DEBUG (optional): uncomment to see exactly what's run
|
||||
# Write-Host ">> $cmdLine"
|
||||
|
||||
& cmd.exe /d /s /c $cmdLine
|
||||
$exit = $LASTEXITCODE
|
||||
|
||||
# Clean up the temp file regardless
|
||||
Remove-Item -Force $tmpSql -ErrorAction SilentlyContinue
|
||||
|
||||
if ($exit -ne 0) {
|
||||
throw "MySQL SQL error while creating DB/user. Verify root creds and server."
|
||||
}
|
||||
|
||||
# Verify the NEW user can actually select the DB (fail fast if not)
|
||||
$mysqlArgsUser = @("-h", $MysqlHost, "-P", $MysqlPort, "-u", $DbUser, "-p$DbPass", "-D", $DbName)
|
||||
$null = & mysql.exe @mysqlArgsUser -e "SELECT 1;" 2>$null
|
||||
if ($LASTEXITCODE -ne 0) {
|
||||
throw ("New MySQL user cannot access DB '{0}' via {1}:{2}. Host match or privileges issue — check users and grants." -f $DbName, $MysqlHost, $MysqlPort)
|
||||
}
|
||||
|
||||
# ---- 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")
|
||||
$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..."
|
||||
# --- Ensure .htaccess exists (for Apache/permalinks) ---
|
||||
$htFile = Join-Path $ProjectDir ".htaccess"
|
||||
if (!(Test-Path $htFile)) {
|
||||
$ht = @"
|
||||
# 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
|
||||
"@
|
||||
Set-Content -Path $htFile -Value $ht -Encoding ASCII
|
||||
Write-Host "`nCreated default .htaccess"
|
||||
}
|
||||
|
||||
# ---- Theme ----
|
||||
Write-Host "`nCloning 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"
|
||||
$ThemeDir = "wp-content\themes\$Slug"
|
||||
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")
|
||||
# --- Stamp Theme Name + Text Domain into style.css ---
|
||||
$styleCss = Join-Path $ThemeDir "style.css"
|
||||
$TextDomain = "$Slug" # matches your theme folder name
|
||||
if (Test-Path $styleCss) {
|
||||
$css = Get-Content $styleCss -Raw
|
||||
|
||||
# Replace existing "Theme Name:" line (supports plain or "* Theme Name:" formats)
|
||||
$css = [regex]::Replace(
|
||||
$css,
|
||||
'(?im)^(?:\s*\*\s*)?Theme\s+Name\s*:\s*.*$',
|
||||
" * Theme Name: $ProjectName"
|
||||
)
|
||||
|
||||
Set-Content -Path $styleCss -Value $css -Encoding UTF8
|
||||
Write-Host "`nUpdated style.css header with Theme Name: $ProjectName`n"
|
||||
}
|
||||
|
||||
Invoke-WP "theme" "activate" "$Slug"
|
||||
|
||||
Push-Location $ThemeDir
|
||||
# ---- Init theme dependencies and build Tailwind ----
|
||||
Write-Host "`nInstalling theme dependencies and building assets..."
|
||||
composer install 2>$null
|
||||
npm install 2>$null
|
||||
npm run build 2>$null
|
||||
|
||||
# ---- Init new git repo ----
|
||||
Write-Host "`nInitializing new git repo for theme..."
|
||||
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") {
|
||||
# ---------- Plugins (optional) ----------
|
||||
# Look for plugins.json in the project, then fall back to script folder
|
||||
$PluginsFileProject = Join-Path $ProjectDir "plugins.json"
|
||||
$PluginsFileScript = Join-Path $ScriptRoot "plugins.json"
|
||||
$PluginsFile = if (Test-Path $PluginsFileProject) { $PluginsFileProject }
|
||||
elseif (Test-Path $PluginsFileScript) { $PluginsFileScript }
|
||||
else { $null }
|
||||
|
||||
if ($PluginsFile) {
|
||||
try {
|
||||
$plugins = Get-Content "plugins.json" | ConvertFrom-Json
|
||||
Write-Host "`nInstalling plugins from $PluginsFile..."
|
||||
$plugins = Get-Content $PluginsFile -Raw | 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
|
||||
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
|
||||
Invoke-WP @args
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
Write-Host "Error reading plugins.json: $_"
|
||||
Write-Host "`nPlugin install error: $_"
|
||||
}
|
||||
} else {
|
||||
Write-Host "`nNo plugins.json found in project or script folder; skipping plugins."
|
||||
}
|
||||
|
||||
# --- Summary ---
|
||||
# ---- Summary ----
|
||||
$Summary = @"
|
||||
VDI WP Bootstrap — Summary
|
||||
Easy WP Bootstrap — Summary
|
||||
===========================
|
||||
|
||||
Project: $ProjectName
|
||||
Slug: $Slug
|
||||
Folder: $ProjectDir
|
||||
Local URL: $LocalUrl
|
||||
Theme Dir: $ThemeDir
|
||||
|
||||
DB Host: $MysqlHost
|
||||
DB Port: $MysqlPort
|
||||
@@ -157,13 +348,17 @@ DB Name: $DbName
|
||||
DB User: $DbUser
|
||||
DB Pass: $DbPass
|
||||
|
||||
Local URL: $LocalUrl
|
||||
Local Admin URL: $LocalUrl/wp-admin/
|
||||
|
||||
WP Admin Email: $AdminEmail
|
||||
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"
|
||||
|
||||
Write-Host "`n`n$Summary"
|
||||
Write-Host "`nSaved summary to $SummaryFile"
|
||||
|
||||
BIN
windows/wp-cli.phar
Normal file
BIN
windows/wp-cli.phar
Normal file
Binary file not shown.
279
wp-bootstrap.sh
279
wp-bootstrap.sh
@@ -1,10 +1,13 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
export WP_CLI_STRICT_ARGS_MODE=1
|
||||
|
||||
# Load machine/user defaults
|
||||
if [[ -f "$HOME/.vdi-wp-bootstraprc" ]]; then
|
||||
if [[ -f "$HOME/.wp-bootstraprc" ]]; then
|
||||
# shellcheck disable=SC1090
|
||||
source "$HOME/.vdi-wp-bootstraprc"
|
||||
source "$HOME/.wp-bootstraprc"
|
||||
fi
|
||||
|
||||
# Sensible fallbacks if config missing
|
||||
@@ -23,9 +26,52 @@ require() {
|
||||
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"
|
||||
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() {
|
||||
# lower, spaces->-, strip non [a-z0-9-]
|
||||
@@ -50,33 +96,33 @@ prompt() {
|
||||
}
|
||||
|
||||
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
|
||||
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
|
||||
|
||||
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
|
||||
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
|
||||
|
||||
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
|
||||
ID=$(invoke_wp post list --post_type=page --name="$SLUG" --field=ID --format=ids)
|
||||
[[ -z "$ID" ]] && invoke_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"
|
||||
invoke_wp option update show_on_front 'page'
|
||||
invoke_wp option update page_on_front "$HOME_ID"
|
||||
invoke_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
|
||||
invoke_wp post delete 1 --force >/dev/null 2>&1 || true
|
||||
invoke_wp post delete 2 --force >/dev/null 2>&1 || true
|
||||
|
||||
wp rewrite structure '/%postname%/'
|
||||
wp rewrite flush --hard
|
||||
invoke_wp rewrite structure '/%postname%/'
|
||||
invoke_wp rewrite flush --hard
|
||||
echo "Default pages and reading options configured."
|
||||
}
|
||||
|
||||
@@ -84,9 +130,19 @@ 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
|
||||
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
|
||||
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')
|
||||
SLUG=$(echo "$item" | jq -r '.slug // empty')
|
||||
VER=$(echo "$item" | jq -r '.version // empty')
|
||||
@@ -94,17 +150,19 @@ install_and_activate_plugins() {
|
||||
|
||||
if [[ -n "$ZIP" ]]; then
|
||||
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"
|
||||
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")
|
||||
local 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
|
||||
if invoke_wp "${args[@]}"; then
|
||||
echo "OK slug: $SLUG (activate=$ACT)" | tee -a "$LOG"
|
||||
else
|
||||
echo "FAIL slug: $SLUG" | tee -a "$LOG"
|
||||
@@ -114,7 +172,7 @@ install_and_activate_plugins() {
|
||||
fi
|
||||
done
|
||||
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
|
||||
else
|
||||
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 "— VDI WP Bootstrap (Herd + DBngin) —"
|
||||
PROJECT_NAME_CLI=""
|
||||
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 "Easy 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; }
|
||||
|
||||
PROJECT_SLUG="$(slugify "$PROJECT_NAME")"
|
||||
@@ -138,70 +269,115 @@ echo " path: $PROJECT_DIR"
|
||||
echo " local URL: $LOCAL_URL"
|
||||
echo
|
||||
|
||||
if [[ -n "$ADMIN_USER_CLI" ]]; then
|
||||
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)"
|
||||
|
||||
DB_NAME="vdi_${PROJECT_SLUG}"
|
||||
DB_NAME="wp_${PROJECT_SLUG}"
|
||||
DB_USER="$DB_NAME"
|
||||
DB_PASS="$(randpass)"
|
||||
|
||||
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_REMOTE_ORIGIN="$(prompt 'New theme repo remote (origin) URL (leave blank to skip push)' "$DEFAULT_THEME_REMOTE_ORIGIN")"
|
||||
|
||||
echo
|
||||
echo "Creating project directory…"
|
||||
echo "Creating project directory..."
|
||||
mkdir -p "$PROJECT_DIR"
|
||||
cd "$PROJECT_DIR"
|
||||
|
||||
echo "Downloading WordPress core…"
|
||||
wp core download --force
|
||||
echo "Downloading WordPress core..."
|
||||
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")
|
||||
if [[ -n "$MYSQL_ROOT_PASS" ]]; then
|
||||
MYSQL_AUTH+=(-p"$MYSQL_ROOT_PASS")
|
||||
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
|
||||
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';
|
||||
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'@'%';
|
||||
FLUSH PRIVILEGES;
|
||||
SQL
|
||||
|
||||
echo "Generating wp-config.php…"
|
||||
wp config create \
|
||||
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
|
||||
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" \
|
||||
--dbuser="$DB_USER" \
|
||||
--dbpass="$DB_PASS" \
|
||||
--dbhost="${MYSQL_HOST}:${MYSQL_PORT}" \
|
||||
--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."
|
||||
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/)"
|
||||
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");'
|
||||
invoke_wp config set AUTH_KEY "dummy" --type=constant --raw >/dev/null 2>&1 || true
|
||||
"$PHP_BIN" -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 \
|
||||
echo "Installing WordPress..."
|
||||
invoke_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"
|
||||
invoke_wp option update siteurl "$LOCAL_URL"
|
||||
invoke_wp option update home "$LOCAL_URL"
|
||||
|
||||
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"
|
||||
rm -rf "$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"
|
||||
fi
|
||||
|
||||
echo "Activating theme…"
|
||||
wp theme activate "${PROJECT_SLUG}-theme" || {
|
||||
echo "Installing theme dependencies and building assets..."
|
||||
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:"
|
||||
wp theme list
|
||||
invoke_wp theme list
|
||||
}
|
||||
|
||||
install_and_activate_plugins
|
||||
|
||||
echo "Initializing theme repo…"
|
||||
echo "Initializing theme repo..."
|
||||
pushd "$THEME_DIR" >/dev/null
|
||||
git init -b main
|
||||
git add -A
|
||||
@@ -247,7 +430,7 @@ create_wpengine_staging
|
||||
|
||||
SUMMARY_FILE="$PROJECT_DIR/bootstrap-summary.txt"
|
||||
cat > "$SUMMARY_FILE" <<TXT
|
||||
VDI WP Bootstrap — Summary
|
||||
Easy WP Bootstrap — Summary
|
||||
===========================
|
||||
|
||||
Project: $PROJECT_NAME
|
||||
@@ -278,7 +461,7 @@ Next steps:
|
||||
TXT
|
||||
|
||||
echo
|
||||
echo "✅ Bootstrap complete."
|
||||
echo "Bootstrap complete."
|
||||
echo "Summary saved to: $SUMMARY_FILE"
|
||||
echo
|
||||
cat "$SUMMARY_FILE"
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
# Herd workspace (adjust if yours differs)
|
||||
HERD_WORKSPACE="$HOME/Herd" # e.g., "$HOME/Sites" or "$HOME/Projects"
|
||||
|
||||
# Default local domain suffix used by Herd/Valet
|
||||
LOCAL_TLD="test"
|
||||
|
||||
# DBngin/MySQL connection
|
||||
MYSQL_HOST="127.0.0.1"
|
||||
MYSQL_PORT="3306"
|
||||
@@ -12,10 +15,10 @@ 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"
|
||||
THEME_STARTER_REPO="git@github.com:Vincent-Design-Inc/VDI-Starter-v5.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"
|
||||
# Optional: WPEngine API token (leave blank to skip)
|
||||
WPE_API_TOKEN=""
|
||||
|
||||
Reference in New Issue
Block a user