Browse Source

Alias dev-master/trunk/default to 9999999-dev instead of normalizing the version to that, fixes #8323

Jordi Boggiano 5 years ago
parent
commit
a7a975ec1c

+ 1 - 1
composer.json

@@ -24,7 +24,7 @@
     "require": {
         "php": "^5.3.2 || ^7.0",
         "composer/ca-bundle": "^1.0",
-        "composer/semver": "^1.0",
+        "composer/semver": "^2.0@dev",
         "composer/spdx-licenses": "^1.2",
         "composer/xdebug-handler": "^1.1",
         "justinrainbow/json-schema": "^3.0 || ^4.0 || ^5.0",

+ 94 - 15
composer.lock

@@ -4,7 +4,7 @@
         "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
         "This file is @generated automatically"
     ],
-    "content-hash": "7bfefa6f7d4d8c18836028dae680bd4f",
+    "content-hash": "a0a9399315ac0b612d4296b8df745112",
     "packages": [
         {
             "name": "composer/ca-bundle",
@@ -60,20 +60,25 @@
                 "ssl",
                 "tls"
             ],
+            "support": {
+                "irc": "irc://irc.freenode.org/composer",
+                "issues": "https://github.com/composer/ca-bundle/issues",
+                "source": "https://github.com/composer/ca-bundle/tree/master"
+            },
             "time": "2020-01-13T10:02:55+00:00"
         },
         {
             "name": "composer/semver",
-            "version": "1.5.1",
+            "version": "2.0.x-dev",
             "source": {
                 "type": "git",
                 "url": "https://github.com/composer/semver.git",
-                "reference": "c6bea70230ef4dd483e6bbcab6005f682ed3a8de"
+                "reference": "4df5ff3249f01018504939d66040d8d2b783d820"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/composer/semver/zipball/c6bea70230ef4dd483e6bbcab6005f682ed3a8de",
-                "reference": "c6bea70230ef4dd483e6bbcab6005f682ed3a8de",
+                "url": "https://api.github.com/repos/composer/semver/zipball/4df5ff3249f01018504939d66040d8d2b783d820",
+                "reference": "4df5ff3249f01018504939d66040d8d2b783d820",
                 "shasum": ""
             },
             "require": {
@@ -85,7 +90,7 @@
             "type": "library",
             "extra": {
                 "branch-alias": {
-                    "dev-master": "1.x-dev"
+                    "dev-master": "2.x-dev"
                 }
             },
             "autoload": {
@@ -121,7 +126,22 @@
                 "validation",
                 "versioning"
             ],
-            "time": "2020-01-13T12:06:48+00:00"
+            "support": {
+                "irc": "irc://irc.freenode.org/composer",
+                "issues": "https://github.com/composer/semver/issues",
+                "source": "https://github.com/composer/semver/tree/2.0"
+            },
+            "funding": [
+                {
+                    "url": "https://packagist.com",
+                    "type": "custom"
+                },
+                {
+                    "url": "https://tidelift.com/funding/github/packagist/composer/composer",
+                    "type": "tidelift"
+                }
+            ],
+            "time": "2020-03-11T13:41:23+00:00"
         },
         {
             "name": "composer/spdx-licenses",
@@ -181,6 +201,11 @@
                 "spdx",
                 "validator"
             ],
+            "support": {
+                "irc": "irc://irc.freenode.org/composer",
+                "issues": "https://github.com/composer/spdx-licenses/issues",
+                "source": "https://github.com/composer/spdx-licenses/tree/1.5.3"
+            },
             "time": "2020-02-14T07:44:31+00:00"
         },
         {
@@ -225,6 +250,11 @@
                 "Xdebug",
                 "performance"
             ],
