From bf009492e8729145eae23306903d1939b2517b2f Mon Sep 17 00:00:00 2001 From: harsh mahajan Date: Wed, 3 Jun 2026 16:43:10 +0530 Subject: [PATCH 1/6] feat: add type field to distinguish function and site runtimes --- src/Runtimes/Runtime.php | 12 +++++++++++- src/Runtimes/Runtimes.php | 4 ++-- 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/src/Runtimes/Runtime.php b/src/Runtimes/Runtime.php index b196ce7..3f45087 100644 --- a/src/Runtimes/Runtime.php +++ b/src/Runtimes/Runtime.php @@ -24,14 +24,23 @@ class Runtime */ protected $versions = []; + /** + * @var string + */ + protected $type; + + public const TYPE_FUNCTION = 'function'; + public const TYPE_SITE = 'site'; + /** * Runtime that can contain different Versions. */ - public function __construct(string $key, string $name, string $startCommand) + public function __construct(string $key, string $name, string $startCommand, string $type = self::TYPE_FUNCTION) { $this->key = $key; $this->name = $name; $this->startCommand = $startCommand; + $this->type = $type; } /** @@ -68,6 +77,7 @@ public function list(): array 'name' => $this->name, 'logo' => "{$this->key}.png", 'startCommand' => $this->startCommand, + 'type' => $this->type, ], $version->get() ); diff --git a/src/Runtimes/Runtimes.php b/src/Runtimes/Runtimes.php index de2a536..2f7aa43 100644 --- a/src/Runtimes/Runtimes.php +++ b/src/Runtimes/Runtimes.php @@ -171,11 +171,11 @@ public function __construct(string $version = '') $rust->addVersion('1.83', 'rust:1.83.0-alpine3.21', 'openruntimes/rust:'.$this->version.'-1.83', [System::X86, System::ARM64]); $this->runtimes['rust'] = $rust; - $static = new Runtime('static', 'Static', 'bash helpers/server.sh'); + $static = new Runtime('static', 'Static', 'bash helpers/server.sh', Runtime::TYPE_SITE); $static->addVersion('1', 'joseluisq/static-web-server:2.33-alpine', 'openruntimes/static:'.$this->version.'-1', [System::X86, System::ARM64]); $this->runtimes['static'] = $static; - $flutter = new Runtime('flutter', 'Flutter', 'bash helpers/server.sh'); + $flutter = new Runtime('flutter', 'Flutter', 'bash helpers/server.sh', Runtime::TYPE_SITE); $flutter->addVersion('3.24', 'ghcr.io/cirruslabs/flutter:3.24.5', 'openruntimes/flutter:'.$this->version.'-3.24', [System::X86, System::ARM64]); $flutter->addVersion('3.27', 'ghcr.io/cirruslabs/flutter:3.27.3', 'openruntimes/flutter:'.$this->version.'-3.27', [System::X86, System::ARM64]); $flutter->addVersion('3.29', 'ghcr.io/cirruslabs/flutter:3.29.1', 'openruntimes/flutter:'.$this->version.'-3.29', [System::X86, System::ARM64]); From e2f10a3c6bd50f15b5aac3452a2929180b455ddb Mon Sep 17 00:00:00 2001 From: harsh mahajan Date: Wed, 3 Jun 2026 16:47:31 +0530 Subject: [PATCH 2/6] fix: add type validation and getType() getter to Runtime --- src/Runtimes/Runtime.php | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/Runtimes/Runtime.php b/src/Runtimes/Runtime.php index 3f45087..37e5615 100644 --- a/src/Runtimes/Runtime.php +++ b/src/Runtimes/Runtime.php @@ -37,12 +37,23 @@ class Runtime */ public function __construct(string $key, string $name, string $startCommand, string $type = self::TYPE_FUNCTION) { + if (!\in_array($type, [self::TYPE_FUNCTION, self::TYPE_SITE], true)) { + throw new \InvalidArgumentException("Invalid runtime type: {$type}"); + } $this->key = $key; $this->name = $name; $this->startCommand = $startCommand; $this->type = $type; } + /** + * Get type. + */ + public function getType(): string + { + return $this->type; + } + /** * Get key. */ From 508a311aab2b4bbf4965df0b24e65051d3d735ce Mon Sep 17 00:00:00 2001 From: harsh mahajan Date: Wed, 3 Jun 2026 17:06:08 +0530 Subject: [PATCH 3/6] refactor: replace type field with services array on Runtime --- src/Runtimes/Runtime.php | 31 +++++++++++++++++++------------ src/Runtimes/Runtimes.php | 4 ++-- 2 files changed, 21 insertions(+), 14 deletions(-) diff --git a/src/Runtimes/Runtime.php b/src/Runtimes/Runtime.php index 37e5615..fe6853b 100644 --- a/src/Runtimes/Runtime.php +++ b/src/Runtimes/Runtime.php @@ -25,33 +25,40 @@ class Runtime protected $versions = []; /** - * @var string + * @var string[] */ - protected $type; + protected $services; - public const TYPE_FUNCTION = 'function'; - public const TYPE_SITE = 'site'; + public const SERVICE_FUNCTIONS = 'functions'; + public const SERVICE_SITES = 'sites'; /** * Runtime that can contain different Versions. + * + * @param string[] $services */ - public function __construct(string $key, string $name, string $startCommand, string $type = self::TYPE_FUNCTION) + public function __construct(string $key, string $name, string $startCommand, array $services = [self::SERVICE_FUNCTIONS]) { - if (!\in_array($type, [self::TYPE_FUNCTION, self::TYPE_SITE], true)) { - throw new \InvalidArgumentException("Invalid runtime type: {$type}"); + $validServices = [self::SERVICE_FUNCTIONS, self::SERVICE_SITES]; + foreach ($services as $service) { + if (!\in_array($service, $validServices, true)) { + throw new \InvalidArgumentException("Invalid runtime service: {$service}"); + } } $this->key = $key; $this->name = $name; $this->startCommand = $startCommand; - $this->type = $type; + $this->services = $services; } /** - * Get type. + * Get services. + * + * @return string[] */ - public function getType(): string + public function getServices(): array { - return $this->type; + return $this->services; } /** @@ -88,7 +95,7 @@ public function list(): array 'name' => $this->name, 'logo' => "{$this->key}.png", 'startCommand' => $this->startCommand, - 'type' => $this->type, + 'services' => $this->services, ], $version->get() ); diff --git a/src/Runtimes/Runtimes.php b/src/Runtimes/Runtimes.php index 2f7aa43..ae27fee 100644 --- a/src/Runtimes/Runtimes.php +++ b/src/Runtimes/Runtimes.php @@ -171,11 +171,11 @@ public function __construct(string $version = '') $rust->addVersion('1.83', 'rust:1.83.0-alpine3.21', 'openruntimes/rust:'.$this->version.'-1.83', [System::X86, System::ARM64]); $this->runtimes['rust'] = $rust; - $static = new Runtime('static', 'Static', 'bash helpers/server.sh', Runtime::TYPE_SITE); + $static = new Runtime('static', 'Static', 'bash helpers/server.sh', [Runtime::SERVICE_SITES]); $static->addVersion('1', 'joseluisq/static-web-server:2.33-alpine', 'openruntimes/static:'.$this->version.'-1', [System::X86, System::ARM64]); $this->runtimes['static'] = $static; - $flutter = new Runtime('flutter', 'Flutter', 'bash helpers/server.sh', Runtime::TYPE_SITE); + $flutter = new Runtime('flutter', 'Flutter', 'bash helpers/server.sh', [Runtime::SERVICE_SITES]); $flutter->addVersion('3.24', 'ghcr.io/cirruslabs/flutter:3.24.5', 'openruntimes/flutter:'.$this->version.'-3.24', [System::X86, System::ARM64]); $flutter->addVersion('3.27', 'ghcr.io/cirruslabs/flutter:3.27.3', 'openruntimes/flutter:'.$this->version.'-3.27', [System::X86, System::ARM64]); $flutter->addVersion('3.29', 'ghcr.io/cirruslabs/flutter:3.29.1', 'openruntimes/flutter:'.$this->version.'-3.29', [System::X86, System::ARM64]); From b9a817fa25cac6d4dbf361c03468188f9d9354ad Mon Sep 17 00:00:00 2001 From: harsh mahajan Date: Wed, 3 Jun 2026 17:10:19 +0530 Subject: [PATCH 4/6] fix: guard against empty services array in Runtime constructor --- src/Runtimes/Runtime.php | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/Runtimes/Runtime.php b/src/Runtimes/Runtime.php index fe6853b..a0b5f3d 100644 --- a/src/Runtimes/Runtime.php +++ b/src/Runtimes/Runtime.php @@ -39,6 +39,9 @@ class Runtime */ public function __construct(string $key, string $name, string $startCommand, array $services = [self::SERVICE_FUNCTIONS]) { + if (empty($services)) { + throw new \InvalidArgumentException('Runtime must be associated with at least one service.'); + } $validServices = [self::SERVICE_FUNCTIONS, self::SERVICE_SITES]; foreach ($services as $service) { if (!\in_array($service, $validServices, true)) { From 81fee144fe4a732fe009b233fc0d3a064b9dfd9a Mon Sep 17 00:00:00 2001 From: harsh mahajan Date: Wed, 3 Jun 2026 17:33:31 +0530 Subject: [PATCH 5/6] fix: address review feedback - add setServices, full test coverage, remove duplication --- src/Runtimes/Runtime.php | 37 +++++++++------- tests/Runtimes/RuntimesTest.php | 75 +++++++++++++++++++++++++++++++++ 2 files changed, 97 insertions(+), 15 deletions(-) diff --git a/src/Runtimes/Runtime.php b/src/Runtimes/Runtime.php index a0b5f3d..1c465f3 100644 --- a/src/Runtimes/Runtime.php +++ b/src/Runtimes/Runtime.php @@ -24,10 +24,7 @@ class Runtime */ protected $versions = []; - /** - * @var string[] - */ - protected $services; + protected array $services; public const SERVICE_FUNCTIONS = 'functions'; public const SERVICE_SITES = 'sites'; @@ -37,21 +34,12 @@ class Runtime * * @param string[] $services */ - public function __construct(string $key, string $name, string $startCommand, array $services = [self::SERVICE_FUNCTIONS]) + public function __construct(string $key, string $name, string $startCommand, array $services = [self::SERVICE_FUNCTIONS, self::SERVICE_SITES]) { - if (empty($services)) { - throw new \InvalidArgumentException('Runtime must be associated with at least one service.'); - } - $validServices = [self::SERVICE_FUNCTIONS, self::SERVICE_SITES]; - foreach ($services as $service) { - if (!\in_array($service, $validServices, true)) { - throw new \InvalidArgumentException("Invalid runtime service: {$service}"); - } - } $this->key = $key; $this->name = $name; $this->startCommand = $startCommand; - $this->services = $services; + $this->setServices($services); } /** @@ -64,6 +52,25 @@ public function getServices(): array return $this->services; } + /** + * Set services. + * + * @param string[] $services + */ + public function setServices(array $services): void + { + if (empty($services)) { + throw new \InvalidArgumentException('Runtime must be associated with at least one service.'); + } + $validServices = [self::SERVICE_FUNCTIONS, self::SERVICE_SITES]; + foreach ($services as $service) { + if (!\in_array($service, $validServices, true)) { + throw new \InvalidArgumentException("Invalid runtime service: {$service}"); + } + } + $this->services = $services; + } + /** * Get key. */ diff --git a/tests/Runtimes/RuntimesTest.php b/tests/Runtimes/RuntimesTest.php index 5281e38..8da87f4 100644 --- a/tests/Runtimes/RuntimesTest.php +++ b/tests/Runtimes/RuntimesTest.php @@ -2,6 +2,7 @@ namespace Appwrite\Tests; +use Appwrite\Runtimes\Runtime; use Appwrite\Runtimes\Runtimes; use PHPUnit\Framework\TestCase; @@ -195,10 +196,84 @@ public function testGetRuntimes(): void $this->assertArrayHasKey('image', $runtime, $runtime['name']); $this->assertArrayHasKey('logo', $runtime, $runtime['name']); $this->assertArrayHasKey('supports', $runtime, $runtime['name']); + $this->assertArrayHasKey('services', $runtime, $runtime['name']); $this->assertStringContainsString('v1-', $runtime['image']); $this->assertIsArray($runtime['supports']); $this->assertNotEmpty($runtime['supports']); + $this->assertIsArray($runtime['services']); + $this->assertNotEmpty($runtime['services']); } } + + public function testFlutterAndStaticAreSiteOnly(): void + { + $flutter = $this->instance->get('flutter'); + $this->assertEquals([Runtime::SERVICE_SITES], $flutter->getServices()); + + $static = $this->instance->get('static'); + $this->assertEquals([Runtime::SERVICE_SITES], $static->getServices()); + } + + public function testDefaultRuntimesAvailableForBothServices(): void + { + $node = $this->instance->get('node'); + $this->assertContains(Runtime::SERVICE_FUNCTIONS, $node->getServices()); + $this->assertContains(Runtime::SERVICE_SITES, $node->getServices()); + } + + public function testServicesFieldInListOutput(): void + { + $node = $this->instance->get('node'); + $versions = $node->list(); + + $this->assertArrayHasKey('node-18.0', $versions); + $this->assertArrayHasKey('services', $versions['node-18.0']); + $this->assertContains(Runtime::SERVICE_FUNCTIONS, $versions['node-18.0']['services']); + $this->assertContains(Runtime::SERVICE_SITES, $versions['node-18.0']['services']); + } + + public function testSetServices(): void + { + $runtime = $this->instance->get('node'); + + $runtime->setServices([Runtime::SERVICE_FUNCTIONS]); + $this->assertEquals([Runtime::SERVICE_FUNCTIONS], $runtime->getServices()); + + $runtime->setServices([Runtime::SERVICE_SITES]); + $this->assertEquals([Runtime::SERVICE_SITES], $runtime->getServices()); + + $runtime->setServices([Runtime::SERVICE_FUNCTIONS, Runtime::SERVICE_SITES]); + $this->assertEquals([Runtime::SERVICE_FUNCTIONS, Runtime::SERVICE_SITES], $runtime->getServices()); + } + + public function testConstructorThrowsOnInvalidService(): void + { + $this->expectException(\InvalidArgumentException::class); + $this->expectExceptionMessage('Invalid runtime service: invalid'); + new Runtime('test', 'Test', 'bash helpers/server.sh', ['invalid']); + } + + public function testConstructorThrowsOnEmptyServices(): void + { + $this->expectException(\InvalidArgumentException::class); + $this->expectExceptionMessage('Runtime must be associated with at least one service.'); + new Runtime('test', 'Test', 'bash helpers/server.sh', []); + } + + public function testSetServicesThrowsOnEmptyArray(): void + { + $this->expectException(\InvalidArgumentException::class); + $this->expectExceptionMessage('Runtime must be associated with at least one service.'); + $runtime = $this->instance->get('node'); + $runtime->setServices([]); + } + + public function testSetServicesThrowsOnInvalidService(): void + { + $this->expectException(\InvalidArgumentException::class); + $this->expectExceptionMessage('Invalid runtime service: invalid'); + $runtime = $this->instance->get('node'); + $runtime->setServices(['invalid']); + } } From 1a951b0d894e470998a2b9316769a2460839569b Mon Sep 17 00:00:00 2001 From: harsh mahajan Date: Wed, 3 Jun 2026 17:34:40 +0530 Subject: [PATCH 6/6] fix: add PHPDoc type hint to satisfy PHPStan level 8 --- src/Runtimes/Runtime.php | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Runtimes/Runtime.php b/src/Runtimes/Runtime.php index 1c465f3..9274881 100644 --- a/src/Runtimes/Runtime.php +++ b/src/Runtimes/Runtime.php @@ -24,6 +24,7 @@ class Runtime */ protected $versions = []; + /** @var string[] */ protected array $services; public const SERVICE_FUNCTIONS = 'functions';