Building and staleness checks
The GraphQL layer is generated from the code API by a build script and committed to source control. This document covers the commands and the check that keeps the committed output in sync with its source.
Regenerating
From plugins/woocommerce/:
# Regenerate core's GraphQL layer from src/Api/
pnpm --filter=@woocommerce/plugin-woocommerce build:api
# Regenerate the test fixture's tree (the DummyApi used by the test suite)
pnpm --filter=@woocommerce/plugin-woocommerce build:api:test
# Check whether the committed output is stale (used by CI)
pnpm --filter=@woocommerce/plugin-woocommerce build:api:check
build:api runs php bin/api-builder/build-api.php. It wipes and regenerates the output directory, formats the result with phpcbf, refreshes the Composer autoloader, and writes the staleness-tracking files. The build tooling under bin/api-builder/ is excluded from release builds.
After regenerating, commit the src/Api/ change and the regenerated src/Internal/Api/Autogenerated/ tree together.
build-api.php flags
build-api.php with no flags produces core's output. The four path/namespace flags are all-or-nothing - provide all four or none:
| Flag | Meaning |
|---|---|
--api-dir=PATH | Directory of code-API source classes to scan. |
--autogen-dir=PATH | Output directory (wiped each run). |
--api-namespace=NS | PSR-4 namespace mapping to --api-dir. |
--autogen-namespace=NS | PSR-4 namespace mapping to --autogen-dir. |
--composer-working-dir=DIR | Where to run composer dump-autoload. |
--phpcbf-path=PATH | Path to the phpcbf binary used for formatting. |
--no-linter | Skip the phpcbf formatting pass. |
For fast local iteration, pass --no-linter: the phpcbf pass is the slowest step in the build, and it only affects whitespace in the generated PHP — the code is functionally identical with or without it. Run a full build (without the flag) before committing so the formatted output is what lands in source control. ApiBuilder::run_for_plugin() also honours --no-linter from argv, so the same shortcut works for plugin builds: WC_PATH=… php bin/build-api.php --no-linter.
Plugins generally don't call the other flags directly; they use ApiBuilder::run_for_plugin() (see Creating a dual API in a plugin).
The staleness check
build:api:check (php bin/api-builder/check-api-staleness.php) fails when the committed generated tree doesn't match the current source.
The check is content-based, not timestamp-based: build:api writes a SHA-256 hash of every .php file under the source dir (each file hashed as relative_path \0 contents \0, files sorted by path) into api_source_hash.txt in the output directory. StalenessChecker::is_stale() recomputes that hash and compares. Because it ignores mtimes and filesystem iteration order, it behaves identically on fresh clones, in CI, and during active development. (api_generation_date.txt is also written, for human reference only.)
CI enforcement
The .github/workflows/api-staleness.yml workflow (GraphQL API Staleness Check) runs build:api:check on PRs and pushes that touch:
plugins/woocommerce/src/Api/**plugins/woocommerce/src/Internal/Api/Autogenerated/**plugins/woocommerce/bin/api-builder/**- the workflow file itself
If the source was changed without regenerating, the job fails with Generated GraphQL API code is out of date. Regenerate, commit, and push to clear it. Plugins that maintain their own dual API should add an equivalent check to their own CI.