diff --git a/CHANGELOG.md b/CHANGELOG.md index b671713..4cd9abe 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,10 +8,46 @@ adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). Entries below `Unreleased` are written by CI from the GitHub release body — see [docs/release.md](docs/release.md). Do not hand-edit released sections. +> **One published release: `v0.1.0`.** The org floors every package at `v0.1.0` while pre-stable, so +> that tag is the only one consumers resolve and it moves as the package moves. Sections under +> *Internal history* below were cut as tags during development and later withdrawn; their content is +> part of `v0.1.0`. They are kept for provenance, not because those versions are installable. + ## Unreleased +_Nothing yet._ + +## v0.1.0 - 2026-08-27 + +Two breaking changes that landed on `main` during org-wide sweeps and were never written down. +Both are mechanical; the provider rename is codemod-able. + ### Changed +- **Breaking. The translation namespace is now the composer package name**, `laranail/validation::`, + where it was `laranail-validation::`. + + There is no alias. `hasTranslations()` is called without an argument, so the old namespace is not + registered at all and every key spelled the old way returns *itself* instead of a message — no + exception, no warning, just the raw key rendered wherever a validation error would have been. An + application that never names these keys directly is unaffected; one that overrode a message, or + published the translations, is not. + + Published files follow the namespace, so an override now lives at + `lang/vendor/laranail/validation/`. That nesting is where Laravel reads it back from — + `FileLoader::loadNamespaceOverrides()` interpolates the namespace into + `{$path}/vendor/{$namespace}/{$locale}/{$group}.php`. + +- **Breaking. The service provider moved** to + `Simtabi\Laranail\Validation\Providers\ValidationServiceProvider`. + + Package auto-discovery handles this on its own. Anything that names the class explicitly — a + Testbench `getPackageProviders()`, a manual entry in `config/app.php`, a `testbench.yaml` — fatals + with "class not found" until it is updated. `rector-migrate-0.1.php` rewrites it. + + The move brings the package in line with the family convention that every provider sits in a + `Providers/` directory, which Laravel's own skeleton does with `app/Providers`. + - The final re-audit against the 1.0 plan closed four release-gate gaps: the README Stability section and installation guide now speak in shipped-1.0 terms (both still said "pre-1.0, pin `^0.1`" after the major); `CREDITS.md` records the Phase-2 datasets (ISO @@ -20,6 +56,14 @@ Entries below `Unreleased` are written by CI from the GitHub release body — se attaches a CycloneDX SBOM via `laranail::package-tools.sbom`, the §12.3 item the release had shipped without. +- Package paths resolve through `package-tools`' `packagePath()` rather than hand-counted + `__DIR__ . '/../..'` strings, which cannot drift when a file moves. + +## Internal history (not published) + +These were tagged during development and the tags have since been withdrawn. Nothing here is +separately installable — it all ships inside `v0.1.0` above. + ## v1.0.1 - 2026-08-24 ### Added diff --git a/README.md b/README.md index 1c31b28..c6e66fb 100644 --- a/README.md +++ b/README.md @@ -111,7 +111,7 @@ Full documentation is at ## Stability -1.0 states what SemVer covers. The **stable surface** is: `FluentRule`, +2.0 states what SemVer covers. The **stable surface** is: `FluentRule`, `FluentSchema`, `RuleSet` (including its events and `before()`/`after()` hooks), the rule classes and their constructor signatures, the contracts (`ClientCheckable`, `PrecognitionSkippable`, `TermList`, `FluentRuleContract`), `Check`, `Regex`, @@ -122,8 +122,9 @@ Everything marked `@internal` — the fast-check compiler, the optimizer validat machinery, everything under `Internal\` — may change in a minor, and an arch test enforces the boundary. Build on the stable list; the optimizer is an implementation detail behind it. -Constrain to `^1.0` and read [UPGRADING.md](UPGRADING.md) before moving between versions — -`rector-migrate-1.0.php` auto-migrates the mechanical 0.x break. Deprecations post-1.0 are +Constrain to `^0.1` and read [UPGRADING.md](UPGRADING.md) before moving between versions — +`rector-migrate-0.1.php` auto-migrates the mechanical break (the service provider moved into +`Providers/`), and `rector-migrate-1.0.php` still covers the 0.x one. Deprecations post-1.0 are marked `@deprecated` with the replacement and removal version, kept for at least one minor, and removed only in the next major. Behaviour-correcting fixes can still mean input an application previously accepted is now correctly rejected; UPGRADING calls those out. diff --git a/UPGRADING.md b/UPGRADING.md index b9b9300..5e9451a 100644 --- a/UPGRADING.md +++ b/UPGRADING.md @@ -2,6 +2,71 @@ Breaking changes, and what to do about them. Versions not listed here need no action. +## v0.1.0 - 2026-08-27 + +The provider rename below can be applied automatically: + +```bash +vendor/bin/rector process app/ --config vendor/laranail/validation/rector-migrate-0.1.php +``` + +The config declares no paths of its own, so pass them: `app/` at minimum, and usually `tests/` too — +a Testbench `getPackageProviders()` is the single most common place the old class name survives. +`config/` and `bootstrap/` are worth a pass. `testbench.yaml` is not PHP, so Rector will not see it; +grep for the old name there by hand. + +The translation-key rename is a string change, which Rector has no clean rule for. It is a +find-and-replace, described below. + + +### The service provider moved into `Providers/` + +| Before | After | +|---|---| +| `Simtabi\Laranail\Validation\ValidationServiceProvider` | `Simtabi\Laranail\Validation\Providers\ValidationServiceProvider` | + +**Most applications need no change.** Laravel's package auto-discovery finds the provider through +`composer.json`, which moved with it. + +You need this change if you name the class yourself: + +- a Testbench `getPackageProviders()` in a test case, +- a manual entry in `config/app.php` or `bootstrap/providers.php`, +- a `testbench.yaml` `providers:` list — worth grepping for specifically, since it is neither + `.php` nor `.json` and a namespace sweep over source files misses it. + +Left unchanged, it fatals with "class not found" the moment the provider is registered — at boot, +for the whole application. + +### The translation namespace is now `laranail/validation::` + +| Before | After | +|---|---| +| `laranail-validation::validation.iban` | `laranail/validation::validation.iban` | +| `lang/vendor/laranail-validation/` | `lang/vendor/laranail/validation/` | + +**There is no alias.** The old namespace is not registered at all, so a key spelled the old way +returns *itself* — `laranail-validation::validation.iban` renders where the message should be. No +exception is raised, which is what makes this worth checking for rather than waiting to notice. + +Two places to look: + +```bash +grep -rn 'laranail-validation::' app/ resources/ config/ lang/ +ls lang/vendor/laranail-validation 2>/dev/null # a published override, now read from elsewhere +``` + +If you published the translations, move the directory: + +```bash +mkdir -p lang/vendor/laranail +git mv lang/vendor/laranail-validation lang/vendor/laranail/validation +``` + +That nesting is not incidental — Laravel interpolates the namespace into the override path itself +(`FileLoader::loadNamespaceOverrides()` reads +`{$path}/vendor/{$namespace}/{$locale}/{$group}.php`), so the slash is exactly where it looks. + ## v1.0.0 - 2026-08-24 The mechanical break below (`getEachRules()`) can be applied automatically: diff --git a/composer.json b/composer.json index 3e1a9e9..2678b38 100644 --- a/composer.json +++ b/composer.json @@ -30,7 +30,7 @@ } ], "name": "laranail/validation", - "description": "Fluent validation rule builders for Laravel — type-aware rule objects, structured array validation, and an optimized wildcard validator.", + "description": "Fluent validation rule builders for Laravel \u2014 type-aware rule objects, structured array validation, and an optimized wildcard validator.", "keywords": [ "laravel", "laranail", @@ -91,7 +91,7 @@ "phpstan/phpstan-deprecation-rules": "^2.0.4", "phpstan/phpstan-phpunit": "^2.0.16", "phpstan/phpstan-strict-rules": "^2.0.10", - "rector/rector": "^2.4.1", + "rector/rector": "^2.4.1 <2.6.4", "sandermuller/boost-skills": "^2.5", "sandermuller/package-boost-laravel": "^1.0", "spaze/phpstan-disallowed-calls": "^4.10", @@ -100,11 +100,11 @@ "tomasvotruba/type-coverage": "^2.3.4" }, "suggest": { - "laranail/email": "^0.1 — maintained disposable-domain and role-account lists plus a production DNS resolver, bound over the Contracts\\Email\\* fallbacks this package ships. Nothing you call changes.", - "laranail/phone": "^0.1 — required by FluentRule::phone() and Rules\\Telecom\\Phone. Suggested rather than required because it carries libphonenumber's numbering-plan metadata, which a project validating only strings and dates should not have to install.", - "nikic/php-parser": "^5.0 — required by Simtabi\\Laranail\\Validation\\Testing\\Arch\\BansFieldRuleTypeMethods, an opt-in Pest/PHPUnit arch helper that bans type-specific method calls on the untyped FluentRule::field() builder.", - "laranail/password-history": "^0.1 — reuse prevention: password()->notReused() appears when installed.", - "laranail/password-tools": "^0.2 — zxcvbn strength scoring and secure generators: password()->strength(3) appears when installed." + "laranail/email": "^0.1 \u2014 maintained disposable-domain and role-account lists plus a production DNS resolver, bound over the Contracts\\Email\\* fallbacks this package ships. Nothing you call changes.", + "laranail/phone": "^0.1 \u2014 required by FluentRule::phone() and Rules\\Telecom\\Phone. Suggested rather than required because it carries libphonenumber's numbering-plan metadata, which a project validating only strings and dates should not have to install.", + "nikic/php-parser": "^5.0 \u2014 required by Simtabi\\Laranail\\Validation\\Testing\\Arch\\BansFieldRuleTypeMethods, an opt-in Pest/PHPUnit arch helper that bans type-specific method calls on the untyped FluentRule::field() builder.", + "laranail/password-history": "^0.1 \u2014 reuse prevention: password()->notReused() appears when installed.", + "laranail/password-tools": "^0.2 \u2014 zxcvbn strength scoring and secure generators: password()->strength(3) appears when installed." }, "autoload": { "psr-4": { @@ -148,7 +148,7 @@ ] }, "branch-alias": { - "dev-main": "1.0.x-dev" + "dev-main": "0.1.x-dev" } }, "config": { diff --git a/docs/release.md b/docs/release.md index 34c7c52..12ae803 100644 --- a/docs/release.md +++ b/docs/release.md @@ -65,23 +65,39 @@ requests, and weekly, since a tag can also be moved on the remote without any pu ## Cutting the release -1. Tag the release commit with the `v`-prefixed version — `v0.1.1`. Composer reads either - form, and the `v` prefix is what the repo's existing tags, the org convention, and the - tag-currency check (`verify-tag-currency.sh` filters on `^v`) all agree on. This page once - said "bare version"; that sentence contradicted all three and the script never matched it. - Releases are real SemVer points — the pre-1.0 moving-tag model is retired for this package. -2. Create the GitHub release against that tag, titled the same (`v0.1.1`). -3. Write a real description in the release body. Summarise what changed and why, in prose — an - empty body or a bare "see CHANGELOG" is not a release description. This body becomes the - changelog entry, so it is the version's permanent record. +**The changelog comes first, and the tag is the trigger.** `release.yml` fires on the tag push and +*extracts* the `## vX.Y.Z` section out of `CHANGELOG.md` to use as the release body — and `exit 1`s if +no such section exists. So there is one order, and tagging is the last step in it. + +1. Write the version's section in `CHANGELOG.md`, headed `## vX.Y.Z - `. Real prose about + what changed and why; this becomes the release body and is the version's permanent record. The + heading has to match `^## (\[)?(v)?X.Y.Z([] ]|$)`, which is what the extractor greps for. +2. Confirm it extracts before you tag anything: + ```bash + awk -v ver="X.Y.Z" '$0 ~ "^## (\\[)?(v)?"ver"([] ]|$)" { grab = 1; next } grab && /^## / { exit } grab { print }' CHANGELOG.md + ``` + Empty output means the release job will fail on its first step. +3. Commit the changelog entry to `main`. +4. Tag the release commit with the `v`-prefixed version — `v0.1.0`. Composer reads either form, and + the `v` prefix is what the repo's existing tags, the org convention, and the tag-currency check + (`verify-tag-currency.sh` filters on `^v`) all agree on. This page once said "bare version"; that + sentence contradicted all three and the script never matched it. Releases are real SemVer points — + the pre-1.0 moving-tag model is retired for this package. +5. Push the tag. Everything else is CI's: it creates the release, injects the benchmark table + between the markers it appends itself, and attaches the SBOM. + +> This page used to say "create the GitHub release, then write a description in the body", with +> the changelog written back afterwards. That was the older flow. `update-changelog.yml` still +> exists for it, but it now only backfills a release authored by hand and skips when the section is +> already present — which, on the normal path, it is. ## What CI does afterwards Publishing the release (`release: released`) fires three workflows: -- **Update Changelog** prepends the release body to `CHANGELOG.md` and commits it back to the - release's target branch. This is why `CHANGELOG.md` is never hand-edited as part of a - release — a manual entry will be duplicated. +- **Update Changelog** backfills `CHANGELOG.md` from the release body, and *only* when the section + is missing — a release authored by hand in the GitHub UI. On the normal path the section is + already there, because it is what the body was built from, and this job does nothing. - **Release Benchmark** re-runs the benchmark suite against the tagged commit and injects the results table into the release body, between the `` and `` markers. Those markers must already be present in the body for the diff --git a/rector-migrate-0.1.php b/rector-migrate-0.1.php new file mode 100644 index 0000000..1e5184f --- /dev/null +++ b/rector-migrate-0.1.php @@ -0,0 +1,46 @@ +withImportNames(importShortClasses: false, removeUnusedImports: true) + ->withConfiguredRule(RenameClassRector::class, [ + 'Simtabi\Laranail\Validation\ValidationServiceProvider' => 'Simtabi\Laranail\Validation\Providers\ValidationServiceProvider', + ]);