fix(write): pluggable normalization framework — repair invalid block markup on every write path #152
Workflow file for this run
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: Tests | |
| on: | |
| pull_request: | |
| push: | |
| branches: [main] | |
| workflow_dispatch: | |
| inputs: | |
| run_integration: | |
| description: 'Run live WordPress integration tests (requires secrets)' | |
| required: false | |
| default: 'false' | |
| type: boolean | |
| jobs: | |
| php: | |
| name: PHP plugin (PHPUnit, real WP + SQLite) | |
| runs-on: ubuntu-latest | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| # 7.4 is the documented floor (`Requires PHP: 7.4` in the plugin | |
| # header); the modern versions are what the typical agent host | |
| # actually runs. Both rails must stay green to ship. | |
| php-version: ['7.4', '8.2', '8.3', '8.4'] | |
| defaults: | |
| run: | |
| working-directory: wordpress-plugin/gk-block-mcp | |
| steps: | |
| - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1 | |
| - name: Setup PHP | |
| uses: shivammathur/setup-php@f3e473d116dcccaddc5834248c87452386958240 # 2.37.2 | |
| with: | |
| php-version: ${{ matrix.php-version }} | |
| tools: composer:v2 | |
| coverage: none | |
| ini-values: error_reporting=E_ALL, memory_limit=512M | |
| # Composer's global download cache. Keyed on composer.lock so the | |
| # cache busts only when dependencies actually change. | |
| - name: Resolve Composer cache directory | |
| id: composer-cache | |
| run: echo "dir=$(composer config cache-files-dir)" >> "$GITHUB_OUTPUT" | |
| - name: Cache Composer downloads | |
| uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0 | |
| with: | |
| path: ${{ steps.composer-cache.outputs.dir }} | |
| key: composer-${{ matrix.php-version }}-${{ hashFiles('wordpress-plugin/gk-block-mcp/composer.lock') }} | |
| restore-keys: | | |
| composer-${{ matrix.php-version }}- | |
| # vendor/ itself, so the install step is a no-op when composer.lock | |
| # AND the scripts that shape vendor content are all unchanged. The | |
| # SQLite installer's hash is part of the key because it decides which | |
| # commit of the upstream SQLite plugin gets cloned; the post-install | |
| # script's hash is part of the key because it patches vendor/ in place | |
| # (Foundation's PHP 8.4 implicit-nullable fixes), so a cached vendor/ | |
| # built by an older script would silently miss those patches. | |
| - name: Cache vendor/ | |
| id: vendor-cache | |
| uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0 | |
| with: | |
| path: wordpress-plugin/gk-block-mcp/vendor | |
| key: vendor-${{ matrix.php-version }}-${{ hashFiles('wordpress-plugin/gk-block-mcp/composer.lock', 'wordpress-plugin/gk-block-mcp/tests/install-sqlite-dropin.php', 'wordpress-plugin/gk-block-mcp/composer_post_update_install.sh') }} | |
| - name: Install dependencies | |
| if: steps.vendor-cache.outputs.cache-hit != 'true' | |
| env: | |
| # GravityKit Foundation is a runtime require resolved from the PRIVATE | |
| # GravityKit/Foundation repo. Composer authenticates via this token to | |
| # clone it. Configure a repo/org Actions secret FOUNDATION_REPO_TOKEN = | |
| # a PAT with read access to GravityKit/Foundation. (The Foundation boot | |
| # itself is disabled under PHPUnit via GK_BLOCK_MCP_DISABLE_FOUNDATION, | |
| # but composer still has to resolve the dependency tree to install.) | |
| COMPOSER_AUTH: '{"github-oauth":{"github.com":"${{ secrets.FOUNDATION_REPO_TOKEN }}"}}' | |
| run: composer install --prefer-dist --no-progress --no-interaction | |
| # composer post-install runs the SQLite installer only on a fresh | |
| # install; if vendor/ came from cache, re-run it explicitly so | |
| # both paths produce an identical state. | |
| - name: Ensure SQLite drop-in is installed | |
| run: composer run-script install-sqlite-dropin | |
| - name: Run PHPUnit (single-site) | |
| run: vendor/bin/phpunit -c tests/phpunit.xml --colors=never | |
| # The yoast group is excluded from the single-site config (it needs a | |
| # loaded Yoast SEO plugin). This config sets GK_LOAD_YOAST so the | |
| # bootstrap loads Yoast and runs exactly that group — keeping Yoast's DB | |
| # migrations and wp_is_block_theme() call out of the general suite's | |
| # process-isolated children. | |
| - name: Run PHPUnit (Yoast — yoast group) | |
| run: vendor/bin/phpunit -c tests/phpunit/yoast.xml --colors=never | |
| # The ms-required group is excluded from the single-site config (it would | |
| # self-skip there). The multisite config runs exactly that group under a | |
| # real multisite install, so the multisite-only contracts (per-blog cap | |
| # provisioning, per-blog agent purge) are exercised in CI, not skipped. | |
| - name: Run PHPUnit (multisite — ms-required group) | |
| run: vendor/bin/phpunit -c tests/phpunit/multisite.xml --colors=never | |
| # phpcs gate. We carry zero errors AND zero warnings on the includes/ | |
| # tree (see phpcs.xml.dist); CI enforces that contract so the next PR | |
| # can't regress it silently. | |
| - name: Run PHPCS | |
| run: composer lint | |
| ts: | |
| name: TypeScript MCP server (Vitest) | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1 | |
| - uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0 | |
| with: | |
| node-version: '20' | |
| cache: 'npm' | |
| - run: npm ci | |
| # esbuild bundles without type-checking and Vitest runs without | |
| # surfacing type errors either; tsc --noEmit against tsconfig.build.json | |
| # (which excludes src/__tests__) is the only gate that catches TS | |
| # regressions in shipped code before they reach a build. | |
| - name: Type check (production code) | |
| run: npx tsc --noEmit -p tsconfig.build.json | |
| - run: npm test | |
| - run: npm run build | |
| # The committed dist/index.cjs and the plugin-embedded copy | |
| # (assets/mcp-server/index.cjs, written by postbuild) are shipped | |
| # artifacts. A fresh build off the locked esbuild must reproduce them | |
| # byte-for-byte; a diff means a source change was committed without | |
| # rebuilding, so npx users and Claude Desktop users would run different | |
| # server versions. Fail the build instead of shipping a stale bundle. | |
| - name: Verify committed bundles are up to date | |
| run: | | |
| git diff --exit-code -- dist/index.cjs \ | |
| wordpress-plugin/gk-block-mcp/assets/mcp-server/index.cjs \ | |
| || { echo "::error::dist/index.cjs or the embedded copy is stale — run 'npm run build' and commit the result."; exit 1; } | |
| ts-integration: | |
| name: TypeScript MCP server (integration — live WP) | |
| runs-on: ubuntu-latest | |
| # Run only on explicit opt-in. GitHub Actions rejects `secrets.X` in | |
| # job-level `if` expressions at workflow-validation time (the previous | |
| # `secrets.WORDPRESS_URL != ''` clause caused every push to fail with | |
| # "workflow file issue" before any job started). Forks without the | |
| # secret configured simply don't have [integration] in their commit | |
| # messages or won't invoke workflow_dispatch, so this is equivalent in | |
| # practice while keeping the file valid. | |
| if: | | |
| (github.event_name == 'workflow_dispatch' && github.event.inputs.run_integration == 'true') || | |
| contains(github.event.head_commit.message, '[integration]') | |
| steps: | |
| - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1 | |
| - uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0 | |
| with: | |
| node-version: '20' | |
| cache: 'npm' | |
| - run: npm ci | |
| - name: Run integration tests | |
| env: | |
| WORDPRESS_URL: ${{ secrets.WORDPRESS_URL }} | |
| WORDPRESS_USER: ${{ secrets.WORDPRESS_USER }} | |
| WORDPRESS_APP_PASSWORD: ${{ secrets.WORDPRESS_APP_PASSWORD }} | |
| run: npm run test:integration | |
| # When secrets are absent every test skips via skipUnlessLive(), so | |
| # the suite exits 0. The step will still appear green in the job log. | |
| # To test against a real WP instance, configure the three secrets in | |
| # repository Settings → Secrets and variables → Actions. |