Skip to content

Foundation CLI

Foundation CLI is development tooling for generating project code. It reads the consuming project’s Composer configuration, follows WordPress naming and formatting conventions, and uses stubs owned by the runtime package that defines each generated API.

Install the CLI as a development dependency in a consuming project:

composer require --dev stellarwp/foundation-cli

List its available commands:

vendor/bin/foundation list

Do not register StellarWP\Foundation\Cli\CliProvider in the WordPress application’s provider list. It boots the Symfony Console application for the foundation executable and is unrelated to WordPress request bootstrap.

The binary can be exposed through a project script in composer.json:

{
  "scripts": {
    "foundation": "@php vendor/bin/foundation"
  }
}

Pass command arguments after --:

composer run foundation -- list
vendor/bin/foundation make:wpcli:command Sync_Products_Command

The generated class extends Foundation’s WP-CLI command base and demonstrates positional arguments, associative options, and flags. A command shipped by the plugin requires the runtime package:

composer require stellarwp/foundation-wpcli

Install the runtime package, then generate a table with its initial migration:

composer require stellarwp/foundation-migrations
vendor/bin/foundation make:database:table Reports --table-name=your_plugin_reports --migration

The command creates Reports_Table.php and a timestamped db/migrations/<timestamp>_create_reports_table.php. Add the columns to the migration, preview its SQL, and apply it through WP-CLI. Follow the complete create-and-alter workflow to configure discovery and run the generated code.

--table-name supplies the stable name before WordPress adds its site prefix. --migration creates an initial migration whose rollback drops the complete table. Table --namespace and --path options affect only the table class. Migrations use migrations.path from root config.php, defaulting to db/migrations.

If you want a provider for application table bindings, generate one with make:database:provider before generating tables. Table generation can update it; migration discovery does not require per-migration provider entries.

Database table and migration generators refuse to overwrite existing files. Edit an unapplied migration directly, or create a new migration after the existing one has been deployed.

The migration generator selects one of three modes:

Invocation Generated behavior Override stub
make:database:migration create_reports_table --create=your_plugin_reports Creates the explicitly selected table; down() drops the table create-table-migration.stub
make:database:migration add_status_to_reports --table=your_plugin_reports Starts an explicit alteration blueprint; down() throws IrreversibleMigration until replaced alter-table-migration.stub
make:database:migration Backfill_Report_Status Generates a generic migration; declare changes using historical table names migration.stub

The --create and --table options are mutually exclusive. Each accepts a stable, unprefixed database table name such as your_plugin_reports. Migration names never select destructive behavior by themselves.

Use Symfony Console’s built-in help for supported names, paths, namespaces, and feature-specific options:

vendor/bin/foundation help make:wpcli:command
vendor/bin/foundation help make:database:provider
vendor/bin/foundation help make:database:table
vendor/bin/foundation help make:database:migration

For tables, providers, and WP-CLI commands, generators use the first autoload.psr-4 entry in the project’s composer.json as their root namespace. Output directories follow the most specific Composer mapping for the selected namespace. Explicit --namespace and --path options override those defaults.

Set project-wide namespaces in the project’s root config.php, alongside existing application settings. The file is optional; include only the generators whose defaults you want to change:

<?php

return [
	'generators' => [
		'wpcli-command' => [
			'namespace' => 'Plugin\\Commands',
		],
		'database-provider' => [
			'namespace' => 'Plugin\\Persistence',
		],
		'database-table' => [
			'namespace' => 'Plugin\\Persistence\\Tables',
		],
	],
];

Run Foundation from the project root. It loads this file once when the CLI starts; use a PHP array that can be loaded independently of WordPress.

Namespaces are fully qualified application namespaces. With Plugin\\ mapped to src/ in Composer, the example produces:

Generator Directory
make:wpcli:command src/Commands/
make:database:provider src/Persistence/
make:database:table src/Persistence/Tables/

An explicit --namespace takes precedence over the configured namespace. Without either, the conventional suffixes are Cli\\Commands, Database, and Database\\Tables, respectively. An explicit --path chooses the output directory for that invocation; it does not change the namespace or update Composer autoload mappings.

The table generator looks for Provider.php in the configured database-provider namespace. Generate that provider first to enable table registration. Use table --provider to select another provider file. Migration --create and --table options take stable, unprefixed database names.

Migration placement is shared with runtime discovery through migrations.path. Configure the migration directory once. Migration commands have no separate --path, --namespace, or --provider option. Use a name such as reports/add_status to generate inside a feature subfolder.

