Files
shopify-ai-backup/EXTERNAL_WP_CLI_TESTING_IMPLEMENTATION.md
2026-02-08 20:15:38 +00:00

9.5 KiB

External WP-CLI Testing - Implementation Reference

Complete Data Flow

1. User Enables Toggle in Builder

File: chat/public/builder.js

// Line ~36
builderState.externalTestingEnabled = false  // default

// User clicks toggle → sets to true
builderState.externalTestingEnabled = true

// When sending message (line ~3967)
const payload = {
  content: messageContent,
  model,
  cli,
  externalTestingEnabled: !!builderState.externalTestingEnabled
}

2. Chat Server Receives Message

File: chat/server.js

// Line ~8804
const wpCliMcpEnabled = message?.externalTestingEnabled === true

// Line ~8885-8901: If enabled, inject MCP server
if (wpCliMcpEnabled) {
  const wpMcpServerPath = path.resolve(__dirname, '../opencode/mcp-servers/wp-cli-testing/index.js')
  
  executionEnv.OPENCODE_EXTRA_MCP_SERVERS = JSON.stringify([
    {
      name: 'wp-cli-testing',
      command: 'node',
      args: [wpMcpServerPath],
      disabled: false
    }
  ])
} else {
  delete executionEnv.OPENCODE_EXTRA_MCP_SERVERS  // Safety gate
}

// Pass env to OpenCode
await opencodeManager.executeInSession(..., { env: executionEnv })

3. OpenCode Reads Environment Variable

File: opencode/packages/opencode/src/config/config.ts

// Line ~233-294: Parse OPENCODE_EXTRA_MCP_SERVERS
const extraMcpRaw = process.env.OPENCODE_EXTRA_MCP_SERVERS
if (extraMcpRaw && typeof extraMcpRaw === "string") {
  const parsed = JSON.parse(extraMcpRaw)
  if (Array.isArray(parsed)) {
    result.mcp ??= {}
    for (const entry of parsed) {
      result.mcp[entry.name] = {
        type: "local",
        command: [...commandParts, ...argsParts],
        env: envRecord,
        enabled: true,
        ...
      }
    }
  }
}

Result: OpenCode config now has mcp['wp-cli-testing'] configured


4. OpenCode Starts MCP Server

File: opencode/packages/opencode/src/mcp/index.ts

OpenCode's MCP system automatically:

  1. Reads config.mcp['wp-cli-testing']
  2. Spawns: node opencode/mcp-servers/wp-cli-testing/index.js
  3. Connects via stdio transport
  4. Calls listTools() to get available tools

5. MCP Server Registers Tools

File: opencode/mcp-servers/wp-cli-testing/index.js

server.setRequestHandler(ListToolsRequestSchema, async () => {
  return {
    tools: [
      {
        name: "test_plugin_external_wp",
        description: "Run WP-CLI based verification...",
        inputSchema: { ... }
      },
      {
        name: "external_wp_testing_config",
        description: "Return resolved config...",
        inputSchema: { ... }
      }
    ]
  }
})

6. AI Invokes test_plugin_external_wp Tool

File: opencode/packages/opencode/src/session/prompt.ts

OpenCode's session handler:

  1. Tool calls go through resolveTools() (line ~668)
  2. MCP tools added via MCP.tools() (line ~745)
  3. AI invokes tool with parameters
test_plugin_external_wp({
  plugin_path: "/workspace/my-plugin",
  plugin_slug: "my-plugin",
  test_scenarios: [...]
})

7. MCP Server Handles Tool Call

File: opencode/mcp-servers/wp-cli-testing/index.js

server.setRequestHandler(CallToolRequestSchema, async (req) => {
  const toolName = req.params.name
  const args = req.params.arguments

  // Import external-wp-testing module
  const externalTesting = await import("../../../chat/external-wp-testing.js")
  const mod = externalTesting.default ?? externalTesting
  
  const tester = mod.createExternalWpTester({})
  
  const result = await tester.runTest(
    {
      plugin_path: args.plugin_path,
      plugin_slug: args.plugin_slug,
      ...
    },
    { configOverrides: {} }
  )
  
  return { content: [{ type: "text", text: JSON.stringify(result) }] }
})

8. External WP Testing Executes

File: chat/external-wp-testing.js

async function runTest(input, opts) {
  const config = getExternalTestingConfig(opts.configOverrides)
  
  // Queue the test
  return queue.enqueue(async () => {
    // 1. Create subsite (if multisite enabled)
    if (config.enableMultisite) {
      subsite = await createSubsite(config, sessionId)
      // Runs: wp site create --slug=test-abc123
    }
    
    // 2. Upload plugin via SCP
    if (pluginPath) {
      await installPluginFromLocal(config, pluginPath, pluginSlug, sessionId)
    }
    
    // 3. Install required plugins
    const installedPlugins = await installRequiredPlugins(config, subsite.url, requiredPlugins)
    
    // 4. Activate test plugin
    if (pluginSlug) {
      await runSshCommand(config, 
        buildWpCliCommand(config, subsite.url, `plugin activate ${pluginSlug}`)
      )
    }
    
    // 5. Run test scenarios
    const cliTests = await runTestScenarios(config, subsite.url, scenarios)
    
    // 6. Schedule cleanup
    if (config.autoCleanup && subsite.slug) {
      setTimeout(() => { deleteSubsite(config, subsite.slug) }, config.cleanupDelayMs)
    }
    
    return { ok: true, test_results: { cli_tests: cliTests }, ... }
  })
}

