Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .claude-plugin/marketplace.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,14 @@
"url": "https://themegrill.com"
},
"description": "Internal Claude Code plugins for ThemeGrill engineering",
"version": "1.7.0",
"version": "1.7.1",
"plugins": [
{
"name": "claudegrill",
"source": "./plugins/claudegrill",
"displayName": "Claudegrill",
"description": "Set a product up, write a fix to house standards, verify it, review a PR, or sweep a whole product before release.",
"version": "1.7.0",
"version": "1.7.1",
"author": {
"name": "ThemeGrill Engineering"
},
Expand Down
30 changes: 30 additions & 0 deletions .github/workflows/pro-suite.yml
Original file line number Diff line number Diff line change
Expand Up @@ -695,6 +695,28 @@ jobs:
restore-keys: |
qa-deps-${{ runner.os }}-

# boot-wp.mjs's own downloads, separate from the product's — see the long
# comment on the same step in suite.yml for why each path is here and why
# `sites/` is deliberately excluded (it is the extracted, BOOTED WordPress
# tree, reused across boots with the same mount config; caching it in CI
# would let a stale site from a previous PR's run leak into this one,
# which is the opposite of what a disposable pro boot is for).
#
# Keyed on boot-wp.mjs's own hash: this cache's only reason to invalidate
# is the pinned `@wp-playground/cli` version changing, a different
# lifecycle from the pnpm/Playwright cache above.
- name: Cache Playground's own downloads (WordPress core, the CLI's dependencies)
if: steps.scope.outputs.run == 'true'
uses: actions/cache@v4
with:
path: |
~/.npm/_cacache
~/.wordpress-playground
!~/.wordpress-playground/sites
key: qa-playground-${{ runner.os }}-${{ hashFiles('claudegrill/plugins/claudegrill/scripts/boot-wp.mjs') }}
restore-keys: |
qa-playground-${{ runner.os }}-

- name: Look for a suite manifest
if: steps.scope.outputs.run == 'true'
id: manifest
Expand Down Expand Up @@ -742,6 +764,14 @@ jobs:
TGQA_LICENSE_ALLCOACH_PRO: ${{ secrets.TGQA_LICENSE_KEY }}
TGQA_LICENSE_MAGAZINE_BLOCKS_PRO: ${{ secrets.TGQA_LICENSE_KEY }}
TGQA_LICENSE_BLOCKART_BLOCKS_PRO: ${{ secrets.TGQA_LICENSE_KEY }}
TGQA_LICENSE_SPACIOUS_PRO: ${{ secrets.TGQA_LICENSE_KEY }}
TGQA_LICENSE_ACCELERATE_PRO: ${{ secrets.TGQA_LICENSE_KEY }}
TGQA_LICENSE_FLASH_PRO: ${{ secrets.TGQA_LICENSE_KEY }}
TGQA_LICENSE_RADIATE_PRO: ${{ secrets.TGQA_LICENSE_KEY }}
TGQA_LICENSE_CENOTE_PRO: ${{ secrets.TGQA_LICENSE_KEY }}
TGQA_LICENSE_HIMALAYAS_PRO: ${{ secrets.TGQA_LICENSE_KEY }}
TGQA_LICENSE_AMPLE_PRO: ${{ secrets.TGQA_LICENSE_KEY }}
TGQA_LICENSE_ESTORE_PRO: ${{ secrets.TGQA_LICENSE_KEY }}
run: |
set -o pipefail
# The `unlicensed` mode boots the SAME pro code with no licence. That
Expand Down
37 changes: 37 additions & 0 deletions .github/workflows/suite.yml
Original file line number Diff line number Diff line change
Expand Up @@ -498,6 +498,43 @@ jobs:
restore-keys: |
qa-deps-${{ runner.os }}-

# boot-wp.mjs's own downloads, separate from the product's. Two of them:
#
# ~/.npm/_cacache npm's content-addressable cache. Confirmed by
# reading a real install on disk: the pinned
# `@wp-playground/cli` pulls in @php-wasm's PHP
# binaries for every version it bundles, ~300MB,
# none of which is on a fresh runner.
# ~/.wordpress-playground Playground's OWN disk cache for the
# WordPress core it downloads at boot — confirmed
# by reading `@wp-playground/cli`'s source: it
# skips the network fetch outright when
# `<version>.zip` already exists here.
#
# `sites/` under that second path is excluded on purpose. It is NOT a
# download cache — it is the extracted, booted WordPress tree, keyed by
# the mount config and REUSED across boots with the same key (see the
# blueprint-idempotency fix in CLAUDE.md: a second boot of a cached site
# hit "term already exists"). On a developer's machine that persistence
# is the point. In CI it would do the opposite of what a disposable site
# is for: a stale site from a previous run's PR would survive into this
# one.
#
# Keyed on boot-wp.mjs's own hash, not the lockfile above — this cache's
# only reason to invalidate is the pinned CLI version changing, which is
# a different lifecycle from the product's pnpm/Playwright dependencies.
- name: Cache Playground's own downloads (WordPress core, the CLI's dependencies)
if: steps.scope.outputs.run == 'true'
uses: actions/cache@v4
with:
path: |
~/.npm/_cacache
~/.wordpress-playground
!~/.wordpress-playground/sites
key: qa-playground-${{ runner.os }}-${{ hashFiles('claudegrill/plugins/claudegrill/scripts/boot-wp.mjs') }}
restore-keys: |
qa-playground-${{ runner.os }}-

