Skip to content

ISSUE-370: composer configuration checks #717

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 29 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 7 commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
531adbd
ISSUE-370: composer configuration checks
beto-aveiga Oct 2, 2024
12d7325
ISSUE-370: final code style fixes
beto-aveiga Oct 2, 2024
5ce7255
ISSUE-370: check for empty instead of negating the value
beto-aveiga Oct 2, 2024
b99340d
ISSUE-370: check empty before array check
beto-aveiga Oct 2, 2024
d8c956f
ISSUE-370: remove code for manual tests | typo
beto-aveiga Oct 2, 2024
a327d19
ISSUE-370: typo [skip ci]
beto-aveiga Oct 2, 2024
43daf67
ISSUE-370: test composer checks
beto-aveiga Oct 2, 2024
91a3580
Update src/ScaffoldInstallerPlugin.php
beto-aveiga Oct 3, 2024
6826cc9
ISSUE-370: avoid prefixing private methods [skip ci]
beto-aveiga Oct 3, 2024
151dac1
ISSUE-370: determine if json file for patches was parsed as expected …
beto-aveiga Oct 3, 2024
85a82b5
ISSUE-370: improve string readability [skip ci]
beto-aveiga Oct 3, 2024
2670c9a
ISSUE-370: create a separate class for composer checks [skip ci]
beto-aveiga Oct 3, 2024
3fe841c
Merge branch 'issue-370-add-validation-and-warnings-for-composer-patc…
beto-aveiga Oct 3, 2024
fd93af6
ISSUE-370: renaming test [skip ci]
beto-aveiga Oct 4, 2024
b273d00
ISSUE-370: move logic to another file [skip ci]
beto-aveiga Oct 4, 2024
b9247ec
ISSUE-370: opt out options for composer checks [skip ci]
beto-aveiga Oct 4, 2024
c34bfef
ISSUE-370: typo [skip ci]
beto-aveiga Oct 4, 2024
0f9280d
ISSUE-370: improve documentatio
beto-aveiga Oct 4, 2024
92d3c4d
Merge branch 'main' of github.com:Lullabot/drainpipe into issue-370-a…
beto-aveiga Oct 4, 2024
5e78a85
ISSUE-370: fixig path
beto-aveiga Oct 4, 2024
87dc05a
ISSUE-370: debug
beto-aveiga Oct 4, 2024
6b0e9a9
ISSUE-370: debug
beto-aveiga Oct 4, 2024
f17afc8
ISSUE-370: fixing path to run composer check scripts
beto-aveiga Oct 4, 2024
ef8e443
ISSUE-370: documetation
beto-aveiga Oct 4, 2024
47be2d4
Merge branch 'main' into issue-370-add-validation-and-warnings-for-co…
mrdavidburns Mar 18, 2025
04202dc
ISSUE-370: Add composer check plugin.
rene-ipiales-lullabot Mar 25, 2025
f477f78
ISSUE-370: Add composer composer-checks plugin for test.
rene-ipiales-lullabot Mar 25, 2025
2f00c5d
ISSUE-370: Add composer composer-checks plugin for github actions.
rene-ipiales-lullabot Mar 25, 2025
f85b4d1
ISSUE-370: Add composer composer-checks plugin for github actions and…
rene-ipiales-lullabot Mar 25, 2025
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
108 changes: 108 additions & 0 deletions .github/workflows/TestScaffoldInstallerPlugin.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
name: Test Scaffold Installer Plugin - Composer Checks
on:
push:
branches:
- main
pull_request:
types: [opened, synchronize, reopened]

concurrency:
group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }}
cancel-in-progress: true

jobs:
Test-Scaffold-Installer-Plugin:
runs-on: ubuntu-24.04
strategy:
matrix:
php-version: [ 8.3 ]
steps:

- name: Create a Drupal project
run: composer create-project drupal/recommended-project${{ matrix.drupal-version }} . --ignore-platform-reqs

- uses: actions/checkout@v4
with:
path: drainpipe

