Skip to content

Commit b2e3cdb

Browse files
committed
wip
1 parent a191c95 commit b2e3cdb

14 files changed

Lines changed: 365 additions & 661 deletions

README.md

Lines changed: 2 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -107,7 +107,7 @@ Moreover, even if a plugin is closed, its existing versions are still downloadab
107107
}
108108
```
109109

110-
To catch these closed plugins, `WP Org Closed Plugin` queries [WordPress.org API](https://codex.wordpress.org/WordPress.org_API#Plugins) to check whether a plugin is closed and mark them as abandoned in Composer.
110+
To catch these closed plugins, `WP Org Closed Plugin` queries [wp-packages.org](https://wp-packages.org/) for the list of plugins closed on WordPress.org and marks them as abandoned in Composer.
111111

112112
## What to do when a plugin is closed?
113113

@@ -153,58 +153,14 @@ You should run `composer audit` without `--locked` to check for closed plugins.
153153

154154
### Cache
155155

156-
WordPress.org API responses are cached for 10 minutes.
156+
The list of closed plugins is fetched from [wp-packages.org](https://wp-packages.org/) in a single request and cached for 10 minutes.
157157

158158
If you must clear the cache, delete the `<composer-cache-dir>/wp-org-closed-plugin` directory.
159159

160160
```sh
161161
rm -rf $(composer config cache-dir)/wp-org-closed-plugin
162162
```
163163

164-
### Why `allow_self_signed` when connecting to `https://api.wordpress.org`?
165-
166-
> [!IMPORTANT]
167-
> **Help Wanted!**
168-
>
169-
> Please send pull requests if you know how to get around the error:
170-
>
171-
> ```console
172-
> $ curl --http3-only 'https://api.wordpress.org/plugins/info/1.2/?action=plugin_information&slug=better-delete-revision'
173-
> curl: (56) ngtcp2_conn_writev_stream returned error: ERR_DRAINING
174-
> ```
175-
176-
It is a hack to disallow HTTP/3, forcing `HttpDownloader` to use `RemoteFilesystem` instead of `CurlDownloader`.
177-
178-
I suspect api.wordpress.org does not properly support HTTP/3:
179-
180-
```console
181-
$ curl --http1.1 'https://api.wordpress.org/plugins/info/1.2/?action=plugin_information&slug=better-delete-revision'
182-
...json response
183-
184-
$ curl --http2 'https://api.wordpress.org/plugins/info/1.2/?action=plugin_information&slug=better-delete-revision'
185-
...json response
186-
187-
$ curl --http3-only 'https://api.wordpress.org/plugins/info/1.2/?action=plugin_information&slug=better-delete-revision'
188-
...sometimes json response
189-
...but most of the time ERR_DRAINING
190-
curl: (56) ngtcp2_conn_writev_stream returned error: ERR_DRAINING
191-
```
192-
193-
See:
194-
- https://github.com/composer/composer/pull/12363
195-
- https://github.com/composer/composer/blob/f5854b140ca27164d352ce30deece798acf3e36b/src/Composer/Util/HttpDownloader.php#L537
196-
- https://github.com/typisttech/wp-org-closed-plugin/pull/22
197-
198-
> [!TIP]
199-
> **Hire Tang Rufus!**
200-
>
201-
> There is no need to understand any of these quirks.
202-
> Let me handle them for you.
203-
> I am seeking my next job, freelance or full-time.
204-
>
205-
> If you are hiring PHP / Ruby / Go developers,
206-
> contact me at https://typist.tech/contact/
207-
208164
## Installation
209165

