Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
43 changes: 43 additions & 0 deletions .github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ jobs:
runs-on: ubuntu-latest
outputs:
matrix: ${{ steps.changed.outputs.matrix }}
lowest: ${{ steps.changed.outputs.lowest }}
steps:
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # ratchet:actions/checkout@v6
with:
Expand Down Expand Up @@ -46,6 +47,7 @@ jobs:
# docs-only change: no packages to test
if [ -z "$CHANGED" ]; then
echo "matrix=[]" >> "$GITHUB_OUTPUT"
echo "lowest=[]" >> "$GITHUB_OUTPUT"
exit 0
fi

Expand All @@ -69,6 +71,11 @@ jobs:
)
echo "matrix=$MATRIX" >> "$GITHUB_OUTPUT"

# The lowest job only has something to prove where the constraint is
# actually exercised. A linked entry pins siblings to the checkout,
# so filter those out here rather than starting a runner that skips.
echo "lowest=$(echo "$MATRIX" | jq -c '[.[] | select(.linked | not)]')" >> "$GITHUB_OUTPUT"

validate:
runs-on: ubuntu-latest
steps:
Expand Down Expand Up @@ -126,3 +133,39 @@ jobs:
TESTS_BITBUCKET_ACCESS_TOKEN: ${{ secrets.TESTS_BITBUCKET_ACCESS_TOKEN }}
TESTS_BITBUCKET_WORKSPACE: ${{ secrets.TESTS_BITBUCKET_WORKSPACE }}
run: bin/monorepo test ${{ matrix.package }} ${{ matrix.linked && '--linked' || '' }}

# Resolves every constraint to the oldest release it admits. The `test` job
# above resolves the newest, so a range that is too loose still installs a
# sibling new enough to satisfy the code and nothing fails — which is how
# queue shipped 1.6.0 calling Pool::maintain() under `pools: ^2.0`, a method
# that only exists from 2.1.0. Informational for now: cache, cli, http,
# platform, pools, servers and telemetry carry the same defect today, and
# gating on it would block unrelated work. Becomes a gate once they are fixed.
lowest:
name: lowest (${{ matrix.package }})
needs: changed
if: needs.changed.outputs.lowest != '[]'
runs-on: ubuntu-latest
continue-on-error: true
strategy:
fail-fast: false
matrix:
include: ${{ fromJson(needs.changed.outputs.lowest) }}
steps:
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # ratchet:actions/checkout@v6

- id: meta
run: |
EXTENSIONS=$(jq -r '[(.require, ."require-dev", .suggest | select(. != null)) | keys[] | select(startswith("ext-")) | ltrimstr("ext-")] | unique | join(", ")' packages/${{ matrix.package }}/composer.json)
FLOOR=$(jq -r '.require.php // ""' packages/${{ matrix.package }}/composer.json | grep -oE '[0-9]+\.[0-9]+' | head -1)
PHP=$(printf '%s\n8.4\n' "$FLOOR" | grep -E '^[0-9]+\.[0-9]+$' | sort -V | tail -1)
echo "extensions=$EXTENSIONS" >> "$GITHUB_OUTPUT"
echo "php=$PHP" >> "$GITHUB_OUTPUT"

- uses: shivammathur/setup-php@f3e473d116dcccaddc5834248c87452386958240 # ratchet:shivammathur/setup-php@v2
with:
php-version: ${{ steps.meta.outputs.php }}
extensions: ${{ steps.meta.outputs.extensions }}
coverage: none

- run: bin/monorepo check ${{ matrix.package }} --lowest
1 change: 1 addition & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,5 +6,6 @@
- Releases are git tags shaped `<package>/<semver>` (e.g. `http/2.1.0`) — never edit a changelog to "release".
- All monorepo tooling lives in `bin/monorepo`; do not add monorepo packages or frameworks for this.
- Run a package's tests with `bin/monorepo test <name>`; add `--linked` to test against monorepo siblings instead of released versions (CI does this for dependents of changed packages). Test contract: `composer test` = unit tier, bare host; `composer test:e2e` = host-run tests against the package's compose services (offset host ports, never tests inside containers).
- `--lowest` (on `check` and `test`) resolves every constraint to the oldest release it admits. The default resolves the newest, so a range that is too loose still installs a sibling carrying the API and nothing fails — that is how a published release ends up uninstallable for a consumer pinning an older sibling. Widen the floor when it fails; never relax the check.
- Markdown docs are linted with Vale (`vale README.md docs packages`; config in `.vale.ini`, vendored style in `.vale/styles`). Errors fail CI. Opinions: sentence-case headings (H1 is the library's proper name and is exempt), no weasel words ("simply", "easily"), correct brand casing (GitHub, Composer, PHPStan), spell-check with the shared vocabulary in `.vale/styles/config/vocabularies/Utopia/accept.txt` — add legitimate new terms there, backtick code identifiers in prose.
- Benchmarks are opt-in and run separately from tests: a package joins by declaring a `composer bench` script, and `bin/monorepo benchmark <name>` runs it. The Benchmark workflow runs these on Linux runners (informational, not a pass/fail gate); packages without a `bench` script are skipped.
27 changes: 21 additions & 6 deletions bin/monorepo
Original file line number Diff line number Diff line change
Expand Up @@ -605,6 +605,12 @@ switch ($command) {
case 'check':
$targets = $arguments !== [] ? $arguments : packages();
toolchain();
// --lowest resolves every constraint to the oldest release it admits,
// which is the only combination that proves the constraints are honest.
// The default `composer update` takes the newest, so a range that is
// too loose still installs a sibling new enough to satisfy the code and
// nothing fails. Forces a re-resolve even when vendor/ is populated.
$lowest = isset($options['lowest']) ? ' --prefer-lowest' : '';
$failed = [];
foreach ($targets as $package) {
is_dir("packages/{$package}") || fail("unknown package: {$package}");
Expand All @@ -613,8 +619,8 @@ switch ($command) {
$directory = "packages/{$package}";

// phpstan and rector reflect against the package's autoloader
if (!is_dir("{$directory}/vendor")) {
passthru("cd {$directory} && composer update --no-interaction --prefer-dist --no-progress --ignore-platform-reqs", $code);
if (!is_dir("{$directory}/vendor") || $lowest !== '') {
passthru("cd {$directory} && composer update --no-interaction --prefer-dist --no-progress --ignore-platform-reqs{$lowest}", $code);
if ($code !== 0) {
$failed[] = "{$package} (composer)";
continue;
Expand Down Expand Up @@ -690,6 +696,13 @@ switch ($command) {
out("==> {$package}");
$directory = "packages/{$package}";

// --lowest: see the note in `check`. Meaningless with --linked,
// which pins siblings to the checkout regardless of the constraint.
$lowest = isset($options['lowest']) ? ' --prefer-lowest' : '';
if ($lowest !== '' && isset($options['linked'])) {
fail('--lowest and --linked are mutually exclusive: --linked ignores the declared constraint');
}

// --linked: resolve monorepo siblings from the local checkout
// instead of Packagist, via a generated manifest (COMPOSER env)
// with a path repository claiming constraint-satisfying versions.
Expand Down Expand Up @@ -718,7 +731,7 @@ switch ($command) {
// --ignore-platform-reqs: CI installs each package's declared
// extensions, and exotic transitive ones (e.g. ext-opentelemetry)
// must not block resolution.
passthru("cd {$directory} && {$environment}composer update --no-interaction --prefer-dist --no-progress --ignore-platform-reqs", $code);
passthru("cd {$directory} && {$environment}composer update --no-interaction --prefer-dist --no-progress --ignore-platform-reqs{$lowest}", $code);

// The test contract: `composer test` is the unit tier and runs on
// a bare host; `composer test:e2e` runs on the host against the
Expand Down Expand Up @@ -815,9 +828,11 @@ switch ($command) {
release <name> <version> [--dry-run]
preview notes, tag <name>/<version> and push it (CI does the rest)
validate check package composer.json conventions
check [name...] [--fix] run pint, phpstan and rector against packages
test [name...] [--linked] install dependencies and run package test suites
(--linked resolves monorepo siblings from the local checkout)
check [name...] [--fix] [--lowest] run pint, phpstan and rector against packages
test [name...] [--linked|--lowest] install dependencies and run package test suites
(--linked resolves monorepo siblings from the local checkout;
--lowest resolves every constraint to its oldest allowed release,
proving the declared ranges actually carry the APIs the code calls)
benchmark [name...] run the `bench` script of packages that declare one
dependents <name...> list packages that depend on the given ones (transitively)
graph regenerate the dependency graph in README.md
Expand Down
2 changes: 1 addition & 1 deletion packages/queue/composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@
},
"require": {
"php": ">=8.5",
"utopia-php/di": "^0.3",
"utopia-php/di": "^0.3.1",
"utopia-php/lock": "^0.3",
"utopia-php/servers": "^0.4",
"utopia-php/pools": "^2.1",
Expand Down
Loading