9. SSH Commands Execute on WordPress Server

Functions:

  • buildWpCliCommand(config, url, command)wp --path=/var/www/html --url=https://site.com/test-abc command
  • runSshCommand(config, command) → Executes via SSH with key auth

Example Commands Executed:

# Create subsite
ssh -i ~/.ssh/wp-key wordpress@wp-test.com "wp --path=/var/www/html site create --slug=test-abc123"

# Copy plugin
scp -i ~/.ssh/wp-key -r /local/plugin wordpress@wp-test.com:/var/www/html/wp-content/plugins/

# Activate plugin
ssh -i ~/.ssh/wp-key wordpress@wp-test.com "wp --path=/var/www/html --url=https://wp-test.com/test-abc123 plugin activate my-plugin"

# Run test
ssh -i ~/.ssh/wp-key wordpress@wp-test.com "wp --path=/var/www/html --url=https://wp-test.com/test-abc123 plugin status my-plugin"

# Later: Delete subsite
ssh -i ~/.ssh/wp-key wordpress@wp-test.com "wp --path=/var/www/html site delete --slug=test-abc123 --yes"

10. Results Return to AI

Return Path:

WordPress Server (via SSH)
  ↓ command output
external-wp-testing.js runTest()
  ↓ formatted result
MCP Server CallToolRequestSchema handler
  ↓ JSON response
OpenCode MCP client
  ↓ tool result
AI Session Handler
  ↓ assistant message
Chat Server
  ↓ SSE stream
Builder UI

Result Format:

{
  "ok": true,
  "session_id": "abc123",
  "subsite_url": "https://wp-test.example.com/test-abc123",
  "test_results": {
    "mode": "cli",
    "cli_tests": {
      "passed": 3,
      "failed": 0,
      "results": [...]
    }
  },
  "installed_plugins": [...],
  "duration": 15230,
  "cleanup_scheduled": "2026-02-08T11:30:00Z"
}

Key Files

File Purpose
chat/public/builder.js Builder UI toggle state
chat/server.js Gate MCP injection via env var
opencode/packages/opencode/src/config/config.ts Parse OPENCODE_EXTRA_MCP_SERVERS
opencode/packages/opencode/src/mcp/index.ts Load and connect MCP servers
opencode/mcp-servers/wp-cli-testing/index.js MCP server implementation
chat/external-wp-testing.js Core testing logic

Environment Variables Flow

Host Environment
  ↓
Chat Server process.env
  ↓
executionEnv = {...process.env, OPENCODE_EXTRA_MCP_SERVERS: "..."}
  ↓
OpenCode spawned with executionEnv
  ↓
process.env.TEST_WP_HOST → external-wp-testing.js getExternalTestingConfig()
  ↓
SSH connection using TEST_WP_SSH_KEY
  ↓
Commands execute on WordPress server

Required Environment Variables:

  • TEST_WP_HOST
  • TEST_WP_SSH_USER
  • TEST_WP_SSH_KEY
  • TEST_WP_PATH
  • TEST_WP_BASE_URL
  • etc. (see EXTERNAL_WP_CLI_TESTING_SETUP.md)

Security Gates

  1. Builder Toggle - User must explicitly enable
  2. Environment Variable Gate - Only set when toggle is on
  3. MCP Loading - Only loads when env var present
  4. SSH Key Auth - Requires valid key file
  5. Tool Gating - Tools not available if toggle off

When toggle is OFF:

  • externalTestingEnabled: false in payload
  • wpCliMcpEnabled = false on server
  • OPENCODE_EXTRA_MCP_SERVERS is deleted from env
  • MCP server never loads
  • Tools never available to AI

Testing The Flow

1. Check Environment

echo $TEST_WP_HOST
echo $TEST_WP_SSH_KEY

2. Test SSH Connection

ssh -i $TEST_WP_SSH_KEY $TEST_WP_SSH_USER@$TEST_WP_HOST "wp site list"

3. Enable Toggle in Builder

  • Open Builder
  • Enable "External WP CLI testing"
  • Verify toggle shows enabled state

4. Check Server Logs

tail -f logs/chat-server.log | grep "WP CLI Testing"
# Should see: "Enabling WP CLI Testing MCP server"

5. Ask AI to Test

"Create a plugin and test it on the external WordPress server"

6. Verify Subsite Created

ssh -i $TEST_WP_SSH_KEY $TEST_WP_SSH_USER@$TEST_WP_HOST "wp site list | grep test-"

Multisite Architecture

WordPress Multisite Network
├── Main Site (/)
│   └── Core WP, Plugins, Themes (shared)
├── Subsite: /test-abc123 (Session 1)
│   ├── Isolated content/options
│   ├── Test Plugin A activated
│   └── Tests running
├── Subsite: /test-def456 (Session 2)
│   ├── Isolated content/options
│   ├── Test Plugin B activated
│   └── Tests running
└── Subsite: /test-xyz789 (Session 3)
    └── ...

Concurrency: 20+ simultaneous tests via queue system + isolated subsites

Cleanup: Each subsite auto-deleted after 1 hour (configurable via TEST_CLEANUP_DELAY)