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
10 changes: 10 additions & 0 deletions figma-transformer/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,16 @@ Run a single local fixture:
homeboy bench --rig figma-transformer-fixture-matrix --profile fixture-matrix --setting bench_env.FIGMA_FIXTURE_MATRIX_FIXTURE=/path/to/fixture.fig --path /path/to/blocks-engine
```

Run with DOM-box capture evidence:

```sh
npm ci --prefix php-transformer/tools/visual-parity
npm --prefix php-transformer/tools/visual-parity run install:browsers
homeboy bench --rig figma-transformer-fixture-matrix --profile fixture-matrix --setting-json bench_env='{"FIGMA_FIXTURE_MATRIX_FIXTURE":"/path/to/fixture.fig","FIGMA_FIXTURE_MATRIX_ARGS":["--capture-dom-boxes","--dom-box-provider-command=node php-transformer/tools/visual-parity/bin/dom-box-provider.mjs"]}' --path /path/to/blocks-engine
```

The matrix runner preflights Homeboy and the canonical DOM-box provider before fixture transforms start. Missing provider configuration, missing `node_modules`, or missing Playwright Chromium exits with an actionable setup command before any partial fixture run.

Run a fixture corpus manually:

```sh
Expand Down
41 changes: 41 additions & 0 deletions figma-transformer/scripts/figma-fixture-matrix.php
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,7 @@

if ( $captureDomBoxes && ! $dryRun ) {
matrix_preflight_homeboy_command($homeboyCommand);
matrix_preflight_dom_box_provider_command($domBoxProviderCommand, $root);
}

$summary = array(
Expand Down Expand Up @@ -392,6 +393,7 @@ function matrix_print_help(): void
php scripts/figma-fixture-matrix.php --dry-run --fixture=/tmp/patched-fixtures/home.fig
php scripts/figma-fixture-matrix.php --dry-run --fixture-dir=/tmp/fixture-corpus
php scripts/figma-fixture-matrix.php --dry-run --fixture=/tmp/home.fig --include-fixture-dir
php scripts/figma-fixture-matrix.php --fixture=/tmp/home.fig --capture-dom-boxes --dom-box-provider-command='node php-transformer/tools/visual-parity/bin/dom-box-provider.mjs'

HELP;
}
Expand Down Expand Up @@ -686,6 +688,45 @@ function matrix_preflight_homeboy_command(string $homeboyCommand): void
exit(1);
}

function matrix_preflight_dom_box_provider_command(string $domBoxProviderCommand, string $root): void
{
$domBoxProviderCommand = trim($domBoxProviderCommand);
if ( '' === $domBoxProviderCommand ) {
fwrite(STDERR, "DOM box capture requires a provider command. Set --dom-box-provider-command, --dom-box-command, or HOMEBOY_DOM_BOX_CAPTURE_COMMAND.\n");
fwrite(STDERR, "Canonical provider: --dom-box-provider-command='node php-transformer/tools/visual-parity/bin/dom-box-provider.mjs'\n");
fwrite(STDERR, "Install provider dependencies first: npm ci --prefix php-transformer/tools/visual-parity && npm --prefix php-transformer/tools/visual-parity run install:browsers\n");
exit(1);
}

if ( ! matrix_dom_box_provider_supports_preflight($domBoxProviderCommand) ) {
return;
}

$previousDirectory = getcwd();
if ( false === $previousDirectory || ! chdir($root) ) {
fwrite(STDERR, "Unable to preflight DOM box provider from repository root: {$root}\n");
exit(1);
}

exec($domBoxProviderCommand . ' --preflight 2>&1', $output, $exitCode);
chdir($previousDirectory);

if ( 0 === $exitCode ) {
return;
}

fwrite(STDERR, "DOM box provider preflight failed before fixture transforms started.\n");
fwrite(STDERR, "Provider command: {$domBoxProviderCommand}\n");
fwrite(STDERR, implode("\n", $output) . "\n");
fwrite(STDERR, "Install provider dependencies first: npm ci --prefix php-transformer/tools/visual-parity && npm --prefix php-transformer/tools/visual-parity run install:browsers\n");
exit(1);
}