+            "support": {
+                "irc": "irc://irc.freenode.org/composer",
+                "issues": "https://github.com/composer/xdebug-handler/issues",
+                "source": "https://github.com/composer/xdebug-handler/tree/master"
+            },
             "funding": [
                 {
                     "url": "https://packagist.com",
@@ -388,12 +418,6 @@
             "license": [
                 "MIT"
             ],
-            "authors": [
-                {
-                    "name": "Jan Sorgalla",
-                    "email": "jsorgalla@gmail.com"
-                }
-            ],
             "description": "A lightweight implementation of CommonJS Promises/A for PHP",
             "time": "2016-03-07T13:46:50+00:00"
         },
@@ -492,6 +516,10 @@
             "keywords": [
                 "phar"
             ],
+            "support": {
+                "issues": "https://github.com/Seldaek/phar-utils/issues",
+                "source": "https://github.com/Seldaek/phar-utils/tree/1.1.0"
+            },
             "time": "2020-02-14T15:25:33+00:00"
         },
         {
@@ -761,6 +789,16 @@
             "license": [
                 "MIT"
             ],
+            "authors": [
+                {
+                    "name": "Gert de Pagter",
+                    "email": "BackEndTea@gmail.com"
+                },
+                {
+                    "name": "Symfony Community",
+                    "homepage": "https://symfony.com/contributors"
+                }
+            ],
             "description": "Symfony polyfill for ctype functions",
             "homepage": "https://symfony.com",
             "keywords": [
@@ -769,6 +807,9 @@
                 "polyfill",
                 "portable"
             ],
+            "support": {
+                "source": "https://github.com/symfony/polyfill-ctype/tree/master"
+            },
             "time": "2020-01-13T11:15:53+00:00"
         },
         {
@@ -828,6 +869,9 @@
                 "portable",
                 "shim"
             ],
+            "support": {
+                "source": "https://github.com/symfony/polyfill-mbstring/tree/master"
+            },
             "time": "2020-01-13T11:15:53+00:00"
         },
         {
@@ -983,6 +1027,16 @@
             "license": [
                 "MIT"
             ],
+            "authors": [
+                {
+                    "name": "Mike van Riel",
+                    "email": "mike.vanriel@naenius.com"
+                }
+            ],
+            "support": {
+                "issues": "https://github.com/phpDocumentor/ReflectionDocBlock/issues",
+                "source": "https://github.com/phpDocumentor/ReflectionDocBlock/tree/release/2.x"
+            },
             "time": "2016-01-25T08:17:30+00:00"
         },
         {
@@ -1046,6 +1100,10 @@
                 "spy",
                 "stub"
             ],
+            "support": {
+                "issues": "https://github.com/phpspec/prophecy/issues",
+                "source": "https://github.com/phpspec/prophecy/tree/v1.10.3"
+            },
             "time": "2020-03-05T15:02:03+00:00"
         },
         {
@@ -1110,6 +1168,10 @@
                 "compare",
                 "equality"
             ],
+            "support": {
+                "issues": "https://github.com/sebastianbergmann/comparator/issues",
+                "source": "https://github.com/sebastianbergmann/comparator/tree/1.2"
+            },
             "time": "2017-01-29T09:50:25+00:00"
         },
         {
@@ -1162,6 +1224,10 @@
             "keywords": [
                 "diff"
             ],
+            "support": {
+                "issues": "https://github.com/sebastianbergmann/diff/issues",
+                "source": "https://github.com/sebastianbergmann/diff/tree/1.4"
+            },
             "time": "2017-05-22T07:24:03+00:00"
         },
         {
@@ -1229,6 +1295,10 @@
                 "export",
                 "exporter"
             ],
+            "support": {
+                "issues": "https://github.com/sebastianbergmann/exporter/issues",
+                "source": "https://github.com/sebastianbergmann/exporter/tree/master"
+            },
             "time": "2016-11-19T08:54:04+00:00"
         },
         {
@@ -1282,6 +1352,10 @@
             ],
             "description": "Provides functionality to recursively process PHP variables",
             "homepage": "http://www.github.com/sebastianbergmann/recursion-context",
+            "support": {
+                "issues": "https://github.com/sebastianbergmann/recursion-context/issues",
+                "source": "https://github.com/sebastianbergmann/recursion-context/tree/master"
+            },
             "time": "2016-11-19T07:33:16+00:00"
         },
         {
@@ -1347,6 +1421,9 @@
             ],
             "description": "Symfony PHPUnit Bridge",
             "homepage": "https://symfony.com",