- name: Setup drainpipe-dev
run: mv drainpipe/drainpipe-dev .

- uses: ./drainpipe/scaffold/github/actions/common/set-env

- name: Install DDEV
uses: ./drainpipe/scaffold/github/actions/common/ddev
with:
composer-cache-dir: composer-cache
git-name: Drainpipe Bot
git-email: no-reply@example.com

- name: Setup Project
run: |
ddev config --auto
ddev config --php-version ${{ matrix.php-version }}
ddev start
ddev composer config extra.drupal-scaffold.gitignore true
ddev composer config --json extra.drupal-scaffold.allowed-packages '["lullabot/drainpipe-dev", "lullabot/drainpipe"]'
ddev composer config --no-plugins allow-plugins.composer/installers true
ddev composer config --no-plugins allow-plugins.drupal/core-composer-scaffold true
ddev composer config --no-plugins allow-plugins.lullabot/drainpipe true
ddev composer config --no-plugins allow-plugins.lullabot/drainpipe-dev true
ddev composer config repositories.drainpipe --json '{"type": "path", "url": "drainpipe", "options": {"symlink": false}}'
ddev composer config repositories.drainpipe-dev --json '{"type": "path", "url": "drainpipe-dev", "options": {"symlink": false}}'
ddev composer config minimum-stability dev
ddev composer require "lullabot/drainpipe @dev" --with-all-dependencies
ddev composer require "lullabot/drainpipe-dev @dev" --dev --with-all-dependencies

- name: Composer checks Tests
run: |

check_composer_install_contents() {
local string_to_check="$1"
local expected="$2"
local file="composer-install-output.txt"

ddev composer install > $file 2>&1

# Search for the string in the file
if grep -q "$string_to_check" "$file"; then
# String is found
if [[ "$expected" -eq 0 ]]; then
echo "Error: String '$string_to_check' found, but it was not expected."
exit 1
fi
else
# String is not found
if [[ "$expected" -eq 1 ]]; then
echo "Error: String '$string_to_check' not found, but it was expected."
exit 1
fi
fi
}

ddev composer config --unset extra.composer-exit-on-patch-failure
check_composer_install_contents "Break Composer install if patches don't apply" 1

ddev composer config extra.composer-exit-on-patch-failure --json false
check_composer_install_contents "Break Composer install if patches don't apply" 1

ddev composer config extra.composer-exit-on-patch-failure --json true
check_composer_install_contents "Break Composer install if patches don't apply" 0

ddev composer config extra.patchLevel --json '{"drupal/core": "-p2"}'
check_composer_install_contents "Configure Composer patches to use \`-p2\` as \`patchLevel\` for Drupal core" 0

ddev composer config --unset extra.patchLevel
check_composer_install_contents "Configure Composer patches to use \`-p2\` as \`patchLevel\` for Drupal core" 1

ddev composer config extra.patches-file "composer.patches.json"
check_composer_install_contents "Store Composer patches configuration in \`composer.json\`" 1

ddev composer config --unset extra.patches-file
check_composer_install_contents "Store Composer patches configuration in \`composer.json\`" 0

ddev composer config extra.patches --json '{"drupal":{"issue-x":"http"}}'
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a great addition that aligns with our ADR: Use local copies of patch files

I know we have completely updated all our projects to adhere to this, so having a check that will enforce this would be a nice addition.

check_composer_install_contents "Use local copies of patch files." 1

ddev composer config extra.patches --json '{"drupal":{"issue-x":"local-path"}}'
check_composer_install_contents "Use local copies of patch files." 0
130 changes: 130 additions & 0 deletions src/ScaffoldInstallerPlugin.php
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,130 @@ public static function getSubscribedEvents()
];
}

