Skip to content

Commit c3b4b11

Browse files
Release v2.0.30: PHP 8.1+, opt-in wrapper updates, and release docs
Document breaking PHP requirement and wrapper overwrite policy, align tests with opt-in auto_update_wrapper behavior, and finalize changelog/upgrading for the v2.0.30 tag. Co-authored-by: Cursor <cursoragent@cursor.com>
1 parent 76cd7be commit c3b4b11

6 files changed

Lines changed: 172 additions & 15 deletions

File tree

README.md

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,19 @@ After installation, two files will be copied to your project root:
5050

5151
**Note:** These files should be committed to your repository so they're available to all team members. The plugin will remove any old `.ignore.txt` entries from `.gitignore` if they exist.
5252

53-
**Auto-update:** The `generate-composer-require.sh` script is automatically updated when you run `composer update` if the content differs from the version in vendor. This ensures you always have the latest version of the script.
53+
**Wrapper updates:** On first install, `generate-composer-require.sh` is copied to your project root. On later `composer update` runs, the plugin **does not overwrite** a local wrapper that differs from the package copy unless you opt in:
54+
55+
```json
56+
{
57+
"extra": {
58+
"composer-update-helper": {
59+
"auto_update_wrapper": true
60+
}
61+
}
62+
}
63+
```
64+
65+
When opt-in is disabled (default), Composer shows a comment with MD5 hashes if the wrapper differs. See [Configuration](docs/CONFIGURATION.md#composer-plugin-options).
5466

5567
### Architecture
5668

docs/CHANGELOG.md

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,37 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
77

88
## [Unreleased]
99

10+
## [2.0.30] - 2026-07-09
11+
12+
### Breaking Changes
13+
14+
- **PHP requirement**: Minimum PHP version is now `>=8.1 <8.6` (previously `>=7.4`). Projects on PHP 7.4 or 8.0 must upgrade PHP before updating this package.
15+
16+
### Added
17+
18+
- **Opt-in wrapper auto-update**: New `extra.composer-update-helper.auto_update_wrapper` flag in the consuming project's `composer.json` to overwrite a customized `generate-composer-require.sh` when the package copy differs (disabled by default to protect local changes).
19+
- **Static analysis and refactoring toolchain**: PHPStan and Rector added to `require-dev`, with `composer phpstan`, `composer rector`, and `composer rector-dry` scripts.
20+
- **Pre-release pipeline**: `make release-check` runs composer sync, code style, Rector dry-run, PHPStan, and coverage validation.
21+
- **Documentation**: [INSTALLATION.md](INSTALLATION.md), [SECURITY.md](SECURITY.md), [SPEC-DRIVEN-DEVELOPMENT.md](SPEC-DRIVEN-DEVELOPMENT.md), and [SPEC-KIT.md](SPEC-KIT.md).
22+
- **CI/CD integrations**: CodeRabbit workflow, PR lint, stale issue management, Scrutinizer configuration, and expanded GitHub Actions CI matrix.
23+
- **Spec Kit / Specify**: Project scaffolding for spec-driven development (`.specify/`, baseline spec `specs/001-baseline/`).
24+
- **Composer archive exclusions**: `demo/` and `.cursor/` excluded from distribution archives.
25+
26+
### Changed
27+
28+
- **Plugin wrapper install behavior**: The plugin installs the wrapper on first run; subsequent updates only overwrite when `auto_update_wrapper` is `true` or content already matches. When skipped, a comment explains how to enable overwrite.
29+
- **Test layout**: PHPUnit tests reorganized into `tests/Unit/` and `tests/Integration/`.
30+
- **README**: Packagist badges, documentation index, PHP 8.1+ requirement, and wrapper update policy.
31+
- **Plugin and Installer**: Code quality pass (PHPStan/Rector/PHP-CS-Fixer), modern string checks (`str_starts_with`), and clearer PHPDoc.
32+
- **composer.json**: Corrected GitHub homepage/support URLs; Symfony FrameworkBundle added as dev dependency for static analysis.
33+
1034
### Documentation
1135

1236
- **README** — Architecture: updated approximate line counts for `generate-composer-require.sh` and `process-updates.php` (and noted they can change between releases). Language list in the YAML example points to **CONFIGURATION.md** for the full locale set instead of a partial inline list.
13-
- **Plugin.php**`installFiles()` docblock now matches behavior (script is updated when vendor copy differs), not “first install only”.
37+
- **Plugin.php**`installFiles()` docblock documents first-install copy and opt-in overwrite behavior.
38+
- **CONFIGURATION.md** — Documents `extra.composer-update-helper.auto_update_wrapper`.
39+
40+
> **Note**: See [UPGRADING.md](UPGRADING.md#upgrading-to-2030-2026-07-09) for migration notes.
1441
1542
## [2.0.29] - 2026-01-22
1643

docs/CONFIGURATION.md

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,31 @@
22

33
This guide covers all configuration options for Composer Update Helper.
44

5+
## Composer plugin options
6+
7+
These settings live in the **consuming project's** `composer.json` under `extra.composer-update-helper` (not in `generate-composer-require.yaml`).
8+
9+
### `auto_update_wrapper`
10+
11+
Controls whether `composer install` / `composer update` may overwrite an existing `generate-composer-require.sh` when it differs from the package copy.
12+
13+
| Value | Behavior |
14+
|-------|----------|
15+
| `false` (default) | Keep the local wrapper; show a comment with MD5 hashes when versions differ |
16+
| `true` | Overwrite the local wrapper with the package version |
17+
18+
```json
19+
{
20+
"extra": {
21+
"composer-update-helper": {
22+
"auto_update_wrapper": true
23+
}
24+
}
25+
}
26+
```
27+
28+
Use `true` when you want the wrapper to track package updates automatically. Leave the default when you maintain local customizations to the shell script.
29+
530
## Package Configuration
631

732
The script searches for configuration files in the current directory (where `composer.json` is located). It supports both `.yaml` and `.yml` extensions, with `.yaml` taking priority.

docs/UPGRADING.md

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,44 @@ This guide will help you upgrade Composer Update Helper to newer versions.
1818

1919
## Version-Specific Upgrade Notes
2020

21+
### Upgrading to 2.0.30+ (2026-07-09)
22+
23+
#### Breaking Changes
24+
25+
- **PHP `>=8.1 <8.6` required**: Upgrade your runtime before updating the package. PHP 7.4 and 8.0 are no longer supported.
26+
27+
#### What's Changed
28+
29+
- **Wrapper auto-update is opt-in**: The plugin no longer silently overwrites a customized `generate-composer-require.sh` on `composer update`. By default, if your local wrapper differs from the package copy, Composer prints a comment with MD5 hashes and keeps your file.
30+
31+
To allow automatic overwrite again, add to your project's `composer.json`:
32+
33+
```json
34+
{
35+
"extra": {
36+
"composer-update-helper": {
37+
"auto_update_wrapper": true
38+
}
39+
}
40+
}
41+
```
42+
43+
- **Development tooling**: Maintainers and contributors can run `make release-check` (PHP-CS-Fixer, Rector dry-run, PHPStan, PHPUnit with coverage).
44+
45+
#### Migration Steps
46+
47+
1. Confirm PHP `>=8.1` on your machine and CI.
48+
2. Update the package: `composer update nowo-tech/composer-update-helper`.
49+
3. If you customized `generate-composer-require.sh`, decide whether to keep your version or enable `auto_update_wrapper`.
50+
4. Review new docs: [INSTALLATION.md](INSTALLATION.md), [SECURITY.md](SECURITY.md).
51+
52+
#### Breaking Changes Summary
53+
54+
| Change | Action |
55+
|--------|--------|
56+
| PHP >=8.1 required | Upgrade PHP before updating the package |
57+
| Wrapper overwrite opt-in | Set `auto_update_wrapper: true` if you want silent updates |
58+
2159
### Upgrading to 2.0.29+ (2026-01-22)
2260

2361
#### What's Changed

src/Plugin.php

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313

1414
use function count;
1515
use function dirname;
16+
use function is_array;
1617
use function sprintf;
1718

1819
/**
@@ -619,7 +620,7 @@ private function removeFiles(IOInterface $io): void
619620
*/
620621
private function readProjectComposerJson(): array
621622
{
622-
$vendorDir = $this->composer->getConfig()->get('vendor-dir');
623+
$vendorDir = $this->composer->getConfig()->get('vendor-dir');
623624
$composerJsonPath = dirname((string) $vendorDir) . '/composer.json';
624625

625626
if (!file_exists($composerJsonPath)) {

tests/Unit/PluginTest.php

Lines changed: 66 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@
1515

1616
use function dirname;
1717

18+
use const JSON_THROW_ON_ERROR;
19+
1820
/**
1921
* Test suite for the Plugin class.
2022
* Tests the Composer plugin functionality including file installation,
@@ -213,7 +215,7 @@ public function testOnPostUpdateUpdatesGitignore(): void
213215
@rmdir($tempDir);
214216
}
215217

216-
public function testInstallFilesSkipsWhenFileExists(): void
218+
public function testInstallFilesSkipsWhenFileExistsWithoutAutoUpdateOptIn(): void
217219
{
218220
$tempDir = sys_get_temp_dir() . '/composer-update-helper-plugin-test-' . uniqid();
219221
$vendorDir = $tempDir . '/vendor';
@@ -224,6 +226,7 @@ public function testInstallFilesSkipsWhenFileExists(): void
224226
// Create existing file with different content
225227
$existingContent = '#!/bin/sh\necho "old"';
226228
file_put_contents($tempDir . '/generate-composer-require.sh', $existingContent);
229+
file_put_contents($tempDir . '/composer.json', json_encode(['name' => 'test/project'], JSON_THROW_ON_ERROR));
227230

228231
// Create source file in package with new content
229232
$sourceFile = $binDir . '/generate-composer-require.sh';
@@ -239,16 +242,9 @@ public function testInstallFilesSkipsWhenFileExists(): void
239242
->willReturn($config);
240243

241244
$io = $this->createMock(IOInterface::class);
242-
// When content differs, file should be updated
243-
// Allow any of these messages (at least one must appear)
244-
$io->expects($this->atLeastOnce())
245+
$io->expects($this->once())
245246
->method('write')
246-
->with($this->logicalOr(
247-
$this->stringContains('Updating generate-composer-require.sh'),
248-
$this->stringContains('Creating generate-composer-require.yaml'),
249-
$this->stringContains('Updated .gitignore'),
250-
$this->stringContains('updated .gitignore'),
251-
));
247+
->with($this->stringContains('auto_update_wrapper'));
252248

253249
$event = $this->createMock(Event::class);
254250
$event->method('getIO')
@@ -258,13 +254,71 @@ public function testInstallFilesSkipsWhenFileExists(): void
258254
$plugin->activate($composer, $io);
259255
$plugin->onPostInstall($event);
260256

261-
// File should be updated with new content
257+
// File should be preserved when auto-update is not enabled
262258
$this->assertFileExists($tempDir . '/generate-composer-require.sh');
263-
$this->assertStringContainsString('new', (string) file_get_contents($tempDir . '/generate-composer-require.sh'));
259+
$this->assertStringContainsString('old', (string) file_get_contents($tempDir . '/generate-composer-require.sh'));
264260

265261
// Cleanup
262+
@unlink($tempDir . '/composer.json');
266263
@unlink($tempDir . '/generate-composer-require.sh');
267264
@unlink($tempDir . '/generate-composer-require.yaml');
265+
@rmdir($binDir);
266+
@rmdir($packageDir);
267+
@rmdir($vendorDir);
268+
@rmdir($tempDir);
269+
}
270+
271+
public function testInstallFilesUpdatesWhenAutoUpdateWrapperEnabled(): void
272+
{
273+
$tempDir = sys_get_temp_dir() . '/composer-update-helper-plugin-test-' . uniqid();
274+
$vendorDir = $tempDir . '/vendor';
275+
$packageDir = $vendorDir . '/nowo-tech/composer-update-helper';
276+
$binDir = $packageDir . '/bin';
277+
mkdir($binDir, 0777, true);
278+
279+
$existingContent = '#!/bin/sh\necho "old"';
280+
file_put_contents($tempDir . '/generate-composer-require.sh', $existingContent);
281+
file_put_contents($tempDir . '/composer.json', json_encode([
282+
'name' => 'test/project',
283+
'extra' => [
284+
'composer-update-helper' => [
285+
'auto_update_wrapper' => true,
286+
],
287+
],
288+
], JSON_THROW_ON_ERROR));
289+
290+
$sourceFile = $binDir . '/generate-composer-require.sh';
291+
file_put_contents($sourceFile, '#!/bin/sh\necho "new"');
292+
293+
$config = $this->createMock(Config::class);
294+
$config->method('get')
295+
->with('vendor-dir')
296+
->willReturn($vendorDir);
297+
298+
$composer = $this->createMock(Composer::class);
299+
$composer->method('getConfig')
300+
->willReturn($config);
301+
302+
$io = $this->createMock(IOInterface::class);
303+
$io->expects($this->atLeastOnce())
304+
->method('write')
305+
->with($this->stringContains('Updating generate-composer-require.sh'));
306+
307+
$event = $this->createMock(Event::class);
308+
$event->method('getIO')
309+
->willReturn($io);
310+
311+
$plugin = new Plugin();
312+
$plugin->activate($composer, $io);
313+
$plugin->onPostInstall($event);
314+
315+
$this->assertStringContainsString('new', (string) file_get_contents($tempDir . '/generate-composer-require.sh'));
316+
317+
@unlink($tempDir . '/composer.json');
318+
@unlink($tempDir . '/generate-composer-require.sh');
319+
@unlink($tempDir . '/generate-composer-require.yaml');
320+
@rmdir($binDir);
321+
@rmdir($packageDir);
268322
@rmdir($vendorDir);
269323
@rmdir($tempDir);
270324
}

0 commit comments

Comments
 (0)