# wp-environments

> Environment detection, configuration, and WP-CLI wrapper selection for local WordPress development

- Skill: `yojahny55/wp-environments` (Agent Skill)
- Install (CLI): `npx skillmds add yojahny55/wp-environments`
- Raw SKILL.md: https://api.skillmd.com/api/skills/yojahny55/wp-environments/raw
- Safety review: pending (external: skill-scanner PASS, skillspector CAUTION)
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Coding & Dev Tools
- Author: yojahny55 (https://skillmd.com/u/yojahny55)
- Updated: 2026-08-19
- Page: https://skillmd.com/skills/yojahny55/wp-environments

---


# WordPress Environment Detection & Configuration

This skill teaches how to detect the local development environment, read project configuration, select the correct WP-CLI wrapper, generate config files from templates, and manage PHP versions. It applies whenever a user requests environment setup or when a `.wp-create.json` manifest is present in the project root.

---

## When This Skill Applies

- User requests WordPress environment setup, creation, or configuration
- A `.wp-create.json` file exists in the project root
- User asks about Docker, native server, DDEV, Lando, or wp-env setup
- User asks about PHP version management or web server configuration

---

## 1. Environment Detection

Run the detection script to discover what tools are available on the system:

```bash
bin/wp-env-setup.sh detect
```

This outputs JSON to stdout. Parse it to understand what is available. The full output structure:

```json
{
  "os": "fedora",
  "package_manager": "dnf",
  "web_servers": {
    "nginx": { "installed": true, "version": "1.24.0", "running": true },
    "apache": { "installed": true, "version": "2.4.58", "running": false },
    "caddy": { "installed": false }
  },
  "php": {
    "versions": ["8.2", "8.3"],
    "active": "8.3",
    "extensions": ["mysql", "curl", "mbstring", "xml", "gd", "zip", "intl"]
  },
  "docker": {
    "installed": true,
    "version": "24.0.7",
    "compose": true
  },
  "tools": {
    "ddev": { "installed": false },
    "lando": { "installed": false },
    "wp-env": { "installed": false },
    "wp-cli": { "installed": true, "version": "2.9.0" }
  },
  "database": {
    "mariadb": { "installed": true, "version": "10.11", "running": true },
    "mysql": { "installed": false }
  }
}
```

Use this output to:
- Present available environment options to the user
- Auto-select the best environment type when the user does not specify
- Determine which web servers, PHP versions, and database engines are ready

---

## 2. Project Manifest: `.wp-create.json`

The `.wp-create.json` file is the single source of truth for all commands, agents, and skills. It is generated by `/wp-create` at the project root.

### Full Manifest Structure

```json
{
  "project": {
    "name": "My Project",
    "slug": "my-project",
    "domain": "my-project.local.com",
    "path": "/var/www/html/my-project",
    "created": "2026-03-15"
  },
  "environment": {
    "type": "docker",
    "engine": "docker-compose",
    "web_server": "nginx",
    "php_version": "8.3",
    "container_prefix": "my-project"
  },
  "database": {
    "name": "wp_my_project",
    "user": "root",
    "password": "root",
    "host": "db"
  },
  "wordpress": {
    "version": "latest",
    "admin_user": "webmaster",
    "admin_email": "webmaster@local.com",
    "url": "https://my-project.local.com",
    "permalink_structure": "/%postname%/"
  },
  "languages": {
    "primary": "en",
    "additional": ["es"],
    "default": "en"
  },
  "plugins": {
    "profile": "starter",
    "installed": [
      "secure-custom-fields",
      "wordpress-seo",
      "wp-fastest-cache"
    ]
  },
  "theme": {
    "slug": "my-project",
    "initialized": false
  },
  "wp_cli": {
    "wrapper": "docker exec my-project-wp wp --allow-root",
    "path_flag": ""
  }
}
```

### Reading the Manifest

Always check for `.wp-create.json` at the project root before executing WP-CLI commands. If it exists, read `wp_cli.wrapper` and use it as the command prefix (referred to as `$WP` throughout this project).

If `.wp-create.json` does not exist, commands and agents work without WP-CLI integration -- no breaking changes to existing workflows.

---

## 3. WP-CLI Wrapper Selection

The `wp_cli.wrapper` field determines how WP-CLI commands are executed. Every agent reads this value and prepends it to all `wp` commands.

| Environment | `wp_cli.wrapper` value |
|-------------|----------------------|
| Native | `wp --path=/var/www/html/my-project` |
| Docker | `docker exec my-project-wp wp --allow-root` |
| DDEV | `ddev wp` |
| Lando | `lando wp` |
| wp-env | `npx wp-env run cli wp` |

### Usage Pattern

```bash
# Read wrapper from manifest
WP=$(jq -r '.wp_cli.wrapper' .wp-create.json)

# Use it for all WP-CLI operations
$WP core version
$WP plugin list --format=table
$WP option update blogname "My Site"
```

Never hardcode the execution method. Always read from the manifest.

---

## 4. Template Placeholder Replacement

All template files (`.tpl` extension) use `{{placeholder}}` syntax. No template engine is needed -- simple string replacement.

### Process

1. Read the `.tpl` file
2. Replace every `{{placeholder}}` with the corresponding value from `.wp-create.json` or the detection output
3. Write the result to the final config file path (without the `.tpl` extension)

### Example

Given `templates/native/nginx.conf.tpl` containing:

```nginx
server {
    listen 443 ssl;
    server_name {{domain}};
    root {{document_root}};

    ssl_certificate {{ssl_cert}};
    ssl_certificate_key {{ssl_key}};

    location ~ \.php$ {
        fastcgi_pass unix:{{php_fpm_sock}};
    }
}
```

Replace placeholders with values from the manifest to produce the final nginx config.

### Common Placeholders

| Placeholder | Example Value |
|-------------|---------------|
| `{{domain}}` | `my-project.local.com` |
| `{{document_root}}` | `/var/www/html/my-project` |
| `{{php_version}}` | `8.3` |
| `{{db_name}}` | `wp_my_project` |
| `{{db_user}}` | `root` |
| `{{db_password}}` | `root` |
| `{{db_host}}` | `localhost` or `db` |
| `{{project_name}}` | `my-project` |
| `{{ssl_cert}}` | `/etc/ssl/certs/my-project.local.com.crt` |
| `{{ssl_key}}` | `/etc/ssl/private/my-project.local.com.key` |
| `{{php_fpm_sock}}` | `/var/run/php/php8.3-fpm.sock` |
| `{{web_user}}` | `nginx` or `www-data` or `apache` |

### Mapping Placeholders to Manifest Fields

| Placeholder | Manifest Path |
|-------------|--------------|
| `{{domain}}` | `project.domain` |
| `{{document_root}}` | `project.path` |
| `{{php_version}}` | `environment.php_version` |
| `{{db_name}}` | `database.name` |
| `{{db_user}}` | `database.user` |
| `{{db_password}}` | `database.password` |
| `{{db_host}}` | `database.host` |
| `{{project_name}}` | `project.slug` |
| `{{ssl_cert}}` | Derived: `/etc/ssl/certs/<domain>.crt` |
| `{{ssl_key}}` | Derived: `/etc/ssl/private/<domain>.key` |
| `{{php_fpm_sock}}` | Derived: `/var/run/php/php<php_version>-fpm.sock` |
| `{{web_user}}` | Derived from OS: `nginx` (Fedora/RHEL), `www-data` (Debian/Ubuntu), `apache` (RHEL with Apache) |

---

## 5. Docker Port Conflict Detection

Before starting Docker containers, check for port conflicts. Default ports used by the stack:

- **80/443** -- Web server (HTTP/HTTPS)
- **3306** -- MariaDB/MySQL
- **8080** -- phpMyAdmin
- **8025** -- Mailpit

### Detection Commands

```bash
# Linux — check listening ports
ss -tlnp | grep ':80 \|:443 \|:3306 \|:8080 \|:8025 '

# macOS or fallback
lsof -i :80 -i :443 -i :3306 -i :8080 -i :8025
```

### Conflict Resolution

If a port is in use:

1. Identify which process holds the port
2. Offer the user alternatives:
   - Stop the conflicting service
   - Assign a different port (e.g., 8081 instead of 8080)
3. Update the manifest and generated config files with the alternative port

---

## 6. PHP Version Management

### Native Environments

PHP version management depends on the OS detected by `bin/wp-env-setup.sh detect`:

| OS | Package Manager | Install Command | Switch Command |
|----|----------------|-----------------|----------------|
| Fedora/RHEL | `dnf` | `sudo dnf install php8.3 php8.3-fpm php8.3-mysql php8.3-mbstring php8.3-xml php8.3-gd php8.3-zip php8.3-intl` | Update PHP-FPM pool and restart service |
| Ubuntu/Debian | `apt` | `sudo apt install php8.3 php8.3-fpm php8.3-mysql php8.3-mbstring php8.3-xml php8.3-gd php8.3-zip php8.3-intl` | `sudo update-alternatives --set php /usr/bin/php8.3` |
| macOS | `brew` | `brew install php@8.3` | `brew link php@8.3 --force` |
| Any (advanced) | `phpbrew` | `phpbrew install 8.3` | `phpbrew switch 8.3` |

Use `bin/wp-env-setup.sh php-list` to see available PHP versions before attempting installation.

Use `bin/wp-env-setup.sh php-install --version=8.3` to install a new version (requires sudo).

### Docker Environments

For Docker-based environments, PHP version is controlled by the container image tag in the template:

```yaml
# In docker-compose.yml.tpl
services:
  wordpress:
    image: wordpress:php{{php_version}}-fpm
```

No system-level PHP installation is needed. Change the `environment.php_version` value in `.wp-create.json` and regenerate the Docker config from the template.

### DDEV / Lando

These tools manage PHP internally:

- **DDEV:** Set `php_version` in `.ddev/config.yaml` (generated from template)
- **Lando:** Set `php` in `.lando.yml` recipe config (generated from template)

---

## 7. Adopt Mode Detection

When targeting an existing WordPress installation, check for `wp-config.php` at the project path to enter "adopt" mode:

```bash
# Check if wp-config.php exists at the target path
if [[ -f "/var/www/html/my-project/wp-config.php" ]]; then
    # Enter adopt mode — skip download, DB creation, and config creation
fi
```

### Adopt Mode Behavior

When `wp-config.php` is found:

1. **Skip:** WordPress download, database creation, config creation, core install
2. **Extract config from the existing install:**
   ```bash
   $WP config list --format=json        # DB credentials, table prefix, debug settings
   $WP core version                      # WordPress version
   $WP plugin list --format=json         # Installed plugins
   $WP theme list --status=active --format=json  # Active theme
   $WP option get siteurl                # Current site URL
   $WP option get home                   # Current home URL
   $WP eval "echo PHP_VERSION;"          # PHP version in use
   ```
3. **Populate the manifest** from extracted values
4. **Still execute:** environment config generation (vhost/Docker), SSL, hosts entry, search-replace if domain changes
5. **Edge cases:**
   - Old WP version (< 5.0): Warn user, offer `$WP core update`
   - Multisite: Detect via `$WP config get MULTISITE`, abort with "Multisite not supported"

---

## Summary Checklist

- [ ] Run `bin/wp-env-setup.sh detect` and parse the JSON output before making environment decisions
- [ ] Read `.wp-create.json` for all project configuration -- never hardcode values
- [ ] Use `wp_cli.wrapper` from the manifest as the WP-CLI command prefix
- [ ] Replace `{{placeholders}}` in `.tpl` files using manifest values
- [ ] Check for Docker port conflicts before starting containers
- [ ] Use OS-appropriate commands for PHP version management
- [ ] Detect `wp-config.php` to determine adopt mode vs fresh install
- [ ] All operations are idempotent -- safe to re-run