Invalid settings for the selected generator or missing Composer mappings stop generation before files are written. Correct the setting or mapping before retrying. If the provider namespace has no mapping, correct generators.database-provider.namespace or its Composer mapping, or select an existing provider file with --provider.

If Foundation scaffolding should be excluded from your production archive, add foundation/ to the project’s .gitattributes export exclusions.

Follow Create a custom generator for a complete stub, generator class, and automatically loaded tooling provider. The generator owns its command name, configuration key, and default namespace.

Place project-specific stubs under foundation/stubs/ using the same feature path as the package default:

foundation/stubs/
  wpcli/
    command.stub
  database/
    provider.stub
    table.stub
    create-table-migration.stub
    alter-table-migration.stub
    migration.stub

Copy the package’s default stub before customizing it so required placeholders remain available. Local scaffolding assets that should not ship in a production zip should be excluded in the consuming project’s .gitattributes.

When the consuming project’s composer.json defines extra.strauss.namespace_prefix, generators apply that prefix to Foundation imports. For example, a configured Plugin\\ prefix changes:

use StellarWP\Foundation\WPCli\Command;

to:

use Plugin\StellarWP\Foundation\WPCli\Command;

This keeps generated classes compatible when Strauss prefixes dependencies without updating project call sites. Handwritten imports remain the application’s responsibility.

Foundation looks for <ProjectNamespace>\Tooling\Tooling_Provider using the first runtime PSR-4 namespace in the project’s composer.json. With Plugin\\ mapped to src/, create src/Tooling/Tooling_Provider.php. Composer determines the physical file location.

Use this one provider to register your developer commands and their dependency bindings. Commands contributed to CliProvider::COMMANDS become available through vendor/bin/foundation. Follow Create a custom generator for a complete example.

The tooling provider runs in the Foundation CLI’s container. Application providers still run through your application’s composition root. Both can read the same root config.php; keep configuration loadable independently of WordPress, such as arrays populated from environment values.

To place your tooling provider elsewhere, move it to the desired Composer-mapped location and update its namespace and class name accordingly. For example, dev/Console/Project_Tooling_Provider.php can declare Plugin\Dev\Console\Project_Tooling_Provider with Plugin\\Dev\\ mapped to dev/ in autoload-dev.

Select the file in the project’s root config.php:

<?php declare(strict_types=1);

return [
	'cli' => [
		'tooling_provider_path' => 'dev/Console/Project_Tooling_Provider.php',
	],
];

Relative paths start at the project root; absolute paths also work. The selected file must contain its matching Composer-autoloadable provider class. This setting replaces the conventional lookup. An invalid path or provider stops startup; correct it and retry, or remove the setting to restore the convention.

To move the conventional provider and commands into dev/Tooling/ while preserving their namespace, add a more specific mapping in the project’s composer.json:

{
  "autoload": {
    "psr-4": {
      "Plugin\\": "src/"
    }
  },
  "autoload-dev": {
    "psr-4": {
      "Plugin\\Tooling\\": "dev/Tooling/"
    }
  }
}

Regenerate the autoloader after changing mappings:

composer dump-autoload

Foundation still finds Plugin\Tooling\Tooling_Provider automatically. Generated application classes continue to use runtime autoload mappings. Install stellarwp/foundation-cli with --dev, and keep generated classes’ runtime dependencies in require.

If commands need services from additional package providers, list their fully qualified class names under cli.providers in root config.php. Foundation registers its own CliProvider, those prerequisites in the listed order, and then the selected project tooling provider before constructing commands. A provider listed more than once is registered once.

Keep service bindings lazy so commands resolve after all providers have registered. Commands that need application services must have those dependencies wired into the tooling container.

Run package:create through composer run foundation inside the Foundation monorepo. It creates split-package scaffolding and configures read-only GitHub repositories for maintainers.

Select an existing package by its directory (src/Log), short name (Log), repository name (foundation-log), or full manifest name (stellarwp/foundation-log). The Docs package is also discovered from its package.json and accepts Docs, src/Docs, foundation-docs, or @stellarwp/foundation-docs.

Each directory represents one split package. When both manifests exist, composer.json determines the package identity for this command and the split workflow; package.json is used only when composer.json is absent.

Invalid JSON in the selected manifest stops discovery. Correct the file before retrying.

Preview the repository actions without changing GitHub:

composer run foundation -- package:create src/Log

The generated actions disable issues, wikis, projects, and pull requests on the split repository.

Pass --apply only after reviewing the generated actions:

composer run foundation -- package:create Log --apply

For a new PHP package, pass its short name. The command asks for confirmation before creating src/<Package> scaffolding, prompts for the Composer package name, and runs composer monorepo merge after local package creation.