Skip to content

Commit 44bd18b

Browse files
authored
Chorale (Replaces Bard) (#241)
1 parent 4c274d8 commit 44bd18b

File tree

109 files changed

+6136
-3
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

109 files changed

+6136
-3
lines changed

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,3 +16,5 @@ packages.json
1616
results.sarif
1717
infection.log
1818
.churn.cache
19+
tools/chorale/composer.lock
20+
tools/chorale/.phpunit.cache/

AGENTS.md

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,11 @@
22

33
This repository is a PHP monorepo containing many packages under `src/`. This guide provides consistent instructions for AI coding agents to work safely and effectively across the codebase.
44

5+
- Use clear variable names and keep code well documented.
6+
- Run tests relevant to the areas you change.
7+
- For changes under `tools/chorale`, run `composer install` and `./vendor/bin/phpunit` in that directory before committing.
8+
- Chorale is the monorepo management CLI using a plan/apply workflow; see `tools/chorale/AGENTS.md` for its roadmap and guidelines.
9+
510
## Repo Layout
611

712
- Root: build tooling (`Makefile`, composer), shared configs, CI inputs.
@@ -56,5 +61,4 @@ This repository is a PHP monorepo containing many packages under `src/`. This gu
5661
- Build passes: `make test` (optionally with coverage).
5762
- Code quality passes: `make php-cs-fixer`, `make psalm`, and (if applicable) `make upgrade-code`.
5863
- Docs updated where needed.
59-
- No changes to `vendor/` or generated artifacts.
60-
64+
- No changes to `vendor/` or generated artifacts.

chorale.yaml

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
version: 1
2+
3+
repo_vendor: SonsOfPHP
4+
repo_name_template: '{name:kebab}.git'
5+
default_repo_template: '{repo_host}:{repo_vendor}/{repo_name_template}'
6+
default_branch: main
7+
splitter: splitsh
8+
tag_strategy: inherit-monorepo-tag
9+
rules:
10+
keep_history: true
11+
skip_if_unchanged: true
12+
require_files:
13+
- composer.json
14+
- LICENSE
15+
patterns:
16+
-
17+
match: 'src/**'
18+
include:
19+
- '**'

docs/SUMMARY.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,10 @@
1111
* [Overview](bard/overview.md)
1212
* [Commands](bard/commands.md)
1313

14+
## 🔧 Tools
15+
16+
* [Chorale](tools/chorale.md)
17+
1418
## Symfony Bundles
1519

1620
* [Feature Toggle](symfony-bundles/feature-toggle.md)

docs/tools/chorale.md

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
# Chorale
2+
3+
Chorale is a CLI tool for managing PHP monorepos. It uses a plan/apply workflow to keep package metadata and the root package in sync.
4+
5+
## Installation
6+
7+
```bash
8+
cd tools/chorale
9+
composer install
10+
```
11+
12+
## Usage
13+
14+
Run the commands from the project root:
15+
16+
```bash
17+
# create chorale.yaml by scanning packages
18+
php bin/chorale setup
19+
20+
# preview changes without modifying files
21+
php bin/chorale plan --json > plan.json
22+
23+
# apply an exported plan
24+
php bin/chorale apply --file plan.json
25+
26+
# build and apply a plan in one go
27+
php bin/chorale run
28+
```
29+
30+
Chorale automatically merges all package `composer.json` files into the root `composer.json` so the monorepo can be installed as a single package. Any dependency conflicts are recorded under the `extra.chorale.dependency-conflicts` section for review.
31+
32+
## Commands
33+
34+
- `setup` – generate configuration and validate required files.
35+
- `plan` – build a plan for splitting packages and root updates.
36+
- `run` – build and immediately apply a plan.
37+
- `apply` – execute steps from a JSON plan file.

rector.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@
3434
earlyReturn: true,
3535
strictBooleans: true,
3636
phpunitCodeQuality: true,
37-
phpunit: true,
37+
//phpunit: true,
3838
)
3939
->withImportNames(
4040
importShortClasses: false,

tools/chorale/AGENTS.md

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
# AGENTS
2+
3+
Chorale is a CLI tool maintained in this repository.
4+
5+
- Use descriptive variable names and document public methods.
6+
- Add unit tests for new features in `src/Tests`.
7+
- Run `composer install` and `./vendor/bin/phpunit` in this directory before committing changes.
8+
9+
## Roadmap
10+
11+
- Implement executors for remaining plan steps such as composer root rebuild and metadata sync.
12+
- Improve conflict resolution strategies for dependency merges.
13+
- Enhance documentation with more real-world examples as features grow.
14+

tools/chorale/bin/chorale

Lines changed: 139 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,139 @@
1+
#!/usr/bin/env php
2+
<?php declare(strict_types=1);
3+
4+
require __DIR__ . '/../vendor/autoload.php';
5+
6+
use Symfony\Component\Console\Application;
7+
use Chorale\Console\Style\ConsoleStyleFactory;
8+
use Chorale\Console\SetupCommand;
9+
use Chorale\Repo\TemplateRenderer;
10+
use Chorale\Util\PathUtils;
11+
use Chorale\Util\Sorting;
12+
use Chorale\Discovery\PackageIdentity;
13+
use Chorale\Config\ConfigDefaults;
14+
use Chorale\Config\SchemaValidator;
15+
use Chorale\IO\BackupManager;
16+
use Chorale\IO\JsonReporter;
17+
use Chorale\Telemetry\RunSummary;
18+
use Chorale\Config\ConfigLoader;
19+
use Chorale\Config\ConfigWriter;
20+
use Chorale\Config\ConfigNormalizer;
21+
use Chorale\Discovery\ComposerMetadata;
22+
use Chorale\Discovery\PackageScanner;
23+
use Chorale\Discovery\PatternMatcher;
24+
use Chorale\Repo\RepoResolver;
25+
use Chorale\Rules\RequiredFilesChecker;
26+
use Chorale\Rules\ConflictDetector;
27+
use Chorale\Composer\ComposerJsonReader;
28+
use Chorale\Composer\DependencyMerger;
29+
use Chorale\Composer\RuleEngine;
30+
use Chorale\Split\ContentHasher;
31+
use Chorale\Split\SplitDecider;
32+
use Chorale\State\FilesystemStateStore;
33+
use Chorale\Util\DiffUtil;
34+
use Chorale\Plan\PlanBuilder;
35+
use Chorale\Console\PlanCommand;
36+
use Chorale\Console\ApplyCommand;
37+
use Chorale\Console\RunCommand;
38+
use Chorale\Run\Runner;
39+
use Chorale\Run\StepExecutorRegistry;
40+
use Chorale\Run\PackageVersionUpdateExecutor;
41+
use Chorale\Run\RootDependencyMergeExecutor;
42+
use Chorale\Run\ComposerRootUpdateExecutor;
43+
44+
$paths = new PathUtils();
45+
$renderer = new TemplateRenderer();
46+
$sorting = new Sorting();
47+
$identity = new PackageIdentity();
48+
$defaults = new ConfigDefaults();
49+
$schema = new SchemaValidator();
50+
$backup = new BackupManager();
51+
$json = new JsonReporter();
52+
$summary = new RunSummary();
53+
$loader = new ConfigLoader();
54+
$composerMeta = new ComposerMetadata();
55+
$composerReader = new ComposerJsonReader();
56+
$stateStore = new FilesystemStateStore();
57+
$hasher = new ContentHasher();
58+
$diffs = new DiffUtil();
59+
60+
$ruleEngine = new RuleEngine($renderer);
61+
$writer = new ConfigWriter($backup);
62+
$normalizer = new ConfigNormalizer($sorting, $defaults);
63+
$scanner = new PackageScanner($paths);
64+
$matcher = new PatternMatcher($paths);
65+
$resolver = new RepoResolver($renderer, $paths);
66+
$required = new RequiredFilesChecker();
67+
$conflicts = new ConflictDetector($matcher);
68+
$depMerger = new DependencyMerger($composerReader);
69+
$splitDecider = new SplitDecider($stateStore, $hasher);
70+
71+
$planner = new PlanBuilder(
72+
defaults: $defaults,
73+
scanner: $scanner,
74+
matcher: $matcher,
75+
resolver: $resolver,
76+
paths: $paths,
77+
composerReader: $composerReader,
78+
depMerger: $depMerger,
79+
ruleEngine: $ruleEngine,
80+
splitDecider: $splitDecider,
81+
diffs: $diffs,
82+
);
83+
$executors = new StepExecutorRegistry([
84+
new PackageVersionUpdateExecutor(),
85+
new RootDependencyMergeExecutor(),
86+
new ComposerRootUpdateExecutor(),
87+
]);
88+
$runner = new Runner(
89+
configLoader: $loader,
90+
planner: $planner,
91+
executors: $executors,
92+
);
93+
94+
95+
// -----------------------------------------------------------------------------
96+
$app = new Application('Chorale', '0.1.0');
97+
// -----------------------------------------------------------------------------
98+
99+
// -----------------------------------------------------------------------------
100+
$app->add(new SetupCommand(
101+
styleFactory: new ConsoleStyleFactory(),
102+
configLoader: $loader,
103+
configWriter: $writer,
104+
configNormalizer: $normalizer,
105+
schemaValidator: $schema,
106+
defaults: $defaults,
107+
scanner: $scanner,
108+
matcher: $matcher,
109+
resolver: $resolver,
110+
identity: $identity,
111+
requiredFiles: $required,
112+
//conflicts: $conflicts,
113+
jsonReporter: $json,
114+
summary: $summary,
115+
composerMeta: $composerMeta,
116+
));
117+
// -----------------------------------------------------------------------------
118+
119+
// -----------------------------------------------------------------------------
120+
$app->add(new PlanCommand(
121+
styleFactory: new ConsoleStyleFactory(),
122+
configLoader: $loader,
123+
planner: $planner,
124+
));
125+
// -----------------------------------------------------------------------------
126+
$app->add(new ApplyCommand(
127+
styleFactory: new ConsoleStyleFactory(),
128+
runner: $runner,
129+
));
130+
// -----------------------------------------------------------------------------
131+
$app->add(new RunCommand(
132+
styleFactory: new ConsoleStyleFactory(),
133+
runner: $runner,
134+
));
135+
// -----------------------------------------------------------------------------
136+
137+
// -----------------------------------------------------------------------------
138+
$app->run();
139+
// -----------------------------------------------------------------------------

tools/chorale/composer.json

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
{
2+
"name": "sonsofphp/chorale",
3+
"description": "Chorale: a CLI tool to help manage PHP monorepos.",
4+
"type": "project",
5+
"license": "MIT",
6+
"require": {
7+
"php": "^8.3",
8+
"ext-mbstring": "*",
9+
"symfony/console": "^7.0",
10+
"symfony/yaml": "^7.0"
11+
},
12+
"require-dev": {
13+
"phpunit/phpunit": "^10.0",
14+
"symfony/var-dumper": "^7.3"
15+
},
16+
"autoload": {
17+
"psr-4": {
18+
"Chorale\\": "src/"
19+
}
20+
},
21+
"autoload-dev": {
22+
"psr-4": {
23+
"Chorale\\Tests\\": "src/Tests/"
24+
}
25+
},
26+
"bin": [
27+
"bin/chorale"
28+
],
29+
"config": {
30+
"sort-packages": true,
31+
"preferred-install": "dist"
32+
},
33+
"minimum-stability": "stable",
34+
"prefer-stable": true
35+
}

tools/chorale/phpunit.xml.dist

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
3+
xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/10.5/phpunit.xsd"
4+
bootstrap="vendor/autoload.php"
5+
cacheDirectory=".phpunit.cache"
6+
requireCoverageMetadata="true"
7+
backupGlobals="false"
8+
colors="true"
9+
cacheResult="true"
10+
executionOrder="defects"
11+
beStrictAboutCoverageMetadata="true"
12+
stopOnDefect="true"
13+
stopOnError="true"
14+
stopOnFailure="true"
15+
stopOnWarning="true"
16+
stopOnDeprecation="true"
17+
stopOnNotice="true"
18+
displayDetailsOnIncompleteTests="true"
19+
displayDetailsOnSkippedTests="true"
20+
displayDetailsOnTestsThatTriggerDeprecations="true"
21+
displayDetailsOnPhpunitDeprecations="true"
22+
displayDetailsOnTestsThatTriggerErrors="true"
23+
displayDetailsOnTestsThatTriggerNotices="true"
24+
displayDetailsOnTestsThatTriggerWarnings="true"
25+
>
26+
27+
<php>
28+
<ini name="error_reporting" value="-1" />
29+
</php>
30+
<testsuites>
31+
<testsuite name="Chorale Test Suite">
32+
<directory>src/Tests</directory>
33+
</testsuite>
34+
</testsuites>
35+
36+
<coverage includeUncoveredFiles="true" pathCoverage="false" ignoreDeprecatedCodeUnits="true" disableCodeCoverageIgnore="false" />
37+
38+
<source>
39+
<include>
40+
<directory suffix=".php">src</directory>
41+
</include>
42+
<exclude>
43+
<directory>src/Tests</directory>
44+
</exclude>
45+
</source>
46+
</phpunit>
47+

0 commit comments

Comments
 (0)