From 5bc2755e1210b468245f0698ebc49efc9f383642 Mon Sep 17 00:00:00 2001 From: Greg Bowler Date: Mon, 11 May 2026 18:08:39 +0100 Subject: [PATCH 1/3] docs: --empty blueprint to match webengine docs --- src/Command/CreateCommand.php | 67 ++++++++++++++++++++++------------- 1 file changed, 43 insertions(+), 24 deletions(-) diff --git a/src/Command/CreateCommand.php b/src/Command/CreateCommand.php index 0de1b2d..2505aed 100644 --- a/src/Command/CreateCommand.php +++ b/src/Command/CreateCommand.php @@ -17,31 +17,9 @@ class CreateCommand extends Command { */ public function run(?ArgumentValueList $arguments = null):int { $name = $this->readValidName($arguments->get("projectName", "")); - $namespace = $this->readValidNamespace($arguments->get("namespace", "")); - $blueprintCollection = new BlueprintCollection(); - $blueprintInput = "0"; - if($arguments->contains("blueprint")) { - $blueprintInput = $arguments->get("blueprint")->get(); - } - elseif($arguments->contains("empty")) { - $blueprintInput = "empty"; - } - else { - $this->writeLine("What blueprint would you like to start with? (type the number)"); - - foreach($blueprintCollection as $i => $blueprint) { - $title = $blueprint->getTitle(); - $description = $blueprint->getDescription(); - $this->writeLine( " $i: $title - $description"); - } - $blueprintInput = $this->readLine($blueprintInput); - - if($blueprintInput < 0 || $blueprintInput >= count($blueprintCollection)) { - $this->writeLine("Cancelling due to invalid blueprint."); - exit; // phpcs:ignore - } - } + $blueprintInput = $this->readBlueprintInput($arguments, $blueprintCollection); + $namespace = $this->readNamespaceForArguments($arguments); if(is_numeric($blueprintInput)) { $selectedBlueprint = $blueprintCollection->getByIndex((int)$blueprintInput); @@ -223,5 +201,46 @@ private function readValidNamespace(ArgumentValue $namespace):string { return $namespace; } + private function readBlueprintInput( + ArgumentValueList $arguments, + BlueprintCollection $blueprintCollection + ):string { + if($arguments->contains("blueprint")) { + return $arguments->get("blueprint")->get(); + } + + if($arguments->contains("empty")) { + return "empty"; + } + + $this->writeLine("What blueprint would you like to start with? (type the number)"); + + foreach($blueprintCollection as $i => $blueprint) { + $title = $blueprint->getTitle(); + $description = $blueprint->getDescription(); + $this->writeLine( " $i: $title - $description"); + } + + $blueprintInput = $this->readLine("0"); + if($blueprintInput < 0 || $blueprintInput >= count($blueprintCollection)) { + $this->writeLine("Cancelling due to invalid blueprint."); + exit; // phpcs:ignore + } + + return $blueprintInput; + } + + private function readNamespaceForArguments(ArgumentValueList $arguments):string { + if($arguments->contains("empty")) { + $namespace = "App"; + $this->writeLine(); + $this->writeLine("Using namespace '$namespace'."); + $this->writeLine(); + return $namespace; + } + + return $this->readValidNamespace($arguments->get("namespace", "")); + } + } From 8ac8f7337a6b5f6a8bc44368230ec3d592a85216 Mon Sep 17 00:00:00 2001 From: Greg Bowler Date: Mon, 18 May 2026 18:39:07 +0100 Subject: [PATCH 2/3] feature: detect ini/json default in webengine closes #81 --- README.md | 1 + bin/gt | 2 ++ src/Command/BuildCommand.php | 10 +++++++++- 3 files changed, 12 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index dcea096..4d309d2 100644 --- a/README.md +++ b/README.md @@ -7,6 +7,7 @@ The following commands are exposed: + `gt add` - add a page, API endpoint or cron script from a template + `gt create` - create a new WebEngine application ++ `gt test` - run configured PHP and JavaScript test suites + `gt serve` - run the inbuilt development server + `gt build` - compile client-side assets + `gt cron` - invoke scripts or static functions at regular intervals diff --git a/bin/gt b/bin/gt index 383a364..d7774de 100755 --- a/bin/gt +++ b/bin/gt @@ -9,6 +9,7 @@ use GT\GtCommand\Command\MigrateCommand; use GT\GtCommand\Command\ServeCommand; use GT\GtCommand\Command\CronCommand; use GT\GtCommand\Command\RunCommand; +use GT\GtCommand\Command\TestCommand; use GT\GtCommand\Command\DeployCommand; foreach([ __DIR__ . "/../../..", __DIR__ . "/../vendor" ] as $vendor) { @@ -24,6 +25,7 @@ $app = new Application( new ArgumentList(...$argv), new AddCommand(), new CreateCommand(), + new TestCommand(), new RunCommand(), new DeployCommand(), new MigrateCommand(), diff --git a/src/Command/BuildCommand.php b/src/Command/BuildCommand.php index cc9221f..b6432e6 100644 --- a/src/Command/BuildCommand.php +++ b/src/Command/BuildCommand.php @@ -11,7 +11,15 @@ public function __construct() { public function run(?ArgumentValueList $arguments = null):int { if(!$arguments->contains("default")) { - $arguments->set("default", "vendor/phpgt/webengine/build.default.json"); + $defaultPathPrefix = "vendor/phpgt/webengine/build.default"; + $defaultPathExtensionPriority = ["ini", "json"]; + foreach($defaultPathExtensionPriority as $ext) { + $defaultPath = "$defaultPathPrefix.$ext"; + if(file_exists($defaultPath)) { + $arguments->set("default", $defaultPath); + break; + } + } } return parent::run($arguments); From ffd41826a826f9873809c57417b73bf1318acf89 Mon Sep 17 00:00:00 2001 From: Greg Bowler Date: Mon, 18 May 2026 18:41:13 +0100 Subject: [PATCH 3/3] feature: gt test --- src/Command/TestCommand.php | 140 ++++++++++++++++++++++++++++++++++++ 1 file changed, 140 insertions(+) create mode 100644 src/Command/TestCommand.php diff --git a/src/Command/TestCommand.php b/src/Command/TestCommand.php new file mode 100644 index 0000000..6300850 --- /dev/null +++ b/src/Command/TestCommand.php @@ -0,0 +1,140 @@ +getTestSuiteList(); + if(!$testSuiteList) { + $this->writeLine("No test suites were found.", Stream::ERROR); + $this->writeLine("Checked composer.json and package.json for a test script.", Stream::ERROR); + return 1; + } + + $passedCount = 0; + $failedCount = 0; + foreach($testSuiteList as $testSuite) { + if($this->runTestSuite($testSuite)) { + $passedCount++; + } + else { + $failedCount++; + } + } + + $this->writeLine(); + if($failedCount > 0) { + $this->writeLine("Test result: $passedCount passed, $failedCount failed.", Stream::ERROR); + return 1; + } + + $this->writeLine("Test result: $passedCount passed."); + return 0; + } + + public function getName():string { + return "test"; + } + + public function getDescription():string { + return "Run configured PHP and JavaScript test suites"; + } + + public function getRequiredNamedParameterList():array { + return []; + } + + public function getOptionalNamedParameterList():array { + return []; + } + + public function getRequiredParameterList():array { + return []; + } + + public function getOptionalParameterList():array { + return []; + } + + /** @return array}> */ + private function getTestSuiteList():array { + $testSuiteList = []; + + if($this->hasScript("composer.json", "test")) { + $testSuiteList[] = [ + "name" => "PHP", + "source" => "composer.json", + "command" => ["composer", "test"], + ]; + } + + if($this->hasScript("package.json", "test")) { + $testSuiteList[] = [ + "name" => "JavaScript", + "source" => "package.json", + "command" => ["npm", "run", "test"], + ]; + } + + return $testSuiteList; + } + + private function hasScript(string $fileName, string $scriptName):bool { + if(!is_file($fileName)) { + return false; + } + + $contents = file_get_contents($fileName); + if($contents === false) { + $this->writeLine("Unable to read $fileName.", Stream::ERROR); + return false; + } + + try { + $json = json_decode($contents, true, 512, JSON_THROW_ON_ERROR); + } + catch(JsonException $exception) { + $this->writeLine("Unable to parse $fileName: " . $exception->getMessage(), Stream::ERROR); + return false; + } + + if(!isset($json["scripts"]) || !is_array($json["scripts"])) { + return false; + } + + return isset($json["scripts"][$scriptName]); + } + + /** @param array{name: string, source: string, command: array} $testSuite */ + private function runTestSuite(array $testSuite):bool { + $this->writeLine(); + $this->writeLine("Running {$testSuite["name"]} tests from {$testSuite["source"]}..."); + + $process = new Process(...$testSuite["command"]); + $process->exec(); + + do { + $this->write($process->getOutput()); + $this->write($process->getErrorOutput(), Stream::ERROR); + usleep(100_000); + } + while($process->isRunning()); + + $exitCode = $process->getExitCode(); + if($exitCode === 0) { + $this->writeLine("{$testSuite["name"]} tests passed."); + return true; + } + + $this->writeLine("{$testSuite["name"]} tests failed with exit code $exitCode.", Stream::ERROR); + return false; + } +}