- name: Look for a suite manifest
if: steps.scope.outputs.run == 'true'
id: manifest
Expand Down
47 changes: 47 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -617,6 +617,53 @@ decision in the suite layer.
org billing page) — the per-job "Report elapsed minutes" step already
reports to the run summary, but nothing rolls that up across runs or repos.

- **boot-wp.mjs's own downloads were invisible and uncached, on top of
everything the existing pnpm/Playwright cache already covers — found by
reading a real `npx` install on this machine, not by guessing.** The
existing cache step only ever covered the PRODUCT's dependencies. What
`npx --yes @wp-playground/cli@latest` pulls in on a cold runner was never
measured until now:
- `@php-wasm/*`, which the CLI bundles PHP.wasm binaries for every supported
PHP version through, is **~300MB** on disk — measured directly (`du -sh`
across every `.wasm` file under a real `@wp-playground` install), not
estimated.
- Playground has its OWN persistent download cache, `~/.wordpress-playground`
— found by reading `@wp-playground/cli` v3.1.53's source (`We()`: `existsSync(n)
|| download(...)`, keyed on `<version>.zip`) and then confirmed on disk: a
real `7.1.zip` (37MB) sitting there from a previous local boot. On a
developer's machine this already makes every boot after the first free;
on a GitHub Actions runner, which starts empty every job, it has never
once been reused.
- `~/.wordpress-playground/sites/<hash>` is a THIRD thing living at that same
path and must NOT be cached: it is the extracted, booted WordPress tree
itself, keyed by mount config and reused across boots with the same key —
the exact mechanism behind the blueprint-idempotency bug recorded above
(`wp term create` failing "already exists" on a second boot of a cached
site). Caching it in CI would let a stale site from one PR's run leak into
another's, which is the opposite of what a disposable boot is for.
Confirmed by inspecting real `sites/` entries on disk: full `wp-admin`/
`wp-includes`/`wp-config.php` trees, not download artefacts.

Two changes:
- `@wp-playground/cli` is now pinned (`3.1.53`) instead of `@latest`, for the
same reason `@playwright/test` is pinned elsewhere in this repo: `@latest`
means CI silently starts running a different Playground release the day
one ships, and it is also what makes a cache key meaningless — a key
cannot promise it matches what was actually resolved when the resolution
itself floats.
- Both workflows gained a second `actions/cache` step, keyed on
`boot-wp.mjs`'s own hash rather than the product's lockfile (a different
invalidation lifecycle), covering `~/.npm/_cacache` and
`~/.wordpress-playground` with `sites/` excluded via a `!` glob.

**Not verified**: no live CI run has happened, so the cache-hit path — a
second run reusing what a first run saved — is unproven. What IS verified:
the pinned version resolves and runs via `npx` (confirmed locally), both
workflow files parse, and every path/exclusion above was read from real
source and real directories on disk, not assumed. The `!` exclusion glob
inside a multi-line `path:` is a documented `@actions/glob` pattern but has
not been observed working inside an actual `actions/cache` run in this repo.

- **Three faults in this repo's own cost controls, found by them failing.**
Worth recording because each one turned a diagnosable failure into an opaque
one:
Expand Down
16 changes: 16 additions & 0 deletions PRO.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,14 @@ Where a row is incomplete it says so; nothing here is inferred.
| **AllCoach Pro** | `themegrill/allcoach-pro` | **ThemeGrill SDK** | `https://api.themegrill.com/licenses/`, item_id `58` | `allcoach_pro_license` | `allcoach_pro_license_status` | `LicenseManager::activate()` — `src/Pro/Core/License/LicenseManager.php:39` → `ThemeGrillSDK\Modules\Licenser::activate()` at `vendor/themegrill/themegrill-sdk/src/Modules/Licenser.php:94` |
| **Magazine Blocks Pro** | `themegrill/magazine-blocks-pro` | **Freemius** | id `15516`, slug `magazine-blocks-pro`, `pk_887f5d98b26b4d26ee2253d83ee8e` | Freemius `fs_accounts` | Freemius `fs_accounts` | `magazine_blocks_pro_freemius()` — `magazine-blocks-pro.php:132`, wrapping `fs_dynamic_init()` at `:142` |
| **BlockArt Blocks Pro** | `themegrill/blockart-blocks-pro` | **Freemius** | id `15515`, slug `blockart-blocks-pro`, `pk_4acfc44f72d6c0d7bc6abd186ae44` | Freemius `fs_accounts` | Freemius `fs_accounts` | `blockart_pro_freemius()` — `blockart-pro.php:132`, wrapping `fs_dynamic_init()` at `:142` |
| **Spacious Pro** | `themegrill/spacious-pro` | **Freemius** | id `4217`, slug `spacious`, `pk_38fe5c4b7d4937b449e2899a2f8fd` | Freemius `fs_accounts` | Freemius `fs_accounts` | `FS_ThemeGrill::init()` — `functions.php:362`, wrapping `fs_dynamic_init()` at `functions.php:317` |
| **Accelerate Pro** | `themegrill/accelerate-pro` | **Freemius** | id `4215`, slug `accelerate`, `pk_6a52bfb38f6bc816da4643b9ebbdc` | Freemius `fs_accounts` | Freemius `fs_accounts` | `FS_ThemeGrill::init()` — `functions.php:330`, wrapping `fs_dynamic_init()` at `functions.php:285` |
| **Flash Pro** | `themegrill/flash-pro` | **Freemius** | id `4223`, slug `flash`, `pk_9b5963cad22158476546c3b6b9d74` | Freemius `fs_accounts` | Freemius `fs_accounts` | `FS_ThemeGrill::init()` — `functions.php:738`, wrapping `fs_dynamic_init()` at `functions.php:693` |
| **Radiate Pro** | `themegrill/radiate-pro` | **Freemius** | id `4214`, slug `radiate`, `pk_0f1958ed39075ce23559b1c1b48dc` | Freemius `fs_accounts` | Freemius `fs_accounts` | `FS_ThemeGrill::init()` — `functions.php:539`, wrapping `fs_dynamic_init()` at `functions.php:494` |
| **Cenote Pro** | `themegrill/cenote-pro` | **Freemius** | id `4224`, slug `cenote`, `pk_b9adb96523e7353dd99ce5f0beedb` | Freemius `fs_accounts` | Freemius `fs_accounts` | `FS_ThemeGrill::init()` — `functions.php:589`, wrapping `fs_dynamic_init()` at `functions.php:544` |
| **Himalayas Pro** | `themegrill/himalayas-pro` | **Freemius** | id `4210`, slug `himalayas`, `pk_915eca4fa2c0282a4959df24c9207` | Freemius `fs_accounts` | Freemius `fs_accounts` | `FS_ThemeGrill::init()` — `functions.php:243`, wrapping `fs_dynamic_init()` at `functions.php:200` |
| **Ample Pro** | `themegrill/ample-pro` | **Freemius** | id `4213`, slug `ample`, `pk_8cdee14ea6e7fc9263af2ec1a0014` | Freemius `fs_accounts` | Freemius `fs_accounts` | `FS_ThemeGrill::init()` — `functions.php:261`, wrapping `fs_dynamic_init()` at `functions.php:218` |
| **eStore Pro** | `themegrill/estore-pro` | **Freemius** | id `4220`, slug `estore`, `pk_4254cbdc5e2a6d66e77a0525284b9` | Freemius `fs_accounts` | Freemius `fs_accounts` | `FS_ThemeGrill::init()` — `functions.php:558`, wrapping `fs_dynamic_init()` at `functions.php:513` |

