Skip to content

Commit 1e95b8e

Browse files
authored
Merge pull request #1071 from WyriHaximus/install-make-on-install-or-update-PIPEPIPE-true-on-composer-post-install-and-update-commands
Install make on-install-or-update || true on composer post install and update commands
2 parents 3fa5c90 + 510f0e3 commit 1e95b8e

File tree

5 files changed

+220
-4
lines changed

5 files changed

+220
-4
lines changed

composer.json

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
"name": "wyrihaximus/test-utilities",
33
"description": "\ud83d\udee0\ufe0f Test utilities for api-clients packages",
44
"license": "MIT",
5+
"type": "composer-plugin",
56
"authors": [
67
{
78
"name": "Cees-Jan Kiewiet",
@@ -10,6 +11,9 @@
1011
],
1112
"require": {
1213
"php": "^8.4",
14+
"ext-hash": "^8.4",
15+
"ext-json": "^8.4",
16+
"composer-plugin-api": "^2",
1317
"ergebnis/composer-normalize": "^2.47.0",
1418
"ergebnis/phpunit-slow-test-detector": "^2.19.1",
1519
"icanhazstring/composer-unused": "^0.9.4",
@@ -31,7 +35,7 @@
3135
},
3236
"conflict": {
3337
"composer/pcre": "<3.3.2",
34-
"wyrihaximus/makefiles": "<0.4.0"
38+
"wyrihaximus/makefiles": "<0.5.0"
3539
},
3640
"suggest": {
3741
"wyrihaximus/async-test-utilities": "The recommended addition to this package when building ReactPHP packages and projects.",
@@ -64,6 +68,7 @@
6468
"sort-packages": true
6569
},
6670
"extra": {
71+
"class": "WyriHaximus\\TestUtilities\\Composer\\Installer",
6772
"phpstan": {
6873
"includes": [
6974
"extension.neon"
@@ -76,6 +81,10 @@
7681
],
7782
"post-update-cmd": [
7883
"make on-install-or-update || true"
84+
],
85+
"pre-autoload-dump": [
86+
"WyriHaximus\\TestUtilities\\Composer\\Installer::findEventListeners",
87+
"make on-install-or-update || true"
7988
]
8089
}
8190
}

composer.lock

Lines changed: 5 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

etc/qa/composer-require-checker.json

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,10 @@
33
"null", "true", "false",
44
"static", "self", "parent",
55
"array", "string", "int", "float", "bool", "iterable", "callable", "void", "object",
6-
"Rector\\Configuration\\RectorConfigBuilder", "Rector\\Config\\RectorConfig"
6+
"Rector\\Configuration\\RectorConfigBuilder", "Rector\\Config\\RectorConfig",
7+
"Composer\\Composer", "Composer\\Config", "Composer\\EventDispatcher\\EventSubscriberInterface",
8+
"Composer\\IO\\IOInterface", "Composer\\Package\\RootPackageInterface", "Composer\\Plugin\\PluginInterface",
9+
"Composer\\Script\\Event", "Composer\\Script\\ScriptEvents", "JetBrains\\PHPStormStub\\PhpStormStubsMap"
710
],
811
"php-core-extensions" : [
912
"Core",

etc/qa/phpstan.neon

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,9 @@ parameters:
1010
-
1111
message: '#Error suppression via "@" should not be used.#'
1212
path: ../../src/TestCase.php
13+
-
14+
message: '#Parameter \#1 \$scriptsSection of static method WyriHaximus\\TestUtilities\\Composer\\Installer::addMakeOnInstallOrUpdateToScriptsSectionAndRemoveCommandsItReplaces\(\) expects array<int, string>, mixed given.#'
15+
path: ../../src/Composer/Installer.php
1316

1417
includes:
1518
- ../../extension.neon

src/Composer/Installer.php

Lines changed: 198 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,198 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace WyriHaximus\TestUtilities\Composer;
6+
7+
use Composer\Composer;
8+
use Composer\EventDispatcher\EventSubscriberInterface;
9+
use Composer\IO\IOInterface;
10+
use Composer\Plugin\PluginInterface;
11+
use Composer\Script\Event;
12+
use Composer\Script\ScriptEvents;
13+
use Exception;
14+
15+
use function array_key_exists;
16+
use function array_keys;
17+
use function dirname;
18+
use function file_exists;
19+
use function file_get_contents;
20+
use function file_put_contents;
21+
use function hash;
22+
use function hash_equals;
23+
use function is_array;
24+
use function is_string;
25+
use function json_decode;
26+
use function json_encode;
27+
use function preg_replace;
28+
29+
use const DIRECTORY_SEPARATOR;
30+
use const JSON_PRETTY_PRINT;
31+
use const JSON_UNESCAPED_SLASHES;
32+
use const PHP_INT_MAX;
33+
34+
final class Installer implements PluginInterface, EventSubscriberInterface
35+
{
36+
/** @return array<string, array<string|int>> */
37+
public static function getSubscribedEvents(): array
38+
{
39+
return [ScriptEvents::PRE_AUTOLOAD_DUMP => ['findEventListeners', PHP_INT_MAX]];
40+
}
41+
42+
public function activate(Composer $composer, IOInterface $io): void
43+
{
44+
// does nothing, see getSubscribedEvents() instead.
45+
}
46+
47+
public function deactivate(Composer $composer, IOInterface $io): void
48+
{
49+
// does nothing, see getSubscribedEvents() instead.
50+
}
51+
52+
public function uninstall(Composer $composer, IOInterface $io): void
53+
{
54+
// does nothing, see getSubscribedEvents() instead.
55+
}
56+
57+
/**
58+
* Called before every dump autoload, generates a fresh PHP class.
59+
*
60+
* @phpstan-ignore shipmonk.deadMethod
61+
*/
62+
public static function findEventListeners(Event $event): void
63+
{
64+
$rootPackagePath = dirname(self::getVendorDir($event->getComposer())) . DIRECTORY_SEPARATOR;
65+
if (! file_exists($rootPackagePath . '/composer.json')) {
66+
return;
67+
}
68+
69+
$jsonRaw = file_get_contents($rootPackagePath . '/composer.json');
70+
if (! is_string($jsonRaw)) {
71+
return;
72+
}
73+
74+
$json = json_decode($jsonRaw, true);
75+
if (! is_array($json)) {
76+
return;
77+
}
78+
79+
if (array_key_exists('name', $json) && $json['name'] === 'wyrihaximus/test-utilities') {
80+
self::addMakeOnInstallOrUpdate($event->getIO(), $rootPackagePath);
81+
82+
return;
83+
}
84+
85+
if (! array_key_exists('require-dev', $json)) {
86+
return;
87+
}
88+
89+
if (! is_array($json['require-dev'])) {
90+
return;
91+
}
92+
93+
foreach (array_keys($json['require-dev']) as $package) {
94+
if ($package === 'wyrihaximus/test-utilities') {
95+
self::addMakeOnInstallOrUpdate($event->getIO(), $rootPackagePath);
96+
97+
return;
98+
}
99+
}
100+
}
101+
102+
private static function addMakeOnInstallOrUpdate(IOInterface $io, string $rootPackagePath): void
103+
{
104+
$io->write('<info>wyrihaximus/test-utilities:</info> Adding <fg=cyan>make on-install-or-update || true</> to scripts');
105+
$composerJsonString = file_get_contents($rootPackagePath . '/composer.json');
106+
if (! is_string($composerJsonString)) {
107+
$io->write('<error>wyrihaximus/test-utilities:</error> Unable to read <fg=cyan>composer.json</> aborting');
108+
109+
return;
110+
}
111+
112+
$composerJson = json_decode($composerJsonString, true);
113+
$composerJsonHash = hash('sha512', (string) json_encode($composerJson, JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT));
114+
if (is_array($composerJson) && array_key_exists('scripts', $composerJson) && is_array($composerJson['scripts'])) {
115+
if (array_key_exists('post-install-cmd', $composerJson['scripts'])) {
116+
$composerJson['scripts']['post-install-cmd'] = self::addMakeOnInstallOrUpdateToScriptsSectionAndRemoveCommandsItReplaces($composerJson['scripts']['post-install-cmd']);
117+
}
118+
119+
if (array_key_exists('post-update-cmd', $composerJson['scripts'])) {
120+
$composerJson['scripts']['post-update-cmd'] = self::addMakeOnInstallOrUpdateToScriptsSectionAndRemoveCommandsItReplaces($composerJson['scripts']['post-update-cmd']);
121+
}
122+
}
123+
124+
$replacementComposerJsonString = json_encode($composerJson, JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT);
125+
if (is_string($replacementComposerJsonString)) {
126+
$replacementComposerJsonHash = hash('sha512', $replacementComposerJsonString);
127+
if (! hash_equals($composerJsonHash, $replacementComposerJsonHash)) {
128+
$replacementComposerJsonString = preg_replace('/^( +?)\\1(?=[^ ])/m', '$1', $replacementComposerJsonString);
129+
if (is_string($replacementComposerJsonString)) {
130+
$io->write('<info>wyrihaximus/test-utilities:</info> Writing new <fg=cyan>composer.json</>');
131+
file_put_contents($rootPackagePath . '/composer.json', $replacementComposerJsonString);
132+
}
133+
}
134+
}
135+
136+
if (is_array($composerJson) && array_key_exists('scripts', $composerJson) && is_array($composerJson['scripts'])) {
137+
if (array_key_exists('post-install-cmd', $composerJson['scripts'])) {
138+
$composerJson['scripts']['post-install-cmd'] = self::addMakeOnInstallOrUpdateToScriptsSectionAndRemoveCommandsItReplaces($composerJson['scripts']['post-install-cmd']);
139+
}
140+
141+
if (array_key_exists('post-update-cmd', $composerJson['scripts'])) {
142+
$composerJson['scripts']['post-update-cmd'] = self::addMakeOnInstallOrUpdateToScriptsSectionAndRemoveCommandsItReplaces($composerJson['scripts']['post-update-cmd']);
143+
}
144+
}
145+
146+
$replacementComposerJsonString = json_encode($composerJson, JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT);
147+
if (is_string($replacementComposerJsonString)) {
148+
$replacementComposerJsonHash = hash('sha512', $replacementComposerJsonString);
149+
if (! hash_equals($composerJsonHash, $replacementComposerJsonHash)) {
150+
$replacementComposerJsonString = preg_replace('/^( +?)\\1(?=[^ ])/m', '$1', $replacementComposerJsonString);
151+
if (is_string($replacementComposerJsonString)) {
152+
$io->write('<info>wyrihaximus/test-utilities:</info> Writing new <fg=cyan>composer.json</>');
153+
file_put_contents($rootPackagePath . '/composer.json', $replacementComposerJsonString);
154+
}
155+
}
156+
}
157+
158+
$io->write('<info>wyrihaximus/test-utilities:</info> Finished <fg=cyan>make on-install-or-update || true</> to scripts');
159+
}
160+
161+
/**
162+
* @param array<int, string> $scriptsSection
163+
*
164+
* @return array<int, string>
165+
*/
166+
private static function addMakeOnInstallOrUpdateToScriptsSectionAndRemoveCommandsItReplaces(array $scriptsSection): array
167+
{
168+
foreach ($scriptsSection as $script) {
169+
if ($script === 'make on-install-or-update || true') {
170+
return $scriptsSection;
171+
}
172+
}
173+
174+
$scripts = [];
175+
foreach ($scriptsSection as $script) {
176+
if ($script === 'composer normalize' || $script === 'composer update --lock --no-scripts') {
177+
continue;
178+
}
179+
180+
$scripts[] = $script;
181+
}
182+
183+
$scripts[] = 'make on-install-or-update || true';
184+
185+
return $scripts;
186+
}
187+
188+
/** @return non-empty-string */
189+
private static function getVendorDir(Composer $composer): string
190+
{
191+
$vendorDir = $composer->getConfig()->get('vendor-dir');
192+
if ($vendorDir === '' || ! file_exists($vendorDir)) {
193+
throw new Exception('vendor-dir must be a string');
194+
}
195+
196+
return $vendorDir;
197+
}
198+
}

0 commit comments

Comments
 (0)