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.
- package/.agents/skills/wp-bootstrap/SKILL.md +314 -0
- package/.agents/skills/wp-bootstrap/references/composer-setup.md +275 -0
- package/.agents/skills/wp-bootstrap/references/monorepo-patterns.md +184 -0
- package/.agents/skills/wp-bootstrap/scripts/bootstrap.sh +151 -0
- package/.agents/skills/wp-bootstrap/scripts/detect-structure.mjs +466 -0
- package/.agents/skills/wp-bootstrap/scripts/package-wp.sh +173 -0
- package/.agents/skills/wp-bootstrap/scripts/playground-start.sh +148 -0
- package/.agents/skills/wp-bootstrap/scripts/playground-verify.sh +165 -0
- package/.agents/skills/wp-bootstrap/scripts/setup-github.sh +417 -0
- package/{.github → .agents}/skills/wp-wpcli-and-ops/SKILL.md +11 -9
- package/.agents/skills/wp-wpengine/SKILL.md +462 -0
- package/.agents/skills/wp-wpengine/references/ci-gate.md +469 -0
- package/.agents/skills/wp-wpengine/references/github-actions-deploy.md +743 -0
- package/.agents/skills/wp-wpengine/scripts/ci-gate.sh +118 -0
- package/.agents/skills/wp-wpengine/scripts/wpe-check.sh +89 -0
- package/.agents/skills/wp-wpengine/scripts/wpe-preflight.sh +104 -0
- package/.github/agents/wp-architect.agent.md +1 -2
- package/.github/copilot-instructions.md +1 -1
- package/.github/instructions/wordpress-workflow.instructions.md +3 -3
- package/AGENTS.md +22 -10
- package/AGENTS.template.md +20 -10
- package/README.md +89 -85
- package/dist/cli.js +7 -1
- package/dist/commands/bootstrap.js +105 -0
- package/dist/commands/clean-skills.js +64 -0
- package/dist/commands/setup.js +6 -2
- package/dist/commands/sync-skills.js +3 -0
- package/dist/lib/api.js +165 -5
- package/dist/lib/bootstrap.js +352 -0
- package/dist/lib/installer.js +166 -2
- package/extensions/wp-agent-kit/index.ts +325 -10
- package/package.json +10 -14
- package/skills-custom/wp-bootstrap/SKILL.md +314 -0
- package/skills-custom/wp-bootstrap/references/composer-setup.md +275 -0
- package/skills-custom/wp-bootstrap/references/monorepo-patterns.md +184 -0
- package/skills-custom/wp-bootstrap/scripts/bootstrap.sh +151 -0
- package/skills-custom/wp-bootstrap/scripts/detect-structure.mjs +466 -0
- package/skills-custom/wp-bootstrap/scripts/package-wp.sh +173 -0
- package/skills-custom/wp-bootstrap/scripts/playground-start.sh +148 -0
- package/skills-custom/wp-bootstrap/scripts/playground-verify.sh +165 -0
- package/skills-custom/wp-bootstrap/scripts/setup-github.sh +417 -0
- package/skills-custom/wp-wpengine/SKILL.md +362 -27
- package/skills-custom/wp-wpengine/references/ci-gate.md +469 -0
- package/skills-custom/wp-wpengine/references/github-actions-deploy.md +743 -0
- package/skills-custom/wp-wpengine/scripts/ci-gate.sh +118 -0
- package/skills-custom/wp-wpengine/scripts/wpe-check.sh +89 -0
- package/skills-custom/wp-wpengine/scripts/wpe-preflight.sh +104 -0
- package/.github/skills/wp-wpengine/SKILL.md +0 -127
- package/.github/workflows/ci.yml +0 -44
- package/.husky/pre-commit +0 -7
- package/CLI_REVIEW.md +0 -250
- package/biome.json +0 -39
- /package/{.github → .agents}/skills/blueprint/SKILL.md +0 -0
- /package/{.github → .agents}/skills/wordpress-router/SKILL.md +0 -0
- /package/{.github → .agents}/skills/wordpress-router/references/decision-tree.md +0 -0
- /package/{.github → .agents}/skills/wp-abilities-api/SKILL.md +0 -0
- /package/{.github → .agents}/skills/wp-abilities-api/references/delegate-helper-pattern.md +0 -0
- /package/{.github → .agents}/skills/wp-abilities-api/references/domain-vs-projection.md +0 -0
- /package/{.github → .agents}/skills/wp-abilities-api/references/error-code-vocabulary.md +0 -0
- /package/{.github → .agents}/skills/wp-abilities-api/references/grouping-heuristic.md +0 -0
- /package/{.github → .agents}/skills/wp-abilities-api/references/input-schema-gotchas.md +0 -0
- /package/{.github → .agents}/skills/wp-abilities-api/references/php-registration.md +0 -0
- /package/{.github → .agents}/skills/wp-abilities-api/references/plugin-family-patterns.md +0 -0
- /package/{.github → .agents}/skills/wp-abilities-api/references/rest-api.md +0 -0
- /package/{.github → .agents}/skills/wp-abilities-api/references/shared-core-service.md +0 -0
- /package/{.github → .agents}/skills/wp-abilities-audit/SKILL.md +0 -0
- /package/{.github → .agents}/skills/wp-abilities-audit/references/audit-schema.md +0 -0
- /package/{.github → .agents}/skills/wp-abilities-audit/references/capability-gate-tracing.md +0 -0
- /package/{.github → .agents}/skills/wp-abilities-audit/references/controller-enumeration.md +0 -0
- /package/{.github → .agents}/skills/wp-abilities-verify/SKILL.md +0 -0
- /package/{.github → .agents}/skills/wp-abilities-verify/references/annotation-correctness.md +0 -0
- /package/{.github → .agents}/skills/wp-abilities-verify/references/audit-schema-validation.md +0 -0
- /package/{.github → .agents}/skills/wp-abilities-verify/references/permission-roundtrip.md +0 -0
- /package/{.github → .agents}/skills/wp-abilities-verify/references/runtime-harness.md +0 -0
- /package/{.github → .agents}/skills/wp-abilities-verify/references/schema-lints.md +0 -0
- /package/{.github → .agents}/skills/wp-abilities-verify/references/static-enumeration.md +0 -0
- /package/{.github → .agents}/skills/wp-block-development/SKILL.md +0 -0
- /package/{.github → .agents}/skills/wp-block-development/references/attributes-and-serialization.md +0 -0
- /package/{.github → .agents}/skills/wp-block-development/references/block-json.md +0 -0
- /package/{.github → .agents}/skills/wp-block-development/references/creating-new-blocks.md +0 -0
- /package/{.github → .agents}/skills/wp-block-development/references/debugging.md +0 -0
- /package/{.github → .agents}/skills/wp-block-development/references/deprecations.md +0 -0
- /package/{.github → .agents}/skills/wp-block-development/references/dynamic-rendering.md +0 -0
- /package/{.github → .agents}/skills/wp-block-development/references/inner-blocks.md +0 -0
- /package/{.github → .agents}/skills/wp-block-development/references/registration.md +0 -0
- /package/{.github → .agents}/skills/wp-block-development/references/supports-and-wrappers.md +0 -0
- /package/{.github → .agents}/skills/wp-block-development/references/tooling-and-testing.md +0 -0
- /package/{.github → .agents}/skills/wp-block-development/scripts/list_blocks.mjs +0 -0
- /package/{.github → .agents}/skills/wp-block-themes/SKILL.md +0 -0
- /package/{.github → .agents}/skills/wp-block-themes/references/creating-new-block-theme.md +0 -0
- /package/{.github → .agents}/skills/wp-block-themes/references/debugging.md +0 -0
- /package/{.github → .agents}/skills/wp-block-themes/references/patterns.md +0 -0
- /package/{.github → .agents}/skills/wp-block-themes/references/style-variations.md +0 -0
- /package/{.github → .agents}/skills/wp-block-themes/references/templates-and-parts.md +0 -0
- /package/{.github → .agents}/skills/wp-block-themes/references/theme-json.md +0 -0
- /package/{.github → .agents}/skills/wp-block-themes/scripts/detect_block_themes.mjs +0 -0
- /package/{.github → .agents}/skills/wp-interactivity-api/SKILL.md +0 -0
- /package/{.github → .agents}/skills/wp-interactivity-api/references/debugging.md +0 -0
- /package/{.github → .agents}/skills/wp-interactivity-api/references/directives-quickref.md +0 -0
- /package/{.github → .agents}/skills/wp-interactivity-api/references/server-side-rendering.md +0 -0
- /package/{.github → .agents}/skills/wp-performance/SKILL.md +0 -0
- /package/{.github → .agents}/skills/wp-performance/references/autoload-options.md +0 -0
- /package/{.github → .agents}/skills/wp-performance/references/cron.md +0 -0
- /package/{.github → .agents}/skills/wp-performance/references/database.md +0 -0
- /package/{.github → .agents}/skills/wp-performance/references/http-api.md +0 -0
- /package/{.github → .agents}/skills/wp-performance/references/measurement.md +0 -0
- /package/{.github → .agents}/skills/wp-performance/references/object-cache.md +0 -0
- /package/{.github → .agents}/skills/wp-performance/references/query-monitor-headless.md +0 -0
- /package/{.github → .agents}/skills/wp-performance/references/server-timing.md +0 -0
- /package/{.github → .agents}/skills/wp-performance/references/wp-cli-doctor.md +0 -0
- /package/{.github → .agents}/skills/wp-performance/references/wp-cli-profile.md +0 -0
- /package/{.github → .agents}/skills/wp-performance/scripts/perf_inspect.mjs +0 -0
- /package/{.github → .agents}/skills/wp-phpstan/SKILL.md +0 -0
- /package/{.github → .agents}/skills/wp-phpstan/references/configuration.md +0 -0
- /package/{.github → .agents}/skills/wp-phpstan/references/third-party-classes.md +0 -0
- /package/{.github → .agents}/skills/wp-phpstan/references/wordpress-annotations.md +0 -0
- /package/{.github → .agents}/skills/wp-phpstan/scripts/phpstan_inspect.mjs +0 -0
- /package/{.github → .agents}/skills/wp-playground/SKILL.md +0 -0
- /package/{.github → .agents}/skills/wp-playground/references/blueprints.md +0 -0
- /package/{.github → .agents}/skills/wp-playground/references/cli-commands.md +0 -0
- /package/{.github → .agents}/skills/wp-playground/references/debugging.md +0 -0
- /package/{.github → .agents}/skills/wp-playground/references/e2e-playwright.md +0 -0
- /package/{.github → .agents}/skills/wp-plugin-development/SKILL.md +0 -0
- /package/{.github → .agents}/skills/wp-plugin-development/references/data-and-cron.md +0 -0
- /package/{.github → .agents}/skills/wp-plugin-development/references/debugging.md +0 -0
- /package/{.github → .agents}/skills/wp-plugin-development/references/lifecycle.md +0 -0
- /package/{.github → .agents}/skills/wp-plugin-development/references/security.md +0 -0
- /package/{.github → .agents}/skills/wp-plugin-development/references/settings-api.md +0 -0
- /package/{.github → .agents}/skills/wp-plugin-development/references/structure.md +0 -0
- /package/{.github → .agents}/skills/wp-plugin-development/scripts/detect_plugins.mjs +0 -0
- /package/{.github → .agents}/skills/wp-plugin-directory-guidelines/SKILL.md +0 -0
- /package/{.github → .agents}/skills/wp-plugin-directory-guidelines/references/gpl-compliance.md +0 -0
- /package/{.github → .agents}/skills/wp-plugin-directory-guidelines/references/guideline-review-checklist.md +0 -0
- /package/{.github → .agents}/skills/wp-plugin-directory-guidelines/references/naming-rules.md +0 -0
- /package/{.github → .agents}/skills/wp-project-triage/SKILL.md +0 -0
- /package/{.github → .agents}/skills/wp-project-triage/references/triage.schema.json +0 -0
- /package/{.github → .agents}/skills/wp-project-triage/scripts/detect_wp_project.mjs +0 -0
- /package/{.github → .agents}/skills/wp-rest-api/SKILL.md +0 -0
- /package/{.github → .agents}/skills/wp-rest-api/references/authentication.md +0 -0
- /package/{.github → .agents}/skills/wp-rest-api/references/custom-content-types.md +0 -0
- /package/{.github → .agents}/skills/wp-rest-api/references/discovery-and-params.md +0 -0
- /package/{.github → .agents}/skills/wp-rest-api/references/responses-and-fields.md +0 -0
- /package/{.github → .agents}/skills/wp-rest-api/references/routes-and-endpoints.md +0 -0
- /package/{.github → .agents}/skills/wp-rest-api/references/schema.md +0 -0
- /package/{.github → .agents}/skills/wp-wpcli-and-ops/references/automation.md +0 -0
- /package/{.github → .agents}/skills/wp-wpcli-and-ops/references/cron-and-cache.md +0 -0
- /package/{.github → .agents}/skills/wp-wpcli-and-ops/references/debugging.md +0 -0
- /package/{.github → .agents}/skills/wp-wpcli-and-ops/references/multisite.md +0 -0
- /package/{.github → .agents}/skills/wp-wpcli-and-ops/references/packages-and-updates.md +0 -0
- /package/{.github → .agents}/skills/wp-wpcli-and-ops/references/safety.md +0 -0
- /package/{.github → .agents}/skills/wp-wpcli-and-ops/references/search-replace.md +0 -0
- /package/{.github → .agents}/skills/wp-wpcli-and-ops/scripts/wpcli_inspect.mjs +0 -0
- /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`
|