Skip to content

Add structured Command execution with composition and redirects#2

Merged
eldadfux merged 5 commits intomainfrom
feat/command-validation
Apr 17, 2026
Merged

Add structured Command execution with composition and redirects#2
eldadfux merged 5 commits intomainfrom
feat/command-validation

Conversation

@TorstenDittmann
Copy link
Copy Markdown
Contributor

@TorstenDittmann TorstenDittmann commented Apr 14, 2026

Summary

  • introduce a Utopia\Command value object for structured command building in Console::execute()
  • replace the initial generic add() API with explicit flag(), option(), and argument() methods backed by per-value validation
  • add static composition helpers for shell operators and redirects: Command::pipe(), Command::and(), Command::or(), Command::group(), Command::redirectStdout(), Command::appendStdout(), and Command::redirectInput()
  • preserve backward compatibility for existing string and array command execution paths while making structured commands the documented default

Command API

  • plain commands can be built fluently with flag(), option(), and argument()
  • option() and argument() default to Text(0) validation while still allowing custom validators or callables
  • composed, grouped, and redirected commands are created through static helpers and serialize to shell expressions
  • plain commands execute in argv mode, while composed commands execute in shell mode
  • toArray() is now limited to plain commands and throws on composed/grouped/redirected expressions

Console updates

  • Console::execute() accepts Command|array|string
  • plain Command instances are converted to argv arrays before proc_open()
  • composed Command expressions are converted to shell strings before execution
  • fix the command exit-code handling so non-string execution paths return the real child process exit code instead of incorrectly falling back to 0

Dependency and platform changes

  • switch from the indirect utopia-php/servers validator dependency to the direct utopia-php/validators package
  • restore the package PHP requirement to >=8.0
  • add PHP 8.1 back to the CI matrix
  • regenerate composer.lock for the updated dependency graph

Documentation

  • update README examples to use the final Command API
  • document command composition, grouping, and redirect usage
  • clarify when commands run in argv mode versus shell mode

Testing

  • migrate the existing execution tests to the new API
  • add coverage for validation, escaping, composition, grouping, redirects, misuse protection, and execution semantics
  • local checks passing:
    • composer test
    • composer lint
    • composer check

- Add Utopia\Command class for building shell commands safely
- Support per-argument validation using Utopia\Servers\Validator or callables
- Add toArray() for safe argv execution and toString() for escaped shell strings
- Update Console::execute() to accept Command|array|string
- Fix exit code handling for Command/array execution
- Add tests for Command functionality
- Update README with Command usage examples
- Add utopia-php/servers dependency for Validator class
@greptile-apps
Copy link
Copy Markdown

greptile-apps bot commented Apr 14, 2026

Greptile Summary

This PR introduces a Command value object for structured shell command building with per-argument validation, and updates Console::execute() to accept Command|array|string. It also fixes a pre-existing bug where array commands always returned exit code 0 because the pipe-3 status trick was only wired for shell-string execution.

Confidence Score: 5/5

Safe to merge; all previously flagged issues (PHP version constraint, bool in type union, missing Stringable) are resolved

The only remaining finding is a P2 visibility concern (protected vs private on $arguments). No P0 or P1 issues remain.

src/Command.php — $arguments visibility

Important Files Changed

Filename Overview
src/Command.php New Command value object with plain/composite/group/redirect types; shell operators are hardcoded and args are escapeshellarg-escaped; minor: $arguments is protected instead of private
src/Console.php Accepts `Command
tests/Console/ConsoleTest.php Comprehensive test coverage added for all Command types, validators, composition, redirects, and shell execution paths
composer.json Adds utopia-php/validators ^0.1.0; lock confirms it requires >=8.0, consistent with this package's constraint
README.md Documents Command usage, all builder methods, composition helpers, and updated Console::execute() signature

Reviews (5): Last reviewed commit: "Refine command composition API and redir..." | Re-trigger Greptile

Comment thread composer.json
Comment thread src/Command.php Outdated
Comment thread src/Command.php
- Remove bool from union types to avoid confusing error messages
- Update PHP version constraint to >=8.2 (required by transitive dependency)
- Implement Stringable interface with __toString() method
@TorstenDittmann
Copy link
Copy Markdown
Contributor Author

@greptile-apps Thanks for the review! I've addressed all three comments:

  1. PHP version constraint: Updated from >=8.0 to >=8.2 to match the transitive dependency requirement from utopia-php/di

  2. Removed bool from union types: The bool type has been removed from both add() and normalize() methods to avoid the confusing error when false is passed (since (string) false === '')

  3. Implemented Stringable: Added implements Stringable and __toString() method that delegates to toString()

All tests pass and PHPStan shows no errors. Could you please take another look?

- Remove PHP 8.1 from test matrix (now requires PHP 8.2+)
- Regenerate composer.lock to sync with updated PHP version constraint
- Fix Console::execute() to use proc_get_status()['exitcode'] instead of proc_close() return value, which can be -1 on some systems
- Regenerate composer.lock using PHP 8.2 to ensure dependencies are compatible with minimum PHP version
@TorstenDittmann
Copy link
Copy Markdown
Contributor Author

@greptile-apps We've pushed fixes for the CI failures:

  1. Fixed exit code handling: Changed Console::execute() to use proc_get_status()['exitcode'] instead of proc_close() return value. The proc_close() function returns -1 on some systems (like macOS with Herd PHP), which was causing test failures.

  2. Regenerated composer.lock with PHP 8.2: Previously the lock file was generated with PHP 8.4, which pulled in doctrine/instantiator requiring PHP ^8.4. This caused syntax errors on PHP 8.2 CI jobs. The lock file is now generated with PHP 8.2 to ensure all dependencies are compatible with the minimum PHP version.

All tests pass locally now. Could you please take another look?

Comment thread README.md Outdated
Comment thread src/Command.php Outdated
@TorstenDittmann
Copy link
Copy Markdown
Contributor Author

@greptile-apps We pushed a larger API refinement on top of the original Command work. Could you please take another look?

Highlights:

  • replaced add() with flag(), option(), and argument()
  • added static composition helpers: Command::pipe(), Command::and(), Command::or(), Command::group()
  • added redirect helpers: Command::redirectStdout(), Command::appendStdout(), Command::redirectInput()
  • switched to utopia-php/validators directly and restored PHP >=8.0
  • expanded the test suite substantially for composition, redirects, and misuse protection

All checks are passing locally:

  • composer test
  • composer lint
  • composer check

@TorstenDittmann TorstenDittmann changed the title Add Command class for structured command building with validation Refine Command API with composition and redirects Apr 17, 2026
@TorstenDittmann TorstenDittmann changed the title Refine Command API with composition and redirects Add structured Command execution with composition and redirects Apr 17, 2026
@eldadfux eldadfux merged commit 0e580dc into main Apr 17, 2026
7 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants