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