├── .gitattributes ├── .gitignore ├── .php-cs-fixer.php ├── CHANGELOG.md ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── VERSIONING.md ├── bin └── git-commit.sh ├── composer.json ├── doc ├── autocommit.md ├── configuration.md └── demo.gif ├── phpstan.neon.dist ├── phpunit.xml.dist ├── src ├── ChangelogsPlugin.php ├── Config │ ├── ConfigBuilder.php │ └── ConfigLocator.php ├── Factory.php ├── Model │ ├── Config.php │ ├── Repository.php │ └── Version.php ├── OperationHandler │ ├── InstallHandler.php │ ├── OperationHandler.php │ ├── UninstallHandler.php │ └── UpdateHandler.php ├── Outputter.php ├── UrlGenerator │ ├── BitbucketUrlGenerator.php │ ├── GitBasedUrlGenerator.php │ ├── GithubUrlGenerator.php │ ├── GitlabUrlGenerator.php │ ├── UrlGenerator.php │ └── WordPressUrlGenerator.php └── Util │ └── FileSystemHelper.php └── tests ├── ChangelogsPluginTest.php ├── Config ├── ConfigBuilderTest.php └── ConfigLocatorTest.php ├── OperationHandler ├── InstallHandlerTest.php ├── UninstallHandlerTest.php └── UpdateHandlerTest.php ├── OutputterTest.php ├── UrlGenerator ├── BitbucketUrlGeneratorTest.php ├── GithubUrlGeneratorTest.php ├── GitlabUrlGeneratorTest.php └── WordPressUrlGeneratorTest.php ├── Util └── FileSystemHelperTest.php ├── VersionTest.php ├── fixtures ├── bin │ └── fake.sh ├── home-commit-message │ └── composer.json ├── home │ └── composer.json ├── local │ └── composer.json └── other-post-update-priority │ └── composer.json └── resources ├── FakeHandler.php ├── FakeOperation.php └── FakeUrlGenerator.php /.gitattributes: -------------------------------------------------------------------------------- 1 | /.github/ export-ignore 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Dependencies 2 | /vendor 3 | 4 | # Composer 5 | composer.lock 6 | composer.phar 7 | 8 | # CS 9 | .php_cs.cache 10 | .php-cs-fixer.cache 11 | 12 | # PHPUnit 13 | .phpunit.result.cache 14 | -------------------------------------------------------------------------------- /.php-cs-fixer.php: -------------------------------------------------------------------------------- 1 | 7 | 8 | For the full copyright and license information, please view the LICENSE 9 | file that was distributed with this source code. 10 | EOF; 11 | 12 | $finder = PhpCsFixer\Finder::create() 13 | ->in([__DIR__]) 14 | ; 15 | 16 | $config = new PhpCsFixer\Config(); 17 | $config 18 | ->setRiskyAllowed(true) 19 | ->setRules([ 20 | '@Symfony' => true, 21 | 'array_syntax' => ['syntax' => 'short'], // Replace array() by [] 22 | 'blank_line_after_opening_tag' => true, // Force new line after ['spacing' => 'one'], // Force space around concatenation operator 24 | 'header_comment' => ['header' => $header], // Add the provided header comment ($header) 25 | 'heredoc_to_nowdoc' => false, // Do not convert heredoc to nowdoc 26 | 'no_superfluous_phpdoc_tags' => false, // Ensure complete PHPDoc annotations for all params 27 | 'phpdoc_order' => true, // Order "use" statements alphabetically 28 | 'simplified_null_return' => false, // Keep return null; 29 | 'single_line_throw' => false, // Allow throwing exceptions in more than one row 30 | 'strict_comparison' => true, // Strict comparison 31 | 'strict_param' => true, // Functions should use $strict param 32 | ]) 33 | ->setUsingCache(true) 34 | ->setFinder($finder) 35 | ; 36 | 37 | return $config; 38 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changes between versions 2 | 3 | ## 2.1.0 (2023-04-21) 4 | 5 | * Default auto commit confirm to "true" ([#82](https://github.com/pyrech/composer-changelogs/pull/82)) 6 | 7 | ## 2.0.0 (2023-02-04) 8 | 9 | > **Note**: 10 | > 11 | >You might hit a TypeError when migrating to this major version: 12 | > 13 | >``` 14 | >[TypeError] 15 | > Cannot assign Pyrech\ComposerChangelogs\Config\Config to property Pyrech\ComposerChangelogs\ChangelogsPlugin_composer_tmp0::$config of type >?Pyrech\ComposerChangelogs\Model\Config 16 | >``` 17 | > 18 | >The temporary fix is to clean Composer's cache before running the update: 19 | > 20 | >```bash 21 | >composer clear-cache && composer update 22 | >``` 23 | > More info [here](https://github.com/pyrech/composer-changelogs/issues/81) 24 | 25 | Changelog: 26 | * Drop support for PHP < 7.4 ([#79](https://github.com/pyrech/composer-changelogs/pull/79)) 27 | * Modernize the whole codebase ([#79](https://github.com/pyrech/composer-changelogs/pull/79)) 28 | * Add static analysis with PHPStan ([#79](https://github.com/pyrech/composer-changelogs/pull/79)) 29 | * Replace PHPDoc types by native PHP typing ([#79](https://github.com/pyrech/composer-changelogs/pull/79)) 30 | * Remove deprecated Pyrech\ComposerChangelogs\UrlGenerator\AbstractUrlGenerator ([#79](https://github.com/pyrech/composer-changelogs/pull/79)) 31 | * Rename Pyrech\ComposerChangelogs\Version to Pyrech\ComposerChangelogs\Model\Version ([#79](https://github.com/pyrech/composer-changelogs/pull/79)) 32 | * Rename Pyrech\ComposerChangelogs\Config\Config to Pyrech\ComposerChangelogs\Model\Config ([#79](https://github.com/pyrech/composer-changelogs/pull/79)) 33 | 34 | ## 1.8.2 (2021-11-08) 35 | 36 | * Fix PHP 8 warning about passing null to strpos() ([#78](https://github.com/pyrech/composer-changelogs/pull/78)) 37 | 38 | ## 1.8.1 (2021-10-13) 39 | 40 | * Fix semver output colors ([#74](https://github.com/pyrech/composer-changelogs/pull/74)) 41 | 42 | ## 1.8.0 (2021-09-06) 43 | 44 | * Add semver color output representation ([#73](https://github.com/pyrech/composer-changelogs/pull/73)) 45 | 46 | ## 1.7.1 (2020-04-27) 47 | 48 | * Add support for Composer ^2.0 ([#68](https://github.com/pyrech/composer-changelogs/pull/68)) 49 | 50 | ## 1.7.0 (2019-10-21) 51 | 52 | * Display VCS Revision for dev version ([#64](https://github.com/pyrech/composer-changelogs/pull/64)) 53 | * Update how the plugin autoload its classes ([#63](https://github.com/pyrech/composer-changelogs/pull/63)) 54 | * Drop support for PHP < 7.1 ([#66](https://github.com/pyrech/composer-changelogs/pull/66)) 55 | 56 | ## 1.6.0 (2017-12-11) 57 | 58 | * Adding configurable post update priority ([#46](https://github.com/pyrech/composer-changelogs/pull/46)) 59 | 60 | ## 1.5.1 (2017-01-10) 61 | 62 | * Fix compatibility with newest Composer Plugin API ([#42](https://github.com/pyrech/composer-changelogs/pull/42)) 63 | 64 | ## 1.5 (2016-07-04) 65 | 66 | * Add better description for downgrade operations ([#39](https://github.com/pyrech/composer-changelogs/pull/39)) 67 | * Remove tests skipping ([#38](https://github.com/pyrech/composer-changelogs/pull/38)) 68 | * Add support for gitlab repositories ([#37](https://github.com/pyrech/composer-changelogs/pull/37)) 69 | 70 | ## 1.4 (2016-03-21) 71 | 72 | * Update coding standards ([#25](https://github.com/pyrech/composer-changelogs/pull/25)) 73 | * Add support for bitbucket ssh urls ([#27](https://github.com/pyrech/composer-changelogs/pull/27)) 74 | * Fix tests with newer composer versions ([#28](https://github.com/pyrech/composer-changelogs/pull/28)) 75 | * Fix bug when switching from a local repository back to the original repository ([#30](https://github.com/pyrech/composer-changelogs/pull/30)) 76 | * Add GitBasedUrlGenerator to replace AbstractUrlGenerator ([#20](https://github.com/pyrech/composer-changelogs/pull/20)) 77 | * Add experimental autocommit feature ([#29](https://github.com/pyrech/composer-changelogs/pull/29)) 78 | * Add support for github ssh urls ([#32](https://github.com/pyrech/composer-changelogs/pull/32)) 79 | 80 | ## 1.3 (2015-11-13) 81 | 82 | * Add support for comparison across forks and better detect dev versions ([#19](https://github.com/pyrech/composer-changelogs/pull/19)) 83 | * Add autoloading of classes required to make the plugin always working ([#22](https://github.com/pyrech/composer-changelogs/pull/22)) 84 | 85 | ## 1.2 (2015-10-22) 86 | 87 | * Add a WordPress url generator for theme and plugin package ([#11](https://github.com/pyrech/composer-changelogs/pull/11)) 88 | * Remove new line in output in case nothing to display ([#12](https://github.com/pyrech/composer-changelogs/pull/12)) 89 | * Update documentation of local install to use the --dev flag ([#16](https://github.com/pyrech/composer-changelogs/pull/16)) 90 | * Update documentation of tests to use the `composer test` command ([#17](https://github.com/pyrech/composer-changelogs/pull/17)) 91 | 92 | ## 1.1.1 (2015-10-11) 93 | 94 | * Add support for old versions of composer (at least v1.0.0-alpha8) ([#9](https://github.com/pyrech/composer-changelogs/pull/9)) 95 | 96 | ## 1.1 (2015-10-10) 97 | 98 | * Add support for bitbucket repositories ([4e90441](https://github.com/pyrech/composer-changelogs/commit/4e9044113dc24654378f6f7676aefaebebcc1163)) 99 | * Add new line after plugin output ([8b22e38](https://github.com/pyrech/composer-changelogs/commit/8b22e38eeffc0ed4ced6e7270fcb4087fea97301)) 100 | * Add support for install and uninstall operations ([#6](https://github.com/pyrech/composer-changelogs/pull/6)) 101 | * Add support for dev versions ([#7](https://github.com/pyrech/composer-changelogs/pull/7)) 102 | 103 | ## 1.0 (2015-09-26) 104 | 105 | * Initial release 106 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | First of all, **thank you** for contributing, **you are awesome**! 4 | 5 | Everybody should be able to help. Here's how you can do it: 6 | 7 | 1. [Fork it](https://github.com/pyrech/composer-changelogs/fork_select) 8 | 2. improve it 9 | 3. submit a [pull request](https://help.github.com/articles/creating-a-pull-request) 10 | 11 | Here's some tips to make you the best contributor ever: 12 | 13 | * [Rules](#rules) 14 | * [Green tests](#green-tests) 15 | * [Standard code](#standard-code) 16 | * [Keeping your fork up-to-date](#keeping-your-fork-up-to-date) 17 | 18 | ## Rules 19 | 20 | Here are a few rules to follow in order to ease code reviews, and discussions 21 | before maintainers accept and merge your work. 22 | 23 | * You MUST follow the [PSR-1](http://www.php-fig.org/psr/1/) and 24 | [PSR-2](http://www.php-fig.org/psr/2/) (see [Rules](#rules)). 25 | * You MUST run the test suite (see [Green tests](#green-tests)). 26 | * You MUST write (or update) unit tests. 27 | * You SHOULD write documentation. 28 | 29 | Please, write [commit messages that make 30 | sense](http://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html), 31 | and [rebase your branch](http://git-scm.com/book/en/Git-Branching-Rebasing) 32 | before submitting your Pull Request (see also how to [keep your 33 | fork up-to-date](#keeping-your-fork-up-to-date)). 34 | 35 | One may ask you to [squash your 36 | commits](http://gitready.com/advanced/2009/02/10/squashing-commits-with-rebase.html) 37 | too. This is used to "clean" your Pull Request before merging it (we don't want 38 | commits such as `fix tests`, `fix 2`, `fix 3`, etc.). 39 | 40 | Also, while creating your Pull Request on GitHub, you MUST write a description 41 | which gives the context and/or explains why you are creating it. 42 | 43 | Your work will then be reviewed as soon as possible (suggestions about some 44 | changes, improvements or alternatives may be given). 45 | 46 | ## Green tests 47 | 48 | Run the tests using the following script: 49 | 50 | ```shell 51 | vendor/bin/simple-phpunit 52 | ``` 53 | or the alias: 54 | ```shell 55 | composer test 56 | ``` 57 | 58 | ## Standard code 59 | 60 | Use [PHP CS fixer](https://cs.symfony.com/) to make your code compliant with 61 | composer-changelogs's coding standards: 62 | 63 | ```shell 64 | vendor/bin/php-cs-fixer fix --config=.php-cs-fixer.php --diff 65 | ``` 66 | or the alias: 67 | ```shell 68 | composer fix-cs 69 | ``` 70 | 71 | ## Keeping your fork up-to-date 72 | 73 | To keep your fork up-to-date, you should track the upstream (original) one 74 | using the following command: 75 | 76 | 77 | ```shell 78 | git remote add upstream https://github.com/pyrech/composer-changelogs.git 79 | ``` 80 | 81 | Then get the upstream changes: 82 | 83 | ```shell 84 | git checkout main 85 | git pull --rebase origin main 86 | git pull --rebase upstream main 87 | git checkout 88 | git rebase main 89 | ``` 90 | 91 | Finally, publish your changes: 92 | 93 | ```shell 94 | git push -f origin 95 | ``` 96 | 97 | Your pull request will be automatically updated. 98 | 99 | Thank you! 100 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2015 Loïck Piera 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is furnished 8 | to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. 20 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # composer-changelogs 2 | 3 | [![Total Downloads](https://poser.pugx.org/pyrech/composer-changelogs/downloads)](https://packagist.org/packages/pyrech/composer-changelogs) 4 | [![Latest Stable Version](https://poser.pugx.org/pyrech/composer-changelogs/v/stable)](https://packagist.org/packages/pyrech/composer-changelogs) 5 | [![Latest Unstable Version](https://poser.pugx.org/pyrech/composer-changelogs/v/unstable)](https://packagist.org/packages/pyrech/composer-changelogs) 6 | 7 | composer-changelogs is a plugin for Composer. It displays some texts after each 8 | Composer update to nicely summarize the modified packages with links to release 9 | and compare urls. Just copy it in your commit body and you get a nice 10 | description. 11 | 12 | ![Demo](doc/demo.gif) 13 | 14 | ## Installation 15 | 16 | You can install it either globally: 17 | 18 | ```shell 19 | composer global require "pyrech/composer-changelogs" 20 | ``` 21 | 22 | or locally: 23 | 24 | ```shell 25 | composer require --dev "pyrech/composer-changelogs" 26 | ``` 27 | 28 | ## Usage 29 | 30 | That's it! Composer will enable automatically the plugin as soon it's 31 | installed. Just run your Composer updates as usual :) 32 | 33 | If you no longer want to display summary, you can either: 34 | - run your Composer command with the option `--no-plugins` 35 | - uninstall the package 36 | 37 | ## Further documentation 38 | 39 | Here is some documentation about the project: 40 | 41 | * [Configuration, like gitlab hosts setup](doc/configuration.md) 42 | * [Experimental autocommit feature](doc/autocommit.md) 43 | 44 | You can see the current and past versions using one of the following: 45 | 46 | * the `git tag` command 47 | * the [releases page on Github](https://github.com/pyrech/composer-changelogs/releases) 48 | * the file listing the [changes between versions](CHANGELOG.md) 49 | 50 | And finally some meta documentation: 51 | 52 | * [versioning and branching models](VERSIONING.md) 53 | * [contribution instructions](CONTRIBUTING.md) 54 | 55 | ## Credits 56 | 57 | * [Loïck Piera](https://github.com/pyrech) 58 | * [All contributors](https://github.com/pyrech/composer-changelogs/graphs/contributors) 59 | 60 | Kudos to [Damien Alexandre](https://github.com/damienalexandre) for the idea. 61 | 62 | ## License 63 | 64 | composer-changelogs is licensed under the MIT License - see the [LICENSE](LICENSE) 65 | file for details. 66 | -------------------------------------------------------------------------------- /VERSIONING.md: -------------------------------------------------------------------------------- 1 | # Versioning and branching models 2 | 3 | This file explains the versioning and branching models of this project. 4 | 5 | ## Versioning 6 | 7 | The versioning is inspired by [Semantic Versioning](http://semver.org/): 8 | 9 | > Given a version number MAJOR.MINOR.PATCH, increment the: 10 | > 11 | > 1. MAJOR version when you make incompatible API changes 12 | > 2. MINOR version when you add functionality in a backwards-compatible manner 13 | > 3. PATCH version when you make backwards-compatible bug fixes 14 | 15 | ## Branching Model 16 | 17 | The branching is inspired by [@jbenet](https://github.com/jbenet) 18 | [simple git branching model](https://gist.github.com/jbenet/ee6c9ac48068889b0912): 19 | 20 | > 1. `main` must always be deployable. 21 | > 2. **all changes** are made through feature branches (pull-request + merge) 22 | > 3. rebase to avoid/resolve conflicts; merge in to `main` 23 | -------------------------------------------------------------------------------- /bin/git-commit.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | git -C "$1" commit -F "$2" composer.lock 4 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "pyrech/composer-changelogs", 3 | "type": "composer-plugin", 4 | "description": "Display changelogs after each composer update", 5 | "keywords": [ 6 | "composer", 7 | "plugin", 8 | "update", 9 | "changelog" 10 | ], 11 | "license": "MIT", 12 | "authors": [ 13 | { 14 | "name": "Loïck Piera", 15 | "email": "pyrech@gmail.com" 16 | } 17 | ], 18 | "require": { 19 | "php": ">=7.4", 20 | "ext-json": "*", 21 | "composer-plugin-api": "^1.0 || ^2.0" 22 | }, 23 | "require-dev": { 24 | "composer/composer": "^1.1 || ^2.0", 25 | "friendsofphp/php-cs-fixer": "^3.0", 26 | "phpstan/phpstan": "^1.9", 27 | "symfony/phpunit-bridge": "^6.2" 28 | }, 29 | "config": { 30 | "sort-packages": true, 31 | "allow-plugins": { 32 | "pyrech/composer-changelogs": true 33 | } 34 | }, 35 | "extra": { 36 | "class": "Pyrech\\ComposerChangelogs\\ChangelogsPlugin" 37 | }, 38 | "autoload": { 39 | "psr-4": { 40 | "Pyrech\\ComposerChangelogs\\": "src/" 41 | } 42 | }, 43 | "autoload-dev": { 44 | "psr-4": { 45 | "Pyrech\\ComposerChangelogs\\tests\\": "tests/" 46 | } 47 | }, 48 | "scripts": { 49 | "cs": "vendor/bin/php-cs-fixer fix --config=.php-cs-fixer.php --diff --dry-run --verbose", 50 | "fix-cs": "vendor/bin/php-cs-fixer fix --config=.php-cs-fixer.php --diff --verbose", 51 | "test": "vendor/bin/simple-phpunit" 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /doc/autocommit.md: -------------------------------------------------------------------------------- 1 | # Autocommit feature 2 | 3 | > Note: This feature is considered as experimental. Use it carefully and 4 | > report any problem with it. 5 | 6 | As this feature was requested many times, composer-changelogs plugin now allows 7 | you to automatically commit the `composer.lock` file after you ran your 8 | `composer update` command. 9 | 10 | Please read the full documentation below to enable and make use of this feature. 11 | 12 | ## Setup 13 | 14 | By default, this feature is disabled. To enabled it, you just need to setup 15 | some `extra` config in your composer.json: 16 | 17 | ```json 18 | { 19 | "extra": { 20 | "composer-changelogs": { 21 | "commit-auto": "ask", 22 | "commit-bin-file": "path/to/bin", 23 | "commit-message": "First line of commit message" 24 | } 25 | } 26 | } 27 | ``` 28 | 29 | > See [this documentation](configuration.md) for where to put your config. 30 | 31 | ## Options available 32 | 33 | ### commit-auto 34 | 35 | This option can be one value between `never`, `ask` and `always`. 36 | 37 | - `never` is the default option. It disable completly the autocommit feature. 38 | - `ask` will propose you interactively to trigger a commit after each 39 | `composer update` in case some dependencies were updated. 40 | - `always` will trigger a commit everytime a `composer update` is ran and some 41 | dependencies were updated. 42 | 43 | ### commit-bin-file 44 | 45 | This option should contain the path of the script to execute to make the 46 | "commit" (this allows the plugin to not be tight to any VCS). The path can be 47 | either absolute or relative to the `composer.json` file containing the plugin 48 | configuration. 49 | 50 | The script will be called with two parameters: 51 | - the first one is the location of the current working directory 52 | - the second one is the path to the temp file containing the message commit 53 | with the complete changelogs summary. 54 | 55 | The plugin provides a default script to trigger a git commit. The script is 56 | located in `bin/git-commit.sh`. Here is an example configuration to use it 57 | directly (it supposes the `composer.json` containing the extra config is the 58 | one requiring the plugin): 59 | 60 | ```json 61 | { 62 | "extra": { 63 | "composer-changelogs": { 64 | "commit-auto": "ask", 65 | "commit-bin-file": "vendor/pyrech/composer-changelogs/bin/git-commit.sh" 66 | } 67 | } 68 | } 69 | ``` 70 | 71 | ### commit-message 72 | 73 | This option can override the first line of the default commit message. If the 74 | setting is omitted or the value is empty, the default message is used: 75 | > Update dependencies 76 | -------------------------------------------------------------------------------- /doc/configuration.md: -------------------------------------------------------------------------------- 1 | # Plugin configuration 2 | 3 | This plugin supports some configuration - for example to specify the gitlab 4 | hosts it should detect. 5 | 6 | ## Location 7 | 8 | Configuration can be setup by adding parameters in the `extra` section of your 9 | `composer.json`. 10 | 11 | ```json 12 | { 13 | "extra": { 14 | "composer-changelogs": { 15 | "{{ the configuration key }}": "{{ the configuration value }}", 16 | } 17 | } 18 | } 19 | ``` 20 | 21 | This `composer-changelogs` extra config can be put either in your local 22 | `composer.json` (the one of the project you are working on) or the global 23 | one in your `.composer` home directory (like 24 | `/home/{user}/.composer/composer.json` on Linux). 25 | 26 | ## Configuration available 27 | 28 | The available configuration options are listed below: 29 | 30 | ### Gitlab hosts 31 | 32 | Unlike Github or Bitbucket that have fixed domains, Gitlab instances are 33 | self-hosted so there is no way to automatically detects that a domain 34 | correspond to a Gitlab instance. 35 | 36 | The `gitlab-hosts` option can be setup to inform the plugin about the hosts it 37 | should consider as Gitlab instance. 38 | 39 | ```json 40 | { 41 | "extra": { 42 | "composer-changelogs": { 43 | "gitlab-hosts": [ 44 | "gitlab.my-company.org" 45 | ], 46 | } 47 | } 48 | } 49 | ``` 50 | 51 | ### Autocommit feature 52 | 53 | See [the full documentation of this feature](autocommit.md). 54 | 55 | ### Post Update Priority 56 | 57 | The option `post-update-priority` can set a custom event priority for 58 | the composer `post-update-cmd` event. This will delay the changelog 59 | printing and [autocommit feature](autocommit.md). 60 | 61 | The default value is set to `-1`. The value must be a signed int. 62 | A lower event priority also means it's run later. 63 | 64 | This default behaviour ensures that you can run user defined 65 | [composer scripts](https://getcomposer.org/doc/articles/scripts.md#command-events) 66 | for the `post-update-cmd` event before. 67 | 68 | ```json 69 | { 70 | "extra": { 71 | "composer-changelogs": { 72 | "post-update-priority": -1 73 | } 74 | } 75 | } 76 | ``` 77 | -------------------------------------------------------------------------------- /doc/demo.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pyrech/composer-changelogs/f517f9424d255a7ec2bcf056ce90f273fa43a691/doc/demo.gif -------------------------------------------------------------------------------- /phpstan.neon.dist: -------------------------------------------------------------------------------- 1 | parameters: 2 | level: 7 3 | paths: 4 | - src 5 | inferPrivatePropertyTypeFromConstructor: true 6 | -------------------------------------------------------------------------------- /phpunit.xml.dist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | ./tests/ 15 | 16 | 17 | 18 | 19 | 20 | ./ 21 | 22 | ./vendor 23 | ./tests 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /src/ChangelogsPlugin.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Pyrech\ComposerChangelogs; 13 | 14 | use Composer\Composer; 15 | use Composer\EventDispatcher\EventSubscriberInterface; 16 | use Composer\Installer\PackageEvent; 17 | use Composer\Installer\PackageEvents; 18 | use Composer\IO\IOInterface; 19 | use Composer\Plugin\PluginInterface; 20 | use Composer\Script\ScriptEvents; 21 | use Pyrech\ComposerChangelogs\Config\ConfigBuilder; 22 | use Pyrech\ComposerChangelogs\Config\ConfigLocator; 23 | use Pyrech\ComposerChangelogs\Model\Config; 24 | 25 | class ChangelogsPlugin implements PluginInterface, EventSubscriberInterface 26 | { 27 | public const EXTRA_KEY = 'composer-changelogs'; 28 | 29 | private ?IOInterface $io = null; 30 | private ?Outputter $outputter = null; 31 | private ?ConfigLocator $configLocator = null; 32 | private ?Config $config = null; 33 | 34 | private static int $postUpdatePriority = -1; 35 | 36 | public function activate(Composer $composer, IOInterface $io): void 37 | { 38 | $this->io = $io; 39 | $this->configLocator = new ConfigLocator($composer); 40 | 41 | $this->setupConfig(); 42 | $this->autoloadNeededClasses(); 43 | 44 | $this->outputter = Factory::createOutputter($this->config->getGitlabHosts()); 45 | } 46 | 47 | public function deactivate(Composer $composer, IOInterface $io): void 48 | { 49 | } 50 | 51 | public function uninstall(Composer $composer, IOInterface $io): void 52 | { 53 | } 54 | 55 | public static function getSubscribedEvents(): array 56 | { 57 | return [ 58 | PackageEvents::POST_PACKAGE_UPDATE => [ 59 | ['postPackageOperation'], 60 | ], 61 | PackageEvents::POST_PACKAGE_INSTALL => [ 62 | ['postPackageOperation'], 63 | ], 64 | PackageEvents::POST_PACKAGE_UNINSTALL => [ 65 | ['postPackageOperation'], 66 | ], 67 | ScriptEvents::POST_UPDATE_CMD => [ 68 | ['postUpdate', self::$postUpdatePriority], 69 | ], 70 | ]; 71 | } 72 | 73 | public function postPackageOperation(PackageEvent $event): void 74 | { 75 | $operation = $event->getOperation(); 76 | 77 | $this->outputter->addOperation($operation); 78 | } 79 | 80 | public function postUpdate(): void 81 | { 82 | $this->io->write($this->outputter->getOutput()); 83 | 84 | $this->handleCommit(); 85 | } 86 | 87 | /** 88 | * This method ensures all the classes required to make the plugin work 89 | * are loaded. 90 | * 91 | * It's required to avoid composer looking for classes which no longer exist 92 | * (for example after the plugin is updated). 93 | * 94 | * Lot of classes (like operation handlers, url generators, Outputter, etc) 95 | * do not need this because they are already autoloaded at the activation 96 | * of the plugin. 97 | */ 98 | private function autoloadNeededClasses(): void 99 | { 100 | foreach (new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator(__DIR__, \FilesystemIterator::SKIP_DOTS)) as $file) { 101 | if ('.php' === substr($file, 0, -4)) { 102 | class_exists(__NAMESPACE__ . str_replace('/', '\\', substr($file, \strlen(__DIR__), -4))); 103 | } 104 | } 105 | } 106 | 107 | private function setupConfig(): void 108 | { 109 | $builder = new ConfigBuilder(); 110 | 111 | $this->config = $builder->build( 112 | $this->configLocator->getConfig(self::EXTRA_KEY), 113 | $this->configLocator->getPath(self::EXTRA_KEY) 114 | ); 115 | 116 | self::$postUpdatePriority = $this->config->getPostUpdatePriority(); 117 | 118 | if (count($builder->getWarnings()) > 0) { 119 | $this->io->writeError('Invalid config for composer-changelogs plugin:'); 120 | foreach ($builder->getWarnings() as $warning) { 121 | $this->io->write(' ' . $warning); 122 | } 123 | } 124 | } 125 | 126 | private function handleCommit(): void 127 | { 128 | if ($this->outputter->isEmpty()) { 129 | return; 130 | } 131 | 132 | switch ($this->config->getCommitAuto()) { 133 | case 'never': 134 | return; 135 | case 'ask': 136 | if ($this->io->askConfirmation('Would you like to commit the update? [yes]: ', true)) { 137 | $this->doCommit(); 138 | } 139 | break; 140 | case 'always': 141 | $this->doCommit(); 142 | } 143 | } 144 | 145 | private function doCommit(): void 146 | { 147 | if (!$this->config->getCommitBinFile()) { 148 | $this->io->writeError('No "commit-bin-file" for composer-changelogs plugin. Commit not done.'); 149 | 150 | return; 151 | } 152 | 153 | $workingDirectory = getcwd(); 154 | if (!$workingDirectory) { 155 | $this->io->writeError('Could not find current working directory. Commit not done.'); 156 | 157 | return; 158 | } 159 | 160 | $filename = tempnam(sys_get_temp_dir(), 'composer-changelogs-'); 161 | if (!$filename) { 162 | $this->io->writeError('Could not generate temporary filename. Commit not done.'); 163 | 164 | return; 165 | } 166 | 167 | $message = $this->config->getCommitMessage() . PHP_EOL . PHP_EOL . strip_tags($this->outputter->getOutput()); 168 | 169 | file_put_contents($filename, $message); 170 | 171 | $command = $this->config->getCommitBinFile() . ' ' . escapeshellarg($workingDirectory) . ' ' . escapeshellarg($filename); 172 | 173 | $this->io->write(sprintf('Executing following command: %s', $command)); 174 | exec($command); 175 | } 176 | } 177 | -------------------------------------------------------------------------------- /src/Config/ConfigBuilder.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Pyrech\ComposerChangelogs\Config; 13 | 14 | use Pyrech\ComposerChangelogs\Model\Config; 15 | use Pyrech\ComposerChangelogs\Util\FileSystemHelper; 16 | 17 | class ConfigBuilder 18 | { 19 | private const COMMIT_AUTO_NEVER = 'never'; 20 | private const COMMIT_AUTO_ASK = 'ask'; 21 | private const COMMIT_AUTO_ALWAYS = 'always'; 22 | 23 | private const VALID_COMMIT_AUTO_VALUES = [ 24 | self::COMMIT_AUTO_NEVER, 25 | self::COMMIT_AUTO_ASK, 26 | self::COMMIT_AUTO_ALWAYS, 27 | ]; 28 | 29 | /** @var string[] */ 30 | private array $warnings = []; 31 | 32 | /** 33 | * @param array $extra 34 | */ 35 | public function build(array $extra, ?string $baseDir): Config 36 | { 37 | $this->reset(); 38 | 39 | $commitAuto = self::COMMIT_AUTO_NEVER; 40 | $commitBinFile = null; 41 | $commitMessage = 'Update dependencies'; 42 | $gitlabHosts = []; 43 | $postUpdatePriority = -1; 44 | 45 | if (array_key_exists('commit-auto', $extra)) { 46 | if (in_array($extra['commit-auto'], self::VALID_COMMIT_AUTO_VALUES, true)) { 47 | $commitAuto = $extra['commit-auto']; 48 | } else { 49 | $this->warnings[] = self::createWarningFromInvalidValue( 50 | $extra, 51 | 'commit-auto', 52 | $commitAuto, 53 | sprintf('Valid options are "%s".', implode('", "', self::VALID_COMMIT_AUTO_VALUES)) 54 | ); 55 | } 56 | } 57 | 58 | if (array_key_exists('commit-bin-file', $extra)) { 59 | if (self::COMMIT_AUTO_NEVER === $commitAuto) { 60 | $this->warnings[] = '"commit-bin-file" is specified but "commit-auto" option is set to "' . self::COMMIT_AUTO_NEVER . '". Ignoring.'; 61 | } else { 62 | $file = realpath( 63 | FileSystemHelper::isAbsolute($extra['commit-bin-file']) 64 | ? $extra['commit-bin-file'] 65 | : $baseDir . '/' . $extra['commit-bin-file'] 66 | ); 67 | 68 | if (!$file || !file_exists($file)) { 69 | $this->warnings[] = 'The file pointed by the option "commit-bin-file" was not found. Ignoring.'; 70 | } else { 71 | $commitBinFile = $file; 72 | } 73 | } 74 | } elseif (self::COMMIT_AUTO_NEVER !== $commitAuto) { 75 | $this->warnings[] = sprintf( 76 | '"commit-auto" is set to "%s" but "commit-bin-file" was not specified.', 77 | $commitAuto 78 | ); 79 | } 80 | 81 | if (array_key_exists('commit-message', $extra)) { 82 | if (0 === strlen(trim($extra['commit-message']))) { 83 | $this->warnings[] = '"commit-message" is specified but empty. Ignoring and using default commit message.'; 84 | } else { 85 | $commitMessage = $extra['commit-message']; 86 | } 87 | } 88 | 89 | if (array_key_exists('gitlab-hosts', $extra)) { 90 | if (!is_array($extra['gitlab-hosts'])) { 91 | $this->warnings[] = '"gitlab-hosts" is specified but should be an array. Ignoring.'; 92 | } else { 93 | $gitlabHosts = (array) $extra['gitlab-hosts']; 94 | } 95 | } 96 | 97 | if (array_key_exists('post-update-priority', $extra)) { 98 | if (!preg_match('/^-?\d+$/', $extra['post-update-priority'])) { 99 | $this->warnings[] = '"post-update-priority" is specified but not an integer. Ignoring and using default commit event priority.'; 100 | } else { 101 | $postUpdatePriority = (int) $extra['post-update-priority']; 102 | } 103 | } 104 | 105 | return new Config($commitAuto, $commitBinFile, $commitMessage, $gitlabHosts, $postUpdatePriority); 106 | } 107 | 108 | /** 109 | * @return string[] 110 | */ 111 | public function getWarnings(): array 112 | { 113 | return $this->warnings; 114 | } 115 | 116 | private function reset(): void 117 | { 118 | $this->warnings = []; 119 | } 120 | 121 | /** 122 | * @param array $extra 123 | * @param mixed $default 124 | */ 125 | private static function createWarningFromInvalidValue( 126 | array $extra, 127 | string $key, 128 | $default, 129 | string $additionalMessage = '' 130 | ): string { 131 | $warning = sprintf( 132 | 'Invalid value "%s" for option "%s", defaulting to "%s".', 133 | $extra[$key], 134 | $key, 135 | $default 136 | ); 137 | 138 | if ($additionalMessage) { 139 | $warning .= ' ' . $additionalMessage; 140 | } 141 | 142 | return $warning; 143 | } 144 | } 145 | -------------------------------------------------------------------------------- /src/Config/ConfigLocator.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Pyrech\ComposerChangelogs\Config; 13 | 14 | use Composer\Composer; 15 | 16 | class ConfigLocator 17 | { 18 | private Composer $composer; 19 | 20 | /** @var array */ 21 | private array $cache = []; 22 | 23 | public function __construct(Composer $composer) 24 | { 25 | $this->composer = $composer; 26 | } 27 | 28 | /** 29 | * @return array 30 | */ 31 | public function getConfig(string $key): array 32 | { 33 | $this->locate($key); 34 | 35 | return $this->cache[$key]['config']; 36 | } 37 | 38 | public function getPath(string $key): ?string 39 | { 40 | $this->locate($key); 41 | 42 | return $this->cache[$key]['path']; 43 | } 44 | 45 | /** 46 | * Try to locate where is the config for the given key. 47 | */ 48 | public function locate(string $key): bool 49 | { 50 | if (array_key_exists($key, $this->cache)) { 51 | return $this->cache[$key]['found']; 52 | } 53 | 54 | if ($this->locateLocal($key)) { 55 | return true; 56 | } 57 | 58 | if ($this->locateGlobal($key)) { 59 | return true; 60 | } 61 | 62 | $this->cache[$key] = [ 63 | 'found' => false, 64 | 'config' => [], 65 | 'path' => null, 66 | ]; 67 | 68 | return false; 69 | } 70 | 71 | /** 72 | * Search config in the local root package. 73 | */ 74 | private function locateLocal(string $key): bool 75 | { 76 | $composerConfig = $this->composer->getConfig(); 77 | 78 | // Sorry for this, I couldn't find any way to get the path of the current root package 79 | $reflection = new \ReflectionClass($composerConfig); 80 | $property = $reflection->getProperty('baseDir'); 81 | $property->setAccessible(true); 82 | 83 | $path = $property->getValue($composerConfig); 84 | 85 | $localComposerExtra = $this->composer->getPackage()->getExtra(); 86 | 87 | if (array_key_exists($key, $localComposerExtra)) { 88 | $this->cache[$key] = [ 89 | 'found' => true, 90 | 'config' => $localComposerExtra[$key], 91 | 'path' => $path, 92 | ]; 93 | 94 | return true; 95 | } 96 | 97 | return false; 98 | } 99 | 100 | /** 101 | * Search config in the global root package. 102 | */ 103 | private function locateGlobal(string $key): bool 104 | { 105 | $path = $this->composer->getConfig()->get('home'); 106 | 107 | $globalComposerJsonFile = $path . '/composer.json'; 108 | 109 | if (file_exists($globalComposerJsonFile)) { 110 | $globalComposerJson = file_get_contents($globalComposerJsonFile); 111 | 112 | if (!$globalComposerJson) { 113 | throw new \RuntimeException('Could not read global composer.json file'); 114 | } 115 | 116 | $globalComposerConfig = json_decode($globalComposerJson, true, 512, JSON_THROW_ON_ERROR); 117 | 118 | if (array_key_exists('extra', $globalComposerConfig) && array_key_exists($key, $globalComposerConfig['extra'])) { 119 | $this->cache[$key] = [ 120 | 'found' => true, 121 | 'config' => $globalComposerConfig['extra'][$key], 122 | 'path' => $path, 123 | ]; 124 | 125 | return true; 126 | } 127 | } 128 | 129 | return false; 130 | } 131 | } 132 | -------------------------------------------------------------------------------- /src/Factory.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Pyrech\ComposerChangelogs; 13 | 14 | use Pyrech\ComposerChangelogs\OperationHandler\InstallHandler; 15 | use Pyrech\ComposerChangelogs\OperationHandler\UninstallHandler; 16 | use Pyrech\ComposerChangelogs\OperationHandler\UpdateHandler; 17 | use Pyrech\ComposerChangelogs\UrlGenerator\BitbucketUrlGenerator; 18 | use Pyrech\ComposerChangelogs\UrlGenerator\GithubUrlGenerator; 19 | use Pyrech\ComposerChangelogs\UrlGenerator\GitlabUrlGenerator; 20 | use Pyrech\ComposerChangelogs\UrlGenerator\WordPressUrlGenerator; 21 | 22 | class Factory 23 | { 24 | /** 25 | * @return OperationHandler\OperationHandler[] 26 | */ 27 | public static function createOperationHandlers(): array 28 | { 29 | return [ 30 | new InstallHandler(), 31 | new UpdateHandler(), 32 | new UninstallHandler(), 33 | ]; 34 | } 35 | 36 | /** 37 | * @param string[] $gitlabHosts 38 | * 39 | * @return UrlGenerator\UrlGenerator[] 40 | */ 41 | public static function createUrlGenerators(array $gitlabHosts = []): array 42 | { 43 | $hosts = [ 44 | new GithubUrlGenerator(), 45 | new BitbucketUrlGenerator(), 46 | new WordPressUrlGenerator(), 47 | ]; 48 | 49 | foreach ($gitlabHosts as $gitlabHost) { 50 | $hosts[] = new GitlabUrlGenerator($gitlabHost); 51 | } 52 | 53 | return $hosts; 54 | } 55 | 56 | /** 57 | * @param string[] $gitlabHosts 58 | */ 59 | public static function createOutputter(array $gitlabHosts = []): Outputter 60 | { 61 | return new Outputter( 62 | self::createOperationHandlers(), 63 | self::createUrlGenerators($gitlabHosts) 64 | ); 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /src/Model/Config.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Pyrech\ComposerChangelogs\Model; 13 | 14 | class Config 15 | { 16 | private string $commitAuto; 17 | private ?string $commitBinFile; 18 | private string $commitMessage; 19 | /** @var string[] */ 20 | private array $gitlabHosts; 21 | private int $postUpdatePriority; 22 | 23 | /** 24 | * @param string[] $gitlabHosts 25 | */ 26 | public function __construct( 27 | string $commitAuto, 28 | ?string $commitBinFile, 29 | string $commitMessage, 30 | array $gitlabHosts, 31 | int $postUpdatePriority) 32 | { 33 | $this->commitAuto = $commitAuto; 34 | $this->commitBinFile = $commitBinFile; 35 | $this->commitMessage = $commitMessage; 36 | $this->gitlabHosts = $gitlabHosts; 37 | $this->postUpdatePriority = $postUpdatePriority; 38 | } 39 | 40 | public function getCommitAuto(): string 41 | { 42 | return $this->commitAuto; 43 | } 44 | 45 | public function getCommitBinFile(): ?string 46 | { 47 | return $this->commitBinFile; 48 | } 49 | 50 | public function getCommitMessage(): string 51 | { 52 | return $this->commitMessage; 53 | } 54 | 55 | /** 56 | * @return string[] 57 | */ 58 | public function getGitlabHosts(): array 59 | { 60 | return $this->gitlabHosts; 61 | } 62 | 63 | public function getPostUpdatePriority(): int 64 | { 65 | return $this->postUpdatePriority; 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /src/Model/Repository.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Pyrech\ComposerChangelogs\Model; 13 | 14 | class Repository 15 | { 16 | private string $user; 17 | private string $name; 18 | 19 | public function __construct(string $user, string $name) 20 | { 21 | $this->user = $user; 22 | $this->name = $name; 23 | } 24 | 25 | public function getUser(): string 26 | { 27 | return $this->user; 28 | } 29 | 30 | public function getName(): string 31 | { 32 | return $this->name; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/Model/Version.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Pyrech\ComposerChangelogs\Model; 13 | 14 | class Version 15 | { 16 | private string $name; 17 | private string $pretty; 18 | private string $fullPretty; 19 | 20 | public function __construct(string $name, string $pretty, string $fullPretty) 21 | { 22 | $this->name = $name; 23 | $this->pretty = $pretty; 24 | $this->fullPretty = $fullPretty; 25 | } 26 | 27 | public function getName(): string 28 | { 29 | return $this->name; 30 | } 31 | 32 | public function getPretty(): string 33 | { 34 | return $this->pretty; 35 | } 36 | 37 | public function getFullPretty(): string 38 | { 39 | return $this->fullPretty; 40 | } 41 | 42 | /** 43 | * Return whether the version is dev or not. 44 | */ 45 | public function isDev(): bool 46 | { 47 | return 'dev-' === substr($this->name, 0, 4) || '-dev' === substr($this->name, -4); 48 | } 49 | 50 | /** 51 | * Return the version string for CLI Output 52 | * In case of dev version it adds the vcs hash. 53 | */ 54 | public function getCliOutput(): string 55 | { 56 | $cliOutput = $this->getPretty(); 57 | if ($this->isDev()) { 58 | $hash = substr( 59 | $this->getFullPretty(), 60 | strlen($this->getPretty()) + 1 61 | ); 62 | if ($hash) { 63 | $cliOutput .= '@' . $hash; 64 | } 65 | } 66 | 67 | return $cliOutput; 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /src/OperationHandler/InstallHandler.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Pyrech\ComposerChangelogs\OperationHandler; 13 | 14 | use Composer\DependencyResolver\Operation\InstallOperation; 15 | use Composer\DependencyResolver\Operation\OperationInterface; 16 | use Pyrech\ComposerChangelogs\Model\Version; 17 | use Pyrech\ComposerChangelogs\UrlGenerator\UrlGenerator; 18 | 19 | class InstallHandler implements OperationHandler 20 | { 21 | public function supports(OperationInterface $operation): bool 22 | { 23 | return $operation instanceof InstallOperation; 24 | } 25 | 26 | public function extractSourceUrl(OperationInterface $operation): ?string 27 | { 28 | if (!($operation instanceof InstallOperation)) { 29 | throw new \LogicException('Operation should be an instance of InstallOperation'); 30 | } 31 | 32 | return $operation->getPackage()->getSourceUrl(); 33 | } 34 | 35 | public function getOutput(OperationInterface $operation, ?UrlGenerator $urlGenerator = null): array 36 | { 37 | if (!($operation instanceof InstallOperation)) { 38 | throw new \LogicException('Operation should be an instance of InstallOperation'); 39 | } 40 | 41 | $output = []; 42 | 43 | $package = $operation->getPackage(); 44 | $version = new Version( 45 | $package->getVersion(), 46 | $package->getPrettyVersion(), 47 | $package->getFullPrettyVersion() 48 | ); 49 | 50 | $output[] = sprintf( 51 | ' - %s installed in version %s', 52 | $package->getName(), 53 | $version->getCliOutput() 54 | ); 55 | 56 | if ($urlGenerator) { 57 | $releaseUrl = $urlGenerator->generateReleaseUrl( 58 | $this->extractSourceUrl($operation), 59 | $version 60 | ); 61 | 62 | if (!empty($releaseUrl)) { 63 | $output[] = sprintf( 64 | ' Release notes: %s', 65 | $releaseUrl 66 | ); 67 | } 68 | } 69 | 70 | return $output; 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /src/OperationHandler/OperationHandler.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Pyrech\ComposerChangelogs\OperationHandler; 13 | 14 | use Composer\DependencyResolver\Operation\OperationInterface; 15 | use Pyrech\ComposerChangelogs\UrlGenerator\UrlGenerator; 16 | 17 | interface OperationHandler 18 | { 19 | /** 20 | * Return whether the handler supports the given operation. 21 | */ 22 | public function supports(OperationInterface $operation): bool; 23 | 24 | /** 25 | * Extract the source url for the package related to the given operation. 26 | */ 27 | public function extractSourceUrl(OperationInterface $operation): ?string; 28 | 29 | /** 30 | * Generate output for the given operation, with some links generated by 31 | * the url generator. 32 | * 33 | * @return string[] 34 | */ 35 | public function getOutput(OperationInterface $operation, ?UrlGenerator $urlGenerator = null): array; 36 | } 37 | -------------------------------------------------------------------------------- /src/OperationHandler/UninstallHandler.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Pyrech\ComposerChangelogs\OperationHandler; 13 | 14 | use Composer\DependencyResolver\Operation\OperationInterface; 15 | use Composer\DependencyResolver\Operation\UninstallOperation; 16 | use Pyrech\ComposerChangelogs\Model\Version; 17 | use Pyrech\ComposerChangelogs\UrlGenerator\UrlGenerator; 18 | 19 | class UninstallHandler implements OperationHandler 20 | { 21 | public function supports(OperationInterface $operation): bool 22 | { 23 | return $operation instanceof UninstallOperation; 24 | } 25 | 26 | public function extractSourceUrl(OperationInterface $operation): ?string 27 | { 28 | if (!($operation instanceof UninstallOperation)) { 29 | throw new \LogicException('Operation should be an instance of UninstallOperation'); 30 | } 31 | 32 | return $operation->getPackage()->getSourceUrl(); 33 | } 34 | 35 | public function getOutput(OperationInterface $operation, ?UrlGenerator $urlGenerator = null): array 36 | { 37 | if (!($operation instanceof UninstallOperation)) { 38 | throw new \LogicException('Operation should be an instance of UninstallOperation'); 39 | } 40 | 41 | $output = []; 42 | 43 | $package = $operation->getPackage(); 44 | $version = new Version( 45 | $package->getVersion(), 46 | $package->getPrettyVersion(), 47 | $package->getFullPrettyVersion() 48 | ); 49 | 50 | $output[] = sprintf( 51 | ' - %s removed (installed version was %s)', 52 | $package->getName(), 53 | $version->getCliOutput() 54 | ); 55 | 56 | return $output; 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/OperationHandler/UpdateHandler.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Pyrech\ComposerChangelogs\OperationHandler; 13 | 14 | use Composer\DependencyResolver\Operation\OperationInterface; 15 | use Composer\DependencyResolver\Operation\UpdateOperation; 16 | use Composer\Semver\Comparator; 17 | use Pyrech\ComposerChangelogs\Model\Version; 18 | use Pyrech\ComposerChangelogs\UrlGenerator\UrlGenerator; 19 | 20 | class UpdateHandler implements OperationHandler 21 | { 22 | public function supports(OperationInterface $operation): bool 23 | { 24 | return $operation instanceof UpdateOperation; 25 | } 26 | 27 | public function extractSourceUrl(OperationInterface $operation): ?string 28 | { 29 | if (!($operation instanceof UpdateOperation)) { 30 | throw new \LogicException('Operation should be an instance of UpdateOperation'); 31 | } 32 | 33 | return $operation->getTargetPackage()->getSourceUrl(); 34 | } 35 | 36 | public function getOutput(OperationInterface $operation, ?UrlGenerator $urlGenerator = null): array 37 | { 38 | if (!($operation instanceof UpdateOperation)) { 39 | throw new \LogicException('Operation should be an instance of UpdateOperation'); 40 | } 41 | 42 | $output = []; 43 | 44 | $initialPackage = $operation->getInitialPackage(); 45 | $targetPackage = $operation->getTargetPackage(); 46 | 47 | $versionFrom = new Version( 48 | $initialPackage->getVersion(), 49 | $initialPackage->getPrettyVersion(), 50 | $initialPackage->getFullPrettyVersion() 51 | ); 52 | $versionTo = new Version( 53 | $targetPackage->getVersion(), 54 | $targetPackage->getPrettyVersion(), 55 | $targetPackage->getFullPrettyVersion() 56 | ); 57 | 58 | $action = 'updated'; 59 | 60 | if (Comparator::greaterThan($versionFrom->getName(), $versionTo->getName())) { 61 | $action = 'downgraded'; 62 | } 63 | 64 | $output[] = sprintf( 65 | ' - %s %s from %s to %s%s', 66 | $initialPackage->getName(), 67 | $action, 68 | $versionFrom->getCliOutput(), 69 | $versionTo->getCliOutput(), 70 | $this->getSemverOutput($versionFrom->getName(), $versionTo->getName()) 71 | ); 72 | 73 | if ($urlGenerator) { 74 | $compareUrl = $urlGenerator->generateCompareUrl( 75 | $initialPackage->getSourceUrl(), 76 | $versionFrom, 77 | $targetPackage->getSourceUrl(), 78 | $versionTo 79 | ); 80 | 81 | if (!empty($compareUrl)) { 82 | $output[] = sprintf( 83 | ' See changes: %s', 84 | $compareUrl 85 | ); 86 | } 87 | 88 | $releaseUrl = $urlGenerator->generateReleaseUrl( 89 | $this->extractSourceUrl($operation), 90 | $versionTo 91 | ); 92 | 93 | if (!empty($releaseUrl)) { 94 | $output[] = sprintf( 95 | ' Release notes: %s', 96 | $releaseUrl 97 | ); 98 | } 99 | } 100 | 101 | return $output; 102 | } 103 | 104 | private function getSemverOutput(string $versionFrom, string $versionTo): string 105 | { 106 | if (false === strpos($versionFrom, '.') && false === strpos($versionTo, '.')) { 107 | return ''; 108 | } 109 | 110 | $versionsFrom = \explode('.', $versionFrom); 111 | $versionsTo = \explode('.', $versionTo); 112 | 113 | if (version_compare($versionsFrom[0], $versionsTo[0], '!=')) { 114 | return ' major'; 115 | } 116 | 117 | if (version_compare($versionsFrom[0], $versionsTo[0], '==') && version_compare($versionsFrom[1], $versionsTo[1], '!=')) { 118 | return ' minor'; 119 | } 120 | 121 | return ' patch'; 122 | } 123 | } 124 | -------------------------------------------------------------------------------- /src/Outputter.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Pyrech\ComposerChangelogs; 13 | 14 | use Composer\DependencyResolver\Operation\OperationInterface; 15 | use Pyrech\ComposerChangelogs\OperationHandler\OperationHandler; 16 | use Pyrech\ComposerChangelogs\UrlGenerator\UrlGenerator; 17 | 18 | class Outputter 19 | { 20 | /** @var OperationHandler[] */ 21 | private array $operationHandlers; 22 | 23 | /** @var UrlGenerator[] */ 24 | private array $urlGenerators; 25 | 26 | /** @var OperationInterface[] */ 27 | private array $operations; 28 | 29 | /** 30 | * @param OperationHandler[] $operationHandlers 31 | * @param UrlGenerator[] $urlGenerators 32 | */ 33 | public function __construct(array $operationHandlers, array $urlGenerators) 34 | { 35 | $this->urlGenerators = $urlGenerators; 36 | $this->operationHandlers = $operationHandlers; 37 | $this->operations = []; 38 | } 39 | 40 | public function addOperation(OperationInterface $operation): void 41 | { 42 | $this->operations[] = $operation; 43 | } 44 | 45 | public function isEmpty(): bool 46 | { 47 | return empty($this->operations); 48 | } 49 | 50 | public function getOutput(): string 51 | { 52 | $output = []; 53 | 54 | if ($this->isEmpty()) { 55 | $output[] = 'No changelogs summary'; 56 | } else { 57 | $output[] = 'Changelogs summary:'; 58 | 59 | foreach ($this->operations as $operation) { 60 | $this->createOperationOutput($output, $operation); 61 | } 62 | 63 | $output[] = ''; 64 | } 65 | 66 | return implode("\n", $output); 67 | } 68 | 69 | /** 70 | * @param string[] $output 71 | */ 72 | private function createOperationOutput(array &$output, OperationInterface $operation): void 73 | { 74 | $operationHandler = $this->getOperationHandler($operation); 75 | 76 | if (!$operationHandler) { 77 | return; 78 | } 79 | 80 | $output[] = ''; 81 | 82 | $urlGenerator = $this->getUrlGenerator( 83 | $operationHandler->extractSourceUrl($operation) 84 | ); 85 | 86 | $output = array_merge( 87 | $output, 88 | $operationHandler->getOutput($operation, $urlGenerator) 89 | ); 90 | } 91 | 92 | private function getOperationHandler(OperationInterface $operation): ?OperationHandler 93 | { 94 | foreach ($this->operationHandlers as $operationHandler) { 95 | if ($operationHandler->supports($operation)) { 96 | return $operationHandler; 97 | } 98 | } 99 | 100 | return null; 101 | } 102 | 103 | private function getUrlGenerator(?string $sourceUrl): ?UrlGenerator 104 | { 105 | foreach ($this->urlGenerators as $urlGenerator) { 106 | if ($urlGenerator->supports($sourceUrl)) { 107 | return $urlGenerator; 108 | } 109 | } 110 | 111 | return null; 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /src/UrlGenerator/BitbucketUrlGenerator.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Pyrech\ComposerChangelogs\UrlGenerator; 13 | 14 | use Pyrech\ComposerChangelogs\Model\Version; 15 | 16 | class BitbucketUrlGenerator extends GitBasedUrlGenerator 17 | { 18 | protected function getDomain(): string 19 | { 20 | return 'bitbucket.org'; 21 | } 22 | 23 | public function generateCompareUrl(?string $sourceUrlFrom, Version $versionFrom, ?string $sourceUrlTo, Version $versionTo) 24 | { 25 | // Check if both urls come from the supported domain 26 | // It avoids problems when one url is from another domain or is local 27 | if (!$this->supports($sourceUrlFrom) || !$this->supports($sourceUrlTo)) { 28 | return false; 29 | } 30 | 31 | $sourceUrlFrom = $this->generateBaseUrl($sourceUrlFrom); 32 | $sourceUrlTo = $this->generateBaseUrl($sourceUrlTo); 33 | 34 | // Check if comparison across forks is needed 35 | if ($sourceUrlFrom !== $sourceUrlTo) { 36 | $repositoryFrom = $this->extractRepositoryInformation($sourceUrlFrom); 37 | $repositoryTo = $this->extractRepositoryInformation($sourceUrlTo); 38 | 39 | return sprintf( 40 | '%s/branches/compare/%s/%s:%s%%0D%s/%s:%s', 41 | $sourceUrlTo, 42 | $repositoryTo->getUser(), 43 | $repositoryTo->getName(), 44 | $this->getCompareVersion($versionTo), 45 | $repositoryFrom->getUser(), 46 | $repositoryFrom->getName(), 47 | $this->getCompareVersion($versionFrom) 48 | ); 49 | } 50 | 51 | return sprintf( 52 | '%s/branches/compare/%s%%0D%s', 53 | $sourceUrlTo, 54 | $this->getCompareVersion($versionTo), 55 | $this->getCompareVersion($versionFrom) 56 | ); 57 | } 58 | 59 | public function generateReleaseUrl(?string $sourceUrl, Version $version): bool 60 | { 61 | // Releases are not supported on Bitbucket :'( 62 | return false; 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /src/UrlGenerator/GitBasedUrlGenerator.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Pyrech\ComposerChangelogs\UrlGenerator; 13 | 14 | use Pyrech\ComposerChangelogs\Model\Repository; 15 | use Pyrech\ComposerChangelogs\Model\Version; 16 | 17 | abstract class GitBasedUrlGenerator implements UrlGenerator 18 | { 19 | public const REGEX_USER = '(?P[^/]+)'; 20 | public const REGEX_REPOSITORY = '(?P[^/]+)'; 21 | 22 | /** 23 | * Returns the domain of the service, like "example.org". 24 | */ 25 | abstract protected function getDomain(): string; 26 | 27 | public function supports(?string $sourceUrl): bool 28 | { 29 | return $sourceUrl && false !== strpos($sourceUrl, $this->getDomain()); 30 | } 31 | 32 | /** 33 | * Generates the canonical http url for a repository. 34 | * 35 | * It ensures there is no .git part in http url. It also supports ssh urls 36 | * by converting them in their http equivalent format. 37 | */ 38 | protected function generateBaseUrl(?string $sourceUrl): string 39 | { 40 | if (!$sourceUrl) { 41 | return ''; 42 | } 43 | 44 | if ($this->isSshUrl($sourceUrl)) { 45 | return $this->transformSshUrlIntoHttp($sourceUrl); 46 | } 47 | 48 | $sourceUrl = parse_url($sourceUrl); 49 | 50 | if (!isset($sourceUrl['scheme'], $sourceUrl['host'], $sourceUrl['path'])) { 51 | return ''; 52 | } 53 | 54 | $pos = strrpos($sourceUrl['path'], '.git'); 55 | 56 | return sprintf( 57 | '%s://%s%s', 58 | $sourceUrl['scheme'], 59 | $sourceUrl['host'], 60 | false === $pos ? $sourceUrl['path'] : substr($sourceUrl['path'], 0, (int) strrpos($sourceUrl['path'], '.git')) 61 | ); 62 | } 63 | 64 | /** 65 | * Get the version to use for the compare url. 66 | * 67 | * For dev versions, it returns the commit short hash in full pretty version. 68 | */ 69 | protected function getCompareVersion(Version $version): string 70 | { 71 | if ($version->isDev()) { 72 | return substr( 73 | $version->getFullPretty(), 74 | strlen($version->getPretty()) + 1 75 | ); 76 | } 77 | 78 | return $version->getPretty(); 79 | } 80 | 81 | /** 82 | * Extracts information like user and repository from the http url. 83 | */ 84 | protected function extractRepositoryInformation(string $sourceUrl): Repository 85 | { 86 | $pattern = '#' . $this->getDomain() . '/' . self::REGEX_USER . '/' . self::REGEX_REPOSITORY . '#'; 87 | 88 | preg_match($pattern, $sourceUrl, $matches); 89 | 90 | if (!isset($matches['user']) || !isset($matches['repository'])) { 91 | throw new \LogicException( 92 | sprintf('Unrecognized url format for %s ("%s")', $this->getDomain(), $sourceUrl) 93 | ); 94 | } 95 | 96 | return new Repository($matches['user'], $matches['repository']); 97 | } 98 | 99 | /** 100 | * Returns whether an url uses an ssh git protocol. 101 | */ 102 | private function isSshUrl(string $url): bool 103 | { 104 | return false !== strpos($url, 'git@'); 105 | } 106 | 107 | /** 108 | * Transform an ssh git url into an http one. 109 | */ 110 | private function transformSshUrlIntoHttp(string $url): string 111 | { 112 | $pattern = '#git@' . $this->getDomain() . ':' . self::REGEX_USER . '/' . self::REGEX_REPOSITORY . '.git$#'; 113 | 114 | if (preg_match($pattern, $url, $matches)) { 115 | return sprintf( 116 | 'https://%s/%s/%s', 117 | $this->getDomain(), 118 | $matches['user'], 119 | $matches['repository'] 120 | ); 121 | } 122 | 123 | return $url; 124 | } 125 | } 126 | -------------------------------------------------------------------------------- /src/UrlGenerator/GithubUrlGenerator.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Pyrech\ComposerChangelogs\UrlGenerator; 13 | 14 | use Pyrech\ComposerChangelogs\Model\Version; 15 | 16 | class GithubUrlGenerator extends GitBasedUrlGenerator 17 | { 18 | protected function getDomain(): string 19 | { 20 | return 'github.com'; 21 | } 22 | 23 | public function generateCompareUrl(?string $sourceUrlFrom, Version $versionFrom, ?string $sourceUrlTo, Version $versionTo) 24 | { 25 | // Check if both urls come from the supported domain 26 | // It avoids problems when one url is from another domain or is local 27 | if (!$this->supports($sourceUrlFrom) || !$this->supports($sourceUrlTo)) { 28 | return false; 29 | } 30 | 31 | $sourceUrlFrom = $this->generateBaseUrl($sourceUrlFrom); 32 | $sourceUrlTo = $this->generateBaseUrl($sourceUrlTo); 33 | 34 | // Check if comparison across forks is needed 35 | if ($sourceUrlFrom !== $sourceUrlTo) { 36 | $repositoryFrom = $this->extractRepositoryInformation($sourceUrlFrom); 37 | $repositoryTo = $this->extractRepositoryInformation($sourceUrlTo); 38 | 39 | return sprintf( 40 | '%s/compare/%s:%s...%s:%s', 41 | $sourceUrlTo, 42 | $repositoryFrom->getUser(), 43 | $this->getCompareVersion($versionFrom), 44 | $repositoryTo->getUser(), 45 | $this->getCompareVersion($versionTo) 46 | ); 47 | } 48 | 49 | return sprintf( 50 | '%s/compare/%s...%s', 51 | $sourceUrlTo, 52 | $this->getCompareVersion($versionFrom), 53 | $this->getCompareVersion($versionTo) 54 | ); 55 | } 56 | 57 | public function generateReleaseUrl(?string $sourceUrl, Version $version) 58 | { 59 | if ($version->isDev()) { 60 | return false; 61 | } 62 | 63 | $baseUrl = $this->generateBaseUrl($sourceUrl); 64 | 65 | if (!$baseUrl) { 66 | return false; 67 | } 68 | 69 | return sprintf( 70 | '%s/releases/tag/%s', 71 | $baseUrl, 72 | $version->getPretty() 73 | ); 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /src/UrlGenerator/GitlabUrlGenerator.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Pyrech\ComposerChangelogs\UrlGenerator; 13 | 14 | use Pyrech\ComposerChangelogs\Model\Version; 15 | 16 | class GitlabUrlGenerator extends GitBasedUrlGenerator 17 | { 18 | private string $host; 19 | 20 | public function __construct(string $host) 21 | { 22 | $this->host = $host; 23 | } 24 | 25 | protected function getDomain(): string 26 | { 27 | return $this->host; 28 | } 29 | 30 | public function generateCompareUrl(?string $sourceUrlFrom, Version $versionFrom, ?string $sourceUrlTo, Version $versionTo) 31 | { 32 | // Check if both urls come from the supported domain 33 | // It avoids problems when one url is from another domain or is local 34 | if (!$this->supports($sourceUrlFrom) || !$this->supports($sourceUrlTo)) { 35 | return false; 36 | } 37 | 38 | $sourceUrlFrom = $this->generateBaseUrl($sourceUrlFrom); 39 | $sourceUrlTo = $this->generateBaseUrl($sourceUrlTo); 40 | 41 | if ($sourceUrlFrom !== $sourceUrlTo) { 42 | // Comparison across forks is not supported 43 | return false; 44 | } 45 | 46 | return sprintf( 47 | '%s/compare/%s...%s', 48 | $sourceUrlTo, 49 | $this->getCompareVersion($versionFrom), 50 | $this->getCompareVersion($versionTo) 51 | ); 52 | } 53 | 54 | public function generateReleaseUrl(?string $sourceUrl, Version $version) 55 | { 56 | if ($version->isDev()) { 57 | return false; 58 | } 59 | 60 | $baseUrl = $this->generateBaseUrl($sourceUrl); 61 | 62 | if (!$baseUrl) { 63 | return false; 64 | } 65 | 66 | return sprintf( 67 | '%s/tags/%s', 68 | $this->generateBaseUrl($sourceUrl), 69 | $version->getPretty() 70 | ); 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /src/UrlGenerator/UrlGenerator.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Pyrech\ComposerChangelogs\UrlGenerator; 13 | 14 | use Pyrech\ComposerChangelogs\Model\Version; 15 | 16 | interface UrlGenerator 17 | { 18 | public function supports(?string $sourceUrl): bool; 19 | 20 | /** 21 | * Return the compare url for these versions or false if compare url is not 22 | * supported. 23 | * 24 | * In case the from and to source urls are different, this probably means 25 | * that an across fork compare url should be generated instead. 26 | * 27 | * @return string|false 28 | */ 29 | public function generateCompareUrl(?string $sourceUrlFrom, Version $versionFrom, ?string $sourceUrlTo, Version $versionTo); 30 | 31 | /** 32 | * Return the release url for the given version or false if compare url is 33 | * not supported. 34 | * 35 | * @return string|false 36 | */ 37 | public function generateReleaseUrl(?string $sourceUrl, Version $version); 38 | } 39 | -------------------------------------------------------------------------------- /src/UrlGenerator/WordPressUrlGenerator.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Pyrech\ComposerChangelogs\UrlGenerator; 13 | 14 | use Pyrech\ComposerChangelogs\Model\Version; 15 | 16 | /** 17 | * @author Sullivan Senechal 18 | */ 19 | class WordPressUrlGenerator implements UrlGenerator 20 | { 21 | public const DOMAIN = 'svn.wordpress.org'; 22 | 23 | public function supports(?string $sourceUrl): bool 24 | { 25 | return $sourceUrl && false !== strpos($sourceUrl, self::DOMAIN); 26 | } 27 | 28 | public function generateCompareUrl(?string $sourceUrlFrom, Version $versionFrom, ?string $sourceUrlTo, Version $versionTo) 29 | { 30 | if (!$sourceUrlTo) { 31 | return false; 32 | } 33 | 34 | if (preg_match('#plugins.svn.wordpress.org/(.*)/#', $sourceUrlTo, $matches)) { 35 | $plugin = $matches[1]; 36 | 37 | return sprintf('https://wordpress.org/plugins/%s/changelog/', $plugin); 38 | } 39 | 40 | if (preg_match('#themes.svn.wordpress.org/(.*)/#', $sourceUrlTo, $matches)) { 41 | $theme = $matches[1]; 42 | 43 | return sprintf('https://themes.trac.wordpress.org/log/%s/', $theme); 44 | } 45 | 46 | return false; 47 | } 48 | 49 | public function generateReleaseUrl(?string $sourceUrl, Version $version): bool 50 | { 51 | return false; 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/Util/FileSystemHelper.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Pyrech\ComposerChangelogs\Util; 13 | 14 | /** 15 | * Provides helper methods to ease file and path handling. 16 | */ 17 | class FileSystemHelper 18 | { 19 | /** 20 | * Inspired from Symfony Filesystem component. 21 | */ 22 | public static function isAbsolute(string $file): bool 23 | { 24 | return strspn($file, '/\\', 0, 1) 25 | || (strlen($file) > 3 && ctype_alpha($file[0]) 26 | && ':' === substr($file, 1, 1) 27 | && strspn($file, '/\\', 2, 1) 28 | ) 29 | || null !== parse_url($file, PHP_URL_SCHEME) 30 | ; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /tests/ChangelogsPluginTest.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Pyrech\ComposerChangelogs\tests; 13 | 14 | use Composer\Composer; 15 | use Composer\Config; 16 | use Composer\DependencyResolver\DefaultPolicy; 17 | use Composer\DependencyResolver\Operation\UpdateOperation; 18 | use Composer\DependencyResolver\Pool; 19 | use Composer\DependencyResolver\Request; 20 | use Composer\Factory; 21 | use Composer\Installer\PackageEvent; 22 | use Composer\Installer\PackageEvents; 23 | use Composer\IO\BufferIO; 24 | use Composer\Package\Package; 25 | use Composer\Package\RootPackage; 26 | use Composer\Plugin\PluginInterface; 27 | use Composer\Repository\CompositeRepository; 28 | use Composer\Repository\RepositoryInterface; 29 | use Composer\Script\ScriptEvents; 30 | use PHPUnit\Framework\TestCase; 31 | use Pyrech\ComposerChangelogs\ChangelogsPlugin; 32 | 33 | class ChangelogsPluginTest extends TestCase 34 | { 35 | /** @var BufferIO */ 36 | private $io; 37 | 38 | /** @var Composer */ 39 | private $composer; 40 | 41 | /** @var Config */ 42 | private $config; 43 | 44 | /** @var string */ 45 | private $tempDir; 46 | 47 | /** 48 | * {@inheritdoc} 49 | */ 50 | protected function setUp(): void 51 | { 52 | $this->tempDir = __DIR__ . '/temp'; 53 | $this->config = new Config(false, realpath(__DIR__ . '/fixtures/local')); 54 | $this->config->merge([ 55 | 'config' => [ 56 | 'home' => __DIR__, 57 | ], 58 | ]); 59 | 60 | $this->io = new BufferIO(); 61 | 62 | $this->composer = Factory::create($this->io, null, false); 63 | $this->composer->setConfig($this->config); 64 | $this->composer->setPackage(new RootPackage('my/project', '1.0.0', '1.0.0')); 65 | 66 | self::cleanTempDir(); 67 | mkdir($this->tempDir); 68 | } 69 | 70 | /** 71 | * {@inheritdoc} 72 | */ 73 | protected function tearDown(): void 74 | { 75 | self::cleanTempDir(); 76 | } 77 | 78 | /** 79 | * Completely remove the temp dir and its content if it exists. 80 | */ 81 | private function cleanTempDir() 82 | { 83 | if (!is_dir($this->tempDir)) { 84 | return; 85 | } 86 | $files = glob($this->tempDir . '/*'); 87 | foreach ($files as $file) { 88 | unlink($file); 89 | } 90 | rmdir($this->tempDir); 91 | } 92 | 93 | public function testItIsRegisteredAndActivated() 94 | { 95 | $plugin = new ChangelogsPlugin(); 96 | 97 | $this->addComposerPlugin($plugin); 98 | 99 | $this->assertSame([$plugin], $this->composer->getPluginManager()->getPlugins()); 100 | } 101 | 102 | public function testItReceivesEvent() 103 | { 104 | $this->addComposerPlugin(new ChangelogsPlugin()); 105 | 106 | $operation = $this->getUpdateOperation(); 107 | 108 | $this->dispatchPostPackageUpdateEvent($operation); 109 | 110 | $this->composer->getEventDispatcher()->dispatchScript(ScriptEvents::POST_UPDATE_CMD); 111 | 112 | $expectedOutput = <<assertSame($expectedOutput, $this->io->getOutput()); 123 | } 124 | 125 | public function testEventsAreHandled() 126 | { 127 | $plugin = new ChangelogsPlugin(); 128 | $plugin->activate($this->composer, $this->io); 129 | 130 | $operation = $this->getUpdateOperation(); 131 | 132 | $packageEvent = $this->createPostPackageUpdateEvent($operation); 133 | 134 | $plugin->postPackageOperation($packageEvent); 135 | 136 | $plugin->postUpdate(); 137 | 138 | $expectedOutput = <<assertSame($expectedOutput, $this->io->getOutput()); 149 | } 150 | 151 | public function testPostUpdateEventPriorityIsHandled() 152 | { 153 | $this->config->merge([ 154 | 'config' => [ 155 | 'home' => realpath(__DIR__ . '/fixtures/other-post-update-priority'), 156 | ], 157 | ]); 158 | 159 | $this->addComposerPlugin(new ChangelogsPlugin()); 160 | 161 | $eventDispatcherReflection = new \ReflectionClass($this->composer->getEventDispatcher()); 162 | $eventListenerReflection = $eventDispatcherReflection->getProperty('listeners'); 163 | $eventListenerReflection->setAccessible(true); 164 | $eventListeners = $eventListenerReflection->getValue($this->composer->getEventDispatcher()); 165 | 166 | $this->assertArrayHasKey(ScriptEvents::POST_UPDATE_CMD, $eventListeners); 167 | $this->assertArrayHasKey(-1337, $eventListeners[ScriptEvents::POST_UPDATE_CMD]); 168 | } 169 | 170 | public function testItCommitsWithAlwaysOption() 171 | { 172 | $this->config->merge([ 173 | 'config' => [ 174 | 'home' => realpath(__DIR__ . '/fixtures/home'), 175 | ], 176 | ]); 177 | 178 | $plugin = new ChangelogsPlugin(); 179 | $plugin->activate($this->composer, $this->io); 180 | 181 | $operation = $this->getUpdateOperation(); 182 | 183 | $packageEvent = $this->createPostPackageUpdateEvent($operation); 184 | 185 | $plugin->postPackageOperation($packageEvent); 186 | 187 | $plugin->postUpdate(); 188 | 189 | $this->assertStringMatchesFormat('%aExecuting following command: %s/tests/fixtures/bin/fake.sh \'%s\' \'%s/composer-changelogs-%s\'', $this->io->getOutput()); 190 | } 191 | 192 | public function testItCommitsWithDefaultCommitMessage() 193 | { 194 | $this->config->merge([ 195 | 'config' => [ 196 | 'home' => realpath(__DIR__ . '/fixtures/home'), 197 | ], 198 | ]); 199 | 200 | $plugin = new ChangelogsPlugin(); 201 | $plugin->activate($this->composer, $this->io); 202 | 203 | $operation = $this->getUpdateOperation(); 204 | 205 | $packageEvent = $this->createPostPackageUpdateEvent($operation); 206 | 207 | $plugin->postPackageOperation($packageEvent); 208 | 209 | $plugin->postUpdate(); 210 | 211 | $this->assertFileExists($this->tempDir . '/commit-message.txt'); 212 | $commitMessage = file_get_contents($this->tempDir . '/commit-message.txt'); 213 | $this->assertStringMatchesFormat('Update dependencies%aChangelogs summary:%a', $commitMessage); 214 | } 215 | 216 | public function testItCommitsWithCustomCommitMessage() 217 | { 218 | $this->config->merge([ 219 | 'config' => [ 220 | 'home' => realpath(__DIR__ . '/fixtures/home-commit-message'), 221 | ], 222 | ]); 223 | 224 | $plugin = new ChangelogsPlugin(); 225 | $plugin->activate($this->composer, $this->io); 226 | 227 | $operation = $this->getUpdateOperation(); 228 | 229 | $packageEvent = $this->createPostPackageUpdateEvent($operation); 230 | 231 | $plugin->postPackageOperation($packageEvent); 232 | 233 | $plugin->postUpdate(); 234 | 235 | $this->assertFileExists($this->tempDir . '/commit-message.txt'); 236 | $commitMessage = file_get_contents($this->tempDir . '/commit-message.txt'); 237 | $this->assertStringMatchesFormat('chore: Update composer%aChangelogs summary:%a', $commitMessage); 238 | } 239 | 240 | private function addComposerPlugin(PluginInterface $plugin) 241 | { 242 | $sourcePackage = new Package('pyrech/composer-changelogs', '1', 'v1'); 243 | 244 | $pluginManager = $this->composer->getPluginManager(); 245 | $pluginManager->addPlugin($plugin, false, $sourcePackage); 246 | } 247 | 248 | /** 249 | * @return UpdateOperation 250 | */ 251 | private function getUpdateOperation() 252 | { 253 | $initialPackage = new Package('foo/bar', '1.0.0.0', 'v1.0.0'); 254 | $initialPackage->setSourceUrl('https://github.com/foo/bar.git'); 255 | 256 | $targetPackage = new Package('foo/bar', '1.0.1.0', 'v1.0.1'); 257 | $targetPackage->setSourceUrl('https://github.com/foo/bar.git'); 258 | 259 | return new UpdateOperation($initialPackage, $targetPackage); 260 | } 261 | 262 | private function createPostPackageUpdateEvent($operation) 263 | { 264 | if (version_compare(PluginInterface::PLUGIN_API_VERSION, '2.0.0') >= 0) { 265 | return new PackageEvent( 266 | PackageEvents::POST_PACKAGE_UPDATE, 267 | $this->composer, 268 | $this->io, 269 | false, 270 | $this->createMock(RepositoryInterface::class), 271 | [$operation], 272 | $operation 273 | ); 274 | } 275 | 276 | return new PackageEvent( 277 | PackageEvents::POST_PACKAGE_UPDATE, 278 | $this->composer, 279 | $this->io, 280 | false, 281 | new DefaultPolicy(false, false), 282 | new Pool(), 283 | new CompositeRepository([]), 284 | new Request(new Pool()), 285 | [$operation], 286 | $operation 287 | ); 288 | } 289 | 290 | private function dispatchPostPackageUpdateEvent($operation) 291 | { 292 | if (version_compare(PluginInterface::PLUGIN_API_VERSION, '2.0.0') >= 0) { 293 | $this->composer->getEventDispatcher()->dispatchPackageEvent( 294 | PackageEvents::POST_PACKAGE_UPDATE, 295 | false, 296 | $this->createMock(RepositoryInterface::class), 297 | [$operation], 298 | $operation 299 | ); 300 | 301 | return; 302 | } 303 | 304 | $this->composer->getEventDispatcher()->dispatchPackageEvent( 305 | PackageEvents::POST_PACKAGE_UPDATE, 306 | false, 307 | new DefaultPolicy(false, false), 308 | new Pool(), 309 | new CompositeRepository([]), 310 | new Request(new Pool()), 311 | [$operation], 312 | $operation 313 | ); 314 | } 315 | } 316 | -------------------------------------------------------------------------------- /tests/Config/ConfigBuilderTest.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Pyrech\ComposerChangelogs\tests\Config; 13 | 14 | use PHPUnit\Framework\TestCase; 15 | use Pyrech\ComposerChangelogs\Config\ConfigBuilder; 16 | 17 | class ConfigBuilderTest extends TestCase 18 | { 19 | public const COMMIT_BIN_FILE = '../fixtures/bin/fake.sh'; 20 | 21 | /** @var string */ 22 | private $absoluteCommitBinFile; 23 | 24 | /** @var ConfigBuilder */ 25 | private $SUT; 26 | 27 | protected function setUp(): void 28 | { 29 | $this->absoluteCommitBinFile = realpath(__DIR__ . '/' . self::COMMIT_BIN_FILE); 30 | $this->SUT = new ConfigBuilder(); 31 | } 32 | 33 | public function testItHasADefaultSetup() 34 | { 35 | $extra = []; 36 | 37 | $config = $this->SUT->build($extra, __DIR__); 38 | 39 | $this->assertInstanceOf('Pyrech\ComposerChangelogs\Model\Config', $config); 40 | $this->assertSame('never', $config->getCommitAuto()); 41 | $this->assertNull($config->getCommitBinFile()); 42 | $this->assertEmpty($config->getGitlabHosts()); 43 | $this->assertEquals(-1, $config->getPostUpdatePriority()); 44 | 45 | $this->assertCount(0, $this->SUT->getWarnings()); 46 | } 47 | 48 | public function testItWarnsWhenCommitAutoOptionIsInvalid() 49 | { 50 | $extra = [ 51 | 'commit-auto' => 'foo', 52 | ]; 53 | 54 | $config = $this->SUT->build($extra, __DIR__); 55 | 56 | $this->assertInstanceOf('Pyrech\ComposerChangelogs\Model\Config', $config); 57 | $this->assertSame('never', $config->getCommitAuto()); 58 | $this->assertNull($config->getCommitBinFile()); 59 | $this->assertEmpty($config->getGitlabHosts()); 60 | $this->assertEquals(-1, $config->getPostUpdatePriority()); 61 | 62 | $this->assertCount(1, $this->SUT->getWarnings()); 63 | $this->assertStringContainsString('Invalid value "foo" for option "commit-auto"', $this->SUT->getWarnings()[0]); 64 | } 65 | 66 | public function testItWarnsWhenSpecifyingCommitBinFileAndNeverAutoCommit() 67 | { 68 | $extra = [ 69 | 'commit-auto' => 'never', 70 | 'commit-bin-file' => self::COMMIT_BIN_FILE, 71 | ]; 72 | 73 | $config = $this->SUT->build($extra, __DIR__); 74 | 75 | $this->assertInstanceOf('Pyrech\ComposerChangelogs\Model\Config', $config); 76 | $this->assertSame('never', $config->getCommitAuto()); 77 | $this->assertNull($config->getCommitBinFile()); 78 | $this->assertEmpty($config->getGitlabHosts()); 79 | $this->assertEquals(-1, $config->getPostUpdatePriority()); 80 | 81 | $this->assertCount(1, $this->SUT->getWarnings()); 82 | $this->assertStringContainsString('"commit-bin-file" is specified but "commit-auto" option is set to "never". Ignoring.', $this->SUT->getWarnings()[0]); 83 | } 84 | 85 | public function testItWarnsWhenSpecifiedCommitBinFileWasNotFound() 86 | { 87 | $extra = [ 88 | 'commit-auto' => 'always', 89 | 'commit-bin-file' => '/tmp/toto', 90 | ]; 91 | 92 | $config = $this->SUT->build($extra, __DIR__); 93 | 94 | $this->assertInstanceOf('Pyrech\ComposerChangelogs\Model\Config', $config); 95 | $this->assertSame('always', $config->getCommitAuto()); 96 | $this->assertNull($config->getCommitBinFile()); 97 | $this->assertEmpty($config->getGitlabHosts()); 98 | $this->assertEquals(-1, $config->getPostUpdatePriority()); 99 | 100 | $this->assertCount(1, $this->SUT->getWarnings()); 101 | $this->assertStringContainsString('The file pointed by the option "commit-bin-file" was not found. Ignoring.', $this->SUT->getWarnings()[0]); 102 | } 103 | 104 | public function testItWarnsWhenCommitBinFileShouldHaveBeenSpecified() 105 | { 106 | $extra = [ 107 | 'commit-auto' => 'ask', 108 | ]; 109 | 110 | $config = $this->SUT->build($extra, __DIR__); 111 | 112 | $this->assertInstanceOf('Pyrech\ComposerChangelogs\Model\Config', $config); 113 | $this->assertSame('ask', $config->getCommitAuto()); 114 | $this->assertNull($config->getCommitBinFile()); 115 | $this->assertEmpty($config->getGitlabHosts()); 116 | $this->assertEquals(-1, $config->getPostUpdatePriority()); 117 | 118 | $this->assertCount(1, $this->SUT->getWarnings()); 119 | $this->assertStringContainsString('"commit-auto" is set to "ask" but "commit-bin-file" was not specified.', $this->SUT->getWarnings()[0]); 120 | } 121 | 122 | public function testItWarnsWhenCommitEventPriorityValueIsInvalid() 123 | { 124 | $extra = [ 125 | 'post-update-priority' => 'invalid-priority', 126 | ]; 127 | 128 | $config = $this->SUT->build($extra, __DIR__); 129 | 130 | $this->assertInstanceOf('Pyrech\ComposerChangelogs\Model\Config', $config); 131 | $this->assertSame('never', $config->getCommitAuto()); 132 | $this->assertNull($config->getCommitBinFile()); 133 | $this->assertEmpty($config->getGitlabHosts()); 134 | $this->assertEquals(-1, $config->getPostUpdatePriority()); 135 | 136 | $this->assertCount(1, $this->SUT->getWarnings()); 137 | $this->assertStringContainsString('"post-update-priority" is specified but not an integer. Ignoring and using default commit event priority.', $this->SUT->getWarnings()[0]); 138 | } 139 | 140 | public function testItWarnsWhenGitlabHostsIsNotAnArray() 141 | { 142 | $extra = [ 143 | 'gitlab-hosts' => 'gitlab.company1.com', 144 | ]; 145 | 146 | $config = $this->SUT->build($extra, __DIR__); 147 | 148 | $this->assertInstanceOf('Pyrech\ComposerChangelogs\Model\Config', $config); 149 | $this->assertSame('never', $config->getCommitAuto()); 150 | $this->assertNull($config->getCommitBinFile()); 151 | $this->assertEmpty($config->getGitlabHosts()); 152 | $this->assertEquals(-1, $config->getPostUpdatePriority()); 153 | 154 | $this->assertCount(1, $this->SUT->getWarnings()); 155 | $this->assertStringContainsString('"gitlab-hosts" is specified but should be an array. Ignoring.', $this->SUT->getWarnings()[0]); 156 | } 157 | 158 | public function testItAcceptsValidSetup() 159 | { 160 | $extra = [ 161 | 'commit-auto' => 'ask', 162 | 'commit-bin-file' => self::COMMIT_BIN_FILE, 163 | 'gitlab-hosts' => ['gitlab.company1.com', 'gitlab.company2.com'], 164 | 'post-update-priority' => '-1337', 165 | ]; 166 | 167 | $config = $this->SUT->build($extra, __DIR__); 168 | 169 | $this->assertInstanceOf('Pyrech\ComposerChangelogs\Model\Config', $config); 170 | $this->assertSame('ask', $config->getCommitAuto()); 171 | $this->assertSame($this->absoluteCommitBinFile, $config->getCommitBinFile()); 172 | $this->assertCount(2, $config->getGitlabHosts()); 173 | $this->assertEquals(-1337, $config->getPostUpdatePriority()); 174 | 175 | $this->assertCount(0, $this->SUT->getWarnings()); 176 | } 177 | } 178 | -------------------------------------------------------------------------------- /tests/Config/ConfigLocatorTest.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Pyrech\ComposerChangelogs\tests; 13 | 14 | use Composer\Composer; 15 | use Composer\Config; 16 | use Composer\Package\RootPackage; 17 | use PHPUnit\Framework\TestCase; 18 | use Pyrech\ComposerChangelogs\Config\ConfigLocator; 19 | 20 | class ConfigLocatorTest extends TestCase 21 | { 22 | /** @var string */ 23 | private $localConfigPath; 24 | 25 | /** @var string */ 26 | private $globalConfigPath; 27 | 28 | /** @var Config */ 29 | private $config; 30 | 31 | /** @var Composer */ 32 | private $composer; 33 | 34 | /** @var ConfigLocator */ 35 | private $SUT; 36 | 37 | /** 38 | * {@inheritdoc} 39 | */ 40 | protected function setUp(): void 41 | { 42 | $this->localConfigPath = realpath(__DIR__ . '/../fixtures/local'); 43 | $this->globalConfigPath = realpath(__DIR__ . '/../fixtures/home'); 44 | 45 | $this->config = new Config(false, $this->localConfigPath); 46 | $this->config->merge([ 47 | 'config' => [ 48 | 'home' => $this->globalConfigPath, 49 | ], 50 | ]); 51 | 52 | $package = new RootPackage('my/project', '1.0.0', '1.0.0'); 53 | $package->setExtra([ 54 | 'my-local-config' => [ 55 | 'foo' => 'bar', 56 | ], 57 | ]); 58 | 59 | $this->composer = new Composer(); 60 | $this->composer->setConfig($this->config); 61 | $this->composer->setPackage($package); 62 | 63 | $this->SUT = new ConfigLocator($this->composer); 64 | } 65 | 66 | public function testItLocatesLocalConfig() 67 | { 68 | $key = 'my-local-config'; 69 | 70 | $this->assertTrue($this->SUT->locate($key)); 71 | 72 | $this->assertSame($this->localConfigPath, $this->SUT->getPath($key)); 73 | $this->assertSame(['foo' => 'bar'], $this->SUT->getConfig($key)); 74 | } 75 | 76 | public function testItLocatesGlobalConfig() 77 | { 78 | $key = 'my-global-config'; 79 | 80 | $this->assertTrue($this->SUT->locate($key)); 81 | 82 | $this->assertSame($this->globalConfigPath, $this->SUT->getPath($key)); 83 | $this->assertSame(['bar' => 'foo'], $this->SUT->getConfig($key)); 84 | } 85 | 86 | public function testItDoesNotLocateNonExistingConfig() 87 | { 88 | $key = 'my-non-existing-config'; 89 | 90 | $this->assertFalse($this->SUT->locate($key)); 91 | 92 | $this->assertNull($this->SUT->getPath($key)); 93 | $this->assertSame([], $this->SUT->getConfig($key)); 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /tests/OperationHandler/InstallHandlerTest.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Pyrech\ComposerChangelogs\tests\OperationHandler; 13 | 14 | use Composer\DependencyResolver\Operation\InstallOperation; 15 | use Composer\Package\Package; 16 | use PHPUnit\Framework\TestCase; 17 | use Pyrech\ComposerChangelogs\OperationHandler\InstallHandler; 18 | use Pyrech\ComposerChangelogs\tests\resources\FakeOperation; 19 | use Pyrech\ComposerChangelogs\tests\resources\FakeUrlGenerator; 20 | 21 | class InstallHandlerTest extends TestCase 22 | { 23 | /** @var InstallHandler */ 24 | private $SUT; 25 | 26 | protected function setUp(): void 27 | { 28 | $this->SUT = new InstallHandler(); 29 | } 30 | 31 | public function testItSupportsInstallOperation() 32 | { 33 | $operation = new InstallOperation( 34 | new Package('acme/my-project', 'v1.0.0.0', 'v1.0.0') 35 | ); 36 | 37 | $this->assertTrue($this->SUT->supports($operation)); 38 | } 39 | 40 | public function testItDoesNotSupportNonInstallOperation() 41 | { 42 | $this->assertFalse($this->SUT->supports(new FakeOperation(''))); 43 | } 44 | 45 | public function testItExtractsSourceUrl() 46 | { 47 | $package = new Package('acme/my-project', 'v1.0.0.0', 'v1.0.0'); 48 | $package->setSourceUrl('https://example.com/acme/my-project.git'); 49 | 50 | $operation = new InstallOperation($package); 51 | 52 | $this->assertSame( 53 | 'https://example.com/acme/my-project.git', 54 | $this->SUT->extractSourceUrl($operation) 55 | ); 56 | } 57 | 58 | public function testItThrowsExceptionWhenExtractingSourceUrlFromNonInstallOperation() 59 | { 60 | $this->expectException(\LogicException::class); 61 | $this->expectExceptionMessage('Operation should be an instance of InstallOperation'); 62 | 63 | $this->SUT->extractSourceUrl(new FakeOperation('')); 64 | } 65 | 66 | public function testItGetsOutputWithoutUrlGenerator() 67 | { 68 | $package = new Package('acme/my-project', 'v1.0.0.0', 'v1.0.0'); 69 | $package->setSourceUrl('https://example.com/acme/my-project.git'); 70 | 71 | $operation = new InstallOperation($package); 72 | 73 | $expectedOutput = [ 74 | ' - acme/my-project installed in version v1.0.0', 75 | ]; 76 | 77 | $this->assertSame( 78 | $expectedOutput, 79 | $this->SUT->getOutput($operation, null) 80 | ); 81 | } 82 | 83 | public function testItGetsOutputWithUrlGeneratorNoSupportingCompareUrl() 84 | { 85 | $operation = new InstallOperation( 86 | new Package('acme/my-project', 'v1.0.0.0', 'v1.0.0') 87 | ); 88 | 89 | $urlGenerator = new FakeUrlGenerator( 90 | true, 91 | false, 92 | 'https://example.com/acme/my-project/release/v1.0.1' 93 | ); 94 | 95 | $expectedOutput = [ 96 | ' - acme/my-project installed in version v1.0.0', 97 | ' Release notes: https://example.com/acme/my-project/release/v1.0.1', 98 | ]; 99 | 100 | $this->assertSame( 101 | $expectedOutput, 102 | $this->SUT->getOutput($operation, $urlGenerator) 103 | ); 104 | } 105 | 106 | public function testItGetsOutputWithUrlGeneratorNoSupportingReleaseUrl() 107 | { 108 | $operation = new InstallOperation( 109 | new Package('acme/my-project', 'v1.0.0.0', 'v1.0.0') 110 | ); 111 | 112 | $urlGenerator = new FakeUrlGenerator( 113 | true, 114 | 'https://example.com/acme/my-project/compare/v1.0.0/v1.0.1', 115 | false 116 | ); 117 | 118 | $expectedOutput = [ 119 | ' - acme/my-project installed in version v1.0.0', 120 | ]; 121 | 122 | $this->assertSame( 123 | $expectedOutput, 124 | $this->SUT->getOutput($operation, $urlGenerator) 125 | ); 126 | } 127 | 128 | public function testItGetsOutputWithUrlGeneratorSupportingAllUrls() 129 | { 130 | $operation = new InstallOperation( 131 | new Package('acme/my-project', 'v1.0.0.0', 'v1.0.0') 132 | ); 133 | 134 | $urlGenerator = new FakeUrlGenerator( 135 | true, 136 | 'https://example.com/acme/my-project/compare/v1.0.0/v1.0.1', 137 | 'https://example.com/acme/my-project/release/v1.0.1' 138 | ); 139 | 140 | $expectedOutput = [ 141 | ' - acme/my-project installed in version v1.0.0', 142 | ' Release notes: https://example.com/acme/my-project/release/v1.0.1', 143 | ]; 144 | 145 | $this->assertSame( 146 | $expectedOutput, 147 | $this->SUT->getOutput($operation, $urlGenerator) 148 | ); 149 | } 150 | 151 | public function testItThrowsExceptionWhenGettingOutputFromNonInstallOperation() 152 | { 153 | $this->expectException(\LogicException::class); 154 | $this->expectExceptionMessage('Operation should be an instance of InstallOperation'); 155 | 156 | $this->SUT->getOutput(new FakeOperation('')); 157 | } 158 | } 159 | -------------------------------------------------------------------------------- /tests/OperationHandler/UninstallHandlerTest.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Pyrech\ComposerChangelogs\tests\OperationHandler; 13 | 14 | use Composer\DependencyResolver\Operation\UninstallOperation; 15 | use Composer\Package\Package; 16 | use PHPUnit\Framework\TestCase; 17 | use Pyrech\ComposerChangelogs\OperationHandler\UninstallHandler; 18 | use Pyrech\ComposerChangelogs\tests\resources\FakeOperation; 19 | use Pyrech\ComposerChangelogs\tests\resources\FakeUrlGenerator; 20 | 21 | class UninstallHandlerTest extends TestCase 22 | { 23 | /** @var UninstallHandler */ 24 | private $SUT; 25 | 26 | protected function setUp(): void 27 | { 28 | $this->SUT = new UninstallHandler(); 29 | } 30 | 31 | public function testItSupportsUninstallOperation() 32 | { 33 | $operation = new UninstallOperation( 34 | new Package('acme/my-project', 'v1.0.0.0', 'v1.0.0') 35 | ); 36 | 37 | $this->assertTrue($this->SUT->supports($operation)); 38 | } 39 | 40 | public function testItDoesNotSupportNonUninstallOperation() 41 | { 42 | $this->assertFalse($this->SUT->supports(new FakeOperation(''))); 43 | } 44 | 45 | public function testItExtractsSourceUrl() 46 | { 47 | $package = new Package('acme/my-project', 'v1.0.0.0', 'v1.0.0'); 48 | $package->setSourceUrl('https://example.com/acme/my-project.git'); 49 | 50 | $operation = new UninstallOperation($package); 51 | 52 | $this->assertSame( 53 | 'https://example.com/acme/my-project.git', 54 | $this->SUT->extractSourceUrl($operation) 55 | ); 56 | } 57 | 58 | public function testItThrowsExceptionWhenExtractingSourceUrlFromNonUninstallOperation() 59 | { 60 | $this->expectException(\LogicException::class); 61 | $this->expectExceptionMessage('Operation should be an instance of UninstallOperation'); 62 | 63 | $this->SUT->extractSourceUrl(new FakeOperation('')); 64 | } 65 | 66 | public function testItGetsOutputWithoutUrlGenerator() 67 | { 68 | $package = new Package('acme/my-project', 'v1.0.0.0', 'v1.0.0'); 69 | $package->setSourceUrl('https://example.com/acme/my-project.git'); 70 | 71 | $operation = new UninstallOperation($package); 72 | 73 | $expectedOutput = [ 74 | ' - acme/my-project removed (installed version was v1.0.0)', 75 | ]; 76 | 77 | $this->assertSame( 78 | $expectedOutput, 79 | $this->SUT->getOutput($operation, null) 80 | ); 81 | } 82 | 83 | public function testItGetsOutputWithUrlGeneratorNoSupportingCompareUrl() 84 | { 85 | $operation = new UninstallOperation( 86 | new Package('acme/my-project', 'v1.0.0.0', 'v1.0.0') 87 | ); 88 | 89 | $urlGenerator = new FakeUrlGenerator( 90 | true, 91 | false, 92 | 'https://example.com/acme/my-project/release/v1.0.1' 93 | ); 94 | 95 | $expectedOutput = [ 96 | ' - acme/my-project removed (installed version was v1.0.0)', 97 | ]; 98 | 99 | $this->assertSame( 100 | $expectedOutput, 101 | $this->SUT->getOutput($operation, $urlGenerator) 102 | ); 103 | } 104 | 105 | public function testItGetsOutputWithUrlGeneratorNoSupportingReleaseUrl() 106 | { 107 | $operation = new UninstallOperation( 108 | new Package('acme/my-project', 'v1.0.0.0', 'v1.0.0') 109 | ); 110 | 111 | $urlGenerator = new FakeUrlGenerator( 112 | true, 113 | 'https://example.com/acme/my-project/compare/v1.0.0/v1.0.1', 114 | false 115 | ); 116 | 117 | $expectedOutput = [ 118 | ' - acme/my-project removed (installed version was v1.0.0)', 119 | ]; 120 | 121 | $this->assertSame( 122 | $expectedOutput, 123 | $this->SUT->getOutput($operation, $urlGenerator) 124 | ); 125 | } 126 | 127 | public function testItGetsOutputWithUrlGeneratorSupportingAllUrls() 128 | { 129 | $operation = new UninstallOperation( 130 | new Package('acme/my-project', 'v1.0.0.0', 'v1.0.0') 131 | ); 132 | 133 | $urlGenerator = new FakeUrlGenerator( 134 | true, 135 | 'https://example.com/acme/my-project/compare/v1.0.0/v1.0.1', 136 | 'https://example.com/acme/my-project/release/v1.0.1' 137 | ); 138 | 139 | $expectedOutput = [ 140 | ' - acme/my-project removed (installed version was v1.0.0)', 141 | ]; 142 | 143 | $this->assertSame( 144 | $expectedOutput, 145 | $this->SUT->getOutput($operation, $urlGenerator) 146 | ); 147 | } 148 | 149 | public function testItThrowsExceptionWhenGettingOutputFromNonUninstallOperation() 150 | { 151 | $this->expectException(\LogicException::class); 152 | $this->expectExceptionMessage('Operation should be an instance of UninstallOperation'); 153 | 154 | $this->SUT->getOutput(new FakeOperation('')); 155 | } 156 | } 157 | -------------------------------------------------------------------------------- /tests/OperationHandler/UpdateHandlerTest.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Pyrech\ComposerChangelogs\tests\OperationHandler; 13 | 14 | use Composer\DependencyResolver\Operation\UpdateOperation; 15 | use Composer\Package\Package; 16 | use PHPUnit\Framework\TestCase; 17 | use Pyrech\ComposerChangelogs\OperationHandler\UpdateHandler; 18 | use Pyrech\ComposerChangelogs\tests\resources\FakeOperation; 19 | use Pyrech\ComposerChangelogs\tests\resources\FakeUrlGenerator; 20 | 21 | class UpdateHandlerTest extends TestCase 22 | { 23 | /** @var UpdateHandler */ 24 | private $SUT; 25 | 26 | protected function setUp(): void 27 | { 28 | $this->SUT = new UpdateHandler(); 29 | } 30 | 31 | public function testItSupportsUpdateOperation() 32 | { 33 | $operation = new UpdateOperation( 34 | new Package('acme/my-project', 'v1.0.0.0', 'v1.0.0'), 35 | new Package('acme/my-project', 'v1.0.1.0', 'v1.0.1') 36 | ); 37 | 38 | $this->assertTrue($this->SUT->supports($operation)); 39 | } 40 | 41 | public function testItDoesNotSupportNonUpdateOperation() 42 | { 43 | $this->assertFalse($this->SUT->supports(new FakeOperation(''))); 44 | } 45 | 46 | public function testItExtractsSourceUrl() 47 | { 48 | $package1 = new Package('acme/my-project1', 'v1.0.0.0', 'v1.0.0'); 49 | $package1->setSourceUrl('https://example.com/acme/my-project1.git'); 50 | 51 | $package2 = new Package('acme/my-project2', 'v1.0.1.0', 'v1.0.1'); 52 | $package2->setSourceUrl('https://example.com/acme/my-project2.git'); 53 | 54 | $operation = new UpdateOperation($package1, $package2); 55 | 56 | $this->assertSame( 57 | 'https://example.com/acme/my-project2.git', 58 | $this->SUT->extractSourceUrl($operation) 59 | ); 60 | } 61 | 62 | public function testItThrowsExceptionWhenExtractingSourceUrlFromNonUpdateOperation() 63 | { 64 | $this->expectException(\LogicException::class); 65 | $this->expectExceptionMessage('Operation should be an instance of UpdateOperation'); 66 | 67 | $this->SUT->extractSourceUrl(new FakeOperation('')); 68 | } 69 | 70 | public function testItGetsOutputWithoutUrlGenerator() 71 | { 72 | $package1 = new Package('acme/my-project1', 'v1.0.0.0', 'v1.0.0'); 73 | $package1->setSourceUrl('https://example.com/acme/my-project1.git'); 74 | 75 | $package2 = new Package('acme/my-project2', 'v1.1.1.0', 'v1.1.1'); 76 | $package2->setSourceUrl('https://example.com/acme/my-project2.git'); 77 | 78 | $operation = new UpdateOperation($package1, $package2); 79 | 80 | $expectedOutput = [ 81 | ' - acme/my-project1 updated from v1.0.0 to v1.1.1 minor', 82 | ]; 83 | 84 | $this->assertSame( 85 | $expectedOutput, 86 | $this->SUT->getOutput($operation, null) 87 | ); 88 | } 89 | 90 | public function testItGetsOutputWithUrlGeneratorNoSupportingCompareUrl() 91 | { 92 | $operation = new UpdateOperation( 93 | new Package('acme/my-project1', 'v1.0.0.0', 'v1.0.0'), 94 | new Package('acme/my-project2', 'v1.0.1.0', 'v1.0.1') 95 | ); 96 | 97 | $urlGenerator = new FakeUrlGenerator( 98 | true, 99 | false, 100 | 'https://example.com/acme/my-project/release/v1.0.1' 101 | ); 102 | 103 | $expectedOutput = [ 104 | ' - acme/my-project1 updated from v1.0.0 to v1.0.1 patch', 105 | ' Release notes: https://example.com/acme/my-project/release/v1.0.1', 106 | ]; 107 | 108 | $this->assertSame( 109 | $expectedOutput, 110 | $this->SUT->getOutput($operation, $urlGenerator) 111 | ); 112 | } 113 | 114 | public function testItGetsOutputWithUrlGeneratorNoSupportingReleaseUrl() 115 | { 116 | $operation = new UpdateOperation( 117 | new Package('acme/my-project1', 'v1.0.0.0', 'v1.0.0'), 118 | new Package('acme/my-project2', 'v1.0.1.0', 'v1.0.1') 119 | ); 120 | 121 | $urlGenerator = new FakeUrlGenerator( 122 | true, 123 | 'https://example.com/acme/my-project/compare/v1.0.0/v1.0.1', 124 | false 125 | ); 126 | 127 | $expectedOutput = [ 128 | ' - acme/my-project1 updated from v1.0.0 to v1.0.1 patch', 129 | ' See changes: https://example.com/acme/my-project/compare/v1.0.0/v1.0.1', 130 | ]; 131 | 132 | $this->assertSame( 133 | $expectedOutput, 134 | $this->SUT->getOutput($operation, $urlGenerator) 135 | ); 136 | } 137 | 138 | public function testItGetsOutputWithUrlGeneratorSupportingAllUrls() 139 | { 140 | $operation = new UpdateOperation( 141 | new Package('acme/my-project1', 'v1.0.0.0', 'v1.0.0'), 142 | new Package('acme/my-project2', 'v1.0.1.0', 'v1.0.1') 143 | ); 144 | 145 | $urlGenerator = new FakeUrlGenerator( 146 | true, 147 | 'https://example.com/acme/my-project/compare/v1.0.0/v1.0.1', 148 | 'https://example.com/acme/my-project/release/v1.0.1' 149 | ); 150 | 151 | $expectedOutput = [ 152 | ' - acme/my-project1 updated from v1.0.0 to v1.0.1 patch', 153 | ' See changes: https://example.com/acme/my-project/compare/v1.0.0/v1.0.1', 154 | ' Release notes: https://example.com/acme/my-project/release/v1.0.1', 155 | ]; 156 | 157 | $this->assertSame( 158 | $expectedOutput, 159 | $this->SUT->getOutput($operation, $urlGenerator) 160 | ); 161 | } 162 | 163 | public function testItThrowsExceptionWhenGettingOutputFromNonUpdateOperation() 164 | { 165 | $this->expectException(\LogicException::class); 166 | $this->expectExceptionMessage('Operation should be an instance of UpdateOperation'); 167 | 168 | $this->SUT->getOutput(new FakeOperation('')); 169 | } 170 | 171 | public function testItUsesCorrectActionName() 172 | { 173 | $package1 = new Package('acme/my-project1', 'v1.0.0.0', 'v1.0.0'); 174 | $package2 = new Package('acme/my-project2', 'v1.0.1.0', 'v1.0.1'); 175 | 176 | $operationUpdate = new UpdateOperation($package1, $package2); 177 | 178 | $expectedOutput = [ 179 | ' - acme/my-project1 updated from v1.0.0 to v1.0.1 patch', 180 | ]; 181 | 182 | $this->assertSame( 183 | $expectedOutput, 184 | $this->SUT->getOutput($operationUpdate, null) 185 | ); 186 | 187 | $operationDowngrade = new UpdateOperation($package2, $package1); 188 | 189 | $expectedOutput = [ 190 | ' - acme/my-project2 downgraded from v1.0.1 to v1.0.0 patch', 191 | ]; 192 | 193 | $this->assertSame( 194 | $expectedOutput, 195 | $this->SUT->getOutput($operationDowngrade, null) 196 | ); 197 | } 198 | 199 | public function testItOutputsTheCorrectSemverColors() 200 | { 201 | $base = new Package('acme/my-project1', 'v1.0.0.0', 'v1.0.0'); 202 | $patch = new Package('acme/my-project1', 'v1.0.1.0', 'v1.0.1'); 203 | $minor = new Package('acme/my-project2', 'v1.1.0.0', 'v1.1.0'); 204 | $major = new Package('acme/my-project2', 'v2.0.0.0', 'v2.0.0'); 205 | 206 | $patchUpdate = new UpdateOperation($base, $patch); 207 | 208 | $expectedOutput = [ 209 | ' - acme/my-project1 updated from v1.0.0 to v1.0.1 patch', 210 | ]; 211 | 212 | $this->assertSame( 213 | $expectedOutput, 214 | $this->SUT->getOutput($patchUpdate, null) 215 | ); 216 | 217 | $minorUpdate = new UpdateOperation($base, $minor); 218 | 219 | $expectedOutput = [ 220 | ' - acme/my-project1 updated from v1.0.0 to v1.1.0 minor', 221 | ]; 222 | 223 | $this->assertSame( 224 | $expectedOutput, 225 | $this->SUT->getOutput($minorUpdate, null) 226 | ); 227 | 228 | $majorUpdate = new UpdateOperation($base, $major); 229 | 230 | $expectedOutput = [ 231 | ' - acme/my-project1 updated from v1.0.0 to v2.0.0 major', 232 | ]; 233 | 234 | $this->assertSame( 235 | $expectedOutput, 236 | $this->SUT->getOutput($majorUpdate, null) 237 | ); 238 | } 239 | 240 | public function testItDisplaysVcsRevisionForDevPackage() 241 | { 242 | $package1 = new Package('acme/my-project1', 'dev-master', 'dev-master'); 243 | $package1->setSourceType('git'); 244 | $package1->setSourceReference('958a5dd'); 245 | $package2 = new Package('acme/my-project2', 'dev-master', 'dev-master'); 246 | $package2->setSourceType('git'); 247 | $package2->setSourceReference('6d57476'); 248 | 249 | $operationUpdate = new UpdateOperation($package1, $package2); 250 | 251 | $expectedOutput = [ 252 | ' - acme/my-project1 updated from dev-master@958a5dd to dev-master@6d57476', 253 | ]; 254 | 255 | $this->assertSame( 256 | $expectedOutput, 257 | $this->SUT->getOutput($operationUpdate, null) 258 | ); 259 | } 260 | } 261 | -------------------------------------------------------------------------------- /tests/OutputterTest.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Pyrech\ComposerChangelogs\tests; 13 | 14 | use PHPUnit\Framework\TestCase; 15 | use Pyrech\ComposerChangelogs\OperationHandler\OperationHandler; 16 | use Pyrech\ComposerChangelogs\Outputter; 17 | use Pyrech\ComposerChangelogs\tests\resources\FakeHandler; 18 | use Pyrech\ComposerChangelogs\tests\resources\FakeOperation; 19 | use Pyrech\ComposerChangelogs\tests\resources\FakeUrlGenerator; 20 | use Pyrech\ComposerChangelogs\UrlGenerator\UrlGenerator; 21 | 22 | class OutputterTest extends TestCase 23 | { 24 | /** @var Outputter */ 25 | private $SUT; 26 | 27 | /** @var OperationHandler[] */ 28 | private $operationHandlers; 29 | 30 | /** @var UrlGenerator[] */ 31 | private $urlGenerators; 32 | 33 | protected function setUp(): void 34 | { 35 | $this->operationHandlers = [ 36 | new FakeHandler(false, 'http://domain1', 'Output handler 1'), 37 | new FakeHandler(true, 'http://domain2', 'Output handler 2'), 38 | new FakeHandler(true, 'http://domain3', 'Output handler 3'), 39 | ]; 40 | 41 | $this->urlGenerators = [ 42 | new FakeUrlGenerator(false, '/compare-url1', '/release-url1'), 43 | new FakeUrlGenerator(true, '/compare-url2', '/release-url2'), 44 | new FakeUrlGenerator(true, '/compare-url3', '/release-url3'), 45 | ]; 46 | 47 | $this->SUT = new Outputter($this->operationHandlers, $this->urlGenerators); 48 | } 49 | 50 | public function testItAddsOperation() 51 | { 52 | $operation1 = new FakeOperation(''); 53 | $this->SUT->addOperation($operation1); 54 | 55 | $operation2 = new FakeOperation(''); 56 | $this->SUT->addOperation($operation2); 57 | 58 | $expectedOutput = <<Changelogs summary: 60 | 61 | - Output handler 2, 62 | /compare-url2 63 | /release-url2 64 | 65 | - Output handler 2, 66 | /compare-url2 67 | /release-url2 68 | 69 | TEXT; 70 | 71 | $this->assertFalse($this->SUT->isEmpty()); 72 | $this->assertSame($expectedOutput, $this->SUT->getOutput()); 73 | } 74 | 75 | public function testItOutputsWithNoSupportedUrlGenerator() 76 | { 77 | $this->SUT = new Outputter($this->operationHandlers, [ 78 | new FakeUrlGenerator(false, '', ''), 79 | ]); 80 | 81 | $this->SUT->addOperation(new FakeOperation('operation 1')); 82 | $this->SUT->addOperation(new FakeOperation('operation 2')); 83 | 84 | $expectedOutput = <<Changelogs summary: 86 | 87 | - Output handler 2, operation 1 88 | 89 | - Output handler 2, operation 2 90 | 91 | TEXT; 92 | 93 | $this->assertFalse($this->SUT->isEmpty()); 94 | $this->assertSame($expectedOutput, $this->SUT->getOutput()); 95 | } 96 | 97 | public function testItOutputsWithNoSupportedOperationHandler() 98 | { 99 | $this->SUT = new Outputter([ 100 | new FakeHandler(false, '', ''), 101 | ], $this->urlGenerators); 102 | 103 | $this->SUT->addOperation(new FakeOperation('operation 1')); 104 | $this->SUT->addOperation(new FakeOperation('operation 2')); 105 | 106 | $expectedOutput = <<Changelogs summary: 108 | 109 | TEXT; 110 | 111 | $this->assertFalse($this->SUT->isEmpty()); 112 | $this->assertSame($expectedOutput, $this->SUT->getOutput()); 113 | } 114 | 115 | public function testItOutputsRightText() 116 | { 117 | $this->SUT->addOperation(new FakeOperation('operation 1')); 118 | $this->SUT->addOperation(new FakeOperation('operation 2')); 119 | 120 | $expectedOutput = <<Changelogs summary: 122 | 123 | - Output handler 2, operation 1 124 | /compare-url2 125 | /release-url2 126 | 127 | - Output handler 2, operation 2 128 | /compare-url2 129 | /release-url2 130 | 131 | TEXT; 132 | 133 | $this->assertFalse($this->SUT->isEmpty()); 134 | $this->assertSame($expectedOutput, $this->SUT->getOutput()); 135 | } 136 | 137 | public function testItOutputsNothingWithoutOperation() 138 | { 139 | $expectedOutput = <<No changelogs summary 141 | TEXT; 142 | 143 | $this->assertTrue($this->SUT->isEmpty()); 144 | $this->assertSame($expectedOutput, $this->SUT->getOutput()); 145 | } 146 | } 147 | -------------------------------------------------------------------------------- /tests/UrlGenerator/BitbucketUrlGeneratorTest.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Pyrech\ComposerChangelogs\tests\UrlGenerator; 13 | 14 | use PHPUnit\Framework\TestCase; 15 | use Pyrech\ComposerChangelogs\Model\Version; 16 | use Pyrech\ComposerChangelogs\UrlGenerator\BitbucketUrlGenerator; 17 | 18 | class BitbucketUrlGeneratorTest extends TestCase 19 | { 20 | /** @var BitbucketUrlGenerator */ 21 | private $SUT; 22 | 23 | protected function setUp(): void 24 | { 25 | $this->SUT = new BitbucketUrlGenerator(); 26 | } 27 | 28 | public function testItSupportsBitbucketUrls() 29 | { 30 | $this->assertTrue($this->SUT->supports('https://bitbucket.org/mailchimp/mandrill-api-php.git')); 31 | $this->assertTrue($this->SUT->supports('https://bitbucket.org/rogoOOS/rog')); 32 | $this->assertTrue($this->SUT->supports('git@bitbucket.org:private/repo.git')); 33 | } 34 | 35 | public function testItDoesNotSupportNonBitbucketUrls() 36 | { 37 | $this->assertFalse($this->SUT->supports('https://github.com/phpunit/phpunit-mock-objects.git')); 38 | $this->assertFalse($this->SUT->supports('https://github.com/symfony/console')); 39 | $this->assertFalse($this->SUT->supports(null)); 40 | } 41 | 42 | public function testItGeneratesCompareUrlsWithOrWithoutGitExtensionInSourceUrl() 43 | { 44 | $versionFrom = new Version('v1.0.0.0', 'v1.0.0', 'v1.0.0'); 45 | $versionTo = new Version('v1.0.1.0', 'v1.0.1', 'v1.0.1'); 46 | 47 | $this->assertSame( 48 | 'https://bitbucket.org/acme/repo/branches/compare/v1.0.1%0Dv1.0.0', 49 | $this->SUT->generateCompareUrl( 50 | 'https://bitbucket.org/acme/repo', 51 | $versionFrom, 52 | 'https://bitbucket.org/acme/repo', 53 | $versionTo 54 | ) 55 | ); 56 | 57 | $this->assertSame( 58 | 'https://bitbucket.org/acme/repo/branches/compare/v1.0.1%0Dv1.0.0', 59 | $this->SUT->generateCompareUrl( 60 | 'https://bitbucket.org/acme/repo.git', 61 | $versionFrom, 62 | 'https://bitbucket.org/acme/repo.git', 63 | $versionTo 64 | ) 65 | ); 66 | } 67 | 68 | public function testItGeneratesCompareUrlsWithDevVersions() 69 | { 70 | $versionFrom = new Version('v1.0.9999999.9999999-dev', 'dev-master', 'dev-master 1234abc'); 71 | $versionTo = new Version('v1.0.1.0', 'v1.0.1', 'v1.0.1'); 72 | 73 | $this->assertSame( 74 | 'https://bitbucket.org/acme/repo/branches/compare/v1.0.1%0D1234abc', 75 | $this->SUT->generateCompareUrl( 76 | 'https://bitbucket.org/acme/repo.git', 77 | $versionFrom, 78 | 'https://bitbucket.org/acme/repo.git', 79 | $versionTo 80 | ) 81 | ); 82 | 83 | $versionFrom = new Version('v1.0.0.0', 'v1.0.0', 'v1.0.0'); 84 | $versionTo = new Version('9999999-dev', 'dev-master', 'dev-master 6789def'); 85 | 86 | $this->assertSame( 87 | 'https://bitbucket.org/acme/repo/branches/compare/6789def%0Dv1.0.0', 88 | $this->SUT->generateCompareUrl( 89 | 'https://bitbucket.org/acme/repo.git', 90 | $versionFrom, 91 | 'https://bitbucket.org/acme/repo.git', 92 | $versionTo 93 | ) 94 | ); 95 | 96 | $versionFrom = new Version('v1.0.1.0', 'v1.0.1', 'v1.0.1'); 97 | $versionTo = new Version('dev-fix/issue', 'dev-fix/issue', 'dev-fix/issue 1234abc'); 98 | 99 | $this->assertSame( 100 | 'https://bitbucket.org/acme/repo/branches/compare/1234abc%0Dv1.0.1', 101 | $this->SUT->generateCompareUrl( 102 | 'https://bitbucket.org/acme/repo.git', 103 | $versionFrom, 104 | 'https://bitbucket.org/acme/repo.git', 105 | $versionTo 106 | ) 107 | ); 108 | } 109 | 110 | public function testItGeneratesCompareUrlsAcrossForks() 111 | { 112 | $versionFrom = new Version('v1.0.0.0', 'v1.0.0', 'v1.0.0'); 113 | $versionTo = new Version('v1.0.1.0', 'v1.0.1', 'v1.0.1'); 114 | 115 | $this->assertSame( 116 | 'https://bitbucket.org/acme2/repo/branches/compare/acme2/repo:v1.0.1%0Dacme1/repo:v1.0.0', 117 | $this->SUT->generateCompareUrl( 118 | 'https://bitbucket.org/acme1/repo', 119 | $versionFrom, 120 | 'https://bitbucket.org/acme2/repo', 121 | $versionTo 122 | ) 123 | ); 124 | } 125 | 126 | public function testItDoesNotGenerateCompareUrlsForUnsupportedUrl() 127 | { 128 | $versionFrom = new Version('v1.0.0.0', 'v1.0.0', 'v1.0.0'); 129 | $versionTo = new Version('v1.0.1.0', 'v1.0.1', 'v1.0.1'); 130 | 131 | $this->assertFalse( 132 | $this->SUT->generateCompareUrl( 133 | '/home/toto/work/my-package', 134 | $versionFrom, 135 | 'https://bitbucket.org/acme2/repo', 136 | $versionTo 137 | ) 138 | ); 139 | 140 | $this->assertFalse( 141 | $this->SUT->generateCompareUrl( 142 | 'https://bitbucket.org/acme1/repo', 143 | $versionFrom, 144 | '/home/toto/work/my-package', 145 | $versionTo 146 | ) 147 | ); 148 | } 149 | 150 | public function testItThrowsExceptionWhenGeneratingCompareUrlsAcrossForksIfASourceUrlIsInvalid() 151 | { 152 | $this->expectException(\LogicException::class); 153 | $this->expectExceptionMessage('Unrecognized url format for bitbucket.org ("https://bitbucket.org/acme2")'); 154 | 155 | $versionFrom = new Version('v1.0.0.0', 'v1.0.0', 'v1.0.0'); 156 | $versionTo = new Version('v1.0.1.0', 'v1.0.1', 'v1.0.1'); 157 | 158 | $this->SUT->generateCompareUrl( 159 | 'https://bitbucket.org/acme1/repo', 160 | $versionFrom, 161 | 'https://bitbucket.org/acme2', 162 | $versionTo 163 | ); 164 | } 165 | 166 | public function testItGeneratesCompareUrlsWithSshSourceUrl() 167 | { 168 | $versionFrom = new Version('v1.0.0.0', 'v1.0.0', 'v1.0.0'); 169 | $versionTo = new Version('v1.0.1.0', 'v1.0.1', 'v1.0.1'); 170 | 171 | $this->assertSame( 172 | 'https://bitbucket.org/acme/repo/branches/compare/v1.0.1%0Dv1.0.0', 173 | $this->SUT->generateCompareUrl( 174 | 'git@bitbucket.org:acme/repo.git', 175 | $versionFrom, 176 | 'git@bitbucket.org:acme/repo.git', 177 | $versionTo 178 | ) 179 | ); 180 | } 181 | 182 | public function testItDoesNotGenerateCompareUrlsWithoutSourceUrl() 183 | { 184 | $versionFrom = new Version('v1.0.0.0', 'v1.0.0', 'v1.0.0'); 185 | $versionTo = new Version('v1.0.1.0', 'v1.0.1', 'v1.0.1'); 186 | 187 | $this->assertFalse( 188 | $this->SUT->generateCompareUrl( 189 | null, 190 | $versionFrom, 191 | null, 192 | $versionTo 193 | ) 194 | ); 195 | } 196 | 197 | public function testItDoesNotGenerateReleaseUrls() 198 | { 199 | $this->assertFalse( 200 | $this->SUT->generateReleaseUrl( 201 | 'https://bitbucket.org/acme/repo', 202 | new Version('v1.0.1.0', 'v1.0.1', 'v1.0.1') 203 | ) 204 | ); 205 | 206 | $this->assertFalse( 207 | $this->SUT->generateReleaseUrl( 208 | 'https://bitbucket.org/acme/repo.git', 209 | new Version('v1.0.1.0', 'v1.0.1', 'v1.0.1') 210 | ) 211 | ); 212 | } 213 | 214 | public function testItDoesNotGenerateReleaseUrlWithoutSourceUrl() 215 | { 216 | $this->assertFalse( 217 | $this->SUT->generateReleaseUrl( 218 | null, 219 | new Version('v1.0.1.0', 'v1.0.1', 'v1.0.1') 220 | ) 221 | ); 222 | } 223 | } 224 | -------------------------------------------------------------------------------- /tests/UrlGenerator/GithubUrlGeneratorTest.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Pyrech\ComposerChangelogs\tests\UrlGenerator; 13 | 14 | use PHPUnit\Framework\TestCase; 15 | use Pyrech\ComposerChangelogs\Model\Version; 16 | use Pyrech\ComposerChangelogs\UrlGenerator\GithubUrlGenerator; 17 | 18 | class GithubUrlGeneratorTest extends TestCase 19 | { 20 | /** @var GithubUrlGenerator */ 21 | private $SUT; 22 | 23 | protected function setUp(): void 24 | { 25 | $this->SUT = new GithubUrlGenerator(); 26 | } 27 | 28 | public function testItSupportsGithubUrls() 29 | { 30 | $this->assertTrue($this->SUT->supports('https://github.com/phpunit/phpunit-mock-objects.git')); 31 | $this->assertTrue($this->SUT->supports('https://github.com/symfony/console')); 32 | $this->assertTrue($this->SUT->supports('git@github.com:private/repo.git')); 33 | } 34 | 35 | public function testItDoesNotSupportNonGithubUrls() 36 | { 37 | $this->assertFalse($this->SUT->supports('https://bitbucket.org/mailchimp/mandrill-api-php.git')); 38 | $this->assertFalse($this->SUT->supports('https://bitbucket.org/rogoOOS/rog')); 39 | } 40 | 41 | public function testItGeneratesCompareUrlsWithOrWithoutGitExtensionInSourceUrl() 42 | { 43 | $versionFrom = new Version('v1.0.0.0', 'v1.0.0', 'v1.0.0'); 44 | $versionTo = new Version('v1.0.1.0', 'v1.0.1', 'v1.0.1'); 45 | 46 | $this->assertSame( 47 | 'https://github.com/acme/repo/compare/v1.0.0...v1.0.1', 48 | $this->SUT->generateCompareUrl( 49 | 'https://github.com/acme/repo', 50 | $versionFrom, 51 | 'https://github.com/acme/repo', 52 | $versionTo 53 | ) 54 | ); 55 | 56 | $this->assertSame( 57 | 'https://github.com/acme/repo/compare/v1.0.0...v1.0.1', 58 | $this->SUT->generateCompareUrl( 59 | 'https://github.com/acme/repo.git', 60 | $versionFrom, 61 | 'https://github.com/acme/repo.git', 62 | $versionTo 63 | ) 64 | ); 65 | } 66 | 67 | public function testItGeneratesCompareUrlsWithDevVersions() 68 | { 69 | $versionFrom = new Version('v.1.0.9999999.9999999-dev', 'dev-master', 'dev-master 1234abc'); 70 | $versionTo = new Version('v1.0.1.0', 'v1.0.1', 'v1.0.1'); 71 | 72 | $this->assertSame( 73 | 'https://github.com/acme/repo/compare/1234abc...v1.0.1', 74 | $this->SUT->generateCompareUrl( 75 | 'https://github.com/acme/repo.git', 76 | $versionFrom, 77 | 'https://github.com/acme/repo.git', 78 | $versionTo 79 | ) 80 | ); 81 | 82 | $versionFrom = new Version('v1.0.0.0', 'v1.0.0', 'v1.0.0'); 83 | $versionTo = new Version('9999999-dev', 'dev-master', 'dev-master 6789def'); 84 | 85 | $this->assertSame( 86 | 'https://github.com/acme/repo/compare/v1.0.0...6789def', 87 | $this->SUT->generateCompareUrl( 88 | 'https://github.com/acme/repo.git', 89 | $versionFrom, 90 | 'https://github.com/acme/repo.git', 91 | $versionTo 92 | ) 93 | ); 94 | 95 | $versionFrom = new Version('v1.0.1.0', 'v1.0.1', 'v1.0.1'); 96 | $versionTo = new Version('dev-fix/issue', 'dev-fix/issue', 'dev-fix/issue 1234abc'); 97 | 98 | $this->assertSame( 99 | 'https://github.com/acme/repo/compare/v1.0.1...1234abc', 100 | $this->SUT->generateCompareUrl( 101 | 'https://github.com/acme/repo.git', 102 | $versionFrom, 103 | 'https://github.com/acme/repo.git', 104 | $versionTo 105 | ) 106 | ); 107 | } 108 | 109 | public function testItGeneratesCompareUrlsAcrossForks() 110 | { 111 | $versionFrom = new Version('v1.0.0.0', 'v1.0.0', 'v1.0.0'); 112 | $versionTo = new Version('v1.0.1.0', 'v1.0.1', 'v1.0.1'); 113 | 114 | $this->assertSame( 115 | 'https://github.com/acme2/repo/compare/acme1:v1.0.0...acme2:v1.0.1', 116 | $this->SUT->generateCompareUrl( 117 | 'https://github.com/acme1/repo', 118 | $versionFrom, 119 | 'https://github.com/acme2/repo', 120 | $versionTo 121 | ) 122 | ); 123 | } 124 | 125 | public function testItDoesNotGenerateCompareUrlsForUnsupportedUrl() 126 | { 127 | $versionFrom = new Version('v1.0.0.0', 'v1.0.0', 'v1.0.0'); 128 | $versionTo = new Version('v1.0.1.0', 'v1.0.1', 'v1.0.1'); 129 | 130 | $this->assertFalse( 131 | $this->SUT->generateCompareUrl( 132 | '/home/toto/work/my-package', 133 | $versionFrom, 134 | 'https://github.com/acme2/repo', 135 | $versionTo 136 | ) 137 | ); 138 | 139 | $this->assertFalse( 140 | $this->SUT->generateCompareUrl( 141 | 'https://github.com/acme1/repo', 142 | $versionFrom, 143 | '/home/toto/work/my-package', 144 | $versionTo 145 | ) 146 | ); 147 | } 148 | 149 | public function testItThrowsExceptionWhenGeneratingCompareUrlsAcrossForksIfASourceUrlIsInvalid() 150 | { 151 | $this->expectException(\LogicException::class); 152 | $this->expectExceptionMessage('Unrecognized url format for github.com ("https://github.com/acme2")'); 153 | 154 | $versionFrom = new Version('v1.0.0.0', 'v1.0.0', 'v1.0.0'); 155 | $versionTo = new Version('v1.0.1.0', 'v1.0.1', 'v1.0.1'); 156 | 157 | $this->SUT->generateCompareUrl( 158 | 'https://github.com/acme1/repo', 159 | $versionFrom, 160 | 'https://github.com/acme2', 161 | $versionTo 162 | ); 163 | } 164 | 165 | public function testItGeneratesCompareUrlsWithSshSourceUrl() 166 | { 167 | $versionFrom = new Version('v1.0.0.0', 'v1.0.0', 'v1.0.0'); 168 | $versionTo = new Version('v1.0.1.0', 'v1.0.1', 'v1.0.1'); 169 | 170 | $this->assertSame( 171 | 'https://github.com/acme/repo/compare/v1.0.0...v1.0.1', 172 | $this->SUT->generateCompareUrl( 173 | 'git@github.com:acme/repo.git', 174 | $versionFrom, 175 | 'git@github.com:acme/repo.git', 176 | $versionTo 177 | ) 178 | ); 179 | } 180 | 181 | public function testItDoesNotGenerateCompareUrlsWithoutSourceUrl() 182 | { 183 | $versionFrom = new Version('v1.0.0.0', 'v1.0.0', 'v1.0.0'); 184 | $versionTo = new Version('v1.0.1.0', 'v1.0.1', 'v1.0.1'); 185 | 186 | $this->assertFalse( 187 | $this->SUT->generateCompareUrl( 188 | null, 189 | $versionFrom, 190 | null, 191 | $versionTo 192 | ) 193 | ); 194 | } 195 | 196 | public function testItDoesNotGenerateReleaseUrlsForDevVersion() 197 | { 198 | $this->assertFalse( 199 | $this->SUT->generateReleaseUrl( 200 | 'https://github.com/acme/repo', 201 | new Version('9999999-dev', 'dev-master', 'dev-master 1234abc') 202 | ) 203 | ); 204 | 205 | $this->assertFalse( 206 | $this->SUT->generateReleaseUrl( 207 | 'https://github.com/acme/repo', 208 | new Version('dev-fix/issue', 'dev-fix/issue', 'dev-fix/issue 1234abc') 209 | ) 210 | ); 211 | } 212 | 213 | public function testItGeneratesReleaseUrls() 214 | { 215 | $this->assertSame( 216 | 'https://github.com/acme/repo/releases/tag/v1.0.1', 217 | $this->SUT->generateReleaseUrl( 218 | 'https://github.com/acme/repo', 219 | new Version('v1.0.1.0', 'v1.0.1', 'v1.0.1') 220 | ) 221 | ); 222 | 223 | $this->assertSame( 224 | 'https://github.com/acme/repo/releases/tag/v1.0.1', 225 | $this->SUT->generateReleaseUrl( 226 | 'https://github.com/acme/repo.git', 227 | new Version('v1.0.1.0', 'v1.0.1', 'v1.0.1') 228 | ) 229 | ); 230 | } 231 | 232 | public function testItGeneratesReleaseUrlWithSshSourceUrl() 233 | { 234 | $this->assertSame( 235 | 'https://github.com/acme/repo/releases/tag/v1.0.1', 236 | $this->SUT->generateReleaseUrl( 237 | 'git@github.com:acme/repo.git', 238 | new Version('v1.0.1.0', 'v1.0.1', 'v1.0.1') 239 | ) 240 | ); 241 | } 242 | 243 | public function testItDoesNotGenerateReleaseUrlWithoutSourceUrl() 244 | { 245 | $this->assertFalse( 246 | $this->SUT->generateReleaseUrl( 247 | null, 248 | new Version('v1.0.1.0', 'v1.0.1', 'v1.0.1') 249 | ) 250 | ); 251 | } 252 | } 253 | -------------------------------------------------------------------------------- /tests/UrlGenerator/GitlabUrlGeneratorTest.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Pyrech\ComposerChangelogs\tests\UrlGenerator; 13 | 14 | use PHPUnit\Framework\TestCase; 15 | use Pyrech\ComposerChangelogs\Model\Version; 16 | use Pyrech\ComposerChangelogs\UrlGenerator\GitlabUrlGenerator; 17 | 18 | class GitlabUrlGeneratorTest extends TestCase 19 | { 20 | /** @var GitlabUrlGenerator */ 21 | private $SUT; 22 | 23 | protected function setUp(): void 24 | { 25 | $this->SUT = new GitlabUrlGenerator('gitlab.company.org'); 26 | } 27 | 28 | public function testItSupportsGitlabUrls() 29 | { 30 | $this->assertTrue($this->SUT->supports('https://gitlab.company.org/phpunit/phpunit-mock-objects.git')); 31 | $this->assertTrue($this->SUT->supports('https://gitlab.company.org/symfony/console')); 32 | $this->assertTrue($this->SUT->supports('git@gitlab.company.org:private/repo.git')); 33 | } 34 | 35 | public function testItDoesNotSupportNonGitlabUrls() 36 | { 37 | $this->assertFalse($this->SUT->supports('https://company.org/about-us')); 38 | $this->assertFalse($this->SUT->supports('https://bitbucket.org/rogoOOS/rog')); 39 | } 40 | 41 | public function testItGeneratesCompareUrlsWithOrWithoutGitExtensionInSourceUrl() 42 | { 43 | $versionFrom = new Version('v1.0.0.0', 'v1.0.0', 'v1.0.0'); 44 | $versionTo = new Version('v1.0.1.0', 'v1.0.1', 'v1.0.1'); 45 | 46 | $this->assertSame( 47 | 'https://gitlab.company.org/acme/repo/compare/v1.0.0...v1.0.1', 48 | $this->SUT->generateCompareUrl( 49 | 'https://gitlab.company.org/acme/repo', 50 | $versionFrom, 51 | 'https://gitlab.company.org/acme/repo', 52 | $versionTo 53 | ) 54 | ); 55 | 56 | $this->assertSame( 57 | 'https://gitlab.company.org/acme/repo/compare/v1.0.0...v1.0.1', 58 | $this->SUT->generateCompareUrl( 59 | 'https://gitlab.company.org/acme/repo.git', 60 | $versionFrom, 61 | 'https://gitlab.company.org/acme/repo.git', 62 | $versionTo 63 | ) 64 | ); 65 | } 66 | 67 | public function testItGeneratesCompareUrlsWithDevVersions() 68 | { 69 | $versionFrom = new Version('v.1.0.9999999.9999999-dev', 'dev-master', 'dev-master 1234abc'); 70 | $versionTo = new Version('v1.0.1.0', 'v1.0.1', 'v1.0.1'); 71 | 72 | $this->assertSame( 73 | 'https://gitlab.company.org/acme/repo/compare/1234abc...v1.0.1', 74 | $this->SUT->generateCompareUrl( 75 | 'https://gitlab.company.org/acme/repo.git', 76 | $versionFrom, 77 | 'https://gitlab.company.org/acme/repo.git', 78 | $versionTo 79 | ) 80 | ); 81 | 82 | $versionFrom = new Version('v1.0.0.0', 'v1.0.0', 'v1.0.0'); 83 | $versionTo = new Version('9999999-dev', 'dev-master', 'dev-master 6789def'); 84 | 85 | $this->assertSame( 86 | 'https://gitlab.company.org/acme/repo/compare/v1.0.0...6789def', 87 | $this->SUT->generateCompareUrl( 88 | 'https://gitlab.company.org/acme/repo.git', 89 | $versionFrom, 90 | 'https://gitlab.company.org/acme/repo.git', 91 | $versionTo 92 | ) 93 | ); 94 | 95 | $versionFrom = new Version('v1.0.1.0', 'v1.0.1', 'v1.0.1'); 96 | $versionTo = new Version('dev-fix/issue', 'dev-fix/issue', 'dev-fix/issue 1234abc'); 97 | 98 | $this->assertSame( 99 | 'https://gitlab.company.org/acme/repo/compare/v1.0.1...1234abc', 100 | $this->SUT->generateCompareUrl( 101 | 'https://gitlab.company.org/acme/repo.git', 102 | $versionFrom, 103 | 'https://gitlab.company.org/acme/repo.git', 104 | $versionTo 105 | ) 106 | ); 107 | } 108 | 109 | public function testItDoesNotGenerateCompareUrlsAcrossForks() 110 | { 111 | $versionFrom = new Version('v1.0.0.0', 'v1.0.0', 'v1.0.0'); 112 | $versionTo = new Version('v1.0.1.0', 'v1.0.1', 'v1.0.1'); 113 | 114 | $this->assertFalse( 115 | $this->SUT->generateCompareUrl( 116 | 'https://gitlab.company.org/acme1/repo', 117 | $versionFrom, 118 | 'https://gitlab.company.org/acme2/repo', 119 | $versionTo 120 | ) 121 | ); 122 | } 123 | 124 | public function testItDoesNotGenerateCompareUrlsForUnsupportedUrl() 125 | { 126 | $versionFrom = new Version('v1.0.0.0', 'v1.0.0', 'v1.0.0'); 127 | $versionTo = new Version('v1.0.1.0', 'v1.0.1', 'v1.0.1'); 128 | 129 | $this->assertFalse( 130 | $this->SUT->generateCompareUrl( 131 | '/home/toto/work/my-package', 132 | $versionFrom, 133 | 'https://gitlab.company.org/acme2/repo', 134 | $versionTo 135 | ) 136 | ); 137 | 138 | $this->assertFalse( 139 | $this->SUT->generateCompareUrl( 140 | 'https://gitlab.company.org/acme1/repo', 141 | $versionFrom, 142 | '/home/toto/work/my-package', 143 | $versionTo 144 | ) 145 | ); 146 | } 147 | 148 | public function testItGeneratesCompareUrlsWithSshSourceUrl() 149 | { 150 | $versionFrom = new Version('v1.0.0.0', 'v1.0.0', 'v1.0.0'); 151 | $versionTo = new Version('v1.0.1.0', 'v1.0.1', 'v1.0.1'); 152 | 153 | $this->assertSame( 154 | 'https://gitlab.company.org/acme/repo/compare/v1.0.0...v1.0.1', 155 | $this->SUT->generateCompareUrl( 156 | 'git@gitlab.company.org:acme/repo.git', 157 | $versionFrom, 158 | 'git@gitlab.company.org:acme/repo.git', 159 | $versionTo 160 | ) 161 | ); 162 | } 163 | 164 | public function testItDoesNotGenerateCompareUrlsWithoutSourceUrl() 165 | { 166 | $versionFrom = new Version('v1.0.0.0', 'v1.0.0', 'v1.0.0'); 167 | $versionTo = new Version('v1.0.1.0', 'v1.0.1', 'v1.0.1'); 168 | 169 | $this->assertFalse( 170 | $this->SUT->generateCompareUrl( 171 | null, 172 | $versionFrom, 173 | null, 174 | $versionTo 175 | ) 176 | ); 177 | } 178 | 179 | public function testItDoesNotGenerateReleaseUrlsForDevVersion() 180 | { 181 | $this->assertFalse( 182 | $this->SUT->generateReleaseUrl( 183 | 'https://gitlab.company.org/acme/repo', 184 | new Version('9999999-dev', 'dev-master', 'dev-master 1234abc') 185 | ) 186 | ); 187 | 188 | $this->assertFalse( 189 | $this->SUT->generateReleaseUrl( 190 | 'https://gitlab.company.org/acme/repo', 191 | new Version('dev-fix/issue', 'dev-fix/issue', 'dev-fix/issue 1234abc') 192 | ) 193 | ); 194 | } 195 | 196 | public function testItGeneratesReleaseUrls() 197 | { 198 | $this->assertSame( 199 | 'https://gitlab.company.org/acme/repo/tags/v1.0.1', 200 | $this->SUT->generateReleaseUrl( 201 | 'https://gitlab.company.org/acme/repo', 202 | new Version('v1.0.1.0', 'v1.0.1', 'v1.0.1') 203 | ) 204 | ); 205 | 206 | $this->assertSame( 207 | 'https://gitlab.company.org/acme/repo/tags/v1.0.1', 208 | $this->SUT->generateReleaseUrl( 209 | 'https://gitlab.company.org/acme/repo.git', 210 | new Version('v1.0.1.0', 'v1.0.1', 'v1.0.1') 211 | ) 212 | ); 213 | } 214 | 215 | public function testItGeneratesReleaseUrlWithSshSourceUrl() 216 | { 217 | $this->assertSame( 218 | 'https://gitlab.company.org/acme/repo/tags/v1.0.1', 219 | $this->SUT->generateReleaseUrl( 220 | 'git@gitlab.company.org:acme/repo.git', 221 | new Version('v1.0.1.0', 'v1.0.1', 'v1.0.1') 222 | ) 223 | ); 224 | } 225 | 226 | public function testItDoesNotGenerateReleaseUrlWithoutSourceUrl() 227 | { 228 | $this->assertFalse( 229 | $this->SUT->generateReleaseUrl( 230 | null, 231 | new Version('v1.0.1.0', 'v1.0.1', 'v1.0.1') 232 | ) 233 | ); 234 | } 235 | } 236 | -------------------------------------------------------------------------------- /tests/UrlGenerator/WordPressUrlGeneratorTest.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Pyrech\ComposerChangelogs\tests\UrlGenerator; 13 | 14 | use PHPUnit\Framework\TestCase; 15 | use Pyrech\ComposerChangelogs\Model\Version; 16 | use Pyrech\ComposerChangelogs\UrlGenerator\WordPressUrlGenerator; 17 | 18 | class WordPressUrlGeneratorTest extends TestCase 19 | { 20 | /** 21 | * @var WordPressUrlGenerator 22 | */ 23 | private $SUT; 24 | 25 | protected function setUp(): void 26 | { 27 | $this->SUT = new WordPressUrlGenerator(); 28 | } 29 | 30 | public function testItSupportsWordpressUrls() 31 | { 32 | $this->assertTrue($this->SUT->supports('http://plugins.svn.wordpress.org/social-networks-auto-poster-facebook-twitter-g/')); 33 | $this->assertTrue($this->SUT->supports('http://plugins.svn.wordpress.org/askimet/')); 34 | $this->assertTrue($this->SUT->supports('http://themes.svn.wordpress.org/minimize/')); 35 | } 36 | 37 | public function testItDoesNotSupportNonWordpressUrls() 38 | { 39 | $this->assertFalse($this->SUT->supports('https://github.com/phpunit/phpunit-mock-objects.git')); 40 | $this->assertFalse($this->SUT->supports('https://github.com/symfony/console')); 41 | $this->assertFalse($this->SUT->supports('https://bitbucket.org/mailchimp/mandrill-api-php.git')); 42 | $this->assertFalse($this->SUT->supports('https://bitbucket.org/rogoOOS/rog')); 43 | } 44 | 45 | public function testItGeneratesCompareUrls() 46 | { 47 | $versionFrom = new Version('v1.0.0.0', 'v1.0.0', 'v1.0.0'); 48 | $versionTo = new Version('v1.0.1.0', 'v1.0.1', 'v1.0.1'); 49 | 50 | $this->assertSame( 51 | 'https://wordpress.org/plugins/askimet/changelog/', 52 | $this->SUT->generateCompareUrl( 53 | 'http://plugins.svn.wordpress.org/askimet/', 54 | $versionFrom, 55 | 'http://plugins.svn.wordpress.org/askimet/', 56 | $versionTo 57 | ) 58 | ); 59 | 60 | $this->assertSame( 61 | 'https://themes.trac.wordpress.org/log/minimize/', 62 | $this->SUT->generateCompareUrl( 63 | 'http://themes.svn.wordpress.org/minimize/', 64 | $versionFrom, 65 | 'http://themes.svn.wordpress.org/minimize/', 66 | $versionTo 67 | ) 68 | ); 69 | } 70 | 71 | public function testItDoesNotGenerateCompareUrlsWithoutSourceUrl() 72 | { 73 | $versionFrom = new Version('v1.0.0.0', 'v1.0.0', 'v1.0.0'); 74 | $versionTo = new Version('v1.0.1.0', 'v1.0.1', 'v1.0.1'); 75 | 76 | $this->assertFalse( 77 | $this->SUT->generateCompareUrl( 78 | null, 79 | $versionFrom, 80 | null, 81 | $versionTo 82 | ) 83 | ); 84 | } 85 | 86 | public function testItDoesNotGenerateReleaseUrls() 87 | { 88 | $this->assertFalse($this->SUT->generateReleaseUrl( 89 | 'http://themes.svn.wordpress.org/minimize/', 90 | new Version('v1.0.1.0', 'v1.0.1', 'v1.0.1') 91 | )); 92 | } 93 | 94 | public function testItDoesNotGenerateReleaseUrlWithoutSourceUrl() 95 | { 96 | $this->assertFalse( 97 | $this->SUT->generateReleaseUrl( 98 | null, 99 | new Version('v1.0.1.0', 'v1.0.1', 'v1.0.1') 100 | ) 101 | ); 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /tests/Util/FileSystemHelperTest.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Pyrech\ComposerChangelogs\tests\Util; 13 | 14 | use PHPUnit\Framework\TestCase; 15 | use Pyrech\ComposerChangelogs\Util\FileSystemHelper; 16 | 17 | class FileSystemHelperTest extends TestCase 18 | { 19 | public function testItCorrectlyDifferentiatesAbsolutePathsFromRelativeOnes() 20 | { 21 | $this->assertTrue(FileSystemHelper::isAbsolute('/var/lib')); 22 | $this->assertTrue(FileSystemHelper::isAbsolute('c:\\\\var\\lib')); 23 | $this->assertTrue(FileSystemHelper::isAbsolute('\\var\\lib')); 24 | 25 | $this->assertFalse(FileSystemHelper::isAbsolute('var/lib')); 26 | $this->assertFalse(FileSystemHelper::isAbsolute('../var/lib')); 27 | $this->assertFalse(FileSystemHelper::isAbsolute('')); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /tests/VersionTest.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Pyrech\ComposerChangelogs\tests; 13 | 14 | use PHPUnit\Framework\TestCase; 15 | use Pyrech\ComposerChangelogs\Model\Version; 16 | 17 | class VersionTest extends TestCase 18 | { 19 | /** @var Version */ 20 | private $SUT; 21 | 22 | public function testItKeepVersionFormats() 23 | { 24 | $this->SUT = new Version('v1.0.0.0', 'v1.0.0', 'v1.0.0'); 25 | 26 | $this->assertSame('v1.0.0.0', $this->SUT->getName()); 27 | $this->assertSame('v1.0.0', $this->SUT->getPretty()); 28 | $this->assertSame('v1.0.0', $this->SUT->getFullPretty()); 29 | 30 | $this->SUT = new Version('v.1.0.9999999.9999999-dev', 'dev-master', 'dev-master 1234abc'); 31 | 32 | $this->assertSame('v.1.0.9999999.9999999-dev', $this->SUT->getName()); 33 | $this->assertSame('dev-master', $this->SUT->getPretty()); 34 | $this->assertSame('dev-master 1234abc', $this->SUT->getFullPretty()); 35 | } 36 | 37 | public function testItDetectsDevVersion() 38 | { 39 | $this->SUT = new Version('v1.0.0.0', 'v1.0.0', 'v1.0.0'); 40 | 41 | $this->assertFalse($this->SUT->isDev()); 42 | 43 | $this->SUT = new Version('v.1.0.9999999.9999999-dev', 'dev-master', 'dev-master 1234abc'); 44 | 45 | $this->assertTrue($this->SUT->isDev()); 46 | 47 | $this->SUT = new Version('dev-fix/issue', 'dev-fix/issue', 'dev-fix/issue 1234abc'); 48 | 49 | $this->assertTrue($this->SUT->isDev()); 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /tests/fixtures/bin/fake.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | FAKE_BIN_DIR=$(dirname $0) 4 | TEST_DIR=$FAKE_BIN_DIR/../../temp 5 | MESSAGE_FILE=$TEST_DIR/commit-message.txt 6 | cat "$2" > $MESSAGE_FILE 7 | -------------------------------------------------------------------------------- /tests/fixtures/home-commit-message/composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "extra": { 3 | "composer-changelogs": { 4 | "commit-auto": "always", 5 | "commit-bin-file": "../bin/fake.sh", 6 | "commit-message": "chore: Update composer" 7 | }, 8 | "my-global-config": { 9 | "bar": "foo" 10 | } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /tests/fixtures/home/composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "extra": { 3 | "composer-changelogs": { 4 | "commit-auto": "always", 5 | "commit-bin-file": "../bin/fake.sh" 6 | }, 7 | "my-global-config": { 8 | "bar": "foo" 9 | } 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /tests/fixtures/local/composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "extra": { 3 | "my-local-config": { 4 | "foo": "bar" 5 | } 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /tests/fixtures/other-post-update-priority/composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "extra": { 3 | "composer-changelogs": { 4 | "post-update-priority": -1337 5 | } 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /tests/resources/FakeHandler.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Pyrech\ComposerChangelogs\tests\resources; 13 | 14 | use Composer\DependencyResolver\Operation\OperationInterface; 15 | use Pyrech\ComposerChangelogs\Model\Version; 16 | use Pyrech\ComposerChangelogs\OperationHandler\OperationHandler; 17 | use Pyrech\ComposerChangelogs\UrlGenerator\UrlGenerator; 18 | 19 | class FakeHandler implements OperationHandler 20 | { 21 | private bool $supports; 22 | 23 | private string $sourceUrl; 24 | 25 | private string $output; 26 | 27 | public function __construct(bool $supports, string $sourceUrl, string $output) 28 | { 29 | $this->supports = $supports; 30 | $this->sourceUrl = $sourceUrl; 31 | $this->output = $output; 32 | } 33 | 34 | public function supports(OperationInterface $operation): bool 35 | { 36 | return $this->supports; 37 | } 38 | 39 | public function extractSourceUrl(OperationInterface $operation): ?string 40 | { 41 | return $this->sourceUrl; 42 | } 43 | 44 | public function getOutput(OperationInterface $operation, UrlGenerator $urlGenerator = null): array 45 | { 46 | if (!($operation instanceof FakeOperation)) { 47 | return []; 48 | } 49 | 50 | $output = [ 51 | ' - ' . $this->output . ', ' . $operation->getText(), 52 | ]; 53 | 54 | if ($urlGenerator) { 55 | $output[] = ' ' . $urlGenerator->generateCompareUrl($this->sourceUrl, new Version('', '', ''), $this->sourceUrl, new Version('', '', '')); 56 | $output[] = ' ' . $urlGenerator->generateReleaseUrl($this->sourceUrl, new Version('', '', '')); 57 | } 58 | 59 | return $output; 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /tests/resources/FakeOperation.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Pyrech\ComposerChangelogs\tests\resources; 13 | 14 | use Composer\DependencyResolver\Operation\OperationInterface; 15 | 16 | class FakeOperation implements OperationInterface 17 | { 18 | private string $text; 19 | 20 | public function __construct(string $text) 21 | { 22 | $this->text = $text; 23 | } 24 | 25 | public function getOperationType() 26 | { 27 | return ''; 28 | } 29 | 30 | public function getText(): string 31 | { 32 | return $this->text; 33 | } 34 | 35 | public function show($lock) 36 | { 37 | return ''; 38 | } 39 | 40 | public function __toString() 41 | { 42 | return ''; 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /tests/resources/FakeUrlGenerator.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Pyrech\ComposerChangelogs\tests\resources; 13 | 14 | use Pyrech\ComposerChangelogs\Model\Version; 15 | use Pyrech\ComposerChangelogs\UrlGenerator\UrlGenerator; 16 | 17 | class FakeUrlGenerator implements UrlGenerator 18 | { 19 | private bool $supports; 20 | 21 | /** @var string|false */ 22 | private $compareUrl; 23 | 24 | /** @var string|false */ 25 | private $releaseUrl; 26 | 27 | /** 28 | * @param string|false $compareUrl 29 | * @param string|false $releaseUrl 30 | */ 31 | public function __construct(bool $supports, $compareUrl, $releaseUrl) 32 | { 33 | $this->supports = $supports; 34 | $this->compareUrl = $compareUrl; 35 | $this->releaseUrl = $releaseUrl; 36 | } 37 | 38 | public function supports($sourceUrl): bool 39 | { 40 | return $this->supports; 41 | } 42 | 43 | public function generateCompareUrl($sourceUrlFrom, Version $versionFrom, $sourceUrlTo, Version $versionTo) 44 | { 45 | return $this->compareUrl; 46 | } 47 | 48 | public function generateReleaseUrl($sourceUrl, Version $version) 49 | { 50 | return $this->releaseUrl; 51 | } 52 | } 53 | --------------------------------------------------------------------------------