210166
```sh

src/MarkClosedPluginAsAbandoned.php

Lines changed: 5 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,12 @@
44

55
namespace TypistTech\WpOrgClosedPlugin;
66

7-
use Composer\Cache;
7+
use Composer\Cache as ComposerCache;
88
use Composer\Composer;
99
use Composer\IO\IOInterface;
1010
use Composer\Package\CompletePackageInterface;
11-
use TypistTech\WpOrgClosedPlugin\WpOrg\Api\ArrayCache;
12-
use TypistTech\WpOrgClosedPlugin\WpOrg\Api\CacheProxy;
11+
use TypistTech\WpOrgClosedPlugin\WpOrg\Api\Cache;
1312
use TypistTech\WpOrgClosedPlugin\WpOrg\Api\Client;
14-
use TypistTech\WpOrgClosedPlugin\WpOrg\Api\FileCache;
1513
use TypistTech\WpOrgClosedPlugin\WpOrg\UrlParser\DownloadUrlParser;
1614
use TypistTech\WpOrgClosedPlugin\WpOrg\UrlParser\MultiUrlParser;
1715
use TypistTech\WpOrgClosedPlugin\WpOrg\UrlParser\SvnUrlParser;
@@ -21,26 +19,19 @@
2119
{
2220
public static function create(Composer $composer, IOInterface $io): self
2321
{
24-
$loop = $composer->getLoop();
25-
2622
$config = $composer->getConfig();
2723
$cachePath = "{$config->get('cache-dir')}/wp-org-closed-plugin";
28-
$composerCache = new Cache($io, $cachePath);
24+
$composerCache = new ComposerCache($io, $cachePath);
2925
$composerCache->setReadOnly($config->get('cache-read-only'));
30-
$cache = new CacheProxy(
31-
new ArrayCache,
32-
new FileCache($composerCache),
33-
);
3426

3527
return new self(
3628
new MultiUrlParser(
3729
new DownloadUrlParser,
3830
new SvnUrlParser,
3931
),
4032
new Client(
41-
$loop->getHttpDownloader(),
42-
$loop,
43-
$cache,
33+
$composer->getLoop()->getHttpDownloader(),
34+
new Cache($composerCache),
4435
),
4536
$io,
4637
);
@@ -52,28 +43,6 @@ public function __construct(
5243
private IOInterface $io,
5344
) {}
5445

55-
public function warmCache(CompletePackageInterface ...$packages): void
56-
{
57-
$slugs = array_map(
58-
fn (CompletePackageInterface $package): ?string => $this->slug(
59-
...$package->getDistUrls(),
60-
...$package->getSourceUrls(),
61-
),
62-
$packages,
63-
);
64-
$slugs = array_filter($slugs, static fn (?string $slug) => $slug !== null);
65-
66-
$slugCount = count($slugs);
67-
$message = sprintf(
68-
'Warming WordPress.org plugin status cache for %d %s',
69-
$slugCount,
70-
$slugCount === 1 ? 'slug' : 'slugs',
71-
);
72-
$this->io->debug($message);
73-
74-
$this->client->warmCache(...$slugs);
75-
}
76-
7746
public function __invoke(CompletePackageInterface $package): void
7847
{
7948
$this->io->debug(

src/Plugin.php

Lines changed: 10 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -48,14 +48,8 @@ public function uninstall(Composer $composer, IOInterface $io): void
4848
public static function getSubscribedEvents(): array
4949
{
5050
return [
51-
PackageEvents::PRE_PACKAGE_INSTALL => [
52-
['warmCache', PHP_INT_MAX - 500],
53-
['markClosedAsAbandoned', PHP_INT_MAX - 1000],
54-
],
55-
PackageEvents::PRE_PACKAGE_UPDATE => [
56-
['warmCache', PHP_INT_MAX - 500],
57-
['markClosedAsAbandoned', PHP_INT_MAX - 1000],
58-
],
51+
PackageEvents::PRE_PACKAGE_INSTALL => ['markClosedAsAbandoned', PHP_INT_MAX - 1000],
52+
PackageEvents::PRE_PACKAGE_UPDATE => ['markClosedAsAbandoned', PHP_INT_MAX - 1000],
5953

6054
ScriptEvents::POST_INSTALL_CMD => ['markClosedLockedPackagesIfNotAlready', PHP_INT_MAX - 1000],
6155
ScriptEvents::POST_UPDATE_CMD => ['markClosedLockedPackagesIfNotAlready', PHP_INT_MAX - 1000],
@@ -64,22 +58,6 @@ public static function getSubscribedEvents(): array
6458
];
6559
}
6660

67-
public function warmCache(PackageEvent $event): void
68-
{
69-
$packages = array_map(
70-
static fn ($operation) => match (true) {
71-
$operation instanceof InstallOperation => $operation->getPackage(),
72-
$operation instanceof UpdateOperation => $operation->getTargetPackage(),
73-
default => null,
74-
},
75-
$event->getOperations(),
76-
);
77-
$packages = array_filter($packages, static fn ($package) => $package instanceof CompletePackageInterface);
78-
$packages = array_filter($packages, static fn ($package) => ! $package->isAbandoned());
79-
80-
$this->markClosedAsAbandoned->warmCache(...$packages);
81-
}
82-
8361
public function markClosedAsAbandoned(PackageEvent $event): void
8462
{
8563
$operation = $event->getOperation();
@@ -109,18 +87,18 @@ public function markClosedLockedPackagesIfNotAlready(ScriptEvent $event): void
10987
->getLocker()
11088
->getLockedRepository(true);
11189

112-
$packages = $lockedRepository->getPackages();
113-
$packages = array_filter($packages, static fn ($package) => $package instanceof CompletePackage);
114-
$packages = array_filter($packages, static fn ($package) => ! $package->isAbandoned());
11590
$packages = array_filter(
116-
$packages,
117-
fn ($package) => ! in_array($package->getPrettyName(), $this->marked, true),
91+
$lockedRepository->getPackages(),
92+
static fn ($package) => $package instanceof CompletePackage && ! $package->isAbandoned(),
11893
);
11994

120-
$this->markClosedAsAbandoned->warmCache(...$packages);
121-
12295
foreach ($packages as $package) {
123-
$this->marked[] = $package->getPrettyName();
96+
$prettyName = $package->getPrettyName();
97+
if (in_array($prettyName, $this->marked, true)) {
98+
continue;
99+
}
100+
101+
$this->marked[] = $prettyName;
124102
$this->markClosedAsAbandoned->__invoke($package);
125103
}
126104
}

src/WpOrg/Api/ArrayCache.php

Lines changed: 0 additions & 21 deletions
This file was deleted.

src/WpOrg/Api/Cache.php

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace TypistTech\WpOrgClosedPlugin\WpOrg\Api;
6+
7+
use Composer\Cache as ComposerCache;
8+
9+
class Cache
10+
{
11+
private const int TTL = 600;
12+
13+
private const string KEY = 'closed.json';
14+
15+
public function __construct(
16+
private readonly ComposerCache $cache,
17+
) {}
18+
19+
public function read(): ?string
20+
{
21+
$age = $this->cache->getAge(self::KEY);
22+
23+
// Missed or expired.
24+
if ($age === false || $age > self::TTL) {
25+
return null;
26+
}
27+
28+
$content = $this->cache->read(self::KEY);
29+
30+
return $content === false ? null : $content;
31+
}
32+
33+
public function write(string $contents): void
34+
{
35+
if ($this->cache->isReadOnly()) {
36+
return;
37+
}
38+
39+
$this->cache->write(self::KEY, $contents);
40+
}
41+
}

src/WpOrg/Api/CacheInterface.php

Lines changed: 0 additions & 12 deletions
This file was deleted.

src/WpOrg/Api/CacheProxy.php

Lines changed: 0 additions & 34 deletions
This file was deleted.

0 commit comments

Comments
 (0)