wordpress-agent-kit 0.4.0 → 0.5.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (132) hide show
  1. package/.agents/skills/blueprint/SKILL.md +418 -0
  2. package/.agents/skills/wordpress-router/SKILL.md +52 -0
  3. package/.agents/skills/wordpress-router/references/decision-tree.md +55 -0
  4. package/.agents/skills/wp-abilities-api/SKILL.md +108 -0
  5. package/.agents/skills/wp-abilities-api/references/delegate-helper-pattern.md +241 -0
  6. package/.agents/skills/wp-abilities-api/references/domain-vs-projection.md +113 -0
  7. package/.agents/skills/wp-abilities-api/references/error-code-vocabulary.md +123 -0
  8. package/.agents/skills/wp-abilities-api/references/grouping-heuristic.md +89 -0
  9. package/.agents/skills/wp-abilities-api/references/input-schema-gotchas.md +265 -0
  10. package/.agents/skills/wp-abilities-api/references/php-registration.md +94 -0
  11. package/.agents/skills/wp-abilities-api/references/plugin-family-patterns.md +233 -0
  12. package/.agents/skills/wp-abilities-api/references/rest-api.md +13 -0
  13. package/.agents/skills/wp-abilities-api/references/shared-core-service.md +184 -0
  14. package/.agents/skills/wp-abilities-audit/SKILL.md +199 -0
  15. package/.agents/skills/wp-abilities-audit/references/audit-schema.md +300 -0
  16. package/.agents/skills/wp-abilities-audit/references/capability-gate-tracing.md +197 -0
  17. package/.agents/skills/wp-abilities-audit/references/controller-enumeration.md +116 -0
  18. package/.agents/skills/wp-abilities-verify/SKILL.md +215 -0
  19. package/.agents/skills/wp-abilities-verify/references/annotation-correctness.md +154 -0
  20. package/.agents/skills/wp-abilities-verify/references/audit-schema-validation.md +131 -0
  21. package/.agents/skills/wp-abilities-verify/references/permission-roundtrip.md +190 -0
  22. package/.agents/skills/wp-abilities-verify/references/runtime-harness.md +462 -0
  23. package/.agents/skills/wp-abilities-verify/references/schema-lints.md +118 -0
  24. package/.agents/skills/wp-abilities-verify/references/static-enumeration.md +126 -0
  25. package/.agents/skills/wp-block-development/SKILL.md +175 -0
  26. package/.agents/skills/wp-block-development/references/attributes-and-serialization.md +22 -0
  27. package/.agents/skills/wp-block-development/references/block-json.md +49 -0
  28. package/.agents/skills/wp-block-development/references/creating-new-blocks.md +46 -0
  29. package/.agents/skills/wp-block-development/references/debugging.md +36 -0
  30. package/.agents/skills/wp-block-development/references/deprecations.md +24 -0
  31. package/.agents/skills/wp-block-development/references/dynamic-rendering.md +23 -0
  32. package/.agents/skills/wp-block-development/references/inner-blocks.md +25 -0
  33. package/.agents/skills/wp-block-development/references/registration.md +30 -0
  34. package/.agents/skills/wp-block-development/references/supports-and-wrappers.md +18 -0
  35. package/.agents/skills/wp-block-development/references/tooling-and-testing.md +21 -0
  36. package/.agents/skills/wp-block-development/scripts/list_blocks.mjs +121 -0
  37. package/.agents/skills/wp-block-themes/SKILL.md +117 -0
  38. package/.agents/skills/wp-block-themes/references/creating-new-block-theme.md +37 -0
  39. package/.agents/skills/wp-block-themes/references/debugging.md +24 -0
  40. package/.agents/skills/wp-block-themes/references/patterns.md +18 -0
  41. package/.agents/skills/wp-block-themes/references/style-variations.md +14 -0
  42. package/.agents/skills/wp-block-themes/references/templates-and-parts.md +16 -0
  43. package/.agents/skills/wp-block-themes/references/theme-json.md +59 -0
  44. package/.agents/skills/wp-block-themes/scripts/detect_block_themes.mjs +117 -0
  45. package/.agents/skills/wp-interactivity-api/SKILL.md +180 -0
  46. package/.agents/skills/wp-interactivity-api/references/debugging.md +29 -0
  47. package/.agents/skills/wp-interactivity-api/references/directives-quickref.md +30 -0
  48. package/.agents/skills/wp-interactivity-api/references/server-side-rendering.md +310 -0
  49. package/.agents/skills/wp-performance/SKILL.md +147 -0
  50. package/.agents/skills/wp-performance/references/autoload-options.md +24 -0
  51. package/.agents/skills/wp-performance/references/cron.md +20 -0
  52. package/.agents/skills/wp-performance/references/database.md +20 -0
  53. package/.agents/skills/wp-performance/references/http-api.md +15 -0
  54. package/.agents/skills/wp-performance/references/measurement.md +21 -0
  55. package/.agents/skills/wp-performance/references/object-cache.md +24 -0
  56. package/.agents/skills/wp-performance/references/query-monitor-headless.md +38 -0
  57. package/.agents/skills/wp-performance/references/server-timing.md +22 -0
  58. package/.agents/skills/wp-performance/references/wp-cli-doctor.md +24 -0
  59. package/.agents/skills/wp-performance/references/wp-cli-profile.md +32 -0
  60. package/.agents/skills/wp-performance/scripts/perf_inspect.mjs +128 -0
  61. package/.agents/skills/wp-phpstan/SKILL.md +98 -0
  62. package/.agents/skills/wp-phpstan/references/configuration.md +52 -0
  63. package/.agents/skills/wp-phpstan/references/third-party-classes.md +76 -0
  64. package/.agents/skills/wp-phpstan/references/wordpress-annotations.md +124 -0
  65. package/.agents/skills/wp-phpstan/scripts/phpstan_inspect.mjs +263 -0
  66. package/.agents/skills/wp-playground/SKILL.md +233 -0
  67. package/.agents/skills/wp-playground/references/blueprints.md +36 -0
  68. package/.agents/skills/wp-playground/references/cli-commands.md +39 -0
  69. package/.agents/skills/wp-playground/references/debugging.md +16 -0
  70. package/.agents/skills/wp-playground/references/e2e-playwright.md +115 -0
  71. package/.agents/skills/wp-plugin-development/SKILL.md +113 -0
  72. package/.agents/skills/wp-plugin-development/references/data-and-cron.md +19 -0
  73. package/.agents/skills/wp-plugin-development/references/debugging.md +19 -0
  74. package/.agents/skills/wp-plugin-development/references/lifecycle.md +33 -0
  75. package/.agents/skills/wp-plugin-development/references/security.md +29 -0
  76. package/.agents/skills/wp-plugin-development/references/settings-api.md +22 -0
  77. package/.agents/skills/wp-plugin-development/references/structure.md +16 -0
  78. package/.agents/skills/wp-plugin-development/scripts/detect_plugins.mjs +122 -0
  79. package/.agents/skills/wp-plugin-directory-guidelines/SKILL.md +133 -0
  80. package/.agents/skills/wp-plugin-directory-guidelines/references/gpl-compliance.md +217 -0
  81. package/.agents/skills/wp-plugin-directory-guidelines/references/guideline-review-checklist.md +592 -0
  82. package/.agents/skills/wp-plugin-directory-guidelines/references/naming-rules.md +121 -0
  83. package/.agents/skills/wp-project-triage/SKILL.md +39 -0
  84. package/.agents/skills/wp-project-triage/references/triage.schema.json +143 -0
  85. package/.agents/skills/wp-project-triage/scripts/detect_wp_project.mjs +610 -0
  86. package/.agents/skills/wp-rest-api/SKILL.md +115 -0
  87. package/.agents/skills/wp-rest-api/references/authentication.md +18 -0
  88. package/.agents/skills/wp-rest-api/references/custom-content-types.md +20 -0
  89. package/.agents/skills/wp-rest-api/references/discovery-and-params.md +20 -0
  90. package/.agents/skills/wp-rest-api/references/responses-and-fields.md +30 -0
  91. package/.agents/skills/wp-rest-api/references/routes-and-endpoints.md +36 -0
  92. package/.agents/skills/wp-rest-api/references/schema.md +22 -0
  93. package/.agents/skills/wp-wpcli-and-ops/SKILL.md +126 -0
  94. package/.agents/skills/wp-wpcli-and-ops/references/automation.md +30 -0
  95. package/.agents/skills/wp-wpcli-and-ops/references/cron-and-cache.md +23 -0
  96. package/.agents/skills/wp-wpcli-and-ops/references/debugging.md +17 -0
  97. package/.agents/skills/wp-wpcli-and-ops/references/multisite.md +22 -0
  98. package/.agents/skills/wp-wpcli-and-ops/references/packages-and-updates.md +22 -0
  99. package/.agents/skills/wp-wpcli-and-ops/references/safety.md +30 -0
  100. package/.agents/skills/wp-wpcli-and-ops/references/search-replace.md +40 -0
  101. package/.agents/skills/wp-wpcli-and-ops/scripts/wpcli_inspect.mjs +90 -0
  102. package/.agents/skills/wp-wpengine/SKILL.md +398 -0
  103. package/.agents/skills/wp-wpengine/references/ci-gate.md +469 -0
  104. package/.agents/skills/wp-wpengine/references/github-actions-deploy.md +736 -0
  105. package/.agents/skills/wp-wpengine/scripts/ci-gate.sh +118 -0
  106. package/.agents/skills/wp-wpengine/scripts/wpe-check.sh +89 -0
  107. package/.agents/skills/wp-wpengine/scripts/wpe-preflight.sh +104 -0
  108. package/.agents/skills/wpds/SKILL.md +59 -0
  109. package/.github/agents/wp-architect.agent.md +1 -2
  110. package/.github/copilot-instructions.md +1 -1
  111. package/.github/instructions/wordpress-workflow.instructions.md +3 -3
  112. package/AGENTS.md +22 -10
  113. package/AGENTS.template.md +20 -10
  114. package/README.md +89 -85
  115. package/dist/cli.js +5 -1
  116. package/dist/commands/clean-skills.js +64 -0
  117. package/dist/commands/setup.js +6 -2
  118. package/dist/commands/sync-skills.js +3 -0
  119. package/dist/lib/api.js +164 -5
  120. package/dist/lib/installer.js +166 -2
  121. package/extensions/wp-agent-kit/index.ts +185 -10
  122. package/package.json +10 -14
  123. package/skills-custom/wp-wpengine/SKILL.md +299 -28
  124. package/skills-custom/wp-wpengine/references/ci-gate.md +469 -0
  125. package/skills-custom/wp-wpengine/references/github-actions-deploy.md +736 -0
  126. package/skills-custom/wp-wpengine/scripts/ci-gate.sh +118 -0
  127. package/skills-custom/wp-wpengine/scripts/wpe-check.sh +89 -0
  128. package/skills-custom/wp-wpengine/scripts/wpe-preflight.sh +104 -0
  129. package/.github/workflows/ci.yml +0 -44
  130. package/.husky/pre-commit +0 -7
  131. package/CLI_REVIEW.md +0 -250
  132. package/biome.json +0 -39
