Composer 2.9.x fails to validate expanded composer.json when project or CI steps add repositories
Problem/Motivation
expand_composer_json.php can generate an invalid repositories structure in the expanded composer.json if a project or CI step adds additional repositories (for example via composer config repositories.NAME or composer repo add).
The hardcoded default template treats repositories as an object, while CI-added repositories are effectively a list. When both are merged, this produces a mixed structure that ends up serialized as a JSON object with numeric keys.
Composer 2.9.x performs stricter schema validation and rejects this structure with errors such as:
repositories[n] : Matched a schema which it should not
repositories[n] : Failed to match all schemas
repositories[n] : Object value found, but a boolean is required
repositories[n] : Does not have a value in the enumeration [false]
repositories[n] : Failed to match at least one schema
Older Composer versions accepted this output more leniently, so the failure appears when upgrading to Composer 2.9.x.
Composer documentation states that the object/map form of repositories is deprecated and that the list/array form is preferred, as repository order is significant (source: https://getcomposer.org/doc/04-schema.md#repositories).
Steps to reproduce
-
Create a minimal
composer.jsonthat represents a custom Drupal module and contains a single additional repository (as commonly added by CI steps):{
"name": "drupal/example_module",
"type": "drupal-module",
"repositories": [
{
"name": "example",
"type": "package",
"package": {
"name": "example/library",
"version": "1.0.0",
"type": "drupal-library",
"dist": {
"type": "zip",
"url": "https://example.com/library.zip"
}
}
}
],
"require": {},
"require-dev": {}
} -
Use
expand_composer_json.phpfrom the GitLab templates:curl -LO https://git.drupalcode.org/project/gitlab_templates/-/raw/main/scripts/expand_composer_json.php
chmod +x expand_composer_json.php -
Set the minimal environment variables required by the script:
export CI_PROJECT_NAME=example_module
export PROJECT_NAME=example_module
export DRUPAL_CORE=11.3.2
export _WEB_ROOT=web -
Run the expansion script:
php expand_composer_json.php --input composer.json
-
Inspect the resulting
repositoriessection in the expandedcomposer.json. It will be emitted as a JSON object with mixed keys, similar to:"repositories": {
"0": {
"name": "example",
"type": "package",
"package": { ... }
},
"drupal": {
"type": "composer",
"url": "https://packages.drupal.org/8"
}
} -
Validate the expanded
composer.jsonusing Composer 2.9.x:docker run --rm -v "$PWD":/app -w /app composer:2.9 validate --strict
-
Composer fails schema validation with errors similar to:
"./composer.json" does not match the expected JSON schema:
- repositories[0] : Matched a schema which it should not
- repositories[0] : Failed to match all schemas
- repositories[0] : Object value found, but a boolean is required
- repositories[0] : Does not have a value in the enumeration [false]
- repositories[0] : Failed to match at least one schema
Proposed resolution
Normalize the repositories section in expand_composer_json.php so that it always uses the recommended list/array form.
- Emit default repositories as a list instead of an object.
- After deep-merging project and default configuration, normalize the result so that:
- mixed numeric/string keys are converted to a sequential list,
- map keys are preserved the as
nameproperty if not already set, - deprecated map-form disabling (e.g.
"packagist.org": false) is preserved using its documented list-form equivalent.
This aligns the generated composer.json with Composer’s current recommendations and restores compatibility with Composer 2.9.x while remaining compatible with older Composer versions.
Remaining tasks
- Implement
- Test
User interface changes
N/A
API changes
N/A
Data model changes
N/A