/**
* Composer configuration advice: Use local copies of patch files.
*
* @return void
*/
private function _checkComposerPatchesAreLocal()
{
$patchesInComposer = $this->extra['patches'] ?? false;
$patchesInExtraFile = $this->extra['patches-file'] ?? false;

// Patches defined on a separate file.
if ($patchesInExtraFile) {
if (!file_exists($patchesInExtraFile)) {
$this->io->warning("The patches file `$patchesInExtraFile` can't be read.");
return;
}

$patchesJsonEncoded = file_get_contents($patchesInExtraFile);
$patchesContent = json_decode($patchesJsonEncoded, true)['patches'] ?? [];
} else if ($patchesInComposer) {
$patchesContent = $patchesInComposer;
}

if (empty($patchesContent)) {
return;
}

// Patches content is not a string.
if (!is_array($patchesContent)) {
$this->io->warning("The patches content can't be validated. Check your patches defined in Composer.");
return;
}

// Collecting remote patches (if any).
$remotePatches = [];
foreach ($patchesContent as $projectName => $patches) {
foreach ($patches as $patchName => $patchUri) {
if (str_starts_with($patchUri, 'http')) {
$remotePatches[$projectName] = "$patchName | $patchUri";
}
}
}

if (!$remotePatches) {
return;
}

// Collect the remote patches info.
$patchesInfo = PHP_EOL;
$count = 1;
foreach ($remotePatches as $projectName => $remote_patch) {
$patchesInfo.= "[$count] $projectName: $remote_patch " . PHP_EOL;
$count++;
}
$patchesInfo = rtrim($patchesInfo, PHP_EOL);

// Communicate the user.
$msg = 'Use local copies of patch files. See';
$link = 'https://architecture.lullabot.com/adr/20220429-composer-patch-files/';
$this->io->warning("$msg $link $patchesInfo");
}

/**
* Composer configuration advice: "composer-exit-on-patch-failure": true
*
* @return void
*/
private function _checkComposerBreaksIfPatchesDoNotApply()
{
$composerExitsOnPatchFailure = $this->extra['composer-exit-on-patch-failure']
?? false;
$composerExitsOnPatchFailureBool = is_bool($composerExitsOnPatchFailure);
$condition1 = !$composerExitsOnPatchFailure ||
!$composerExitsOnPatchFailureBool;
$condition2 = $composerExitsOnPatchFailureBool
&& $composerExitsOnPatchFailure !== true;
$warn = $condition1 && $condition2;

if (!$warn) {
return;
}

$msg = 'Break Composer install if patches don\'t apply. See';
$link = 'https://architecture.lullabot.com/adr/20220429-composer-exit-failure/';
$this->io->warning("$msg $link");
}

/**
* Composer configuration advice: "patchLevel": {"drupal/core": "-p2"}
*
* @return void
*/
private function _checkDrupalCoreComposerPatchesLevel()
{
$patchLevel = $this->extra['patchLevel']['drupal/core'] ?? false;
$patchLevelIsString = is_string($patchLevel);
$warn = (!$patchLevel || !$patchLevelIsString)
|| ($patchLevelIsString && $patchLevel != '-p2');

if (!$warn) {
return;
}

$msg = 'Configure Composer patches to use `-p2` as `patchLevel` for Drupal core. See';
$link = 'https://architecture.lullabot.com/adr/20220429-composer-patchlevel/';
$this->io->warning("$msg $link");
}

/**
* Composer configuration advice: "patches-file" is not set/used.
*
* @return void
*/
private function _checkPatchesStoredInComposerJson()
{
if (!isset($this->extra['patches-file'])) {
return;
}

$msg = 'Store Composer patches configuration in `composer.json`. See';
$link = 'https://architecture.lullabot.com/adr/20220429-composer-patches-inline/';
$this->io->warning("$msg $link");
}

/**
* Handle post install command events.
*
Expand All @@ -84,6 +208,12 @@ public function onPostInstallCmd(Event $event)
$this->installDdevCommand();
$this->installCICommands();
$this->installEnvSupport();

// Composer checks.
$this->_checkDrupalCoreComposerPatchesLevel();
$this->_checkComposerBreaksIfPatchesDoNotApply();
$this->_checkPatchesStoredInComposerJson();
$this->_checkComposerPatchesAreLocal();
}

/**
Expand Down
Loading