function matrix_dom_box_provider_supports_preflight(string $domBoxProviderCommand): bool
{
return str_contains($domBoxProviderCommand, 'dom-box-provider.mjs') || str_contains($domBoxProviderCommand, 'blocks-engine-dom-box-provider');
}

function matrix_inspect_command(string $figmaRoot, string $fixturePath, string $resultPath, string $zstdCommand, int $inspectLimit): string
{
$parts = array(
Expand Down
12 changes: 12 additions & 0 deletions figma-transformer/tests/contract/FixtureMatrixContract.php
Original file line number Diff line number Diff line change
Expand Up @@ -432,6 +432,13 @@ function blocks_engine_figma_transformer_run_fixture_matrix_contract(callable $a
. ' --homeboy-command=' . escapeshellarg($matrixFixtureDir . '/missing-homeboy')
. ' 2>&1';
exec($missingHomeboyCommand, $missingHomeboyOutput, $missingHomeboyExitCode);
$missingProviderOutput = array();
$missingProviderCommand = escapeshellarg(PHP_BINARY)
. ' ' . escapeshellarg(__DIR__ . '/../../scripts/figma-fixture-matrix.php')
. ' --capture-dom-boxes --fixture-dir=' . escapeshellarg($matrixFixtureDir)
. ' --homeboy-command=' . escapeshellarg(PHP_BINARY)
. ' 2>&1';
exec($missingProviderCommand, $missingProviderOutput, $missingProviderExitCode);
$matrixHelpOutput = array();
$matrixHelpCommand = escapeshellarg(PHP_BINARY)
. ' ' . escapeshellarg(__DIR__ . '/../../scripts/figma-fixture-matrix.php')
Expand Down Expand Up @@ -528,6 +535,11 @@ function blocks_engine_figma_transformer_run_fixture_matrix_contract(callable $a
$missingHomeboyMessage = implode("\n", $missingHomeboyOutput);
$assert(str_contains($missingHomeboyMessage, 'DOM box capture requires a runnable Homeboy command'), 'fixture-matrix-capture-preflight-missing-homeboy-message');
$assert(str_contains($missingHomeboyMessage, 'Set --homeboy-command, --homeboy-bin, or HOMEBOY_COMMAND'), 'fixture-matrix-capture-preflight-homeboy-remediation');
$assert(0 !== $missingProviderExitCode, 'fixture-matrix-capture-preflight-missing-provider-fails');
$missingProviderMessage = implode("\n", $missingProviderOutput);
$assert(str_contains($missingProviderMessage, 'DOM box capture requires a provider command'), 'fixture-matrix-capture-preflight-missing-provider-message');
$assert(str_contains($missingProviderMessage, "--dom-box-provider-command='node php-transformer/tools/visual-parity/bin/dom-box-provider.mjs'"), 'fixture-matrix-capture-preflight-provider-remediation');
$assert(str_contains($missingProviderMessage, 'npm ci --prefix php-transformer/tools/visual-parity'), 'fixture-matrix-capture-preflight-provider-install-command');
$assert(0 === $matrixHelpExitCode, 'fixture-matrix-help-exits-zero');
$matrixHelpMessage = implode("\n", $matrixHelpOutput);
$assert(str_contains($matrixHelpMessage, 'Usage:'), 'fixture-matrix-help-usage');
Expand Down
3 changes: 3 additions & 0 deletions php-transformer/tools/visual-parity/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ Install the tool dependencies and Chromium browser once per checkout:
```sh
npm ci --prefix php-transformer/tools/visual-parity
npm --prefix php-transformer/tools/visual-parity run install:browsers
node php-transformer/tools/visual-parity/bin/dom-box-provider.mjs --preflight
```

Capture DOM-box evidence for a generated static HTML artifact root:
Expand Down Expand Up @@ -93,6 +94,8 @@ homeboy tunnel artifact-origin dom-boxes \

The report is repeatable when the artifact root, entrypoint, browser version, viewport defaults, and node identity attributes stay fixed. Attach the JSON report to the Homeboy run, issue, or PR evidence surface so the next operator can compare generated HTML structure and positions without re-running the full transform.

The fixture matrix runner preflights this provider before running transforms when DOM-box capture is enabled, so missing Node dependencies or Playwright browser installs fail fast with the install command above instead of producing partial matrix artifacts.

```sh
HOMEBOY_DOM_BOX_BASE_URL=https://example.test \
HOMEBOY_DOM_BOX_PAGE_PATHS_JSON='["/index.html"]' \
Expand Down
20 changes: 19 additions & 1 deletion php-transformer/tools/visual-parity/bin/dom-box-provider.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,11 @@ async function main() {
return;
}

if (process.argv.includes('--preflight')) {
await preflight();
return;
}

const cli = parseCliArgs(process.argv.slice(2));
const baseUrl = requiredEnv('HOMEBOY_DOM_BOX_BASE_URL').replace(/\/+$/, '');
const pagePaths = parsePagePaths(requiredEnv('HOMEBOY_DOM_BOX_PAGE_PATHS_JSON'));
Expand Down Expand Up @@ -100,6 +105,19 @@ async function main() {
}
}

async function preflight() {
const { chromium } = await loadPlaywright();
let browser;
try {
browser = await chromium.launch();
} catch (error) {
throw withPlaywrightSetupHelp(error);
} finally {
await browser?.close();
}
process.stdout.write(`${JSON.stringify({ status: 'ok', provider: 'blocks-engine-dom-box-provider' }, null, 2)}\n`);
}

async function loadPlaywright() {
try {
return await import('playwright');
Expand Down Expand Up @@ -209,7 +227,7 @@ function resolveNodeNameAttrs(cli) {
}

function printHelp() {
process.stdout.write(`Capture DOM boxes for Homeboy artifact-origin dom-boxes.\n\nNode identity is keyed off a configurable attribute so the tool is product-neutral.\nThe figma-transformer's data-figma-* attributes remain the backward-compatible default.\n\nEnvironment:\n HOMEBOY_DOM_BOX_BASE_URL Static artifact origin base URL.\n HOMEBOY_DOM_BOX_PAGE_PATHS_JSON JSON array of page paths to capture.\n HOMEBOY_DOM_BOX_TEXT_SAMPLE_LIMIT Optional positive integer, default 160.\n HOMEBOY_DOM_BOX_NODE_ID_ATTR Node identity attribute, default ${DEFAULT_NODE_ID_ATTR}.\n HOMEBOY_DOM_BOX_NODE_NAME_ATTR Comma-separated node name attributes, default ${DEFAULT_NODE_NAME_ATTRS.join(',')} (aria-label is always a final fallback).\n\nFlags (override the matching environment variable):\n --node-id-attr=<attr> Node identity attribute used for enumeration, selectors, and id reads.\n --node-name-attr=<attr>[,<attr>...] Node name attributes, tried in order before aria-label.\n\nOutput:\n JSON browser payload on stdout for Homeboy to shape as homeboy/static-artifact-dom-boxes/v1.\n`);
process.stdout.write(`Capture DOM boxes for Homeboy artifact-origin dom-boxes.\n\nNode identity is keyed off a configurable attribute so the tool is product-neutral.\nThe figma-transformer's data-figma-* attributes remain the backward-compatible default.\n\nEnvironment:\n HOMEBOY_DOM_BOX_BASE_URL Static artifact origin base URL.\n HOMEBOY_DOM_BOX_PAGE_PATHS_JSON JSON array of page paths to capture.\n HOMEBOY_DOM_BOX_TEXT_SAMPLE_LIMIT Optional positive integer, default 160.\n HOMEBOY_DOM_BOX_NODE_ID_ATTR Node identity attribute, default ${DEFAULT_NODE_ID_ATTR}.\n HOMEBOY_DOM_BOX_NODE_NAME_ATTR Comma-separated node name attributes, default ${DEFAULT_NODE_NAME_ATTRS.join(',')} (aria-label is always a final fallback).\n\nFlags (override the matching environment variable):\n --node-id-attr=<attr> Node identity attribute used for enumeration, selectors, and id reads.\n --node-name-attr=<attr>[,<attr>...] Node name attributes, tried in order before aria-label.\n --preflight Verify Playwright and Chromium are installed, then exit.\n\nOutput:\n JSON browser payload on stdout for Homeboy to shape as homeboy/static-artifact-dom-boxes/v1.\n`);
}

async function extractElements(page, pagePath, textSampleLimit, nodeIdAttr, nodeNameAttrs) {
Expand Down
Loading