|
| 1 | +# Not only Composer tools to build a Monorepo |
| 2 | + |
| 3 | +[](https://packagist.org/packages/symplify/monorepo-builder/stats) |
| 4 | + |
| 5 | +Do you maintain [a monorepo](https://tomasvotruba.com/blog/2019/10/28/all-you-always-wanted-to-know-about-monorepo-but-were-afraid-to-ask/) with more packages? |
| 6 | + |
| 7 | +**This package has few useful tools, that will make that easier**. |
| 8 | + |
| 9 | +## Install |
| 10 | + |
| 11 | +```bash |
| 12 | +composer require symplify/monorepo-builder --dev |
| 13 | +``` |
| 14 | + |
| 15 | +## Usage |
| 16 | + |
| 17 | +### 0. Are you New to Monorepo? |
| 18 | + |
| 19 | +The best to lean-in fast is to read basic intro at blog post [All You Always Wanted to Know About Monorepo](https://www.tomasvotruba.com/blog/2019/10/28/all-you-always-wanted-to-know-about-monorepo-but-were-afraid-to-ask/#what-is-monorepo). |
| 20 | +We also made a simple command to make that easy for you: |
| 21 | + |
| 22 | +```bash |
| 23 | +vendor/bin/monorepo-builder init |
| 24 | +``` |
| 25 | + |
| 26 | +And the basic setup is done! |
| 27 | + |
| 28 | +### 1. Merge local `composer.json` to the Root One |
| 29 | + |
| 30 | +Merges configured sections to the root `composer.json`, so you can only edit `composer.json` of particular packages and let script to synchronize it. |
| 31 | + |
| 32 | +Sections that are needed for monorepo to work will be merged: |
| 33 | + |
| 34 | +- 'require' |
| 35 | +- 'require-dev' |
| 36 | +- 'autoload' |
| 37 | +- 'autoload-dev' |
| 38 | +- 'repositories' |
| 39 | +- 'extra' |
| 40 | + |
| 41 | +To merge run: |
| 42 | + |
| 43 | +```bash |
| 44 | +vendor/bin/monorepo-builder merge |
| 45 | +``` |
| 46 | + |
| 47 | +<br> |
| 48 | + |
| 49 | +Typical location for packages is `/packages`. But what if you have different naming or extra `/projects` directory? |
| 50 | + |
| 51 | +```php |
| 52 | +declare(strict_types=1); |
| 53 | + |
| 54 | +use Symfony\Component\DependencyInjection\Loader\Configurator\ContainerConfigurator; |
| 55 | +use Symplify\MonorepoBuilder\ValueObject\Option; |
| 56 | + |
| 57 | +return static function (ContainerConfigurator $containerConfigurator): void { |
| 58 | + $parameters = $containerConfigurator->parameters(); |
| 59 | + |
| 60 | + // where are the packages located? |
| 61 | + $parameters->set(Option::PACKAGE_DIRECTORIES, [ |
| 62 | + // default vaulue |
| 63 | + __DIR__ . '/packages', |
| 64 | + // custom |
| 65 | + __DIR__ . '/projects', |
| 66 | + ]); |
| 67 | + |
| 68 | + // how skip packages in loaded direectories? |
| 69 | + $parameters->set(Option::PACKAGE_DIRECTORIES_EXCLUDES, [__DIR__ . '/packages/secret-package']); |
| 70 | + |
| 71 | + // "merge" command related |
| 72 | + |
| 73 | + // what extra parts to add after merge? |
| 74 | + $parameters->set(Option::DATA_TO_APPEND, [ |
| 75 | + 'autoload-dev' => [ |
| 76 | + 'psr-4' => [ |
| 77 | + 'Symplify\Tests\\' => 'tests', |
| 78 | + ], |
| 79 | + ], |
| 80 | + 'require-dev' => [ |
| 81 | + 'phpstan/phpstan' => '^0.12', |
| 82 | + ], |
| 83 | + ]); |
| 84 | + |
| 85 | + $parameters->set(Option::DATA_TO_REMOVE, [ |
| 86 | + 'require' => [ |
| 87 | + // the line is removed by key, so version is irrelevant, thus * |
| 88 | + 'phpunit/phpunit' => '*', |
| 89 | + ], |
| 90 | + ]); |
| 91 | +}; |
| 92 | +``` |
| 93 | + |
| 94 | +### 2. Bump Package Inter-dependencies |
| 95 | + |
| 96 | +Let's say you release `symplify/symplify` 4.0 and you need package to depend on version `^4.0` for each other: |
| 97 | + |
| 98 | +```bash |
| 99 | +vendor/bin/monorepo-builder bump-interdependency "^4.0" |
| 100 | +``` |
| 101 | + |
| 102 | +### 3. Keep Synchronized Package Version |
| 103 | + |
| 104 | +In synchronized monorepo, it's common to use same package version to prevent bugs and WTFs. So if one of your package uses `symfony/console` 3.4 and the other `symfony/console` 4.1, this will tell you: |
| 105 | + |
| 106 | +```bash |
| 107 | +vendor/bin/monorepo-builder validate |
| 108 | +``` |
| 109 | + |
| 110 | +### 4. Keep Package Alias Up-To-Date |
| 111 | + |
| 112 | +You can see this even if there is already version 3.0 out: |
| 113 | + |
| 114 | +```json |
| 115 | +{ |
| 116 | + "extra": { |
| 117 | + "branch-alias": { |
| 118 | + "dev-master": "2.0-dev" |
| 119 | + } |
| 120 | + } |
| 121 | +} |
| 122 | +``` |
| 123 | + |
| 124 | +**Not good.** Get rid of this manual work and add this command to your release workflow: |
| 125 | + |
| 126 | +```bash |
| 127 | +vendor/bin/monorepo-builder package-alias |
| 128 | +``` |
| 129 | + |
| 130 | +This will add alias `3.1-dev` to `composer.json` in each package. |
| 131 | + |
| 132 | +If you prefer [`3.1.x-dev`](https://getcomposer.org/doc/articles/aliases.md#branch-alias) over default `3.1-dev`, you can configure it: |
| 133 | + |
| 134 | +```php |
| 135 | +declare(strict_types=1); |
| 136 | + |
| 137 | +use Symfony\Component\DependencyInjection\Loader\Configurator\ContainerConfigurator; |
| 138 | +use Symplify\MonorepoBuilder\ValueObject\Option; |
| 139 | + |
| 140 | +return static function (ContainerConfigurator $containerConfigurator): void { |
| 141 | + $parameters = $containerConfigurator->parameters(); |
| 142 | + // default: "<major>.<minor>-dev" |
| 143 | + $parameters->set(Option::PACKAGE_ALIAS_FORMAT, '<major>.<minor>.x-dev'); |
| 144 | +}; |
| 145 | +``` |
| 146 | + |
| 147 | +### 5. Split Directories to Git Repositories |
| 148 | + |
| 149 | +Classic use case for monorepo is to synchronize last tag and the `master` branch to allow testing of `@dev` version. |
| 150 | + |
| 151 | +```php |
| 152 | +declare(strict_types=1); |
| 153 | + |
| 154 | +use Symfony\Component\DependencyInjection\Loader\Configurator\ContainerConfigurator; |
| 155 | +use Symplify\MonorepoBuilder\ValueObject\Option; |
| 156 | + |
| 157 | +return static function (ContainerConfigurator $containerConfigurator): void { |
| 158 | + $parameters = $containerConfigurator->parameters(); |
| 159 | + $parameters->set(Option::DIRECTORIES_TO_REPOSITORIES, [ |
| 160 | + __DIR__ . '/packages/package-builder' => ' [email protected]:symplify/package-builder.git', |
| 161 | + __DIR__ . '/packagages/monorepo-builder' => ' [email protected]:symplify/monorepo-builder.git', |
| 162 | + __DIR__ . '/packagages/coding-standard' => ' [email protected]:symplify/coding-standard.git', |
| 163 | + ]); |
| 164 | +}; |
| 165 | +``` |
| 166 | + |
| 167 | +Or even simpler: |
| 168 | + |
| 169 | +```php |
| 170 | +declare(strict_types=1); |
| 171 | + |
| 172 | +use Symfony\Component\DependencyInjection\Loader\Configurator\ContainerConfigurator; |
| 173 | +use Symplify\MonorepoBuilder\ValueObject\Option; |
| 174 | + |
| 175 | +return static function (ContainerConfigurator $containerConfigurator): void { |
| 176 | + $parameters = $containerConfigurator->parameters(); |
| 177 | + $parameters->set(Option::DIRECTORIES_TO_REPOSITORIES, [ |
| 178 | + __DIR__ . '/packages/*' => ' [email protected]:symplify/*.git', |
| 179 | + ]); |
| 180 | +}; |
| 181 | +``` |
| 182 | + |
| 183 | +Do you have non standard directory <=> repository name structure? |
| 184 | + |
| 185 | +```bash |
| 186 | +/packages/MyFirstPackage => my-first-package.git |
| 187 | +``` |
| 188 | + |
| 189 | +Add `Option::DIRECTORIES_TO_REPOSITORIES_CONVERT_FORMAT`: |
| 190 | + |
| 191 | +```php |
| 192 | +declare(strict_types=1); |
| 193 | + |
| 194 | +use Symfony\Component\DependencyInjection\Loader\Configurator\ContainerConfigurator; |
| 195 | +use Symplify\MonorepoBuilder\Split\ValueObject\ConvertFormat; |
| 196 | +use Symplify\MonorepoBuilder\ValueObject\Option; |
| 197 | + |
| 198 | +return static function (ContainerConfigurator $containerConfigurator): void { |
| 199 | + $parameters = $containerConfigurator->parameters(); |
| 200 | + $parameters->set(Option::DIRECTORIES_TO_REPOSITORIES, [ |
| 201 | + __DIR__ . '/packages/*' => ' [email protected]:symplify/*.git', |
| 202 | + ]); |
| 203 | + |
| 204 | + $parameters->set(Option::DIRECTORIES_TO_REPOSITORIES_CONVERT_FORMAT, ConvertFormat::PASCAL_CASE_TO_KEBAB_CASE); |
| 205 | +}; |
| 206 | +``` |
| 207 | + |
| 208 | +<br> |
| 209 | + |
| 210 | +And run by: |
| 211 | + |
| 212 | +```bash |
| 213 | +vendor/bin/monorepo-builder split |
| 214 | +``` |
| 215 | + |
| 216 | +To speed up the process about 50-60 %, all repositories are synchronized in parallel. |
| 217 | + |
| 218 | +#### Testing split locally |
| 219 | + |
| 220 | +If you want to test on local machine, you can set local targets by creating bare repositories: |
| 221 | + |
| 222 | +```bash |
| 223 | +mkdir -p [target/path.git] |
| 224 | +cd [target/path.git] |
| 225 | +git init --bare |
| 226 | +# ^^^^^^ bare!!! |
| 227 | +``` |
| 228 | + |
| 229 | +Then you can set the target using `file://` prefix for absolute path: |
| 230 | + |
| 231 | +```php |
| 232 | +declare(strict_types=1); |
| 233 | + |
| 234 | +use Symfony\Component\DependencyInjection\Loader\Configurator\ContainerConfigurator; |
| 235 | +use Symplify\MonorepoBuilder\ValueObject\Option; |
| 236 | + |
| 237 | +return static function (ContainerConfigurator $containerConfigurator): void { |
| 238 | + $parameters = $containerConfigurator->parameters(); |
| 239 | + $parameters->set(Option::DIRECTORIES_TO_REPOSITORIES, [ |
| 240 | + __DIR__ . '/packages/package-builder' => 'file:///home/developer/git/package-builder.git', |
| 241 | + __DIR__ . '/packagages/monorepo-builder' => 'file:///home/developer/git/monorepo-builder.git', |
| 242 | + ]); |
| 243 | +}; |
| 244 | +``` |
| 245 | + |
| 246 | +After that you can test the result: |
| 247 | + |
| 248 | +```bash |
| 249 | +vendor/bin/monorepo-builder split |
| 250 | +cd /tmp |
| 251 | +git clone /home/developer/git/package-builder.git |
| 252 | +cd package-builder |
| 253 | +git log |
| 254 | +``` |
| 255 | + |
| 256 | +### 6. Release Flow |
| 257 | + |
| 258 | +When a new version of your package is released, you have to do many manual steps: |
| 259 | + |
| 260 | +- bump mutual dependencies, |
| 261 | +- tag this version, |
| 262 | +- `git push` with tag, |
| 263 | +- change `CHANGELOG.md` title *Unreleated* to `v<version> - Y-m-d` format |
| 264 | +- bump alias and mutual dependency to next version alias |
| 265 | + |
| 266 | +But what if **you forget one or do it in wrong order**? Everything will crash! |
| 267 | + |
| 268 | +The `release` command will make you safe: |
| 269 | + |
| 270 | +```bash |
| 271 | +vendor/bin/monorepo-builder release v7.0 |
| 272 | +``` |
| 273 | + |
| 274 | +Are you afraid to tag and push? Use `--dry-run` to see only descriptions: |
| 275 | + |
| 276 | +```bash |
| 277 | +vendor/bin/monorepo-builder release v7.0 --dry-run |
| 278 | +``` |
| 279 | + |
| 280 | +Do you want ot release next [patch version](https://semver.org/), e.g. current `v0.7.1` → next `v0.7.2`? |
| 281 | + |
| 282 | +```bash |
| 283 | +vendor/bin/monorepo-builder release patch |
| 284 | +``` |
| 285 | + |
| 286 | +You can use `minor` and `major` too. |
| 287 | + |
| 288 | +### 7. Set Your Own Release Flow |
| 289 | + |
| 290 | +There is set of few default release workers - classes that implement `Symplify\MonorepoBuilder\Release\Contract\ReleaseWorker\ReleaseWorkerInterface`. |
| 291 | + |
| 292 | +You need to register them as services. Feel free to start with default ones: |
| 293 | + |
| 294 | +```php |
| 295 | +declare(strict_types=1); |
| 296 | + |
| 297 | +use Symfony\Component\DependencyInjection\Loader\Configurator\ContainerConfigurator; |
| 298 | + |
| 299 | +return static function (ContainerConfigurator $containerConfigurator): void { |
| 300 | + $services = $containerConfigurator->services(); |
| 301 | + |
| 302 | + // release workers - in order to execute |
| 303 | + $services->set(Symplify\MonorepoBuilder\Release\ReleaseWorker\SetCurrentMutualDependenciesReleaseWorker::class); |
| 304 | + $services->set(Symplify\MonorepoBuilder\Release\ReleaseWorker\AddTagToChangelogReleaseWorker::class); |
| 305 | + |
| 306 | + // you can extend with your own |
| 307 | + $services->set(App\SendPigeonToTwitterReleaseWorker::class); |
| 308 | + |
| 309 | + $services->set(Symplify\MonorepoBuilder\Release\ReleaseWorker\TagVersionReleaseWorker::class); |
| 310 | + $services->set(Symplify\MonorepoBuilder\Release\ReleaseWorker\PushTagReleaseWorker::class); |
| 311 | + $services->set(Symplify\MonorepoBuilder\Release\ReleaseWorker\SetNextMutualDependenciesReleaseWorker::class); |
| 312 | + $services->set(Symplify\MonorepoBuilder\Release\ReleaseWorker\UpdateBranchAliasReleaseWorker::class); |
| 313 | + $services->set(Symplify\MonorepoBuilder\Release\ReleaseWorker\PushNextDevReleaseWorker::class); |
| 314 | +}; |
| 315 | +``` |
| 316 | + |
| 317 | +## Contribute |
| 318 | + |
| 319 | +The sources of this package are contained in the symplify monorepo. We welcome contributions for this package at [symplify/symplify](https://github.com/symplify/symplify). |
0 commit comments