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
13 changes: 11 additions & 2 deletions php-transformer/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -132,7 +132,7 @@ Unsupported or unsafe artifact inputs are reported through diagnostics instead o

## Parity Checks

Run the package contract and parity fixtures with `composer test`. The checked-in fixtures assert current transformer behavior.
Run the package contract, parity fixtures, and clean package-install proof with `composer test`. The checked-in fixtures assert current transformer behavior, and the install proof verifies that Composer can install `automattic/blocks-engine-php-transformer` from the `php-transformer/` package root without symlinking back to the working tree.

## Release Consumption

Expand Down Expand Up @@ -184,14 +184,23 @@ Before the first tag is available, review branches may use a Composer VCS or pat

### Release Readiness Checklist

Before tagging the first package release:
Reviewer-safe checks before approving the first package release PR:

- Run `composer validate --strict` and `composer test` from `php-transformer/`.
- Confirm `VERSION`, `php-transformer.php`, and the intended tag all resolve to `0.1.0`.
- Confirm Packagist or the subtree split indexes `php-transformer/` as the package root, not the repository root.
- Confirm downstream merge candidates use `automattic/blocks-engine-php-transformer:^0.1.0` instead of path repositories, unpublished branches, or inline aliases.
- Keep the transformer metadata free of downstream wrapper package names, `replace`, and `provide` declarations.

Operator-only release checklist after the release-readiness PR merges:

- Choose the exact merged commit that should own the `0.1.0` tag.
- Run the Homeboy release dry-run from that merged commit.
- Create the package tag from the accepted release path.
- Publish or connect the Packagist/subtree-split package root if that is the chosen distribution path.
- Update downstream wrapper/product PRs from review-only constraints to tagged constraints.
- Decide whether GitHub Releases are part of this first package publication path.

Homeboy owns the local release preflight for this package through `php-transformer/homeboy.json`. The only version target is `VERSION`, currently `0.1.0`; release automation should tag from the package subtree after the upstream PRs are merged, without adding wrapper-package names to this package metadata.

