From 297817e4ce008741eb01d6af163e1c2601abde8e Mon Sep 17 00:00:00 2001 From: Holger Veltrup Date: Tue, 9 Jul 2024 11:55:41 +0200 Subject: [PATCH] feat: load environment-files --- composer.json | 28 ++- composer.lock | 144 ++++++++--- phpstan.neon.dist | 8 + src/Composer/ComposerPlugin.php | 12 +- src/Executor/EnvSetter.php | 134 +++++++++++ src/Executor/IniSetter.php | 7 - src/Executor/Platform.php | 17 ++ test/Executor/EnvSetterTest.php | 226 ++++++++++++++++++ .../resources/Executor/EnvSetter/environments | 4 + .../Executor/EnvSetter/no-environments | 4 + 10 files changed, 522 insertions(+), 62 deletions(-) create mode 100644 src/Executor/EnvSetter.php create mode 100644 test/Executor/EnvSetterTest.php create mode 100644 test/resources/Executor/EnvSetter/environments create mode 100644 test/resources/Executor/EnvSetter/no-environments diff --git a/composer.json b/composer.json index 7158d51..899111a 100644 --- a/composer.json +++ b/composer.json @@ -11,9 +11,10 @@ ], "require": { "php": ">=8.1 <8.4.0", + "ext-posix": "*", "composer-plugin-api": "^2.1", - "symfony/filesystem": "^6.4 || ^7.0", - "ext-posix": "*" + "symfony/dotenv": "^7.1", + "symfony/filesystem": "^6.4 || ^7.0" }, "require-dev": { "composer/composer": "^2.0", @@ -48,8 +49,8 @@ }, "config": { "allow-plugins": { - "infection/extension-installer": true, - "dealerdirect/phpcodesniffer-composer-installer": true + "dealerdirect/phpcodesniffer-composer-installer": true, + "infection/extension-installer": true }, "optimize-autoloader": true, "preferred-install": { @@ -58,19 +59,20 @@ "sort-packages": true }, "extra": { - "class": "Atoolo\\Runtime\\Composer\\ComposerPlugin", - "branch-alias" : { - "dev-main" : "1.x-dev" - }, - "atoolo" : { - "runtime" : { - "executor" : [ + "atoolo": { + "runtime": { + "executor": [ + "Atoolo\\Runtime\\Executor\\EnvSetter", "Atoolo\\Runtime\\Executor\\IniSetter", "Atoolo\\Runtime\\Executor\\UmaskSetter", "Atoolo\\Runtime\\Executor\\UserValidator" ] } - } + }, + "branch-alias": { + "dev-main": "1.x-dev" + }, + "class": "Atoolo\\Runtime\\Composer\\ComposerPlugin" }, "scripts": { "post-install-cmd": "phive --no-progress install --force-accept-unsigned --trust-gpg-keys C00543248C87FB13,4AA394086372C20A,CF1A108D0E7AE720,51C67305FFC2E5C0,E82B2FB314E9906E", @@ -81,7 +83,7 @@ "@analyse:compatibilitycheck" ], "analyse:compatibilitycheck": "./vendor/bin/phpcs --standard=./phpcs.compatibilitycheck.xml", - "analyse:phpcsfixer": "./tools/php-cs-fixer check --diff --show-progress=dots", + "analyse:phpcsfixer": "./tools/php-cs-fixer check --diff --show-progress=dots", "analyse:phplint": "./tools/phplint", "analyse:phpstan": "./tools/phpstan analyse", "cs-fix": [ diff --git a/composer.lock b/composer.lock index 9653a75..003515c 100644 --- a/composer.lock +++ b/composer.lock @@ -4,8 +4,82 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "539ad7ffbedb22ed5b6bb50ae7e315f6", + "content-hash": "4f5126db7a73b9a7793eb6c90f0c8520", "packages": [ + { + "name": "symfony/dotenv", + "version": "v7.1.1", + "source": { + "type": "git", + "url": "https://github.com/symfony/dotenv.git", + "reference": "efa715ec40c098f2fba62444f4fd75d0d4248ede" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/dotenv/zipball/efa715ec40c098f2fba62444f4fd75d0d4248ede", + "reference": "efa715ec40c098f2fba62444f4fd75d0d4248ede", + "shasum": "" + }, + "require": { + "php": ">=8.2" + }, + "conflict": { + "symfony/console": "<6.4", + "symfony/process": "<6.4" + }, + "require-dev": { + "symfony/console": "^6.4|^7.0", + "symfony/process": "^6.4|^7.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Dotenv\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Registers environment variables from a .env file", + "homepage": "https://symfony.com", + "keywords": [ + "dotenv", + "env", + "environment" + ], + "support": { + "source": "https://github.com/symfony/dotenv/tree/v7.1.1" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-05-31T14:57:53+00:00" + }, { "name": "symfony/filesystem", "version": "v7.1.2", @@ -326,16 +400,16 @@ }, { "name": "composer/ca-bundle", - "version": "1.5.0", + "version": "1.5.1", "source": { "type": "git", "url": "https://github.com/composer/ca-bundle.git", - "reference": "0c5ccfcfea312b5c5a190a21ac5cef93f74baf99" + "reference": "063d9aa8696582f5a41dffbbaf3c81024f0a604a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/composer/ca-bundle/zipball/0c5ccfcfea312b5c5a190a21ac5cef93f74baf99", - "reference": "0c5ccfcfea312b5c5a190a21ac5cef93f74baf99", + "url": "https://api.github.com/repos/composer/ca-bundle/zipball/063d9aa8696582f5a41dffbbaf3c81024f0a604a", + "reference": "063d9aa8696582f5a41dffbbaf3c81024f0a604a", "shasum": "" }, "require": { @@ -345,7 +419,7 @@ }, "require-dev": { "phpstan/phpstan": "^1.10", - "psr/log": "^1.0", + "psr/log": "^1.0 || ^2.0 || ^3.0", "symfony/phpunit-bridge": "^4.2 || ^5", "symfony/process": "^4.0 || ^5.0 || ^6.0 || ^7.0" }, @@ -382,7 +456,7 @@ "support": { "irc": "irc://irc.freenode.org/composer", "issues": "https://github.com/composer/ca-bundle/issues", - "source": "https://github.com/composer/ca-bundle/tree/1.5.0" + "source": "https://github.com/composer/ca-bundle/tree/1.5.1" }, "funding": [ { @@ -398,7 +472,7 @@ "type": "tidelift" } ], - "time": "2024-03-15T14:00:32+00:00" + "time": "2024-07-08T15:28:20+00:00" }, { "name": "composer/class-map-generator", @@ -1403,20 +1477,20 @@ }, { "name": "justinrainbow/json-schema", - "version": "v5.2.13", + "version": "5.3.0", "source": { "type": "git", "url": "https://github.com/jsonrainbow/json-schema.git", - "reference": "fbbe7e5d79f618997bc3332a6f49246036c45793" + "reference": "feb2ca6dd1cebdaf1ed60a4c8de2e53ce11c4fd8" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/jsonrainbow/json-schema/zipball/fbbe7e5d79f618997bc3332a6f49246036c45793", - "reference": "fbbe7e5d79f618997bc3332a6f49246036c45793", + "url": "https://api.github.com/repos/jsonrainbow/json-schema/zipball/feb2ca6dd1cebdaf1ed60a4c8de2e53ce11c4fd8", + "reference": "feb2ca6dd1cebdaf1ed60a4c8de2e53ce11c4fd8", "shasum": "" }, "require": { - "php": ">=5.3.3" + "php": ">=7.1" }, "require-dev": { "friendsofphp/php-cs-fixer": "~2.2.20||~2.15.1", @@ -1427,11 +1501,6 @@ "bin/validate-json" ], "type": "library", - "extra": { - "branch-alias": { - "dev-master": "5.0.x-dev" - } - }, "autoload": { "psr-4": { "JsonSchema\\": "src/JsonSchema/" @@ -1467,9 +1536,9 @@ ], "support": { "issues": "https://github.com/jsonrainbow/json-schema/issues", - "source": "https://github.com/jsonrainbow/json-schema/tree/v5.2.13" + "source": "https://github.com/jsonrainbow/json-schema/tree/5.3.0" }, - "time": "2023-09-26T02:20:38+00:00" + "time": "2024-07-06T21:00:26+00:00" }, { "name": "myclabs/deep-copy", @@ -2168,16 +2237,16 @@ }, { "name": "phpunit/phpunit", - "version": "10.5.24", + "version": "10.5.26", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/phpunit.git", - "reference": "5f124e3e3e561006047b532fd0431bf5bb6b9015" + "reference": "42e2f13ceaa2e34461bc89bea75407550b40b2aa" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/5f124e3e3e561006047b532fd0431bf5bb6b9015", - "reference": "5f124e3e3e561006047b532fd0431bf5bb6b9015", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/42e2f13ceaa2e34461bc89bea75407550b40b2aa", + "reference": "42e2f13ceaa2e34461bc89bea75407550b40b2aa", "shasum": "" }, "require": { @@ -2249,7 +2318,7 @@ "support": { "issues": "https://github.com/sebastianbergmann/phpunit/issues", "security": "https://github.com/sebastianbergmann/phpunit/security/policy", - "source": "https://github.com/sebastianbergmann/phpunit/tree/10.5.24" + "source": "https://github.com/sebastianbergmann/phpunit/tree/10.5.26" }, "funding": [ { @@ -2265,7 +2334,7 @@ "type": "tidelift" } ], - "time": "2024-06-20T13:09:54+00:00" + "time": "2024-07-08T05:30:46+00:00" }, { "name": "psr/container", @@ -2449,12 +2518,12 @@ "source": { "type": "git", "url": "https://github.com/Roave/SecurityAdvisories.git", - "reference": "27714b56f04815b654c3805502ab77207505ac19" + "reference": "0970dcafb84065dda980b50a7074acacafd68368" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Roave/SecurityAdvisories/zipball/27714b56f04815b654c3805502ab77207505ac19", - "reference": "27714b56f04815b654c3805502ab77207505ac19", + "url": "https://api.github.com/repos/Roave/SecurityAdvisories/zipball/0970dcafb84065dda980b50a7074acacafd68368", + "reference": "0970dcafb84065dda980b50a7074acacafd68368", "shasum": "" }, "conflict": { @@ -2462,7 +2531,10 @@ "admidio/admidio": "<4.2.13", "adodb/adodb-php": "<=5.20.20|>=5.21,<=5.21.3", "aheinze/cockpit": "<2.2", + "aimeos/ai-admin-graphql": ">=2022.04.1,<2022.10.10|>=2023.04.1,<2023.10.6|>=2024.04.1,<2024.04.6", + "aimeos/ai-admin-jsonadm": "<2020.10.13|>=2021.04.1,<2021.10.6|>=2022.04.1,<2022.10.3|>=2023.04.1,<2023.10.4|==2024.04.1", "aimeos/ai-client-html": ">=2020.04.1,<2020.10.27|>=2021.04.1,<2021.10.22|>=2022.04.1,<2022.10.13|>=2023.04.1,<2023.10.15|>=2024.04.1,<2024.04.7", + "aimeos/ai-controller-frontend": "<2020.10.15|>=2021.04.1,<2021.10.8|>=2022.04.1,<2022.10.8|>=2023.04.1,<2023.10.9", "aimeos/aimeos-core": ">=2022.04.1,<2022.10.17|>=2023.04.1,<2023.10.17|>=2024.04.1,<2024.04.7", "aimeos/aimeos-typo3": "<19.10.12|>=20,<20.10.5", "airesvsg/acf-to-rest-api": "<=3.1", @@ -2593,7 +2665,7 @@ "ec-cube/ec-cube": "<2.4.4|>=2.11,<=2.17.1|>=3,<=3.0.18.0-patch4|>=4,<=4.1.2", "ecodev/newsletter": "<=4", "ectouch/ectouch": "<=2.7.2", - "egroupware/egroupware": "<16.1.20170922", + "egroupware/egroupware": "<23.1.20240624", "elefant/cms": "<2.0.7", "elgg/elgg": "<3.3.24|>=4,<4.0.5", "elijaa/phpmemcacheadmin": "<=1.3", @@ -2966,9 +3038,9 @@ "shopware/core": "<6.5.8.8-dev|>=6.6.0.0-RC1-dev,<6.6.1", "shopware/platform": "<6.5.8.8-dev|>=6.6.0.0-RC1-dev,<6.6.1", "shopware/production": "<=6.3.5.2", - "shopware/shopware": "<6.2.3", + "shopware/shopware": "<=5.7.17", "shopware/storefront": "<=6.4.8.1|>=6.5.8,<6.5.8.7-dev", - "shopxo/shopxo": "<2.2.6", + "shopxo/shopxo": "<=6.1", "showdoc/showdoc": "<2.10.4", "silverstripe-australia/advancedreports": ">=1,<=2", "silverstripe/admin": "<1.13.19|>=2,<2.1.8", @@ -3160,7 +3232,7 @@ "yidashi/yii2cmf": "<=2", "yii2mod/yii2-cms": "<1.9.2", "yiisoft/yii": "<1.1.29", - "yiisoft/yii2": "<2.0.50", + "yiisoft/yii2": "<2.0.49.4-dev", "yiisoft/yii2-authclient": "<2.2.15", "yiisoft/yii2-bootstrap": "<2.0.4", "yiisoft/yii2-dev": "<2.0.43", @@ -3246,7 +3318,7 @@ "type": "tidelift" } ], - "time": "2024-06-26T15:05:17+00:00" + "time": "2024-07-08T20:05:45+00:00" }, { "name": "sanmai/later", @@ -5649,8 +5721,8 @@ "prefer-lowest": false, "platform": { "php": ">=8.1 <8.4.0", - "composer-plugin-api": "^2.1", - "ext-posix": "*" + "ext-posix": "*", + "composer-plugin-api": "^2.1" }, "platform-dev": [], "plugin-api-version": "2.6.0" diff --git a/phpstan.neon.dist b/phpstan.neon.dist index f67529f..9122321 100644 --- a/phpstan.neon.dist +++ b/phpstan.neon.dist @@ -9,6 +9,10 @@ parameters: executor?: array>, umask?: string, users?: array, + env?: array{ + file?: string, + set?: array + }, ini?: array{ set?: array } @@ -21,6 +25,10 @@ parameters: executor?: array>, umask?: string, users?: array, + env?: array{ + file?: string, + set?: array + }, ini?: array{ set?: array } diff --git a/src/Composer/ComposerPlugin.php b/src/Composer/ComposerPlugin.php index 3ee801f..4cb9d82 100644 --- a/src/Composer/ComposerPlugin.php +++ b/src/Composer/ComposerPlugin.php @@ -37,7 +37,10 @@ public function __construct( ?? new RuntimeFileFactory(); } - public static function getSubscribedEvents() + /** + * @return array> + */ + public static function getSubscribedEvents(): array { if (!self::$activated) { return []; @@ -50,9 +53,6 @@ public static function getSubscribedEvents() ]; } - /** - * @throws JsonException - */ public function activate(Composer $composer, IOInterface $io): void { $this->io = $io; @@ -80,12 +80,12 @@ public function updateRuntime(): void ); } - public function deactivate(Composer $composer, IOInterface $io) + public function deactivate(Composer $composer, IOInterface $io): void { self::$activated = false; } - public function uninstall(Composer $composer, IOInterface $io) + public function uninstall(Composer $composer, IOInterface $io): void { $this->runtimeFile->removeRuntimeFile($this->io); $this->composerJson->removeAutoloadFile( diff --git a/src/Executor/EnvSetter.php b/src/Executor/EnvSetter.php new file mode 100644 index 0000000..5fbf65e --- /dev/null +++ b/src/Executor/EnvSetter.php @@ -0,0 +1,134 @@ + + */ + private $alreadySet = []; + + private Dotenv $dotenv; + + public function __construct( + private readonly Platform $platform = new Platform(), + ) { + $this->dotenv = new Dotenv(); + } + + /** + * @param RuntimeOptions $options + * @throws RuntimeException if multiple packages define the same ini option + */ + public function execute(string $projectDir, array $options): void + { + /** + * @var array $settings + */ + $settings = []; + foreach ($options as $package => $packageOptions) { + + $file = $packageOptions['env']['file'] ?? ''; + + $env = array_merge( + $this->loadEnvironmentFile($package, $file), + $packageOptions['env']['set'] ?? [], + ); + + + foreach ($env as $key => $value) { + $value = $this->validate($package, $key, $value); + if ($value !== null) { + $settings[$key] = [ + 'value' => $value, + 'package' => $package, + ]; + } + } + } + + foreach ($settings as $key => $setting) { + $value = $setting['value']; + if ($this->platform->putEnv($key, $value) === false) { + $package = $setting['package']; + throw new RuntimeException( + "[atoolo.runtime.env.set]: " + . "Failed to set $key to $value for, package: $package", + ); + } + } + } + + /** + * @throws RuntimeException + * if the ini option non-scalar or has already been set by this instance + */ + private function validate( + string $package, + string $key, + mixed $value, + ): ?string { + + if ($value === null) { + return null; + } + + if (is_string($value) === false) { + throw new RuntimeException( + "[atoolo.runtime.init.set]: " + . "Value for $key in package $package must be string", + ); + } + + if (isset($this->alreadySet[$key])) { + $existsValue = $this->alreadySet[$key]['value']; + if ($existsValue === $value) { + return null; + } + $package = $this->alreadySet[$key]['package']; + throw new RuntimeException( + "[atoolo.runtime.env.set]: " + . "$key is already set to '$existsValue', package: $package", + ); + } + + $this->alreadySet[$key] = [ + 'value' => $value, + 'package' => $package, + ]; + + return $value; + } + + /** + * @return array + */ + private function loadEnvironmentFile(string $package, string $file): array + { + if (empty($file)) { + return []; + } + + $content = @file_get_contents($file); + if ($content === false) { + throw new RuntimeException( + "[atoolo.runtime.env.file]: " + . "Failed to load file $file for, package: $package", + ); + } + + return $this->dotenv->parse($content); + } +} diff --git a/src/Executor/IniSetter.php b/src/Executor/IniSetter.php index 511a6d8..398d187 100644 --- a/src/Executor/IniSetter.php +++ b/src/Executor/IniSetter.php @@ -91,13 +91,6 @@ private function validate( ); } - if (ini_set($key, $value) === false) { - throw new RuntimeException( - "[atoolo.runtime.init.set]: " - . "Failed to set $key to $value for, package: $package", - ); - } - $this->alreadySet[$key] = [ 'value' => $value, 'package' => $package, diff --git a/src/Executor/Platform.php b/src/Executor/Platform.php index c20864a..362585f 100644 --- a/src/Executor/Platform.php +++ b/src/Executor/Platform.php @@ -4,11 +4,20 @@ namespace Atoolo\Runtime\Executor; +use Symfony\Component\Dotenv\Dotenv; + /** * @codeCoverageIgnore */ class Platform { + private Dotenv $dotenv; + + public function __construct() + { + $this->dotenv = new Dotenv(); + } + public function umask(int $umask): int { return umask($umask); @@ -20,4 +29,12 @@ public function setIni( ): false|string { return ini_set($name, $value); } + + public function putEnv( + string $name, + string $value, + ): bool { + $this->dotenv->populate([$name => $value]); + return true; + } } diff --git a/test/Executor/EnvSetterTest.php b/test/Executor/EnvSetterTest.php new file mode 100644 index 0000000..c38dc85 --- /dev/null +++ b/test/Executor/EnvSetterTest.php @@ -0,0 +1,226 @@ +createMock(Platform::class); + $envSetter = new EnvSetter($platform); + + $platform->expects($this->once()) + ->method('putEnv') + ->with('a', 'B') + ->willReturn(true); + + $envSetter->execute('', [ + 'package1' => [ + 'env' => [ + 'set' => [ + 'a' => 'B', + ], + ], + ], + ]); + } + + public function testSetEnvTwiceWithSameValueAgain(): void + { + $platform = $this->createMock(Platform::class); + $envSetter = new EnvSetter($platform); + + $platform->expects($this->once()) + ->method('putEnv') + ->with('a', 'B') + ->willReturn(true); + + $envSetter->execute('', [ + 'package1' => [ + 'env' => [ + 'set' => [ + 'a' => 'B', + ], + ], + ], + 'package2' => [ + 'env' => [ + 'set' => [ + 'a' => 'B', + ], + ], + ], + ]); + } + + public function testSetIniTwiceWithDifferentValues(): void + { + $envSetter = new EnvSetter(); + + $this->expectException(RuntimeException::class); + $envSetter->execute('', [ + 'package1' => [ + 'env' => [ + 'set' => [ + 'a' => 'B', + ], + ], + ], + 'package2' => [ + 'env' => [ + 'set' => [ + 'a' => 'C', + ], + ], + ], + ]); + } + + public function testSetIniWithNonString(): void + { + $envSetter = new EnvSetter(); + $this->expectException(RuntimeException::class); + $envSetter->execute('', [ + 'package1' => [ + 'env' => [ + 'set' => [ + 'a' => 123, + ], + ], + ], + ]); + } + + public function testSetIniWithNull(): void + { + $platform = $this->createMock(Platform::class); + $envSetter = new EnvSetter($platform); + + $platform->expects($this->never()) + ->method('putEnv'); + + $envSetter->execute('', [ + 'package1' => [ + 'env' => [ + 'set' => [ + 'a' => null, + ], + ], + ], + ]); + } + + public function testPutEnvFailed(): void + { + $platform = $this->createMock(Platform::class); + $envSetter = new EnvSetter($platform); + + $platform->method('putEnv') + ->willReturn(false); + + $this->expectException(RuntimeException::class); + $envSetter->execute('', [ + 'package1' => [ + 'env' => [ + 'set' => [ + 'a' => 'B', + ], + ], + ], + ]); + } + + public function testWithInvalidEnvironmentFile(): void + { + $platform = $this->createMock(Platform::class); + $envSetter = new EnvSetter($platform); + + $this->expectException(RuntimeException::class); + $envSetter->execute('', [ + 'package1' => [ + 'env' => [ + 'file' => 'invalid.file', + ], + ], + ]); + } + + public function testEnvironmentFileWithoutEnvironments(): void + { + $platform = $this->createMock(Platform::class); + $envSetter = new EnvSetter($platform); + + $platform->expects($this->never()) + ->method('putEnv'); + + $envSetter->execute('', [ + 'package' => [ + 'env' => [ + 'file' => $this->resourceDir + . '/no-environments', + ], + ], + ]); + } + + public function testWithoutEnvironmentFile(): void + { + $platform = $this->createMock(Platform::class); + $envSetter = new EnvSetter($platform); + + $expected = [ + ['FOO', 'foo'], + ['BAZ', 'qux'], + ['test', 'Test'], + ]; + $matcher = $this->exactly(count($expected)); + + $platform->expects($matcher) + ->method('putEnv') + ->willReturnCallback( + function ( + string $key, + string $value, + ) use ( + $matcher, + $expected, + ) { + $case = $matcher->numberOfInvocations(); + $this->assertEquals( + $expected[$case - 1], + [$key, $value], + 'unexpected env', + ); + return true; + }, + ); + + $envSetter->execute('', [ + 'package' => [ + 'env' => [ + 'file' => $this->resourceDir . '/environments', + 'set' => [ + 'FOO' => 'foo', + 'test' => 'Test', + ], + ], + ], + ]); + } +} diff --git a/test/resources/Executor/EnvSetter/environments b/test/resources/Executor/EnvSetter/environments new file mode 100644 index 0000000..e48325c --- /dev/null +++ b/test/resources/Executor/EnvSetter/environments @@ -0,0 +1,4 @@ +# comment +FOO=bar +# comment +BAZ=qux diff --git a/test/resources/Executor/EnvSetter/no-environments b/test/resources/Executor/EnvSetter/no-environments new file mode 100644 index 0000000..6224bba --- /dev/null +++ b/test/resources/Executor/EnvSetter/no-environments @@ -0,0 +1,4 @@ +# comment + + +# comment