diff options
| -rw-r--r-- | composer.lock | 278 | ||||
| -rw-r--r-- | run.php | 4 | ||||
| -rw-r--r-- | src/Commands/DownloadVendorCommand.php | 135 | ||||
| -rw-r--r-- | src/Commands/InitCommand.php | 36 | ||||
| -rw-r--r-- | src/Commands/UpdateCommand.php | 31 | ||||
| -rw-r--r-- | src/Services/VendorDownloader.php | 151 | ||||
| -rw-r--r-- | src/app.php | 2 | ||||
| -rw-r--r-- | tests/Commands/BackupCommandTest.php | 2 | ||||
| -rw-r--r-- | tests/Commands/UpdateCommandTest.php | 34 |
9 files changed, 358 insertions, 315 deletions
diff --git a/composer.lock b/composer.lock index 42de317..7069c2e 100644 --- a/composer.lock +++ b/composer.lock @@ -70,16 +70,16 @@ }, { "name": "phpoption/phpoption", - "version": "1.9.3", + "version": "1.9.4", "source": { "type": "git", "url": "https://github.com/schmittjoh/php-option.git", - "reference": "e3fac8b24f56113f7cb96af14958c0dd16330f54" + "reference": "638a154f8d4ee6a5cfa96d6a34dfbe0cffa9566d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/schmittjoh/php-option/zipball/e3fac8b24f56113f7cb96af14958c0dd16330f54", - "reference": "e3fac8b24f56113f7cb96af14958c0dd16330f54", + "url": "https://api.github.com/repos/schmittjoh/php-option/zipball/638a154f8d4ee6a5cfa96d6a34dfbe0cffa9566d", + "reference": "638a154f8d4ee6a5cfa96d6a34dfbe0cffa9566d", "shasum": "" }, "require": { @@ -87,7 +87,7 @@ }, "require-dev": { "bamarni/composer-bin-plugin": "^1.8.2", - "phpunit/phpunit": "^8.5.39 || ^9.6.20 || ^10.5.28" + "phpunit/phpunit": "^8.5.44 || ^9.6.25 || ^10.5.53 || ^11.5.34" }, "type": "library", "extra": { @@ -129,7 +129,7 @@ ], "support": { "issues": "https://github.com/schmittjoh/php-option/issues", - "source": "https://github.com/schmittjoh/php-option/tree/1.9.3" + "source": "https://github.com/schmittjoh/php-option/tree/1.9.4" }, "funding": [ { @@ -141,7 +141,7 @@ "type": "tidelift" } ], - "time": "2024-07-20T21:41:07+00:00" + "time": "2025-08-21T11:53:16+00:00" }, { "name": "psr/container", @@ -198,16 +198,16 @@ }, { "name": "symfony/console", - "version": "v6.4.21", + "version": "v6.4.30", "source": { "type": "git", "url": "https://github.com/symfony/console.git", - "reference": "a3011c7b7adb58d89f6c0d822abb641d7a5f9719" + "reference": "1b2813049506b39eb3d7e64aff033fd5ca26c97e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/console/zipball/a3011c7b7adb58d89f6c0d822abb641d7a5f9719", - "reference": "a3011c7b7adb58d89f6c0d822abb641d7a5f9719", + "url": "https://api.github.com/repos/symfony/console/zipball/1b2813049506b39eb3d7e64aff033fd5ca26c97e", + "reference": "1b2813049506b39eb3d7e64aff033fd5ca26c97e", "shasum": "" }, "require": { @@ -272,7 +272,7 @@ "terminal" ], "support": { - "source": "https://github.com/symfony/console/tree/v6.4.21" + "source": "https://github.com/symfony/console/tree/v6.4.30" }, "funding": [ { @@ -284,24 +284,28 @@ "type": "github" }, { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2025-04-07T15:42:41+00:00" + "time": "2025-12-05T13:47:41+00:00" }, { "name": "symfony/deprecation-contracts", - "version": "v3.5.1", + "version": "v3.6.0", "source": { "type": "git", "url": "https://github.com/symfony/deprecation-contracts.git", - "reference": "74c71c939a79f7d5bf3c1ce9f5ea37ba0114c6f6" + "reference": "63afe740e99a13ba87ec199bb07bbdee937a5b62" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/74c71c939a79f7d5bf3c1ce9f5ea37ba0114c6f6", - "reference": "74c71c939a79f7d5bf3c1ce9f5ea37ba0114c6f6", + "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/63afe740e99a13ba87ec199bb07bbdee937a5b62", + "reference": "63afe740e99a13ba87ec199bb07bbdee937a5b62", "shasum": "" }, "require": { @@ -314,7 +318,7 @@ "name": "symfony/contracts" }, "branch-alias": { - "dev-main": "3.5-dev" + "dev-main": "3.6-dev" } }, "autoload": { @@ -339,7 +343,7 @@ "description": "A generic function and convention to trigger deprecation notices", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/deprecation-contracts/tree/v3.5.1" + "source": "https://github.com/symfony/deprecation-contracts/tree/v3.6.0" }, "funding": [ { @@ -355,11 +359,11 @@ "type": "tidelift" } ], - "time": "2024-09-25T14:20:29+00:00" + "time": "2024-09-25T14:21:43+00:00" }, { "name": "symfony/polyfill-ctype", - "version": "v1.32.0", + "version": "v1.33.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-ctype.git", @@ -418,7 +422,7 @@ "portable" ], "support": { - "source": "https://github.com/symfony/polyfill-ctype/tree/v1.32.0" + "source": "https://github.com/symfony/polyfill-ctype/tree/v1.33.0" }, "funding": [ { @@ -430,6 +434,10 @@ "type": "github" }, { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } @@ -438,16 +446,16 @@ }, { "name": "symfony/polyfill-intl-grapheme", - "version": "v1.32.0", + "version": "v1.33.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-intl-grapheme.git", - "reference": "b9123926e3b7bc2f98c02ad54f6a4b02b91a8abe" + "reference": "380872130d3a5dd3ace2f4010d95125fde5d5c70" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/b9123926e3b7bc2f98c02ad54f6a4b02b91a8abe", - "reference": "b9123926e3b7bc2f98c02ad54f6a4b02b91a8abe", + "url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/380872130d3a5dd3ace2f4010d95125fde5d5c70", + "reference": "380872130d3a5dd3ace2f4010d95125fde5d5c70", "shasum": "" }, "require": { @@ -496,7 +504,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-intl-grapheme/tree/v1.32.0" + "source": "https://github.com/symfony/polyfill-intl-grapheme/tree/v1.33.0" }, "funding": [ { @@ -508,15 +516,19 @@ "type": "github" }, { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2024-09-09T11:45:10+00:00" + "time": "2025-06-27T09:58:17+00:00" }, { "name": "symfony/polyfill-intl-normalizer", - "version": "v1.32.0", + "version": "v1.33.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-intl-normalizer.git", @@ -577,7 +589,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.32.0" + "source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.33.0" }, "funding": [ { @@ -589,6 +601,10 @@ "type": "github" }, { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } @@ -597,7 +613,7 @@ }, { "name": "symfony/polyfill-mbstring", - "version": "v1.32.0", + "version": "v1.33.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-mbstring.git", @@ -658,7 +674,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.32.0" + "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.33.0" }, "funding": [ { @@ -670,6 +686,10 @@ "type": "github" }, { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } @@ -678,7 +698,7 @@ }, { "name": "symfony/polyfill-php80", - "version": "v1.32.0", + "version": "v1.33.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-php80.git", @@ -738,7 +758,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-php80/tree/v1.32.0" + "source": "https://github.com/symfony/polyfill-php80/tree/v1.33.0" }, "funding": [ { @@ -750,6 +770,10 @@ "type": "github" }, { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } @@ -758,16 +782,16 @@ }, { "name": "symfony/process", - "version": "v6.4.20", + "version": "v6.4.26", "source": { "type": "git", "url": "https://github.com/symfony/process.git", - "reference": "e2a61c16af36c9a07e5c9906498b73e091949a20" + "reference": "48bad913268c8cafabbf7034b39c8bb24fbc5ab8" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/process/zipball/e2a61c16af36c9a07e5c9906498b73e091949a20", - "reference": "e2a61c16af36c9a07e5c9906498b73e091949a20", + "url": "https://api.github.com/repos/symfony/process/zipball/48bad913268c8cafabbf7034b39c8bb24fbc5ab8", + "reference": "48bad913268c8cafabbf7034b39c8bb24fbc5ab8", "shasum": "" }, "require": { @@ -799,7 +823,7 @@ "description": "Executes commands in sub-processes", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/process/tree/v6.4.20" + "source": "https://github.com/symfony/process/tree/v6.4.26" }, "funding": [ { @@ -811,24 +835,28 @@ "type": "github" }, { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2025-03-10T17:11:00+00:00" + "time": "2025-09-11T09:57:09+00:00" }, { "name": "symfony/service-contracts", - "version": "v3.5.1", + "version": "v3.6.1", "source": { "type": "git", "url": "https://github.com/symfony/service-contracts.git", - "reference": "e53260aabf78fb3d63f8d79d69ece59f80d5eda0" + "reference": "45112560a3ba2d715666a509a0bc9521d10b6c43" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/service-contracts/zipball/e53260aabf78fb3d63f8d79d69ece59f80d5eda0", - "reference": "e53260aabf78fb3d63f8d79d69ece59f80d5eda0", + "url": "https://api.github.com/repos/symfony/service-contracts/zipball/45112560a3ba2d715666a509a0bc9521d10b6c43", + "reference": "45112560a3ba2d715666a509a0bc9521d10b6c43", "shasum": "" }, "require": { @@ -846,7 +874,7 @@ "name": "symfony/contracts" }, "branch-alias": { - "dev-main": "3.5-dev" + "dev-main": "3.6-dev" } }, "autoload": { @@ -882,7 +910,7 @@ "standards" ], "support": { - "source": "https://github.com/symfony/service-contracts/tree/v3.5.1" + "source": "https://github.com/symfony/service-contracts/tree/v3.6.1" }, "funding": [ { @@ -894,24 +922,28 @@ "type": "github" }, { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2024-09-25T14:20:29+00:00" + "time": "2025-07-15T11:30:57+00:00" }, { "name": "symfony/string", - "version": "v6.4.21", + "version": "v6.4.30", "source": { "type": "git", "url": "https://github.com/symfony/string.git", - "reference": "73e2c6966a5aef1d4892873ed5322245295370c6" + "reference": "50590a057841fa6bf69d12eceffce3465b9e32cb" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/string/zipball/73e2c6966a5aef1d4892873ed5322245295370c6", - "reference": "73e2c6966a5aef1d4892873ed5322245295370c6", + "url": "https://api.github.com/repos/symfony/string/zipball/50590a057841fa6bf69d12eceffce3465b9e32cb", + "reference": "50590a057841fa6bf69d12eceffce3465b9e32cb", "shasum": "" }, "require": { @@ -925,7 +957,6 @@ "symfony/translation-contracts": "<2.5" }, "require-dev": { - "symfony/error-handler": "^5.4|^6.0|^7.0", "symfony/http-client": "^5.4|^6.0|^7.0", "symfony/intl": "^6.2|^7.0", "symfony/translation-contracts": "^2.5|^3.0", @@ -968,7 +999,7 @@ "utf8" ], "support": { - "source": "https://github.com/symfony/string/tree/v6.4.21" + "source": "https://github.com/symfony/string/tree/v6.4.30" }, "funding": [ { @@ -980,11 +1011,15 @@ "type": "github" }, { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2025-04-18T15:23:29+00:00" + "time": "2025-11-21T18:03:05+00:00" }, { "name": "vlucas/phpdotenv", @@ -1074,16 +1109,16 @@ "packages-dev": [ { "name": "myclabs/deep-copy", - "version": "1.13.1", + "version": "1.13.4", "source": { "type": "git", "url": "https://github.com/myclabs/DeepCopy.git", - "reference": "1720ddd719e16cf0db4eb1c6eca108031636d46c" + "reference": "07d290f0c47959fd5eed98c95ee5602db07e0b6a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/1720ddd719e16cf0db4eb1c6eca108031636d46c", - "reference": "1720ddd719e16cf0db4eb1c6eca108031636d46c", + "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/07d290f0c47959fd5eed98c95ee5602db07e0b6a", + "reference": "07d290f0c47959fd5eed98c95ee5602db07e0b6a", "shasum": "" }, "require": { @@ -1122,7 +1157,7 @@ ], "support": { "issues": "https://github.com/myclabs/DeepCopy/issues", - "source": "https://github.com/myclabs/DeepCopy/tree/1.13.1" + "source": "https://github.com/myclabs/DeepCopy/tree/1.13.4" }, "funding": [ { @@ -1130,20 +1165,20 @@ "type": "tidelift" } ], - "time": "2025-04-29T12:36:36+00:00" + "time": "2025-08-01T08:46:24+00:00" }, { "name": "nikic/php-parser", - "version": "v5.4.0", + "version": "v5.7.0", "source": { "type": "git", "url": "https://github.com/nikic/PHP-Parser.git", - "reference": "447a020a1f875a434d62f2a401f53b82a396e494" + "reference": "dca41cd15c2ac9d055ad70dbfd011130757d1f82" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/447a020a1f875a434d62f2a401f53b82a396e494", - "reference": "447a020a1f875a434d62f2a401f53b82a396e494", + "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/dca41cd15c2ac9d055ad70dbfd011130757d1f82", + "reference": "dca41cd15c2ac9d055ad70dbfd011130757d1f82", "shasum": "" }, "require": { @@ -1162,7 +1197,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "5.0-dev" + "dev-master": "5.x-dev" } }, "autoload": { @@ -1186,9 +1221,9 @@ ], "support": { "issues": "https://github.com/nikic/PHP-Parser/issues", - "source": "https://github.com/nikic/PHP-Parser/tree/v5.4.0" + "source": "https://github.com/nikic/PHP-Parser/tree/v5.7.0" }, - "time": "2024-12-30T11:07:19+00:00" + "time": "2025-12-06T11:56:16+00:00" }, { "name": "phar-io/manifest", @@ -1631,16 +1666,16 @@ }, { "name": "phpunit/phpunit", - "version": "10.5.46", + "version": "10.5.60", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/phpunit.git", - "reference": "8080be387a5be380dda48c6f41cee4a13aadab3d" + "reference": "f2e26f52f80ef77832e359205f216eeac00e320c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/8080be387a5be380dda48c6f41cee4a13aadab3d", - "reference": "8080be387a5be380dda48c6f41cee4a13aadab3d", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/f2e26f52f80ef77832e359205f216eeac00e320c", + "reference": "f2e26f52f80ef77832e359205f216eeac00e320c", "shasum": "" }, "require": { @@ -1650,7 +1685,7 @@ "ext-mbstring": "*", "ext-xml": "*", "ext-xmlwriter": "*", - "myclabs/deep-copy": "^1.13.1", + "myclabs/deep-copy": "^1.13.4", "phar-io/manifest": "^2.0.4", "phar-io/version": "^3.2.1", "php": ">=8.1", @@ -1661,13 +1696,13 @@ "phpunit/php-timer": "^6.0.0", "sebastian/cli-parser": "^2.0.1", "sebastian/code-unit": "^2.0.0", - "sebastian/comparator": "^5.0.3", + "sebastian/comparator": "^5.0.4", "sebastian/diff": "^5.1.1", "sebastian/environment": "^6.1.0", - "sebastian/exporter": "^5.1.2", + "sebastian/exporter": "^5.1.4", "sebastian/global-state": "^6.0.2", "sebastian/object-enumerator": "^5.0.0", - "sebastian/recursion-context": "^5.0.0", + "sebastian/recursion-context": "^5.0.1", "sebastian/type": "^4.0.0", "sebastian/version": "^4.0.1" }, @@ -1712,7 +1747,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.46" + "source": "https://github.com/sebastianbergmann/phpunit/tree/10.5.60" }, "funding": [ { @@ -1736,7 +1771,7 @@ "type": "tidelift" } ], - "time": "2025-05-02T06:46:24+00:00" + "time": "2025-12-06T07:50:42+00:00" }, { "name": "sebastian/cli-parser", @@ -1908,16 +1943,16 @@ }, { "name": "sebastian/comparator", - "version": "5.0.3", + "version": "5.0.4", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/comparator.git", - "reference": "a18251eb0b7a2dcd2f7aa3d6078b18545ef0558e" + "reference": "e8e53097718d2b53cfb2aa859b06a41abf58c62e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/a18251eb0b7a2dcd2f7aa3d6078b18545ef0558e", - "reference": "a18251eb0b7a2dcd2f7aa3d6078b18545ef0558e", + "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/e8e53097718d2b53cfb2aa859b06a41abf58c62e", + "reference": "e8e53097718d2b53cfb2aa859b06a41abf58c62e", "shasum": "" }, "require": { @@ -1973,15 +2008,27 @@ "support": { "issues": "https://github.com/sebastianbergmann/comparator/issues", "security": "https://github.com/sebastianbergmann/comparator/security/policy", - "source": "https://github.com/sebastianbergmann/comparator/tree/5.0.3" + "source": "https://github.com/sebastianbergmann/comparator/tree/5.0.4" }, "funding": [ { "url": "https://github.com/sebastianbergmann", "type": "github" + }, + { + "url": "https://liberapay.com/sebastianbergmann", + "type": "liberapay" + }, + { + "url": "https://thanks.dev/u/gh/sebastianbergmann", + "type": "thanks_dev" + }, + { + "url": "https://tidelift.com/funding/github/packagist/sebastian/comparator", + "type": "tidelift" } ], - "time": "2024-10-18T14:56:07+00:00" + "time": "2025-09-07T05:25:07+00:00" }, { "name": "sebastian/complexity", @@ -2174,16 +2221,16 @@ }, { "name": "sebastian/exporter", - "version": "5.1.2", + "version": "5.1.4", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/exporter.git", - "reference": "955288482d97c19a372d3f31006ab3f37da47adf" + "reference": "0735b90f4da94969541dac1da743446e276defa6" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/955288482d97c19a372d3f31006ab3f37da47adf", - "reference": "955288482d97c19a372d3f31006ab3f37da47adf", + "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/0735b90f4da94969541dac1da743446e276defa6", + "reference": "0735b90f4da94969541dac1da743446e276defa6", "shasum": "" }, "require": { @@ -2192,7 +2239,7 @@ "sebastian/recursion-context": "^5.0" }, "require-dev": { - "phpunit/phpunit": "^10.0" + "phpunit/phpunit": "^10.5" }, "type": "library", "extra": { @@ -2240,15 +2287,27 @@ "support": { "issues": "https://github.com/sebastianbergmann/exporter/issues", "security": "https://github.com/sebastianbergmann/exporter/security/policy", - "source": "https://github.com/sebastianbergmann/exporter/tree/5.1.2" + "source": "https://github.com/sebastianbergmann/exporter/tree/5.1.4" }, "funding": [ { "url": "https://github.com/sebastianbergmann", "type": "github" + }, + { + "url": "https://liberapay.com/sebastianbergmann", + "type": "liberapay" + }, + { + "url": "https://thanks.dev/u/gh/sebastianbergmann", + "type": "thanks_dev" + }, + { + "url": "https://tidelift.com/funding/github/packagist/sebastian/exporter", + "type": "tidelift" } ], - "time": "2024-03-02T07:17:12+00:00" + "time": "2025-09-24T06:09:11+00:00" }, { "name": "sebastian/global-state", @@ -2484,23 +2543,23 @@ }, { "name": "sebastian/recursion-context", - "version": "5.0.0", + "version": "5.0.1", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/recursion-context.git", - "reference": "05909fb5bc7df4c52992396d0116aed689f93712" + "reference": "47e34210757a2f37a97dcd207d032e1b01e64c7a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/05909fb5bc7df4c52992396d0116aed689f93712", - "reference": "05909fb5bc7df4c52992396d0116aed689f93712", + "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/47e34210757a2f37a97dcd207d032e1b01e64c7a", + "reference": "47e34210757a2f37a97dcd207d032e1b01e64c7a", "shasum": "" }, "require": { "php": ">=8.1" }, "require-dev": { - "phpunit/phpunit": "^10.0" + "phpunit/phpunit": "^10.5" }, "type": "library", "extra": { @@ -2535,15 +2594,28 @@ "homepage": "https://github.com/sebastianbergmann/recursion-context", "support": { "issues": "https://github.com/sebastianbergmann/recursion-context/issues", - "source": "https://github.com/sebastianbergmann/recursion-context/tree/5.0.0" + "security": "https://github.com/sebastianbergmann/recursion-context/security/policy", + "source": "https://github.com/sebastianbergmann/recursion-context/tree/5.0.1" }, "funding": [ { "url": "https://github.com/sebastianbergmann", "type": "github" + }, + { + "url": "https://liberapay.com/sebastianbergmann", + "type": "liberapay" + }, + { + "url": "https://thanks.dev/u/gh/sebastianbergmann", + "type": "thanks_dev" + }, + { + "url": "https://tidelift.com/funding/github/packagist/sebastian/recursion-context", + "type": "tidelift" } ], - "time": "2023-02-03T07:05:40+00:00" + "time": "2025-08-10T07:50:56+00:00" }, { "name": "sebastian/type", @@ -2656,16 +2728,16 @@ }, { "name": "theseer/tokenizer", - "version": "1.2.3", + "version": "1.3.1", "source": { "type": "git", "url": "https://github.com/theseer/tokenizer.git", - "reference": "737eda637ed5e28c3413cb1ebe8bb52cbf1ca7a2" + "reference": "b7489ce515e168639d17feec34b8847c326b0b3c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/theseer/tokenizer/zipball/737eda637ed5e28c3413cb1ebe8bb52cbf1ca7a2", - "reference": "737eda637ed5e28c3413cb1ebe8bb52cbf1ca7a2", + "url": "https://api.github.com/repos/theseer/tokenizer/zipball/b7489ce515e168639d17feec34b8847c326b0b3c", + "reference": "b7489ce515e168639d17feec34b8847c326b0b3c", "shasum": "" }, "require": { @@ -2694,7 +2766,7 @@ "description": "A small library for converting tokenized PHP source code into XML and potentially other formats", "support": { "issues": "https://github.com/theseer/tokenizer/issues", - "source": "https://github.com/theseer/tokenizer/tree/1.2.3" + "source": "https://github.com/theseer/tokenizer/tree/1.3.1" }, "funding": [ { @@ -2702,7 +2774,7 @@ "type": "github" } ], - "time": "2024-03-03T12:36:25+00:00" + "time": "2025-11-17T20:03:58+00:00" } ], "aliases": [], @@ -25,8 +25,8 @@ $formatter->setStyle('error', new OutputFormatterStyle('red')); // Run the command and handle errors try { - $output->writeln("<warn>WARNING: This CLI is in alpha testing.</warn>"); - $output->writeln("<warn>There's a high chance of issues, and the CLI API is subject to change.</warn>"); + $output->writeln("<warn>WARNING: This CLI is in beta testing.</warn>"); + $output->writeln("<warn>There's a chance of issues, and the CLI API is subject to change.</warn>"); $output->writeln(""); $app->run(null, $output); diff --git a/src/Commands/DownloadVendorCommand.php b/src/Commands/DownloadVendorCommand.php index d3b0501..5019238 100644 --- a/src/Commands/DownloadVendorCommand.php +++ b/src/Commands/DownloadVendorCommand.php @@ -3,14 +3,12 @@ namespace Cli\Commands; use Cli\Services\AppLocator; +use Cli\Services\VendorDownloader; use Exception; -use RecursiveDirectoryIterator; -use RecursiveIteratorIterator; use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Output\OutputInterface; -use ZipArchive; class DownloadVendorCommand extends Command { @@ -28,136 +26,9 @@ class DownloadVendorCommand extends Command { $appDir = AppLocator::require($input->getOption('app-directory')); - $output->writeln("<info>Checking app version...</info>"); - $version = AppLocator::getVersion($appDir); - if (empty($version)) { - throw new CommandError("Could not determine instance BookStack version."); - } - $targetChecksum = $this->getTargetChecksum($appDir); - - $output->writeln("<info>Downloading ZIP from files.bookstackapp.com...</info>"); - $zip = $this->downloadVendorZip($version); - - $output->writeln("<info>Validating downloaded ZIP...</info>"); - $this->verifyZipChecksum($zip, $targetChecksum); - - $output->writeln("<info>Deleting existing vendor/ directory...</info>"); - try { - $this->deleteAppVendorFiles($appDir); - } catch (Exception $exception) { - unlink($zip); - throw $exception; - } - - $output->writeln("<info>Extracting ZIP into BookStack instance...</info>"); - $this->extractZip($zip, $appDir); - - $output->writeln("<info>Cleaning up old app services...</info>"); - $cleaned = $this->cleanupAppServices($appDir); - if (!$cleaned) { - $output->writeln("<warning>Failed to remove exising app services file</warning>"); - } - - $output->writeln("<success>Successfully downloaded & extracted vendor files into BookStack instance!</success>"); + $downloader = new VendorDownloader($appDir, $output); + $downloader->download(); return Command::SUCCESS; } - - protected function cleanupAppServices(string $appDir): bool - { - $filesToClear = [ - implode(DIRECTORY_SEPARATOR, [$appDir, 'bootstrap', 'cache', 'services.php']), - implode(DIRECTORY_SEPARATOR, [$appDir, 'bootstrap', 'cache', 'packages.php']), - ]; - - $status = true; - - foreach ($filesToClear as $file) { - if (file_exists($file)) { - if (@unlink($file) === false) { - $status = false; - } - } - } - - return $status; - } - - protected function extractZip(string $zipPath, string $appDir): void - { - $zip = new ZipArchive(); - $opened = $zip->open($zipPath, ZipArchive::RDONLY); - $extracted = $zip->extractTo($appDir); - $closed = $zip->close(); - - unlink($zipPath); - if (!$opened || !$extracted || !$closed) { - throw new CommandError("Failed to extract ZIP files into {$appDir}"); - } - } - - protected function deleteAppVendorFiles(string $appDir): void - { - $targetDir = $appDir . DIRECTORY_SEPARATOR . 'vendor'; - if (!is_dir($targetDir)) { - return; - } - - $it = new RecursiveDirectoryIterator($targetDir, RecursiveDirectoryIterator::SKIP_DOTS); - $files = new RecursiveIteratorIterator($it, RecursiveIteratorIterator::CHILD_FIRST); - foreach($files as $file) { - if ($file->isDir()){ - rmdir($file->getPathname()); - } else { - unlink($file->getPathname()); - } - } - - $deleted = rmdir($targetDir); - if (!$deleted) { - throw new CommandError("Could not delete existing app vendor directory."); - } - } - - protected function verifyZipChecksum(string $zipPath, string $targetChecksum): void - { - $zipChecksum = hash_file('sha256', $zipPath); - if ($zipChecksum !== $targetChecksum) { - unlink($zipPath); - throw new CommandError("Checksum of downloaded ZIP does not match the expected checksum."); - } - } - - protected function downloadVendorZip(string $version): string - { - $tempFile = tempnam(sys_get_temp_dir(), 'bs-cli-vendor-zip'); - $targetUrl = "https://files.bookstackapp.com/vendor/{$version}.zip"; - - $targetFile = @fopen($targetUrl, 'rb'); - if ($targetFile === false) { - throw new CommandError("Failed to download ZIP file from $targetUrl"); - } - - file_put_contents($tempFile, $targetFile); - - return $tempFile; - } - - /** - * @throws CommandError - */ - protected function getTargetChecksum(string $appDir): string - { - $checksumFile = implode(DIRECTORY_SEPARATOR, [$appDir, 'dev', 'checksums', 'vendor']); - $checksum = ''; - if (file_exists($checksumFile)) { - $checksum = trim(file_get_contents($checksumFile)); - } - - if (empty($checksum)) { - throw new CommandError("Could not find a vendor checksum for validation."); - } - - return $checksum; - } } diff --git a/src/Commands/InitCommand.php b/src/Commands/InitCommand.php index 4ebd267..80d17e9 100644 --- a/src/Commands/InitCommand.php +++ b/src/Commands/InitCommand.php @@ -2,11 +2,11 @@ namespace Cli\Commands; -use Cli\Services\ComposerLocator; use Cli\Services\EnvironmentLoader; use Cli\Services\Paths; use Cli\Services\ProgramRunner; use Cli\Services\RequirementsValidator; +use Cli\Services\VendorDownloader; use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Input\InputArgument; use Symfony\Component\Console\Input\InputInterface; @@ -38,16 +38,9 @@ class InitCommand extends Command $output->writeln("<info>Cloning down BookStack project to install directory...</info>"); $this->cloneBookStackViaGit($installDir); - $output->writeln("<info>Checking composer exists...</info>"); - $composerLocator = new ComposerLocator($installDir); - $composer = $composerLocator->getProgram(); - if (!$composer->isFound()) { - $output->writeln("<info>Composer does not exist, downloading a local copy...</info>"); - $composerLocator->download(); - } - - $output->writeln("<info>Installing application dependencies using composer...</info>"); - $this->installComposerDependencies($composer, $installDir); + $output->writeln("<info>Downloading PHP dependencies from files.bookstackapp.com..</info>"); + $vendorDownloader = new VendorDownloader($installDir, $output); + $vendorDownloader->download(); $output->writeln("<info>Creating .env file from .env.example...</info>"); copy(Paths::join($installDir, '.env.example'), Paths::join($installDir, '.env')); @@ -84,24 +77,7 @@ class InitCommand extends Command } /** - * Run composer install to download PHP dependencies. - * @throws CommandError - */ - protected function installComposerDependencies(ProgramRunner $composer, string $installDir): void - { - $errors = $composer->runCapturingStdErr([ - 'install', - '--no-dev', '-n', '-q', '--no-progress', - '-d', $installDir - ]); - - if ($errors) { - throw new CommandError("Failed composer install with errors:\n" . $errors); - } - } - - /** - * Clone a new instance of BookStack to the given install folder. + * Clone a new instance of BookStack to the given installation folder. * @throws CommandError */ protected function cloneBookStackViaGit(string $installDir): void @@ -114,7 +90,7 @@ class InitCommand extends Command 'clone', '-q', '--branch', 'release', '--single-branch', - 'https://github.com/BookStackApp/BookStack.git', + 'https://source.bookstackapp.com/bookstack.git', $installDir ]); diff --git a/src/Commands/UpdateCommand.php b/src/Commands/UpdateCommand.php index 23ffc2d..c6fc476 100644 --- a/src/Commands/UpdateCommand.php +++ b/src/Commands/UpdateCommand.php @@ -4,10 +4,10 @@ namespace Cli\Commands; use Cli\Services\AppLocator; use Cli\Services\ArtisanRunner; -use Cli\Services\ComposerLocator; use Cli\Services\Paths; use Cli\Services\ProgramRunner; use Cli\Services\RequirementsValidator; +use Cli\Services\VendorDownloader; use Phar; use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Input\InputInterface; @@ -35,14 +35,6 @@ class UpdateCommand extends Command $output->writeln("<info>Checking local Git repository is active...</info>"); $this->ensureGitRepoExists($appDir); - $output->writeln("<info>Checking composer exists...</info>"); - $composerLocator = new ComposerLocator($appDir); - $composer = $composerLocator->getProgram(); - if (!$composer->isFound()) { - $output->writeln("<info>Composer does not exist, downloading a local copy...</info>"); - $composerLocator->download(); - } - $cliPath = Phar::running(false); $cliPreUpdateHash = $cliPath ? hash_file('sha256', $cliPath) : ''; @@ -55,8 +47,9 @@ class UpdateCommand extends Command return Command::FAILURE; } - $output->writeln("<info>Installing PHP dependencies via composer...</info>"); - $this->installComposerDependencies($composer, $appDir); + $output->writeln("<info>Downloading PHP dependencies from files.bookstackapp.com...</info>"); + $vendorDownloader = new VendorDownloader($appDir, $output); + $vendorDownloader->download(); $output->writeln("<info>Running database migrations...</info>"); $artisan = (new ArtisanRunner($appDir)); @@ -90,22 +83,6 @@ class UpdateCommand extends Command } } - /** - * @throws CommandError - */ - protected function installComposerDependencies(ProgramRunner $composer, string $appDir): void - { - $errors = $composer->runCapturingStdErr([ - 'install', - '--no-dev', '-n', '-q', '--no-progress', - '-d', $appDir, - ]); - - if ($errors) { - throw new CommandError("Failed composer install with errors:\n" . $errors); - } - } - protected function ensureGitRepoExists(string $appDir): void { $expectedPath = Paths::join($appDir, '.git'); diff --git a/src/Services/VendorDownloader.php b/src/Services/VendorDownloader.php new file mode 100644 index 0000000..389baf2 --- /dev/null +++ b/src/Services/VendorDownloader.php @@ -0,0 +1,151 @@ +<?php declare(strict_types=1); + +namespace Cli\Services; + +use Cli\Commands\CommandError; +use Exception; +use RecursiveDirectoryIterator; +use RecursiveIteratorIterator; +use Symfony\Component\Console\Output\OutputInterface; +use ZipArchive; + +class VendorDownloader +{ + public function __construct( + protected string $appDir, + protected OutputInterface $output + ) {} + + public function download(): void + { + $this->output->writeln("<info>Checking app version...</info>"); + $version = AppLocator::getVersion($this->appDir); + if (empty($version)) { + throw new CommandError("Could not determine instance BookStack version."); + } + $targetChecksum = $this->getTargetChecksum(); + + $this->output->writeln("<info>Downloading ZIP from files.bookstackapp.com...</info>"); + $zip = $this->downloadVendorZip($version); + + $this->output->writeln("<info>Validating downloaded ZIP...</info>"); + $this->verifyZipChecksum($zip, $targetChecksum); + + $this->output->writeln("<info>Deleting existing vendor/ directory...</info>"); + try { + $this->deleteAppVendorFiles(); + } catch (Exception $exception) { + unlink($zip); + throw $exception; + } + + $this->output->writeln("<info>Extracting ZIP into BookStack instance...</info>"); + $this->extractZip($zip); + + $this->output->writeln("<info>Cleaning up old app services...</info>"); + $cleaned = $this->cleanupAppServices(); + if (!$cleaned) { + $this->output->writeln("<warning>Failed to remove exising app services file</warning>"); + } + + $this->output->writeln("<success>Successfully downloaded & extracted vendor files into BookStack instance!</success>"); + } + + protected function cleanupAppServices(): bool + { + $filesToClear = [ + implode(DIRECTORY_SEPARATOR, [$this->appDir, 'bootstrap', 'cache', 'services.php']), + implode(DIRECTORY_SEPARATOR, [$this->appDir, 'bootstrap', 'cache', 'packages.php']), + ]; + + $status = true; + + foreach ($filesToClear as $file) { + if (file_exists($file)) { + if (@unlink($file) === false) { + $status = false; + } + } + } + + return $status; + } + + protected function extractZip(string $zipPath): void + { + $zip = new ZipArchive(); + $opened = $zip->open($zipPath, ZipArchive::RDONLY); + $extracted = $zip->extractTo($this->appDir); + $closed = $zip->close(); + + unlink($zipPath); + if (!$opened || !$extracted || !$closed) { + throw new CommandError("Failed to extract ZIP files into {$this->appDir}"); + } + } + + protected function deleteAppVendorFiles(): void + { + $targetDir = $this->appDir . DIRECTORY_SEPARATOR . 'vendor'; + if (!is_dir($targetDir)) { + return; + } + + $it = new RecursiveDirectoryIterator($targetDir, RecursiveDirectoryIterator::SKIP_DOTS); + $files = new RecursiveIteratorIterator($it, RecursiveIteratorIterator::CHILD_FIRST); + foreach($files as $file) { + if ($file->isDir()){ + rmdir($file->getPathname()); + } else { + unlink($file->getPathname()); + } + } + + $deleted = rmdir($targetDir); + if (!$deleted) { + throw new CommandError("Could not delete existing app vendor directory."); + } + } + + protected function verifyZipChecksum(string $zipPath, string $targetChecksum): void + { + $zipChecksum = hash_file('sha256', $zipPath); + if ($zipChecksum !== $targetChecksum) { + unlink($zipPath); + throw new CommandError("Checksum of downloaded ZIP does not match the expected checksum."); + } + } + + protected function downloadVendorZip(string $version): string + { + $tempFile = tempnam(sys_get_temp_dir(), 'bs-cli-vendor-zip'); + $targetUrl = "https://files.bookstackapp.com/vendor/{$version}.zip"; + + $targetFile = @fopen($targetUrl, 'rb'); + if ($targetFile === false) { + throw new CommandError("Failed to download ZIP file from $targetUrl"); + } + + file_put_contents($tempFile, $targetFile); + + return $tempFile; + } + + /** + * @throws CommandError + */ + protected function getTargetChecksum(): string + { + $checksumFile = implode(DIRECTORY_SEPARATOR, [$this->appDir, 'dev', 'checksums', 'vendor']); + $checksum = ''; + if (file_exists($checksumFile)) { + $checksum = trim(file_get_contents($checksumFile)); + } + + if (empty($checksum)) { + throw new CommandError("Could not find a vendor checksum for validation."); + } + + return $checksum; + } +} diff --git a/src/app.php b/src/app.php index cef8388..26e5c53 100644 --- a/src/app.php +++ b/src/app.php @@ -9,7 +9,7 @@ use Cli\Commands\RestoreCommand; use Cli\Commands\UpdateCommand; // Setup our CLI -$app = new Application('bookstack-system-cli', '0.3.1'); +$app = new Application('bookstack-system-cli', '0.4.0'); $app->setCatchExceptions(false); $app->add(new BackupCommand()); diff --git a/tests/Commands/BackupCommandTest.php b/tests/Commands/BackupCommandTest.php index 6652731..d9aad3e 100644 --- a/tests/Commands/BackupCommandTest.php +++ b/tests/Commands/BackupCommandTest.php @@ -159,7 +159,7 @@ class BackupCommandTest extends TestCase public function test_backup_using_database_credentials_with_special_chars() { - // The user details used here is created as part of the database docker-mysql-init.sql script. + // The user details used here are created as part of the database docker-mysql-init.sql script. chdir('/var/www/bookstack'); $this->assertCount(0, glob('storage/backups/bookstack-backup-*.zip')); diff --git a/tests/Commands/UpdateCommandTest.php b/tests/Commands/UpdateCommandTest.php index d0d0f2e..3a7973a 100644 --- a/tests/Commands/UpdateCommandTest.php +++ b/tests/Commands/UpdateCommandTest.php @@ -15,25 +15,6 @@ class UpdateCommandTest extends TestCase $result->assertStdoutContains("Your BookStack instance at [/var/www/bookstack] has been updated!"); } - public function test_composer_gets_downloaded_locally_if_not_found() - { - chdir('/var/www/bookstack'); - - rename('/usr/local/bin/composer', '/usr/local/bin/hiddencomposer'); - - $this->assertFileDoesNotExist('/var/www/bookstack/composer'); - - $result = $this->runCommand('update'); - $result->assertSuccessfulExit(); - $result->assertStdoutContains("Composer does not exist, downloading a local copy..."); - $result->assertStdoutContains("Your BookStack instance at [/var/www/bookstack] has been updated!"); - - $this->assertFileExists('/var/www/bookstack/composer'); - unlink('/var/www/bookstack/composer'); - - rename('/usr/local/bin/hiddencomposer', '/usr/local/bin/composer'); - } - public function test_command_rejects_on_no_instance_found() { chdir('/home'); @@ -65,4 +46,19 @@ class UpdateCommandTest extends TestCase $result->assertSuccessfulExit(); $result->assertStdoutContains("Your BookStack instance at [/var/www/bookstack] has been updated!"); } + + public function test_vendor_folder_gets_created() + { + $this->withOwnBookStackFolder(function (string $basePath) { + exec("rm -rf {$basePath}/vendor"); + + $this->assertDirectoryDoesNotExist("{$basePath}/vendor"); + + $this->runCommand('update', ['--app-directory' => $basePath]) + ->assertSuccessfulExit(); + + $this->assertDirectoryExists("{$basePath}/vendor"); + $this->assertGreaterThan(20, glob("{$basePath}/vendor/composer/*")); + }); + } }
\ No newline at end of file |
