-
Notifications
You must be signed in to change notification settings - Fork 0
CLI Package Internals
php-upgrade-preflight/cli provides the framework-neutral upgrade-intel executable. It owns command syntax, adapter discovery, request construction, and output delivery. Core owns analysis.
Composer exposes bin/upgrade-intel. The script looks for an autoloader in this order:
- Composer's
_composer_autoload_pathglobal, when supplied; - the monorepo root
vendor/autoload.phplayout; - a nearby
autoload.phplayout; - the package-local
vendor/autoload.phplayout.
If none exists, it prints Unable to find Composer autoload.php. to standard error and exits 1.
vendor/bin/upgrade-intel wizard
vendor/bin/upgrade-intel analyze --target=vendor/package:^2.0 [options]
vendor/bin/upgrade-intel analyze --target-platform-profile=platform.json [options]Application dispatches to WizardCommand only for the literal wizard subcommand and otherwise delegates to AnalyzeCommand. Both implement the small CommandRunner boundary. -h and --help are recognized before ordinary parsing.
CommandLineOptions::all() is the single source for accepted option names, parse modes, defaults, and help text.
| Option | Repeatable | Default | Meaning |
|---|---|---|---|
--path=PATH |
No | Current directory | Project root to analyze |
--target=PACKAGE:VALUE |
Yes | Empty | Composer package target; php:VERSION is normalized specially |
--target-php=VERSION |
No | None | Exact simulated PHP value |
--target-platform-profile=PATH |
No | None | JSON target platform profile |
--from-php=VALUE |
No | None | Exact current PHP evidence for staged reasoning |
--with-extension=EXT[:VERSION] |
Yes | Empty | Assume an extension is present, optionally at a version |
--without-extension=EXT |
Yes | Empty | Assume an extension is absent |
--source=PATH |
Yes | Empty | Add a project-contained source path |
--framework=NAME |
Yes | Empty | Explicitly activate an installed adapter by name |
--format=json|markdown |
No | json |
Report writer |
--output=PATH |
No | Standard output | Write the rendered report to a validated destination |
--save-report=PATH |
No | None | Preserve stdout and save the same rendered report to a validated destination |
--composer-mode=MODE |
No | compatible |
compatible or restricted
|
--composer-executable=PATH |
No | composer |
Composer command or executable path |
--composer-version=RANGE |
No | >=2.0.0 <3.0.0 |
Expected Composer version constraint |
--composer-timeout=SEC |
No | 300 |
Scenario timeout, validated by the model from 1 through 3600 |
--composer-diagnostic-timeout=SEC |
No | 60 |
Diagnostic timeout, validated by the model from 1 through 900 |
--debug |
No | Off | Preserve temporary Composer workspaces and expose debug paths |
-h, --help
|
No | — | Print help |
At least one package target, target PHP, or target-platform profile is required. --output and --save-report are mutually exclusive.
The parser accepts long values only as --name=value; it does not consume a following token as the value.
Correct:
vendor/bin/upgrade-intel analyze --target=phpunit/phpunit:^12.0Rejected:
vendor/bin/upgrade-intel analyze --target phpunit/phpunit:^12.0Single-valued options and flags are rejected when repeated. List options and extension assumptions are intentionally repeatable.
vendor/bin/upgrade-intel analyze \
--target=laravel/framework:^13.0 \
--target=laravel/passport:^13.0 \
--source=app \
--source=packages/Billing/srcUnknown options produce the generic Unknown option. diagnostic. A flag with a value, such as --debug=true, is rejected because --debug is valueless.
AnalyzeCommand converts parsed strings to:
-
UpgradeTargetvalues; - an optional
TargetPlatformProfileloaded from JSON; -
ExtensionAssumptionvalues; -
ComposerExecutionConfiguration; - one validated
UpgradeRequest.
The command validates an output destination before analysis starts. This avoids spending time on Composer scenarios only to discover that the requested report path is invalid.
--output is file-only delivery and preserves its established success acknowledgement. --save-report first validates the destination, then emits the canonical rendered report on stdout and writes the identical rendered value as a copy.
WizardCommand is a human-facing request builder, not a second analyzer. It requires terminal-attached input and stderr, collects explicit choices, prints the equivalent analyze invocation, obtains confirmation, and delegates that argument vector through CommandRunner. Redirected or non-interactive sessions fail with code 2 and direct callers to analyze; prompt cancellation returns 130.
Package choices use injectable validation seams:
-
LocalPackageTargetValidatorchecks only rootcomposer.jsonrequirements and launches no process. -
ComposerLookupPackageTargetValidatoradapts Core's explicitlocal_cache_onlyorproject_repositorieslookup result. -
PackageTargetValidationdistinguishesinvalid,found,no_matching_version,not_found, andunverified. Onlyfoundand operationallyunverifiedpermit analysis. -
PackageTargetCandidateProvidercan offer a bounded candidate list derived from discovered versions while preserving a custom constraint path.
The project-repository lookup may use network, credentials, and configured repositories. The cache-only path requests no network and never turns missing local metadata into nonexistence. Both use their configured Composer executable with plugins, scripts, interaction, and ANSI disabled, a bounded diagnostic timeout, and redacted bounded diagnostics.
DefaultAnalyzerFactory injects TerminalAnalysisProgressReporter into Core. It prints durable phase and scenario state lines only when stderr is a terminal. Non-TTY runs suppress progress so stdout remains machine-readable. The reporter catches its own failures: the progress surface cannot change the canonical report, exit policy, or analysis result.
| Exit code | CLI meaning |
|---|---|
0 |
A valid report was rendered or written, or help was printed |
1 |
Analysis or delivery failed unexpectedly |
2 |
Invocation was invalid |
130 |
The wizard was cancelled before analysis |
A valid report whose resolution is blocked or unknown still exits 0. Consumers must inspect the JSON report status instead of translating the process exit code into upgrade readiness.
Example CI pattern:
vendor/bin/upgrade-intel analyze \
--path=. \
--target=laravel/framework:^12.0 \
--target-php=8.3 \
--format=json \
--output=build/upgrade-report.json
# Next, validate and inspect metadata.schema_version and resolution.status.FrameworkIntegrationRegistry enumerates installed Composer packages and asks AdapterManifestReader to inspect each package's composer.json.
An adapter package advertises classes like this:
{
"extra": {
"php-upgrade-preflight": {
"framework-adapters": [
"Vendor\\UpgradeAdapter\\FrameworkIntegration"
]
}
}
}The advertised value must be a non-empty JSON list. Every item must be a non-empty, trimmed class name. The registry also requires each loaded class to:
- exist and be instantiable;
- have no required constructor parameters;
- implement
FrameworkIntegration; - return a non-empty, trimmed integration name;
- avoid duplicate class and case-insensitive integration-name registrations.
Healthy integrations are sorted case-insensitively by name, then by class name, giving stable discovery order.
Discovery is isolated per installed package. If one package has unreadable or invalid adapter metadata, the registry skips that package, keeps other integrations, and exposes a diagnostic on standard error.
This behavior differs for an explicit request:
vendor/bin/upgrade-intel analyze \
--target=laravel/framework:^12.0 \
--framework=laravelIf laravel is unavailable, the command fails validation. When adapter packages were skipped, the error names them and includes their manifest-read reasons so the missing adapter is diagnosable.
With no --framework, Core may activate installed adapters through project detection. With --framework=name, the requested named integration must be installed and available.
All command exceptions are written to standard error after SensitiveOutputRedactor::redact(). Reports go to standard output unless --output is supplied. --save-report preserves stdout and adds an identical validated file copy. TTY-only progress also uses stderr; it is absent when stderr is redirected.
Operational messages use path-exposure policy. For example, a successful file write prints a safe path marker when the real path should not be exposed.
--debug is deliberately different: it preserves temporary workspaces for investigation. Those workspaces can contain copied Composer metadata and should be handled as potentially sensitive.
vendor/bin/upgrade-intel analyze --path=. --target-php=8.3vendor/bin/upgrade-intel analyze \
--path=. \
--target=symfony/console:^7.0 \
--from-php=8.1 \
--target-php=8.2 \
--format=markdownvendor/bin/upgrade-intel analyze \
--target=laravel/framework:^11.0 \
--target-php=8.2 \
--with-extension=curl:8.2.0 \
--without-extension=imagickvendor/bin/upgrade-intel analyze \
--target=vendor/package:^2.0 \
--composer-mode=restricted \
--composer-timeout=120 \
--composer-diagnostic-timeout=30Restricted mode can prevent network-backed resolution when artifacts are not already available. Treat that as an environment/evidence limitation, not automatically as a package blocker.
| Class | Responsibility |
|---|---|
CommandLineOption |
Immutable definition of one option |
CommandLineOptions |
Complete option vocabulary, defaults, modes, and help rendering |
CommandLineParser |
Shell-string validation and normalized option array |
Application |
Dispatch between explicit analysis and the interactive wizard |
CommandRunner |
Shared executable-command seam |
AnalyzeCommand |
Request construction, delegation, rendering, exit codes |
WizardCommand |
TTY prompts, review, cancellation, and delegation to analyze
|
WizardInputException |
Typed cancellation or end-of-input signal |
PackageTargetValidator |
Package-target validation contract |
PackageTargetCandidateProvider |
Optional discovered target-candidate contract |
PackageTargetValidation |
Found/not-found/no-match/unverified/invalid result value |
LocalPackageTargetValidator |
Manifest-only package validation without a process |
ComposerLookupPackageTargetValidator |
Adapter from Core Composer metadata results to wizard results |
TerminalAnalysisProgressReporter |
TTY-only stderr renderer for Core progress events |
AdapterManifestReader |
Strictly read one package's adapter metadata |
FrameworkIntegrationRegistry |
Enumerate, instantiate, sort, select, and diagnose adapters |
DefaultAnalyzerFactory |
Construct DefaultUpgradeAnalyzer with discovered integrations |
AnalyzerFactory |
Injection seam for alternate analyzer construction |
PHP Upgrade Preflight — common product and monorepo Wiki · Common repository
- Home
- Key Concepts
- Package Map
- Class and Service Index
- Getting Started
- CLI Reference
- Artisan Command
- Reading the Report
- Safety and Trust Boundaries
- Troubleshooting and FAQ
- Architecture Overview
- Core Package Guide
- Core Analysis Pipeline
- Core Service Reference
- Determinism and Evidence
- Report Schema
- CLI Package Internals
- Laravel Package Internals
- Test Adapters
- Writing a Framework Adapter
- Laravel Adapter Internals
- Contributing
- Roadmap and Status
- Tools Reference
- Quality and Release Tooling
- Release Wiki Strategy