### How each product gates a premium feature

Expand All @@ -50,6 +58,14 @@ This differs per product and is what a `@pro` spec ultimately asserts against.
| AllCoach Pro | `get_option('allcoach_pro_license_status') === 'valid'` |
| Magazine Blocks Pro | `magazine_blocks_pro_freemius()->can_use_premium_code()` |
| BlockArt Blocks Pro | `blockart_pro_freemius()->can_use_premium_code()` |
| Spacious Pro | `FS_ThemeGrill::freemius()->can_use_premium_code()` |
| Accelerate Pro | `FS_ThemeGrill::freemius()->can_use_premium_code()` |
| Flash Pro | `FS_ThemeGrill::freemius()->can_use_premium_code()` |
| Radiate Pro | `FS_ThemeGrill::freemius()->can_use_premium_code()` |
| Cenote Pro | `FS_ThemeGrill::freemius()->can_use_premium_code()` |
| Himalayas Pro | `FS_ThemeGrill::freemius()->can_use_premium_code()` |
| Ample Pro | `FS_ThemeGrill::freemius()->can_use_premium_code()` |
| eStore Pro | `FS_ThemeGrill::freemius()->can_use_premium_code()` |

Those last two are a **fourth expression shape** — a plain global function, not
the `Class::method` accessor ColorMag and Zakra publish. Both mu-plugins match
Expand Down
Loading
Loading