Skip to content

Commands ​

furnace catalog ​

List supported Magento/MageOS and PHP combinations, cell coverage and installed storefront themes with their package versions.

bash
furnace catalog
furnace catalog --json=/tmp/catalog.json

Select versions and themes in furnace.json, then preview them with furnace plan. See project configuration for examples and theme version constraints.

furnace conform ​

Generate a manifest with editable version and theme lists, or verify an existing one.

bash
furnace conform /path/to/project --init
furnace conform /path/to/project --explain

An existing manifest is preserved. Scaffolding declares the test layers present in the repository; legacy browser suites still need their protocol migration.

furnace run ​

Test the extension in the current directory.

bash
furnace run --gate=all --parallel=3
optioneffect
--gate=syntax|static|allRector and syntax, full static checks, or the full ladder (default static)
--syntax-php=8.3scope --gate=syntax to one PHP minor, useful for CI containers
--cell=m248one cell
--parallel=3test several cells at once, with a live table
--skip-e2eskip Playwright
--skip-cronskip the cron gate
--keep-installedleave the extension installed, for debugging
--fresh-installinstead of the test ladder: remove app/etc/env.php and config.php, drop and recreate the database, setup:install with the extension in place, then check that its modules are enabled, the schema is settled and the home page serves (needs --gate=all)
--install=composer|copyinstall the composer packages, or copy modules into app/code (default: furnace.json install.method, else copy)
--source=path|archive|packagistwhere the composer packages come from: the staged working tree, what composer archive ships, or a registry the cell knows (default: install.source, else path)
--release=1.2.0test a released version: composer require that constraint from the registry, with this checkout's tests; implies --source=packagist
--package=vendor/name[:constraint]instead of PATH: composer require a harness package into a work project and run from it
--repository=URL|DIRwhere --package is found: a composer repository, or a local directory as a path repository (repeatable; default packagist.org)
--allow-incompletereport success despite skipped gates or missing tuples
--json=PATHmachine-readable result
--html=PATHstandalone certification matrix, no external assets

Static runs and failed gates also write requested reports and the internal run.json.

A fresh install tests the other path a customer takes. The ladder upgrades a running store; --fresh-install installs Magento from nothing with the extension already in vendor or app/code, which is where install-time patch order, declarative schema and config-before-store bugs show. The gate records the settings it read from the cell (locale, currency, timezone, search engine, admin path), removes the two deployment files, recreates the database, runs setup:install with those settings and a _fresh search index prefix, and checks the result by state: env.php and config.php exist, the database has tables, the extension's modules are enabled, setup:db:status is clean, the home page serves a complete page. No upgrade, tests or production build run. Cleanup restores the golden as usual. The verdict reads "fresh install passed", never "certified": the report records purpose: fresh-install and demands only the install and fresh-install gates.

A release test installs what customers get and judges it with the tests of the checkout you run from, so run it from a checkout of the release tag. The install gate warns when HEAD is not at a tag matching the installed version, when the tree has uncommitted changes, or when the directory is not a git repository. Credentials for private registries are composer's own: the cell's auth.json, your global auth.json, or a COMPOSER_AUTH variable.

bash
git checkout 1.2.0
furnace run --gate=all --release=1.2.0        # qoliber/foo:1.2.0 from the registry
furnace run --gate=all --source=packagist     # the newest release instead

A harness needs no checkout at all: a furnace.json without modules whose install section names the packages and their repositories (see project configuration). Run it from its directory, or let composer fetch it:

bash
furnace run --gate=all --release=1.2.0                         # from the harness directory
furnace run --gate=all --package=qoliber/furnace-foo \
    --repository=https://qoliber.repo.packagist.com/qoliber-shop  # from the registry
furnace run --gate=all --package=qoliber/furnace-foo:dev-main \
    --repository=~/src/furnace-foo                             # a local checkout, as a path repository

A harness always runs --gate=all: it has no source until a cell has its packages, so rector, the syntax floor, static analysis and unit tests run on each cell after the install, with the cell's own PHPUnit and PHPStan, and land in that cell's row of the report. Cells are locked before recovery; run, clean, init, golden and deep canaries respect that ownership.

furnace init ​

Repair every cell, then say whether the fleet is ready.

bash
furnace init --deep        # and prove each cell can run an integration test
furnace init --recapture   # and refresh the goldens of healthy cells
furnace init -c m248       # one cell

Run it after a reboot, after changing a cell, and before a matrix.

init also brings each cell's auth.json up to date with the global one, for the registries the cell declares and only where the registry proves it: a cell credential the registry refuses is replaced by the global one when the registry accepts that, a host the cell has nothing for gets the global credential when it is accepted, and a global credential the registry refuses is reported, never copied. A working cell credential is never overwritten, so a stale global file cannot break a cell. Furnace never stores credentials of its own; in CI, a masked COMPOSER_AUTH variable is enough, composer reads it natively.

furnace doctor ​

Verify cells are testable, without repairing anything.

bash
furnace doctor             # seconds per cell
furnace doctor --deep      # runs a real integration test per cell, ~1–2 min each
furnace doctor -c m248

--deep is the only check that proves a cell can execute a test. Every other check reads the database or fetches a page.

The registries check requests packages.json from every composer repository the cell's composer.json declares, with the credentials composer would send (the cell's auth.json, the global auth.json, then COMPOSER_AUTH). A refusal fails the cell, because composer require aborts on the first repository it cannot read, and composer reports that as "could not be found in any version" rather than as a 401. A registry that does not answer is a warning.

furnace golden ​

bash
furnace golden                      # list: which cells have a snapshot, size, age
furnace golden --capture            # capture all
furnace golden --capture -c m248
furnace golden --capture --force    # capture an unhealthy cell (rarely right)
furnace golden --restore            # put cells back

furnace clean ​

bash
furnace clean                # remove installed extensions
furnace clean --restore      # and restore the database from the golden
furnace clean --dry-run      # report, change nothing

furnace plan ​

Resolve the required matrix before running anything.

bash
furnace plan
furnace plan --json=PATH
furnace plan --require-ready   # exit non-zero when a required tuple has no cell

furnace cells ​

List discovered cells and the URLs they expose.

Every installed MageBox instance under the cell root is a cell. To keep one out of the fleet, for example an instance you develop in with modules symlinked into app/code, create an empty .furnace-ignore file in its directory; run, doctor, init, clean and golden then never touch it. Delete the file to make it a cell again.

furnace sweep ​

Check the real PHP floor of every extension in a directory — the floor its code actually parses on, against the floor its composer.json declares.

bash
furnace sweep ~/qoliber-extensions

Furnace 1.0.0-alpha · internal tooling · qoliber.com