Recommended post-merge dry-run:
Expand Down
11 changes: 9 additions & 2 deletions php-transformer/composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,11 @@
"description": "PHP primitives for transforming HTML, Markdown, and website artifacts into WordPress block outputs.",
"type": "library",
"license": "GPL-3.0-or-later",
"authors": [
{
"name": "Automattic"
}
],
"keywords": [
"blocks",
"html",
Expand Down Expand Up @@ -33,7 +38,8 @@
"parity": "@test:parity",
"test": [
"@test:canonical",
"@test:parity"
"@test:parity",
"@test:packaging"
],
"test:canonical": [
"php tests/contract/runtime-no-wordpress.php",
Expand All @@ -43,7 +49,8 @@
],
"test:parity": "php tests/parity/run.php",
"test:migration:examples": "php tests/migration/examples.php",
"test:migration:legacy-parity": "BLOCKS_ENGINE_PARITY_LEGACY=1 php tests/parity/run.php --migration-comparison"
"test:migration:legacy-parity": "BLOCKS_ENGINE_PARITY_LEGACY=1 php tests/parity/run.php --migration-comparison",
"test:packaging": "php tests/packaging/install-proof.php"
},
"config": {
"platform": {
Expand Down
6 changes: 3 additions & 3 deletions php-transformer/docs/consumer-prs/packaging.md
Original file line number Diff line number Diff line change
Expand Up @@ -152,13 +152,13 @@ homeboy component show php-transformer
homeboy release php-transformer --dry-run --skip-publish --no-github-release
```

Recommended release command after upstream PRs are merged and the dry-run passes:
Operator-only release command after upstream PRs are merged, the dry-run passes, and the exact release commit is accepted:

```sh
homeboy release php-transformer --skip-publish --no-github-release
```

Run the release from the merged Blocks Engine branch that should own the tag, not from a downstream wrapper branch. Keep `VERSION` as the Homeboy-managed package version source.
Run `composer test:packaging` separately when you only need to re-check Composer installability. Run release commands from the merged Blocks Engine branch that should own the tag, not from a downstream wrapper branch. Do not run release commands from review worktrees, draft downstream branches, or while path repositories, unpublished branch constraints, or local-only proof are still required by merge candidates. Keep `VERSION` as the Homeboy-managed package version source.

Before `1.0.0`, public PHP class names, constructor signatures, result-envelope keys, diagnostic codes, and Composer package metadata may change between minor versions, but each change must include downstream migration notes and fixture updates.

Expand Down Expand Up @@ -224,7 +224,7 @@ The PR can leave draft when the package is reviewable as a releasable Composer l
Maintainers can merge the transformer package when these checks are true:

- Composer can install `automattic/blocks-engine-php-transformer` from the package directory and from the repository branch used for review.
- Package tests pass through the documented Composer script.
- Package tests, including `composer test:packaging`, pass through the documented Composer scripts.
- Fixture documentation explains how downstream wrappers compare old behavior with transformer-backed behavior.
- The public namespace and result-envelope keys needed by phase-1 wrappers are stable enough to tag.
- The PR description includes the intended initial version, downstream release order, and the no-permanent-compatibility stance for old repositories.
Expand Down
32 changes: 22 additions & 10 deletions php-transformer/docs/install-proof.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# PHP Transformer Install Proof

This proof verifies that `automattic/blocks-engine-php-transformer` installs from a clean throwaway Composer project through a Composer path repository. The proof uses path variables so the command shape is reusable and does not rely on reviewer-facing local links.
This proof verifies that `automattic/blocks-engine-php-transformer` installs from a clean throwaway Composer project through a Composer path repository. The automated proof mirrors the package into the throwaway project with `symlink: false` so tests catch missing package files, incorrect autoload paths, and monorepo-root assumptions.

## Package Verification

Expand All @@ -9,21 +9,31 @@ Run from the transformer package directory:
```sh
cd "$BLOCKS_ENGINE_WORKTREE/php-transformer"
composer install
composer validate
composer validate --strict
composer test
git diff --check
```

Observed result on 2026-06-19:
Observed result on 2026-06-21:

| Command | Result |
| --- | --- |
| `composer install` | Installed 9 packages from `composer.lock` and generated autoload files. |
| `composer validate` | `./composer.json is valid`. |
| `composer test` | Passed runtime no-WP, runtime stubs, HTML-to-blocks contract, format bridge scaffold, downstream examples smoke, and 15 parity fixtures. |
| `composer install` | Installed packages from `composer.lock` and generated autoload files. |
| `composer validate --strict` | `./composer.json is valid`. |
| `composer test` | Passed runtime contracts, 37 parity fixtures, and clean package-install proof. |
| `git diff --check` | Passed with no whitespace errors. |

## Clean Path Repository Install
## Automated Clean Path Repository Install

Run from the transformer package directory:

```sh
composer test:packaging
```

The test creates a throwaway Composer project under the system temp directory, configures a non-symlinked path repository that points at `php-transformer/`, requires `automattic/blocks-engine-php-transformer:*@dev`, and runs a PHP autoload smoke check against `HtmlTransformer`.

## Manual Clean Path Repository Install

Run from an empty throwaway directory outside the repository:

Expand All @@ -35,7 +45,9 @@ mkdir "$PROOF_DIR"
cd "$PROOF_DIR"
composer init --no-interaction --name=proof/php-transformer-install
composer config repositories.php-transformer '{"type":"path","url":"'"$BLOCKS_ENGINE_WORKTREE"'/php-transformer","options":{"symlink":false}}' --json
composer require "automattic/blocks-engine-php-transformer:dev-cook/php-transformer-package-install-proof as 0.1.x-dev"
composer config minimum-stability dev
composer config prefer-stable true
composer require "automattic/blocks-engine-php-transformer:*@dev"
composer validate --no-check-publish
composer install
php -r 'require __DIR__ . "/vendor/autoload.php"; $result = (new Automattic\BlocksEngine\PhpTransformer\HtmlToBlocks\HtmlTransformer())->transform("<h1>Proof</h1>")->toArray(); if (($result["serialized_blocks"] ?? "") === "") { fwrite(STDERR, "missing serialized_blocks\n"); exit(1); } print $result["status"] . "\n";'
Expand All @@ -47,9 +59,9 @@ Observed result on 2026-06-19 using a throwaway project under the local temp wor
| --- | --- |
| `composer init --no-interaction --name=proof/php-transformer-install` | Created a new root `composer.json`. |
| `composer config repositories.php-transformer ... --json` | Added a path repository pointing at `php-transformer` with `symlink: false`. |
| `composer require "automattic/blocks-engine-php-transformer:dev-cook/php-transformer-package-install-proof as 0.1.x-dev"` | Locked 10 installs, including `automattic/blocks-engine-php-transformer` at `dev-cook/php-transformer-package-install-proof` with path dist reference `f11cbf91c9b73172c7a25ac6c4c17e60633732d9`; Composer mirrored the package instead of symlinking it. |
| `composer require "automattic/blocks-engine-php-transformer:*@dev"` | Locked the local path package and mirrored it instead of symlinking it. |
| `composer validate --no-check-publish` | Valid for an unpublished throwaway project; only the expected no-license warning was reported for the proof root package. |
| `composer install` | Verified the lock file and reported `Nothing to install, update or remove`. |
| PHP autoload smoke command | Printed `success`, proving the installed package autoloads `HtmlTransformer` and returns serialized block output. |

The package is untagged on this branch, so the proof uses an explicit dev branch constraint with an inline `0.1.x-dev` alias. A tagged release can replace the path repository and dev constraint with the published version constraint.
The package is untagged on review branches, so the proof allows the local dev package explicitly. A tagged release can replace the path repository and dev constraint with the published version constraint.
105 changes: 105 additions & 0 deletions php-transformer/tests/packaging/install-proof.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
<?php
/**
* Verify the package installs from php-transformer/ as a Composer package root.
*
* This catches monorepo path/autoload drift before a release operator tags or
* asks Packagist/subtree automation to index the package.
*
* @package BlocksEnginePhpTransformer
*/

declare(strict_types=1);

$packageRoot = dirname(__DIR__, 2);
$proofRoot = rtrim(sys_get_temp_dir(), DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR . 'blocks-engine-php-transformer-install-proof-' . bin2hex(random_bytes(6));

mkdir($proofRoot, 0777, true);

try {
$repositoryConfig = json_encode(
array(
'type' => 'path',
'url' => $packageRoot,
'options' => array(
'symlink' => false,
),
),
JSON_THROW_ON_ERROR
);

run($proofRoot, array('composer', 'init', '--no-interaction', '--name=proof/php-transformer-install'));
run($proofRoot, array('composer', 'config', 'repositories.php-transformer', $repositoryConfig, '--json'));
run($proofRoot, array('composer', 'config', 'minimum-stability', 'dev'));
run($proofRoot, array('composer', 'config', 'prefer-stable', 'true'));
run($proofRoot, array('composer', 'require', 'automattic/blocks-engine-php-transformer:*@dev', '--no-interaction', '--prefer-dist', '--no-progress'));

$smoke = <<<'PHP'
require __DIR__ . '/vendor/autoload.php';
$result = (new Automattic\BlocksEngine\PhpTransformer\HtmlToBlocks\HtmlTransformer())->transform('<h1>Proof</h1>')->toArray();
if ('success' !== ($result['status'] ?? '') || '' === ($result['serialized_blocks'] ?? '')) {
fwrite(STDERR, "php-transformer install proof failed\n");
exit(1);
}
PHP;

run($proofRoot, array(PHP_BINARY, '-r', $smoke));
} finally {
removeTree($proofRoot);
}

fwrite(STDOUT, "php-transformer package install proof passed\n");

/**
* @param list<string> $command Command and arguments.
*/
function run(string $cwd, array $command): void
{
$env = getenv();
$env['COMPOSER_ROOT_VERSION'] = '1.0.0';

$process = proc_open(
$command,
array(
0 => array('pipe', 'r'),
1 => STDOUT,
2 => STDERR,
),
$pipes,
$cwd,
$env
);

if ( ! is_resource($process) ) {
throw new RuntimeException('Failed to start command: ' . implode(' ', $command));
}

fclose($pipes[0]);
$status = proc_close($process);

if ( 0 !== $status ) {
throw new RuntimeException('Command failed with status ' . $status . ': ' . implode(' ', $command));
}
}

function removeTree(string $path): void
{
if ( ! file_exists($path) ) {
return;
}

$items = new RecursiveIteratorIterator(
new RecursiveDirectoryIterator($path, FilesystemIterator::SKIP_DOTS),
RecursiveIteratorIterator::CHILD_FIRST
);

foreach ( $items as $item ) {
if ( $item->isDir() && ! $item->isLink() ) {
rmdir($item->getPathname());
continue;
}

unlink($item->getPathname());
}

rmdir($path);
}
Loading