+            "support": {
+                "source": "https://github.com/symfony/phpunit-bridge/tree/v3.4.38"
+            },
             "funding": [
                 {
                     "url": "https://symfony.com/sponsor",
@@ -1366,7 +1443,9 @@
     ],
     "aliases": [],
     "minimum-stability": "stable",
-    "stability-flags": [],
+    "stability-flags": {
+        "composer/semver": 20
+    },
     "prefer-stable": false,
     "prefer-lowest": false,
     "platform": {
@@ -1376,5 +1455,5 @@
     "platform-overrides": {
         "php": "5.3.9"
     },
-    "plugin-api-version": "1.1.0"
+    "plugin-api-version": "2.0.0"
 }

+ 10 - 7
src/Composer/Command/ShowCommand.php

@@ -160,6 +160,7 @@ EOT
             $installedRepo = new InstalledRepository(array($platformRepo));
             if ($composer) {
                 $repos = new CompositeRepository($composer->getRepositoryManager()->getRepositories());
+                $installedRepo->addRepository($composer->getRepositoryManager()->getLocalRepository());
             } else {
                 $defaultRepos = RepositoryFactory::defaultRepos($io);
                 $repos = new CompositeRepository($defaultRepos);
@@ -675,15 +676,17 @@ EOT
      */
     protected function printVersions(CompletePackageInterface $package, array $versions, InstalledRepository $installedRepo)
     {
-        uasort($versions, 'version_compare');
-        $versions = array_keys(array_reverse($versions));
+        $versions = array_keys($versions);
+        $versions = Semver::rsort($versions);
 
         // highlight installed version
-        if ($installedRepo->hasPackage($package)) {
-            $installedVersion = $package->getPrettyVersion();
-            $key = array_search($installedVersion, $versions);
-            if (false !== $key) {
-                $versions[$key] = '<info>* ' . $installedVersion . '</info>';
+        if ($installedPackages = $installedRepo->findPackages($package->getName())) {
+            foreach ($installedPackages as $installedPackage) {
+                $installedVersion = $installedPackage->getPrettyVersion();
+                $key = array_search($installedVersion, $versions);
+                if (false !== $key) {
+                    $versions[$key] = '<info>* ' . $installedVersion . '</info>';
+                }
             }
         }
 

+ 4 - 0
src/Composer/DependencyResolver/Problem.php

@@ -273,6 +273,10 @@ class Problem
             $prepared[$package->getName()]['versions'][$package->getVersion()] = $package->getPrettyVersion();
         }
         foreach ($prepared as $name => $package) {
+            // remove the implicit dev-master alias to avoid cruft in the display
+            if (isset($package['versions']['9999999-dev']) && isset($package['versions']['dev-master'])) {
+                unset($package['versions']['9999999-dev']);
+            }
             $prepared[$name] = $package['name'].'['.implode(', ', $package['versions']).']';
         }
 

+ 33 - 25
src/Composer/Package/Loader/ArrayLoader.php

@@ -89,6 +89,11 @@ class ArrayLoader implements LoaderInterface
         // handle already normalized versions
         if (isset($config['version_normalized'])) {
             $version = $config['version_normalized'];
+
+            // handling of existing repos which need to remain composer v1 compatible, in case the version_normalized contained 9999999-dev, we renormalize it
+            if ($version === '9999999-dev') {
+                $version = $this->versionParser->normalize($config['version']);
+            }
         } else {
             $version = $this->versionParser->normalize($config['version']);
         }
@@ -320,39 +325,42 @@ class ArrayLoader implements LoaderInterface
      */
     public function getBranchAlias(array $config)
     {
-        if (('dev-' !== substr($config['version'], 0, 4) && '-dev' !== substr($config['version'], -4))
-            || !isset($config['extra']['branch-alias'])
-            || !is_array($config['extra']['branch-alias'])
-        ) {
+        if ('dev-' !== substr($config['version'], 0, 4) && '-dev' !== substr($config['version'], -4)) {
             return;
         }
 
-        foreach ($config['extra']['branch-alias'] as $sourceBranch => $targetBranch) {
-            // ensure it is an alias to a -dev package
-            if ('-dev' !== substr($targetBranch, -4)) {
-                continue;
-            }
+        if (isset($config['extra']['branch-alias']) && is_array($config['extra']['branch-alias'])) {
+            foreach ($config['extra']['branch-alias'] as $sourceBranch => $targetBranch) {
+                // ensure it is an alias to a -dev package
+                if ('-dev' !== substr($targetBranch, -4)) {
+                    continue;
+                }
 
-            // normalize without -dev and ensure it's a numeric branch that is parseable
-            $validatedTargetBranch = $this->versionParser->normalizeBranch(substr($targetBranch, 0, -4));
-            if ('-dev' !== substr($validatedTargetBranch, -4)) {
-                continue;
-            }
+                // normalize without -dev and ensure it's a numeric branch that is parseable
+                $validatedTargetBranch = $this->versionParser->normalizeBranch(substr($targetBranch, 0, -4));
+                if ('-dev' !== substr($validatedTargetBranch, -4)) {
+                    continue;
+                }
 
-            // ensure that it is the current branch aliasing itself
-            if (strtolower($config['version']) !== strtolower($sourceBranch)) {
-                continue;
-            }
+                // ensure that it is the current branch aliasing itself
+                if (strtolower($config['version']) !== strtolower($sourceBranch)) {
+                    continue;
+                }
 
-            // If using numeric aliases ensure the alias is a valid subversion
-            if (($sourcePrefix = $this->versionParser->parseNumericAliasPrefix($sourceBranch))
-                && ($targetPrefix = $this->versionParser->parseNumericAliasPrefix($targetBranch))
-                && (stripos($targetPrefix, $sourcePrefix) !== 0)
-            ) {
-                continue;
+                // If using numeric aliases ensure the alias is a valid subversion
+                if (($sourcePrefix = $this->versionParser->parseNumericAliasPrefix($sourceBranch))
+                    && ($targetPrefix = $this->versionParser->parseNumericAliasPrefix($targetBranch))
+                    && (stripos($targetPrefix, $sourcePrefix) !== 0)
+                ) {
+                    continue;
+                }
+
+                return $validatedTargetBranch;
             }
+        }
 
-            return $validatedTargetBranch;
+        if (in_array($config['version'], array('dev-master', 'dev-default', 'dev-trunk'), true)) {
+            return '9999999-dev';
         }
     }
 }

+ 1 - 1
src/Composer/Package/Loader/ValidatingArrayLoader.php

@@ -219,7 +219,7 @@ class ValidatingArrayLoader implements LoaderInterface
             }
         }
 
-        $unboundConstraint = new Constraint('=', $this->versionParser->normalize('dev-master'));
+        $unboundConstraint = new Constraint('=', '10000000-dev');
         $stableConstraint = new Constraint('=', '1.0.0');
 
         foreach (array_keys(BasePackage::$supportedLinkTypes) as $linkType) {

+ 3 - 0
src/Composer/Package/Version/VersionParser.php

@@ -70,6 +70,9 @@ class VersionParser extends SemverVersionParser
      */
     public static function isUpgrade($normalizedFrom, $normalizedTo)
     {
+        $normalizedFrom = str_replace(array('dev-master', 'dev-trunk', 'dev-default'), '9999999-dev', $normalizedFrom);
+        $normalizedTo = str_replace(array('dev-master', 'dev-trunk', 'dev-default'), '9999999-dev', $normalizedTo);
+
         if (substr($normalizedFrom, 0, 4) === 'dev-' || substr($normalizedTo, 0, 4) === 'dev-') {
             return true;
         }

+ 1 - 0
tests/Composer/Test/Fixtures/installer/alias-with-reference.test

@@ -28,4 +28,5 @@ install
 --EXPECT--
 Installing a/aliased (dev-master abcd)
 Marking a/aliased (1.0.0) as installed, alias of a/aliased (dev-master abcd)
+Marking a/aliased (9999999-dev abcd) as installed, alias of a/aliased (dev-master abcd)
 Installing b/requirer (1.0.0)

+ 9 - 12
tests/Composer/Test/Fixtures/installer/aliased-priority-conflicting.test

@@ -14,12 +14,12 @@ Aliases take precedence over default package even if default is selected
                     "name": "a/req", "version": "dev-master",
                     "extra": { "branch-alias": { "dev-master": "1.0.x-dev" } },
                     "source": { "reference": "forked", "type": "git", "url": "" }
-                }
-            ]
-        },
-        {
-            "type": "package",
-            "package": [
+                },
+                {
+                    "name": "a/req", "version": "dev-master",
+                    "extra": { "branch-alias": { "dev-master": "1.0.x-dev" } },
+                    "source": { "reference": "master", "type": "git", "url": "" }
+                },
                 {
                     "name": "a/a", "version": "dev-master",
                     "require": { "a/req": "dev-master" }
@@ -27,11 +27,6 @@ Aliases take precedence over default package even if default is selected
                 {
                     "name": "a/b", "version": "dev-master",
                     "require": { "a/req": "dev-master" }
-                },
-                {
-                    "name": "a/req", "version": "dev-master",
-                    "extra": { "branch-alias": { "dev-master": "1.0.x-dev" } },
-                    "source": { "reference": "master", "type": "git", "url": "" }
                 }
             ]
         }
@@ -66,7 +61,7 @@ Aliases take precedence over default package even if default is selected
     "aliases": [
         {
             "alias": "dev-master",
-            "alias_normalized": "9999999-dev",
+            "alias_normalized": "dev-master",
             "version": "dev-feature-foo",
             "package": "a/req"
         }
@@ -88,4 +83,6 @@ install
 Installing a/req (dev-feature-foo feat.f)
 Marking a/req (dev-master feat.f) as installed, alias of a/req (dev-feature-foo feat.f)
 Installing a/a (dev-master)
+Marking a/a (9999999-dev) as installed, alias of a/a (dev-master)
 Installing a/b (dev-master)
+Marking a/b (9999999-dev) as installed, alias of a/b (dev-master)

+ 1 - 0
tests/Composer/Test/Fixtures/installer/install-aliased-alias.test

@@ -34,3 +34,4 @@ Installing b/b (dev-foo)
 Marking b/b (dev-master) as installed, alias of b/b (dev-foo)
 Marking b/b (1.0.x-dev) as installed, alias of b/b (dev-foo)
 Installing a/a (dev-master)
+Marking a/a (9999999-dev) as installed, alias of a/a (dev-master)

+ 1 - 0
tests/Composer/Test/Fixtures/installer/install-dev-using-dist.test

@@ -53,3 +53,4 @@ install --prefer-dist
 }
 --EXPECT--
 Installing a/a (dev-master)
+Marking a/a (9999999-dev) as installed, alias of a/a (dev-master)

+ 1 - 0
tests/Composer/Test/Fixtures/installer/install-reference.test

@@ -21,3 +21,4 @@ Installs a dev package forcing it's reference
 install
 --EXPECT--
 Installing a/a (dev-master def000)
+Marking a/a (9999999-dev def000) as installed, alias of a/a (dev-master def000)

+ 31 - 0
tests/Composer/Test/Fixtures/installer/unbounded-conflict-does-not-match-dev-master.test

@@ -0,0 +1,31 @@
+--TEST--
+Test that a conflict against >=5 does not include dev-master or other dev-x
+--COMPOSER--
+{
+    "repositories": [
+        {
+            "type": "package",
+            "package": [
+                { "name": "conflicter/pkg", "version": "1.0.0", "conflict": { "victim/pkg": ">=5", "victim/pkg2": ">=5" } },
+                { "name": "victim/pkg", "version": "dev-master" },
+                { "name": "victim/pkg2", "version": "dev-foo" }
+            ]
+        }
+    ],
+    "require": {
+        "conflicter/pkg": "1.0.0",
+        "victim/pkg": "*",
+        "victim/pkg2": "*"
+    },
+    "minimum-stability": "dev"
+}
+
+
+--RUN--
+update
+
+--EXPECT--
+Installing conflicter/pkg (1.0.0)
+Installing victim/pkg (dev-master)
+Marking victim/pkg (9999999-dev) as installed, alias of victim/pkg (dev-master)
+Installing victim/pkg2 (dev-foo)

+ 1 - 0
tests/Composer/Test/Fixtures/installer/update-changes-url.test

@@ -217,4 +217,5 @@ update a/a b/b d/d g/g
 Upgrading a/a (dev-master 1111111 => dev-master 2222222)
 Upgrading b/b (2.0.3 1111111 => 2.0.3 2222222)
 Installing e/e (dev-master 1111111)
+Marking e/e (9999999-dev 1111111) as installed, alias of e/e (dev-master 1111111)
 Upgrading g/g (dev-master 0000000 => dev-master 1111111)

+ 1 - 0
tests/Composer/Test/Fixtures/installer/update-dev-to-new-ref-picks-up-changes.test

@@ -38,4 +38,5 @@ Updating a dev package to its latest ref should pick up new dependencies
 update
 --EXPECT--
 Installing a/dependency (dev-master ref)
+Marking a/dependency (9999999-dev ref) as installed, alias of a/dependency (dev-master ref)
 Upgrading a/devpackage (dev-master oldref => dev-master newref)

+ 1 - 0
tests/Composer/Test/Fixtures/installer/update-downgrades-unstable-packages.test

@@ -47,3 +47,4 @@ Downgrading from unstable to more stable package should work even if already ins
 update
 --EXPECT--
 Downgrading a/a (dev-master abcd => 1.0.0)
+Marking a/a (9999999-dev abcd) as uninstalled, alias of a/a (dev-master abcd)

+ 1 - 0
tests/Composer/Test/Fixtures/installer/update-to-empty-from-locked.test

@@ -45,3 +45,4 @@ update
 }
 --EXPECT--
 Uninstalling a/a (dev-master 1234)
+Marking a/a (9999999-dev 1234) as uninstalled, alias of a/a (dev-master 1234)

+ 1 - 0
tests/Composer/Test/Fixtures/installer/updating-dev-from-lock-removes-old-deps.test

@@ -43,3 +43,4 @@ install
 --EXPECT--
 Uninstalling a/dependency (dev-master ref)
 Upgrading a/devpackage (dev-master oldref => dev-master newref)
+Marking a/dependency (9999999-dev ref) as uninstalled, alias of a/dependency (dev-master ref)

+ 2 - 0
tests/Composer/Test/Package/Loader/RootPackageLoaderTest.php

@@ -160,6 +160,8 @@ class RootPackageLoaderTest extends TestCase
         $loader = new RootPackageLoader($manager, $config, null, new VersionGuesser($config, $executor, new VersionParser()));
         $package = $loader->load(array('require' => array('foo/bar' => 'self.version')));
 
+        $this->assertEquals("9999999-dev", $package->getPrettyVersion());
+        $package = $package->getAliasOf();
         $this->assertEquals("dev-master", $package->getPrettyVersion());
     }
 

+ 0 - 2
tests/Composer/Test/Package/Loader/ValidatingArrayLoaderTest.php

@@ -356,7 +356,6 @@ class ValidatingArrayLoaderTest extends TestCase
                     'require' => array(
                         'foo/baz' => '*',
                         'bar/baz' => '>=1.0',
-                        'bar/foo' => 'dev-master',
                         'bar/hacked' => '@stable',
                         'bar/woo' => '1.0.0',
                     ),
@@ -364,7 +363,6 @@ class ValidatingArrayLoaderTest extends TestCase
                 array(
                     'require.foo/baz : unbound version constraints (*) should be avoided',
                     'require.bar/baz : unbound version constraints (>=1.0) should be avoided',
-                    'require.bar/foo : unbound version constraints (dev-master) should be avoided',
                     'require.bar/hacked : unbound version constraints (@stable) should be avoided',
                     'require.bar/woo : exact version constraints (1.0.0) should be avoided if the package follows semantic versioning',
                 ),

+ 2 - 2
tests/Composer/Test/Package/Version/VersionGuesserTest.php

@@ -89,7 +89,7 @@ class VersionGuesserTest extends TestCase
         $guesser = new VersionGuesser($config, $executor, new VersionParser());
         $versionArray = $guesser->guessVersion(array(), 'dummy/path');
 
-        $this->assertEquals("9999999-dev", $versionArray['version']);
+        $this->assertEquals("dev-".$branch, $versionArray['version']);
         $this->assertEquals("dev-".$branch, $versionArray['pretty_version']);
         $this->assertEmpty($versionArray['commit']);
     }
@@ -124,7 +124,7 @@ class VersionGuesserTest extends TestCase
         $guesser = new VersionGuesser($config, $executor, new VersionParser());
         $versionArray = $guesser->guessVersion(array(), 'dummy/path');
 
-        $this->assertEquals("9999999-dev", $versionArray['version']);
+        $this->assertEquals("dev-master", $versionArray['version']);
         $this->assertEquals("dev-master", $versionArray['pretty_version']);
         $this->assertArrayNotHasKey('feature_version', $versionArray);
         $this->assertArrayNotHasKey('feature_pretty_version', $versionArray);