wordpress-agent-kit 0.4.0 → 0.6.0

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