# Build and release

The plugin distribution bundles its editable `src/`, `assets/js/`, `assets/css/`,
Webpack configuration and dependency manifests. Development tests, lint configs
and release/check scripts stay in the source checkout and are excluded from the
installable ZIP. To rebuild bundled assets in an extracted ZIP, run `npm ci` and
`npm run build`. Run the checks and release commands below from the full checkout.
Build from the directory containing `package.json`. PHP and the Classic scripts are
used directly; `src/frontend.js` and its imports compile to `build/frontend.*`.

## Prerequisites and commands

Use Node 26+ (validated here with 26.7.0) and npm 11 (11.19.0 here). The direct
jsdom test dependency also supports Node 22.22.2+ in the 22.x line or 24.15.0+ in
the 24.x line. These are developer tooling requirements, separate from plugin PHP
and WordPress requirements. Use PHP CLI, or Docker with the already installed
`wordpress:cli-php8.3` image. ZIP creation needs `zip` and `unzip` on PATH.

```sh
npm ci
npm run lint:js
npm run lint:css
npm run test:php
npm run test:frontend
npm run build
```

`npm run start` watches and rebuilds during development. The Webpack configuration
extends `@wordpress/scripts` and maps WooCommerce imports to WordPress script
handles; dependencies are recorded in `build/frontend.asset.php`. Do not copy a
stale asset metadata file from another build.

JavaScript lint covers `src`, `assets/js`, `tests/test-frontend-dom.js` and
`tests/test-age-gate-dom.js`. Style
lint covers `assets/css/**/*.css` and `src/**/*.scss`; vendored libraries, compiled
output and old distributions are outside those scopes.

`test:php` syntax-checks production PHP in the entry point, uninstall, admin,
includes, templates and compiled asset metadata, then executes **all root
`tests/test-*.php` files** as separate PHP processes. They are focused standalone
suites with stubs; no WordPress runtime is loaded. Real WordPress/runtime helpers,
including `tests/runtime/payment-and-profile.php`, are outside that pattern and
are never executed or bundled by this runner.

If host PHP is unavailable, the runner uses a new disposable Docker process with
`--network=none`, a read-only filesystem and a read-only project mount. It never
pulls an image or touches existing WordPress containers. To choose another local
PHP image, set `CLOUDFFL_PHP_IMAGE` for that command. No tests need a database,
CloudFFL credentials, network access or writable WordPress installation.

## Release candidate

```sh
npm run check:release
npm run release
```

`check:release` only validates version metadata and required allowlisted files;
it neither builds nor packages. A release compares the plugin Version header,
`CLOUDFFL_VERSION`, readme Stable tag, package version and both lockfile root
versions. It runs JavaScript lint, style lint, focused PHP suites, frontend DOM
regressions and the production build, failing on any nonzero exit. There is no
skip-check or skip-build release option.

Only explicit runtime/source/build paths and approved file extensions enter the
ZIP. Credentials, agent configs, hidden files, node_modules, old ZIPs, dist, tests
and executable development scripts are not allowlisted. Symlinks are rejected. Review `scripts/release.mjs` when
adding a runtime asset or source/config type; do not broaden the allowlist to the
whole repository. `THIRD-PARTY-NOTICES.txt` describes the local Leaflet files.

The candidate is assembled in a unique versioned temporary directory under dist.
Production PHP syntax checks run again on the staged files after the asset build; ZIP integrity is
checked and a SHA-256 checksum is saved. The complete directory is published by a
same-filesystem atomic rename. For version 2.3.2, output is:

```
dist/2.3.2-candidate/cloudffl-for-woocommerce/
dist/2.3.2-candidate/cloudffl-for-woocommerce-2.3.2.zip
dist/2.3.2-candidate/cloudffl-for-woocommerce-2.3.2.zip.sha256
```

The script refuses an existing candidate or active version lock. It never removes
old dist content or overwrites the original 2.2.0 archive. Failed checks create no
candidate; incomplete private staging directories are cleaned up. After an
interrupted build, inspect any retained `.VERSION-release.lock` before manually
removing it; another process may still own it.

## Runtime and acceptance gates

After integrating frontend/PHP fixes, install the **exact generated ZIP** on a
fresh local WordPress site. Verify Classic and Blocks, guest/account checkout,
selected/pick-later dealers, order payment-state preservation, saved profile
isolation, HPOS/legacy storage, REST privacy, invalid/unavailable dealers and the
approved CloudFFL test API. Actual payment-gateway execution is outside the
standalone plugin release scope; offline BACS and controlled local payment-state
events verify that dealer operations do not invent payments or reduce stock
twice. Focused suites and ZIP integrity do not prove runtime behavior.

Original source files carry CloudFFL/Longhorn Web Solutions, LLC attribution;
Webpack adds the same scoped GPL notice to generated JS/CSS. COPYRIGHT.txt
documents first-party ownership, including generated dependency metadata. Keep
LICENSE.txt and the vendored Leaflet notices unchanged. Do not stamp third-party
files with first-party ownership. For an attribution/documentation-only rebuild,
compare executable code with the last runtime-tested candidate and run the
release checks, exact-ZIP installation and Plugin Check; retain prior runtime
results under their actual hashes.

The tested-version headers record WordPress 7.1 and WooCommerce 11.2.1,
following successful local Classic and Blocks live-API checkout and reload tests
on WordPress 7.1.3 with PHP 8.3.32. These headers do not establish compatibility for every extension
or server configuration. Keep exact-artifact runtime evidence and the version
matrix separate from focused checks. Directory/marketplace submission,
publication, deployment and human acceptance are separate authorized steps.
