From 9e0d5edad1508b24f49902db39dda5ec36322e2f Mon Sep 17 00:00:00 2001 From: Shavonn Brown Date: Sun, 14 Jun 2026 15:20:23 -0400 Subject: [PATCH 1/3] fix: update models with fillable array --- src/Models/Toggle.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/Models/Toggle.php b/src/Models/Toggle.php index ae1e4ad..1697175 100644 --- a/src/Models/Toggle.php +++ b/src/Models/Toggle.php @@ -15,11 +15,13 @@ */ class Toggle extends Model { + /** @var list */ protected $fillable = [ 'name', 'active', ]; + /** @var array */ protected $casts = [ 'active' => 'boolean', ]; From 1374e7fecf4f3af88da5f089389935a3977d37f2 Mon Sep 17 00:00:00 2001 From: Shavonn Brown Date: Sun, 14 Jun 2026 15:22:53 -0400 Subject: [PATCH 2/3] feat: add laravel boost skill --- skills/SKILL.md | 215 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 215 insertions(+) create mode 100644 skills/SKILL.md diff --git a/skills/SKILL.md b/skills/SKILL.md new file mode 100644 index 0000000..96fa318 --- /dev/null +++ b/skills/SKILL.md @@ -0,0 +1,215 @@ +--- +name: Laravel Toggle +description: Conventions and APIs for the offload-project/laravel-toggle package — config and database feature toggles, per-flag driver routing, Blade directives, Inertia sharing, and cache management. +compatible_agents: + - Claude Code + - Cursor +tags: + - laravel + - php + - feature-flags + - feature-toggles + - inertia + - blade + - eloquent +--- + +## Context + +`offload-project/laravel-toggle` is a Laravel 11/12/13 package (PHP 8.3+) for global on/off feature flags. It ships: + +- A `ToggleManager` resolved via the `Toggle` facade (`OffloadProject\Toggle\Facades\Toggle`). +- A `Driver` contract with three implementations: `ConfigDriver` (read-only, env-driven), `DatabaseDriver` (mutable at runtime with config fallback), and `PerFlagDriver` (routes each flag to the config or database driver based on `flags` / `database_flags` lists). +- A `Toggle` Eloquent model (`OffloadProject\Toggle\Models\Toggle`) backing the database driver. +- Blade directives `@toggle`, `@elsetoggle`, `@endtoggle`. +- `ShareTogglesWithInertia` middleware that exposes all flags to the frontend as a `flags` Inertia prop (keys camelCased). +- Artisan commands `toggle:list`, `toggle:create`, `toggle:cache-clear`. +- Exceptions `ToggleNotFoundException` and `ReadOnlyDriverException`. + +Apply this skill when working in a Laravel app that has `offload-project/laravel-toggle` in `composer.json`, or when the user asks for help with `Toggle::`, `@toggle`, the `Toggle` model, feature flags, or driver/cache wiring in this package. + +## Rules + +### Calling toggles + +1. Always check toggle state through the `Toggle` facade (`Toggle::active($name)` / `Toggle::inactive($name)`). Do **not** read `config('toggle.flags.foo')` or query the `toggles` table directly — those paths bypass per-flag routing and the cache. +2. Pass either a string key (kebab-case) or a backed-string enum case. Prefer enums in application code for type safety; reserve raw strings for Blade or quick scripts. +3. Use `Toggle::inactive($name)` instead of `! Toggle::active($name)` so intent is explicit in diffs. + +### Enabling / disabling at runtime + +4. `Toggle::enable($name)`, `Toggle::disable($name)`, and `Toggle::delete($name)` only work when the flag resolves through the database driver. If you call them on a config-only flag, the underlying `ConfigDriver` throws `ReadOnlyDriverException`. To make a flag mutable, list it in `database_flags` (or set the global driver to `database`). +5. `enable()` / `disable()` / `delete()` automatically forget the cache entry for that flag. Do **not** add manual `Toggle::forgetCache()` calls after them. +6. Modifying the `Toggle` Eloquent model directly (e.g. `Toggle::updateOrCreate(...)`) also clears the cache because the model's `saved` and `deleted` hooks call `Toggle::forgetCache()`. Prefer the facade methods, but the model is safe when you need to seed or bulk-write. + +### Per-flag driver routing + +7. Put **read-only / env-driven** flags in `config('toggle.flags')` with an `env()` default. These are baked into the deploy. +8. Put **runtime-mutable** flag names in `config('toggle.database_flags')`. Listed flags always resolve through the database driver (with config fallback), regardless of `TOGGLE_DRIVER`. +9. Don't list the same flag in both `flags` and `database_flags` unless you intend the config value to be the fallback when the database row is missing — `database_flags` wins for resolution, and `flags` only contributes the fallback value. +10. For unlisted flags, the global `TOGGLE_DRIVER` (`config` or `database`) decides which driver handles them. Avoid relying on unlisted flags in production code; declare every flag explicitly. + +### Defaults for unknown flags + +11. Set `TOGGLE_DEFAULT=false` (default), `true`, or `exception`. Use `exception` in development to catch typos early; keep production at `false` (or `true` for safe-by-default flags). +12. When `TOGGLE_DEFAULT=exception`, callers must be prepared for `ToggleNotFoundException`. Don't wrap every `Toggle::active()` in try/catch — instead, declare the flag. + +### Caching + +13. Caching is on by default (`TOGGLE_CACHE_ENABLED=true`) with a 1-hour TTL. The cache key prefix is `toggle:`. Leave caching on in production. +14. If the cache store is unavailable, `Toggle::active()` still resolves correctly by falling through to the driver — there's no need to wrap calls in defensive try/catch. +15. After bulk-changing many flags out-of-band (e.g. a seed or migration), call `Toggle::flushCache()` once rather than `forgetCache()` per flag. +16. Tests that exercise toggle changes should either use the `array` cache driver or call `Toggle::flushCache()` in `beforeEach` to avoid cross-test bleed. + +### Blade directives + +17. Use `@toggle('flag-name') ... @elsetoggle ... @endtoggle` for template-side conditionals. The directive accepts a string or an enum case (passed via `{{ Feature::NewCheckout->value }}` or directly if your Blade compiler supports it). +18. Do **not** call `Toggle::active()` inline in Blade unless you need to combine it with another expression — `@toggle` is the canonical form. + +### Inertia integration + +19. To expose flags to the frontend, replace `HandleInertiaRequests` in `bootstrap/app.php` with `ShareTogglesWithInertia` (or extend it). The middleware shares all flags as a `flags` prop with camelCased keys (`new-checkout` → `newCheckout`). +20. Anything exposed via the Inertia middleware is **public to the browser**. Do not put sensitive kill-switches (rate-limit bypasses, internal admin features) in the same toggle namespace as user-facing flags; route those through a separate authorization check. + +### Artisan + +21. Use `php artisan toggle:create ` to scaffold a new config-driven flag. Add `--active` to default it to true, `--db` to also create a database row. The command edits `config/toggle.php` and `.env`; review the diff before committing. +22. `php artisan toggle:list` shows all defined flags and current state — useful for verifying a deploy or debugging which driver a flag is resolving through. +23. `php artisan toggle:cache-clear [name]` clears the cache for a specific flag or all flags. + +### Naming + +24. Use kebab-case for flag names (`new-checkout`, `dark-mode`). The Inertia middleware converts these to camelCase for the frontend; mixing snake_case or PascalCase breaks that contract. +25. Prefix `TOGGLE_` env vars and shout-case the rest (`TOGGLE_NEW_CHECKOUT`). The `toggle:create` command follows this convention; match it for hand-added flags. + +## Examples + +### Defining flags in config + +```php +// config/toggle.php + +'flags' => [ + // Config-driven, read-only — controlled by .env + 'new-checkout' => env('TOGGLE_NEW_CHECKOUT', false), + 'dark-mode' => env('TOGGLE_DARK_MODE', true), +], + +'database_flags' => [ + // Database-driven, mutable at runtime via Toggle::enable() / Toggle::disable() + 'maintenance-banner', + 'beta-access', +], +``` + +### Checking a flag + +```php +use OffloadProject\Toggle\Facades\Toggle; + +if (Toggle::active('new-checkout')) { + return new NewCheckoutController()->show($request); +} + +if (Toggle::inactive('dark-mode')) { + // Light mode only +} +``` + +### Using a backed enum + +```php +enum Feature: string +{ + case NewCheckout = 'new-checkout'; + case DarkMode = 'dark-mode'; + case BetaAccess = 'beta-access'; +} + +use App\Enums\Feature; +use OffloadProject\Toggle\Facades\Toggle; + +if (Toggle::active(Feature::NewCheckout)) { + // ... +} + +Toggle::enable(Feature::BetaAccess); +``` + +### Blade + +```blade +@toggle('new-checkout') + +@elsetoggle + +@endtoggle +``` + +### Mutating at runtime (database-routed flag) + +```php +use OffloadProject\Toggle\Facades\Toggle; + +// 'maintenance-banner' is listed in config('toggle.database_flags') +Toggle::enable('maintenance-banner'); +Toggle::disable('maintenance-banner'); +Toggle::delete('maintenance-banner'); // removes the row, falls back to config +``` + +### Sharing flags with Inertia + +```php +// bootstrap/app.php +use OffloadProject\Toggle\Middleware\ShareTogglesWithInertia; + +->withMiddleware(function (Middleware $middleware) { + $middleware->web(append: [ + ShareTogglesWithInertia::class, + ]); +}) +``` + +```js +// resources/js/Pages/Checkout.vue (or React equivalent) +const {flags} = usePage().props + +if (flags.newCheckout) { + // ... +} +``` + +### Testing pattern + +```php +use OffloadProject\Toggle\Facades\Toggle; + +beforeEach(function () { + config()->set('toggle.driver', 'database'); + Toggle::flushCache(); +}); + +it('shows the new checkout when enabled', function () { + Toggle::enable('new-checkout'); + + $this->get('/checkout')->assertSee('New Checkout'); +}); +``` + +## Anti-patterns + +- ❌ Reading `config('toggle.flags.foo')` directly — bypasses per-flag routing and the cache; database overrides will be ignored. +- ❌ Querying the `toggles` table with raw Eloquent in feature code — use the facade so the cache and config fallback stay consistent. +- ❌ Calling `Toggle::enable()` / `disable()` / `delete()` on a flag that's only in `config('toggle.flags')` — `ReadOnlyDriverException` will be thrown. Add the flag to `database_flags` (or switch `TOGGLE_DRIVER` to `database`). +- ❌ Wrapping every `Toggle::active()` in try/catch. With `TOGGLE_DEFAULT` set to `false` or `true`, undefined flags don't throw; with `exception` only undefined flags throw and the right fix is to declare the flag. +- ❌ Manually clearing the cache after `Toggle::enable()` / `disable()` — those facade methods already do it. +- ❌ Putting sensitive admin kill-switches in the same flag namespace as user-facing flags when `ShareTogglesWithInertia` is enabled — they will leak to the browser. +- ❌ Subclassing `OffloadProject\Toggle\Models\Toggle` to add behavior. Use events on the model or a separate service; the model is part of the driver wiring. +- ❌ Mixing snake_case / camelCase flag names. The Inertia middleware camelCases kebab-case names — anything else produces inconsistent prop keys. +- ❌ Editing files inside `vendor/offload-project/laravel-toggle`. All extension points (driver, cache store, default behavior, flags) are exposed via `config/toggle.php` and the `Driver` contract. + +## References + +- Repository: +- README: +- Pennant comparison (when to prefer Pennant): From fcf9fd425f24c64be371c4f80a5da21904a36e13 Mon Sep 17 00:00:00 2001 From: Shavonn Brown Date: Sun, 14 Jun 2026 15:23:45 -0400 Subject: [PATCH 3/3] docs: add missing, update README --- .github/ISSUE_TEMPLATE/bug_report.md | 52 ++++++ .github/ISSUE_TEMPLATE/config.yml | 22 +++ .../ISSUE_TEMPLATE/documentation_request.md | 27 ++++ .github/ISSUE_TEMPLATE/feature_request.md | 35 ++++ .github/ISSUE_TEMPLATE/general_report.md | 29 ++++ .github/pull_request_template.md | 42 +++++ .github/workflows/tests.yml | 5 +- CODE_OF_CONDUCT.md | 46 ++++++ CONTRIBUTING.md | 153 ++++++++++++++++++ LICENSE => LICENSE.md | 2 +- README.md | 79 +++++++-- SECURITY.md | 60 +++++++ 12 files changed, 533 insertions(+), 19 deletions(-) create mode 100644 .github/ISSUE_TEMPLATE/bug_report.md create mode 100644 .github/ISSUE_TEMPLATE/config.yml create mode 100644 .github/ISSUE_TEMPLATE/documentation_request.md create mode 100644 .github/ISSUE_TEMPLATE/feature_request.md create mode 100644 .github/ISSUE_TEMPLATE/general_report.md create mode 100644 .github/pull_request_template.md create mode 100644 CODE_OF_CONDUCT.md create mode 100644 CONTRIBUTING.md rename LICENSE => LICENSE.md (96%) create mode 100644 SECURITY.md diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md new file mode 100644 index 0000000..deb9d9e --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -0,0 +1,52 @@ +--- +name: Bug Report +about: Report a bug in laravel-toggle +title: "[Bug]: " +labels: bug +assignees: '' +--- + +### Description + +A clear and concise description of the bug. + +### Steps to Reproduce + +Provide a minimal code sample or steps that reproduce the issue. + +```php +// e.g. defining a flag, calling Toggle::active(), enabling/disabling, etc. +``` + +1. Configure '...' +2. Call '...' +3. See the error. + +### Expected Behavior + +Explain what you expected to happen. + +### Actual Behavior + +What actually happened? Include stack traces, exception messages, or relevant log output. + +``` +// Paste stack trace / error output here +``` + +### Environment + +- Package version: [e.g., 2.0.0] +- Laravel version: [e.g., 11.x, 12.x, 13.x] +- PHP version: [e.g., 8.3, 8.4, 8.5] +- Driver: [e.g., config, database, mixed] +- Cache store: [e.g., file, redis, array, none] +- OS: [e.g., macOS, Linux, Windows/WSL] + +### Relevant Configuration + +If applicable, share your `config/toggle.php` overrides (`driver`, `default`, `flags`, `database_flags`, cache settings). + +### Additional Context + +Add any other context about the problem here (related packages, Inertia setup, custom drivers, etc.). diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml new file mode 100644 index 0000000..b32538f --- /dev/null +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -0,0 +1,22 @@ +blank_issues_enabled: false +issue_templates: + - name: "Bug Report" + description: "Report a bug." + title: "[Bug]: " + labels: ["bug"] + body: "./ISSUE_TEMPLATE/bug_report.md" + - name: "Feature Request" + description: "Propose a new feature or improvement." + title: "[Feature]: " + labels: ["enhancement"] + body: "./ISSUE_TEMPLATE/feature_request.md" + - name: "Documentation" + description: "Suggest updates or additions to documentation." + title: "[Docs]: " + labels: ["documentation"] + body: "./ISSUE_TEMPLATE/documentation_request.md" + - name: "General Report" + description: "Provide general feedback or inquiries." + title: "[General]: " + labels: ["general"] + body: "./ISSUE_TEMPLATE/general_report.md" diff --git a/.github/ISSUE_TEMPLATE/documentation_request.md b/.github/ISSUE_TEMPLATE/documentation_request.md new file mode 100644 index 0000000..5fa0024 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/documentation_request.md @@ -0,0 +1,27 @@ +--- +name: Documentation +about: Suggest updates or additions to documentation +title: "[Docs]: " +labels: documentation +assignees: '' +--- + +### Affected Documentation + +Which file or section needs updating? (e.g., `README.md`, installation guide, configuration reference, driver examples, CHANGELOG) + +### What Is Missing or Incorrect? + +Describe what's unclear, outdated, or absent. + +### Suggested Changes + +Provide a detailed description — or a draft — of the proposed wording or examples. + +### Why Is This Needed? + +Explain who benefits and how (e.g., new adopters, upgraders from v1.x, contributors). + +### Additional Context + +Links to related issues, PRs, or external references. diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md new file mode 100644 index 0000000..effcfa4 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -0,0 +1,35 @@ +--- +name: Feature Request +about: Suggest a new feature or improvement for laravel-toggle +title: "[Feature]: " +labels: enhancement +assignees: '' +--- + +### Feature Description + +What feature would you like to see in the package? + +### Why Is This Needed? + +Describe the problem this solves or the use case it enables. Is it related to drivers, caching, Blade directives, Artisan commands, Inertia integration, per-flag routing, etc.? + +### Suggested API / Implementation + +If you have an idea of how this should look, share a code sketch. + +```php +// e.g. proposed method signature, config key, or usage example +``` + +### Alternatives Considered + +Are there workarounds you've tried, or alternative approaches you've ruled out? + +### Backwards Compatibility + +Would this change break existing API surface (facade methods, Blade directives, drivers, config keys, migrations)? + +### Additional Context + +Add any relevant links, references, or related issues. diff --git a/.github/ISSUE_TEMPLATE/general_report.md b/.github/ISSUE_TEMPLATE/general_report.md new file mode 100644 index 0000000..d1897e3 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/general_report.md @@ -0,0 +1,29 @@ +--- +name: General Report +about: Provide general feedback, questions, or inquiries about laravel-toggle +title: "[General]: " +labels: general +assignees: '' +--- + +### Topic + +What is this about? (e.g., question, feedback, discussion, support inquiry) + +### Details + +Describe your question, feedback, or inquiry clearly. Include code samples if relevant. + +```php +// Optional: code context +``` + +### Environment (if applicable) + +- Package version: [e.g., 2.0.0] +- Laravel version: [e.g., 11.x, 12.x, 13.x] +- PHP version: [e.g., 8.3, 8.4, 8.5] + +### Additional Context + +Add any other information that might help us respond. diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md new file mode 100644 index 0000000..0949de3 --- /dev/null +++ b/.github/pull_request_template.md @@ -0,0 +1,42 @@ +# Description + +Please include a summary of the changes and the related issue. Include the motivation and context, and list any package +dependencies impacted by this change. + +Fixes # (issue) + +## Type of change + +- [ ] Bug fix (non-breaking change which fixes an issue) +- [ ] New feature (non-breaking change which adds functionality) +- [ ] Breaking change (fix or feature that would cause existing functionality to not work as expected) +- [ ] Deprecation (marks existing API as deprecated) +- [ ] Refactor / internal change (no functional impact) +- [ ] Documentation update +- [ ] CI / tooling change + +## How Has This Been Tested? + +Describe the tests you ran to verify your changes (Pest tests, manual checks in a host Laravel app, etc.). Provide +instructions so reviewers can reproduce. + +- [ ] `composer test` passes +- [ ] `composer analyse` (PHPStan / Larastan) passes +- [ ] `composer pint` shows no formatting changes +- [ ] Added or updated tests covering the change + +**Test Configuration**: + +- PHP version(s): [e.g., 8.3, 8.4, 8.5] +- Laravel version(s): [e.g., 11.x, 12.x, 13.x] +- Driver(s) exercised: [e.g., config, database, both] + +## Checklist + +- [ ] My code follows the style guidelines of this project (Pint / PSR-12) +- [ ] I have performed a self-review of my code +- [ ] I have added tests that prove my fix is effective or that my feature works +- [ ] New and existing tests pass locally with my changes +- [ ] I have updated the README / docs where relevant +- [ ] My commit messages follow Conventional Commits +- [ ] Any breaking changes are clearly called out above diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 449e075..c829896 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -18,7 +18,7 @@ jobs: fail-fast: true matrix: php: [ 8.3, 8.4 ] - laravel: [ 12.*, 13.* ] + laravel: [ 11.*, 12.*, 13.* ] stability: [ prefer-lowest, prefer-stable ] name: PHP ${{ matrix.php }} - Laravel ${{ matrix.laravel }} - ${{ matrix.stability }} @@ -36,6 +36,9 @@ jobs: - name: Install dependencies run: | + if [[ "${{ matrix.laravel }}" == "11.*" ]]; then + composer config --json policy.advisories.block false + fi composer require "laravel/framework:${{ matrix.laravel }}" --no-interaction --no-update composer update --${{ matrix.stability }} --prefer-dist --no-interaction diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md new file mode 100644 index 0000000..fca433d --- /dev/null +++ b/CODE_OF_CONDUCT.md @@ -0,0 +1,46 @@ +# Code of Conduct + +## Our pledge + +We want this project to be a welcoming, respectful space for everyone who wants to contribute — regardless of background, identity, or experience level. + +## Expected behavior + +- Be kind and patient. Assume good faith. +- Critique code and ideas, not people. +- Welcome questions, including basic ones. +- Acknowledge differing viewpoints and accept constructive feedback gracefully. +- Take responsibility for mistakes; learn and move on. + +## Unacceptable behavior + +- Personal attacks, insults, or demeaning language. +- Harassment of any kind, in public or private channels. +- Discriminatory language or behavior. +- Publishing others' private information without consent. +- Disruptive behavior that derails productive discussion. +- Any conduct a reasonable person would consider inappropriate in a professional setting. + +## Scope + +This Code of Conduct applies to all project spaces — issues, pull requests, discussions, commit messages, and any other communication tied to the project. It also applies when an individual is representing the project in public spaces. + +## Enforcement + +If you experience or witness behavior that violates this Code of Conduct, please report it via [GitHub Security Advisories](https://github.com/offload-project/laravel-toggle/security/advisories/new) (which supports private reporting) or by opening a private channel with a maintainer. + +Maintainers will review reports promptly and respond with appropriate action, which may include: + +- A private warning +- A temporary ban from project spaces +- A permanent ban from project spaces + +Reports will be handled with discretion. The reporter's identity will be kept confidential unless they choose otherwise. + +## Maintainer responsibilities + +Maintainers are responsible for clarifying standards, responding to reports fairly, and applying enforcement consistently. Maintainers who do not uphold this Code of Conduct in good faith may face consequences as determined by other maintainers. + +## Attribution + +This Code of Conduct is inspired by the [Contributor Covenant](https://www.contributor-covenant.org/), adapted to be concise for this project. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..85bc90b --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,153 @@ +# Contributing to Laravel Toggle + +Thanks for your interest in contributing! This document outlines the process and standards for contributing to `offload-project/laravel-toggle`. + +## Code of Conduct + +By participating in this project, you agree to treat fellow contributors with respect. Be kind, assume good intent, and keep discussions focused on the work. See [CODE_OF_CONDUCT.md](CODE_OF_CONDUCT.md). + +## Ways to Contribute + +- Reporting bugs via the [Bug Report](.github/ISSUE_TEMPLATE/bug_report.md) template +- Proposing new features via the [Feature Request](.github/ISSUE_TEMPLATE/feature_request.md) template +- Improving documentation (`README.md`, `CHANGELOG.md`) +- Fixing bugs or implementing features through pull requests +- Reviewing open pull requests + +Before opening a large PR, please open an issue first to discuss the approach. + +## Requirements + +- PHP **8.3+** (CI matrix runs 8.3, 8.4, 8.5) +- Composer 2 +- A local SQLite extension (the test suite uses an in-memory SQLite database via Orchestra Testbench) + +## Getting Set Up + +1. Fork the repository on GitHub and clone your fork: + + ```bash + git clone git@github.com:/laravel-toggle.git + cd laravel-toggle + ``` + +2. Install dependencies: + + ```bash + composer install + ``` + +3. Install the Git hooks (runs Pint pre-commit, validates Conventional Commits on commit-msg, runs tests and static analysis pre-push): + + ```bash + composer install-hooks + ``` + +4. Create a feature branch off `main`: + + ```bash + git checkout -b feat/short-description + ``` + +## Development Workflow + +This package supports Laravel 11, 12, and 13 and PHP 8.3–8.5. Changes must work across that matrix. + +### Running the Test Suite + +```bash +composer test +``` + +Run with coverage: + +```bash +composer test-coverage +``` + +Tests are written with [Pest](https://pestphp.com/) and live under `tests/`. New behavior should be covered by tests; bug fixes should include a regression test. + +### Static Analysis + +```bash +composer analyse +``` + +We use Larastan (PHPStan for Laravel). If you must suppress a finding, prefer narrow ignores via the baseline over loosening the rule set, and explain why in your PR. + +### Code Style + +```bash +composer pint +``` + +Pint runs on `pre-commit`. PRs must be Pint-clean — the `code-style.yml` workflow will fail otherwise. + +## Commit Messages + +We use [Conventional Commits](https://www.conventionalcommits.org/). The `commit-msg` hook validates this; CI/release tooling depends on it. + +Format: `(): ` + +Common types used in this repo: + +| Type | Use for | +| ------------ | ------------------------------------------------------------------- | +| `feat` | New user-facing functionality | +| `fix` | Bug fixes | +| `deprecate` | Marking existing API as deprecated | +| `refactor` | Internal change with no behavior difference | +| `test` | Adding or updating tests | +| `docs` | Documentation only | +| `chore` | Tooling, dependency bumps, repo housekeeping | +| `ci` | Changes to GitHub Actions workflows | + +Breaking changes: add `!` after the type (e.g., `feat!: change Toggle::all() return type`) and explain the migration path in the PR body. + +## Pull Requests + +1. Make sure your branch is up to date with `main`. +2. Run the full local check before pushing: + + ```bash + composer pint && composer analyse && composer test + ``` + +3. Push your branch and open a PR against `main` using the [PR template](.github/pull_request_template.md). +4. Fill in: + - What changed and why + - Type of change (bug fix, feature, breaking, deprecation, etc.) + - How it was tested (PHP/Laravel/DB versions) + - Whether docs or `CHANGELOG.md` were updated +5. Keep PRs focused. One logical change per PR makes review faster and bisection easier. +6. CI must pass before review: + - `tests.yml` — Pest across the PHP × Laravel × stability matrix + - `code-style.yml` — Pint +7. Address review feedback in additional commits rather than force-pushing while review is active. + +## Adding or Changing Features + +When working on this package, keep these areas in mind: + +- **Drivers** — `Driver` is the contract for `ConfigDriver` and `DatabaseDriver`. New drivers should implement it and be wired through `ToggleManager`. +- **Per-flag routing** — `flags` and `database_flags` keep config-driven and database-driven flags separate. Changes to resolution order are user-visible. +- **Cache** — `enable()`, `disable()`, `delete()`, and the model's saved/deleted hooks must keep the cache coherent. Don't add code paths that mutate state without clearing the cached value. +- **Blade directives & middleware** — `@toggle` / `@elsetoggle` / `@endtoggle` and `ShareTogglesWithInertia` are part of the public API. +- **Config** — new config keys must have safe defaults and be documented in `config/toggle.php` with a comment. +- **Migrations** — schema changes need a new migration. Don't edit existing published migrations. +- **Exceptions** — `ToggleNotFoundException` is the established pattern; follow it for new error types. + +## Documentation + +If your change affects public API, configuration, or usage, update: + +- `README.md` — quick start / feature list +- `CHANGELOG.md` — under the `Unreleased` section (or note in your PR if you'd like a maintainer to add it) + +## Reporting Security Issues + +Please do **not** open a public issue for security vulnerabilities. Report them privately via GitHub's "Report a vulnerability" feature on the repository's Security tab so a fix can be coordinated before disclosure. See [SECURITY.md](SECURITY.md). + +## License + +By contributing, you agree that your contributions will be licensed under the [MIT License](LICENSE.md) that covers this project. diff --git a/LICENSE b/LICENSE.md similarity index 96% rename from LICENSE rename to LICENSE.md index 80b9422..340cb63 100644 --- a/LICENSE +++ b/LICENSE.md @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Offload Project +Copyright (c) 2025–2026 Offload Project Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/README.md b/README.md index bf26692..b7e2ce7 100644 --- a/README.md +++ b/README.md @@ -11,14 +11,41 @@ flags, database-driven toggles, or both. ## Features -- **Two storage drivers**: Config (environment variables) or Database (runtime toggleable) -- **Per-flag driver routing**: Mix config and database flags in the same app via `database_flags` -- **Layered approach**: Database driver falls back to config, allowing gradual migration -- **Built-in caching**: Configurable cache store and TTL for performance -- **Blade directives**: `@toggle`, `@elsetoggle`, `@endtoggle` for clean templates -- **Enum support**: Use backed enums for type-safe toggle names -- **Artisan commands**: Scaffold new toggles and manage cache -- **Configurable defaults**: Return false, true, or throw exceptions for undefined toggles +- **Two storage drivers** - Config (environment variables) or Database (runtime toggleable) +- **Per-flag driver routing** - Mix config and database flags in the same app via `database_flags` +- **Layered approach** - Database driver falls back to config, allowing gradual migration +- **Built-in caching** - Configurable cache store and TTL for performance +- **Blade directives** - `@toggle`, `@elsetoggle`, `@endtoggle` for clean templates +- **Enum support** - Use backed enums for type-safe toggle names +- **Artisan commands** - Scaffold new toggles and manage cache +- **Inertia integration** - Share all flags with the frontend via middleware +- **Configurable defaults** - Return false, true, or throw exceptions for undefined toggles + +## Table of Contents + +- [Why Laravel Toggle?](#why-laravel-toggle) +- [Requirements](#requirements) +- [Installation](#installation) +- [Quick Start](#quick-start) + - [Define a toggle in config](#define-a-toggle-in-config) + - [Check toggles in code](#check-toggles-in-code) + - [Use Blade directives](#use-blade-directives) +- [Configuration](#configuration) + - [Driver](#driver) + - [Per-flag driver routing](#per-flag-driver-routing) + - [Default behavior for undefined toggles](#default-behavior-for-undefined-toggles) + - [Caching](#caching) +- [Usage](#usage) + - [Facade methods](#facade-methods) + - [Using enums](#using-enums) + - [Eloquent model](#eloquent-model) + - [Inertia](#inertia) +- [Artisan Commands](#artisan-commands) +- [AI Coding Assistant Skill](#ai-coding-assistant-skill) +- [Testing](#testing) +- [Contributing](#contributing) +- [Security](#security) +- [License](#license) ## Why Laravel Toggle? @@ -28,18 +55,14 @@ records, with no user resolution or driver complexity. ## Requirements -- PHP 8.2+ -- Laravel 11 or 12 or 13 +- PHP 8.3+ +- Laravel 11/12/13 ## Installation ```bash composer require offload-project/laravel-toggle -``` - -Publish the configuration file: -```bash php artisan vendor:publish --tag=toggle-config ``` @@ -98,7 +121,7 @@ TOGGLE_DRIVER=config # Read-only, uses config/toggle.php flags TOGGLE_DRIVER=database # Read-write, falls back to config ``` -The **config driver** is read-only at runtime - values come from environment variables and config files. +The **config driver** is read-only at runtime — values come from environment variables and config files. The **database driver** checks the database first, then falls back to config values. This allows you to define defaults in config while overriding specific toggles at runtime. @@ -125,6 +148,7 @@ in `database_flags`: ``` Resolution logic: + - Flags in `database_flags` always use the database driver (with config fallback) - Flags in `flags` always use the config driver (read-only) - Unlisted flags use the global `driver` setting @@ -161,7 +185,7 @@ Toggle::inactive('feature-name'); // bool // Modify toggles (database driver only) Toggle::enable('feature-name'); // Enable a toggle Toggle::disable('feature-name'); // Disable a toggle -Toggle::delete('feature-name'); // Remove from database +Toggle::delete('feature-name'); // Remove from database // Get all toggles Toggle::all(); // ['feature-name' => true, ...] @@ -280,12 +304,33 @@ php artisan toggle:cache-clear php artisan toggle:cache-clear new-feature ``` +## AI Coding Assistant Skill + +This package ships a [Laravel Boost](https://skills.laravel.cloud/) skill so coding assistants (Claude Code, Cursor, etc.) follow the package's conventions when generating code. Install it in your app with: + +```bash +php artisan boost:add-skill offload-project/laravel-toggle +``` + +The skill source lives at [`skills/SKILL.md`](skills/SKILL.md). + ## Testing ```bash composer test ``` +## Contributing + +Contributions are welcome! Please see the documents below before getting started. + +- [Contributing Guide](CONTRIBUTING.md) — setup, workflow, commit conventions, and PR process +- [Code of Conduct](CODE_OF_CONDUCT.md) — expectations for participation in this project + +## Security + +- [Security Policy](SECURITY.md) — how to report a vulnerability privately + ## License -The MIT License (MIT). Please see [License File](LICENSE) for more information. +The MIT License (MIT). Please see [License File](LICENSE.md) for more information. diff --git a/SECURITY.md b/SECURITY.md new file mode 100644 index 0000000..57a7a28 --- /dev/null +++ b/SECURITY.md @@ -0,0 +1,60 @@ +# Security Policy + +## Supported versions + +Security fixes are applied to the latest minor release line. Older minor versions may receive fixes for critical issues at the maintainers' discretion — when in doubt, please upgrade. + +| Version | Supported | +| ------------- | ---------------------- | +| `2.x` | ✅ | +| `1.x` | ⚠️ critical fixes only | +| `< 1.0` | ❌ (please upgrade) | + +## Reporting a vulnerability + +**Please do not open a public GitHub issue for security reports.** + +Use [GitHub Security Advisories](https://github.com/offload-project/laravel-toggle/security/advisories/new) to report privately. This lets us discuss, fix, and coordinate disclosure before details become public. + +When reporting, please include: + +- A description of the issue and its potential impact. +- Steps to reproduce, or a minimal proof-of-concept. +- Affected package version(s), Laravel version, and PHP version. +- Any suggested fix or mitigation (optional). + +## Response expectations + +- **Acknowledgement:** within 5 business days. +- **Initial assessment:** within 10 business days. +- **Fix timeline:** depends on severity. Critical issues get prioritized; lower-severity issues may be batched into the next regular release. + +We'll keep you updated on progress and credit you in the advisory unless you'd prefer to stay anonymous. + +## Scope + +Things in scope for this project: + +- Vulnerabilities in any code published under `OffloadProject\Toggle\` (drivers, manager, models, middleware, facade, console commands). +- Toggle resolution issues — bypasses that let an inactive toggle resolve as active (or vice versa) due to caching, fallback, or per-flag routing logic. +- Cache poisoning or stale-cache issues that could keep a flag in the wrong state after `enable()`/`disable()`/`delete()` calls. +- Mass-assignment, SQL injection, or query-scope bypass in the `Toggle` Eloquent model or database driver. +- Information disclosure via the Inertia middleware (e.g., leaking flags that should not be shared with the frontend). +- Insecure defaults in the published config or migrations. + +Things **not** in scope (please report upstream or with the relevant project): + +- Vulnerabilities in Laravel itself or other Composer dependencies — please file with the respective project. +- Application-level misconfiguration (e.g., exposing a sensitive flag through the Inertia middleware in a public route). +- Issues caused by user-supplied custom drivers or overridden manager methods. +- Vulnerabilities in the host application's cache driver, database, or queue. + +## Disclosure + +Once a fix is published, we will: + +1. Publish a GitHub Security Advisory with details and credit. +2. Tag a patch release. +3. Update the changelog with a brief mention (without exploit details prior to the disclosure window). + +Thanks for helping keep the project and its users safe.