@@ -0,0 +1,736 @@
1
+ # GitHub Actions CI/CD for WP Engine
2
+
3
+ Push-to-deploy for WordPress on WP Engine, using GitHub as the source of truth.
4
+ Based on the pattern established by Kris Jordan (2013): a bare git remote + post-receive hook deploys the working tree. WP Engine operates this natively. GitHub Actions wraps it with safety gates.
5
+
6
+ ---
7
+
8
+ ## CI gate policy: `--no-verify` is forbidden
9
+
10
+ `--no-verify` skips local git hooks. It is **explicitly prohibited** on any branch that feeds
11
+ a WP Engine deploy. Hooks are there to surface problems before they reach CI — bypassing
12
+ them shifts the cost of broken code from a 5-second local check to a failed deploy and
13
+ possible production incident.
14
+
15
+ Enforcement is two-layered so that `--no-verify` has no actual effect:
16
+
17
+ 1. **CI gate** (`ci-gate.yml`) runs on every push to `develop`, `staging`, and `main`,
18
+ re-running every check that hooks run. It is a required status check. Broken code
19
+ cannot reach a protected branch regardless of what happened locally.
20
+ 2. **Deploy workflows** run a `verify` job as their **first dependency**. Deploys never
21
+ start without it passing — even on `workflow_dispatch` or emergency force pushes.
22
+
23
+ See `ci-gate.md` for the full CI gate workflow and Husky hook setup.
24
+
25
+ ---
26
+
27
+ ## Branch model
28
+
29
+ ```
30
+ feature/* ──PR──→ develop ──auto──→ WP Engine dev
31
+
32
+ PR + review
33
+
34
+ staging ──auto──→ WP Engine staging
35
+
36
+ PR + 2 reviewers + staging-only rule
37
+
38
+ main ──auto──→ WP Engine production
39
+ ```
40
+
41
+ | Branch | WP Engine install | Auto-deploy | Backup before | Smoke test | Rollback |
42
+ |--------|------------------|-------------|---------------|------------|----------|
43
+ | `develop` | `<install>dev` | ✅ | ❌ | ❌ | manual |
44
+ | `staging` | `<install>stg` | ✅ | ✅ DB snapshot | ✅ | manual |
45
+ | `main` | `<install>` | ✅ | ✅ DB snapshot | ✅ | ✅ auto |
46
+
47
+ ---
48
+
49
+ ## GitHub repository setup
50
+
51
+ ### Branch protection rules
52
+
53
+ Configure in **Settings → Branches** for each protected branch:
54
+
55
+ **`develop`:**
56
+ - ✅ Require status checks: `ci-gate / full-check`
57
+ - ✅ Require branches to be up to date
58
+
59
+ **`staging`:**
60
+ - ✅ Require pull request before merging
61
+ - ✅ Require 1 approving review
62
+ - ✅ Dismiss stale reviews on push
63
+ - ✅ Require status checks: `ci-gate / full-check`
64
+ - ✅ Require branches to be up to date
65
+ - ✅ Restrict who can push: only Actions + team leads
66
+ - ✅ **Do not allow bypassing the above settings**
67
+
68
+ **`main`:**
69
+ - ✅ Require pull request before merging
70
+ - ✅ Require 2 approving reviews
71
+ - ✅ Dismiss stale reviews on push
72
+ - ✅ Require status checks: `ci-gate / full-check`, `staging-source-check`
73
+ - ✅ Require branches to be up to date
74
+ - ✅ Restrict who can push: only Actions + team leads
75
+ - ✅ Do not allow force pushes
76
+ - ✅ Do not allow deletions
77
+ - ✅ **Do not allow bypassing the above settings** ← admins cannot override
78
+
79
+ ### Required GitHub Secrets
80
+
81
+ Add under **Settings → Secrets and variables → Actions**:
82
+
83
+ | Secret | Value |
84
+ |--------|-------|
85
+ | `WPE_SSH_KEY` | Private key (contents of `wpengine_ed25519`) |
86
+ | `WPE_SSH_KNOWN_HOSTS` | Output of `ssh-keyscan -t rsa git.wpengine.com && ssh-keyscan -H ssh.wpengine.net` |
87
+ | `WPE_PROD_INSTALL` | Production install slug (e.g., `mysite`) |
88
+ | `WPE_STAGING_INSTALL` | Staging install slug (e.g., `mysitestg`) |
89
+ | `WPE_DEV_INSTALL` | Development install slug (e.g., `mysitedev`) |
90
+ | `WPE_API_USER` | WP Engine API username (for backup snapshots) |
91
+ | `WPE_API_PASSWORD` | WP Engine API password |
92
+ | `SLACK_WEBHOOK_URL` | Slack incoming webhook (optional, for notifications) |
93
+
94
+ Generate the known hosts value once and save:
95
+ ```bash
96
+ { ssh-keyscan -t rsa git.wpengine.com; ssh-keyscan -H ssh.wpengine.net; } 2>/dev/null
97
+ ```
98
+
99
+ ---
100
+
101
+ ## Workflow files
102
+
103
+ ### `lint-and-test.yml` — PR quality gate (all environments)
104
+
105
+ Runs on every PR targeting `develop`, `staging`, or `main`. No deployment.
106
+
107
+ ```yaml
108
+ # .github/workflows/lint-and-test.yml
109
+ name: Lint & Test
110
+
111
+ on:
112
+ pull_request:
113
+ branches: [develop, staging, main]
114
+
115
+ jobs:
116
+ lint-and-test:
117
+ name: Lint & Test
118
+ runs-on: ubuntu-latest
119
+ steps:
120
+ - uses: actions/checkout@v4
121
+
122
+ - name: Setup PHP
123
+ uses: shivammathur/setup-php@v2
124
+ with:
125
+ php-version: '8.2'
126
+ tools: composer, phpcs, phpstan
127
+
128
+ - name: Setup Node
129
+ uses: actions/setup-node@v4
130
+ with:
131
+ node-version: '20'
132
+ cache: 'npm'
133
+
134
+ - name: Install PHP dependencies
135
+ run: composer install --no-dev --prefer-dist --quiet
136
+
137
+ - name: Install JS dependencies
138
+ run: npm ci
139
+
140
+ - name: PHP lint (syntax)
141
+ run: find . -name "*.php" -not -path "*/vendor/*" -not -path "*/node_modules/*" | xargs -P4 php -l
142
+
143
+ - name: PHPCS (WordPress coding standards)
144
+ run: phpcs --standard=WordPress --extensions=php --ignore=vendor/,node_modules/ .
145
+
146
+ - name: PHPStan
147
+ run: phpstan analyse --no-progress
148
+
149
+ - name: JS/CSS lint
150
+ run: npm run lint
151
+
152
+ - name: Build assets
153
+ run: npm run build
154
+
155
+ - name: Run PHP tests
156
+ run: composer test
157
+ ```
158
+
159
+ ---
160
+
161
+ ### `staging-source-check.yml` — Production PR guard
162
+
163
+ Blocks any PR merging into `main` that does NOT originate from `staging`. Required status check on `main`.
164
+
165
+ ```yaml
166
+ # .github/workflows/staging-source-check.yml
167
+ name: Staging Source Check
168
+
169
+ on:
170
+ pull_request:
171
+ branches: [main]
172
+
173
+ jobs:
174
+ staging-source-check:
175
+ name: Verify PR comes from staging
176
+ runs-on: ubuntu-latest
177
+ steps:
178
+ - name: Check source branch
179
+ run: |
180
+ SOURCE="${{ github.head_ref }}"
181
+ echo "Source branch: $SOURCE"
182
+ if [[ "$SOURCE" != "staging" ]]; then
183
+ echo "❌ Production deploys must come from the 'staging' branch."
184
+ echo " Current source: '$SOURCE'"
185
+ echo " Merge staging into main via a PR from the staging branch."
186
+ exit 1
187
+ fi
188
+ echo "✅ Source branch is staging — cleared for production deploy."
189
+ ```
190
+
191
+ ---
192
+
193
+ ### `deploy-dev.yml` — Development auto-deploy
194
+
195
+ Deploys on every push to `develop`. Minimal guards — this is the scratch environment.
196
+
197
+ ```yaml
198
+ # .github/workflows/deploy-dev.yml
199
+ name: Deploy → Development
200
+
201
+ on:
202
+ push:
203
+ branches: [develop]
204
+
205
+ concurrency:
206
+ group: deploy-dev
207
+ cancel-in-progress: true
208
+
209
+ jobs:
210
+ # ── Gate: verify before touching dev ────────────────────────────────────
211
+ verify:
212
+ name: Verify — full check (mirrors hooks, blocks --no-verify)
213
+ runs-on: ubuntu-latest
214
+ steps:
215
+ - uses: actions/checkout@v4
216
+ - uses: actions/setup-node@v4
217
+ with: { node-version: '20', cache: 'npm' }
218
+ - run: npm ci
219
+ - run: npm run format:check
220
+ - run: npm run lint
221
+ - run: npm run check
222
+ - run: npm test -- --run
223
+ - run: npm run build
224
+
225
+ deploy-dev:
226
+ name: Deploy to WP Engine Dev
227
+ runs-on: ubuntu-latest
228
+ needs: verify
229
+ environment:
230
+ name: development
231
+ url: https://${{ secrets.WPE_DEV_INSTALL }}.wpenginepowered.com
232
+ steps:
233
+ - uses: actions/checkout@v4
234
+ with:
235
+ fetch-depth: 0
236
+
237
+ - name: Setup Node & build assets
238
+ uses: actions/setup-node@v4
239
+ with:
240
+ node-version: '20'
241
+ cache: 'npm'
242
+
243
+ - run: npm ci && npm run build
244
+
245
+ - name: Setup SSH
246
+ uses: webfactory/ssh-agent@v0.9.0
247
+ with:
248
+ ssh-private-key: ${{ secrets.WPE_SSH_KEY }}
249
+
250
+ - name: Add WP Engine to known hosts
251
+ run: echo "${{ secrets.WPE_SSH_KNOWN_HOSTS }}" >> ~/.ssh/known_hosts
252
+
253
+ - name: Push to WP Engine dev
254
+ env:
255
+ INSTALL: ${{ secrets.WPE_DEV_INSTALL }}
256
+ run: |
257
+ git remote add wpe-dev git@git.wpengine.com:development/${INSTALL}.git
258
+ # Force-add built assets (normally gitignored)
259
+ git add -f dist/ build/ 2>/dev/null || true
260
+ git diff --cached --quiet || git commit -m "ci: add built assets [skip ci]"
261
+ git push wpe-dev HEAD:main --force
262
+
263
+ - name: Flush cache (dev)
264
+ env:
265
+ INSTALL: ${{ secrets.WPE_DEV_INSTALL }}
266
+ run: |
267
+ ssh -o StrictHostKeyChecking=no ${INSTALL}@${INSTALL}.ssh.wpengine.net \
268
+ wp cache flush --skip-plugins --skip-themes
269
+ ```
270
+
271
+ ---
272
+
273
+ ### `deploy-staging.yml` — Staging deploy with guards
274
+
275
+ Deploys on push to `staging`. Takes a DB backup, deploys, flushes cache, runs a smoke test.
276
+
277
+ ```yaml
278
+ # .github/workflows/deploy-staging.yml
279
+ name: Deploy → Staging
280
+
281
+ on:
282
+ push:
283
+ branches: [staging]
284
+
285
+ concurrency:
286
+ group: deploy-staging
287
+ cancel-in-progress: false # never cancel in-flight staging deploys
288
+
289
+ jobs:
290
+ # ── Gate: verify before touching staging ────────────────────────────────
291
+ verify:
292
+ name: Verify — full check (mirrors hooks, blocks --no-verify)
293
+ runs-on: ubuntu-latest
294
+ steps:
295
+ - uses: actions/checkout@v4
296
+ - uses: actions/setup-node@v4
297
+ with: { node-version: '20', cache: 'npm' }
298
+ - run: npm ci
299
+ - run: npm run format:check
300
+ - run: npm run lint
301
+ - run: npm run check
302
+ - run: npm test -- --run
303
+ - run: npm run build
304
+
305
+ deploy-staging:
306
+ name: Deploy to WP Engine Staging
307
+ runs-on: ubuntu-latest
308
+ needs: verify
309
+ environment:
310
+ name: staging
311
+ url: https://${{ secrets.WPE_STAGING_INSTALL }}.wpenginepowered.com
312
+ steps:
313
+ - uses: actions/checkout@v4
314
+ with:
315
+ fetch-depth: 0
316
+
317
+ - name: Setup Node & build assets
318
+ uses: actions/setup-node@v4
319
+ with:
320
+ node-version: '20'
321
+ cache: 'npm'
322
+
323
+ - run: npm ci && npm run build
324
+
325
+ - name: Setup SSH
326
+ uses: webfactory/ssh-agent@v0.9.0
327
+ with:
328
+ ssh-private-key: ${{ secrets.WPE_SSH_KEY }}
329
+
330
+ - name: Add WP Engine to known hosts
331
+ run: echo "${{ secrets.WPE_SSH_KNOWN_HOSTS }}" >> ~/.ssh/known_hosts
332
+
333
+ - name: Pre-deploy DB backup (staging)
334
+ env:
335
+ INSTALL: ${{ secrets.WPE_STAGING_INSTALL }}
336
+ WPE_API_USER: ${{ secrets.WPE_API_USER }}
337
+ WPE_API_PASSWORD: ${{ secrets.WPE_API_PASSWORD }}
338
+ run: |
339
+ echo "📦 Creating staging DB backup via WP Engine API..."
340
+ RESPONSE=$(curl -s -w "\n%{http_code}" -X POST \
341
+ -u "${WPE_API_USER}:${WPE_API_PASSWORD}" \
342
+ "https://api.wpengineapi.com/v1/installs/${INSTALL}/backups" \
343
+ -H "Content-Type: application/json" \
344
+ -d '{"description":"Pre-deploy backup — GitHub Actions","notification_emails":[]}')
345
+ HTTP_CODE=$(echo "$RESPONSE" | tail -1)
346
+ BODY=$(echo "$RESPONSE" | head -1)
347
+ echo "API response: $BODY (HTTP $HTTP_CODE)"
348
+ if [[ "$HTTP_CODE" != "200" && "$HTTP_CODE" != "202" ]]; then
349
+ echo "⚠️ Backup API call failed (HTTP $HTTP_CODE) — proceeding anyway"
350
+ else
351
+ echo "✅ Backup initiated"
352
+ fi
353
+
354
+ - name: Push to WP Engine staging
355
+ env:
356
+ INSTALL: ${{ secrets.WPE_STAGING_INSTALL }}
357
+ run: |
358
+ git remote add wpe-staging git@git.wpengine.com:staging/${INSTALL}.git
359
+ git add -f dist/ build/ 2>/dev/null || true
360
+ git diff --cached --quiet || git commit -m "ci: add built assets [skip ci]"
361
+ git push wpe-staging HEAD:main --force
362
+
363
+ - name: Post-deploy WP-CLI (staging)
364
+ env:
365
+ INSTALL: ${{ secrets.WPE_STAGING_INSTALL }}
366
+ run: |
367
+ ssh -o StrictHostKeyChecking=no ${INSTALL}@${INSTALL}.ssh.wpengine.net bash -s <<'EOF'
368
+ set -e
369
+ wp cache flush --skip-plugins --skip-themes
370
+ wp rewrite flush --skip-plugins --skip-themes
371
+ wp cron event run --due-now --skip-plugins --skip-themes
372
+ echo "✅ Post-deploy WP-CLI complete"
373
+ EOF
374
+
375
+ - name: Smoke test (staging)
376
+ env:
377
+ INSTALL: ${{ secrets.WPE_STAGING_INSTALL }}
378
+ run: |
379
+ SITE_URL="https://${INSTALL}.wpenginepowered.com"
380
+ echo "🧪 Smoke testing $SITE_URL..."
381
+ sleep 30 # Allow WP Engine deploy to propagate
382
+
383
+ check_url() {
384
+ STATUS=$(curl -s -o /dev/null -w "%{http_code}" -L --max-time 30 "$1")
385
+ if [[ "$STATUS" -ge 200 && "$STATUS" -lt 400 ]]; then
386
+ echo " ✅ $1 → $STATUS"
387
+ else
388
+ echo " ❌ $1 → $STATUS"
389
+ return 1
390
+ fi
391
+ }
392
+
393
+ check_url "${SITE_URL}/"
394
+ check_url "${SITE_URL}/wp-login.php"
395
+ check_url "${SITE_URL}/wp-json/wp/v2/"
396
+ echo "✅ Staging smoke tests passed"
397
+
398
+ - name: Notify Slack (staging)
399
+ if: always()
400
+ env:
401
+ SLACK_WEBHOOK: ${{ secrets.SLACK_WEBHOOK_URL }}
402
+ INSTALL: ${{ secrets.WPE_STAGING_INSTALL }}
403
+ STATUS: ${{ job.status }}
404
+ COMMIT: ${{ github.sha }}
405
+ ACTOR: ${{ github.actor }}
406
+ run: |
407
+ [[ -z "$SLACK_WEBHOOK" ]] && exit 0
408
+ EMOJI=$([[ "$STATUS" == "success" ]] && echo "✅" || echo "❌")
409
+ MSG="${EMOJI} *Staging deploy ${STATUS}* by @${ACTOR}"
410
+ MSG+=" | \`${COMMIT:0:7}\` | <https://${INSTALL}.wpenginepowered.com|${INSTALL}>"
411
+ curl -s -X POST -H 'Content-type: application/json' \
412
+ --data "{\"text\":\"${MSG}\"}" \
413
+ "$SLACK_WEBHOOK"
414
+ ```
415
+
416
+ ---
417
+
418
+ ### `deploy-production.yml` — Production deploy with aggressive guards
419
+
420
+ The full safety chain: source-branch check → pre-deploy snapshot → deploy → smoke test → auto-rollback on failure.
421
+
422
+ ```yaml
423
+ # .github/workflows/deploy-production.yml
424
+ name: Deploy → Production
425
+
426
+ on:
427
+ push:
428
+ branches: [main]
429
+
430
+ concurrency:
431
+ group: deploy-production
432
+ cancel-in-progress: false # NEVER cancel in-flight production deploys
433
+
434
+ jobs:
435
+ # ── Gate: verify FIRST — no deploy without clean code ──────────────────────
436
+ verify:
437
+ name: Verify — full check (--no-verify cannot skip this)
438
+ runs-on: ubuntu-latest
439
+ steps:
440
+ - uses: actions/checkout@v4
441
+ - uses: actions/setup-node@v4
442
+ with: { node-version: '20', cache: 'npm' }
443
+ - run: npm ci
444
+ - run: npm run format:check
445
+ - run: npm run lint
446
+ - run: npm run check
447
+ - run: npm test -- --run
448
+ - run: npm run build
449
+
450
+ # ── Guard: verify merge came from staging ──────────────────────────────────
451
+ production-guard:
452
+ name: Production safety checks
453
+ runs-on: ubuntu-latest
454
+ needs: verify
455
+ outputs:
456
+ previous-sha: ${{ steps.get-sha.outputs.sha }}
457
+ steps:
458
+ - uses: actions/checkout@v4
459
+ with:
460
+ fetch-depth: 2
461
+
462
+ - name: Verify merge source is staging
463
+ run: |
464
+ # Get the merge commit parent — should be from staging
465
+ MERGE_BASE=$(git log --merges --format="%P" -1 | awk '{print $2}')
466
+ if [[ -z "$MERGE_BASE" ]]; then
467
+ # Direct push — check if it matches the last commit on staging
468
+ echo "⚠️ Direct push to main detected (not a merge commit)."
469
+ echo " Direct pushes to main are strongly discouraged."
470
+ echo " Use a PR from staging instead."
471
+ # Allow for emergency hotfixes but log loudly
472
+ fi
473
+ echo "✅ Merge source check complete"
474
+
475
+ - name: Record previous deploy SHA (for rollback)
476
+ id: get-sha
477
+ run: |
478
+ PREV=$(git rev-parse HEAD~1)
479
+ echo "sha=$PREV" >> "$GITHUB_OUTPUT"
480
+ echo "Previous SHA: $PREV"
481
+
482
+ # ─── Deploy ─────────────────────────────────────────────────────────────────
483
+ deploy-production:
484
+ name: Deploy to WP Engine Production
485
+ runs-on: ubuntu-latest
486
+ needs: [verify, production-guard]
487
+ environment:
488
+ name: production
489
+ url: https://${{ secrets.WPE_PROD_INSTALL }}.wpenginepowered.com
490
+ steps:
491
+ - uses: actions/checkout@v4
492
+ with:
493
+ fetch-depth: 0
494
+
495
+ - name: Setup Node & build assets
496
+ uses: actions/setup-node@v4
497
+ with:
498
+ node-version: '20'
499
+ cache: 'npm'
500
+
501
+ - run: npm ci && npm run build
502
+
503
+ - name: Setup SSH
504
+ uses: webfactory/ssh-agent@v0.9.0
505
+ with:
506
+ ssh-private-key: ${{ secrets.WPE_SSH_KEY }}
507
+
508
+ - name: Add WP Engine to known hosts
509
+ run: echo "${{ secrets.WPE_SSH_KNOWN_HOSTS }}" >> ~/.ssh/known_hosts
510
+
511
+ - name: Pre-deploy backup (production — mandatory)
512
+ env:
513
+ INSTALL: ${{ secrets.WPE_PROD_INSTALL }}
514
+ WPE_API_USER: ${{ secrets.WPE_API_USER }}
515
+ WPE_API_PASSWORD: ${{ secrets.WPE_API_PASSWORD }}
516
+ run: |
517
+ echo "📦 Creating production backup — this is required before every deploy..."
518
+ RESPONSE=$(curl -s -w "\n%{http_code}" -X POST \
519
+ -u "${WPE_API_USER}:${WPE_API_PASSWORD}" \
520
+ "https://api.wpengineapi.com/v1/installs/${INSTALL}/backups" \
521
+ -H "Content-Type: application/json" \
522
+ -d "{\"description\":\"Pre-deploy — ${{ github.sha }} — ${{ github.actor }}\",\"notification_emails\":[]}")
523
+ HTTP_CODE=$(echo "$RESPONSE" | tail -1)
524
+ BODY=$(echo "$RESPONSE" | head -1)
525
+ echo "API response: $BODY (HTTP $HTTP_CODE)"
526
+ if [[ "$HTTP_CODE" != "200" && "$HTTP_CODE" != "202" ]]; then
527
+ echo "❌ Backup API call failed (HTTP $HTTP_CODE)"
528
+ echo " Production deploy aborted — backup is mandatory."
529
+ exit 1
530
+ fi
531
+ echo "✅ Backup initiated — proceeding with deploy"
532
+
533
+ - name: Push to WP Engine production
534
+ env:
535
+ INSTALL: ${{ secrets.WPE_PROD_INSTALL }}
536
+ run: |
537
+ git remote add wpe-prod git@git.wpengine.com:production/${INSTALL}.git
538
+ git add -f dist/ build/ 2>/dev/null || true
539
+ git diff --cached --quiet || git commit -m "ci: add built assets [skip ci]"
540
+ git push wpe-prod HEAD:main --force
541
+
542
+ - name: Post-deploy WP-CLI (production)
543
+ env:
544
+ INSTALL: ${{ secrets.WPE_PROD_INSTALL }}
545
+ run: |
546
+ ssh -o StrictHostKeyChecking=no ${INSTALL}@${INSTALL}.ssh.wpengine.net bash -s <<'EOF'
547
+ set -e
548
+ wp cache flush --skip-plugins --skip-themes
549
+ wp rewrite flush --skip-plugins --skip-themes
550
+ wp cron event run --due-now --skip-plugins --skip-themes
551
+ # Report current state
552
+ echo "--- Site health ---"
553
+ wp option get siteurl
554
+ wp core version
555
+ wp plugin list --status=active --format=count
556
+ echo "✅ Post-deploy WP-CLI complete"
557
+ EOF
558
+
559
+ - name: Smoke test (production)
560
+ id: smoke-test
561
+ env:
562
+ INSTALL: ${{ secrets.WPE_PROD_INSTALL }}
563
+ run: |
564
+ # Give WP Engine ~45s to fully propagate
565
+ sleep 45
566
+
567
+ SITE_URL="https://${INSTALL}.wpenginepowered.com"
568
+ FAILED=0
569
+
570
+ check_url() {
571
+ STATUS=$(curl -s -o /dev/null -w "%{http_code}" -L \
572
+ -A "WPE-Deploy-SmokeTest/1.0" --max-time 30 "$1")
573
+ if [[ "$STATUS" -ge 200 && "$STATUS" -lt 400 ]]; then
574
+ echo " ✅ $1 → HTTP $STATUS"
575
+ else
576
+ echo " ❌ $1 → HTTP $STATUS (expected 2xx/3xx)"
577
+ FAILED=1
578
+ fi
579
+ }
580
+
581
+ echo "🧪 Smoke testing production: $SITE_URL"
582
+ check_url "${SITE_URL}/"
583
+ check_url "${SITE_URL}/wp-login.php"
584
+ check_url "${SITE_URL}/wp-json/wp/v2/"
585
+
586
+ if [[ "$FAILED" -ne 0 ]]; then
587
+ echo "❌ Smoke tests FAILED — triggering rollback"
588
+ exit 1
589
+ fi
590
+ echo "✅ All smoke tests passed"
591
+
592
+ - name: Auto-rollback on smoke test failure
593
+ if: failure() && steps.smoke-test.outcome == 'failure'
594
+ env:
595
+ INSTALL: ${{ secrets.WPE_PROD_INSTALL }}
596
+ PREV_SHA: ${{ needs.production-guard.outputs.previous-sha }}
597
+ run: |
598
+ echo "🔄 Smoke test failed — rolling back to $PREV_SHA..."
599
+ git push wpe-prod ${PREV_SHA}:main --force
600
+ echo "✅ Rollback pushed — verifying..."
601
+ sleep 30
602
+ STATUS=$(curl -s -o /dev/null -w "%{http_code}" -L \
603
+ "https://${INSTALL}.wpenginepowered.com/" --max-time 30)
604
+ echo "Post-rollback status: HTTP $STATUS"
605
+ # Flush cache after rollback
606
+ ssh -o StrictHostKeyChecking=no ${INSTALL}@${INSTALL}.ssh.wpengine.net \
607
+ wp cache flush --skip-plugins --skip-themes
608
+ echo "❌ Deployment was ROLLED BACK to $PREV_SHA"
609
+ exit 1 # Mark the job as failed so Slack notifies
610
+
611
+ - name: Notify Slack (production)
612
+ if: always()
613
+ env:
614
+ SLACK_WEBHOOK: ${{ secrets.SLACK_WEBHOOK_URL }}
615
+ INSTALL: ${{ secrets.WPE_PROD_INSTALL }}
616
+ STATUS: ${{ job.status }}
617
+ COMMIT: ${{ github.sha }}
618
+ ACTOR: ${{ github.actor }}
619
+ run: |
620
+ [[ -z "$SLACK_WEBHOOK" ]] && exit 0
621
+ if [[ "$STATUS" == "success" ]]; then
622
+ EMOJI="🚀"
623
+ TEXT="*Production deploy succeeded* by @${ACTOR}"
624
+ else
625
+ EMOJI="🔥"
626
+ TEXT="*Production deploy FAILED* — check logs immediately | @${ACTOR}"
627
+ fi
628
+ MSG="${EMOJI} ${TEXT} | \`${COMMIT:0:7}\` | <https://${INSTALL}.wpenginepowered.com|${INSTALL}>"
629
+ curl -s -X POST -H 'Content-type: application/json' \
630
+ --data "{\"text\":\"${MSG}\"}" \
631
+ "$SLACK_WEBHOOK"
632
+ ```
633
+
634
+ ---
635
+
636
+ ## Safety escalation by environment
637
+
638
+ | Guard | Dev | Staging | Production |
639
+ |---|---|---|---|
640
+ | `--no-verify` skippable? | Never — CI gate catches it | Never — CI gate catches it | Never — CI gate catches it |
641
+ | `verify` job in deploy workflow | ✅ (blocks deploy) | ✅ (blocks deploy) | ✅ (blocks deploy) |
642
+ | PR required | ❌ | ✅ (1 reviewer) | ✅ (2 reviewers) |
643
+ | Must merge from specific branch | ❌ | `develop` | `staging` only |
644
+ | CI gate required status check | ✅ | ✅ | ✅ |
645
+ | Pre-deploy DB backup | ❌ | ✅ API (warn if fail) | ✅ API **(abort if fail)** |
646
+ | Deploy propagation wait | 0s | 30s | 45s |
647
+ | Smoke test URLs | ❌ | `/`, `/wp-login.php`, `/wp-json/` | `/`, `/wp-login.php`, `/wp-json/` |
648
+ | Auto-rollback | ❌ | ❌ | ✅ push `HEAD~1` + cache flush |
649
+ | Concurrency: cancel in-progress | ✅ | ❌ | ❌ |
650
+ | Slack notification | ❌ | ✅ | ✅ (pings channel on failure) |
651
+
652
+ ---
653
+
654
+ ## Built assets: the common gotcha
655
+
656
+ WordPress themes/plugins often have build pipelines (`npm run build`) that output to `dist/` or `build/` — directories that are **gitignored** in the source repo. WP Engine deploys exactly what you push, so gitignored files aren't deployed.
657
+
658
+ The workflows above handle this by running `npm run build` in CI and then force-adding the output before pushing to WP Engine:
659
+
660
+ ```yaml
661
+ - run: npm ci && npm run build
662
+ # ...
663
+ - run: |
664
+ git add -f dist/ build/ 2>/dev/null || true
665
+ git diff --cached --quiet || git commit -m "ci: add built assets [skip ci]"
666
+ git push wpe-prod HEAD:main --force
667
+ ```
668
+
669
+ > `[skip ci]` on the build commit prevents GitHub Actions from triggering again on that commit.
670
+
671
+ For Composer vendor: if your WP Engine environment doesn't run `composer install` on deploy (most don't), also add:
672
+ ```yaml
673
+ git add -f vendor/ 2>/dev/null || true
674
+ ```
675
+
676
+ ---
677
+
678
+ ## Manual rollback procedures
679
+
680
+ **Fast rollback (previous commit):**
681
+ ```bash
682
+ # From your local machine
683
+ git push wpe-prod main~1:main --force
684
+ ```
685
+
686
+ **WP Engine API rollback (to a specific snapshot):**
687
+ ```bash
688
+ # List available backups
689
+ curl -s -u "$WPE_API_USER:$WPE_API_PASSWORD" \
690
+ "https://api.wpengineapi.com/v1/installs/${INSTALL}/backups" | jq '.results[].id,.results[].created_at'
691
+
692
+ # Restore a specific backup (by ID)
693
+ curl -s -X POST \
694
+ -u "$WPE_API_USER:$WPE_API_PASSWORD" \
695
+ "https://api.wpengineapi.com/v1/installs/${INSTALL}/backups/${BACKUP_ID}/restore"
696
+ ```
697
+
698
+ **Emergency: revert code + flush:**
699
+ ```bash
700
+ # Push the previous commit
701
+ git push wpe-prod HEAD~1:main --force
702
+
703
+ # Flush via SSH gateway
704
+ ssh <install>@<install>.ssh.wpengine.net \
705
+ wp cache flush --skip-plugins --skip-themes
706
+ ```
707
+
708
+ ---
709
+
710
+ ## Adding custom smoke test URLs
711
+
712
+ Expand the smoke test section with your site's key pages:
713
+
714
+ ```yaml
715
+ check_url "${SITE_URL}/"
716
+ check_url "${SITE_URL}/about/"
717
+ check_url "${SITE_URL}/contact/"
718
+ check_url "${SITE_URL}/shop/" # WooCommerce
719
+ check_url "${SITE_URL}/wp-json/wp/v2/" # REST API
720
+ # Check no PHP errors in homepage body
721
+ BODY=$(curl -sL "${SITE_URL}/")
722
+ if echo "$BODY" | grep -qi "fatal error\|parse error\|warning:"; then
723
+ echo "❌ PHP errors detected in homepage body"
724
+ FAILED=1
725
+ fi
726
+ ```
727
+
728
+ ---
729
+
730
+ ## References
731
+
732
+ - WP Engine git push docs: `https://wpengine.com/support/git-version-control/`
733
+ - WP Engine API reference: `https://wpengineapi.com/`
734
+ - GitHub Actions `webfactory/ssh-agent`: `https://github.com/webfactory/ssh-agent`
735
+ - WP-CLI remote SSH: `https://make.wordpress.org/cli/handbook/guides/running-commands-remotely/`
736
+ - Kris Jordan (2013) — foundational push-to-deploy pattern: `https://krisjordan.com/blog/2013/11/02/push-to-deploy-with-git`