diff --git a/.claude/settings.local.json b/.claude/settings.local.json index 9bd9f8c9..16e3e31f 100644 --- a/.claude/settings.local.json +++ b/.claude/settings.local.json @@ -1,6 +1,6 @@ { + "enableAllProjectMcpServers": true, "enabledMcpjsonServers": [ "laravel-boost" - ], - "enableAllProjectMcpServers": true -} \ No newline at end of file + ] +} diff --git a/.docker/traefik/config.yml b/.docker/traefik/config.yml new file mode 100644 index 00000000..f3899e6d --- /dev/null +++ b/.docker/traefik/config.yml @@ -0,0 +1,15 @@ +http: + middlewares: + vite-cors: + headers: + accessControlAllowOriginList: + - "*" + accessControlAllowMethods: + - "GET" + - "POST" + - "OPTIONS" + accessControlAllowHeaders: + - "*" + accessControlAllowCredentials: true + accessControlMaxAge: 100 + addVaryHeader: true diff --git a/.dockerignore b/.dockerignore index 04dda16a..ba084357 100644 --- a/.dockerignore +++ b/.dockerignore @@ -3,6 +3,7 @@ node_modules # Laravel .mcp.json +boost.json .env.example .env.testing phpstan.neon @@ -13,13 +14,12 @@ tests stubs app-modules/**/tests .phpunit.cache -.ai # Ide .editorconfig .prettierignore .prettierrc -*.md +*/**/.md .idea .vscode .junie @@ -34,3 +34,6 @@ app-modules/**/tests # Others art +.ai +.docker +auth.json diff --git a/.editorconfig b/.editorconfig index 8fda640c..e3067757 100644 --- a/.editorconfig +++ b/.editorconfig @@ -11,7 +11,7 @@ trim_trailing_whitespace = true [*.md] trim_trailing_whitespace = false -[*{.yml,.scss,.js,.json,.css}] +[*{.yml,.yaml,.css,.js,.json}] indent_size = 2 [docker-compose*.yml] diff --git a/.env.example b/.env.example index 083f10d9..9948b419 100644 --- a/.env.example +++ b/.env.example @@ -5,33 +5,27 @@ APP_DEBUG=true APP_DOMAIN=laravelcm.local APP_URL=http://laravelcm.local ASSET_URL=https://laravelcm.local -APP_LOCALE=fr -APP_FALLBACK_LOCALE=fr + APP_PORT=8080 APP_SERVICE=laravelcm +APP_LOCALE=fr +APP_FALLBACK_LOCALE=fr +APP_FAKER_LOCALE=fr_FR + +APP_MAINTENANCE_DRIVER=file +# APP_MAINTENANCE_STORE=database + +PHP_CLI_SERVER_WORKERS=4 + BCRYPT_ROUNDS=12 LOG_CHANNEL=stack -LOG_STACK=single,nightwatch +LOG_STACK=single,stderr +LOG_DEPRECATIONS_CHANNEL=null LOG_LEVEL=debug LOG_SOCKET_URL=buggregator:9913 -# Ray Configuration -RAY_HOST=ray@buggregator -RAY_PORT=8000 -# Sentry -SENTRY_LARAVEL_DSN=http://sentry@buggregator:8000/1 -SENTRY_TRACES_SAMPLE_RATE=1.0 -#Var Dumper -VAR_DUMPER_FORMAT=server -VAR_DUMPER_SERVER=tcp://buggregator:9912 -# Inspector -INSPECTOR_URL=http://inspector@buggregator:8000 -INSPECTOR_API_KEY=test -INSPECTOR_INGESTION_KEY=1test -INSPECTOR_ENABLE=true - DB_CONNECTION=pgsql DB_HOST=pgsql DB_PORT=5432 @@ -39,25 +33,33 @@ DB_DATABASE=laravelcm DB_USERNAME=sail DB_PASSWORD=password +SESSION_DRIVER=database +SESSION_LIFETIME=120 +SESSION_ENCRYPT=false +SESSION_PATH=/ +SESSION_DOMAIN=null + BROADCAST_DRIVER=log +BROADCAST_CONNECTION=log + MEDIA_DISK=media FILESYSTEM_DISK="${MEDIA_DISK}" -FILAMENT_PATH=cpanel QUEUE_CONNECTION=database -BROADCAST_CONNECTION=log -CACHE_DRIVER=file -SESSION_DRIVER=database -SESSION_LIFETIME=1400 -MEMCACHED_HOST=127.0.0.1 +CACHE_DRIVER=database +# CACHE_PREFIX= + +MEMCACHED_HOST=memcached -REDIS_HOST=127.0.0.1 +REDIS_CLIENT=phpredis +REDIS_HOST=redis REDIS_PASSWORD=null REDIS_PORT=6379 -MAIL_MAILER=smtp -MAIL_HOST=mailhog +MAIL_MAILER=log +MAIL_SCHEME=null +MAIL_HOST=buggregator MAIL_PORT=1025 MAIL_USERNAME=null MAIL_PASSWORD=null @@ -70,17 +72,19 @@ AWS_ACCESS_KEY_ID=sail AWS_SECRET_ACCESS_KEY=password AWS_DEFAULT_REGION=us-east-1 AWS_BUCKET=laravelcm -AWS_ENDPOINT="http://minio:9000" -AWS_URL="http://localhost:9000/laravelcm" +AWS_ENDPOINT=https://minio:9000 +AWS_URL=https://${APP_DOMAIN}:${FORWARD_MINIO_PORT:-9000}/${AWS_BUCKET} AWS_USE_PATH_STYLE_ENDPOINT=true +AWS_ROOT_DIRECTORY=public PUSHER_APP_ID= PUSHER_APP_KEY= PUSHER_APP_SECRET= PUSHER_APP_CLUSTER=mt1 -MIX_PUSHER_APP_KEY="${PUSHER_APP_KEY}" -MIX_PUSHER_APP_CLUSTER="${PUSHER_APP_CLUSTER}" +VITE_APP_NAME="${APP_NAME}" +VITE_PUSHER_APP_KEY="${PUSHER_APP_KEY}" +VITE_PUSHER_APP_CLUSTER="${PUSHER_APP_CLUSTER}" GITHUB_CLIENT_ID= GITHUB_CLIENT_SECRET= @@ -116,3 +120,9 @@ SSH_TUNNEL_PRIVATE_KEY="-----BEGIN OPENSSH PRIVATE KEY----- oi!jkdiososbXCbnNzaC1rZXktdjEABG5vbmUAAAAEbm9uZQAAAAAAAAFwAAAAdzc2gtcn ... (votre clé SSH privée complète ici) ... -----END OPENSSH PRIVATE KEY-----" + +# Laravel Octane configuration +OCTANE_SERVER=off + +WWWUSER=1000 +WWWGROUP=1000 diff --git a/.gemini/settings.json b/.gemini/settings.json new file mode 100644 index 00000000..04eba29c --- /dev/null +++ b/.gemini/settings.json @@ -0,0 +1,11 @@ +{ + "mcpServers": { + "laravel-boost": { + "command": "vendor/bin/sail", + "args": [ + "artisan", + "boost:mcp" + ] + } + } +} \ No newline at end of file diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml index 0633d86b..90dbc437 100644 --- a/.github/ISSUE_TEMPLATE/config.yml +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -1,11 +1,11 @@ blank_issues_enabled: false contact_links: - - name: Get Help - url: https://laravel.cm/forum/new-thread - about: If you need help with your project create a topic on our forum. - - name: Bug Report - url: https://github.com/laravelcm/issues - about: 'For projects issues, suggest changes on our issues repository.' - - name: Feature Request - url: https://github.com/laravelcm/laravel.cm/discussions/new?category=ideas - about: Share ideas for new features + - name: Get Help + url: https://laravel.cm/forum/new-thread + about: If you need help with your project create a topic on our forum. + - name: Bug Report + url: https://github.com/laravelcm/issues + about: "For projects issues, suggest changes on our issues repository." + - name: Feature Request + url: https://github.com/laravelcm/laravel.cm/discussions/new?category=ideas + about: Share ideas for new features diff --git a/.github/actions/setup/action.yml b/.github/actions/setup/action.yml index 68295b4c..3c1b3890 100644 --- a/.github/actions/setup/action.yml +++ b/.github/actions/setup/action.yml @@ -1,25 +1,36 @@ name: CI Setup description: "Sets up the environment for jobs during CI workflow" +inputs: + flux-username: + description: "Flux UI Pro username" + required: true + flux-license-key: + description: "Flux UI Pro license key" + required: true + runs: using: composite steps: - name: 🐘 Setup PHP uses: shivammathur/setup-php@v2 with: - php-version: "8.4" + php-version: 8.4 extensions: dom, curl, libxml, mbstring, zip, pcntl, pdo, pdo_pgsql, bcmath, soap, intl, gd, exif, iconv, imagick tools: composer:v2 - coverage: none + coverage: xdebug - name: ℹ Setup Problem Matches shell: sh run: | echo "::add-matcher::${{ runner.tool_cache }}/php.json" echo "::add-matcher::${{ runner.tool_cache }}/phpunit.json" + - name: Add Flux Credentials Loaded From ENV + shell: sh + run: composer config http-basic.composer.fluxui.dev "${{ inputs.flux-username }}" "${{ inputs.flux-license-key }}" - name: 🗂 Get composer cache directory id: composer-cache shell: sh - run: echo "::set-output name=dir::$(composer config cache-files-dir)" + run: echo "dir=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT - name: Cache dependencies uses: actions/cache@v4 with: diff --git a/.github/workflows/dependabot-auto-merge.yml b/.github/workflows/dependabot-auto-merge.yml index 88fc5943..945a32ef 100644 --- a/.github/workflows/dependabot-auto-merge.yml +++ b/.github/workflows/dependabot-auto-merge.yml @@ -10,7 +10,6 @@ jobs: runs-on: ubuntu-latest if: ${{ github.actor == 'dependabot[bot]' }} steps: - - name: Dependabot metadata id: metadata uses: dependabot/fetch-metadata@v2.3.0 diff --git a/.github/workflows/quality.yml b/.github/workflows/quality.yml index a537842e..0ae3c1fe 100644 --- a/.github/workflows/quality.yml +++ b/.github/workflows/quality.yml @@ -4,7 +4,6 @@ on: push: branches: - main - - develop pull_request: concurrency: @@ -13,48 +12,62 @@ concurrency: jobs: pint: - runs-on: ubuntu-22.04 + runs-on: ubuntu-latest steps: - name: 👀 Checkout uses: actions/checkout@v6 - - name: 🪄 Setup + - name: 🪄 Setup PHP uses: ./.github/actions/setup + with: + flux-username: ${{ secrets.FLUX_USERNAME }} + flux-license-key: ${{ secrets.FLUX_LICENSE_KEY }} - name: 🔮 Install Composer Dependencies run: composer install --no-progress --no-interaction --prefer-dist --optimize-autoloader + - name: Install Node Dependencies & Build Assets + run: npm install && npm run build - name: 🕵️‍♂️ Run Laravel Pint - run: ./vendor/bin/pint + run: composer test:lint phpstan: - runs-on: ubuntu-22.04 + runs-on: ubuntu-latest steps: - name: 👀 Checkout uses: actions/checkout@v6 - name: 🪄 Setup uses: ./.github/actions/setup + with: + flux-username: ${{ secrets.FLUX_USERNAME }} + flux-license-key: ${{ secrets.FLUX_LICENSE_KEY }} - name: 🔮 Install Composer Dependencies run: composer install --no-progress --no-interaction --prefer-dist --optimize-autoloader - name: 🕵️‍♂️ Run PHPStan - run: composer types -- --ansi --no-interaction --no-progress --error-format=github + run: composer test:types - rector: - runs-on: ubuntu-22.04 + type-coverage: + runs-on: ubuntu-latest steps: - name: 👀 Checkout uses: actions/checkout@v6 - - name: 🪄 Setup + - name: 🪄 Setup PHP uses: ./.github/actions/setup + with: + flux-username: ${{ secrets.FLUX_USERNAME }} + flux-license-key: ${{ secrets.FLUX_LICENSE_KEY }} - name: 🔮 Install Composer Dependencies run: composer install --no-progress --no-interaction --prefer-dist --optimize-autoloader - - name: 🕵️‍♂️ Run Rector - run: composer rector:preview + - name: 🕵️‍♂️ Run PHPStan + run: composer test:type-coverage composer: - runs-on: ubuntu-22.04 + runs-on: ubuntu-latest steps: - name: 👀 Checkout uses: actions/checkout@v6 - name: 🪄 Setup uses: ./.github/actions/setup + with: + flux-username: ${{ secrets.FLUX_USERNAME }} + flux-license-key: ${{ secrets.FLUX_LICENSE_KEY }} - name: 🕵️‍♂️ Run Composer Validate run: composer validate - name: 🕵️‍♂️ Run Composer Audit diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 56d0921f..763e704b 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -4,7 +4,6 @@ on: push: branches: - main - - develop pull_request: concurrency: @@ -13,7 +12,15 @@ concurrency: jobs: pest: - runs-on: ubuntu-22.04 + runs-on: ubuntu-latest + + env: + DB_CONNECTION: pgsql + DB_HOST: 127.0.0.1 + DB_PORT: 5432 + DB_DATABASE: testing + DB_USERNAME: sail + DB_PASSWORD: password services: postgres: @@ -35,11 +42,31 @@ jobs: uses: actions/checkout@v6 - name: 🪄 Setup uses: ./.github/actions/setup + with: + flux-username: ${{ secrets.FLUX_USERNAME }} + flux-license-key: ${{ secrets.FLUX_LICENSE_KEY }} - name: 🔮 Install Composer Dependencies run: composer install --no-progress --no-interaction --prefer-dist --optimize-autoloader - name: 🧶 Install Node Dependencies - run: yarn install --frozen-lockfile --no-progress + run: npm ci - name: 🧱 Build JS Dependencies - run: yarn build + run: npm run build + + - name: Rector Cache + uses: actions/cache@v5 + with: + path: /tmp/rector + key: ${{ runner.os }}-rector-${{ hashFiles('composer.lock') }} + restore-keys: ${{ runner.os }}-rector- + - run: mkdir -p /tmp/rector + + - name: PHPStan Cache + uses: actions/cache@v5 + with: + path: /tmp/phpstan + key: ${{ runner.os }}-phpstan-${{ hashFiles('composer.lock') }} + restore-keys: ${{ runner.os }}-phpstan- + - run: mkdir -p /tmp/phpstan + - name: 🕵️‍♂️ Run Pest Tests - run: ./vendor/bin/pest --configuration=phpunit.ci.xml --parallel --processes=4 --bail + run: composer test:unit diff --git a/.gitignore b/.gitignore index 1891ec24..94fe2925 100644 --- a/.gitignore +++ b/.gitignore @@ -1,29 +1,60 @@ +# Dependencies /node_modules -npm-debug.log -yarn-error.log -package-json.lock /vendor -composer.phar -/.idea -/.vscode -.phpunit.cache -.phpunit.result.cache + +# Build outputs /public/build /public/hot /public/storage -/public/fonts -/public/media /public/css /public/js +/public/fonts /public/**/*.xml + +# Storage /storage/*.key -/storage/framework/cache /storage/pail + +# Environment files .env .env.backup +.env.local +.env.production +.env.staging +.env.testing + +# Testing +/.phpunit.cache +.phpunit.result.cache + +# Laravel Homestead +Homestead.json +Homestead.yaml + +# Package manager files +auth.json +npm-debug.log +yarn-error.log + +# IDE and editor files +/.fleet +/.idea +/.nova +/.vscode +/.zed +.phpactor.json .DS_Store +*.DS_Store Thumbs.db +# Docker and deployment +docker-compose.override.yml +compose.override.yml +.composer-packages +.docker/traefik/certs + +# Octane **/caddy frankenphp frankenphp-worker.php +/public/frankenphp-worker.php diff --git a/.junie/guidelines.md b/.junie/guidelines.md new file mode 100644 index 00000000..130ce873 --- /dev/null +++ b/.junie/guidelines.md @@ -0,0 +1,682 @@ + +=== foundation rules === + +# Laravel Boost Guidelines + +The Laravel Boost guidelines are specifically curated by Laravel maintainers for this application. These guidelines should be followed closely to enhance the user's satisfaction building Laravel applications. + +## Foundational Context +This application is a Laravel application and its main Laravel ecosystems package & versions are below. You are an expert with them all. Ensure you abide by these specific packages & versions. + +- php - 8.4.10 +- filament/filament (FILAMENT) - v4 +- laravel/framework (LARAVEL) - v12 +- laravel/nightwatch (NIGHTWATCH) - v1 +- laravel/octane (OCTANE) - v2 +- laravel/prompts (PROMPTS) - v0 +- laravel/socialite (SOCIALITE) - v5 +- livewire/flux (FLUXUI_FREE) - v2 +- livewire/flux-pro (FLUXUI_PRO) - v2 +- livewire/livewire (LIVEWIRE) - v3 +- livewire/volt (VOLT) - v1 +- larastan/larastan (LARASTAN) - v3 +- laravel/breeze (BREEZE) - v2 +- laravel/mcp (MCP) - v0 +- laravel/pint (PINT) - v1 +- laravel/sail (SAIL) - v1 +- pestphp/pest (PEST) - v3 +- phpunit/phpunit (PHPUNIT) - v11 +- rector/rector (RECTOR) - v2 +- alpinejs (ALPINEJS) - v3 +- prettier (PRETTIER) - v3 +- tailwindcss (TAILWINDCSS) - v4 + +## Conventions +- You must follow all existing code conventions used in this application. When creating or editing a file, check sibling files for the correct structure, approach, naming. +- Use descriptive names for variables and methods. For example, `isRegisteredForDiscounts`, not `discount()`. +- Check for existing components to reuse before writing a new one. + +## Verification Scripts +- Do not create verification scripts or tinker when tests cover that functionality and prove it works. Unit and feature tests are more important. + +## Application Structure & Architecture +- Stick to existing directory structure - don't create new base folders without approval. +- Do not change the application's dependencies without approval. + +## Frontend Bundling +- If the user doesn't see a frontend change reflected in the UI, it could mean they need to run `vendor/bin/sail npm run build`, `vendor/bin/sail npm run dev`, or `vendor/bin/sail composer run dev`. Ask them. + +## Replies +- Be concise in your explanations - focus on what's important rather than explaining obvious details. + +## Documentation Files +- You must only create documentation files if explicitly requested by the user. + + +=== boost rules === + +## Laravel Boost +- Laravel Boost is an MCP server that comes with powerful tools designed specifically for this application. Use them. + +## Artisan +- Use the `list-artisan-commands` tool when you need to call an Artisan command to double check the available parameters. + +## URLs +- Whenever you share a project URL with the user you should use the `get-absolute-url` tool to ensure you're using the correct scheme, domain / IP, and port. + +## Tinker / Debugging +- You should use the `tinker` tool when you need to execute PHP to debug code or query Eloquent models directly. +- Use the `database-query` tool when you only need to read from the database. + +## Reading Browser Logs With the `browser-logs` Tool +- You can read browser logs, errors, and exceptions using the `browser-logs` tool from Boost. +- Only recent browser logs will be useful - ignore old logs. + +## Searching Documentation (Critically Important) +- Boost comes with a powerful `search-docs` tool you should use before any other approaches. This tool automatically passes a list of installed packages and their versions to the remote Boost API, so it returns only version-specific documentation specific for the user's circumstance. You should pass an array of packages to filter on if you know you need docs for particular packages. +- The 'search-docs' tool is perfect for all Laravel related packages, including Laravel, Inertia, Livewire, Filament, Tailwind, Pest, Nova, Nightwatch, etc. +- You must use this tool to search for Laravel-ecosystem documentation before falling back to other approaches. +- Search the documentation before making code changes to ensure we are taking the correct approach. +- Use multiple, broad, simple, topic based queries to start. For example: `['rate limiting', 'routing rate limiting', 'routing']`. +- Do not add package names to queries - package information is already shared. For example, use `test resource table`, not `filament 4 test resource table`. + +### Available Search Syntax +- You can and should pass multiple queries at once. The most relevant results will be returned first. + +1. Simple Word Searches with auto-stemming - query=authentication - finds 'authenticate' and 'auth' +2. Multiple Words (AND Logic) - query=rate limit - finds knowledge containing both "rate" AND "limit" +3. Quoted Phrases (Exact Position) - query="infinite scroll" - Words must be adjacent and in that order +4. Mixed Queries - query=middleware "rate limit" - "middleware" AND exact phrase "rate limit" +5. Multiple Queries - queries=["authentication", "middleware"] - ANY of these terms + + +=== php rules === + +## PHP + +- Always use curly braces for control structures, even if it has one line. + +### Constructors +- Use PHP 8 constructor property promotion in `__construct()`. + - public function __construct(public GitHub $github) { } +- Do not allow empty `__construct()` methods with zero parameters. + +### Type Declarations +- Always use explicit return type declarations for methods and functions. +- Use appropriate PHP type hints for method parameters. + + +protected function isAccessible(User $user, ?string $path = null): bool +{ + ... +} + + +## Comments +- Prefer PHPDoc blocks over comments. Never use comments within the code itself unless there is something _very_ complex going on. + +## PHPDoc Blocks +- Add useful array shape type definitions for arrays when appropriate. + +## Enums +- Typically, keys in an Enum should be TitleCase. For example: `FavoritePerson`, `BestLake`, `Monthly`. + + +=== sail rules === + +## Laravel Sail + +- This project runs inside Laravel Sail's Docker containers. You MUST execute all commands through Sail. +- Start services using `vendor/bin/sail up -d` and stop them with `vendor/bin/sail stop`. +- Open the application in the browser by running `vendor/bin/sail open`. +- Always prefix PHP, Artisan, Composer, and Node commands** with `vendor/bin/sail`. Examples: +- Run Artisan Commands: `vendor/bin/sail artisan migrate` +- Install Composer packages: `vendor/bin/sail composer install` +- Execute node commands: `vendor/bin/sail npm run dev` +- Execute PHP scripts: `vendor/bin/sail php [script]` +- View all available Sail commands by running `vendor/bin/sail` without arguments. + + +=== tests rules === + +## Test Enforcement + +- Every change must be programmatically tested. Write a new test or update an existing test, then run the affected tests to make sure they pass. +- Run the minimum number of tests needed to ensure code quality and speed. Use `vendor/bin/sail artisan test` with a specific filename or filter. + + +=== laravel/core rules === + +## Do Things the Laravel Way + +- Use `vendor/bin/sail artisan make:` commands to create new files (i.e. migrations, controllers, models, etc.). You can list available Artisan commands using the `list-artisan-commands` tool. +- If you're creating a generic PHP class, use `vendor/bin/sail artisan make:class`. +- Pass `--no-interaction` to all Artisan commands to ensure they work without user input. You should also pass the correct `--options` to ensure correct behavior. + +### Database +- Always use proper Eloquent relationship methods with return type hints. Prefer relationship methods over raw queries or manual joins. +- Use Eloquent models and relationships before suggesting raw database queries +- Avoid `DB::`; prefer `Model::query()`. Generate code that leverages Laravel's ORM capabilities rather than bypassing them. +- Generate code that prevents N+1 query problems by using eager loading. +- Use Laravel's query builder for very complex database operations. + +### Model Creation +- When creating new models, create useful factories and seeders for them too. Ask the user if they need any other things, using `list-artisan-commands` to check the available options to `vendor/bin/sail artisan make:model`. + +### APIs & Eloquent Resources +- For APIs, default to using Eloquent API Resources and API versioning unless existing API routes do not, then you should follow existing application convention. + +### Controllers & Validation +- Always create Form Request classes for validation rather than inline validation in controllers. Include both validation rules and custom error messages. +- Check sibling Form Requests to see if the application uses array or string based validation rules. + +### Queues +- Use queued jobs for time-consuming operations with the `ShouldQueue` interface. + +### Authentication & Authorization +- Use Laravel's built-in authentication and authorization features (gates, policies, Sanctum, etc.). + +### URL Generation +- When generating links to other pages, prefer named routes and the `route()` function. + +### Configuration +- Use environment variables only in configuration files - never use the `env()` function directly outside of config files. Always use `config('app.name')`, not `env('APP_NAME')`. + +### Testing +- When creating models for tests, use the factories for the models. Check if the factory has custom states that can be used before manually setting up the model. +- Faker: Use methods such as `$this->faker->word()` or `fake()->randomDigit()`. Follow existing conventions whether to use `$this->faker` or `fake()`. +- When creating tests, make use of `vendor/bin/sail artisan make:test [options] {name}` to create a feature test, and pass `--unit` to create a unit test. Most tests should be feature tests. + +### Vite Error +- If you receive an "Illuminate\Foundation\ViteException: Unable to locate file in Vite manifest" error, you can run `vendor/bin/sail npm run build` or ask the user to run `vendor/bin/sail npm run dev` or `vendor/bin/sail composer run dev`. + + +=== laravel/v12 rules === + +## Laravel 12 + +- Use the `search-docs` tool to get version specific documentation. +- Since Laravel 11, Laravel has a new streamlined file structure which this project uses. + +### Laravel 12 Structure +- No middleware files in `app/Http/Middleware/`. +- `bootstrap/app.php` is the file to register middleware, exceptions, and routing files. +- `bootstrap/providers.php` contains application specific service providers. +- **No app\Console\Kernel.php** - use `bootstrap/app.php` or `routes/console.php` for console configuration. +- **Commands auto-register** - files in `app/Console/Commands/` are automatically available and do not require manual registration. + +### Database +- When modifying a column, the migration must include all of the attributes that were previously defined on the column. Otherwise, they will be dropped and lost. +- Laravel 11 allows limiting eagerly loaded records natively, without external packages: `$query->latest()->limit(10);`. + +### Models +- Casts can and likely should be set in a `casts()` method on a model rather than the `$casts` property. Follow existing conventions from other models. + + +=== fluxui-pro/core rules === + +## Flux UI Pro + +- This project is using the Pro version of Flux UI. It has full access to the free components and variants, as well as full access to the Pro components and variants. +- Flux UI is a component library for Livewire. Flux is a robust, hand-crafted, UI component library for your Livewire applications. It's built using Tailwind CSS and provides a set of components that are easy to use and customize. +- You should use Flux UI components when available. +- Fallback to standard Blade components if Flux is unavailable. +- If available, use Laravel Boost's `search-docs` tool to get the exact documentation and code snippets available for this project. +- Flux UI components look like this: + + + + + + +### Available Components +This is correct as of Boost installation, but there may be additional components within the codebase. + + +accordion, autocomplete, avatar, badge, brand, breadcrumbs, button, calendar, callout, card, chart, checkbox, command, composer, context, date-picker, dropdown, editor, field, file-upload, heading, icon, input, kanban, modal, navbar, otp-input, pagination, pillbox, popover, profile, radio, select, separator, skeleton, slider, switch, table, tabs, text, textarea, time-picker, toast, tooltip + + + +=== livewire/core rules === + +## Livewire Core +- Use the `search-docs` tool to find exact version specific documentation for how to write Livewire & Livewire tests. +- Use the `vendor/bin/sail artisan make:livewire [Posts\CreatePost]` artisan command to create new components +- State should live on the server, with the UI reflecting it. +- All Livewire requests hit the Laravel backend, they're like regular HTTP requests. Always validate form data, and run authorization checks in Livewire actions. + +## Livewire Best Practices +- Livewire components require a single root element. +- Use `wire:loading` and `wire:dirty` for delightful loading states. +- Add `wire:key` in loops: + + ```blade + @foreach ($items as $item) +
+ {{ $item->name }} +
+ @endforeach + ``` + +- Prefer lifecycle hooks like `mount()`, `updatedFoo()` for initialization and reactive side effects: + + + public function mount(User $user) { $this->user = $user; } + public function updatedSearch() { $this->resetPage(); } + + + +## Testing Livewire + + + Livewire::test(Counter::class) + ->assertSet('count', 0) + ->call('increment') + ->assertSet('count', 1) + ->assertSee(1) + ->assertStatus(200); + + + + + $this->get('/posts/create') + ->assertSeeLivewire(CreatePost::class); + + + +=== livewire/v3 rules === + +## Livewire 3 + +### Key Changes From Livewire 2 +- These things changed in Livewire 2, but may not have been updated in this application. Verify this application's setup to ensure you conform with application conventions. + - Use `wire:model.live` for real-time updates, `wire:model` is now deferred by default. + - Components now use the `App\Livewire` namespace (not `App\Http\Livewire`). + - Use `$this->dispatch()` to dispatch events (not `emit` or `dispatchBrowserEvent`). + - Use the `components.layouts.app` view as the typical layout path (not `layouts.app`). + +### New Directives +- `wire:show`, `wire:transition`, `wire:cloak`, `wire:offline`, `wire:target` are available for use. Use the documentation to find usage examples. + +### Alpine +- Alpine is now included with Livewire, don't manually include Alpine.js. +- Plugins included with Alpine: persist, intersect, collapse, and focus. + +### Lifecycle Hooks +- You can listen for `livewire:init` to hook into Livewire initialization, and `fail.status === 419` for the page expiring: + + +document.addEventListener('livewire:init', function () { + Livewire.hook('request', ({ fail }) => { + if (fail && fail.status === 419) { + alert('Your session expired'); + } + }); + + Livewire.hook('message.failed', (message, component) => { + console.error(message); + }); +}); + + + +=== volt/core rules === + +## Livewire Volt + +- This project uses Livewire Volt for interactivity within its pages. New pages requiring interactivity must also use Livewire Volt. There is documentation available for it. +- Make new Volt components using `vendor/bin/sail artisan make:volt [name] [--test] [--pest]` +- Volt is a **class-based** and **functional** API for Livewire that supports single-file components, allowing a component's PHP logic and Blade templates to co-exist in the same file +- Livewire Volt allows PHP logic and Blade templates in one file. Components use the `@volt` directive. +- You must check existing Volt components to determine if they're functional or class based. If you can't detect that, ask the user which they prefer before writing a Volt component. + +### Volt Functional Component Example + + +@volt + 0]); + +$increment = fn () => $this->count++; +$decrement = fn () => $this->count--; + +$double = computed(fn () => $this->count * 2); +?> + +
+

Count: {{ $count }}

+

Double: {{ $this->double }}

+ + +
+@endvolt +
+ + +### Volt Class Based Component Example +To get started, define an anonymous class that extends Livewire\Volt\Component. Within the class, you may utilize all of the features of Livewire using traditional Livewire syntax: + + + +use Livewire\Volt\Component; + +new class extends Component { + public $count = 0; + + public function increment() + { + $this->count++; + } +} ?> + +
+

{{ $count }}

+ +
+
+ + +### Testing Volt & Volt Components +- Use the existing directory for tests if it already exists. Otherwise, fallback to `tests/Feature/Volt`. + + +use Livewire\Volt\Volt; + +test('counter increments', function () { + Volt::test('counter') + ->assertSee('Count: 0') + ->call('increment') + ->assertSee('Count: 1'); +}); + + + + +declare(strict_types=1); + +use App\Models\{User, Product}; +use Livewire\Volt\Volt; + +test('product form creates product', function () { + $user = User::factory()->create(); + + Volt::test('pages.products.create') + ->actingAs($user) + ->set('form.name', 'Test Product') + ->set('form.description', 'Test Description') + ->set('form.price', 99.99) + ->call('create') + ->assertHasNoErrors(); + + expect(Product::where('name', 'Test Product')->exists())->toBeTrue(); +}); + + + +### Common Patterns + + + + null, 'search' => '']); + +$products = computed(fn() => Product::when($this->search, + fn($q) => $q->where('name', 'like', "%{$this->search}%") +)->get()); + +$edit = fn(Product $product) => $this->editing = $product->id; +$delete = fn(Product $product) => $product->delete(); + +?> + + + + + + + + + + + Save + Saving... + + + + +=== pint/core rules === + +## Laravel Pint Code Formatter + +- You must run `vendor/bin/sail bin pint --dirty` before finalizing changes to ensure your code matches the project's expected style. +- Do not run `vendor/bin/sail bin pint --test`, simply run `vendor/bin/sail bin pint` to fix any formatting issues. + + +=== pest/core rules === + +## Pest +### Testing +- If you need to verify a feature is working, write or update a Unit / Feature test. + +### Pest Tests +- All tests must be written using Pest. Use `vendor/bin/sail artisan make:test --pest {name}`. +- You must not remove any tests or test files from the tests directory without approval. These are not temporary or helper files - these are core to the application. +- Tests should test all of the happy paths, failure paths, and weird paths. +- Tests live in the `tests/Feature` and `tests/Unit` directories. +- Pest tests look and behave like this: + +it('is true', function () { + expect(true)->toBeTrue(); +}); + + +### Running Tests +- Run the minimal number of tests using an appropriate filter before finalizing code edits. +- To run all tests: `vendor/bin/sail artisan test`. +- To run all tests in a file: `vendor/bin/sail artisan test tests/Feature/ExampleTest.php`. +- To filter on a particular test name: `vendor/bin/sail artisan test --filter=testName` (recommended after making a change to a related file). +- When the tests relating to your changes are passing, ask the user if they would like to run the entire test suite to ensure everything is still passing. + +### Pest Assertions +- When asserting status codes on a response, use the specific method like `assertForbidden` and `assertNotFound` instead of using `assertStatus(403)` or similar, e.g.: + +it('returns all', function () { + $response = $this->postJson('/api/docs', []); + + $response->assertSuccessful(); +}); + + +### Mocking +- Mocking can be very helpful when appropriate. +- When mocking, you can use the `Pest\Laravel\mock` Pest function, but always import it via `use function Pest\Laravel\mock;` before using it. Alternatively, you can use `$this->mock()` if existing tests do. +- You can also create partial mocks using the same import or self method. + +### Datasets +- Use datasets in Pest to simplify tests which have a lot of duplicated data. This is often the case when testing validation rules, so consider going with this solution when writing tests for validation rules. + + +it('has emails', function (string $email) { + expect($email)->not->toBeEmpty(); +})->with([ + 'james' => 'james@laravel.com', + 'taylor' => 'taylor@laravel.com', +]); + + + +=== tailwindcss/core rules === + +## Tailwind Core + +- Use Tailwind CSS classes to style HTML, check and use existing tailwind conventions within the project before writing your own. +- Offer to extract repeated patterns into components that match the project's conventions (i.e. Blade, JSX, Vue, etc..) +- Think through class placement, order, priority, and defaults - remove redundant classes, add classes to parent or child carefully to limit repetition, group elements logically +- You can use the `search-docs` tool to get exact examples from the official documentation when needed. + +### Spacing +- When listing items, use gap utilities for spacing, don't use margins. + + +
+
Superior
+
Michigan
+
Erie
+
+
+ + +### Dark Mode +- If existing pages and components support dark mode, new pages and components must support dark mode in a similar way, typically using `dark:`. + + +=== tailwindcss/v4 rules === + +## Tailwind 4 + +- Always use Tailwind CSS v4 - do not use the deprecated utilities. +- `corePlugins` is not supported in Tailwind v4. +- In Tailwind v4, configuration is CSS-first using the `@theme` directive — no separate `tailwind.config.js` file is needed. + +@theme { + --color-brand: oklch(0.72 0.11 178); +} + + +- In Tailwind v4, you import Tailwind using a regular CSS `@import` statement, not using the `@tailwind` directives used in v3: + + + - @tailwind base; + - @tailwind components; + - @tailwind utilities; + + @import "tailwindcss"; + + + +### Replaced Utilities +- Tailwind v4 removed deprecated utilities. Do not use the deprecated option - use the replacement. +- Opacity values are still numeric. + +| Deprecated | Replacement | +|------------+--------------| +| bg-opacity-* | bg-black/* | +| text-opacity-* | text-black/* | +| border-opacity-* | border-black/* | +| divide-opacity-* | divide-black/* | +| ring-opacity-* | ring-black/* | +| placeholder-opacity-* | placeholder-black/* | +| flex-shrink-* | shrink-* | +| flex-grow-* | grow-* | +| overflow-ellipsis | text-ellipsis | +| decoration-slice | box-decoration-slice | +| decoration-clone | box-decoration-clone | + + +=== filament/filament rules === + +## Filament +- Filament is used by this application, check how and where to follow existing application conventions. +- Filament is a Server-Driven UI (SDUI) framework for Laravel. It allows developers to define user interfaces in PHP using structured configuration objects. It is built on top of Livewire, Alpine.js, and Tailwind CSS. +- You can use the `search-docs` tool to get information from the official Filament documentation when needed. This is very useful for Artisan command arguments, specific code examples, testing functionality, relationship management, and ensuring you're following idiomatic practices. +- Utilize static `make()` methods for consistent component initialization. + +### Artisan +- You must use the Filament specific Artisan commands to create new files or components for Filament. You can find these with the `list-artisan-commands` tool, or with `php artisan` and the `--help` option. +- Inspect the required options, always pass `--no-interaction`, and valid arguments for other options when applicable. + +### Filament's Core Features +- Actions: Handle doing something within the application, often with a button or link. Actions encapsulate the UI, the interactive modal window, and the logic that should be executed when the modal window is submitted. They can be used anywhere in the UI and are commonly used to perform one-time actions like deleting a record, sending an email, or updating data in the database based on modal form input. +- Forms: Dynamic forms rendered within other features, such as resources, action modals, table filters, and more. +- Infolists: Read-only lists of data. +- Notifications: Flash notifications displayed to users within the application. +- Panels: The top-level container in Filament that can include all other features like pages, resources, forms, tables, notifications, actions, infolists, and widgets. +- Resources: Static classes that are used to build CRUD interfaces for Eloquent models. Typically live in `app/Filament/Resources`. +- Schemas: Represent components that define the structure and behavior of the UI, such as forms, tables, or lists. +- Tables: Interactive tables with filtering, sorting, pagination, and more. +- Widgets: Small component included within dashboards, often used for displaying data in charts, tables, or as a stat. + +### Relationships +- Determine if you can use the `relationship()` method on form components when you need `options` for a select, checkbox, repeater, or when building a `Fieldset`: + + +Forms\Components\Select::make('user_id') + ->label('Author') + ->relationship('author') + ->required(), + + + +## Testing +- It's important to test Filament functionality for user satisfaction. +- Ensure that you are authenticated to access the application within the test. +- Filament uses Livewire, so start assertions with `livewire()` or `Livewire::test()`. + +### Example Tests + + + livewire(ListUsers::class) + ->assertCanSeeTableRecords($users) + ->searchTable($users->first()->name) + ->assertCanSeeTableRecords($users->take(1)) + ->assertCanNotSeeTableRecords($users->skip(1)) + ->searchTable($users->last()->email) + ->assertCanSeeTableRecords($users->take(-1)) + ->assertCanNotSeeTableRecords($users->take($users->count() - 1)); + + + + livewire(CreateUser::class) + ->fillForm([ + 'name' => 'Howdy', + 'email' => 'howdy@example.com', + ]) + ->call('create') + ->assertNotified() + ->assertRedirect(); + + assertDatabaseHas(User::class, [ + 'name' => 'Howdy', + 'email' => 'howdy@example.com', + ]); + + + + use Filament\Facades\Filament; + + Filament::setCurrentPanel('app'); + + + + livewire(EditInvoice::class, [ + 'invoice' => $invoice, + ])->callAction('send'); + + expect($invoice->refresh())->isSent()->toBeTrue(); + + + +### Important Version 4 Changes +- File visibility is now `private` by default. +- The `deferFilters` method from Filament v3 is now the default behavior in Filament v4, so users must click a button before the filters are applied to the table. To disable this behavior, you can use the `deferFilters(false)` method. +- The `Grid`, `Section`, and `Fieldset` layout components no longer span all columns by default. +- The `all` pagination page method is not available for tables by default. +- All action classes extend `Filament\Actions\Action`. No action classes exist in `Filament\Tables\Actions`. +- The `Form` & `Infolist` layout components have been moved to `Filament\Schemas\Components`, for example `Grid`, `Section`, `Fieldset`, `Tabs`, `Wizard`, etc. +- A new `Repeater` component for Forms has been added. +- Icons now use the `Filament\Support\Icons\Heroicon` Enum by default. Other options are available and documented. + +### Organize Component Classes Structure +- Schema components: `Schemas/Components/` +- Table columns: `Tables/Columns/` +- Table filters: `Tables/Filters/` +- Actions: `Actions/` +
diff --git a/.junie/mcp/mcp.json b/.junie/mcp/mcp.json new file mode 100644 index 00000000..04eba29c --- /dev/null +++ b/.junie/mcp/mcp.json @@ -0,0 +1,11 @@ +{ + "mcpServers": { + "laravel-boost": { + "command": "vendor/bin/sail", + "args": [ + "artisan", + "boost:mcp" + ] + } + } +} \ No newline at end of file diff --git a/.mcp.json b/.mcp.json index 29eda418..90de8b5c 100644 --- a/.mcp.json +++ b/.mcp.json @@ -1,11 +1,8 @@ { "mcpServers": { "laravel-boost": { - "command": "./vendor/bin/sail", - "args": [ - "artisan", - "boost:mcp" - ] + "command": "vendor/bin/sail", + "args": ["artisan", "boost:mcp"] } } } diff --git a/.prettierignore b/.prettierignore deleted file mode 100644 index 78bb481a..00000000 --- a/.prettierignore +++ /dev/null @@ -1,7 +0,0 @@ -node_modules -/vendor -/public -.git -package-lock.json -yarn.lock -composer.lock diff --git a/.prettierrc b/.prettierrc deleted file mode 100644 index 9ef92b45..00000000 --- a/.prettierrc +++ /dev/null @@ -1,9 +0,0 @@ -{ - "printWidth": 120, - "semi": false, - "singleQuote": true, - "tabWidth": 4, - "tailwindConfig": "./tailwind.config.js", - "trailingComma": "all", - "plugins": ["prettier-plugin-tailwindcss"] -} diff --git a/CLAUDE.md b/CLAUDE.md index e86b7619..130ce873 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -15,6 +15,8 @@ This application is a Laravel application and its main Laravel ecosystems packag - laravel/octane (OCTANE) - v2 - laravel/prompts (PROMPTS) - v0 - laravel/socialite (SOCIALITE) - v5 +- livewire/flux (FLUXUI_FREE) - v2 +- livewire/flux-pro (FLUXUI_PRO) - v2 - livewire/livewire (LIVEWIRE) - v3 - livewire/volt (VOLT) - v1 - larastan/larastan (LARASTAN) - v3 @@ -29,7 +31,6 @@ This application is a Laravel application and its main Laravel ecosystems packag - prettier (PRETTIER) - v3 - tailwindcss (TAILWINDCSS) - v4 - ## Conventions - You must follow all existing code conventions used in this application. When creating or editing a file, check sibling files for the correct structure, approach, naming. - Use descriptive names for variables and methods. For example, `isRegisteredForDiscounts`, not `discount()`. @@ -43,7 +44,7 @@ This application is a Laravel application and its main Laravel ecosystems packag - Do not change the application's dependencies without approval. ## Frontend Bundling -- If the user doesn't see a frontend change reflected in the UI, it could mean they need to run `npm run build`, `npm run dev`, or `composer run dev`. Ask them. +- If the user doesn't see a frontend change reflected in the UI, it could mean they need to run `vendor/bin/sail npm run build`, `vendor/bin/sail npm run dev`, or `vendor/bin/sail composer run dev`. Ask them. ## Replies - Be concise in your explanations - focus on what's important rather than explaining obvious details. @@ -121,116 +122,35 @@ protected function isAccessible(User $user, ?string $path = null): bool - Typically, keys in an Enum should be TitleCase. For example: `FavoritePerson`, `BestLake`, `Monthly`. -=== filament/core rules === - -## Filament -- Filament is used by this application, check how and where to follow existing application conventions. -- Filament is a Server-Driven UI (SDUI) framework for Laravel. It allows developers to define user interfaces in PHP using structured configuration objects. It is built on top of Livewire, Alpine.js, and Tailwind CSS. -- You can use the `search-docs` tool to get information from the official Filament documentation when needed. This is very useful for Artisan command arguments, specific code examples, testing functionality, relationship management, and ensuring you're following idiomatic practices. -- Utilize static `make()` methods for consistent component initialization. - -### Artisan -- You must use the Filament specific Artisan commands to create new files or components for Filament. You can find these with the `list-artisan-commands` tool, or with `php artisan` and the `--help` option. -- Inspect the required options, always pass `--no-interaction`, and valid arguments for other options when applicable. - -### Filament's Core Features -- Actions: Handle doing something within the application, often with a button or link. Actions encapsulate the UI, the interactive modal window, and the logic that should be executed when the modal window is submitted. They can be used anywhere in the UI and are commonly used to perform one-time actions like deleting a record, sending an email, or updating data in the database based on modal form input. -- Forms: Dynamic forms rendered within other features, such as resources, action modals, table filters, and more. -- Infolists: Read-only lists of data. -- Notifications: Flash notifications displayed to users within the application. -- Panels: The top-level container in Filament that can include all other features like pages, resources, forms, tables, notifications, actions, infolists, and widgets. -- Resources: Static classes that are used to build CRUD interfaces for Eloquent models. Typically live in `app/Filament/Resources`. -- Schemas: Represent components that define the structure and behavior of the UI, such as forms, tables, or lists. -- Tables: Interactive tables with filtering, sorting, pagination, and more. -- Widgets: Small component included within dashboards, often used for displaying data in charts, tables, or as a stat. - -### Relationships -- Determine if you can use the `relationship()` method on form components when you need `options` for a select, checkbox, repeater, or when building a `Fieldset`: - - -Forms\Components\Select::make('user_id') - ->label('Author') - ->relationship('author') - ->required(), - - - -## Testing -- It's important to test Filament functionality for user satisfaction. -- Ensure that you are authenticated to access the application within the test. -- Filament uses Livewire, so start assertions with `livewire()` or `Livewire::test()`. - -### Example Tests - - - livewire(ListUsers::class) - ->assertCanSeeTableRecords($users) - ->searchTable($users->first()->name) - ->assertCanSeeTableRecords($users->take(1)) - ->assertCanNotSeeTableRecords($users->skip(1)) - ->searchTable($users->last()->email) - ->assertCanSeeTableRecords($users->take(-1)) - ->assertCanNotSeeTableRecords($users->take($users->count() - 1)); - - - - livewire(CreateUser::class) - ->fillForm([ - 'name' => 'Howdy', - 'email' => 'howdy@example.com', - ]) - ->call('create') - ->assertNotified() - ->assertRedirect(); - - assertDatabaseHas(User::class, [ - 'name' => 'Howdy', - 'email' => 'howdy@example.com', - ]); - - - - use Filament\Facades\Filament; - - Filament::setCurrentPanel('app'); - - - - livewire(EditInvoice::class, [ - 'invoice' => $invoice, - ])->callAction('send'); +=== sail rules === - expect($invoice->refresh())->isSent()->toBeTrue(); - +## Laravel Sail +- This project runs inside Laravel Sail's Docker containers. You MUST execute all commands through Sail. +- Start services using `vendor/bin/sail up -d` and stop them with `vendor/bin/sail stop`. +- Open the application in the browser by running `vendor/bin/sail open`. +- Always prefix PHP, Artisan, Composer, and Node commands** with `vendor/bin/sail`. Examples: +- Run Artisan Commands: `vendor/bin/sail artisan migrate` +- Install Composer packages: `vendor/bin/sail composer install` +- Execute node commands: `vendor/bin/sail npm run dev` +- Execute PHP scripts: `vendor/bin/sail php [script]` +- View all available Sail commands by running `vendor/bin/sail` without arguments. -=== filament/v4 rules === -## Filament 4 +=== tests rules === -### Important Version 4 Changes -- File visibility is now `private` by default. -- The `deferFilters` method from Filament v3 is now the default behavior in Filament v4, so users must click a button before the filters are applied to the table. To disable this behavior, you can use the `deferFilters(false)` method. -- The `Grid`, `Section`, and `Fieldset` layout components no longer span all columns by default. -- The `all` pagination page method is not available for tables by default. -- All action classes extend `Filament\Actions\Action`. No action classes exist in `Filament\Tables\Actions`. -- The `Form` & `Infolist` layout components have been moved to `Filament\Schemas\Components`, for example `Grid`, `Section`, `Fieldset`, `Tabs`, `Wizard`, etc. -- A new `Repeater` component for Forms has been added. -- Icons now use the `Filament\Support\Icons\Heroicon` Enum by default. Other options are available and documented. +## Test Enforcement -### Organize Component Classes Structure -- Schema components: `Schemas/Components/` -- Table columns: `Tables/Columns/` -- Table filters: `Tables/Filters/` -- Actions: `Actions/` +- Every change must be programmatically tested. Write a new test or update an existing test, then run the affected tests to make sure they pass. +- Run the minimum number of tests needed to ensure code quality and speed. Use `vendor/bin/sail artisan test` with a specific filename or filter. === laravel/core rules === ## Do Things the Laravel Way -- Use `php artisan make:` commands to create new files (i.e. migrations, controllers, models, etc.). You can list available Artisan commands using the `list-artisan-commands` tool. -- If you're creating a generic PHP class, use `artisan make:class`. +- Use `vendor/bin/sail artisan make:` commands to create new files (i.e. migrations, controllers, models, etc.). You can list available Artisan commands using the `list-artisan-commands` tool. +- If you're creating a generic PHP class, use `vendor/bin/sail artisan make:class`. - Pass `--no-interaction` to all Artisan commands to ensure they work without user input. You should also pass the correct `--options` to ensure correct behavior. ### Database @@ -241,7 +161,7 @@ Forms\Components\Select::make('user_id') - Use Laravel's query builder for very complex database operations. ### Model Creation -- When creating new models, create useful factories and seeders for them too. Ask the user if they need any other things, using `list-artisan-commands` to check the available options to `php artisan make:model`. +- When creating new models, create useful factories and seeders for them too. Ask the user if they need any other things, using `list-artisan-commands` to check the available options to `vendor/bin/sail artisan make:model`. ### APIs & Eloquent Resources - For APIs, default to using Eloquent API Resources and API versioning unless existing API routes do not, then you should follow existing application convention. @@ -265,10 +185,10 @@ Forms\Components\Select::make('user_id') ### Testing - When creating models for tests, use the factories for the models. Check if the factory has custom states that can be used before manually setting up the model. - Faker: Use methods such as `$this->faker->word()` or `fake()->randomDigit()`. Follow existing conventions whether to use `$this->faker` or `fake()`. -- When creating tests, make use of `php artisan make:test [options] ` to create a feature test, and pass `--unit` to create a unit test. Most tests should be feature tests. +- When creating tests, make use of `vendor/bin/sail artisan make:test [options] {name}` to create a feature test, and pass `--unit` to create a unit test. Most tests should be feature tests. ### Vite Error -- If you receive an "Illuminate\Foundation\ViteException: Unable to locate file in Vite manifest" error, you can run `npm run build` or ask the user to run `npm run dev` or `composer run dev`. +- If you receive an "Illuminate\Foundation\ViteException: Unable to locate file in Vite manifest" error, you can run `vendor/bin/sail npm run build` or ask the user to run `vendor/bin/sail npm run dev` or `vendor/bin/sail composer run dev`. === laravel/v12 rules === @@ -293,11 +213,35 @@ Forms\Components\Select::make('user_id') - Casts can and likely should be set in a `casts()` method on a model rather than the `$casts` property. Follow existing conventions from other models. +=== fluxui-pro/core rules === + +## Flux UI Pro + +- This project is using the Pro version of Flux UI. It has full access to the free components and variants, as well as full access to the Pro components and variants. +- Flux UI is a component library for Livewire. Flux is a robust, hand-crafted, UI component library for your Livewire applications. It's built using Tailwind CSS and provides a set of components that are easy to use and customize. +- You should use Flux UI components when available. +- Fallback to standard Blade components if Flux is unavailable. +- If available, use Laravel Boost's `search-docs` tool to get the exact documentation and code snippets available for this project. +- Flux UI components look like this: + + + + + + +### Available Components +This is correct as of Boost installation, but there may be additional components within the codebase. + + +accordion, autocomplete, avatar, badge, brand, breadcrumbs, button, calendar, callout, card, chart, checkbox, command, composer, context, date-picker, dropdown, editor, field, file-upload, heading, icon, input, kanban, modal, navbar, otp-input, pagination, pillbox, popover, profile, radio, select, separator, skeleton, slider, switch, table, tabs, text, textarea, time-picker, toast, tooltip + + + === livewire/core rules === ## Livewire Core - Use the `search-docs` tool to find exact version specific documentation for how to write Livewire & Livewire tests. -- Use the `php artisan make:livewire [Posts\CreatePost]` artisan command to create new components +- Use the `vendor/bin/sail artisan make:livewire [Posts\CreatePost]` artisan command to create new components - State should live on the server, with the UI reflecting it. - All Livewire requests hit the Laravel backend, they're like regular HTTP requests. Always validate form data, and run authorization checks in Livewire actions. @@ -381,10 +325,33 @@ document.addEventListener('livewire:init', function () { ## Livewire Volt - This project uses Livewire Volt for interactivity within its pages. New pages requiring interactivity must also use Livewire Volt. There is documentation available for it. -- Make new Volt components using `php artisan make:volt [name] [--test] [--pest]` +- Make new Volt components using `vendor/bin/sail artisan make:volt [name] [--test] [--pest]` - Volt is a **class-based** and **functional** API for Livewire that supports single-file components, allowing a component's PHP logic and Blade templates to co-exist in the same file -- Livewire Volt allows PHP logic and Blade templates in one file. Components use the `@livewire("volt-anonymous-fragment-eyJuYW1lIjoidm9sdC1hbm9ueW1vdXMtZnJhZ21lbnQtYmQ5YWJiNTE3YWMyMTgwOTA1ZmUxMzAxODk0MGJiZmIiLCJwYXRoIjoic3RvcmFnZVwvZnJhbWV3b3JrXC92aWV3c1wvMTUxYWRjZWRjMzBhMzllOWIxNzQ0ZDRiMWRjY2FjYWIuYmxhZGUucGhwIn0=", Livewire\Volt\Precompilers\ExtractFragments::componentArguments([...get_defined_vars(), ...array ( -)])) +- Livewire Volt allows PHP logic and Blade templates in one file. Components use the `@volt` directive. +- You must check existing Volt components to determine if they're functional or class based. If you can't detect that, ask the user which they prefer before writing a Volt component. + +### Volt Functional Component Example + + +@volt + 0]); + +$increment = fn () => $this->count++; +$decrement = fn () => $this->count--; + +$double = computed(fn () => $this->count * 2); +?> + +
+

Count: {{ $count }}

+

Double: {{ $this->double }}

+ + +
+@endvolt
@@ -490,19 +457,18 @@ $delete = fn(Product $product) => $product->delete(); ## Laravel Pint Code Formatter -- You must run `vendor/bin/pint --dirty` before finalizing changes to ensure your code matches the project's expected style. -- Do not run `vendor/bin/pint --test`, simply run `vendor/bin/pint` to fix any formatting issues. +- You must run `vendor/bin/sail bin pint --dirty` before finalizing changes to ensure your code matches the project's expected style. +- Do not run `vendor/bin/sail bin pint --test`, simply run `vendor/bin/sail bin pint` to fix any formatting issues. === pest/core rules === ## Pest - ### Testing - If you need to verify a feature is working, write or update a Unit / Feature test. ### Pest Tests -- All tests must be written using Pest. Use `php artisan make:test --pest `. +- All tests must be written using Pest. Use `vendor/bin/sail artisan make:test --pest {name}`. - You must not remove any tests or test files from the tests directory without approval. These are not temporary or helper files - these are core to the application. - Tests should test all of the happy paths, failure paths, and weird paths. - Tests live in the `tests/Feature` and `tests/Unit` directories. @@ -515,9 +481,9 @@ it('is true', function () { ### Running Tests - Run the minimal number of tests using an appropriate filter before finalizing code edits. -- To run all tests: `php artisan test`. -- To run all tests in a file: `php artisan test tests/Feature/ExampleTest.php`. -- To filter on a particular test name: `php artisan test --filter=testName` (recommended after making a change to a related file). +- To run all tests: `vendor/bin/sail artisan test`. +- To run all tests in a file: `vendor/bin/sail artisan test tests/Feature/ExampleTest.php`. +- To filter on a particular test name: `vendor/bin/sail artisan test --filter=testName` (recommended after making a change to a related file). - When the tests relating to your changes are passing, ask the user if they would like to run the entire test suite to ensure everything is still passing. ### Pest Assertions @@ -579,6 +545,13 @@ it('has emails', function (string $email) { - Always use Tailwind CSS v4 - do not use the deprecated utilities. - `corePlugins` is not supported in Tailwind v4. +- In Tailwind v4, configuration is CSS-first using the `@theme` directive — no separate `tailwind.config.js` file is needed. + +@theme { + --color-brand: oklch(0.72 0.11 178); +} + + - In Tailwind v4, you import Tailwind using a regular CSS `@import` statement, not using the `@tailwind` directives used in v3: @@ -608,10 +581,102 @@ it('has emails', function (string $email) { | decoration-clone | box-decoration-clone | -=== tests rules === +=== filament/filament rules === -## Test Enforcement +## Filament +- Filament is used by this application, check how and where to follow existing application conventions. +- Filament is a Server-Driven UI (SDUI) framework for Laravel. It allows developers to define user interfaces in PHP using structured configuration objects. It is built on top of Livewire, Alpine.js, and Tailwind CSS. +- You can use the `search-docs` tool to get information from the official Filament documentation when needed. This is very useful for Artisan command arguments, specific code examples, testing functionality, relationship management, and ensuring you're following idiomatic practices. +- Utilize static `make()` methods for consistent component initialization. -- Every change must be programmatically tested. Write a new test or update an existing test, then run the affected tests to make sure they pass. -- Run the minimum number of tests needed to ensure code quality and speed. Use `php artisan test` with a specific filename or filter. +### Artisan +- You must use the Filament specific Artisan commands to create new files or components for Filament. You can find these with the `list-artisan-commands` tool, or with `php artisan` and the `--help` option. +- Inspect the required options, always pass `--no-interaction`, and valid arguments for other options when applicable. + +### Filament's Core Features +- Actions: Handle doing something within the application, often with a button or link. Actions encapsulate the UI, the interactive modal window, and the logic that should be executed when the modal window is submitted. They can be used anywhere in the UI and are commonly used to perform one-time actions like deleting a record, sending an email, or updating data in the database based on modal form input. +- Forms: Dynamic forms rendered within other features, such as resources, action modals, table filters, and more. +- Infolists: Read-only lists of data. +- Notifications: Flash notifications displayed to users within the application. +- Panels: The top-level container in Filament that can include all other features like pages, resources, forms, tables, notifications, actions, infolists, and widgets. +- Resources: Static classes that are used to build CRUD interfaces for Eloquent models. Typically live in `app/Filament/Resources`. +- Schemas: Represent components that define the structure and behavior of the UI, such as forms, tables, or lists. +- Tables: Interactive tables with filtering, sorting, pagination, and more. +- Widgets: Small component included within dashboards, often used for displaying data in charts, tables, or as a stat. + +### Relationships +- Determine if you can use the `relationship()` method on form components when you need `options` for a select, checkbox, repeater, or when building a `Fieldset`: + + +Forms\Components\Select::make('user_id') + ->label('Author') + ->relationship('author') + ->required(), + + + +## Testing +- It's important to test Filament functionality for user satisfaction. +- Ensure that you are authenticated to access the application within the test. +- Filament uses Livewire, so start assertions with `livewire()` or `Livewire::test()`. + +### Example Tests + + + livewire(ListUsers::class) + ->assertCanSeeTableRecords($users) + ->searchTable($users->first()->name) + ->assertCanSeeTableRecords($users->take(1)) + ->assertCanNotSeeTableRecords($users->skip(1)) + ->searchTable($users->last()->email) + ->assertCanSeeTableRecords($users->take(-1)) + ->assertCanNotSeeTableRecords($users->take($users->count() - 1)); + + + + livewire(CreateUser::class) + ->fillForm([ + 'name' => 'Howdy', + 'email' => 'howdy@example.com', + ]) + ->call('create') + ->assertNotified() + ->assertRedirect(); + + assertDatabaseHas(User::class, [ + 'name' => 'Howdy', + 'email' => 'howdy@example.com', + ]); + + + + use Filament\Facades\Filament; + + Filament::setCurrentPanel('app'); + + + + livewire(EditInvoice::class, [ + 'invoice' => $invoice, + ])->callAction('send'); + + expect($invoice->refresh())->isSent()->toBeTrue(); + + + +### Important Version 4 Changes +- File visibility is now `private` by default. +- The `deferFilters` method from Filament v3 is now the default behavior in Filament v4, so users must click a button before the filters are applied to the table. To disable this behavior, you can use the `deferFilters(false)` method. +- The `Grid`, `Section`, and `Fieldset` layout components no longer span all columns by default. +- The `all` pagination page method is not available for tables by default. +- All action classes extend `Filament\Actions\Action`. No action classes exist in `Filament\Tables\Actions`. +- The `Form` & `Infolist` layout components have been moved to `Filament\Schemas\Components`, for example `Grid`, `Section`, `Fieldset`, `Tabs`, `Wizard`, etc. +- A new `Repeater` component for Forms has been added. +- Icons now use the `Filament\Support\Icons\Heroicon` Enum by default. Other options are available and documented. + +### Organize Component Classes Structure +- Schema components: `Schemas/Components/` +- Table columns: `Tables/Columns/` +- Table filters: `Tables/Filters/` +- Actions: `Actions/` diff --git a/Dockerfile b/Dockerfile index 6b7eefd3..0057ed97 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,16 +1,16 @@ ARG PHP_VERSION=8.4 ARG NODE_VERSION=22 -############################################ +#--------------------------------- # Base Image -############################################ +#--------------------------------- FROM ghcr.io/yieldstudio/php:${PHP_VERSION}-frankenphp AS base ENV HEALTHCHECK_PATH="/up" -############################################ +#--------------------------------- # Development Image -############################################ +#--------------------------------- FROM base AS development ARG WWWUSER @@ -19,7 +19,7 @@ ARG NODE_VERSION=22 ARG MYSQL_CLIENT="mysql-client" ARG POSTGRES_VERSION=17 -ENV XDEBUG_MODE="off" +ENV XDEBUG_MODE="coverage,debug" ENV XDEBUG_CONFIG="client_host=host.docker.internal" ENV PHP_MEMORY_LIMIT=1024M @@ -27,7 +27,9 @@ USER root RUN apt-get update && apt-get upgrade -y \ && mkdir -p /etc/apt/keyrings \ - && apt-get install -y gnupg gosu curl ca-certificates zip unzip git supervisor sqlite3 libcap2-bin libpng-dev python3 dnsutils librsvg2-bin fswatch ffmpeg nano \ + && apt-get install -y gnupg gosu curl ca-certificates zip unzip git supervisor sqlite3 libcap2-bin libpng-dev python3 dnsutils librsvg2-bin fswatch ffmpeg nano \ + libevent-2.1-7 libevent-core-2.1-7 libevent-extra-2.1-7 libevent-openssl-2.1-7 libevent-pthreads-2.1-7 \ + libflite1 flite1-dev libenchant-2-2 libsecret-1-0 libmanette-0.2-0 libgles2-mesa libx264-dev \ && curl -fsSL https://deb.nodesource.com/gpgkey/nodesource-repo.gpg.key | gpg --dearmor -o /etc/apt/keyrings/nodesource.gpg \ && echo "deb [signed-by=/etc/apt/keyrings/nodesource.gpg] https://deb.nodesource.com/node_$NODE_VERSION.x nodistro main" > /etc/apt/sources.list.d/nodesource.list \ && apt-get update \ @@ -49,13 +51,13 @@ RUN docker-php-set-id www-data $WWWUSER:$WWWGROUP \ && docker-php-set-file-permissions --owner $WWWUSER:$WWWGROUP --service frankenphp \ && useradd -mNo -g www-data -u $(id -u www-data) sail -RUN install-php-extensions xdebug +RUN install-php-extensions xdebug sockets USER www-data -############################################ +#--------------------------------- # CI image -############################################ +#--------------------------------- FROM base AS ci ENV AUTORUN_ENABLED=false @@ -69,11 +71,11 @@ ENV XDEBUG_CONFIG="client_host=host.docker.internal client_port=9003" # the PHP-FPM pool to run as www-data USER root -RUN install-php-extensions xdebug +RUN install-php-extensions xdebug sockets -############################################ +#--------------------------------- # Composer Build -############################################ +#--------------------------------- FROM base AS composer COPY --from=composer:latest /usr/bin/composer /usr/bin/composer @@ -83,9 +85,9 @@ COPY --chown=www-data:www-data . . RUN composer install --no-dev --no-interaction --no-scripts --prefer-dist \ && composer dump-autoload --classmap-authoritative --no-dev --optimize -############################################ +#--------------------------------- # Assets Build -############################################ +#--------------------------------- FROM node:${NODE_VERSION}-slim AS frontend WORKDIR /app @@ -108,15 +110,18 @@ RUN if [ -f yarn.lock ]; then \ echo "No lock file found, skipping asset build."; \ fi -############################################ +#--------------------------------- # Production Image -############################################ +#--------------------------------- FROM base -ENV AUTORUN_ENABLED=true -ENV PHP_OPCACHE_ENABLE=1 -ENV PHP_MEMORY_LIMIT=512M -ENV SSL_MODE=mixed +ENV \ + AUTORUN_ENABLED=true \ + SSL_MODE=off \ + PHP_OPCACHE_ENABLE=1 \ + PHP_MEMORY_LIMIT=512M \ + OCTANE_SERVER=frankenphp \ + HEALTHCHECK_PATH="/up" USER root @@ -124,17 +129,11 @@ RUN apt-get update && apt-get install -y openssh-client && rm -rf /var/lib/apt/l USER www-data -# Copy Filament assets from Composer COPY --from=composer --chown=www-data:www-data /var/www/html/public/css ./public/css COPY --from=composer --chown=www-data:www-data /var/www/html/public/js ./public/js - -# Composer dependencies COPY --from=composer --chown=www-data:www-data /var/www/html/vendor ./vendor - -# Application assets COPY --from=frontend --chown=www-data:www-data /app/public/build ./public/build -# Application source COPY --chown=www-data:www-data . /var/www/html # Start Octane with FrankenPHP diff --git a/GEMINI.md b/GEMINI.md new file mode 100644 index 00000000..130ce873 --- /dev/null +++ b/GEMINI.md @@ -0,0 +1,682 @@ + +=== foundation rules === + +# Laravel Boost Guidelines + +The Laravel Boost guidelines are specifically curated by Laravel maintainers for this application. These guidelines should be followed closely to enhance the user's satisfaction building Laravel applications. + +## Foundational Context +This application is a Laravel application and its main Laravel ecosystems package & versions are below. You are an expert with them all. Ensure you abide by these specific packages & versions. + +- php - 8.4.10 +- filament/filament (FILAMENT) - v4 +- laravel/framework (LARAVEL) - v12 +- laravel/nightwatch (NIGHTWATCH) - v1 +- laravel/octane (OCTANE) - v2 +- laravel/prompts (PROMPTS) - v0 +- laravel/socialite (SOCIALITE) - v5 +- livewire/flux (FLUXUI_FREE) - v2 +- livewire/flux-pro (FLUXUI_PRO) - v2 +- livewire/livewire (LIVEWIRE) - v3 +- livewire/volt (VOLT) - v1 +- larastan/larastan (LARASTAN) - v3 +- laravel/breeze (BREEZE) - v2 +- laravel/mcp (MCP) - v0 +- laravel/pint (PINT) - v1 +- laravel/sail (SAIL) - v1 +- pestphp/pest (PEST) - v3 +- phpunit/phpunit (PHPUNIT) - v11 +- rector/rector (RECTOR) - v2 +- alpinejs (ALPINEJS) - v3 +- prettier (PRETTIER) - v3 +- tailwindcss (TAILWINDCSS) - v4 + +## Conventions +- You must follow all existing code conventions used in this application. When creating or editing a file, check sibling files for the correct structure, approach, naming. +- Use descriptive names for variables and methods. For example, `isRegisteredForDiscounts`, not `discount()`. +- Check for existing components to reuse before writing a new one. + +## Verification Scripts +- Do not create verification scripts or tinker when tests cover that functionality and prove it works. Unit and feature tests are more important. + +## Application Structure & Architecture +- Stick to existing directory structure - don't create new base folders without approval. +- Do not change the application's dependencies without approval. + +## Frontend Bundling +- If the user doesn't see a frontend change reflected in the UI, it could mean they need to run `vendor/bin/sail npm run build`, `vendor/bin/sail npm run dev`, or `vendor/bin/sail composer run dev`. Ask them. + +## Replies +- Be concise in your explanations - focus on what's important rather than explaining obvious details. + +## Documentation Files +- You must only create documentation files if explicitly requested by the user. + + +=== boost rules === + +## Laravel Boost +- Laravel Boost is an MCP server that comes with powerful tools designed specifically for this application. Use them. + +## Artisan +- Use the `list-artisan-commands` tool when you need to call an Artisan command to double check the available parameters. + +## URLs +- Whenever you share a project URL with the user you should use the `get-absolute-url` tool to ensure you're using the correct scheme, domain / IP, and port. + +## Tinker / Debugging +- You should use the `tinker` tool when you need to execute PHP to debug code or query Eloquent models directly. +- Use the `database-query` tool when you only need to read from the database. + +## Reading Browser Logs With the `browser-logs` Tool +- You can read browser logs, errors, and exceptions using the `browser-logs` tool from Boost. +- Only recent browser logs will be useful - ignore old logs. + +## Searching Documentation (Critically Important) +- Boost comes with a powerful `search-docs` tool you should use before any other approaches. This tool automatically passes a list of installed packages and their versions to the remote Boost API, so it returns only version-specific documentation specific for the user's circumstance. You should pass an array of packages to filter on if you know you need docs for particular packages. +- The 'search-docs' tool is perfect for all Laravel related packages, including Laravel, Inertia, Livewire, Filament, Tailwind, Pest, Nova, Nightwatch, etc. +- You must use this tool to search for Laravel-ecosystem documentation before falling back to other approaches. +- Search the documentation before making code changes to ensure we are taking the correct approach. +- Use multiple, broad, simple, topic based queries to start. For example: `['rate limiting', 'routing rate limiting', 'routing']`. +- Do not add package names to queries - package information is already shared. For example, use `test resource table`, not `filament 4 test resource table`. + +### Available Search Syntax +- You can and should pass multiple queries at once. The most relevant results will be returned first. + +1. Simple Word Searches with auto-stemming - query=authentication - finds 'authenticate' and 'auth' +2. Multiple Words (AND Logic) - query=rate limit - finds knowledge containing both "rate" AND "limit" +3. Quoted Phrases (Exact Position) - query="infinite scroll" - Words must be adjacent and in that order +4. Mixed Queries - query=middleware "rate limit" - "middleware" AND exact phrase "rate limit" +5. Multiple Queries - queries=["authentication", "middleware"] - ANY of these terms + + +=== php rules === + +## PHP + +- Always use curly braces for control structures, even if it has one line. + +### Constructors +- Use PHP 8 constructor property promotion in `__construct()`. + - public function __construct(public GitHub $github) { } +- Do not allow empty `__construct()` methods with zero parameters. + +### Type Declarations +- Always use explicit return type declarations for methods and functions. +- Use appropriate PHP type hints for method parameters. + + +protected function isAccessible(User $user, ?string $path = null): bool +{ + ... +} + + +## Comments +- Prefer PHPDoc blocks over comments. Never use comments within the code itself unless there is something _very_ complex going on. + +## PHPDoc Blocks +- Add useful array shape type definitions for arrays when appropriate. + +## Enums +- Typically, keys in an Enum should be TitleCase. For example: `FavoritePerson`, `BestLake`, `Monthly`. + + +=== sail rules === + +## Laravel Sail + +- This project runs inside Laravel Sail's Docker containers. You MUST execute all commands through Sail. +- Start services using `vendor/bin/sail up -d` and stop them with `vendor/bin/sail stop`. +- Open the application in the browser by running `vendor/bin/sail open`. +- Always prefix PHP, Artisan, Composer, and Node commands** with `vendor/bin/sail`. Examples: +- Run Artisan Commands: `vendor/bin/sail artisan migrate` +- Install Composer packages: `vendor/bin/sail composer install` +- Execute node commands: `vendor/bin/sail npm run dev` +- Execute PHP scripts: `vendor/bin/sail php [script]` +- View all available Sail commands by running `vendor/bin/sail` without arguments. + + +=== tests rules === + +## Test Enforcement + +- Every change must be programmatically tested. Write a new test or update an existing test, then run the affected tests to make sure they pass. +- Run the minimum number of tests needed to ensure code quality and speed. Use `vendor/bin/sail artisan test` with a specific filename or filter. + + +=== laravel/core rules === + +## Do Things the Laravel Way + +- Use `vendor/bin/sail artisan make:` commands to create new files (i.e. migrations, controllers, models, etc.). You can list available Artisan commands using the `list-artisan-commands` tool. +- If you're creating a generic PHP class, use `vendor/bin/sail artisan make:class`. +- Pass `--no-interaction` to all Artisan commands to ensure they work without user input. You should also pass the correct `--options` to ensure correct behavior. + +### Database +- Always use proper Eloquent relationship methods with return type hints. Prefer relationship methods over raw queries or manual joins. +- Use Eloquent models and relationships before suggesting raw database queries +- Avoid `DB::`; prefer `Model::query()`. Generate code that leverages Laravel's ORM capabilities rather than bypassing them. +- Generate code that prevents N+1 query problems by using eager loading. +- Use Laravel's query builder for very complex database operations. + +### Model Creation +- When creating new models, create useful factories and seeders for them too. Ask the user if they need any other things, using `list-artisan-commands` to check the available options to `vendor/bin/sail artisan make:model`. + +### APIs & Eloquent Resources +- For APIs, default to using Eloquent API Resources and API versioning unless existing API routes do not, then you should follow existing application convention. + +### Controllers & Validation +- Always create Form Request classes for validation rather than inline validation in controllers. Include both validation rules and custom error messages. +- Check sibling Form Requests to see if the application uses array or string based validation rules. + +### Queues +- Use queued jobs for time-consuming operations with the `ShouldQueue` interface. + +### Authentication & Authorization +- Use Laravel's built-in authentication and authorization features (gates, policies, Sanctum, etc.). + +### URL Generation +- When generating links to other pages, prefer named routes and the `route()` function. + +### Configuration +- Use environment variables only in configuration files - never use the `env()` function directly outside of config files. Always use `config('app.name')`, not `env('APP_NAME')`. + +### Testing +- When creating models for tests, use the factories for the models. Check if the factory has custom states that can be used before manually setting up the model. +- Faker: Use methods such as `$this->faker->word()` or `fake()->randomDigit()`. Follow existing conventions whether to use `$this->faker` or `fake()`. +- When creating tests, make use of `vendor/bin/sail artisan make:test [options] {name}` to create a feature test, and pass `--unit` to create a unit test. Most tests should be feature tests. + +### Vite Error +- If you receive an "Illuminate\Foundation\ViteException: Unable to locate file in Vite manifest" error, you can run `vendor/bin/sail npm run build` or ask the user to run `vendor/bin/sail npm run dev` or `vendor/bin/sail composer run dev`. + + +=== laravel/v12 rules === + +## Laravel 12 + +- Use the `search-docs` tool to get version specific documentation. +- Since Laravel 11, Laravel has a new streamlined file structure which this project uses. + +### Laravel 12 Structure +- No middleware files in `app/Http/Middleware/`. +- `bootstrap/app.php` is the file to register middleware, exceptions, and routing files. +- `bootstrap/providers.php` contains application specific service providers. +- **No app\Console\Kernel.php** - use `bootstrap/app.php` or `routes/console.php` for console configuration. +- **Commands auto-register** - files in `app/Console/Commands/` are automatically available and do not require manual registration. + +### Database +- When modifying a column, the migration must include all of the attributes that were previously defined on the column. Otherwise, they will be dropped and lost. +- Laravel 11 allows limiting eagerly loaded records natively, without external packages: `$query->latest()->limit(10);`. + +### Models +- Casts can and likely should be set in a `casts()` method on a model rather than the `$casts` property. Follow existing conventions from other models. + + +=== fluxui-pro/core rules === + +## Flux UI Pro + +- This project is using the Pro version of Flux UI. It has full access to the free components and variants, as well as full access to the Pro components and variants. +- Flux UI is a component library for Livewire. Flux is a robust, hand-crafted, UI component library for your Livewire applications. It's built using Tailwind CSS and provides a set of components that are easy to use and customize. +- You should use Flux UI components when available. +- Fallback to standard Blade components if Flux is unavailable. +- If available, use Laravel Boost's `search-docs` tool to get the exact documentation and code snippets available for this project. +- Flux UI components look like this: + + + + + + +### Available Components +This is correct as of Boost installation, but there may be additional components within the codebase. + + +accordion, autocomplete, avatar, badge, brand, breadcrumbs, button, calendar, callout, card, chart, checkbox, command, composer, context, date-picker, dropdown, editor, field, file-upload, heading, icon, input, kanban, modal, navbar, otp-input, pagination, pillbox, popover, profile, radio, select, separator, skeleton, slider, switch, table, tabs, text, textarea, time-picker, toast, tooltip + + + +=== livewire/core rules === + +## Livewire Core +- Use the `search-docs` tool to find exact version specific documentation for how to write Livewire & Livewire tests. +- Use the `vendor/bin/sail artisan make:livewire [Posts\CreatePost]` artisan command to create new components +- State should live on the server, with the UI reflecting it. +- All Livewire requests hit the Laravel backend, they're like regular HTTP requests. Always validate form data, and run authorization checks in Livewire actions. + +## Livewire Best Practices +- Livewire components require a single root element. +- Use `wire:loading` and `wire:dirty` for delightful loading states. +- Add `wire:key` in loops: + + ```blade + @foreach ($items as $item) +
+ {{ $item->name }} +
+ @endforeach + ``` + +- Prefer lifecycle hooks like `mount()`, `updatedFoo()` for initialization and reactive side effects: + + + public function mount(User $user) { $this->user = $user; } + public function updatedSearch() { $this->resetPage(); } + + + +## Testing Livewire + + + Livewire::test(Counter::class) + ->assertSet('count', 0) + ->call('increment') + ->assertSet('count', 1) + ->assertSee(1) + ->assertStatus(200); + + + + + $this->get('/posts/create') + ->assertSeeLivewire(CreatePost::class); + + + +=== livewire/v3 rules === + +## Livewire 3 + +### Key Changes From Livewire 2 +- These things changed in Livewire 2, but may not have been updated in this application. Verify this application's setup to ensure you conform with application conventions. + - Use `wire:model.live` for real-time updates, `wire:model` is now deferred by default. + - Components now use the `App\Livewire` namespace (not `App\Http\Livewire`). + - Use `$this->dispatch()` to dispatch events (not `emit` or `dispatchBrowserEvent`). + - Use the `components.layouts.app` view as the typical layout path (not `layouts.app`). + +### New Directives +- `wire:show`, `wire:transition`, `wire:cloak`, `wire:offline`, `wire:target` are available for use. Use the documentation to find usage examples. + +### Alpine +- Alpine is now included with Livewire, don't manually include Alpine.js. +- Plugins included with Alpine: persist, intersect, collapse, and focus. + +### Lifecycle Hooks +- You can listen for `livewire:init` to hook into Livewire initialization, and `fail.status === 419` for the page expiring: + + +document.addEventListener('livewire:init', function () { + Livewire.hook('request', ({ fail }) => { + if (fail && fail.status === 419) { + alert('Your session expired'); + } + }); + + Livewire.hook('message.failed', (message, component) => { + console.error(message); + }); +}); + + + +=== volt/core rules === + +## Livewire Volt + +- This project uses Livewire Volt for interactivity within its pages. New pages requiring interactivity must also use Livewire Volt. There is documentation available for it. +- Make new Volt components using `vendor/bin/sail artisan make:volt [name] [--test] [--pest]` +- Volt is a **class-based** and **functional** API for Livewire that supports single-file components, allowing a component's PHP logic and Blade templates to co-exist in the same file +- Livewire Volt allows PHP logic and Blade templates in one file. Components use the `@volt` directive. +- You must check existing Volt components to determine if they're functional or class based. If you can't detect that, ask the user which they prefer before writing a Volt component. + +### Volt Functional Component Example + + +@volt + 0]); + +$increment = fn () => $this->count++; +$decrement = fn () => $this->count--; + +$double = computed(fn () => $this->count * 2); +?> + +
+

Count: {{ $count }}

+

Double: {{ $this->double }}

+ + +
+@endvolt +
+ + +### Volt Class Based Component Example +To get started, define an anonymous class that extends Livewire\Volt\Component. Within the class, you may utilize all of the features of Livewire using traditional Livewire syntax: + + + +use Livewire\Volt\Component; + +new class extends Component { + public $count = 0; + + public function increment() + { + $this->count++; + } +} ?> + +
+

{{ $count }}

+ +
+
+ + +### Testing Volt & Volt Components +- Use the existing directory for tests if it already exists. Otherwise, fallback to `tests/Feature/Volt`. + + +use Livewire\Volt\Volt; + +test('counter increments', function () { + Volt::test('counter') + ->assertSee('Count: 0') + ->call('increment') + ->assertSee('Count: 1'); +}); + + + + +declare(strict_types=1); + +use App\Models\{User, Product}; +use Livewire\Volt\Volt; + +test('product form creates product', function () { + $user = User::factory()->create(); + + Volt::test('pages.products.create') + ->actingAs($user) + ->set('form.name', 'Test Product') + ->set('form.description', 'Test Description') + ->set('form.price', 99.99) + ->call('create') + ->assertHasNoErrors(); + + expect(Product::where('name', 'Test Product')->exists())->toBeTrue(); +}); + + + +### Common Patterns + + + + null, 'search' => '']); + +$products = computed(fn() => Product::when($this->search, + fn($q) => $q->where('name', 'like', "%{$this->search}%") +)->get()); + +$edit = fn(Product $product) => $this->editing = $product->id; +$delete = fn(Product $product) => $product->delete(); + +?> + + + + + + + + + + + Save + Saving... + + + + +=== pint/core rules === + +## Laravel Pint Code Formatter + +- You must run `vendor/bin/sail bin pint --dirty` before finalizing changes to ensure your code matches the project's expected style. +- Do not run `vendor/bin/sail bin pint --test`, simply run `vendor/bin/sail bin pint` to fix any formatting issues. + + +=== pest/core rules === + +## Pest +### Testing +- If you need to verify a feature is working, write or update a Unit / Feature test. + +### Pest Tests +- All tests must be written using Pest. Use `vendor/bin/sail artisan make:test --pest {name}`. +- You must not remove any tests or test files from the tests directory without approval. These are not temporary or helper files - these are core to the application. +- Tests should test all of the happy paths, failure paths, and weird paths. +- Tests live in the `tests/Feature` and `tests/Unit` directories. +- Pest tests look and behave like this: + +it('is true', function () { + expect(true)->toBeTrue(); +}); + + +### Running Tests +- Run the minimal number of tests using an appropriate filter before finalizing code edits. +- To run all tests: `vendor/bin/sail artisan test`. +- To run all tests in a file: `vendor/bin/sail artisan test tests/Feature/ExampleTest.php`. +- To filter on a particular test name: `vendor/bin/sail artisan test --filter=testName` (recommended after making a change to a related file). +- When the tests relating to your changes are passing, ask the user if they would like to run the entire test suite to ensure everything is still passing. + +### Pest Assertions +- When asserting status codes on a response, use the specific method like `assertForbidden` and `assertNotFound` instead of using `assertStatus(403)` or similar, e.g.: + +it('returns all', function () { + $response = $this->postJson('/api/docs', []); + + $response->assertSuccessful(); +}); + + +### Mocking +- Mocking can be very helpful when appropriate. +- When mocking, you can use the `Pest\Laravel\mock` Pest function, but always import it via `use function Pest\Laravel\mock;` before using it. Alternatively, you can use `$this->mock()` if existing tests do. +- You can also create partial mocks using the same import or self method. + +### Datasets +- Use datasets in Pest to simplify tests which have a lot of duplicated data. This is often the case when testing validation rules, so consider going with this solution when writing tests for validation rules. + + +it('has emails', function (string $email) { + expect($email)->not->toBeEmpty(); +})->with([ + 'james' => 'james@laravel.com', + 'taylor' => 'taylor@laravel.com', +]); + + + +=== tailwindcss/core rules === + +## Tailwind Core + +- Use Tailwind CSS classes to style HTML, check and use existing tailwind conventions within the project before writing your own. +- Offer to extract repeated patterns into components that match the project's conventions (i.e. Blade, JSX, Vue, etc..) +- Think through class placement, order, priority, and defaults - remove redundant classes, add classes to parent or child carefully to limit repetition, group elements logically +- You can use the `search-docs` tool to get exact examples from the official documentation when needed. + +### Spacing +- When listing items, use gap utilities for spacing, don't use margins. + + +
+
Superior
+
Michigan
+
Erie
+
+
+ + +### Dark Mode +- If existing pages and components support dark mode, new pages and components must support dark mode in a similar way, typically using `dark:`. + + +=== tailwindcss/v4 rules === + +## Tailwind 4 + +- Always use Tailwind CSS v4 - do not use the deprecated utilities. +- `corePlugins` is not supported in Tailwind v4. +- In Tailwind v4, configuration is CSS-first using the `@theme` directive — no separate `tailwind.config.js` file is needed. + +@theme { + --color-brand: oklch(0.72 0.11 178); +} + + +- In Tailwind v4, you import Tailwind using a regular CSS `@import` statement, not using the `@tailwind` directives used in v3: + + + - @tailwind base; + - @tailwind components; + - @tailwind utilities; + + @import "tailwindcss"; + + + +### Replaced Utilities +- Tailwind v4 removed deprecated utilities. Do not use the deprecated option - use the replacement. +- Opacity values are still numeric. + +| Deprecated | Replacement | +|------------+--------------| +| bg-opacity-* | bg-black/* | +| text-opacity-* | text-black/* | +| border-opacity-* | border-black/* | +| divide-opacity-* | divide-black/* | +| ring-opacity-* | ring-black/* | +| placeholder-opacity-* | placeholder-black/* | +| flex-shrink-* | shrink-* | +| flex-grow-* | grow-* | +| overflow-ellipsis | text-ellipsis | +| decoration-slice | box-decoration-slice | +| decoration-clone | box-decoration-clone | + + +=== filament/filament rules === + +## Filament +- Filament is used by this application, check how and where to follow existing application conventions. +- Filament is a Server-Driven UI (SDUI) framework for Laravel. It allows developers to define user interfaces in PHP using structured configuration objects. It is built on top of Livewire, Alpine.js, and Tailwind CSS. +- You can use the `search-docs` tool to get information from the official Filament documentation when needed. This is very useful for Artisan command arguments, specific code examples, testing functionality, relationship management, and ensuring you're following idiomatic practices. +- Utilize static `make()` methods for consistent component initialization. + +### Artisan +- You must use the Filament specific Artisan commands to create new files or components for Filament. You can find these with the `list-artisan-commands` tool, or with `php artisan` and the `--help` option. +- Inspect the required options, always pass `--no-interaction`, and valid arguments for other options when applicable. + +### Filament's Core Features +- Actions: Handle doing something within the application, often with a button or link. Actions encapsulate the UI, the interactive modal window, and the logic that should be executed when the modal window is submitted. They can be used anywhere in the UI and are commonly used to perform one-time actions like deleting a record, sending an email, or updating data in the database based on modal form input. +- Forms: Dynamic forms rendered within other features, such as resources, action modals, table filters, and more. +- Infolists: Read-only lists of data. +- Notifications: Flash notifications displayed to users within the application. +- Panels: The top-level container in Filament that can include all other features like pages, resources, forms, tables, notifications, actions, infolists, and widgets. +- Resources: Static classes that are used to build CRUD interfaces for Eloquent models. Typically live in `app/Filament/Resources`. +- Schemas: Represent components that define the structure and behavior of the UI, such as forms, tables, or lists. +- Tables: Interactive tables with filtering, sorting, pagination, and more. +- Widgets: Small component included within dashboards, often used for displaying data in charts, tables, or as a stat. + +### Relationships +- Determine if you can use the `relationship()` method on form components when you need `options` for a select, checkbox, repeater, or when building a `Fieldset`: + + +Forms\Components\Select::make('user_id') + ->label('Author') + ->relationship('author') + ->required(), + + + +## Testing +- It's important to test Filament functionality for user satisfaction. +- Ensure that you are authenticated to access the application within the test. +- Filament uses Livewire, so start assertions with `livewire()` or `Livewire::test()`. + +### Example Tests + + + livewire(ListUsers::class) + ->assertCanSeeTableRecords($users) + ->searchTable($users->first()->name) + ->assertCanSeeTableRecords($users->take(1)) + ->assertCanNotSeeTableRecords($users->skip(1)) + ->searchTable($users->last()->email) + ->assertCanSeeTableRecords($users->take(-1)) + ->assertCanNotSeeTableRecords($users->take($users->count() - 1)); + + + + livewire(CreateUser::class) + ->fillForm([ + 'name' => 'Howdy', + 'email' => 'howdy@example.com', + ]) + ->call('create') + ->assertNotified() + ->assertRedirect(); + + assertDatabaseHas(User::class, [ + 'name' => 'Howdy', + 'email' => 'howdy@example.com', + ]); + + + + use Filament\Facades\Filament; + + Filament::setCurrentPanel('app'); + + + + livewire(EditInvoice::class, [ + 'invoice' => $invoice, + ])->callAction('send'); + + expect($invoice->refresh())->isSent()->toBeTrue(); + + + +### Important Version 4 Changes +- File visibility is now `private` by default. +- The `deferFilters` method from Filament v3 is now the default behavior in Filament v4, so users must click a button before the filters are applied to the table. To disable this behavior, you can use the `deferFilters(false)` method. +- The `Grid`, `Section`, and `Fieldset` layout components no longer span all columns by default. +- The `all` pagination page method is not available for tables by default. +- All action classes extend `Filament\Actions\Action`. No action classes exist in `Filament\Tables\Actions`. +- The `Form` & `Infolist` layout components have been moved to `Filament\Schemas\Components`, for example `Grid`, `Section`, `Fieldset`, `Tabs`, `Wizard`, etc. +- A new `Repeater` component for Forms has been added. +- Icons now use the `Filament\Support\Icons\Heroicon` Enum by default. Other options are available and documented. + +### Organize Component Classes Structure +- Schema components: `Schemas/Components/` +- Table columns: `Tables/Columns/` +- Table filters: `Tables/Filters/` +- Actions: `Actions/` +
diff --git a/app-modules/database-migration/composer.json b/app-modules/database-migration/composer.json index ff3ef00a..7aa7a581 100644 --- a/app-modules/database-migration/composer.json +++ b/app-modules/database-migration/composer.json @@ -14,11 +14,7 @@ }, "autoload": { "psr-4": { - "Laravelcm\\DatabaseMigration\\": "src/" - } - }, - "autoload-dev": { - "psr-4": { + "Laravelcm\\DatabaseMigration\\": "src/", "Laravelcm\\DatabaseMigration\\Tests\\": "tests/" } }, diff --git a/app-modules/database-migration/src/Commands/MigrateDatabaseCommand.php b/app-modules/database-migration/src/Commands/MigrateDatabaseCommand.php index 3a2d19cd..f0da1118 100644 --- a/app-modules/database-migration/src/Commands/MigrateDatabaseCommand.php +++ b/app-modules/database-migration/src/Commands/MigrateDatabaseCommand.php @@ -4,6 +4,7 @@ namespace Laravelcm\DatabaseMigration\Commands; +use Exception; use Illuminate\Console\Command; use Laravelcm\DatabaseMigration\Services\DatabaseMigrationService; use Laravelcm\DatabaseMigration\Services\SshTunnelService; @@ -70,15 +71,15 @@ public function handle( } $this->newLine(); - $this->info("🔄 Migrating table: {$table}"); + $this->info('🔄 Migrating table: '.$table); if (! $isDryRun) { - $migrationService->migrateTable($table, $chunkSize, function ($processed, $total): void { - $this->line(" 📊 Processed {$processed}/{$total} records"); + $migrationService->migrateTable($table, $chunkSize, function (string $processed, $total): void { + $this->line(sprintf(' 📊 Processed %s/%s records', $processed, $total)); }); } else { $count = $migrationService->getTableRecordCount($table); - $this->line(" 📊 Would migrate {$count} records"); + $this->line(sprintf(' 📊 Would migrate %d records', $count)); } $progressBar->advance(); @@ -100,8 +101,8 @@ public function handle( return Command::SUCCESS; - } catch (\Exception $e) { - $this->error("❌ Migration failed: {$e->getMessage()}"); + } catch (Exception $exception) { + $this->error('❌ Migration failed: '.$exception->getMessage()); return Command::FAILURE; } finally { diff --git a/app-modules/database-migration/src/Commands/MigrateFilesToS3Command.php b/app-modules/database-migration/src/Commands/MigrateFilesToS3Command.php index e74dc3b4..a10e71a5 100644 --- a/app-modules/database-migration/src/Commands/MigrateFilesToS3Command.php +++ b/app-modules/database-migration/src/Commands/MigrateFilesToS3Command.php @@ -4,9 +4,11 @@ namespace Laravelcm\DatabaseMigration\Commands; +use Exception; use Illuminate\Console\Command; use Illuminate\Support\Facades\File; use Illuminate\Support\Facades\Storage; +use SplFileInfo; use Symfony\Component\Finder\Finder; final class MigrateFilesToS3Command extends Command @@ -48,7 +50,7 @@ public function handle(): int $sourceDirs = $this->getSourceDirectories(); foreach ($sourceDirs as $sourceDir) { - $this->info("📁 Processing directory: {$sourceDir['path']}"); + $this->info('📁 Processing directory: '.$sourceDir['path']); $this->migrateDirectory($sourceDir, $targetDisk, $chunkSize, $isDryRun); } @@ -63,8 +65,8 @@ public function handle(): int return Command::SUCCESS; - } catch (\Exception $e) { - $this->error("❌ Migration failed: {$e->getMessage()}"); + } catch (Exception $exception) { + $this->error('❌ Migration failed: '.$exception->getMessage()); return Command::FAILURE; } @@ -73,28 +75,28 @@ public function handle(): int private function verifyS3Configuration(string $disk): bool { try { - $config = config("filesystems.disks.{$disk}"); + $config = config('filesystems.disks.'.$disk); if (! $config) { - $this->error("❌ Disk '{$disk}' not found in configuration"); + $this->error(sprintf("❌ Disk '%s' not found in configuration", $disk)); return false; } if ($config['driver'] !== 's3') { - $this->error("❌ Disk '{$disk}' is not an S3 disk"); + $this->error(sprintf("❌ Disk '%s' is not an S3 disk", $disk)); return false; } // Test S3 connection Storage::disk($disk)->files(''); - $this->info("✅ S3 disk '{$disk}' is properly configured"); + $this->info(sprintf("✅ S3 disk '%s' is properly configured", $disk)); return true; - } catch (\Exception $e) { - $this->error("❌ S3 configuration error: {$e->getMessage()}"); + } catch (Exception $exception) { + $this->error('❌ S3 configuration error: '.$exception->getMessage()); return false; } @@ -119,7 +121,7 @@ private function getSourceDirectories(): array private function migrateDirectory(array $sourceDir, string $targetDisk, int $chunkSize, bool $isDryRun): void { if (! File::exists($sourceDir['path'])) { - $this->warn("⚠️ Directory does not exist: {$sourceDir['path']}"); + $this->warn('⚠️ Directory does not exist: '.$sourceDir['path']); return; } @@ -132,12 +134,12 @@ private function migrateDirectory(array $sourceDir, string $targetDisk, int $chu $this->totalFiles += $totalFiles; if ($totalFiles === 0) { - $this->info("📂 No files found in {$sourceDir['name']}"); + $this->info('📂 No files found in '.$sourceDir['name']); return; } - $this->info("📊 Found {$totalFiles} files in {$sourceDir['name']}"); + $this->info(sprintf('📊 Found %d files in %s', $totalFiles, $sourceDir['name'])); $progressBar = $this->output->createProgressBar($totalFiles); $progressBar->setFormat(' %current%/%max% [%bar%] %percent:3s%% %elapsed:6s%/%estimated:-6s% %memory:6s%'); @@ -157,13 +159,13 @@ private function migrateDirectory(array $sourceDir, string $targetDisk, int $chu $this->newLine(); } - private function migrateFile(\SplFileInfo $file, array $sourceDir, string $targetDisk, bool $isDryRun): void + private function migrateFile(SplFileInfo $file, array $sourceDir, string $targetDisk, bool $isDryRun): void { try { $relativePath = str_replace($sourceDir['path'].'/', '', $file->getPathname()); // Get the root directory from S3 disk configuration - $diskConfig = config("filesystems.disks.{$targetDisk}"); + $diskConfig = config('filesystems.disks.'.$targetDisk); $rootDir = $diskConfig['root'] ?? 'public'; // Build S3 path: root/relativePath (without additional prefix) @@ -185,9 +187,9 @@ private function migrateFile(\SplFileInfo $file, array $sourceDir, string $targe $this->failedFiles++; } - } catch (\Exception $e) { + } catch (Exception $exception) { $this->failedFiles++; - $this->line(" ❌ Error migrating {$file->getFilename()}: {$e->getMessage()}"); + $this->line(sprintf(' ❌ Error migrating %s: %s', $file->getFilename(), $exception->getMessage())); } } @@ -206,7 +208,7 @@ private function displaySummary(): void ); if ($this->failedFiles > 0) { - $this->warn("⚠️ {$this->failedFiles} files failed to migrate. Check the logs above for details."); + $this->warn(sprintf('⚠️ %d files failed to migrate. Check the logs above for details.', $this->failedFiles)); } } } diff --git a/app-modules/database-migration/src/Commands/ResetPostgresSequencesCommand.php b/app-modules/database-migration/src/Commands/ResetPostgresSequencesCommand.php index 89543d35..b84728d3 100644 --- a/app-modules/database-migration/src/Commands/ResetPostgresSequencesCommand.php +++ b/app-modules/database-migration/src/Commands/ResetPostgresSequencesCommand.php @@ -4,6 +4,7 @@ namespace Laravelcm\DatabaseMigration\Commands; +use Exception; use Illuminate\Console\Command; use Illuminate\Support\Collection; use Illuminate\Support\Facades\DB; @@ -49,14 +50,14 @@ public function handle(): int $nextVal = $maxId + 1; if ($isDryRun) { - $this->line("Would reset {$sequenceName} to {$nextVal} (max {$columnName}: {$maxId})"); + $this->line(sprintf('Would reset %s to %s (max %s: %s)', $sequenceName, $nextVal, $columnName, $maxId)); } else { DB::statement('SELECT setval(?, GREATEST(?, 1))', [$sequenceName, $nextVal]); } $resetCount++; - } catch (\Exception $e) { - $this->line("⚠️ Skipped {$sequenceInfo->table_name}: {$e->getMessage()}"); + } catch (Exception $e) { + $this->line(sprintf('⚠️ Skipped %s: %s', $sequenceInfo->table_name, $e->getMessage())); $skipCount++; } @@ -67,9 +68,9 @@ public function handle(): int $this->newLine(2); if ($isDryRun) { - $this->info("✅ Dry run completed - {$resetCount} sequences would be reset, {$skipCount} skipped"); + $this->info(sprintf('✅ Dry run completed - %d sequences would be reset, %d skipped', $resetCount, $skipCount)); } else { - $this->info("✅ Sequences reset completed - {$resetCount} reset, {$skipCount} skipped"); + $this->info(sprintf('✅ Sequences reset completed - %d reset, %d skipped', $resetCount, $skipCount)); } return Command::SUCCESS; @@ -96,8 +97,8 @@ private function getTablesWithSequences(): Collection "); return collect($result); - } catch (\Exception $e) { - $this->warn("Error querying sequences: {$e->getMessage()}"); + } catch (Exception $exception) { + $this->warn('Error querying sequences: '.$exception->getMessage()); return collect(); } diff --git a/app-modules/database-migration/src/Commands/SshTunnelCommand.php b/app-modules/database-migration/src/Commands/SshTunnelCommand.php index 418f0762..c2153b7c 100644 --- a/app-modules/database-migration/src/Commands/SshTunnelCommand.php +++ b/app-modules/database-migration/src/Commands/SshTunnelCommand.php @@ -29,8 +29,8 @@ public function handle(SshTunnelService $tunnelService): int return $this->activateTunnel($tunnelService); - } catch (SshTunnelException $e) { - $this->error($e->getMessage()); + } catch (SshTunnelException $sshTunnelException) { + $this->error($sshTunnelException->getMessage()); return Command::FAILURE; } diff --git a/app-modules/database-migration/src/Commands/UpdateStorageUrlsCommand.php b/app-modules/database-migration/src/Commands/UpdateStorageUrlsCommand.php index 76f04a60..9e6c3b4c 100644 --- a/app-modules/database-migration/src/Commands/UpdateStorageUrlsCommand.php +++ b/app-modules/database-migration/src/Commands/UpdateStorageUrlsCommand.php @@ -4,6 +4,7 @@ namespace Laravelcm\DatabaseMigration\Commands; +use Exception; use Illuminate\Console\Command; use Illuminate\Support\Collection; use Illuminate\Support\Facades\DB; @@ -43,13 +44,13 @@ public function handle(): int $newBaseUrl = $this->getNewBaseUrl($newDisk); if (! $newBaseUrl) { - $this->error("❌ Could not determine base URL for disk: {$newDisk}"); + $this->error('❌ Could not determine base URL for disk: '.$newDisk); return Command::FAILURE; } - $this->info("Old domain: {$oldDomain}"); - $this->info("New base URL: {$newBaseUrl}"); + $this->info('Old domain: '.$oldDomain); + $this->info('New base URL: '.$newBaseUrl); $this->newLine(); $totalUpdated = 0; @@ -65,7 +66,7 @@ public function handle(): int continue; } - $this->info("📋 Processing {$table}.{$column} - {$recordCount} records found"); + $this->info(sprintf('📋 Processing %s.%s - %d records found', $table, $column, $recordCount)); $progressBar = $this->output->createProgressBar($recordCount); $progressBar->start(); @@ -80,6 +81,7 @@ public function handle(): int ->where('id', $record->id) ->update([$column => $newContent]); } + $updated++; } @@ -91,9 +93,9 @@ public function handle(): int if ($updated > 0) { if ($isDryRun) { - $this->line(" Would update {$updated} records in {$table}.{$column}"); + $this->line(sprintf(' Would update %d records in %s.%s', $updated, $table, $column)); } else { - $this->line(" ✅ Updated {$updated} records in {$table}.{$column}"); + $this->line(sprintf(' ✅ Updated %d records in %s.%s', $updated, $table, $column)); } } @@ -103,9 +105,9 @@ public function handle(): int $this->newLine(); if ($isDryRun) { - $this->info("✅ Dry run completed - {$totalUpdated} records would be updated out of {$totalRecords} total records with old URLs"); + $this->info(sprintf('✅ Dry run completed - %d records would be updated out of %d total records with old URLs', $totalUpdated, $totalRecords)); } else { - $this->info("✅ URL migration completed - {$totalUpdated} records updated out of {$totalRecords} total records with old URLs"); + $this->info(sprintf('✅ URL migration completed - %d records updated out of %d total records with old URLs', $totalUpdated, $totalRecords)); } return Command::SUCCESS; @@ -114,7 +116,7 @@ public function handle(): int private function getNewBaseUrl(string $disk): ?string { try { - $diskConfig = config("filesystems.disks.{$disk}"); + $diskConfig = config('filesystems.disks.'.$disk); if (! $diskConfig) { return null; @@ -124,17 +126,17 @@ private function getNewBaseUrl(string $disk): ?string $baseUrl = ''; if (isset($diskConfig['url']) && $diskConfig['url']) { - $baseUrl = rtrim($diskConfig['url'], '/'); + $baseUrl = mb_rtrim($diskConfig['url'], '/'); } else { $bucket = $diskConfig['bucket'] ?? ''; $region = $diskConfig['region'] ?? ''; if ($bucket && $region) { - $baseUrl = "https://{$bucket}.s3.{$region}.amazonaws.com"; + $baseUrl = sprintf('https://%s.s3.%s.amazonaws.com', $bucket, $region); } } if ($baseUrl && isset($diskConfig['root']) && $diskConfig['root']) { - $root = trim($diskConfig['root'], '/'); + $root = mb_trim($diskConfig['root'], '/'); if (filled($root)) { $baseUrl .= '/'.$root; @@ -145,8 +147,8 @@ private function getNewBaseUrl(string $disk): ?string } return Storage::disk($disk)->url(''); - } catch (\Exception $e) { - $this->error("Error getting base URL for disk {$disk}: ".$e->getMessage()); + } catch (Exception $exception) { + $this->error(sprintf('Error getting base URL for disk %s: ', $disk).$exception->getMessage()); return null; } @@ -157,7 +159,7 @@ private function getRecordsWithOldUrls(string $table, string $column, string $ol return collect( DB::table($table) ->select('id', $column) - ->where($column, 'LIKE', "%{$oldDomain}%") + ->where($column, 'LIKE', sprintf('%%%s%%', $oldDomain)) ->get() ); } diff --git a/app-modules/database-migration/src/Jobs/CreateSshTunnel.php b/app-modules/database-migration/src/Jobs/CreateSshTunnel.php index 87253b77..40909035 100644 --- a/app-modules/database-migration/src/Jobs/CreateSshTunnel.php +++ b/app-modules/database-migration/src/Jobs/CreateSshTunnel.php @@ -13,7 +13,10 @@ final class CreateSshTunnel implements ShouldQueue { - use Dispatchable, InteractsWithQueue, Queueable, SerializesModels; + use Dispatchable; + use InteractsWithQueue; + use Queueable; + use SerializesModels; public function handle(SshTunnelService $tunnelService): int { diff --git a/app-modules/database-migration/src/Providers/DatabaseMigrationServiceProvider.php b/app-modules/database-migration/src/Providers/DatabaseMigrationServiceProvider.php index 23872845..145b6b41 100644 --- a/app-modules/database-migration/src/Providers/DatabaseMigrationServiceProvider.php +++ b/app-modules/database-migration/src/Providers/DatabaseMigrationServiceProvider.php @@ -4,6 +4,7 @@ namespace Laravelcm\DatabaseMigration\Providers; +use Exception; use Illuminate\Support\ServiceProvider; use Laravelcm\DatabaseMigration\Commands\MigrateDatabaseCommand; use Laravelcm\DatabaseMigration\Commands\MigrateFilesToS3Command; @@ -47,9 +48,9 @@ public function boot(): void $this->app->booted(function (): void { try { $this->app->make(SshTunnelService::class)->activate(); - } catch (\Exception $e) { + } catch (Exception $exception) { // Log error but don't break the application - logger()->error('Failed to auto-activate SSH tunnel: '.$e->getMessage()); + logger()->error('Failed to auto-activate SSH tunnel: '.$exception->getMessage()); } }); } diff --git a/app-modules/database-migration/src/Services/DatabaseMigrationService.php b/app-modules/database-migration/src/Services/DatabaseMigrationService.php index 6aa96ab4..62bb14ae 100644 --- a/app-modules/database-migration/src/Services/DatabaseMigrationService.php +++ b/app-modules/database-migration/src/Services/DatabaseMigrationService.php @@ -4,6 +4,7 @@ namespace Laravelcm\DatabaseMigration\Services; +use Exception; use Illuminate\Support\Collection; use Illuminate\Support\Facades\DB; use Illuminate\Support\Facades\Schema; @@ -33,24 +34,6 @@ public function getSourceTables(): array ->toArray(); } - /** - * Get tables that should be excluded from migration - * - * @return array - */ - private function getExcludedTables(): array - { - return [ - 'migrations', - 'password_resets', - 'personal_access_tokens', - 'failed_jobs', - 'jobs', - 'job_batches', - 'temporary_uploads', - ]; - } - /** * Get the number of records in a table */ @@ -79,7 +62,7 @@ public function migrateTable(string $table, int $chunkSize = 1000, ?callable $pr } else { $columns = Schema::connection($this->sourceConnection)->getColumnListing($table); - if (! empty($columns)) { + if (filled($columns)) { $query->orderBy($columns[0]); } } @@ -90,7 +73,7 @@ public function migrateTable(string $table, int $chunkSize = 1000, ?callable $pr $totalRecords, $progressCallback ): void { - $data = $records->map(fn ($record): array => $this->transformRecord((array) $record))->toArray(); + $data = $records->map(fn ($record): array => $this->transformRecord((array) $record))->all(); DB::connection($this->targetConnection) ->table($table) @@ -98,7 +81,7 @@ public function migrateTable(string $table, int $chunkSize = 1000, ?callable $pr $processedRecords += count($records); - if ($progressCallback) { + if ($progressCallback !== null) { $progressCallback($processedRecords, $totalRecords); } }); @@ -116,35 +99,6 @@ public function enableForeignKeyConstraints(): void ->statement('SET session_replication_role = DEFAULT;'); } - private function hasIdColumn(string $table): bool - { - return Schema::connection($this->sourceConnection) - ->hasColumn($table, 'id'); - } - - /** - * Transform a record for PostgreSQL compatibility - * - * @param array $record - * @return array - */ - private function transformRecord(array $record): array - { - foreach ($record as $key => $value) { - // Handle MySQL boolean fields (tinyint) to PostgreSQL boolean - if (is_int($value) && in_array($value, [0, 1]) && preg_match('/^(is_|has_|can_|should_|enabled|active|certified|public|featured|published|pinned|opt_in|sponsored|verified|locked)/', $key)) { - $record[$key] = (bool) $value; - } - - // Handle MySQL timestamp '0000-00-00 00:00:00' to null - if ($value === '0000-00-00 00:00:00') { - $record[$key] = null; - } - } - - return $record; - } - /** * Verify migration by comparing record counts * @@ -180,19 +134,66 @@ public function testConnections(): array try { DB::connection($this->sourceConnection)->getPdo(); $results['source'] = true; - } catch (\Exception $e) { + } catch (Exception $exception) { $results['source'] = false; - $results['source_error'] = $e->getMessage(); + $results['source_error'] = $exception->getMessage(); } try { DB::connection($this->targetConnection)->getPdo(); $results['target'] = true; - } catch (\Exception $e) { + } catch (Exception $exception) { $results['target'] = false; - $results['target_error'] = $e->getMessage(); + $results['target_error'] = $exception->getMessage(); } return $results; } + + /** + * Get tables that should be excluded from migration + * + * @return array + */ + private function getExcludedTables(): array + { + return [ + 'migrations', + 'password_resets', + 'personal_access_tokens', + 'failed_jobs', + 'jobs', + 'job_batches', + 'temporary_uploads', + ]; + } + + private function hasIdColumn(string $table): bool + { + return Schema::connection($this->sourceConnection) + ->hasColumn($table, 'id'); + } + + /** + * Transform a record for PostgreSQL compatibility + * + * @param array $record + * @return array + */ + private function transformRecord(array $record): array + { + foreach ($record as $key => $value) { + // Handle MySQL boolean fields (tinyint) to PostgreSQL boolean + if (is_int($value) && in_array($value, [0, 1]) && preg_match('/^(is_|has_|can_|should_|enabled|active|certified|public|featured|published|pinned|opt_in|sponsored|verified|locked)/', $key)) { + $record[$key] = (bool) $value; + } + + // Handle MySQL timestamp '0000-00-00 00:00:00' to null + if ($value === '0000-00-00 00:00:00') { + $record[$key] = null; + } + } + + return $record; + } } diff --git a/app-modules/database-migration/src/Services/SshTunnelService.php b/app-modules/database-migration/src/Services/SshTunnelService.php index abfedba8..f9fd65b6 100644 --- a/app-modules/database-migration/src/Services/SshTunnelService.php +++ b/app-modules/database-migration/src/Services/SshTunnelService.php @@ -25,6 +25,13 @@ public function __construct() $this->buildCommands(); } + public function __destruct() + { + // Ne pas nettoyer automatiquement à la destruction pour permettre + // la réutilisation du fichier pendant la durée de vie de l'application + // Le nettoyage doit être fait explicitement via forceCleanupTempKeyFile() + } + public function activate(): int { if ($this->isActive()) { @@ -43,7 +50,7 @@ public function activate(): int return 2; } - usleep(config('ssh-tunnel.connection.wait_microseconds', 1000000)); + \Illuminate\Support\Sleep::usleep(config('ssh-tunnel.connection.wait_microseconds', 1000000)); } throw new SshTunnelException( @@ -80,6 +87,11 @@ public function destroy(): bool return $result; } + public function forceCleanupTempKeyFile(): void + { + $this->cleanupTempKeyFile(); + } + private function createTunnel(): void { $nohupPath = config('ssh-tunnel.executables.nohup'); @@ -94,7 +106,7 @@ private function createTunnel(): void $this->runCommand($command); - usleep(config('ssh-tunnel.connection.wait_microseconds', 1000000)); + \Illuminate\Support\Sleep::usleep(config('ssh-tunnel.connection.wait_microseconds', 1000000)); $this->log('SSH tunnel creation command executed', ['command' => $command]); } @@ -111,7 +123,7 @@ private function buildCommands(): void ); $this->bashCommand = sprintf( - 'timeout 1 %s -c \'cat < /dev/null > /dev/tcp/%s/%d\' > /dev/null 2>&1', + "timeout 1 %s -c 'cat < /dev/null > /dev/tcp/%s/%d' > /dev/null 2>&1", $config['executables']['bash'], $config['local']['address'], $config['local']['port'] @@ -144,7 +156,7 @@ private function runCommand(string $command): bool private function buildIdentityOption(array $config): string { - if (! empty($config['ssh']['private_key_content'])) { + if (filled($config['ssh']['private_key_content'])) { $tempKeyFile = $this->createTempKeyFile($config['ssh']['private_key_content']); return sprintf('-i %s', $tempKeyFile); @@ -158,7 +170,7 @@ private function createTempKeyFile(string $keyContent): string $cleanedKeyContent = $this->normalizeKeyContent($keyContent); $keyHash = hash('sha256', $cleanedKeyContent); - $tempFile = sys_get_temp_dir().'/ssh_key_'.substr($keyHash, 0, 16); + $tempFile = sys_get_temp_dir().'/ssh_key_'.mb_substr($keyHash, 0, 16); if (file_exists($tempFile)) { $existingContent = file_get_contents($tempFile); @@ -170,9 +182,7 @@ private function createTempKeyFile(string $keyContent): string } } - if (file_put_contents($tempFile, $cleanedKeyContent) === false) { - throw new SshTunnelException('Unable to write SSH key to temporary file'); - } + throw_if(file_put_contents($tempFile, $cleanedKeyContent) === false, SshTunnelException::class, 'Unable to write SSH key to temporary file'); if (chmod($tempFile, 0600) === false) { unlink($tempFile); @@ -197,7 +207,7 @@ private function normalizeKeyContent(string $keyContent): string } $normalized = str_replace('\\n', "\n", $keyContent); - $normalized = trim($normalized); + $normalized = mb_trim($normalized); if (! str_ends_with($normalized, "\n")) { $normalized .= "\n"; @@ -232,22 +242,11 @@ private function cleanupTempKeyFile(): void } else { $this->log('Failed to cleanup temporary SSH key file', ['file' => $this->tempKeyFile]); } + $this->tempKeyFile = null; } } - public function forceCleanupTempKeyFile(): void - { - $this->cleanupTempKeyFile(); - } - - public function __destruct() - { - // Ne pas nettoyer automatiquement à la destruction pour permettre - // la réutilisation du fichier pendant la durée de vie de l'application - // Le nettoyage doit être fait explicitement via forceCleanupTempKeyFile() - } - private function log(string $message, array $context = []): void { if (! config('ssh-tunnel.logging.enabled', true)) { diff --git a/app-modules/gamify/composer.json b/app-modules/gamify/composer.json index 7899c1a7..d55b6715 100644 --- a/app-modules/gamify/composer.json +++ b/app-modules/gamify/composer.json @@ -5,7 +5,8 @@ "version": "1.1.0", "license": "proprietary", "require": { - "php": "^8.4" + "php": "^8.4", + "Illuminate/Support": "^11.0|^12.0" }, "require-dev": { "pestphp/pest": "^3.8", @@ -15,17 +16,13 @@ "psr-4": { "Laravelcm\\Gamify\\": "src/", "Laravelcm\\Gamify\\Database\\Factories\\": "database/factories/", - "Laravelcm\\Gamify\\Database\\Seeders\\": "database/seeders/" + "Laravelcm\\Gamify\\Database\\Seeders\\": "database/seeders/", + "Laravelcm\\Gamify\\Tests\\": "tests/" }, "files": [ "src/helpers.php" ] }, - "autoload-dev": { - "psr-4": { - "Laravelcm\\Gamify\\Tests\\": "tests/" - } - }, "minimum-stability": "stable", "extra": { "laravel": { diff --git a/app-modules/gamify/database/factories/.gitkeep b/app-modules/gamify/database/factories/.gitkeep deleted file mode 100644 index e69de29b..00000000 diff --git a/app-modules/gamify/database/factories/ReputationFactory.php b/app-modules/gamify/database/factories/ReputationFactory.php new file mode 100644 index 00000000..01378618 --- /dev/null +++ b/app-modules/gamify/database/factories/ReputationFactory.php @@ -0,0 +1,23 @@ + + */ +final class ReputationFactory extends Factory +{ + protected $model = Reputation::class; + + public function definition(): array + { + return [ + // + ]; + } +} diff --git a/app-modules/gamify/database/migrations/2022_01_15_201921_add_reputation_field_on_user_table.php b/app-modules/gamify/database/migrations/2022_01_15_201921_add_reputation_field_on_user_table.php index e6988be0..4b10682c 100644 --- a/app-modules/gamify/database/migrations/2022_01_15_201921_add_reputation_field_on_user_table.php +++ b/app-modules/gamify/database/migrations/2022_01_15_201921_add_reputation_field_on_user_table.php @@ -6,11 +6,11 @@ use Illuminate\Database\Schema\Blueprint; use Illuminate\Support\Facades\Schema; -final class AddReputationFieldOnUserTable extends Migration +return new class extends Migration { public function up(): void { - Schema::table('users', function (Blueprint $table): void { + Schema::table('users', static function (Blueprint $table): void { $table->unsignedInteger('reputation') ->nullable() ->default(0) @@ -20,8 +20,8 @@ public function up(): void public function down(): void { - Schema::table('users', function (Blueprint $table): void { + Schema::table('users', static function (Blueprint $table): void { $table->dropColumn('reputation'); }); } -} +}; diff --git a/app-modules/gamify/database/migrations/2022_01_15_201921_create_gamify_tables.php b/app-modules/gamify/database/migrations/2022_01_15_201921_create_gamify_tables.php index f13df07e..14c73279 100644 --- a/app-modules/gamify/database/migrations/2022_01_15_201921_create_gamify_tables.php +++ b/app-modules/gamify/database/migrations/2022_01_15_201921_create_gamify_tables.php @@ -6,18 +6,20 @@ use Illuminate\Database\Schema\Blueprint; use Illuminate\Support\Facades\Schema; -final class CreateGamifyTables extends Migration +return new class extends Migration { public function up(): void { - Schema::create('reputations', function (Blueprint $table): void { + Schema::create('reputations', static function (Blueprint $table): void { $table->id(); + $table->string('name'); - $table->mediumInteger('point', false)->default(0); + $table->mediumInteger('point')->default(0); $table->bigInteger('subject_id')->nullable(); $table->string('subject_type')->nullable(); $table->unsignedBigInteger('payee_id')->nullable(); $table->text('meta')->nullable(); + $table->timestamps(); }); } @@ -26,4 +28,4 @@ public function down(): void { Schema::dropIfExists('reputations'); } -} +}; diff --git a/app-modules/gamify/src/Exceptions/InvalidPayeeModelException.php b/app-modules/gamify/src/Exceptions/InvalidPayeeModelException.php index 3216f87f..46a58a98 100644 --- a/app-modules/gamify/src/Exceptions/InvalidPayeeModelException.php +++ b/app-modules/gamify/src/Exceptions/InvalidPayeeModelException.php @@ -4,7 +4,9 @@ namespace Laravelcm\Gamify\Exceptions; -final class InvalidPayeeModelException extends \Exception +use Exception; + +final class InvalidPayeeModelException extends Exception { public function __construct() { diff --git a/app-modules/gamify/src/Models/Reputation.php b/app-modules/gamify/src/Models/Reputation.php index b5b348c5..b43a65b9 100644 --- a/app-modules/gamify/src/Models/Reputation.php +++ b/app-modules/gamify/src/Models/Reputation.php @@ -5,19 +5,25 @@ namespace Laravelcm\Gamify\Models; use App\Models\User; +use Carbon\CarbonInterface; +use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Relations\BelongsTo; use Illuminate\Database\Eloquent\Relations\MorphTo; +use Laravelcm\Gamify\Database\Factories\ReputationFactory; /** * @property-read int $id * @property-read int $point - * @property-read User|null $payee - * @property-read \Illuminate\Support\Carbon $created_at - * @property-read \Illuminate\Support\Carbon $updated_at + * @property-read CarbonInterface $created_at + * @property-read CarbonInterface $updated_at + * @property-read ?User $payee */ final class Reputation extends Model { + /** @use HasFactory */ + use HasFactory; + protected $guarded = []; /** diff --git a/app-modules/gamify/src/PointType.php b/app-modules/gamify/src/PointType.php index 71bc7138..2177040d 100644 --- a/app-modules/gamify/src/PointType.php +++ b/app-modules/gamify/src/PointType.php @@ -12,14 +12,21 @@ use Laravelcm\Gamify\Exceptions\PointsNotDefinedException; use Laravelcm\Gamify\Exceptions\PointSubjectNotSetException; use Laravelcm\Gamify\Models\Reputation; +use Throwable; /** - * @property string $name + * @property ?string $name * @property int $points * @property Authenticatable|string|null $payer */ abstract class PointType { + public $payee; + + public $name; + + public $points; + /** * @var Model|null */ @@ -36,7 +43,7 @@ public function qualifier(): bool /** * Payee who will be receiving points * - * @throws PointSubjectNotSetException + * @throws PointSubjectNotSetException|Throwable */ public function payee(): ?User { @@ -50,13 +57,11 @@ public function payee(): ?User /** * Subject model for point * - * @throws PointSubjectNotSetException + * @throws PointSubjectNotSetException|Throwable */ public function getSubject(): Model { - if (blank($this->subject)) { - throw new PointSubjectNotSetException; - } + throw_if(blank($this->subject), PointSubjectNotSetException::class); return $this->subject; } @@ -66,22 +71,19 @@ public function getSubject(): Model */ public function getName(): string { - return property_exists($this, 'name') + return filled($this->name) ? $this->name - : class_basename($this); + : class_basename(static::class); } /** * Get points * - * @throws PointsNotDefinedException + * @throws PointsNotDefinedException|Throwable */ public function getPoints(): int { - // @phpstan-ignore-next-line - if (blank($this->points)) { - throw new PointsNotDefinedException; - } + throw_if(blank($this->points), PointsNotDefinedException::class); return $this->points; } @@ -99,7 +101,7 @@ public function setSubject(Model $subject): void * * * @throws InvalidPayeeModelException - * @throws PointSubjectNotSetException + * @throws PointSubjectNotSetException|Throwable */ public function reputationExists(): bool { @@ -110,7 +112,7 @@ public function reputationExists(): bool * Get first reputation for point * * @throws InvalidPayeeModelException - * @throws PointSubjectNotSetException + * @throws PointSubjectNotSetException|Throwable */ public function firstReputation(): Reputation { @@ -122,7 +124,7 @@ public function firstReputation(): Reputation * * @throws InvalidPayeeModelException * @throws PointSubjectNotSetException - * @throws PointsNotDefinedException + * @throws PointsNotDefinedException|Throwable */ public function storeReputation(array $meta): Reputation { @@ -142,7 +144,7 @@ public function storeReputation(array $meta): Reputation * * * @throws InvalidPayeeModelException - * @throws PointSubjectNotSetException + * @throws PointSubjectNotSetException|Throwable */ public function reputationQuery(): HasMany { @@ -157,17 +159,14 @@ public function reputationQuery(): HasMany /** * Return reputations payee relation * - * * @throws InvalidPayeeModelException - * @throws PointSubjectNotSetException + * @throws PointSubjectNotSetException|Throwable */ protected function payeeReputations(): HasMany { $model = $this->payee(); - if (! $model instanceof Authenticatable) { - throw new InvalidPayeeModelException; - } + throw_unless($model instanceof Authenticatable, InvalidPayeeModelException::class); return $model->reputations(); } diff --git a/app-modules/gamify/src/helpers.php b/app-modules/gamify/src/helpers.php index 66274c74..ea5cf4eb 100644 --- a/app-modules/gamify/src/helpers.php +++ b/app-modules/gamify/src/helpers.php @@ -27,8 +27,8 @@ function givePoint(PointType $pointType, ?User $payee = null): void /** * Undo a given point * - * @throws \Laravelcm\Gamify\Exceptions\InvalidPayeeModelException - * @throws \Laravelcm\Gamify\Exceptions\PointSubjectNotSetException + * @throws Laravelcm\Gamify\Exceptions\InvalidPayeeModelException + * @throws Laravelcm\Gamify\Exceptions\PointSubjectNotSetException */ function undoPoint(PointType $pointType, ?User $payee = null): void { diff --git a/app-modules/gamify/tests/Feature/PointTest.php b/app-modules/gamify/tests/Feature/PointTest.php index 3b85ef0a..ca361448 100644 --- a/app-modules/gamify/tests/Feature/PointTest.php +++ b/app-modules/gamify/tests/Feature/PointTest.php @@ -6,13 +6,17 @@ use Laravelcm\Gamify\Exceptions\InvalidPayeeModelException; use Laravelcm\Gamify\Exceptions\PointsNotDefinedException; use Laravelcm\Gamify\Exceptions\PointSubjectNotSetException; +use Laravelcm\Gamify\PointType; use Laravelcm\Gamify\Tests\Fixtures; +/** + * @var Tests\TestCase $this + */ beforeEach(function (): void { $this->user = $this->createUser(); }); -describe('PointType', function (): void { +describe(PointType::class, function (): void { it('sets point type name from class name', function (): void { $point = new Fixtures\FakeCreatePostPoint($this->user); diff --git a/app-modules/gamify/tests/Fixtures/FakeCreatePostPoint.php b/app-modules/gamify/tests/Fixtures/FakeCreatePostPoint.php index 1738ff64..1f26a8b5 100644 --- a/app-modules/gamify/tests/Fixtures/FakeCreatePostPoint.php +++ b/app-modules/gamify/tests/Fixtures/FakeCreatePostPoint.php @@ -9,7 +9,7 @@ final class FakeCreatePostPoint extends PointType { - public int $points = 10; + public $points = 10; public ?User $author; diff --git a/app-modules/gamify/tests/Fixtures/FakePayeeFieldPoint.php b/app-modules/gamify/tests/Fixtures/FakePayeeFieldPoint.php index 8c6f5486..17892e72 100644 --- a/app-modules/gamify/tests/Fixtures/FakePayeeFieldPoint.php +++ b/app-modules/gamify/tests/Fixtures/FakePayeeFieldPoint.php @@ -8,10 +8,10 @@ final class FakePayeeFieldPoint extends PointType { - protected int $points = 10; + public $points = 10; - /** @var string payee model relation with subject */ - protected string $payee = 'user'; + /** @var string */ + public $payee = 'user'; public function __construct(mixed $subject) { diff --git a/app-modules/gamify/tests/Fixtures/FakePointTypeWithoutPayee.php b/app-modules/gamify/tests/Fixtures/FakePointTypeWithoutPayee.php index 6dd36aa0..18b2ee0b 100644 --- a/app-modules/gamify/tests/Fixtures/FakePointTypeWithoutPayee.php +++ b/app-modules/gamify/tests/Fixtures/FakePointTypeWithoutPayee.php @@ -4,14 +4,20 @@ namespace Laravelcm\Gamify\Tests\Fixtures; +use App\Models\User; use Laravelcm\Gamify\PointType; final class FakePointTypeWithoutPayee extends PointType { - protected int $point = 24; + public $points = 24; public function __construct(mixed $subject = null) { - $this->subject = $subject; + $this->subject = $subject ?? User::factory()->create(); + } + + public function payee(): null + { + return null; } } diff --git a/app-modules/gamify/tests/Fixtures/FakePointTypeWithoutSubject.php b/app-modules/gamify/tests/Fixtures/FakePointTypeWithoutSubject.php index 4f4e2fcc..c365a23a 100644 --- a/app-modules/gamify/tests/Fixtures/FakePointTypeWithoutSubject.php +++ b/app-modules/gamify/tests/Fixtures/FakePointTypeWithoutSubject.php @@ -9,7 +9,7 @@ final class FakePointTypeWithoutSubject extends PointType { - protected int $point = 12; + public $point = 12; public function __construct($subject = null) { diff --git a/app-modules/gamify/tests/Fixtures/FakePointWithoutPoint.php b/app-modules/gamify/tests/Fixtures/FakePointWithoutPoint.php index fd1507c4..a1a172b1 100644 --- a/app-modules/gamify/tests/Fixtures/FakePointWithoutPoint.php +++ b/app-modules/gamify/tests/Fixtures/FakePointWithoutPoint.php @@ -8,7 +8,7 @@ final class FakePointWithoutPoint extends PointType { - protected string $payee = 'user'; + public $payee = 'user'; public function __construct($subject = null) { diff --git a/app-modules/gamify/tests/Fixtures/FakeWelcomeUserWithFalseQualifier.php b/app-modules/gamify/tests/Fixtures/FakeWelcomeUserWithFalseQualifier.php index eda96f37..979de668 100644 --- a/app-modules/gamify/tests/Fixtures/FakeWelcomeUserWithFalseQualifier.php +++ b/app-modules/gamify/tests/Fixtures/FakeWelcomeUserWithFalseQualifier.php @@ -9,7 +9,7 @@ final class FakeWelcomeUserWithFalseQualifier extends PointType { - protected int $points = 10; + public $points = 10; public function __construct($subject) { diff --git a/app-modules/gamify/tests/Fixtures/FakeWelcomeUserWithNamePoint.php b/app-modules/gamify/tests/Fixtures/FakeWelcomeUserWithNamePoint.php index 79dd0f41..cba898b4 100644 --- a/app-modules/gamify/tests/Fixtures/FakeWelcomeUserWithNamePoint.php +++ b/app-modules/gamify/tests/Fixtures/FakeWelcomeUserWithNamePoint.php @@ -9,9 +9,9 @@ final class FakeWelcomeUserWithNamePoint extends PointType { - public int $points = 30; + public $points = 30; - public string $name = 'FakeName'; + public $name = 'FakeName'; public function __construct(mixed $subject, public ?User $author = null) {} diff --git a/app/Actions/Article/DeclineArticleAction.php b/app/Actions/Article/DeclineArticleAction.php index bba9a898..d94f175f 100644 --- a/app/Actions/Article/DeclineArticleAction.php +++ b/app/Actions/Article/DeclineArticleAction.php @@ -6,7 +6,6 @@ use App\Models\Article; use App\Notifications\ArticleDeclinedNotification; -use Carbon\Carbon; use Illuminate\Support\Facades\DB; final class DeclineArticleAction @@ -15,7 +14,7 @@ public function execute(string $reason, Article $article): Article { return DB::transaction(function () use ($reason, $article): Article { $article->update([ - 'declined_at' => Carbon::now(), + 'declined_at' => \Illuminate\Support\Facades\Date::now(), 'reason' => $reason, 'submitted_at' => null, ]); diff --git a/app/Actions/Discussion/ConvertDiscussionToThreadAction.php b/app/Actions/Discussion/ConvertDiscussionToThreadAction.php index 386aeb72..2a4723b8 100644 --- a/app/Actions/Discussion/ConvertDiscussionToThreadAction.php +++ b/app/Actions/Discussion/ConvertDiscussionToThreadAction.php @@ -28,7 +28,7 @@ public function execute(Discussion $discussion): Thread $discussion->delete(); - app(NotifyUsersOfThreadConversionAction::class)->execute($thread); + resolve(NotifyUsersOfThreadConversionAction::class)->execute($thread); return $thread; }); diff --git a/app/Actions/Discussion/CreateDiscussionReplyAction.php b/app/Actions/Discussion/CreateDiscussionReplyAction.php index ed35f06b..3dead5bb 100644 --- a/app/Actions/Discussion/CreateDiscussionReplyAction.php +++ b/app/Actions/Discussion/CreateDiscussionReplyAction.php @@ -16,7 +16,7 @@ public function __invoke(string $body, User $user, Model $model): Reply { $reply = new Reply(['body' => $body]); $reply->authoredBy($user); - $reply->to($model); // @phpstan-ignore-line + $reply->to($model); $reply->save(); $user->givePoint(new ReplyCreated($model, $user)); diff --git a/app/Actions/Forum/CreateThreadAction.php b/app/Actions/Forum/CreateThreadAction.php index f198d15a..609b7ee2 100644 --- a/app/Actions/Forum/CreateThreadAction.php +++ b/app/Actions/Forum/CreateThreadAction.php @@ -16,7 +16,7 @@ public function execute(array $formValues): Thread return DB::transaction(function () use ($formValues) { $thread = Thread::query()->create($formValues); - app(SubscribeToThreadAction::class)->execute($thread); + resolve(SubscribeToThreadAction::class)->execute($thread); givePoint(new ThreadCreated($thread)); diff --git a/app/Actions/User/BanUserAction.php b/app/Actions/User/BanUserAction.php index 5f9b4110..1304601b 100644 --- a/app/Actions/User/BanUserAction.php +++ b/app/Actions/User/BanUserAction.php @@ -13,13 +13,9 @@ final class BanUserAction { public function execute(User $user, string $reason): void { - if ($user->isAdmin() || $user->isModerator()) { - throw new CannotBanAdminException('Impossible de bannir un administrateur.'); - } + throw_if($user->isAdmin() || $user->isModerator(), CannotBanAdminException::class, 'Impossible de bannir un administrateur.'); - if ($user->banned()) { - throw new UserAlreadyBannedException('Impossible de bannir cet utilisateur car il est déjà banni.'); - } + throw_if($user->banned(), UserAlreadyBannedException::class, 'Impossible de bannir cet utilisateur car il est déjà banni.'); $user->update([ 'banned_at' => now(), diff --git a/app/Console/Commands/Benchmark/BenchmarkCommand.php b/app/Console/Commands/Benchmark/BenchmarkCommand.php index 72c3dada..3abb9bc6 100644 --- a/app/Console/Commands/Benchmark/BenchmarkCommand.php +++ b/app/Console/Commands/Benchmark/BenchmarkCommand.php @@ -32,10 +32,10 @@ private function runBenchmarkTests(): void $testCases = $this->getTestCases(); foreach ($testCases as $description => $value) { - $this->line("Testing: {$description}"); + $this->line(sprintf('Testing: %s', $description)); $blankMetrics = $this->measurePerformance(fn (): bool => blank($value)); - $emptyMetrics = $this->measurePerformance(fn (): bool => empty($value)); + $emptyMetrics = $this->measurePerformance(fn (): bool => blank($value)); $this->displayResults($blankMetrics, $emptyMetrics); $this->newLine(); @@ -95,7 +95,7 @@ private function displayResults(array $blankMetrics, array $emptyMetrics): void : round($blankMetrics['time'] / $emptyMetrics['time'], 2); $winner = $isFasterBlank ? 'blank()' : 'empty()'; - $this->line(" {$winner} is {$ratio}x faster"); + $this->line(sprintf(' %s is %sx faster', $winner, $ratio)); } private function formatTime(float $timeInSeconds): string diff --git a/app/Console/Commands/Cleanup/DeleteOldUnverifiedUsers.php b/app/Console/Commands/Cleanup/DeleteOldUnverifiedUsers.php index 412c0d05..886764f3 100644 --- a/app/Console/Commands/Cleanup/DeleteOldUnverifiedUsers.php +++ b/app/Console/Commands/Cleanup/DeleteOldUnverifiedUsers.php @@ -32,7 +32,7 @@ public function handle(): void $count = $query->delete(); - $this->comment("Deleted {$count} unverified users."); + $this->comment(sprintf('Deleted %d unverified users.', $count)); $this->info('All done!'); } diff --git a/app/Console/Commands/CreateAdminUser.php b/app/Console/Commands/CreateAdminUser.php index 6ad6985b..8b5b93ca 100644 --- a/app/Console/Commands/CreateAdminUser.php +++ b/app/Console/Commands/CreateAdminUser.php @@ -33,7 +33,7 @@ protected function createUser(): void // Passwords don't match if ($password !== $confirmPassword) { - $this->info('Passwords don\'t match'); + $this->info("Passwords don't match"); } $this->info('Creating admin account...'); diff --git a/app/Console/Commands/PublishArticles.php b/app/Console/Commands/PublishArticles.php index fcaf1663..b747ca82 100644 --- a/app/Console/Commands/PublishArticles.php +++ b/app/Console/Commands/PublishArticles.php @@ -28,7 +28,7 @@ public function handle(): void $count = $articles->count(); - $this->comment("Published {$count} articles."); + $this->comment(sprintf('Published %s articles.', $count)); $this->info('All done!'); } diff --git a/app/Console/Commands/Sitemap/GenerateArticlesSitemapCommand.php b/app/Console/Commands/Sitemap/GenerateArticlesSitemapCommand.php index 7a5c62f5..d4d43479 100644 --- a/app/Console/Commands/Sitemap/GenerateArticlesSitemapCommand.php +++ b/app/Console/Commands/Sitemap/GenerateArticlesSitemapCommand.php @@ -6,7 +6,9 @@ use App\Models\Article; use Illuminate\Console\Command; +use Spatie\Sitemap\Contracts\Sitemapable; use Spatie\Sitemap\Sitemap; +use Spatie\Sitemap\Tags\Url; final class GenerateArticlesSitemapCommand extends Command { @@ -18,8 +20,8 @@ public function handle(): void { $sitemap = Sitemap::create(); - Article::query()->whereNotNull('approved_at')->each(function ($article) use ($sitemap): void { - $sitemap->add($article); // @phpstan-ignore-line + Article::query()->whereNotNull('approved_at')->each(function (string|Url|Sitemapable|iterable $article) use ($sitemap): void { + $sitemap->add($article); }); $sitemap->writeToFile(public_path('sitemaps/blog_sitemap.xml')); diff --git a/app/Console/Commands/Sitemap/GenerateDiscussionsSitemapCommand.php b/app/Console/Commands/Sitemap/GenerateDiscussionsSitemapCommand.php index bc1a49b7..4ac11613 100644 --- a/app/Console/Commands/Sitemap/GenerateDiscussionsSitemapCommand.php +++ b/app/Console/Commands/Sitemap/GenerateDiscussionsSitemapCommand.php @@ -6,7 +6,9 @@ use App\Models\Discussion; use Illuminate\Console\Command; +use Spatie\Sitemap\Contracts\Sitemapable; use Spatie\Sitemap\Sitemap; +use Spatie\Sitemap\Tags\Url; final class GenerateDiscussionsSitemapCommand extends Command { @@ -18,8 +20,8 @@ public function handle(): void { $sitemap = Sitemap::create(); - Discussion::query()->whereHas('replies')->each(function ($discussion) use ($sitemap): void { - $sitemap->add($discussion); // @phpstan-ignore-line + Discussion::query()->whereHas('replies')->each(function (string|Url|Sitemapable|iterable $discussion) use ($sitemap): void { + $sitemap->add($discussion); }); $sitemap->writeToFile(public_path('sitemaps/discussion_sitemap.xml')); diff --git a/app/Enums/PlanType.php b/app/Enums/PlanType.php index cda4806e..86fc1caf 100644 --- a/app/Enums/PlanType.php +++ b/app/Enums/PlanType.php @@ -4,9 +4,19 @@ namespace App\Enums; -enum PlanType: string +use Filament\Support\Contracts\HasLabel; + +enum PlanType: string implements HasLabel { case DEVELOPER = 'developer'; case ENTERPRISE = 'enterprise'; + + public function getLabel(): string + { + return match ($this) { + self::DEVELOPER => __('Développeur'), + self::ENTERPRISE => __('Entreprise'), + }; + } } diff --git a/app/Events/UserBannedEvent.php b/app/Events/UserBannedEvent.php index 3625582b..264259a8 100644 --- a/app/Events/UserBannedEvent.php +++ b/app/Events/UserBannedEvent.php @@ -11,7 +11,9 @@ final class UserBannedEvent { - use Dispatchable, InteractsWithSockets, SerializesModels; + use Dispatchable; + use InteractsWithSockets; + use SerializesModels; public function __construct(public User $user) {} } diff --git a/app/Events/UserUnbannedEvent.php b/app/Events/UserUnbannedEvent.php index 0e9844b5..93ebf18a 100644 --- a/app/Events/UserUnbannedEvent.php +++ b/app/Events/UserUnbannedEvent.php @@ -11,7 +11,9 @@ final class UserUnbannedEvent { - use Dispatchable, InteractsWithSockets, SerializesModels; + use Dispatchable; + use InteractsWithSockets; + use SerializesModels; public function __construct(public User $user) {} } diff --git a/app/Exceptions/CannotAddChannelToChild.php b/app/Exceptions/CannotAddChannelToChild.php index 1241f5f5..3434904a 100644 --- a/app/Exceptions/CannotAddChannelToChild.php +++ b/app/Exceptions/CannotAddChannelToChild.php @@ -11,6 +11,6 @@ final class CannotAddChannelToChild extends Exception { public static function childChannelCannotBeParent(Channel $channel): self { - return new self("Le channel [{$channel->name} ne peut pas être un channel parent.]"); + return new self(sprintf('Le channel [%s ne peut pas être un channel parent.]', $channel->name)); } } diff --git a/app/Exceptions/CannotCreateUser.php b/app/Exceptions/CannotCreateUser.php index 9cdd8223..fda13b3e 100644 --- a/app/Exceptions/CannotCreateUser.php +++ b/app/Exceptions/CannotCreateUser.php @@ -10,11 +10,11 @@ final class CannotCreateUser extends Exception { public static function duplicateEmailAddress(string $emailAddress): self { - return new CannotCreateUser("Cet adresse e-mail [{$emailAddress}] existe déjà."); + return new self(sprintf('Cet adresse e-mail [%s] existe déjà.', $emailAddress)); } public static function duplicateUsername(string $username): self { - return new CannotCreateUser("Ce pseudo [{$username}] existe déjà."); + return new self(sprintf('Ce pseudo [%s] existe déjà.', $username)); } } diff --git a/app/Exceptions/CouldNotMarkReplyAsSolution.php b/app/Exceptions/CouldNotMarkReplyAsSolution.php index a127f9e0..04c0c6cc 100644 --- a/app/Exceptions/CouldNotMarkReplyAsSolution.php +++ b/app/Exceptions/CouldNotMarkReplyAsSolution.php @@ -11,6 +11,6 @@ final class CouldNotMarkReplyAsSolution extends Exception { public static function replyAbleIsNotAThread(Reply $reply): self { - return new self("La réponse avec l'identifiant [{$reply->id} n'est pas lié à un thread.]"); + return new self(sprintf("La réponse avec l'identifiant [%d n'est pas lié à un thread.]", $reply->id)); } } diff --git a/app/Exceptions/Handler.php b/app/Exceptions/Handler.php index 658a1dd5..e4480fbf 100644 --- a/app/Exceptions/Handler.php +++ b/app/Exceptions/Handler.php @@ -21,7 +21,7 @@ public function register(): void { $this->reportable(function (Throwable $e): void { if ($this->shouldReport($e) && app()->bound('sentry')) { - app('sentry')->captureException($e); + resolve('sentry')->captureException($e); } }); } diff --git a/app/Filament/Resources/Articles/ArticleResource.php b/app/Filament/Resources/Articles/ArticleResource.php index a2113e3c..3feadf6b 100644 --- a/app/Filament/Resources/Articles/ArticleResource.php +++ b/app/Filament/Resources/Articles/ArticleResource.php @@ -9,6 +9,7 @@ use App\Models\Article; use Awcodes\BadgeableColumn\Components\Badge; use Awcodes\BadgeableColumn\Components\BadgeableColumn; +use BackedEnum; use Filament\Actions; use Filament\Forms\Components\Textarea; use Filament\Notifications\Notification; @@ -26,7 +27,7 @@ final class ArticleResource extends Resource { protected static ?string $model = Article::class; - protected static string|\BackedEnum|null $navigationIcon = 'heroicon-o-newspaper'; + protected static string|BackedEnum|null $navigationIcon = 'heroicon-o-newspaper'; public static function getNavigationGroup(): string { @@ -47,7 +48,7 @@ public static function table(Table $table): Table ->tooltip(function (Columns\TextColumn $column): ?string { $state = $column->getState(); - if (strlen($state) <= $column->getCharacterLimit()) { + if (mb_strlen($state) <= $column->getCharacterLimit()) { return null; } @@ -114,7 +115,7 @@ public static function table(Table $table): Table ->action(function (Article $record): void { Gate::authorize('approve', $record); - app(ApprovedArticleAction::class)->execute($record); + resolve(ApprovedArticleAction::class)->execute($record); }), Actions\Action::make('declined') ->visible(fn (Article $record): bool => $record->isAwaitingApproval()) @@ -135,7 +136,7 @@ public static function table(Table $table): Table ->action(function (array $data, Article $record): void { Gate::authorize('decline', $record); - app(DeclineArticleAction::class)->execute($data['reason'], $record); + resolve(DeclineArticleAction::class)->execute($data['reason'], $record); Notification::make() ->title('Article décliné') diff --git a/app/Filament/Resources/Channels/ChannelResource.php b/app/Filament/Resources/Channels/ChannelResource.php index 9e9fa676..f26bf973 100644 --- a/app/Filament/Resources/Channels/ChannelResource.php +++ b/app/Filament/Resources/Channels/ChannelResource.php @@ -5,6 +5,7 @@ namespace App\Filament\Resources\Channels; use App\Models\Channel; +use BackedEnum; use Filament\Actions; use Filament\Forms\Components; use Filament\Resources\Resource; @@ -22,7 +23,7 @@ final class ChannelResource extends Resource protected static ?string $model = Channel::class; - protected static string|\BackedEnum|null $navigationIcon = 'untitledui-git-branch'; + protected static string|BackedEnum|null $navigationIcon = 'untitledui-git-branch'; public static function getNavigationGroup(): string { diff --git a/app/Filament/Resources/Discussions/DiscussionResource.php b/app/Filament/Resources/Discussions/DiscussionResource.php index e3b72341..bc714252 100644 --- a/app/Filament/Resources/Discussions/DiscussionResource.php +++ b/app/Filament/Resources/Discussions/DiscussionResource.php @@ -5,6 +5,7 @@ namespace App\Filament\Resources\Discussions; use App\Models\Discussion; +use BackedEnum; use Filament\Actions; use Filament\Resources\Resource; use Filament\Tables\Columns; @@ -16,7 +17,7 @@ final class DiscussionResource extends Resource { protected static ?string $model = Discussion::class; - protected static string|\BackedEnum|null $navigationIcon = 'untitledui-message-chat-square'; + protected static string|BackedEnum|null $navigationIcon = 'untitledui-message-chat-square'; public static function getNavigationGroup(): string { diff --git a/app/Filament/Resources/Tags/TagResource.php b/app/Filament/Resources/Tags/TagResource.php index 073e6896..fb59b14a 100644 --- a/app/Filament/Resources/Tags/TagResource.php +++ b/app/Filament/Resources/Tags/TagResource.php @@ -5,6 +5,7 @@ namespace App\Filament\Resources\Tags; use App\Models\Tag; +use BackedEnum; use Filament\Actions; use Filament\Forms\Components; use Filament\Resources\Resource; @@ -18,7 +19,7 @@ final class TagResource extends Resource { protected static ?string $model = Tag::class; - protected static string|\BackedEnum|null $navigationIcon = 'untitledui-tag-03'; + protected static string|BackedEnum|null $navigationIcon = 'untitledui-tag-03'; public static function getNavigationGroup(): string { diff --git a/app/Filament/Resources/Threads/ThreadResource.php b/app/Filament/Resources/Threads/ThreadResource.php index 187898d7..a7a54ffe 100644 --- a/app/Filament/Resources/Threads/ThreadResource.php +++ b/app/Filament/Resources/Threads/ThreadResource.php @@ -5,6 +5,7 @@ namespace App\Filament\Resources\Threads; use App\Models\Thread; +use BackedEnum; use Filament\Actions; use Filament\Resources\Resource; use Filament\Tables\Columns; @@ -15,7 +16,7 @@ final class ThreadResource extends Resource { protected static ?string $model = Thread::class; - protected static string|\BackedEnum|null $navigationIcon = 'heroicon-o-chat-bubble-left-right'; + protected static string|BackedEnum|null $navigationIcon = 'heroicon-o-chat-bubble-left-right'; public static function getNavigationGroup(): string { diff --git a/app/Filament/Resources/Users/UserResource.php b/app/Filament/Resources/Users/UserResource.php index 95e54d01..56a14577 100644 --- a/app/Filament/Resources/Users/UserResource.php +++ b/app/Filament/Resources/Users/UserResource.php @@ -9,6 +9,7 @@ use App\Models\User; use Awcodes\BadgeableColumn\Components\Badge; use Awcodes\BadgeableColumn\Components\BadgeableColumn; +use BackedEnum; use Filament\Actions; use Filament\Forms\Components\TextInput; use Filament\Notifications\Notification; @@ -22,7 +23,7 @@ final class UserResource extends Resource { protected static ?string $model = User::class; - protected static string|\BackedEnum|null $navigationIcon = 'untitledui-users-02'; + protected static string|BackedEnum|null $navigationIcon = 'untitledui-users-02'; public static function getNavigationGroup(): string { @@ -45,7 +46,7 @@ public static function table(Table $table): Table BadgeableColumn::make('name') ->suffixBadges([ Badge::make('username') - ->label(fn (User $record): string => "@{$record->username}") + ->label(fn (User $record): string => '@'.$record->username) ->color('gray'), ]) ->description(fn (User $record): ?string => $record->location) @@ -83,7 +84,7 @@ public static function table(Table $table): Table ->required(), ]) ->action(function (User $record, array $data): void { - app(BanUserAction::class)->execute($record, $data['banned_reason']); + resolve(BanUserAction::class)->execute($record, $data['banned_reason']); Notification::make() ->success() @@ -100,7 +101,7 @@ public static function table(Table $table): Table ->visible(fn (User $record): bool => $record->banned_at !== null) ->authorize('unban', User::class) ->action(function (User $record): void { - app(UnBanUserAction::class)->execute($record); + resolve(UnBanUserAction::class)->execute($record); Notification::make() ->success() diff --git a/app/Gamify/Points/AddPhone.php b/app/Gamify/Points/AddPhone.php index 2b779398..ab411659 100644 --- a/app/Gamify/Points/AddPhone.php +++ b/app/Gamify/Points/AddPhone.php @@ -8,9 +8,11 @@ final class AddPhone extends PointType { - public int $points = 10; + /** @var int */ + public $points = 10; - protected string $payee = 'user'; + /** @var string */ + public $payee = 'user'; public function __construct(mixed $subject) { diff --git a/app/Gamify/Points/AddSocialLinks.php b/app/Gamify/Points/AddSocialLinks.php index 3eb32f12..ed9ee328 100644 --- a/app/Gamify/Points/AddSocialLinks.php +++ b/app/Gamify/Points/AddSocialLinks.php @@ -8,9 +8,11 @@ final class AddSocialLinks extends PointType { - public int $points = 15; + /** @var int */ + public $points = 15; - protected string $payee = 'user'; + /** @var string */ + public $payee = 'user'; public function __construct(mixed $subject) { diff --git a/app/Gamify/Points/ArticlePublished.php b/app/Gamify/Points/ArticlePublished.php index 19f68cb7..bcba7c4a 100644 --- a/app/Gamify/Points/ArticlePublished.php +++ b/app/Gamify/Points/ArticlePublished.php @@ -9,9 +9,11 @@ final class ArticlePublished extends PointType { - public int $points = 50; + /** @var int */ + public $points = 50; - protected string $payee = 'user'; + /** @var string */ + public $payee = 'user'; public function __construct(Article $subject) { diff --git a/app/Gamify/Points/BestReply.php b/app/Gamify/Points/BestReply.php index e5a420ef..e1cde23e 100644 --- a/app/Gamify/Points/BestReply.php +++ b/app/Gamify/Points/BestReply.php @@ -9,9 +9,11 @@ final class BestReply extends PointType { - public int $points = 20; + /** @var int */ + public $points = 20; - protected string $payee = 'user'; + /** @var string */ + public $payee = 'user'; public function __construct(Reply $subject) { diff --git a/app/Gamify/Points/DiscussionCreated.php b/app/Gamify/Points/DiscussionCreated.php index 712f7a07..c0ed2538 100644 --- a/app/Gamify/Points/DiscussionCreated.php +++ b/app/Gamify/Points/DiscussionCreated.php @@ -9,9 +9,11 @@ final class DiscussionCreated extends PointType { - public int $points = 20; + /** @var int */ + public $points = 20; - protected string $payee = 'user'; + /** @var string */ + public $payee = 'user'; public function __construct(Discussion $subject) { diff --git a/app/Gamify/Points/PostCreated.php b/app/Gamify/Points/PostCreated.php index 5109793b..d4c9cf93 100644 --- a/app/Gamify/Points/PostCreated.php +++ b/app/Gamify/Points/PostCreated.php @@ -9,9 +9,11 @@ final class PostCreated extends PointType { - public int $points = 50; + /** @var int */ + public $points = 50; - protected string $payee = 'user'; + /** @var string */ + public $payee = 'user'; public function __construct(Article $subject) { diff --git a/app/Gamify/Points/ReplyCreated.php b/app/Gamify/Points/ReplyCreated.php index 0656e006..d9465273 100644 --- a/app/Gamify/Points/ReplyCreated.php +++ b/app/Gamify/Points/ReplyCreated.php @@ -10,7 +10,8 @@ final class ReplyCreated extends PointType { - public int $points = 10; + /** @var int */ + public $points = 10; public function __construct(Reply $subject, public ?User $author = null) { diff --git a/app/Gamify/Points/ThreadCreated.php b/app/Gamify/Points/ThreadCreated.php index 84d66abd..18cbc6be 100644 --- a/app/Gamify/Points/ThreadCreated.php +++ b/app/Gamify/Points/ThreadCreated.php @@ -9,9 +9,11 @@ final class ThreadCreated extends PointType { - public int $points = 55; + /** @var int */ + public $points = 55; - protected string $payee = 'user'; + /** @var string */ + public $payee = 'user'; public function __construct(Thread $subject) { diff --git a/app/Http/Controllers/Api/Auth/LoginController.php b/app/Http/Controllers/Api/Auth/LoginController.php index c9a6563b..3198a764 100644 --- a/app/Http/Controllers/Api/Auth/LoginController.php +++ b/app/Http/Controllers/Api/Auth/LoginController.php @@ -8,7 +8,6 @@ use App\Http\Requests\Api\LoginRequest; use App\Models\User; use App\Traits\UserResponse; -use Carbon\Carbon; use Illuminate\Http\JsonResponse; use Illuminate\Http\Request; use Illuminate\Support\Facades\Auth; @@ -33,7 +32,7 @@ public function login(LoginRequest $request): JsonResponse if (! $user || ! Auth::attempt($sanitized)) { throw ValidationException::withMessages([ - 'email' => __('Les informations d\'identification fournies sont incorrectes.'), + 'email' => __("Les informations d'identification fournies sont incorrectes."), ]); } @@ -41,7 +40,7 @@ public function login(LoginRequest $request): JsonResponse $user->tokens()->delete(); } - $user->last_login_at = Carbon::now(); + $user->last_login_at = \Illuminate\Support\Facades\Date::now(); $user->last_login_ip = $request->ip(); $user->save(); diff --git a/app/Http/Controllers/Api/Auth/RegisterController.php b/app/Http/Controllers/Api/Auth/RegisterController.php index 56212c64..f49db667 100644 --- a/app/Http/Controllers/Api/Auth/RegisterController.php +++ b/app/Http/Controllers/Api/Auth/RegisterController.php @@ -10,7 +10,6 @@ use App\Models\SocialAccount; use App\Models\User; use App\Traits\UserResponse; -use Carbon\Carbon; use Illuminate\Http\JsonResponse; use Illuminate\Http\Request; use Illuminate\Support\Facades\Hash; diff --git a/app/Http/Controllers/Api/PremiumController.php b/app/Http/Controllers/Api/PremiumController.php index 5c3dde24..b06db1a1 100644 --- a/app/Http/Controllers/Api/PremiumController.php +++ b/app/Http/Controllers/Api/PremiumController.php @@ -30,6 +30,7 @@ public function users(): JsonResponse foreach ($usersRow as $user) { $usersArray[$key][] = new PremiumUserResource($user); } + $premium->push($usersArray[$key]); } diff --git a/app/Http/Controllers/Auth/VerifyEmailController.php b/app/Http/Controllers/Auth/VerifyEmailController.php index b273f1c9..d5fd7c1d 100644 --- a/app/Http/Controllers/Auth/VerifyEmailController.php +++ b/app/Http/Controllers/Auth/VerifyEmailController.php @@ -19,7 +19,7 @@ public function __invoke(EmailVerificationRequest $request): RedirectResponse $user = $request->user(); if ($user === null) { - return redirect()->route('login'); + return to_route('login'); } if ($user->hasVerifiedEmail()) { diff --git a/app/Http/Controllers/NotchPayCallBackController.php b/app/Http/Controllers/NotchPayCallBackController.php index 892d1ac5..c24ac875 100644 --- a/app/Http/Controllers/NotchPayCallBackController.php +++ b/app/Http/Controllers/NotchPayCallBackController.php @@ -50,12 +50,12 @@ public function __invoke(Request $request): RedirectResponse ); } - } catch (Exception $e) { - Log::error($e->getMessage()); + } catch (Exception $exception) { + Log::error($exception->getMessage()); session()->flash( key: 'error', - value: __('Une erreur s\'est produite lors de votre paiement. Veuillez relancer Merci.') + value: __("Une erreur s'est produite lors de votre paiement. Veuillez relancer Merci.") ); } diff --git a/app/Http/Controllers/OAuthController.php b/app/Http/Controllers/OAuthController.php index 0fe363a7..1651699b 100644 --- a/app/Http/Controllers/OAuthController.php +++ b/app/Http/Controllers/OAuthController.php @@ -20,9 +20,8 @@ final class OAuthController extends Controller public function redirectToProvider(string $provider): RedirectResponse|\Symfony\Component\HttpFoundation\RedirectResponse { if (! in_array($provider, $this->getAcceptedProviders(), true)) { - return redirect() - ->route('login') - ->withErrors(__('La connexion via :provider n\'est pas disponible', ['provider' => e($provider)])); + return to_route('login') + ->withErrors(__("La connexion via :provider n'est pas disponible", ['provider' => e($provider)])); } return $this->getAuthorizationFirst($provider); @@ -32,17 +31,17 @@ public function handleProviderCallback(string $provider): RedirectResponse { try { $socialiteUser = $this->getSocialiteUser($provider); - } catch (InvalidStateException $exception) { + } catch (InvalidStateException $invalidStateException) { session()->flash('error', __('La demande a expirée. Veuillez réessayer.')); - return redirect()->route('login'); + return to_route('login'); } try { $user = User::findOrCreateSocialUserProvider($socialiteUser, $provider); - } catch (ModelNotFoundException $exception) { + } catch (ModelNotFoundException $modelNotFoundException) { // @phpstan-ignore-next-line - return $this->userNotFound($socialiteUser->getRaw(), $exception->getMessage()); + return $this->userNotFound($socialiteUser->getRaw(), $modelNotFoundException->getMessage()); } $this->updateOrRegisterProvider($user, $socialiteUser, $provider); @@ -61,7 +60,7 @@ private function userNotFound(User $socialUser, string $errorMessage): RedirectR { session(['socialData' => $socialUser->toArray()]); - return redirect()->route('register')->withErrors($errorMessage); + return to_route('register')->withErrors($errorMessage); } private function updateOrRegisterProvider(User $user, SocialUser $socialiteUser, string $provider): void diff --git a/app/Http/Controllers/SubscriptionController.php b/app/Http/Controllers/SubscriptionController.php index 8d8d067b..ea401ef6 100644 --- a/app/Http/Controllers/SubscriptionController.php +++ b/app/Http/Controllers/SubscriptionController.php @@ -19,7 +19,7 @@ public function unsubscribe(Subscribe $subscription): RedirectResponse session()->flash('status', __('Vous êtes maintenant désabonné de ce sujet.')); - return redirect()->route('forum.show', $thread->slug); + return to_route('forum.show', $thread->slug); } public function redirect(int $id, string $type): RedirectResponse diff --git a/app/Http/Middleware/CheckIfBanned.php b/app/Http/Middleware/CheckIfBanned.php index 86222878..ee758389 100644 --- a/app/Http/Middleware/CheckIfBanned.php +++ b/app/Http/Middleware/CheckIfBanned.php @@ -17,7 +17,7 @@ public function handle(Request $request, Closure $next): Response if (Auth::check() && Auth::user()->banned()) { Auth::logout(); - return redirect()->route('login')->withErrors([ + return to_route('login')->withErrors([ 'email' => __('user.ban.message'), ]); } diff --git a/app/Http/Requests/Api/Enterprise/RegisterRequest.php b/app/Http/Requests/Api/Enterprise/RegisterRequest.php index 7f9d51e4..cc6721f3 100644 --- a/app/Http/Requests/Api/Enterprise/RegisterRequest.php +++ b/app/Http/Requests/Api/Enterprise/RegisterRequest.php @@ -18,9 +18,9 @@ public function rules(): array $regex = '/^(https?:\/\/)?([\da-z\.-]+)\.([a-z\.]{2,6})([\/\w \.-]*)*\/?$/'; return [ - 'name' => 'required', + 'name' => ['required'], 'website' => 'required|unique:enterprises,website|regex:'.$regex, - 'user_id' => 'required', + 'user_id' => ['required'], ]; } } diff --git a/app/Http/Requests/Api/ForgotPasswordRequest.php b/app/Http/Requests/Api/ForgotPasswordRequest.php index 1e595da0..e9073378 100644 --- a/app/Http/Requests/Api/ForgotPasswordRequest.php +++ b/app/Http/Requests/Api/ForgotPasswordRequest.php @@ -16,7 +16,7 @@ public function authorize(): bool public function rules(): array { return [ - 'email' => 'required|email|exists:users,email', + 'email' => ['required', 'email', 'exists:users,email'], ]; } } diff --git a/app/Http/Requests/Api/RegisterRequest.php b/app/Http/Requests/Api/RegisterRequest.php index f9c043af..6343631a 100644 --- a/app/Http/Requests/Api/RegisterRequest.php +++ b/app/Http/Requests/Api/RegisterRequest.php @@ -16,9 +16,9 @@ public function authorize(): bool public function rules(): array { return [ - 'name' => 'required|string|max:255', - 'email' => 'required|email|max:255|unique:users', - 'password' => 'required|min:6', + 'name' => ['required', 'string', 'max:255'], + 'email' => ['required', 'email', 'max:255', 'unique:users'], + 'password' => ['required', 'min:6'], ]; } } diff --git a/app/Http/Requests/Api/ResetPasswordRequest.php b/app/Http/Requests/Api/ResetPasswordRequest.php index d3bd43fa..131c1e47 100644 --- a/app/Http/Requests/Api/ResetPasswordRequest.php +++ b/app/Http/Requests/Api/ResetPasswordRequest.php @@ -17,7 +17,7 @@ public function authorize(): bool public function rules(): array { return [ - 'token' => 'required', + 'token' => ['required'], 'email' => ['required', 'email', 'exists:users,email'], 'password' => [ 'required', diff --git a/app/Http/Requests/Api/UpdateProfileRequest.php b/app/Http/Requests/Api/UpdateProfileRequest.php index 80440fd8..6e6c5bf2 100644 --- a/app/Http/Requests/Api/UpdateProfileRequest.php +++ b/app/Http/Requests/Api/UpdateProfileRequest.php @@ -17,13 +17,13 @@ public function authorize(): bool public function rules(): array { return [ - 'name' => 'required|max:255', + 'name' => ['required', 'max:255'], 'email' => 'required|email|max:255|unique:users,email,'.Auth::id(), 'username' => 'required|alpha_dash|max:30|unique:users,username,'.Auth::id(), 'twitter_profile' => 'max:255|nullable|unique:users,twitter_profile,'.Auth::id(), 'github_profile' => 'max:255|nullable|unique:users,github_profile,'.Auth::id(), - 'bio' => 'nullable|max:160', - 'website' => 'nullable|url', + 'bio' => ['nullable', 'max:160'], + 'website' => ['nullable', 'url'], ]; } } diff --git a/app/Http/Requests/FormRequest.php b/app/Http/Requests/FormRequest.php index 65ff2d4e..13a10529 100644 --- a/app/Http/Requests/FormRequest.php +++ b/app/Http/Requests/FormRequest.php @@ -17,7 +17,7 @@ abstract class FormRequest extends LaravelFormRequest */ abstract public function rules(): array; - public function failedValidation(Validator $validator): JsonResponse + protected function failedValidation(Validator $validator): JsonResponse { $transformed = []; diff --git a/app/Jobs/SendBanEmailJob.php b/app/Jobs/SendBanEmailJob.php index c41414fc..3b6a13f8 100644 --- a/app/Jobs/SendBanEmailJob.php +++ b/app/Jobs/SendBanEmailJob.php @@ -14,7 +14,10 @@ final class SendBanEmailJob implements ShouldQueue { - use Dispatchable, InteractsWithQueue, Queueable, SerializesModels; + use Dispatchable; + use InteractsWithQueue; + use Queueable; + use SerializesModels; public function __construct(public User $user) {} diff --git a/app/Jobs/SendUnbanEmailJob.php b/app/Jobs/SendUnbanEmailJob.php index db8ff912..1424d0de 100644 --- a/app/Jobs/SendUnbanEmailJob.php +++ b/app/Jobs/SendUnbanEmailJob.php @@ -14,7 +14,10 @@ final class SendUnbanEmailJob implements ShouldQueue { - use Dispatchable, InteractsWithQueue, Queueable, SerializesModels; + use Dispatchable; + use InteractsWithQueue; + use Queueable; + use SerializesModels; public function __construct(public User $user) {} diff --git a/app/Listeners/SendNewThreadNotification.php b/app/Listeners/SendNewThreadNotification.php index 680a553b..448cef40 100644 --- a/app/Listeners/SendNewThreadNotification.php +++ b/app/Listeners/SendNewThreadNotification.php @@ -4,11 +4,9 @@ namespace App\Listeners; -use App\Events\ThreadWasCreated; - final class SendNewThreadNotification { - public function handle(ThreadWasCreated $event): void + public function handle(): void { // @Todo: Send notification to Discord } diff --git a/app/Livewire/Components/Discussion/Comments.php b/app/Livewire/Components/Discussion/Comments.php index ebcbe929..e2f68116 100644 --- a/app/Livewire/Components/Discussion/Comments.php +++ b/app/Livewire/Components/Discussion/Comments.php @@ -22,7 +22,7 @@ use Livewire\Component; /** - * @property \Filament\Schemas\Schema $form + * @property Schema $form */ final class Comments extends Component implements HasActions, HasForms { diff --git a/app/Livewire/Components/Forum/ReplyForm.php b/app/Livewire/Components/Forum/ReplyForm.php index 53dc4a28..5d4d53a4 100644 --- a/app/Livewire/Components/Forum/ReplyForm.php +++ b/app/Livewire/Components/Forum/ReplyForm.php @@ -19,7 +19,7 @@ use Livewire\Component; /** - * @property \Filament\Schemas\Schema $form + * @property Schema $form */ final class ReplyForm extends Component implements HasActions, HasForms { @@ -92,7 +92,7 @@ public function createReply(): void { $this->authorize('create', Reply::class); - app(CreateReplyAction::class)->execute( + resolve(CreateReplyAction::class)->execute( body: (string) $this->body, model: $this->thread, ); diff --git a/app/Livewire/Components/Forum/Subscribe.php b/app/Livewire/Components/Forum/Subscribe.php index bcffadaa..5a847f23 100644 --- a/app/Livewire/Components/Forum/Subscribe.php +++ b/app/Livewire/Components/Forum/Subscribe.php @@ -20,7 +20,7 @@ public function subscribe(): void { $this->authorize('subscribe', $this->thread); - app(SubscribeToThreadAction::class)->execute($this->thread); + resolve(SubscribeToThreadAction::class)->execute($this->thread); Notification::make() ->title(__('notifications.thread.subscribe')) diff --git a/app/Livewire/Components/ReportSpam.php b/app/Livewire/Components/ReportSpam.php index 8a2caf54..1fdc9e95 100644 --- a/app/Livewire/Components/ReportSpam.php +++ b/app/Livewire/Components/ReportSpam.php @@ -36,7 +36,7 @@ public function reportAction(): Action ->requiresConfirmation() ->action(function (): void { try { - app(ReportSpamAction::class)->execute( + resolve(ReportSpamAction::class)->execute( user: Auth::user(), // @phpstan-ignore-line model: $this->model, content: $this->reason, @@ -49,9 +49,9 @@ public function reportAction(): Action ->send(); $this->reset('reason'); - } catch (CanReportSpamException $e) { + } catch (CanReportSpamException $canReportSpamException) { Notification::make() - ->title($e->getMessage()) + ->title($canReportSpamException->getMessage()) ->danger() ->duration(3500) ->send(); diff --git a/app/Livewire/Components/Slideovers/ArticleForm.php b/app/Livewire/Components/Slideovers/ArticleForm.php index 382f3676..65d6adf7 100644 --- a/app/Livewire/Components/Slideovers/ArticleForm.php +++ b/app/Livewire/Components/Slideovers/ArticleForm.php @@ -31,7 +31,7 @@ use Laravelcm\LivewireSlideOvers\SlideOverComponent; /** - * @property-read \Filament\Schemas\Schema $form + * @property-read Schema $form */ final class ArticleForm extends SlideOverComponent implements HasActions, HasForms { @@ -43,6 +43,16 @@ final class ArticleForm extends SlideOverComponent implements HasActions, HasFor public ?array $data = []; + public static function panelMaxWidth(): string + { + return '6xl'; + } + + public static function closePanelOnClickAway(): bool + { + return false; + } + public function mount(?int $articleId = null): void { // @phpstan-ignore-next-line @@ -57,16 +67,6 @@ public function mount(?int $articleId = null): void ])); } - public static function panelMaxWidth(): string - { - return '6xl'; - } - - public static function closePanelOnClickAway(): bool - { - return false; - } - public function form(Schema $schema): Schema { return $schema @@ -184,7 +184,7 @@ public function save(): void ]; if ($this->article?->id) { - $article = app(UpdateArticleAction::class)->execute( + $article = resolve(UpdateArticleAction::class)->execute( articleData: ArticleData::from(array_merge($state, $publishedFields)), article: $this->article ); @@ -198,7 +198,7 @@ public function save(): void ->success() ->send(); } else { - $article = app(CreateArticleAction::class)->execute( + $article = resolve(CreateArticleAction::class)->execute( ArticleData::from(array_merge($state, $publishedFields)) ); diff --git a/app/Livewire/Components/Slideovers/DiscussionForm.php b/app/Livewire/Components/Slideovers/DiscussionForm.php index cc4c3f34..0450b765 100644 --- a/app/Livewire/Components/Slideovers/DiscussionForm.php +++ b/app/Livewire/Components/Slideovers/DiscussionForm.php @@ -25,7 +25,7 @@ use Laravelcm\LivewireSlideOvers\SlideOverComponent; /** - * @property \Filament\Schemas\Schema $form + * @property Schema $form */ final class DiscussionForm extends SlideOverComponent implements HasActions, HasForms { @@ -37,6 +37,16 @@ final class DiscussionForm extends SlideOverComponent implements HasActions, Has public ?array $data = []; + public static function panelMaxWidth(): string + { + return '2xl'; + } + + public static function closePanelOnClickAway(): bool + { + return false; + } + public function mount(?int $discussionId = null): void { // @phpstan-ignore-next-line @@ -118,7 +128,7 @@ public function save(): void $this->validate(); - $discussion = app(CreateOrUpdateDiscussionAction::class)->handle( + $discussion = resolve(CreateOrUpdateDiscussionAction::class)->handle( formValues : $this->form->getState(), discussionId: $this->discussion?->id ); @@ -137,16 +147,6 @@ public function save(): void $this->redirect(route('discussions.show', ['discussion' => $discussion]), navigate: true); } - public static function panelMaxWidth(): string - { - return '2xl'; - } - - public static function closePanelOnClickAway(): bool - { - return false; - } - public function render(): View { return view('livewire.components.slideovers.discussion-form'); diff --git a/app/Livewire/Components/Slideovers/ThreadForm.php b/app/Livewire/Components/Slideovers/ThreadForm.php index 686828a6..877b082c 100644 --- a/app/Livewire/Components/Slideovers/ThreadForm.php +++ b/app/Livewire/Components/Slideovers/ThreadForm.php @@ -26,7 +26,7 @@ use Laravelcm\LivewireSlideOvers\SlideOverComponent; /** - * @property \Filament\Schemas\Schema $form + * @property Schema $form */ final class ThreadForm extends SlideOverComponent implements HasActions, HasForms { @@ -38,18 +38,6 @@ final class ThreadForm extends SlideOverComponent implements HasActions, HasForm public ?array $data = []; - public function mount(?int $threadId = null): void - { - $this->thread = filled($threadId) - ? Thread::with('channels')->findOrFail($threadId) - : new Thread; - - $this->form->fill(array_merge($this->thread->toArray(), [ - 'user_id' => $this->thread->user_id ?? Auth::id(), - 'locale' => $this->thread->locale ?? app()->getLocale(), - ])); - } - public static function panelMaxWidth(): string { return '2xl'; @@ -60,6 +48,20 @@ public static function closePanelOnClickAway(): bool return false; } + public function mount(?int $threadId = null): void + { + $this->thread = filled($threadId) + ? Thread::with('channels')->findOrFail($threadId) + : new Thread; + + if (! $this->thread->exists) { + $this->form->fill([ + 'user_id' => Auth::id(), + 'locale' => app()->getLocale(), + ]); + } + } + public function form(Schema $schema): Schema { return $schema @@ -70,8 +72,10 @@ public function form(Schema $schema): Schema ->helperText(__('pages/forum.min_thread_length')) ->required() ->live(onBlur: true) - ->afterStateUpdated(function (string $operation, $state, Set $set): void { - $set('slug', Str::slug($state)); + ->afterStateUpdated(function (string $operation, ?string $state, Set $set): void { + if ($state) { + $set('slug', Str::slug($state)); + } }) ->minLength(10), Components\Hidden::make('slug'), @@ -131,8 +135,8 @@ public function save(): void $validated = $this->form->getState(); $thread = ($this->thread?->id) - ? app(UpdateThreadAction::class)->execute($validated, $this->thread) - : app(CreateThreadAction::class)->execute($validated); + ? resolve(UpdateThreadAction::class)->execute($validated, $this->thread) + : resolve(CreateThreadAction::class)->execute($validated); $this->form->model($thread)->saveRelationships(); diff --git a/app/Livewire/Components/SponsorSubscription.php b/app/Livewire/Components/SponsorSubscription.php index fa81b18e..49025b87 100644 --- a/app/Livewire/Components/SponsorSubscription.php +++ b/app/Livewire/Components/SponsorSubscription.php @@ -29,7 +29,7 @@ use NotchPay\Payment; /** - * @property-read \Filament\Schemas\Schema $form + * @property-read Schema $form */ final class SponsorSubscription extends Component implements HasActions, HasForms { @@ -161,8 +161,8 @@ public function submit(): void ]); $this->redirect($payload->authorization_url); // @phpstan-ignore-line - } catch (ApiException $e) { - Log::error($e->getMessage()); + } catch (ApiException $apiException) { + Log::error($apiException->getMessage()); Notification::make() ->title(__('notifications.sponsor_error_title')) diff --git a/app/Livewire/Components/User/Discussions.php b/app/Livewire/Components/User/Discussions.php index f00f6963..cb0d0d34 100644 --- a/app/Livewire/Components/User/Discussions.php +++ b/app/Livewire/Components/User/Discussions.php @@ -71,7 +71,7 @@ public function deleteAction(): Action $this->authorize('delete', $discussion); - app(DeleteDiscussionAction::class)->execute($discussion); + resolve(DeleteDiscussionAction::class)->execute($discussion); Notification::make() ->success() diff --git a/app/Livewire/Components/User/Password.php b/app/Livewire/Components/User/Password.php index d437490d..eda05876 100644 --- a/app/Livewire/Components/User/Password.php +++ b/app/Livewire/Components/User/Password.php @@ -18,7 +18,7 @@ use Livewire\Component; /** - * @property \Filament\Schemas\Schema $form + * @property-read Schema $form */ final class Password extends Component implements HasActions, HasForms { diff --git a/app/Livewire/Components/User/Preferences.php b/app/Livewire/Components/User/Preferences.php index c5d9dd67..b7b18ab6 100644 --- a/app/Livewire/Components/User/Preferences.php +++ b/app/Livewire/Components/User/Preferences.php @@ -19,7 +19,7 @@ use Livewire\Component; /** - * @property \Filament\Schemas\Schema $form + * @property Schema $form * @property User $user */ final class Preferences extends Component implements HasActions, HasForms diff --git a/app/Livewire/Components/User/Profile.php b/app/Livewire/Components/User/Profile.php index dab55205..cab8face 100644 --- a/app/Livewire/Components/User/Profile.php +++ b/app/Livewire/Components/User/Profile.php @@ -25,7 +25,7 @@ use Ysfkaya\FilamentPhoneInput\Forms\PhoneInput; /** - * @property \Filament\Schemas\Schema $form + * @property Schema $form * @property User $user */ final class Profile extends Component implements HasActions, HasForms @@ -73,7 +73,7 @@ public function form(Schema $schema): Schema ->label(__('validation.attributes.bio')) ->hint(__('global.characters', ['number' => 160])) ->maxLength(160) - ->afterStateUpdated(fn (?string $state): string => trim(strip_tags((string) $state))) + ->afterStateUpdated(fn (?string $state): string => mb_trim(strip_tags((string) $state))) ->helperText(__('pages/account.settings.bio_description')), TextInput::make('website') ->label(__('validation.attributes.website')) @@ -167,7 +167,7 @@ public function save(): void { $this->validate(); - app(UpdateUserProfileAction::class)->execute( + resolve(UpdateUserProfileAction::class)->execute( data: $this->form->getState(), user: $this->user, currentUserEmail: $this->currentUserEmail, diff --git a/app/Livewire/Components/User/Threads.php b/app/Livewire/Components/User/Threads.php index e975a871..ca3cb58f 100644 --- a/app/Livewire/Components/User/Threads.php +++ b/app/Livewire/Components/User/Threads.php @@ -73,7 +73,7 @@ public function deleteAction(): Action $this->authorize('delete', $thread); - app(DeleteThreadAction::class)->execute($thread); + resolve(DeleteThreadAction::class)->execute($thread); Notification::make() ->success() ->title(__('notifications.thread.deleted')) diff --git a/app/Livewire/Pages/Account/Profile.php b/app/Livewire/Pages/Account/Profile.php index cfc635a6..0cd18a67 100644 --- a/app/Livewire/Pages/Account/Profile.php +++ b/app/Livewire/Pages/Account/Profile.php @@ -42,8 +42,7 @@ public function threads(): Collection ttl: now()->addDays(3), callback: fn () => Thread::with('channels') ->withCount('replies') - ->whereBelongsTo($this->user) - ->orderByDesc('created_at') + ->whereBelongsTo($this->user)->latest() ->limit(5) ->get() ); diff --git a/app/Livewire/Pages/Discussions/SingleDiscussion.php b/app/Livewire/Pages/Discussions/SingleDiscussion.php index b785b66b..00e7e32e 100644 --- a/app/Livewire/Pages/Discussions/SingleDiscussion.php +++ b/app/Livewire/Pages/Discussions/SingleDiscussion.php @@ -73,13 +73,13 @@ public function convertedToThreadAction(): Action ->modalHeading(__('pages/discussion.convert_to_thread')) ->modalDescription(__('pages/discussion.text_confirmation')) ->action(function (): void { - $thread = app(ConvertDiscussionToThreadAction::class)->execute($this->discussion); + $thread = resolve(ConvertDiscussionToThreadAction::class)->execute($this->discussion); $this->redirectRoute('forum.show', $thread, navigate: true); }); } - public function deleteAction(): Action + public function deleteAction(): DeleteAction { return DeleteAction::make() ->record($this->discussion) @@ -88,7 +88,7 @@ public function deleteAction(): Action ->requiresConfirmation() ->successNotificationTitle(__('notifications.discussion.deleted')) ->action(function (): void { - app(DeleteDiscussionAction::class)->execute($this->discussion); + resolve(DeleteDiscussionAction::class)->execute($this->discussion); $this->redirectRoute('discussions.index', navigate: true); }); diff --git a/app/Livewire/Pages/Forum/DetailThread.php b/app/Livewire/Pages/Forum/DetailThread.php index 23b3e74d..b0c7d208 100644 --- a/app/Livewire/Pages/Forum/DetailThread.php +++ b/app/Livewire/Pages/Forum/DetailThread.php @@ -55,7 +55,7 @@ public function deleteAction(): Action ->authorize('delete', $this->thread) ->requiresConfirmation() ->action(function (): void { - app(DeleteThreadAction::class)->execute($this->thread); + resolve(DeleteThreadAction::class)->execute($this->thread); $this->redirectRoute('forum.index', navigate: true); }); diff --git a/app/Livewire/Pages/Forum/Index.php b/app/Livewire/Pages/Forum/Index.php index 4c3f8bb7..b6b4f96e 100644 --- a/app/Livewire/Pages/Forum/Index.php +++ b/app/Livewire/Pages/Forum/Index.php @@ -71,6 +71,37 @@ public function redirectToLogin(): void $this->redirectRoute('login', navigate: true); } + public function render(): View + { + $query = Thread::with([ + 'channels', + 'channels.parent', + 'user:id,username,name,avatar_type', + 'user.providers:id,user_id,provider,avatar', + 'user.media', + ]); + + if (blank($this->solved)) { + $query->withCount('replies')->withViewsCount(); + } + + $query = $this->applyChannel($query); + $query = $this->applySearch($query); + $query = $this->applySolved($query); + $query = $this->applyLocale($query); + $query = $this->applyAuthor($query); + $query = $this->applySubscribe($query); + $query = $this->applyUnAnswer($query); + $query = $this->applySorting($query); + + $threads = $query->paginate($this->perPage); + + return view('livewire.pages.forum.index', [ + 'threads' => $threads, + ]) + ->title(__('pages/forum.channel_title', ['channel' => filled($this->currentChannel) ? ' ~ '.$this->currentChannel->name : ''])); + } + protected function applyPopular(Builder $query): Builder { if (filled($this->popular)) { @@ -161,37 +192,6 @@ protected function applySorting(Builder $query): Builder { return filled($this->popular) ? $this->applyPopular($query) - : $query->orderByDesc('created_at'); - } - - public function render(): View - { - $query = Thread::with([ - 'channels', - 'channels.parent', - 'user:id,username,name,avatar_type', - 'user.providers:id,user_id,provider,avatar', - 'user.media', - ]); - - if (blank($this->solved)) { - $query->withCount('replies')->withViewsCount(); - } - - $query = $this->applyChannel($query); - $query = $this->applySearch($query); - $query = $this->applySolved($query); - $query = $this->applyLocale($query); - $query = $this->applyAuthor($query); - $query = $this->applySubscribe($query); - $query = $this->applyUnAnswer($query); - $query = $this->applySorting($query); - - $threads = $query->paginate($this->perPage); - - return view('livewire.pages.forum.index', [ - 'threads' => $threads, - ]) - ->title(__('pages/forum.channel_title', ['channel' => filled($this->currentChannel) ? ' ~ '.$this->currentChannel->name : ''])); + : $query->latest(); } } diff --git a/app/Livewire/Pages/Notifications.php b/app/Livewire/Pages/Notifications.php index 8688684e..cbb58d84 100644 --- a/app/Livewire/Pages/Notifications.php +++ b/app/Livewire/Pages/Notifications.php @@ -5,7 +5,6 @@ namespace App\Livewire\Pages; use App\Policies\NotificationPolicy; -use Carbon\Carbon; use Filament\Notifications\Notification; use Illuminate\Contracts\View\View; use Illuminate\Foundation\Auth\Access\AuthorizesRequests; @@ -60,7 +59,7 @@ public function render(): View ->take(10) ->get() ->groupBy( - fn ($notification): string => Carbon::parse($notification->created_at)->format('M, Y') + fn ($notification): string => \Illuminate\Support\Facades\Date::parse($notification->created_at)->format('M, Y') ), ]); } diff --git a/app/Mail/NewReplyEmail.php b/app/Mail/NewReplyEmail.php index ae3ab234..71b0a838 100644 --- a/app/Mail/NewReplyEmail.php +++ b/app/Mail/NewReplyEmail.php @@ -22,7 +22,7 @@ public function __construct( public function build(): self { $subject = $this->reply->replyAble - ? "Re: {$this->reply->replyAble->subject()}" // @phpstan-ignore-line + ? 'Re: '.$this->reply->replyAble->subject() // @phpstan-ignore-line : 'New Reply'; return $this->subject($subject) diff --git a/app/Mail/SendSponsorThanksMail.php b/app/Mail/SendSponsorThanksMail.php index dbd066eb..0688b315 100644 --- a/app/Mail/SendSponsorThanksMail.php +++ b/app/Mail/SendSponsorThanksMail.php @@ -12,7 +12,8 @@ final class SendSponsorThanksMail extends Mailable { - use Queueable, SerializesModels; + use Queueable; + use SerializesModels; public function __construct(public string $name) {} diff --git a/app/Mail/UserBannedEMail.php b/app/Mail/UserBannedEMail.php index f73ac1cf..0052dc73 100644 --- a/app/Mail/UserBannedEMail.php +++ b/app/Mail/UserBannedEMail.php @@ -13,7 +13,8 @@ final class UserBannedEMail extends Mailable { - use Queueable, SerializesModels; + use Queueable; + use SerializesModels; public function __construct(public User $user) {} diff --git a/app/Mail/UserUnBannedEMail.php b/app/Mail/UserUnBannedEMail.php index 28f17fe3..733f6beb 100644 --- a/app/Mail/UserUnBannedEMail.php +++ b/app/Mail/UserUnBannedEMail.php @@ -13,7 +13,8 @@ final class UserUnBannedEMail extends Mailable { - use Queueable, SerializesModels; + use Queueable; + use SerializesModels; public function __construct(public User $user) {} diff --git a/app/Markdown/BaseExtension.php b/app/Markdown/BaseExtension.php index 05dda59c..a4d491be 100644 --- a/app/Markdown/BaseExtension.php +++ b/app/Markdown/BaseExtension.php @@ -20,6 +20,10 @@ abstract class BaseExtension */ protected $customBlockRenderer; + abstract protected function codeNodes(): array; + + abstract protected function getLiteralContent($node): string; + public function onDocumentParsed(DocumentParsedEvent $event): void { $walker = $event->getDocument()->walker(); @@ -67,17 +71,13 @@ public function defaultBlockRenderer(): Closure array_unshift($blocks, $block); foreach ($blocks as $block) { - $inner .= "attrsAsString()}class='{$block->classes}' style='{$block->styles}'>{$block->highlighted}"; + $inner .= sprintf("%s", $block->attrsAsString(), $block->classes, $block->styles, $block->highlighted); } - return "
$inner
"; + return sprintf('
%s
', $inner); }; } - abstract protected function codeNodes(): array; - - abstract protected function getLiteralContent($node): string; - /** * Bind into a Commonmark V1 or V2 environment. */ @@ -117,6 +117,8 @@ protected function renderNode($node): mixed return call_user_func($renderer, static::$torchlightBlocks[$hash]); } + + return null; } protected function getContent($node): string @@ -128,7 +130,7 @@ protected function getContent($node): string return $content; } - $file = trim(Str::after($content, '<<<')); + $file = mb_trim(Str::after($content, '<<<')); // It must be only one line, because otherwise it might be a heredoc. if (count(explode("\n", $file)) > 1) { diff --git a/app/Markdown/MarkdownHelper.php b/app/Markdown/MarkdownHelper.php index 6a15c581..90874ac2 100644 --- a/app/Markdown/MarkdownHelper.php +++ b/app/Markdown/MarkdownHelper.php @@ -73,6 +73,7 @@ public static function replaceCodePenTag(string $html, array $tagArray, string $ if (isset($tagArray[3]) && $tagArray[3] !== '%}') { $defaultTag = $tagArray[3]; } + $codepenEmbed = '
'; $html = str_replace($original_string, $codepenEmbed, $html); } diff --git a/app/Models/Activity.php b/app/Models/Activity.php index bb82dc18..3f30feef 100644 --- a/app/Models/Activity.php +++ b/app/Models/Activity.php @@ -4,41 +4,24 @@ namespace App\Models; -use Database\Factories\ActivityFactory; -use Illuminate\Database\Eloquent\Builder; +use Carbon\CarbonInterface; use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Relations\BelongsTo; use Illuminate\Database\Eloquent\Relations\MorphTo; -use Illuminate\Support\Carbon; use Illuminate\Support\Collection; /** - * @property int $id - * @property string $subject_type - * @property int $subject_id - * @property string $type - * @property array|null $data - * @property int $user_id - * @property Carbon $created_at - * @property Carbon $updated_at + * @property-read int $id + * @property-read string $subject_type + * @property-read int $subject_id + * @property-read string $type + * @property-read array|null $data + * @property-read int $user_id + * @property-read CarbonInterface $created_at + * @property-read CarbonInterface $updated_at * @property-read Model $subject * @property-read User $user - * - * @method static ActivityFactory factory($count = null, $state = []) - * @method static Builder|Activity newModelQuery() - * @method static Builder|Activity newQuery() - * @method static Builder|Activity query() - * @method static Builder|Activity whereCreatedAt($value) - * @method static Builder|Activity whereData($value) - * @method static Builder|Activity whereId($value) - * @method static Builder|Activity whereSubjectId($value) - * @method static Builder|Activity whereSubjectType($value) - * @method static Builder|Activity whereType($value) - * @method static Builder|Activity whereUpdatedAt($value) - * @method static Builder|Activity whereUserId($value) - * - * @mixin Model */ final class Activity extends Model { @@ -46,13 +29,9 @@ final class Activity extends Model protected $guarded = []; - protected function casts(): array - { - return [ - 'data' => 'array', - ]; - } - + /** + * @return Collection + */ public static function feed(User $user, int $take = 50): Collection { return self::query() @@ -65,14 +44,16 @@ public static function feed(User $user, int $take = 50): Collection ->groupBy(fn (Activity $activity): string => $activity->created_at->format('Y-m-d')); } + /** + * @return Collection + */ public static function latestFeed(User $user, int $take = 10): Collection { return self::query() ->where('user_id', $user->id) ->with('subject') ->latest() - ->limit($take) - ->orderByDesc('created_at') + ->limit($take)->latest() ->get(); } @@ -88,4 +69,11 @@ public function user(): BelongsTo { return $this->belongsTo(User::class); } + + protected function casts(): array + { + return [ + 'data' => 'array', + ]; + } } diff --git a/app/Models/Article.php b/app/Models/Article.php index 462cffdd..2adacfb0 100644 --- a/app/Models/Article.php +++ b/app/Models/Article.php @@ -13,13 +13,14 @@ use App\Traits\HasTags; use App\Traits\Reactable; use App\Traits\RecordsActivity; -use Carbon\Carbon; +use Carbon\CarbonInterface; use CyrildeWit\EloquentViewable\Contracts\Viewable; use CyrildeWit\EloquentViewable\InteractsWithViews; use Illuminate\Database\Eloquent\Attributes\ObservedBy; use Illuminate\Database\Eloquent\Collection; use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Model; +use Illuminate\Support\Facades\Date; use Illuminate\Support\Str; use Spatie\MediaLibrary\HasMedia; use Spatie\MediaLibrary\InteractsWithMedia; @@ -34,21 +35,21 @@ * @property-read bool $show_toc * @property-read bool $is_pinned * @property-read int $is_sponsored - * @property-read string|null $canonical_url - * @property-read string|null $reason - * @property-read int|null $tweet_id + * @property-read ?string $canonical_url + * @property-read ?string $reason + * @property-read ?int $tweet_id * @property-read int $user_id - * @property-read string|null $locale + * @property-read ?string $locale * @property-read User $user - * @property-read \Illuminate\Support\Carbon|null $published_at - * @property-read \Illuminate\Support\Carbon|null $submitted_at - * @property-read \Illuminate\Support\Carbon|null $approved_at - * @property-read \Illuminate\Support\Carbon|null $shared_at - * @property-read \Illuminate\Support\Carbon|null $declined_at - * @property-read \Illuminate\Support\Carbon|null $sponsored_at - * @property-read \Illuminate\Support\Carbon $created_at - * @property-read \Illuminate\Support\Carbon $updated_at - * @property Collection $tags + * @property-read ?CarbonInterface $published_at + * @property-read ?CarbonInterface $submitted_at + * @property-read ?CarbonInterface $approved_at + * @property-read ?CarbonInterface $shared_at + * @property-read ?CarbonInterface $declined_at + * @property-read ?CarbonInterface $sponsored_at + * @property-read CarbonInterface $created_at + * @property-read CarbonInterface $updated_at + * @property-read Collection $tags */ #[ObservedBy(ArticleObserver::class)] final class Article extends Model implements HasMedia, ReactableInterface, Sitemapable, Viewable @@ -67,18 +68,22 @@ final class Article extends Model implements HasMedia, ReactableInterface, Sitem protected bool $removeViewsOnDelete = true; - protected function casts(): array + public static function nextForSharing(): ?self { - return [ - 'submitted_at' => 'datetime', - 'approved_at' => 'datetime', - 'declined_at' => 'datetime', - 'shared_at' => 'datetime', - 'sponsored_at' => 'datetime', - 'published_at' => 'datetime', - 'show_toc' => 'boolean', - 'is_pinned' => 'boolean', - ]; + // @phpstan-ignore-next-line + return self::notShared() + ->published() + ->orderBy('published_at') + ->first(); + } + + public static function nexForSharingToTelegram(): ?self + { + // @phpstan-ignore-next-line + return self::published() + ->whereNull('tweet_id') + ->orderBy('published_at', 'asc') + ->first(); } public function getRouteKeyName(): string @@ -94,7 +99,7 @@ public function newEloquentBuilder($query): ArticleQueryBuilder public function toSitemapTag(): Url { return Url::create(route('articles.show', $this)) - ->setLastModificationDate(Carbon::create($this->updated_at)) // @phpstan-ignore-line + ->setLastModificationDate(Date::create($this->updated_at)) ->setChangeFrequency(Url::CHANGE_FREQUENCY_YEARLY) ->setPriority(0.5); } @@ -121,12 +126,12 @@ public function canonicalUrl(): string return $this->canonical_url ?: route('articles.show', $this->slug); } - public function nextArticle(): ?Article + public function nextArticle(): ?self { return self::published()->where('id', '>', $this->id)->orderBy('id')->first(); // @phpstan-ignore-line } - public function previousArticle(): ?Article + public function previousArticle(): ?self { return self::published()->where('id', '<', $this->id)->orderByDesc('id')->first(); // @phpstan-ignore-line } @@ -183,7 +188,11 @@ public function isPublished(): bool public function isNotPublished(): bool { - return $this->isNotSubmitted() || $this->isNotApproved(); + if ($this->isNotSubmitted()) { + return true; + } + + return $this->isNotApproved(); } public function isPinned(): bool @@ -216,24 +225,6 @@ public function markAsShared(): void $this->update(['shared_at' => now()]); } - public static function nextForSharing(): ?self - { - // @phpstan-ignore-next-line - return self::notShared() - ->published() - ->orderBy('published_at') - ->first(); - } - - public static function nexForSharingToTelegram(): ?self - { - // @phpstan-ignore-next-line - return self::published() - ->whereNull('tweet_id') - ->orderBy('published_at', 'asc') - ->first(); - } - public function markAsPublish(): void { $this->update(['tweet_id' => $this->user->id]); @@ -245,4 +236,18 @@ public function delete(): ?bool return parent::delete(); } + + protected function casts(): array + { + return [ + 'submitted_at' => 'datetime', + 'approved_at' => 'datetime', + 'declined_at' => 'datetime', + 'shared_at' => 'datetime', + 'sponsored_at' => 'datetime', + 'published_at' => 'datetime', + 'show_toc' => 'boolean', + 'is_pinned' => 'boolean', + ]; + } } diff --git a/app/Models/Builders/ArticleQueryBuilder.php b/app/Models/Builders/ArticleQueryBuilder.php index 4ebd2d4b..450c1b21 100644 --- a/app/Models/Builders/ArticleQueryBuilder.php +++ b/app/Models/Builders/ArticleQueryBuilder.php @@ -88,15 +88,14 @@ public function awaitingApproval(): self public function recent(): self { - return $this->orderByDesc('published_at') - ->orderByDesc('created_at'); + return $this->latest('published_at')->latest(); } public function popular(): self { return $this->withCount('reactions') ->orderBy('reactions_count', 'desc') - ->orderBy('published_at', 'desc'); + ->latest('published_at'); } public function trending(): self @@ -105,6 +104,6 @@ public function trending(): self $query->where('created_at', '>=', now()->subWeek()); }]) ->orderBy('reactions_count', 'desc') - ->orderBy('published_at', 'desc'); + ->latest('published_at'); } } diff --git a/app/Models/Builders/DiscussionQueryBuilder.php b/app/Models/Builders/DiscussionQueryBuilder.php index 2e515eb7..e44ad3b6 100644 --- a/app/Models/Builders/DiscussionQueryBuilder.php +++ b/app/Models/Builders/DiscussionQueryBuilder.php @@ -24,8 +24,7 @@ public function notPinned(): self public function recent(): self { - return $this->orderBy('is_pinned', 'desc') - ->orderBy('created_at', 'desc'); + return $this->orderBy('is_pinned', 'desc')->latest(); } public function popular(): self @@ -44,7 +43,6 @@ public function active(): self public function noComments(): self { - return $this->whereDoesntHave('replies') - ->orderByDesc('created_at'); + return $this->whereDoesntHave('replies')->latest(); } } diff --git a/app/Models/Channel.php b/app/Models/Channel.php index aad1dbfb..efd16cb4 100644 --- a/app/Models/Channel.php +++ b/app/Models/Channel.php @@ -19,12 +19,12 @@ * @property-read int $id * @property-read string $name * @property-read string $slug - * @property-read string|null $description + * @property-read ?string $description * @property-read string $color - * @property-read int|null $parent_id - * @property-read Channel|null $parent - * @property-read Collection $items - * @property-read Collection $threads + * @property-read ?int $parent_id + * @property-read ?Channel $parent + * @property-read Collection $items + * @property-read Collection $threads */ final class Channel extends Model { @@ -32,23 +32,11 @@ final class Channel extends Model use HasSlug; use HasTranslations; - protected $guarded = []; - - public array $translatable = ['description']; - - protected static function boot(): void - { - parent::boot(); + public array $translatable = [ + 'description', + ]; - self::saving(function (self $channel): void { - /** @var self $record */ - $record = self::query()->find($channel->parent_id); - - if ($channel->parent_id && $record->exists() && $record->parent_id) { - throw CannotAddChannelToChild::childChannelCannotBeParent($channel); - } - }); - } + protected $guarded = []; public function getRouteKeyName(): string { @@ -83,4 +71,18 @@ public function threads(): BelongsToMany { return $this->belongsToMany(Thread::class); } + + protected static function boot(): void + { + parent::boot(); + + self::saving(function (self $channel): void { + /** @var self $record */ + $record = self::query()->find($channel->parent_id); + + if ($channel->parent_id && $record->exists() && $record->parent_id) { + throw CannotAddChannelToChild::childChannelCannotBeParent($channel); + } + }); + } } diff --git a/app/Models/Discussion.php b/app/Models/Discussion.php index ef69fceb..efa0e7b8 100644 --- a/app/Models/Discussion.php +++ b/app/Models/Discussion.php @@ -18,12 +18,14 @@ use App\Traits\HasTags; use App\Traits\Reactable; use App\Traits\RecordsActivity; -use Carbon\Carbon; +use Carbon\CarbonInterface; use CyrildeWit\EloquentViewable\Contracts\Viewable; use CyrildeWit\EloquentViewable\InteractsWithViews; +use Database\Factories\DiscussionFactory; use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Model; use Illuminate\Support\Collection; +use Illuminate\Support\Facades\Date; use Illuminate\Support\Str; use Spatie\Sitemap\Contracts\Sitemapable; use Spatie\Sitemap\Tags\Url; @@ -35,21 +37,24 @@ * @property-read string $body * @property-read bool $locked * @property-read bool $is_pinned - * @property-read string|null $locale + * @property-read ?string $locale * @property-read int $user_id * @property-read int $count_all_replies_with_child * @property-read User $user - * @property-read \Illuminate\Support\Carbon $created_at - * @property-read \Illuminate\Support\Carbon $updated_at - * @property-read Collection $spamReports - * @property-read Collection $replies - * @property-read Collection $tags - * @property-read Collection $reactions + * @property-read CarbonInterface $created_at + * @property-read CarbonInterface $updated_at + * @property-read Collection $spamReports + * @property-read Collection $replies + * @property-read Collection $tags + * @property-read Collection $reactions */ final class Discussion extends Model implements ReactableInterface, ReplyInterface, Sitemapable, SpamReportableContract, SubscribeInterface, Viewable { use HasAuthor; + + /** @use HasFactory */ use HasFactory; + use HasLocaleScope; use HasReplies; use HasSlug; @@ -64,14 +69,6 @@ final class Discussion extends Model implements ReactableInterface, ReplyInterfa protected bool $removeViewsOnDelete = true; - protected function casts(): array - { - return [ - 'locked' => 'boolean', - 'is_pinned' => 'boolean', - ]; - } - public function newEloquentBuilder($query): DiscussionQueryBuilder { return new DiscussionQueryBuilder($query); @@ -105,7 +102,7 @@ public function excerpt(int $limit = 110): string public function toSitemapTag(): Url { return Url::create(route('discussions.show', $this)) - ->setLastModificationDate(Carbon::create($this->updated_at)) // @phpstan-ignore-line + ->setLastModificationDate(Date::create($this->updated_at)) ->setChangeFrequency(Url::CHANGE_FREQUENCY_YEARLY) ->setPriority(0.5); } @@ -132,4 +129,12 @@ public function delete(): ?bool return parent::delete(); } + + protected function casts(): array + { + return [ + 'locked' => 'boolean', + 'is_pinned' => 'boolean', + ]; + } } diff --git a/app/Models/Enterprise.php b/app/Models/Enterprise.php index fbb68168..e08ec318 100644 --- a/app/Models/Enterprise.php +++ b/app/Models/Enterprise.php @@ -7,6 +7,7 @@ use App\Filters\Enterprise\EnterpriseFilters; use App\Models\Traits\HasSlug; use App\Traits\HasSettings; +use Database\Factories\EnterpriseFactory; use Illuminate\Database\Eloquent\Attributes\Scope; use Illuminate\Database\Eloquent\Builder; use Illuminate\Database\Eloquent\Factories\HasFactory; @@ -21,27 +22,19 @@ * @property-read bool $is_public * @property-read bool $is_certified * @property-read bool $is_featured - * @property array|null $settings + * @property-read array|null $settings */ final class Enterprise extends Model implements HasMedia { + /** @use HasFactory */ use HasFactory; + use HasSettings; use HasSlug; use InteractsWithMedia; protected $guarded = []; - protected function casts(): array - { - return [ - 'settings' => 'array', - 'is_public' => 'boolean', - 'is_certified' => 'boolean', - 'is_featured' => 'boolean', - ]; - } - public function registerMediaCollections(): void { $this->addMediaCollection('logo') @@ -63,6 +56,24 @@ public function registerMediaCollections(): void ]); } + /** + * @return BelongsTo + */ + public function owner(): BelongsTo + { + return $this->belongsTo(User::class, 'user_id'); + } + + protected function casts(): array + { + return [ + 'settings' => 'array', + 'is_public' => 'boolean', + 'is_certified' => 'boolean', + 'is_featured' => 'boolean', + ]; + } + /** * @param Builder $query */ @@ -99,12 +110,4 @@ protected function filters(Builder $query, Request $request, array $filters = [] { return new EnterpriseFilters($request)->add($filters)->filter($query); } - - /** - * @return BelongsTo - */ - public function owner(): BelongsTo - { - return $this->belongsTo(User::class, 'user_id'); - } } diff --git a/app/Models/Feature.php b/app/Models/Feature.php deleted file mode 100644 index 519017bb..00000000 --- a/app/Models/Feature.php +++ /dev/null @@ -1,12 +0,0 @@ -where('type', PlanType::DEVELOPER->value); + $query->where('type', PlanType::DEVELOPER); } /** @@ -40,6 +40,6 @@ protected function developer(Builder $query): void #[Scope] protected function enterprise(Builder $query): void { - $query->where('type', PlanType::ENTERPRISE->value); + $query->where('type', PlanType::ENTERPRISE); } } diff --git a/app/Models/Reaction.php b/app/Models/Reaction.php index ca3abdeb..8d0a9de5 100644 --- a/app/Models/Reaction.php +++ b/app/Models/Reaction.php @@ -4,10 +4,15 @@ namespace App\Models; +use Database\Factories\ReactionFactory; +use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Model; final class Reaction extends Model { + /** @use HasFactory */ + use HasFactory; + protected $guarded = []; public static function createFromName(string $name): self @@ -17,7 +22,7 @@ public static function createFromName(string $name): self public function getResponder(): mixed { - if ($this->getOriginal('pivot_responder_type', null)) { + if ($this->getOriginal('pivot_responder_type')) { return forward_static_call( [$this->getOriginal('pivot_responder_type'), 'find'], // @phpstan-ignore-line $this->getOriginal('pivot_responder_id') diff --git a/app/Models/Reply.php b/app/Models/Reply.php index 4159aedb..d93fe3c7 100644 --- a/app/Models/Reply.php +++ b/app/Models/Reply.php @@ -12,7 +12,8 @@ use App\Traits\HasSpamReports; use App\Traits\Reactable; use App\Traits\RecordsActivity; -use Carbon\Carbon; +use Carbon\CarbonInterface; +use Database\Factories\ReplyFactory; use Illuminate\Database\Eloquent\Attributes\Scope; use Illuminate\Database\Eloquent\Builder; use Illuminate\Database\Eloquent\Factories\HasFactory; @@ -25,19 +26,23 @@ /** * @property-read int $id - * @property string $body - * @property int $user_id - * @property Carbon $created_at - * @property Carbon $updated_at - * @property User $user - * @property int $replyable_id - * @property string $replyable_type - * @property Collection | SpamReport[] $spamReports + * @property-read string $body + * @property-read int $user_id + * @property-read User $user + * @property-read int $replyable_id + * @property-read string $replyable_type + * @property-read CarbonInterface $created_at + * @property-read CarbonInterface $updated_at + * @property-read Collection $spamReports + * @property-read ?Thread $solutionTo */ final class Reply extends Model implements ReactableInterface, ReplyInterface, SpamReportableContract { use HasAuthor; + + /** @use HasFactory */ use HasFactory; + use HasReplies; use HasSpamReports; use Reactable; @@ -57,12 +62,12 @@ public function replyAbleSubject(): int public function getPathUrl(): string { - return "#reply-{$this->id}"; + return '#reply-'.$this->id; } public function wasJustPublished(): bool { - return $this->created_at->gt(Carbon::now()->subMinute()); + return $this->created_at->gt(\Illuminate\Support\Facades\Date::now()->subMinute()); } public function excerpt(int $limit = 100): string @@ -82,15 +87,6 @@ public function to(ReplyInterface|Model $replyable): void $this->replyAble()->associate($replyable); // @phpstan-ignore-line } - /** - * @param Builder $query - */ - #[Scope] - protected function isSolution(Builder $query): Builder - { - return $query->has('solutionTo'); - } - public function delete(): bool { $this->deleteReplies(); @@ -125,4 +121,13 @@ public function replyAble(): MorphTo { return $this->morphTo('replyAble', 'replyable_type', 'replyable_id'); } + + /** + * @param Builder $query + */ + #[Scope] + protected function isSolution(Builder $query): Builder + { + return $query->has('solutionTo'); + } } diff --git a/app/Models/SocialAccount.php b/app/Models/SocialAccount.php index c600865e..e6b5bcbe 100644 --- a/app/Models/SocialAccount.php +++ b/app/Models/SocialAccount.php @@ -4,6 +4,8 @@ namespace App\Models; +use Database\Factories\SocialAccountFactory; +use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Relations\BelongsTo; @@ -12,12 +14,15 @@ * @property-read string $provider * @property-read string $provider_id * @property-read int $user_id - * @property-read string|null $token - * @property-read string|null $avatar + * @property-read ?string $token + * @property-read ?string $avatar * @property-read User $user */ final class SocialAccount extends Model { + /** @use HasFactory */ + use HasFactory; + protected $guarded = []; /** diff --git a/app/Models/SpamReport.php b/app/Models/SpamReport.php index 9708ed21..24ee7b6a 100644 --- a/app/Models/SpamReport.php +++ b/app/Models/SpamReport.php @@ -4,20 +4,25 @@ namespace App\Models; +use Database\Factories\SpamReportFactory; +use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Relations\BelongsTo; use Illuminate\Database\Eloquent\Relations\MorphTo; /** * @property-read int $id - * @property int $user_id - * @property int $reportable_id - * @property string $reportable_type - * @property string | null $reason - * @property User | null $user + * @property-read int $user_id + * @property-read int $reportable_id + * @property-read string $reportable_type + * @property-read ?string $reason + * @property-read ?User $user */ final class SpamReport extends Model { + /** @use HasFactory */ + use HasFactory; + protected $guarded = []; /** diff --git a/app/Models/Subscribe.php b/app/Models/Subscribe.php index 911adf9f..e60e17e5 100644 --- a/app/Models/Subscribe.php +++ b/app/Models/Subscribe.php @@ -5,19 +5,24 @@ namespace App\Models; use App\Traits\HasUuid; +use Database\Factories\SubscribeFactory; +use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Relations\BelongsTo; use Illuminate\Database\Eloquent\Relations\MorphTo; /** - * @property string $uuid - * @property int $user_id - * @property int $subscribeable_id - * @property string $subscribeable_type - * @property User $user + * @property-read string $uuid + * @property-read int $user_id + * @property-read int $subscribeable_id + * @property-read string $subscribeable_type + * @property-read User $user */ final class Subscribe extends Model { + /** @use HasFactory */ + use HasFactory; + use HasUuid; /** diff --git a/app/Models/Tag.php b/app/Models/Tag.php index 26030b08..dec3ca97 100644 --- a/app/Models/Tag.php +++ b/app/Models/Tag.php @@ -5,6 +5,7 @@ namespace App\Models; use App\Models\Traits\HasSlug; +use Database\Factories\TagFactory; use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Relations\MorphPivot; @@ -15,26 +16,21 @@ * @property-read int $id * @property-read string $name * @property-read string $slug - * @property-read string|null $description - * @property-read array $concerns - * @property-read Collection $articles + * @property-read ?string $description + * @property-read array $concerns + * @property-read Collection $articles */ final class Tag extends Model { + /** @use HasFactory */ use HasFactory; + use HasSlug; public $timestamps = false; protected $guarded = []; - protected function casts(): array - { - return [ - 'concerns' => 'array', - ]; - } - /** * @return MorphToMany */ @@ -42,4 +38,11 @@ public function articles(): MorphToMany { return $this->morphedByMany(Article::class, 'taggable'); } + + protected function casts(): array + { + return [ + 'concerns' => 'array', + ]; + } } diff --git a/app/Models/Thread.php b/app/Models/Thread.php index 60c77b3c..64810dbf 100644 --- a/app/Models/Thread.php +++ b/app/Models/Thread.php @@ -18,13 +18,14 @@ use App\Traits\HasSubscribers; use App\Traits\Reactable; use App\Traits\RecordsActivity; -use Carbon\Carbon; +use Carbon\CarbonInterface; use CyrildeWit\EloquentViewable\Contracts\Viewable; use CyrildeWit\EloquentViewable\InteractsWithViews; use Database\Factories\ThreadFactory; use Exception; use Illuminate\Database\Eloquent\Attributes\Scope; use Illuminate\Database\Eloquent\Builder; +use Illuminate\Database\Eloquent\Collection; use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Relations\BelongsTo; @@ -40,22 +41,22 @@ /** * @property-read int $id - * @property string $title - * @property string $slug - * @property string $body - * @property int $user_id - * @property int $solution_reply_id - * @property bool $locked - * @property string | null $locale - * @property Carbon | null $last_posted_at - * @property Carbon $created_at - * @property Carbon $updated_at - * @property int | null $resolved_by - * @property User | null $resolvedBy - * @property User $user - * @property Reply | null $solutionReply - * @property \Illuminate\Database\Eloquent\Collection | Channel[] $channels - * @property \Illuminate\Database\Eloquent\Collection | Reply[] $replies + * @property-read string $title + * @property-read string $slug + * @property-read string $body + * @property-read int $user_id + * @property-read int $solution_reply_id + * @property-read bool $locked + * @property-read ?string $locale + * @property-read ?CarbonInterface $last_posted_at + * @property-read CarbonInterface $created_at + * @property-read CarbonInterface $updated_at + * @property-read ?int $resolved_by + * @property-read User $user + * @property-read ?User $resolvedBy + * @property-read ?Reply $solutionReply + * @property-read Collection $channels + * @property-read Collection $replies */ final class Thread extends Model implements Feedable, ReactableInterface, ReplyInterface, SpamReportableContract, SubscribeInterface, Viewable { @@ -63,6 +64,7 @@ final class Thread extends Model implements Feedable, ReactableInterface, ReplyI /** @use HasFactory */ use HasFactory; + use HasLocaleScope; use HasReplies; use HasSlug; @@ -79,12 +81,28 @@ final class Thread extends Model implements Feedable, ReactableInterface, ReplyI protected bool $removeViewsOnDelete = true; - protected function casts(): array + /** + * This will calculate the average resolution time in days of all threads marked as resolved. + */ + public static function resolutionTime(): bool|int { - return [ - 'locked' => 'boolean', - 'last_posted_at' => 'datetime', - ]; + try { + // @phpstan-ignore-next-line + return self::query() + ->join('replies', 'threads.solution_reply_id', '=', 'replies.id') + ->select(DB::raw('avg(datediff(replies.created_at, threads.created_at)) as duration')) + ->first() + ->duration; + } catch (Exception $exception) { + return false; + } + } + + public static function getFeedItems(): SupportCollection + { + return self::with(['reactions'])->feedQuery() + ->paginate(self::FEED_PAGE_SIZE) + ->getCollection(); } public function getRouteKeyName(): string @@ -167,7 +185,7 @@ public function delete(): bool public function toFeedItem(): FeedItem { - $updatedAt = Carbon::parse($this->latest_creation); // @phpstan-ignore-line + $updatedAt = \Illuminate\Support\Facades\Date::parse($this->latest_creation); // @phpstan-ignore-line return FeedItem::create() ->id((string) $this->id) @@ -178,30 +196,6 @@ public function toFeedItem(): FeedItem ->authorName($this->user->name); } - /** - * This will calculate the average resolution time in days of all threads marked as resolved. - */ - public static function resolutionTime(): bool|int - { - try { - // @phpstan-ignore-next-line - return self::query() - ->join('replies', 'threads.solution_reply_id', '=', 'replies.id') - ->select(DB::raw('avg(datediff(replies.created_at, threads.created_at)) as duration')) - ->first() - ->duration; - } catch (Exception $e) { - return false; - } - } - - public static function getFeedItems(): SupportCollection - { - return self::with(['reactions'])->feedQuery() - ->paginate(self::FEED_PAGE_SIZE) - ->getCollection(); - } - /** * @param int[] $channels */ @@ -220,6 +214,38 @@ public function removeChannels(): void $this->unsetRelation('channels'); } + /** + * @return BelongsTo + */ + public function resolvedBy(): BelongsTo + { + return $this->belongsTo(User::class, 'resolved_by'); + } + + /** + * @return BelongsToMany + */ + public function channels(): BelongsToMany + { + return $this->belongsToMany(Channel::class); + } + + /** + * @return BelongsTo + */ + public function solutionReply(): BelongsTo + { + return $this->belongsTo(Reply::class, 'solution_reply_id'); + } + + protected function casts(): array + { + return [ + 'locked' => 'boolean', + 'last_posted_at' => 'datetime', + ]; + } + /** * @param Builder $query * @return Builder @@ -314,28 +340,4 @@ protected function feedQuery(Builder $query): Builder END AS latest_creation ')); } - - /** - * @return BelongsTo - */ - public function resolvedBy(): BelongsTo - { - return $this->belongsTo(User::class, 'resolved_by'); - } - - /** - * @return BelongsToMany - */ - public function channels(): BelongsToMany - { - return $this->belongsToMany(Channel::class); - } - - /** - * @return BelongsTo - */ - public function solutionReply(): BelongsTo - { - return $this->belongsTo(Reply::class, 'solution_reply_id'); - } } diff --git a/app/Models/Traits/HasReplies.php b/app/Models/Traits/HasReplies.php index f62b12c5..90a16f05 100644 --- a/app/Models/Traits/HasReplies.php +++ b/app/Models/Traits/HasReplies.php @@ -5,14 +5,14 @@ namespace App\Models\Traits; use App\Models\Reply; +use Illuminate\Database\Eloquent\Collection; use Illuminate\Database\Eloquent\Relations\HasOne; use Illuminate\Database\Eloquent\Relations\MorphMany; -use Illuminate\Support\Collection; trait HasReplies { /** - * @return Collection + * @return Collection */ public function latestReplies(int $amount = 5): Collection { @@ -33,7 +33,7 @@ public function deleteReplies(): void public function solutionReplyUrl(): string { // @phpstan-ignore-next-line - return $this->getPathUrl()."#reply-{$this->solution_reply_id}"; + return $this->getPathUrl().('#reply-'.$this->solution_reply_id); } public function isConversationOld(): bool diff --git a/app/Models/Traits/HasSlug.php b/app/Models/Traits/HasSlug.php index d8a7f320..09ab1c1b 100644 --- a/app/Models/Traits/HasSlug.php +++ b/app/Models/Traits/HasSlug.php @@ -9,19 +9,20 @@ trait HasSlug { - protected function slug(): Attribute + public static function findBySlug(string $slug): static { - return Attribute::set(fn (string $value) => $this->generateUniqueSlug($value)); + return static::query()->where('slug', $slug)->firstOrFail(); } - public static function findBySlug(string $slug): static + protected function slug(): Attribute { - return static::query()->where('slug', $slug)->firstOrFail(); + return Attribute::set(fn (string $value): string => $this->generateUniqueSlug($value)); } private function generateUniqueSlug(string $value): string { - $slug = $originalSlug = Str::slug($value) ?: Str::random(5); + $slug = Str::slug($value) ?: Str::random(5); + $originalSlug = Str::slug($value) ?: Str::random(5); $counter = 0; while ($this->slugExists($slug, $this->exists ? $this->id : null)) { @@ -34,9 +35,9 @@ private function generateUniqueSlug(string $value): string private function slugExists(string $slug, ?int $ignoreId = null): bool { - $query = $this->where('slug', $slug); + $query = static::query()->where('slug', $slug); - if (! blank($ignoreId)) { + if (filled($ignoreId)) { $query->where('id', '!=', $ignoreId); } diff --git a/app/Models/Transaction.php b/app/Models/Transaction.php index af9d2f99..b3140dc1 100644 --- a/app/Models/Transaction.php +++ b/app/Models/Transaction.php @@ -5,29 +5,31 @@ namespace App\Models; use App\Enums\TransactionStatus; +use Database\Factories\TransactionFactory; use Illuminate\Database\Eloquent\Attributes\Scope; use Illuminate\Database\Eloquent\Builder; use Illuminate\Database\Eloquent\Concerns\HasUuids; +use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Relations\BelongsTo; /** * @property-read string $id - * @property array|null $metadata + * @property-read TransactionStatus $status + * @property-read array|null $metadata */ final class Transaction extends Model { + /** @use HasFactory */ + use HasFactory; + use HasUuids; public $guarded = []; - protected function casts(): array - { - return [ - 'metadata' => 'array', - ]; - } - + /** + * @return string|array + */ public function getMetadata(string $key, string $default = ''): string|array { if ($this->metadata && array_key_exists($key, $this->metadata)) { @@ -37,9 +39,14 @@ public function getMetadata(string $key, string $default = ''): string|array return $default; } + /** + * @param array $revisions + */ public function setMetadata(array $revisions, bool $save = true): self { - $this->metadata = array_unique(array_merge($this->metadata ?? [], $revisions)); + $this->fill([ + 'metadata' => array_unique(array_merge($this->metadata ?? [], $revisions)), + ]); if ($save) { $this->save(); @@ -49,19 +56,27 @@ public function setMetadata(array $revisions, bool $save = true): self } /** - * @param Builder $query + * @return BelongsTo */ - #[Scope] - protected function complete(Builder $query): void + public function user(): BelongsTo { - $query->where('status', TransactionStatus::COMPLETE->value); + return $this->belongsTo(User::class); + } + + protected function casts(): array + { + return [ + 'metadata' => 'array', + 'status' => TransactionStatus::class, + ]; } /** - * @return BelongsTo + * @param Builder $query */ - public function user(): BelongsTo + #[Scope] + protected function complete(Builder $query): void { - return $this->belongsTo(User::class); + $query->where('status', TransactionStatus::COMPLETE); } } diff --git a/app/Models/User.php b/app/Models/User.php index 4f43f091..6ee35d6e 100644 --- a/app/Models/User.php +++ b/app/Models/User.php @@ -11,6 +11,7 @@ use App\Traits\HasSettings; use App\Traits\HasUsername; use App\Traits\Reacts; +use Carbon\CarbonInterface; use Database\Factories\UserFactory; use Filament\Models\Contracts\FilamentUser; use Filament\Models\Contracts\HasAvatar; @@ -27,7 +28,6 @@ use Illuminate\Database\Eloquent\Relations\HasOne; use Illuminate\Foundation\Auth\User as Authenticatable; use Illuminate\Notifications\Notifiable; -use Illuminate\Support\Carbon; use Illuminate\Support\Facades\Auth; use Laravel\Socialite\Contracts\User as SocialUser; use Laravelcm\Gamify\Traits\Gamify; @@ -52,19 +52,18 @@ * @property-read string|null $website * @property-read string|null $banned_reason * @property-read array|null $settings - * @property-read Carbon|null $email_verified_at - * @property-read Carbon|null $last_login_at - * @property-read Carbon|null $banned_at - * @property-read Carbon $created_at - * @property-read Carbon $updated_at - * @property-read Carbon|null $last_active_at - * @property-read \Illuminate\Support\Collection $activities - * @property-read \Illuminate\Support\Collection $articles - * @property-read \Illuminate\Support\Collection $threads - * @property-read \Illuminate\Support\Collection $discussions - * @property-read \Illuminate\Support\Collection $subscriptions - * @property-read \Illuminate\Support\Collection $providers - * @property-read int $discussions_count + * @property-read CarbonInterface|null $email_verified_at + * @property-read CarbonInterface|null $last_login_at + * @property-read CarbonInterface|null $banned_at + * @property-read CarbonInterface $created_at + * @property-read CarbonInterface $updated_at + * @property-read CarbonInterface|null $last_active_at + * @property-read Collection $activities + * @property-read Collection $articles + * @property-read Collection $threads + * @property-read Collection $discussions + * @property-read Collection $subscriptions + * @property-read Collection $providers */ #[ObservedBy(UserObserver::class)] final class User extends Authenticatable implements FilamentUser, HasAvatar, HasCachedMediaInterface, HasMedia, HasName, MustVerifyEmail @@ -73,6 +72,7 @@ final class User extends Authenticatable implements FilamentUser, HasAvatar, Has /** @use HasFactory */ use HasFactory; + use HasPlanSubscriptions; use HasProfilePhoto; use HasRoles; @@ -92,41 +92,32 @@ final class User extends Authenticatable implements FilamentUser, HasAvatar, Has 'last_active_at', ]; - protected function casts(): array + public static function findByEmailAddress(string $emailAddress): self { - return [ - 'email_verified_at' => 'datetime', - 'last_login_at' => 'datetime', - 'banned_at' => 'datetime', - 'settings' => 'array', - 'last_active_at' => 'datetime', - ]; + return self::query()->where('email', $emailAddress)->firstOrFail(); } - protected function rolesLabel(): Attribute + public static function findOrCreateSocialUserProvider(SocialUser $socialUser, string $provider, string $role = 'user'): self { - $roles = $this->getRoleNames()->toArray(); + $socialEmail = $socialUser->getEmail() ?? sprintf('%s@%s.com', $socialUser->getId(), $provider); - return Attribute::get( - fn (): string => count($roles) > 0 - ? implode(', ', array_map(fn ($item): string => ucwords($item), $roles)) - : 'N/A' - ); - } + $user = self::query()->where('email', $socialEmail)->first(); - protected function IsSponsor(): Attribute - { - return Attribute::get(function (): bool { - if ($this->transactions_count > 0) { - $transaction = $this->transactions() - ->where('status', TransactionStatus::COMPLETE->value) - ->first(); + if (! $user instanceof self) { + $user = self::query()->create([ + 'name' => $socialUser->getName() ?? $socialUser->getNickName() ?? $socialUser->getId(), + 'email' => $socialEmail, + 'username' => $socialUser->getNickName() ?? $socialUser->getId(), + 'github_profile' => $provider === 'github' ? $socialUser->getNickName() : null, + 'twitter_profile' => $provider === 'twitter' ? $socialUser->getNickName() : null, + 'email_verified_at' => now(), + 'avatar_type' => $provider, + ]); - return (bool) $transaction; - } + $user->assignRole($role); + } - return false; - }); + return $user; } public function hasProvider(string $provider): bool @@ -161,7 +152,15 @@ public function isLoggedInUser(): bool public function canAccessPanel(Panel $panel): bool { - return str_ends_with($this->email, '@laravel.cm') || $this->isModerator() || $this->isAdmin(); + if (str_ends_with($this->email, '@laravel.cm')) { + return true; + } + + if ($this->isModerator()) { + return true; + } + + return $this->isAdmin(); } public function getFilamentName(): string @@ -198,34 +197,6 @@ public function registerMediaCollections(): void ]); } - public static function findByEmailAddress(string $emailAddress): self - { - return self::query()->where('email', $emailAddress)->firstOrFail(); - } - - public static function findOrCreateSocialUserProvider(SocialUser $socialUser, string $provider, string $role = 'user'): self - { - $socialEmail = $socialUser->getEmail() ?? "{$socialUser->getId()}@{$provider}.com"; - - $user = self::query()->where('email', $socialEmail)->first(); - - if (! $user instanceof self) { - $user = self::query()->create([ - 'name' => $socialUser->getName() ?? $socialUser->getNickName() ?? $socialUser->getId(), - 'email' => $socialEmail, - 'username' => $socialUser->getNickName() ?? $socialUser->getId(), - 'github_profile' => $provider === 'github' ? $socialUser->getNickName() : null, - 'twitter_profile' => $provider === 'twitter' ? $socialUser->getNickName() : null, - 'email_verified_at' => now(), - 'avatar_type' => $provider, - ]); - - $user->assignRole($role); - } - - return $user; - } - public function deleteThreads(): void { // We need to explicitly iterate over the threads and delete them @@ -273,7 +244,7 @@ public function hasPassword(): bool { $password = $this->getAuthPassword(); - return filled($password) || $password !== null; // @phpstan-ignore-line + return filled($password); } public function delete(): ?bool @@ -324,6 +295,123 @@ public function notBanned(): bool return ! $this->banned(); } + /** + * @return HasOne + */ + public function enterprise(): HasOne + { + return $this->hasOne(Enterprise::class); + } + + /** + * @return HasMany + */ + public function providers(): HasMany + { + return $this->hasMany(SocialAccount::class); + } + + /** + * @return HasMany + */ + public function articles(): HasMany + { + return $this->hasMany(Article::class); + } + + /** + * @return HasMany + */ + public function activities(): HasMany + { + return $this->hasMany(Activity::class); + } + + /** + * @return HasMany + */ + public function threads(): HasMany + { + return $this->hasMany(Thread::class); + } + + /** + * @return HasMany + */ + public function replyAble(): HasMany + { + return $this->hasMany(Reply::class); + } + + /** + * @return HasMany + */ + public function discussions(): HasMany + { + return $this->hasMany(Discussion::class); + } + + /** + * @return HasMany + */ + public function subscriptions(): HasMany + { + return $this->hasMany(Subscribe::class); + } + + /** + * @return HasMany + */ + public function transactions(): HasMany + { + return $this->hasMany(Transaction::class); + } + + /** + * @return HasMany + */ + public function spamReports(): HasMany + { + return $this->hasMany(SpamReport::class, 'user_id'); + } + + protected function casts(): array + { + return [ + 'email_verified_at' => 'datetime', + 'last_login_at' => 'datetime', + 'banned_at' => 'datetime', + 'settings' => 'array', + 'last_active_at' => 'datetime', + ]; + } + + protected function rolesLabel(): Attribute + { + $roles = $this->getRoleNames()->toArray(); + + return Attribute::get( + fn (): string => count($roles) > 0 + ? implode(', ', array_map(fn ($item): string => ucwords($item), $roles)) + : 'N/A' + ); + } + + protected function IsSponsor(): Attribute + { + return Attribute::get(function (): bool { + if ($this->transactions_count > 0) { + $transaction = $this->transactions() + ->where('status', TransactionStatus::COMPLETE->value) + ->first(); + + return (bool) $transaction; + } + + return false; + }); + } + /** * @param Builder $query * @return Builder @@ -480,84 +568,4 @@ protected function isNotBanned(Builder $query): Builder { return $query->whereNull('banned_at'); } - - /** - * @return HasOne - */ - public function enterprise(): HasOne - { - return $this->hasOne(Enterprise::class); - } - - /** - * @return HasMany - */ - public function providers(): HasMany - { - return $this->hasMany(SocialAccount::class); - } - - /** - * @return HasMany - */ - public function articles(): HasMany - { - return $this->hasMany(Article::class); - } - - /** - * @return HasMany - */ - public function activities(): HasMany - { - return $this->hasMany(Activity::class); - } - - /** - * @return HasMany - */ - public function threads(): HasMany - { - return $this->hasMany(Thread::class); - } - - /** - * @return HasMany - */ - public function replyAble(): HasMany - { - return $this->hasMany(Reply::class); - } - - /** - * @return HasMany - */ - public function discussions(): HasMany - { - return $this->hasMany(Discussion::class); - } - - /** - * @return HasMany - */ - public function subscriptions(): HasMany - { - return $this->hasMany(Subscribe::class); - } - - /** - * @return HasMany - */ - public function transactions(): HasMany - { - return $this->hasMany(Transaction::class); - } - - /** - * @return HasMany - */ - public function spamReports(): HasMany - { - return $this->hasMany(SpamReport::class, 'user_id'); - } } diff --git a/app/Notifications/ArticleSubmitted.php b/app/Notifications/ArticleSubmitted.php index b7885ccc..22c06f4d 100644 --- a/app/Notifications/ArticleSubmitted.php +++ b/app/Notifications/ArticleSubmitted.php @@ -20,8 +20,8 @@ public function __construct(private readonly Article $article) {} public function via(mixed $notifiable): array { if ( - ! blank(config('services.telegram-bot-api.token')) && - ! blank(config('services.telegram-bot-api.channel')) + filled(config('services.telegram-bot-api.token')) && + filled(config('services.telegram-bot-api.channel')) ) { return [TelegramChannel::class]; } @@ -36,7 +36,7 @@ public function toTelegram(): TelegramMessage return TelegramMessage::create() ->to(config('services.telegram-bot-api.channel')) ->content($this->content()) - ->button('Voir l\'article', $url); + ->button("Voir l'article", $url); } private function content(): string diff --git a/app/Notifications/NewCommentNotification.php b/app/Notifications/NewCommentNotification.php index 1583ac5c..8c0b8139 100644 --- a/app/Notifications/NewCommentNotification.php +++ b/app/Notifications/NewCommentNotification.php @@ -34,7 +34,7 @@ public function via(mixed $notifiable): array public function toMail(): MailMessage { return (new MailMessage) - ->subject("Re: {$this->discussion->subject()}") + ->subject('Re: '.$this->discussion->subject()) ->line(__('@:name a répondu à ce sujet.', ['name' => $this->reply->user->username])) ->line($this->reply->excerpt(150)) ->action(__('Voir la discussion'), route('discussions.show', $this->discussion)) diff --git a/app/Notifications/NewSponsorPaymentNotification.php b/app/Notifications/NewSponsorPaymentNotification.php index d5ac6f02..aa2b1d5e 100644 --- a/app/Notifications/NewSponsorPaymentNotification.php +++ b/app/Notifications/NewSponsorPaymentNotification.php @@ -20,8 +20,8 @@ public function __construct(public readonly Transaction $transaction) {} public function via(mixed $notifiable): array { if ( - ! blank(config('services.telegram-bot-api.token')) && - ! blank(config('services.telegram-bot-api.channel')) + filled(config('services.telegram-bot-api.token')) && + filled(config('services.telegram-bot-api.channel')) ) { return [TelegramChannel::class]; } diff --git a/app/Notifications/PendingArticlesNotification.php b/app/Notifications/PendingArticlesNotification.php index 05bdea75..21649c82 100644 --- a/app/Notifications/PendingArticlesNotification.php +++ b/app/Notifications/PendingArticlesNotification.php @@ -33,7 +33,9 @@ public function toTelegram(): TelegramMessage private function content(): string { $heading = "*Articles soumis en attente d'approbation!*"; - $messages = "{$heading}\n\n"; + $messages = $heading.' + +'; foreach ($this->pendingArticles as $article) { $messages .= __( diff --git a/app/Notifications/PostArticleToTelegram.php b/app/Notifications/PostArticleToTelegram.php index b1d4ecb3..10d9b1fc 100644 --- a/app/Notifications/PostArticleToTelegram.php +++ b/app/Notifications/PostArticleToTelegram.php @@ -25,6 +25,6 @@ public function toTelegram(): TelegramMessage { return TelegramMessage::create() ->to('@laravelcm') - ->content("{$this->article->title} ".route('articles.show', $this->article->slug)); + ->content($this->article->title.' '.route('articles.show', $this->article->slug)); } } diff --git a/app/Notifications/PostArticleToTwitter.php b/app/Notifications/PostArticleToTwitter.php index 7135a9c1..09ac50db 100644 --- a/app/Notifications/PostArticleToTwitter.php +++ b/app/Notifications/PostArticleToTwitter.php @@ -40,7 +40,7 @@ public function generateTweet(): string $title = $this->article->title; $url = route('articles.show', $this->article->slug); $author = $this->article->user; - $author = $author->twitter() ? "@{$author->twitter()}" : $author->name; + $author = $author->twitter() ? '@'.$author->twitter() : $author->name; return "{$title} par {$author}\n\n{$url}\n\n #CaParleDev"; } diff --git a/app/Notifications/PostDiscussionToTelegram.php b/app/Notifications/PostDiscussionToTelegram.php index d254a26a..16080402 100644 --- a/app/Notifications/PostDiscussionToTelegram.php +++ b/app/Notifications/PostDiscussionToTelegram.php @@ -25,6 +25,6 @@ public function toTelegram(): TelegramMessage { return TelegramMessage::create() ->to('@laravelcm') - ->content("{$this->discussion->title} ".route('discussions.show', $this->discussion->slug)); + ->content($this->discussion->title.' '.route('discussions.show', $this->discussion->slug)); } } diff --git a/app/Notifications/PostThreadToTelegram.php b/app/Notifications/PostThreadToTelegram.php index e7501d2e..edbca581 100644 --- a/app/Notifications/PostThreadToTelegram.php +++ b/app/Notifications/PostThreadToTelegram.php @@ -22,6 +22,6 @@ public function toTelegram(mixed $notifiable): TelegramMessage { return TelegramMessage::create() ->to('@laravelcm') - ->content("{$notifiable->subject()} ".route('forum.show', $notifiable)); + ->content($notifiable->subject().' '.route('forum.show', $notifiable)); } } diff --git a/app/Notifications/ReportedSpamToTelegram.php b/app/Notifications/ReportedSpamToTelegram.php index 134000a6..673b6c02 100644 --- a/app/Notifications/ReportedSpamToTelegram.php +++ b/app/Notifications/ReportedSpamToTelegram.php @@ -25,7 +25,7 @@ public function toTelegram(): TelegramMessage { return TelegramMessage::create() ->to('@laravelcm') - ->content("{$this->spamReport->user?->name} vient de reporter un contenu spam") + ->content($this->spamReport->user?->name.' vient de reporter un contenu spam') ->button('Voir les spams', route('filament.admin.pages.dashboard')); } } diff --git a/app/Notifications/YouWereMentioned.php b/app/Notifications/YouWereMentioned.php index 10f563ad..3c3aa102 100644 --- a/app/Notifications/YouWereMentioned.php +++ b/app/Notifications/YouWereMentioned.php @@ -30,8 +30,8 @@ public function toMail(): MailMessage return (new MailMessage) ->subject(__('Nouvelle mention: :subject', ['subject' => $thread->subject()])) ->line(__(':name vous a mentionné dans le sujet :subject', ['name' => $this->reply->user?->name, 'subject' => $thread->subject()])) - ->action(__('Afficher'), url($thread->getPathUrl()."#reply-{$this->reply->id}")) - ->line(__('Merci d\'utiliser Laravel Cameroun!')); + ->action(__('Afficher'), url($thread->getPathUrl().('#reply-'.$this->reply->id))) + ->line(__("Merci d'utiliser Laravel Cameroun!")); } public function toArray(): array diff --git a/app/Observers/ArticleObserver.php b/app/Observers/ArticleObserver.php index 516621ea..dbd55beb 100644 --- a/app/Observers/ArticleObserver.php +++ b/app/Observers/ArticleObserver.php @@ -27,7 +27,7 @@ public function deleting(Article $article): void private function invalidateCaches(Article $article): void { - $cacheService = app(CacheInvalidationService::class); + $cacheService = resolve(CacheInvalidationService::class); Cache::forget('article.'.$article->id); diff --git a/app/Providers/AppServiceProvider.php b/app/Providers/AppServiceProvider.php index 36820b0f..eb368907 100644 --- a/app/Providers/AppServiceProvider.php +++ b/app/Providers/AppServiceProvider.php @@ -14,7 +14,6 @@ use App\View\Composers\ProfileUsersComposer; use App\View\Composers\TopContributorsComposer; use ArchTech\SEO\SEOManager; -use Carbon\Carbon; use Filament\Actions; use Filament\Support\Colors\Color; use Filament\Support\Enums\Width; @@ -49,9 +48,9 @@ public function boot(): void public function registerBladeDirective(): void { - Blade::directive('title', fn ($expression): string => ""); - Blade::directive('shareImage', fn ($expression): string => ""); - Blade::directive('canonical', fn ($expression): string => ""); + Blade::directive('title', fn (string $expression): string => sprintf('', $expression)); + Blade::directive('shareImage', fn (string $expression): string => sprintf('', $expression)); + Blade::directive('canonical', fn (string $expression): string => sprintf('', $expression)); } public function configureMacros(): void @@ -122,7 +121,7 @@ protected function registerLocaleDate(): void setlocale(LC_TIME, 'fr_FR', 'fr', 'FR', 'French', 'fr_FR.UTF-8'); setlocale(LC_ALL, 'fr_FR', 'fr', 'FR', 'French', 'fr_FR.UTF-8'); - Carbon::setLocale('fr'); + \Illuminate\Support\Facades\Date::setLocale('fr'); } protected function configureSeo(): void diff --git a/app/Services/CacheInvalidationService.php b/app/Services/CacheInvalidationService.php index 6e201aac..ed625e8f 100644 --- a/app/Services/CacheInvalidationService.php +++ b/app/Services/CacheInvalidationService.php @@ -1,39 +1,5 @@ - - Copyright © 2024-2025, Shopper Labs SARL. All rights reserved. - - Universy App™ is licensed under the Elastic License 2.0. For more details, - see https://github.com/shopperlabs/institute-app/blob/main/LICENSE. - - Notice: - - - You may not provide the software to third parties as a hosted or managed - service, where the service provides users with access to any substantial set of - the features or functionality of the software. - - You may not move, change, disable, or circumvent the license key functionality - in the software, and you may not remove or obscure any functionality in the - software that is protected by the license key. - - You may not alter, remove, or obscure any licensing, copyright, or other notices - of the licensor in the software. Any use of the licensor’s trademarks is subject - to applicable law. - - Shopper Labs SARL respects the intellectual property rights of others and expects the - same in return. Institute App™ are registered trademarks of - Shopper Labs SARL, and we are committed to enforcing and protecting our trademarks - vigorously. - - The software solution, including services, infrastructure, and code, is offered as a - Software as a Service (SaaS) by Shopper Labs SARL. - - Use of this software implies agreement to the license terms and conditions as stated - in the Elastic License 2.0. - - For more information or inquiries, please visit our website at - https://www.shopperlabs.co or contact us via email at contact@shopperlabs.co. - - -*/ - declare(strict_types=1); namespace App\Services; @@ -62,10 +28,10 @@ private function invalidateRedisPattern(string $pattern): void $connection = config('cache.stores.redis.connection', 'cache'); $keys = Redis::connection($connection)->keys($pattern.'*'); - if (! empty($keys)) { + if (filled($keys)) { Redis::connection($connection)->del($keys); } - } catch (Exception $e) { + } catch (Exception $exception) { $this->invalidateFallbackPattern(); } } diff --git a/app/Services/MediaCacheService.php b/app/Services/MediaCacheService.php index 33678fd2..d581d8e1 100644 --- a/app/Services/MediaCacheService.php +++ b/app/Services/MediaCacheService.php @@ -44,15 +44,15 @@ private function resolveMediaUrl(HasMedia $model, string $collection, ?string $c private function buildCacheKey(HasCachedMediaInterface $model, string $collection, ?string $conversion = null): string { - $suffix = filled($conversion) ? ".{$conversion}" : ''; + $suffix = filled($conversion) ? '.'.$conversion : ''; - return "{$model->getCacheKey($collection)}{$suffix}"; + return $model->getCacheKey($collection).$suffix; } private function flushCollectionCache(HasCachedMediaInterface $model, string $collection): void { $baseKey = $model->getCacheKey($collection); - app(CacheInvalidationService::class)->invalidateByPattern($baseKey); + resolve(CacheInvalidationService::class)->invalidateByPattern($baseKey); } } diff --git a/app/Spotlight/Article.php b/app/Spotlight/Article.php index 6c6651e8..1164e890 100644 --- a/app/Spotlight/Article.php +++ b/app/Spotlight/Article.php @@ -33,7 +33,7 @@ public function searchArticle(string $query): Collection { return ArticleModel::with('user') ->published() - ->where('title', 'like', "%{$query}%") + ->where('title', 'like', sprintf('%%%s%%', $query)) ->get() ->map(fn (ArticleModel $article): SpotlightSearchResult => new SpotlightSearchResult( // @phpstan-ignore-line $article->slug, diff --git a/app/Spotlight/Discussion.php b/app/Spotlight/Discussion.php index 9fdf74d9..f03103a0 100644 --- a/app/Spotlight/Discussion.php +++ b/app/Spotlight/Discussion.php @@ -32,7 +32,7 @@ public function dependencies(): SpotlightCommandDependencies public function searchDiscussion(string $query): Collection { return DiscussionModel::with('user') - ->where('title', 'like', "%{$query}%") + ->where('title', 'like', sprintf('%%%s%%', $query)) ->get() // @phpstan-ignore-next-line ->map(fn (DiscussionModel $discussion): SpotlightSearchResult => new SpotlightSearchResult( diff --git a/app/Spotlight/Sujet.php b/app/Spotlight/Sujet.php index 09e3fed5..fc8cf084 100644 --- a/app/Spotlight/Sujet.php +++ b/app/Spotlight/Sujet.php @@ -37,7 +37,7 @@ public function dependencies(): SpotlightCommandDependencies public function searchThread(string $query): Collection { return Thread::with('user') - ->where('title', 'like', "%{$query}%") + ->where('title', 'like', sprintf('%%%s%%', $query)) ->get() ->map(fn (Thread $thread): SpotlightSearchResult => new SpotlightSearchResult( $thread->slug, diff --git a/app/Spotlight/User.php b/app/Spotlight/User.php index c4cc8e8e..864f57bb 100644 --- a/app/Spotlight/User.php +++ b/app/Spotlight/User.php @@ -31,8 +31,8 @@ public function dependencies(): SpotlightCommandDependencies public function searchUser(string $query): Collection { - return UserModel::query()->where('name', 'like', "%{$query}%") - ->orWhere('username', 'like', "%{$query}%") + return UserModel::query()->where('name', 'like', sprintf('%%%s%%', $query)) + ->orWhere('username', 'like', sprintf('%%%s%%', $query)) ->get() ->map(fn (UserModel $user): SpotlightSearchResult => new SpotlightSearchResult( $user->id, diff --git a/app/Traits/FormatSocialAccount.php b/app/Traits/FormatSocialAccount.php index 3c3785cc..2ad9642e 100644 --- a/app/Traits/FormatSocialAccount.php +++ b/app/Traits/FormatSocialAccount.php @@ -15,7 +15,7 @@ public function formatGithubHandle(?string $userSocial): ?string $handle = $this->trimAndRemovePrefix($userSocial); if (str_contains($handle, 'github.com')) { - return substr($handle, strpos($handle, 'github.com/') + 11); + return mb_substr($handle, mb_strpos($handle, 'github.com/') + 11); } return $handle; @@ -30,7 +30,7 @@ public function formatLinkedinHandle(?string $userSocial): ?string $handle = $this->trimAndRemovePrefix($userSocial); if (str_contains($handle, 'linkedin.com/in/')) { - return substr($handle, strpos($handle, 'linkedin.com/in/') + 16); + return mb_substr($handle, mb_strpos($handle, 'linkedin.com/in/') + 16); } return $handle; @@ -41,18 +41,19 @@ public function formatTwitterHandle(?string $userSocial): ?string if (blank($userSocial)) { return null; } - if (str_starts_with(trim($userSocial), '@')) { - return substr(trim($userSocial), 1); + + if (str_starts_with(mb_trim($userSocial), '@')) { + return mb_substr(mb_trim($userSocial), 1); } $handle = $this->trimAndRemovePrefix($userSocial); if (str_contains($handle, 'twitter.com/') || str_contains($handle, 'x.com/')) { if (str_contains($handle, 'twitter.com/')) { - return substr($handle, strpos($handle, 'twitter.com/') + 12); + return mb_substr($handle, mb_strpos($handle, 'twitter.com/') + 12); } - return substr($handle, strpos($handle, 'x.com/') + 6); + return mb_substr($handle, mb_strpos($handle, 'x.com/') + 6); } return $handle; @@ -62,6 +63,6 @@ private function trimAndRemovePrefix(string $url): string { $url = (string) preg_replace('~https?://~', '', $url); - return trim((string) preg_replace('~www\.~', '', $url)); + return mb_trim((string) preg_replace('~www\.~', '', $url)); } } diff --git a/app/Traits/HasCachedMedia.php b/app/Traits/HasCachedMedia.php index 680d5cff..1f9cbec8 100644 --- a/app/Traits/HasCachedMedia.php +++ b/app/Traits/HasCachedMedia.php @@ -13,7 +13,7 @@ public function getCacheKey(string $collection): string { return sprintf( '%s.%d.media.%s', - strtolower(class_basename($this)), + mb_strtolower(class_basename($this)), $this->getKey(), $collection ); @@ -26,7 +26,7 @@ public function getCacheTtl(): DateTimeInterface public function flushMediaCache(?string $collection = null): void { - app(MediaCacheService::class)->flushCache($this, $collection); + resolve(MediaCacheService::class)->flushCache($this, $collection); } public function getMediaCollections(): array @@ -36,6 +36,6 @@ public function getMediaCollections(): array public function getCachedMediaUrl(string $collection, ?string $conversion = null): ?string { - return app(MediaCacheService::class)->getCachedMediaUrl($this, $collection, $conversion); + return resolve(MediaCacheService::class)->getCachedMediaUrl($this, $collection, $conversion); } } diff --git a/app/Traits/HasProfilePhoto.php b/app/Traits/HasProfilePhoto.php index 3bb4c9d9..0654caed 100644 --- a/app/Traits/HasProfilePhoto.php +++ b/app/Traits/HasProfilePhoto.php @@ -12,15 +12,6 @@ trait HasProfilePhoto { use HasCachedMedia; - protected function profilePhotoUrl(): Attribute - { - return Attribute::get(fn (): string => Cache::remember( - "user.{$this->id}.profile_photo_url", - now()->addYear(), - fn (): string => $this->resolveProfilePhotoUrl() - )); - } - public function getMediaCollections(): array { return ['avatar']; @@ -28,11 +19,25 @@ public function getMediaCollections(): array public function flushAvatarCache(): void { - Cache::forget("user.{$this->id}.profile_photo_url"); + Cache::forget(sprintf('user.%s.profile_photo_url', $this->id)); $this->flushMediaCache('avatar'); } + protected function profilePhotoUrl(): Attribute + { + return Attribute::get(fn (): string => Cache::remember( + sprintf('user.%s.profile_photo_url', $this->id), + now()->addYear(), + fn (): string => $this->resolveProfilePhotoUrl() + )); + } + + protected function defaultProfilePhotoUrl(): string + { + return 'https://ui-avatars.com/api/?name='.urlencode($this->name).'&color=065F46&background=D1FAE5'; + } + private function resolveProfilePhotoUrl(): string { if ($this->avatar_type === 'storage') { @@ -50,9 +55,4 @@ private function resolveProfilePhotoUrl(): string return $this->defaultProfilePhotoUrl(); } - - protected function defaultProfilePhotoUrl(): string - { - return 'https://ui-avatars.com/api/?name='.urlencode($this->name).'&color=065F46&background=D1FAE5'; - } } diff --git a/app/Traits/HasSocialite.php b/app/Traits/HasSocialite.php index 1a3d0e96..f0832a0a 100644 --- a/app/Traits/HasSocialite.php +++ b/app/Traits/HasSocialite.php @@ -29,9 +29,9 @@ protected function getSocialiteUser(string $provider): User protected function getAuthorizationFirst(string $provider): RedirectResponse|\Symfony\Component\HttpFoundation\RedirectResponse { $socialite = Socialite::driver($provider); - $scopes = config("services.{$provider}.scopes", false); - $with = config("services.{$provider}.with", false); - $fields = config("services.{$provider}.fields", false); + $scopes = config(sprintf('services.%s.scopes', $provider), false); + $with = config(sprintf('services.%s.with', $provider), false); + $fields = config(sprintf('services.%s.fields', $provider), false); if ($scopes) { $socialite->scopes($scopes); // @phpstan-ignore-line diff --git a/app/Traits/HasTags.php b/app/Traits/HasTags.php index 32aee32f..6a7cac00 100644 --- a/app/Traits/HasTags.php +++ b/app/Traits/HasTags.php @@ -29,14 +29,6 @@ public function removeTags(): void $this->unsetRelation('tags'); } - #[Scope] - protected function forTag(Builder $query, string $tag): void - { - $query->whereHas('tags', function ($query) use ($tag): void { - $query->where('tags.slug', $tag); - }); - } - /** * @return MorphToMany */ @@ -44,4 +36,12 @@ public function tags(): MorphToMany { return $this->morphToMany(Tag::class, 'taggable'); } + + #[Scope] + protected function forTag(Builder $query, string $tag): void + { + $query->whereHas('tags', function ($query) use ($tag): void { + $query->where('tags.slug', $tag); + }); + } } diff --git a/app/Traits/HasUsername.php b/app/Traits/HasUsername.php index 9e26cfde..86bd985e 100644 --- a/app/Traits/HasUsername.php +++ b/app/Traits/HasUsername.php @@ -8,19 +8,20 @@ trait HasUsername { - protected function username(): Attribute + public static function findByUsername(string $username): self { - return Attribute::set(fn (string $value): string => $this->generateUniqueUsername($value)); + return static::query()->where('username', $username)->firstOrFail(); } - public static function findByUsername(string $username): self + protected function username(): Attribute { - return static::query()->where('username', $username)->firstOrFail(); + return Attribute::set(fn (string $value): string => $this->generateUniqueUsername($value)); } private function generateUniqueUsername(string $value): string { - $username = $originalUsername = $value; + $username = $value; + $originalUsername = $value; $counter = 0; while ($this->usernameExists($username, $this->exists ? $this->id : null)) { diff --git a/app/Traits/HasUuid.php b/app/Traits/HasUuid.php index 28388d3d..fd84739a 100644 --- a/app/Traits/HasUuid.php +++ b/app/Traits/HasUuid.php @@ -9,6 +9,11 @@ trait HasUuid { + public static function findByUuidOrFail(UuidInterface $uuid): self + { + return static::where('uuid', $uuid->toString())->firstOrFail(); + } + public function uuid(): UuidInterface { return Uuid::fromString($this->uuid); @@ -23,9 +28,4 @@ public function getIncrementing(): bool { return false; } - - public static function findByUuidOrFail(UuidInterface $uuid): self - { - return static::where('uuid', $uuid->toString())->firstOrFail(); - } } diff --git a/app/Traits/RecordsActivity.php b/app/Traits/RecordsActivity.php index 755ed390..3c163f6e 100644 --- a/app/Traits/RecordsActivity.php +++ b/app/Traits/RecordsActivity.php @@ -11,6 +11,14 @@ trait RecordsActivity { + /** + * @return MorphMany + */ + public function activities(): MorphMany + { + return $this->morphMany(Activity::class, 'subject'); + } + protected static function bootRecordsActivity(): void { if (Auth::guest()) { @@ -52,14 +60,6 @@ protected function getActivityType(string $event): string { $type = mb_strtolower(new ReflectionClass($this)->getShortName()); - return "{$event}_{$type}"; - } - - /** - * @return MorphMany - */ - public function activities(): MorphMany - { - return $this->morphMany(Activity::class, 'subject'); + return sprintf('%s_%s', $event, $type); } } diff --git a/app/helpers.php b/app/helpers.php index b8c2dd2c..4f05be62 100644 --- a/app/helpers.php +++ b/app/helpers.php @@ -4,7 +4,6 @@ use App\Models\Discussion; use App\Models\Thread; -use Carbon\Carbon; use GrahamCampbell\Markdown\Facades\Markdown; use Illuminate\Support\Facades\Auth; use League\CommonMark\Output\RenderedContentInterface; @@ -15,7 +14,7 @@ */ function active(array $routes, string $activeClass = 'active', string $defaultClass = '', bool $condition = true): string { - return call_user_func_array([app('router'), 'is'], $routes) && $condition ? $activeClass : $defaultClass; + return call_user_func_array([resolve(Illuminate\Routing\Router::class), 'is'], $routes) && $condition ? $activeClass : $defaultClass; } } @@ -25,7 +24,7 @@ function active(array $routes, string $activeClass = 'active', string $defaultCl */ function is_active(string ...$routes): bool { - return (bool) call_user_func_array([app('router'), 'is'], $routes); + return (bool) call_user_func_array([resolve(Illuminate\Routing\Router::class), 'is'], $routes); } } @@ -60,7 +59,7 @@ function get_current_theme(): string */ function canonical(string $route, array $params = []): string { - $page = app('request')->get('page'); + $page = resolve('request')->get('page'); $params = array_merge($params, ['page' => $page !== 1 ? $page : null]); ksort($params); @@ -98,10 +97,10 @@ function route_to_reply_able(mixed $replyAble): string if (! function_exists('isHolidayWeek')) { function isHolidayWeek(): bool { - $now = Carbon::now(); + $now = Illuminate\Support\Facades\Date::now(); - $holidayStart = Carbon::createFromDate($now->year, 12, 21)->startOfDay(); - $holidayEnd = Carbon::createFromDate($now->year + 1, 1, 2)->endOfDay(); + $holidayStart = Illuminate\Support\Facades\Date::createFromDate($now->year, 12, 21)->startOfDay(); + $holidayEnd = Illuminate\Support\Facades\Date::createFromDate($now->year + 1, 1, 2)->endOfDay(); return $now->between($holidayStart, $holidayEnd); } diff --git a/boost.json b/boost.json new file mode 100644 index 00000000..7d6448c8 --- /dev/null +++ b/boost.json @@ -0,0 +1,17 @@ +{ + "agents": [ + "claude_code", + "gemini", + "phpstorm" + ], + "editors": [ + "claude_code", + "gemini", + "phpstorm", + "vscode" + ], + "guidelines": [ + "filament/filament" + ], + "sail": true +} diff --git a/composer.json b/composer.json index 01438ac7..985fa869 100644 --- a/composer.json +++ b/composer.json @@ -1,5 +1,5 @@ { - "name": "laravel/laravel", + "name": "laravelcm/laravelcm", "type": "project", "description": "The Laravel Framework.", "keywords": [ @@ -13,65 +13,66 @@ "php": "^8.4", "ext-fileinfo": "*", "ext-json": "*", - "archtechx/laravel-seo": "^0.10", - "awcodes/filament-badgeable-column": "^3.0", - "codeat3/blade-phosphor-icons": "^2.0", - "cyrildewit/eloquent-viewable": "^7.0", - "doctrine/dbal": "^4.2.1", - "filament/filament": "^4.0", - "filament/spatie-laravel-media-library-plugin": "^4.0", + "archtechx/laravel-seo": "^0.10.3", + "awcodes/filament-badgeable-column": "^3.0.1", + "codeat3/blade-phosphor-icons": "^2.3", + "cyrildewit/eloquent-viewable": "^7.0.6", + "filament/filament": "^4.3.1", + "filament/spatie-laravel-media-library-plugin": "^4.3.1", "gehrisandro/tailwind-merge-laravel": "^1.3", "graham-campbell/markdown": "^16.0", - "guzzlehttp/guzzle": "^7.7.0", + "guzzlehttp/guzzle": "^7.10.0", "internachi/modular": "^2.3", "jenssegers/agent": "^2.6.4", - "lara-zeus/spatie-translatable": "^1.0", + "lara-zeus/spatie-translatable": "^1.0.4", "laravel-notification-channels/telegram": "^6.0", - "laravel-notification-channels/twitter": "^8.1", - "laravel/framework": "^12.0", - "laravel/nightwatch": "^1.13", - "laravel/octane": "^2.12", - "laravel/socialite": "^5.6.3", - "laravel/tinker": "^2.8.1", - "laravelcm/database-migration": "*", + "laravel-notification-channels/twitter": "^8.3", + "laravel/framework": "^12.43.1", + "laravel/nightwatch": "^1.21.1", + "laravel/octane": "^2.13.3", + "laravel/socialite": "^5.24.0", + "laravel/tinker": "^2.10.2", + "laravelcm/database-migration": "^1.0", "laravelcm/gamify": "^1.1", - "laravelcm/laravel-subscriptions": "^1.3", - "laravelcm/livewire-slide-overs": "^1.0", - "league/flysystem-aws-s3-v3": "^3.0", - "livewire/volt": "^1.6", - "mckenziearts/blade-untitledui-icons": "^1.4", - "notchpay/notchpay-php": "^1.6", - "predis/predis": "^3.2", - "ramsey/uuid": "^4.7.4", - "spatie/laravel-data": "^4.10", - "spatie/laravel-feed": "^4.2.1", - "spatie/laravel-permission": "^6.10.0", - "spatie/laravel-sitemap": "^7.3", - "stevebauman/location": "^7.4.0", - "torchlight/torchlight-laravel": "^0.6", - "vormkracht10/filament-mails": "^3.0", - "wire-elements/modal": "^2.0", - "wire-elements/spotlight": "^2.0", - "yarri/link-finder": "^2.7.10", + "laravelcm/laravel-subscriptions": "^1.5", + "laravelcm/livewire-slide-overs": "^1.0.8", + "league/flysystem-aws-s3-v3": "^3.30.1", + "livewire/flux": "^2.10.2", + "livewire/flux-pro": "^2.10.2", + "livewire/volt": "^1.10.1", + "mckenziearts/filament-untitledui-icons": "^1.0", + "notchpay/notchpay-php": "^1.6.1", + "predis/predis": "^3.3", + "ramsey/uuid": "^4.9.2", + "spatie/laravel-data": "^4.18", + "spatie/laravel-feed": "^4.4.3", + "spatie/laravel-permission": "^6.24.0", + "spatie/laravel-sitemap": "^7.3.8", + "stevebauman/location": "^7.6.0", + "torchlight/torchlight-laravel": "^0.6.1", + "wire-elements/modal": "^2.0.13", + "wire-elements/spotlight": "^2.0.2", + "yarri/link-finder": "^2.7.12", "ysfkaya/filament-phone-input": "^4.0" }, "require-dev": { - "barryvdh/laravel-debugbar": "^3.14", - "driftingly/rector-laravel": "^2.0", - "fakerphp/faker": "^1.23.0", - "filament/upgrade": "^4.0", - "larastan/larastan": "^3.0", - "laravel/boost": "^1.1", - "laravel/breeze": "^2.0", - "laravel/pail": "^1.2", - "laravel/pint": "^1.18", - "laravel/sail": "^1.45", - "mockery/mockery": "^1.6.2", - "nunomaduro/collision": "^8.1", - "pestphp/pest": "^3.8", - "pestphp/pest-plugin-laravel": "^3.0", + "barryvdh/laravel-debugbar": "^3.16.2", + "driftingly/rector-laravel": "^2.1.8", + "fakerphp/faker": "^1.24.1", + "larastan/larastan": "^3.8.1", + "laravel/boost": "^1.8.6", + "laravel/breeze": "^2.3.8", + "laravel/pail": "^1.2.4", + "laravel/pint": "^1.26", + "laravel/sail": "^1.51.0", + "mockery/mockery": "^1.6.12", + "nunomaduro/collision": "^8.8.3", + "pestphp/pest": "^3.8.4", + "pestphp/pest-plugin-laravel": "^3.2", "pestphp/pest-plugin-livewire": "^3.0", - "spatie/test-time": "^1.3.2" + "pestphp/pest-plugin-type-coverage": "^3.6.1", + "rector/rector": "^2.2.14", + "spatie/test-time": "^1.3.3" }, "autoload": { "files": [ @@ -89,39 +90,61 @@ } }, "scripts": { + "dev:install": [ + "composer install", + "./vendor/bin/sail up -d", + "./vendor/bin/sail artisan octane:install --server=frankenphp", + "./vendor/bin/sail npm install --no-update-notifier", + "./vendor/bin/sail npm run build", + "./vendor/bin/sail stop" + ], + "dev:uninstall": "docker compose down --rmi all --volumes --remove-orphans", + "dev:run": [ + "Composer\\Config::disableProcessTimeout", + "./vendor/bin/sail up -d", + "./vendor/bin/sail npm config set update-notifier false", + "./vendor/bin/sail npx concurrently -c \"#505b92,#d32d21\" \"composer install\" \"npm install --no-update-notifier\" --names=composer,npm", + "./vendor/bin/sail npx concurrently -c \"#fb7185,#fdba74\" \"artisan pail --timeout=0\" \"npm run dev\" --names=logs,vite" + ], + "dev:stop": "./vendor/bin/sail down", "post-autoload-dump": [ "Illuminate\\Foundation\\ComposerScripts::postAutoloadDump", "@php artisan package:discover --ansi", "@php artisan filament:upgrade" ], + "post-update-cmd": [ + "@php artisan vendor:publish --tag=laravel-assets --ansi --force", + "@php artisan clear", + "@update:requirements", + "./vendor/bin/sail artisan boost:update --ansi" + ], "post-root-package-install": [ "@php -r \"file_exists('.env') || copy('.env.example', '.env');\"" ], "post-create-project-cmd": [ "@php artisan key:generate --ansi" ], - "dev:install": [ - "./vendor/bin/sail up -d", - "./vendor/bin/sail artisan migrate", - "./vendor/bin/sail npm install --no-update-notifier", - "./vendor/bin/sail stop" + "update:requirements": [ + "composer bump", + "npx npm-check-updates -u" ], - "dev:unsinstall": "docker compose down --rmi all --volumes --remove-orphans", - "dev:run": [ - "Composer\\Config::disableProcessTimeout", - "./vendor/bin/sail up -d", - "./vendor/bin/sail npm config set update-notifier false", - "./vendor/bin/sail npx concurrently -c \"#505b92,#d32d21\" \"composer install\" \"npm install --no-update-notifier\" --names=composer,npm", - "./vendor/bin/sail npx concurrently -c \"#93c5fd,#fb7185,#fdba74\" \"php artisan queue:listen --tries=1\" \"php artisan pail --timeout=0\" \"npm run dev\" --names=queue,logs,vite" + "lint": [ + "rector", + "pint --parallel", + "npm run lint" ], - "dev:stop": "./vendor/bin/sail down", - "lint": "./vendor/bin/pint", - "types": "phpstan analyse --memory-limit=-1", - "rector": "./vendor/bin/rector", - "rector:preview": "./vendor/bin/rector --dry-run", + "test:lint": [ + "pint --parallel --test", + "rector --dry-run", + "npm run test:lint" + ], + "test:unit": "pest --parallel --coverage --min=30.0", + "test:types": "phpstan analyse --memory-limit=-1", + "test:type-coverage": "pest --type-coverage --min=98 --memory-limit=-1", "test": [ - "Composer\\Config::disableProcessTimeout", - "./vendor/bin/sail artisan test --parallel --processes=4" + "@test:type-coverage", + "@test:types", + "@test:unit" ] }, "config": { @@ -134,6 +157,11 @@ } }, "repositories": [ + { + "name": "flux-pro", + "type": "composer", + "url": "https://composer.fluxui.dev" + }, { "type": "path", "url": "app-modules/*", diff --git a/composer.lock b/composer.lock index 4d79b0bb..82447843 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "e6a46905189ec7d3b819e5548e20989d", + "content-hash": "dc9a5146fb5d07d86bff8a1c4281d06d", "packages": [ { "name": "abraham/twitteroauth", @@ -70,16 +70,16 @@ }, { "name": "anourvalar/eloquent-serialize", - "version": "1.3.4", + "version": "1.3.5", "source": { "type": "git", "url": "https://github.com/AnourValar/eloquent-serialize.git", - "reference": "0934a98866e02b73e38696961a9d7984b834c9d9" + "reference": "1a7dead8d532657e5358f8f27c0349373517681e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/AnourValar/eloquent-serialize/zipball/0934a98866e02b73e38696961a9d7984b834c9d9", - "reference": "0934a98866e02b73e38696961a9d7984b834c9d9", + "url": "https://api.github.com/repos/AnourValar/eloquent-serialize/zipball/1a7dead8d532657e5358f8f27c0349373517681e", + "reference": "1a7dead8d532657e5358f8f27c0349373517681e", "shasum": "" }, "require": { @@ -130,9 +130,9 @@ ], "support": { "issues": "https://github.com/AnourValar/eloquent-serialize/issues", - "source": "https://github.com/AnourValar/eloquent-serialize/tree/1.3.4" + "source": "https://github.com/AnourValar/eloquent-serialize/tree/1.3.5" }, - "time": "2025-07-30T15:45:57+00:00" + "time": "2025-12-04T13:38:21+00:00" }, { "name": "archtechx/laravel-seo", @@ -321,16 +321,16 @@ }, { "name": "aws/aws-sdk-php", - "version": "3.363.3", + "version": "3.368.2", "source": { "type": "git", "url": "https://github.com/aws/aws-sdk-php.git", - "reference": "0ec2218d32e291b988b1602583032ca5d11f8e8d" + "reference": "96397db9a3fd0b5e6b3c52e363b6a55831a93b1d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/0ec2218d32e291b988b1602583032ca5d11f8e8d", - "reference": "0ec2218d32e291b988b1602583032ca5d11f8e8d", + "url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/96397db9a3fd0b5e6b3c52e363b6a55831a93b1d", + "reference": "96397db9a3fd0b5e6b3c52e363b6a55831a93b1d", "shasum": "" }, "require": { @@ -343,7 +343,8 @@ "guzzlehttp/psr7": "^2.4.5", "mtdowling/jmespath.php": "^2.8.0", "php": ">=8.1", - "psr/http-message": "^1.0 || ^2.0" + "psr/http-message": "^1.0 || ^2.0", + "symfony/filesystem": "^v6.4.3 || ^v7.1.0 || ^v8.0.0" }, "require-dev": { "andrewsville/php-token-reflection": "^1.4", @@ -354,13 +355,11 @@ "doctrine/cache": "~1.4", "ext-dom": "*", "ext-openssl": "*", - "ext-pcntl": "*", "ext-sockets": "*", - "phpunit/phpunit": "^5.6.3 || ^8.5 || ^9.5", + "phpunit/phpunit": "^9.6", "psr/cache": "^2.0 || ^3.0", "psr/simple-cache": "^2.0 || ^3.0", "sebastian/comparator": "^1.2.3 || ^4.0 || ^5.0", - "symfony/filesystem": "^v6.4.0 || ^v7.1.0", "yoast/phpunit-polyfills": "^2.0" }, "suggest": { @@ -368,6 +367,7 @@ "doctrine/cache": "To use the DoctrineCacheAdapter", "ext-curl": "To send requests using cURL", "ext-openssl": "Allows working with CloudFront private distributions and verifying received SNS messages", + "ext-pcntl": "To use client-side monitoring", "ext-sockets": "To use client-side monitoring" }, "type": "library", @@ -412,88 +412,9 @@ "support": { "forum": "https://github.com/aws/aws-sdk-php/discussions", "issues": "https://github.com/aws/aws-sdk-php/issues", - "source": "https://github.com/aws/aws-sdk-php/tree/3.363.3" + "source": "https://github.com/aws/aws-sdk-php/tree/3.368.2" }, - "time": "2025-11-26T19:05:22+00:00" - }, - { - "name": "backstage/laravel-mails", - "version": "v2.1.0", - "source": { - "type": "git", - "url": "https://github.com/backstagephp/laravel-mails.git", - "reference": "b590728f2295be781ea19abd0275783d1598ca5c" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/backstagephp/laravel-mails/zipball/b590728f2295be781ea19abd0275783d1598ca5c", - "reference": "b590728f2295be781ea19abd0275783d1598ca5c", - "shasum": "" - }, - "require": { - "illuminate/contracts": "^10.0 || ^11.0 || ^12.0", - "laravel/helpers": "^1.7.0", - "php": "^8.2", - "spatie/laravel-package-tools": "^1.15.0" - }, - "require-dev": { - "larastan/larastan": "^3.0", - "laravel-notification-channels/discord": "^1.6", - "laravel-notification-channels/telegram": "^4.0 || ^5.0 || ^6.0", - "laravel/pint": "^1.17.0", - "laravel/slack-notification-channel": "^2.5 || ^3.3.2", - "nunomaduro/collision": "^7.5.0|^8.4", - "orchestra/testbench": "^8.5.0|^9.4.0", - "pestphp/pest": "^3.0", - "pestphp/pest-plugin-laravel": "^3.0", - "phpstan/extension-installer": "^1.4", - "phpstan/phpstan-deprecation-rules": "^2.0", - "phpstan/phpstan-phpunit": "^2.0", - "phpunit/phpunit": "^11.0" - }, - "type": "library", - "extra": { - "laravel": { - "providers": [ - "Backstage\\Mails\\MailsServiceProvider" - ] - } - }, - "autoload": { - "psr-4": { - "Backstage\\Mails\\": "src", - "Backstage\\Mails\\Database\\Factories\\": "database/factories" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Mark van Eijk", - "email": "mark@vormkracht10.nl", - "role": "Developer" - } - ], - "description": "Laravel Mails can collect everything you might want to track about the mails that has been sent by your Laravel app.", - "homepage": "https://github.com/backstagephp/laravel-mails", - "keywords": [ - "backstagephp", - "laravel", - "laravel-mails" - ], - "support": { - "issues": "https://github.com/backstagephp/laravel-mails/issues", - "source": "https://github.com/backstagephp/laravel-mails/tree/v2.1.0" - }, - "funding": [ - { - "url": "https://github.com/vormkracht10", - "type": "github" - } - ], - "time": "2025-07-22T08:08:52+00:00" + "time": "2025-12-18T19:07:30+00:00" }, { "name": "blade-ui-kit/blade-heroicons", @@ -1006,16 +927,16 @@ }, { "name": "composer/ca-bundle", - "version": "1.5.9", + "version": "1.5.10", "source": { "type": "git", "url": "https://github.com/composer/ca-bundle.git", - "reference": "1905981ee626e6f852448b7aaa978f8666c5bc54" + "reference": "961a5e4056dd2e4a2eedcac7576075947c28bf63" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/composer/ca-bundle/zipball/1905981ee626e6f852448b7aaa978f8666c5bc54", - "reference": "1905981ee626e6f852448b7aaa978f8666c5bc54", + "url": "https://api.github.com/repos/composer/ca-bundle/zipball/961a5e4056dd2e4a2eedcac7576075947c28bf63", + "reference": "961a5e4056dd2e4a2eedcac7576075947c28bf63", "shasum": "" }, "require": { @@ -1062,7 +983,7 @@ "support": { "irc": "irc://irc.freenode.org/composer", "issues": "https://github.com/composer/ca-bundle/issues", - "source": "https://github.com/composer/ca-bundle/tree/1.5.9" + "source": "https://github.com/composer/ca-bundle/tree/1.5.10" }, "funding": [ { @@ -1074,7 +995,7 @@ "type": "github" } ], - "time": "2025-11-06T11:46:17+00:00" + "time": "2025-12-08T15:06:51+00:00" }, { "name": "composer/class-map-generator", @@ -1893,112 +1814,6 @@ }, "time": "2024-07-08T12:26:09+00:00" }, - { - "name": "doctrine/dbal", - "version": "4.4.1", - "source": { - "type": "git", - "url": "https://github.com/doctrine/dbal.git", - "reference": "3d544473fb93f5c25b483ea4f4ce99f8c4d9d44c" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/doctrine/dbal/zipball/3d544473fb93f5c25b483ea4f4ce99f8c4d9d44c", - "reference": "3d544473fb93f5c25b483ea4f4ce99f8c4d9d44c", - "shasum": "" - }, - "require": { - "doctrine/deprecations": "^1.1.5", - "php": "^8.2", - "psr/cache": "^1|^2|^3", - "psr/log": "^1|^2|^3" - }, - "require-dev": { - "doctrine/coding-standard": "14.0.0", - "fig/log-test": "^1", - "jetbrains/phpstorm-stubs": "2023.2", - "phpstan/phpstan": "2.1.30", - "phpstan/phpstan-phpunit": "2.0.7", - "phpstan/phpstan-strict-rules": "^2", - "phpunit/phpunit": "11.5.23", - "slevomat/coding-standard": "8.24.0", - "squizlabs/php_codesniffer": "4.0.0", - "symfony/cache": "^6.3.8|^7.0|^8.0", - "symfony/console": "^5.4|^6.3|^7.0|^8.0" - }, - "suggest": { - "symfony/console": "For helpful console commands such as SQL execution and import of files." - }, - "type": "library", - "autoload": { - "psr-4": { - "Doctrine\\DBAL\\": "src" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Guilherme Blanco", - "email": "guilhermeblanco@gmail.com" - }, - { - "name": "Roman Borschel", - "email": "roman@code-factory.org" - }, - { - "name": "Benjamin Eberlei", - "email": "kontakt@beberlei.de" - }, - { - "name": "Jonathan Wage", - "email": "jonwage@gmail.com" - } - ], - "description": "Powerful PHP database abstraction layer (DBAL) with many features for database schema introspection and management.", - "homepage": "https://www.doctrine-project.org/projects/dbal.html", - "keywords": [ - "abstraction", - "database", - "db2", - "dbal", - "mariadb", - "mssql", - "mysql", - "oci8", - "oracle", - "pdo", - "pgsql", - "postgresql", - "queryobject", - "sasql", - "sql", - "sqlite", - "sqlserver", - "sqlsrv" - ], - "support": { - "issues": "https://github.com/doctrine/dbal/issues", - "source": "https://github.com/doctrine/dbal/tree/4.4.1" - }, - "funding": [ - { - "url": "https://www.doctrine-project.org/sponsorship.html", - "type": "custom" - }, - { - "url": "https://www.patreon.com/phpdoctrine", - "type": "patreon" - }, - { - "url": "https://tidelift.com/funding/github/packagist/doctrine%2Fdbal", - "type": "tidelift" - } - ], - "time": "2025-12-04T10:11:03+00:00" - }, { "name": "doctrine/deprecations", "version": "1.1.5", @@ -2345,18 +2160,77 @@ ], "time": "2025-03-06T22:45:56+00:00" }, + { + "name": "filafly/filament-icons", + "version": "v2.0.1", + "source": { + "type": "git", + "url": "https://github.com/filafly/filament-icons.git", + "reference": "091b9098751f8c893fb8caa6eb6cf39e9a3a984c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/filafly/filament-icons/zipball/091b9098751f8c893fb8caa6eb6cf39e9a3a984c", + "reference": "091b9098751f8c893fb8caa6eb6cf39e9a3a984c", + "shasum": "" + }, + "require": { + "filament/filament": "^4.0", + "php": "^8.2" + }, + "require-dev": { + "laravel/pint": "^1.20", + "phpunit/phpunit": "^11.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Filafly\\Icons\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nolan Nordlund", + "email": "nolan@filafly.com", + "homepage": "https://www.filafly.com/", + "role": "Developer" + } + ], + "description": "Core package for managing Filament icon drivers via a common interface.", + "homepage": "https://filafly.com/plugins/filament-icons", + "keywords": [ + "Phosphor", + "carbon", + "filament", + "font awesome", + "iconoir", + "icons", + "laravel", + "replace", + "swap" + ], + "support": { + "issues": "https://github.com/filafly/filament-icons/issues", + "source": "https://github.com/filafly/filament-icons/tree/v2.0.1" + }, + "time": "2025-10-15T18:30:12+00:00" + }, { "name": "filament/actions", - "version": "v4.2.4", + "version": "v4.3.1", "source": { "type": "git", "url": "https://github.com/filamentphp/actions.git", - "reference": "43a0012caaca601e1cb3536d354a586ef8c3f318" + "reference": "8c436949d3fa5cc79a2aeb0b6d1741c5555e9d33" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/filamentphp/actions/zipball/43a0012caaca601e1cb3536d354a586ef8c3f318", - "reference": "43a0012caaca601e1cb3536d354a586ef8c3f318", + "url": "https://api.github.com/repos/filamentphp/actions/zipball/8c436949d3fa5cc79a2aeb0b6d1741c5555e9d33", + "reference": "8c436949d3fa5cc79a2aeb0b6d1741c5555e9d33", "shasum": "" }, "require": { @@ -2392,20 +2266,20 @@ "issues": "https://github.com/filamentphp/filament/issues", "source": "https://github.com/filamentphp/filament" }, - "time": "2025-11-28T11:21:07+00:00" + "time": "2025-12-09T09:55:17+00:00" }, { "name": "filament/filament", - "version": "v4.2.4", + "version": "v4.3.1", "source": { "type": "git", "url": "https://github.com/filamentphp/panels.git", - "reference": "b9a65067099683dfaace04043dfb8c5a50c19bfe" + "reference": "2387e41380a304c411d420b17e965e11a8e24711" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/filamentphp/panels/zipball/b9a65067099683dfaace04043dfb8c5a50c19bfe", - "reference": "b9a65067099683dfaace04043dfb8c5a50c19bfe", + "url": "https://api.github.com/repos/filamentphp/panels/zipball/2387e41380a304c411d420b17e965e11a8e24711", + "reference": "2387e41380a304c411d420b17e965e11a8e24711", "shasum": "" }, "require": { @@ -2449,20 +2323,20 @@ "issues": "https://github.com/filamentphp/filament/issues", "source": "https://github.com/filamentphp/filament" }, - "time": "2025-11-28T11:21:48+00:00" + "time": "2025-12-09T09:56:39+00:00" }, { "name": "filament/forms", - "version": "v4.2.4", + "version": "v4.3.1", "source": { "type": "git", "url": "https://github.com/filamentphp/forms.git", - "reference": "4772a165a16cb9ea5da346fc3ba0ffca5b47a53b" + "reference": "6f4843f45906e0583997d2543fb747e07720a774" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/filamentphp/forms/zipball/4772a165a16cb9ea5da346fc3ba0ffca5b47a53b", - "reference": "4772a165a16cb9ea5da346fc3ba0ffca5b47a53b", + "url": "https://api.github.com/repos/filamentphp/forms/zipball/6f4843f45906e0583997d2543fb747e07720a774", + "reference": "6f4843f45906e0583997d2543fb747e07720a774", "shasum": "" }, "require": { @@ -2499,11 +2373,11 @@ "issues": "https://github.com/filamentphp/filament/issues", "source": "https://github.com/filamentphp/filament" }, - "time": "2025-11-28T11:21:32+00:00" + "time": "2025-12-09T09:53:27+00:00" }, { "name": "filament/infolists", - "version": "v4.2.4", + "version": "v4.3.1", "source": { "type": "git", "url": "https://github.com/filamentphp/infolists.git", @@ -2548,7 +2422,7 @@ }, { "name": "filament/notifications", - "version": "v4.2.4", + "version": "v4.3.1", "source": { "type": "git", "url": "https://github.com/filamentphp/notifications.git", @@ -2595,16 +2469,16 @@ }, { "name": "filament/query-builder", - "version": "v4.2.4", + "version": "v4.3.1", "source": { "type": "git", "url": "https://github.com/filamentphp/query-builder.git", - "reference": "82aa18f09039d015a1d1b9a1b8efb9625fe42129" + "reference": "2f7e138801e7630c56a7588651497a7f468a9149" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/filamentphp/query-builder/zipball/82aa18f09039d015a1d1b9a1b8efb9625fe42129", - "reference": "82aa18f09039d015a1d1b9a1b8efb9625fe42129", + "url": "https://api.github.com/repos/filamentphp/query-builder/zipball/2f7e138801e7630c56a7588651497a7f468a9149", + "reference": "2f7e138801e7630c56a7588651497a7f468a9149", "shasum": "" }, "require": { @@ -2637,20 +2511,20 @@ "issues": "https://github.com/filamentphp/filament/issues", "source": "https://github.com/filamentphp/filament" }, - "time": "2025-11-28T11:19:23+00:00" + "time": "2025-12-09T09:54:30+00:00" }, { "name": "filament/schemas", - "version": "v4.2.4", + "version": "v4.3.1", "source": { "type": "git", "url": "https://github.com/filamentphp/schemas.git", - "reference": "79fa31a8f691f542471a603e754f941f7d9ad09d" + "reference": "02ec53daed03d2feae75832db0920c4357079133" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/filamentphp/schemas/zipball/79fa31a8f691f542471a603e754f941f7d9ad09d", - "reference": "79fa31a8f691f542471a603e754f941f7d9ad09d", + "url": "https://api.github.com/repos/filamentphp/schemas/zipball/02ec53daed03d2feae75832db0920c4357079133", + "reference": "02ec53daed03d2feae75832db0920c4357079133", "shasum": "" }, "require": { @@ -2682,20 +2556,20 @@ "issues": "https://github.com/filamentphp/filament/issues", "source": "https://github.com/filamentphp/filament" }, - "time": "2025-11-28T11:21:50+00:00" + "time": "2025-12-05T14:53:55+00:00" }, { "name": "filament/spatie-laravel-media-library-plugin", - "version": "v4.2.4", + "version": "v4.3.1", "source": { "type": "git", "url": "https://github.com/filamentphp/spatie-laravel-media-library-plugin.git", - "reference": "220d1ad5f6c5a52dcf9e45eca6461f9a93ae8be2" + "reference": "73748df28a9c2e8c34d2c02f9314c330602e1830" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/filamentphp/spatie-laravel-media-library-plugin/zipball/220d1ad5f6c5a52dcf9e45eca6461f9a93ae8be2", - "reference": "220d1ad5f6c5a52dcf9e45eca6461f9a93ae8be2", + "url": "https://api.github.com/repos/filamentphp/spatie-laravel-media-library-plugin/zipball/73748df28a9c2e8c34d2c02f9314c330602e1830", + "reference": "73748df28a9c2e8c34d2c02f9314c330602e1830", "shasum": "" }, "require": { @@ -2719,20 +2593,20 @@ "issues": "https://github.com/filamentphp/filament/issues", "source": "https://github.com/filamentphp/filament" }, - "time": "2025-11-28T11:22:08+00:00" + "time": "2025-12-09T09:54:02+00:00" }, { "name": "filament/support", - "version": "v4.2.4", + "version": "v4.3.1", "source": { "type": "git", "url": "https://github.com/filamentphp/support.git", - "reference": "50d19b69fecc0636485c0329b9bd35136066ef8c" + "reference": "7c644018cc5c9a74503039ecf7ff10f340123a8c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/filamentphp/support/zipball/50d19b69fecc0636485c0329b9bd35136066ef8c", - "reference": "50d19b69fecc0636485c0329b9bd35136066ef8c", + "url": "https://api.github.com/repos/filamentphp/support/zipball/7c644018cc5c9a74503039ecf7ff10f340123a8c", + "reference": "7c644018cc5c9a74503039ecf7ff10f340123a8c", "shasum": "" }, "require": { @@ -2777,20 +2651,20 @@ "issues": "https://github.com/filamentphp/filament/issues", "source": "https://github.com/filamentphp/filament" }, - "time": "2025-11-28T11:20:04+00:00" + "time": "2025-12-05T14:53:38+00:00" }, { "name": "filament/tables", - "version": "v4.2.4", + "version": "v4.3.1", "source": { "type": "git", "url": "https://github.com/filamentphp/tables.git", - "reference": "7408f290bda5e908fbe010ba20ab59a01e7e932f" + "reference": "93227775fff27b0662c5f82e2e7ca6ad20cdf42c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/filamentphp/tables/zipball/7408f290bda5e908fbe010ba20ab59a01e7e932f", - "reference": "7408f290bda5e908fbe010ba20ab59a01e7e932f", + "url": "https://api.github.com/repos/filamentphp/tables/zipball/93227775fff27b0662c5f82e2e7ca6ad20cdf42c", + "reference": "93227775fff27b0662c5f82e2e7ca6ad20cdf42c", "shasum": "" }, "require": { @@ -2823,20 +2697,20 @@ "issues": "https://github.com/filamentphp/filament/issues", "source": "https://github.com/filamentphp/filament" }, - "time": "2025-11-28T11:20:04+00:00" + "time": "2025-12-09T09:54:45+00:00" }, { "name": "filament/widgets", - "version": "v4.2.4", + "version": "v4.3.1", "source": { "type": "git", "url": "https://github.com/filamentphp/widgets.git", - "reference": "495409437dfcd524313dcd6d605b97298fc487b2" + "reference": "555102cf4aea7f24977d24dd36f4bad0f7be528c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/filamentphp/widgets/zipball/495409437dfcd524313dcd6d605b97298fc487b2", - "reference": "495409437dfcd524313dcd6d605b97298fc487b2", + "url": "https://api.github.com/repos/filamentphp/widgets/zipball/555102cf4aea7f24977d24dd36f4bad0f7be528c", + "reference": "555102cf4aea7f24977d24dd36f4bad0f7be528c", "shasum": "" }, "require": { @@ -2867,7 +2741,7 @@ "issues": "https://github.com/filamentphp/filament/issues", "source": "https://github.com/filamentphp/filament" }, - "time": "2025-11-28T11:22:05+00:00" + "time": "2025-12-09T09:56:57+00:00" }, { "name": "firebase/php-jwt", @@ -3206,16 +3080,16 @@ }, { "name": "giggsey/libphonenumber-for-php-lite", - "version": "9.0.19", + "version": "9.0.21", "source": { "type": "git", "url": "https://github.com/giggsey/libphonenumber-for-php-lite.git", - "reference": "b8b7fcb1a78c1423d633d28fc79297c246686d1b" + "reference": "ab593d0ced185c149459d283a770d842662dd54e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/giggsey/libphonenumber-for-php-lite/zipball/b8b7fcb1a78c1423d633d28fc79297c246686d1b", - "reference": "b8b7fcb1a78c1423d633d28fc79297c246686d1b", + "url": "https://api.github.com/repos/giggsey/libphonenumber-for-php-lite/zipball/ab593d0ced185c149459d283a770d842662dd54e", + "reference": "ab593d0ced185c149459d283a770d842662dd54e", "shasum": "" }, "require": { @@ -3280,7 +3154,7 @@ "issues": "https://github.com/giggsey/libphonenumber-for-php-lite/issues", "source": "https://github.com/giggsey/libphonenumber-for-php-lite" }, - "time": "2025-11-20T18:22:51+00:00" + "time": "2025-12-18T14:36:21+00:00" }, { "name": "graham-campbell/markdown", @@ -4042,21 +3916,21 @@ }, { "name": "justinrainbow/json-schema", - "version": "6.6.2", + "version": "6.6.3", "source": { "type": "git", "url": "https://github.com/jsonrainbow/json-schema.git", - "reference": "3c25fe750c1599716ef26aa997f7c026cee8c4b7" + "reference": "134e98916fa2f663afa623970af345cd788e8967" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/jsonrainbow/json-schema/zipball/3c25fe750c1599716ef26aa997f7c026cee8c4b7", - "reference": "3c25fe750c1599716ef26aa997f7c026cee8c4b7", + "url": "https://api.github.com/repos/jsonrainbow/json-schema/zipball/134e98916fa2f663afa623970af345cd788e8967", + "reference": "134e98916fa2f663afa623970af345cd788e8967", "shasum": "" }, "require": { "ext-json": "*", - "marc-mabe/php-enum": "^4.0", + "marc-mabe/php-enum": "^4.4", "php": "^7.2 || ^8.0" }, "require-dev": { @@ -4111,22 +3985,22 @@ ], "support": { "issues": "https://github.com/jsonrainbow/json-schema/issues", - "source": "https://github.com/jsonrainbow/json-schema/tree/6.6.2" + "source": "https://github.com/jsonrainbow/json-schema/tree/6.6.3" }, - "time": "2025-11-28T15:24:03+00:00" + "time": "2025-12-02T10:21:33+00:00" }, { "name": "kirschbaum-development/eloquent-power-joins", - "version": "4.2.10", + "version": "4.2.11", "source": { "type": "git", "url": "https://github.com/kirschbaum-development/eloquent-power-joins.git", - "reference": "ccda351a75701f5b0a6f94586d9a40f1114302b4" + "reference": "0e3e3372992e4bf82391b3c7b84b435c3db73588" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/kirschbaum-development/eloquent-power-joins/zipball/ccda351a75701f5b0a6f94586d9a40f1114302b4", - "reference": "ccda351a75701f5b0a6f94586d9a40f1114302b4", + "url": "https://api.github.com/repos/kirschbaum-development/eloquent-power-joins/zipball/0e3e3372992e4bf82391b3c7b84b435c3db73588", + "reference": "0e3e3372992e4bf82391b3c7b84b435c3db73588", "shasum": "" }, "require": { @@ -4174,9 +4048,9 @@ ], "support": { "issues": "https://github.com/kirschbaum-development/eloquent-power-joins/issues", - "source": "https://github.com/kirschbaum-development/eloquent-power-joins/tree/4.2.10" + "source": "https://github.com/kirschbaum-development/eloquent-power-joins/tree/4.2.11" }, - "time": "2025-11-13T14:57:49+00:00" + "time": "2025-12-17T00:37:48+00:00" }, { "name": "kylewm/brevity", @@ -4719,63 +4593,6 @@ }, "time": "2025-12-16T18:53:08+00:00" }, - { - "name": "laravel/helpers", - "version": "v1.8.2", - "source": { - "type": "git", - "url": "https://github.com/laravel/helpers.git", - "reference": "98499eea4c1cca76fb0fb37ed365a468773daf0a" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/laravel/helpers/zipball/98499eea4c1cca76fb0fb37ed365a468773daf0a", - "reference": "98499eea4c1cca76fb0fb37ed365a468773daf0a", - "shasum": "" - }, - "require": { - "illuminate/support": "~5.8.0|^6.0|^7.0|^8.0|^9.0|^10.0|^11.0|^12.0", - "php": "^7.2.0|^8.0" - }, - "require-dev": { - "phpstan/phpstan": "^1.10", - "phpunit/phpunit": "^7.0|^8.0|^9.0|^10.0|^11.0|^12.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.x-dev" - } - }, - "autoload": { - "files": [ - "src/helpers.php" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Taylor Otwell", - "email": "taylor@laravel.com" - }, - { - "name": "Dries Vints", - "email": "dries@laravel.com" - } - ], - "description": "Provides backwards compatibility for helpers in the latest Laravel release.", - "keywords": [ - "helpers", - "laravel" - ], - "support": { - "source": "https://github.com/laravel/helpers/tree/v1.8.2" - }, - "time": "2025-11-25T14:46:28+00:00" - }, { "name": "laravel/nightwatch", "version": "v1.21.1", @@ -5224,7 +5041,7 @@ "dist": { "type": "path", "url": "app-modules/database-migration", - "reference": "b02428e7b5250a27c88966f0e131b900431f9e26" + "reference": "df9a1bcf3f53cab40d1387bae66cde1b6e0534bc" }, "require": { "illuminate/support": "^11.0|^12.0", @@ -5244,11 +5061,7 @@ }, "autoload": { "psr-4": { - "Laravelcm\\DatabaseMigration\\": "src/" - } - }, - "autoload-dev": { - "psr-4": { + "Laravelcm\\DatabaseMigration\\": "src/", "Laravelcm\\DatabaseMigration\\Tests\\": "tests/" } }, @@ -5267,9 +5080,10 @@ "dist": { "type": "path", "url": "app-modules/gamify", - "reference": "343936faa71e1f1cf7ccc207fc7c78a6c4f0a6a0" + "reference": "b41769f219151b7f6b9315e18adc657e09c963de" }, "require": { + "illuminate/support": "^11.0|^12.0", "php": "^8.4" }, "require-dev": { @@ -5288,17 +5102,13 @@ "psr-4": { "Laravelcm\\Gamify\\": "src/", "Laravelcm\\Gamify\\Database\\Factories\\": "database/factories/", - "Laravelcm\\Gamify\\Database\\Seeders\\": "database/seeders/" + "Laravelcm\\Gamify\\Database\\Seeders\\": "database/seeders/", + "Laravelcm\\Gamify\\Tests\\": "tests/" }, "files": [ "src/helpers.php" ] }, - "autoload-dev": { - "psr-4": { - "Laravelcm\\Gamify\\Tests\\": "tests/" - } - }, "license": [ "proprietary" ], @@ -6152,20 +5962,20 @@ }, { "name": "league/uri-components", - "version": "7.6.0", + "version": "7.7.0", "source": { "type": "git", "url": "https://github.com/thephpleague/uri-components.git", - "reference": "ffa1215dbee72ee4b7bc08d983d25293812456c2" + "reference": "005f8693ce8c1f16f80e88a05cbf08da04c1c374" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/thephpleague/uri-components/zipball/ffa1215dbee72ee4b7bc08d983d25293812456c2", - "reference": "ffa1215dbee72ee4b7bc08d983d25293812456c2", + "url": "https://api.github.com/repos/thephpleague/uri-components/zipball/005f8693ce8c1f16f80e88a05cbf08da04c1c374", + "reference": "005f8693ce8c1f16f80e88a05cbf08da04c1c374", "shasum": "" }, "require": { - "league/uri": "^7.6", + "league/uri": "^7.7", "php": "^8.1" }, "suggest": { @@ -6225,7 +6035,7 @@ "docs": "https://uri.thephpleague.com", "forum": "https://thephpleague.slack.com", "issues": "https://github.com/thephpleague/uri-src/issues", - "source": "https://github.com/thephpleague/uri-components/tree/7.6.0" + "source": "https://github.com/thephpleague/uri-components/tree/7.7.0" }, "funding": [ { @@ -6233,7 +6043,7 @@ "type": "github" } ], - "time": "2025-11-18T12:17:23+00:00" + "time": "2025-12-07T16:02:56+00:00" }, { "name": "league/uri-interfaces", @@ -6319,18 +6129,157 @@ ], "time": "2025-12-07T16:03:21+00:00" }, + { + "name": "livewire/flux", + "version": "v2.10.2", + "source": { + "type": "git", + "url": "https://github.com/livewire/flux.git", + "reference": "e7a93989788429bb6c0a908a056d22ea3a6c7975" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/livewire/flux/zipball/e7a93989788429bb6c0a908a056d22ea3a6c7975", + "reference": "e7a93989788429bb6c0a908a056d22ea3a6c7975", + "shasum": "" + }, + "require": { + "illuminate/console": "^10.0|^11.0|^12.0", + "illuminate/support": "^10.0|^11.0|^12.0", + "illuminate/view": "^10.0|^11.0|^12.0", + "laravel/prompts": "^0.1|^0.2|^0.3", + "livewire/livewire": "^3.7.3|^4.0", + "php": "^8.1", + "symfony/console": "^6.0|^7.0" + }, + "conflict": { + "livewire/blaze": "<1.0.0" + }, + "type": "library", + "extra": { + "laravel": { + "aliases": { + "Flux": "Flux\\Flux" + }, + "providers": [ + "Flux\\FluxServiceProvider" + ] + } + }, + "autoload": { + "psr-4": { + "Flux\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "proprietary" + ], + "authors": [ + { + "name": "Caleb Porzio", + "email": "calebporzio@gmail.com" + } + ], + "description": "The official UI component library for Livewire.", + "keywords": [ + "components", + "flux", + "laravel", + "livewire", + "ui" + ], + "support": { + "issues": "https://github.com/livewire/flux/issues", + "source": "https://github.com/livewire/flux/tree/v2.10.2" + }, + "time": "2025-12-19T02:11:45+00:00" + }, + { + "name": "livewire/flux-pro", + "version": "2.10.2", + "dist": { + "type": "zip", + "url": "https://composer.fluxui.dev/download/a0a0798f-1cf8-4999-8f57-8688c21f2d59/flux-pro-2.10.2.zip", + "reference": "9440435e467c4bb775efbc2c510ec7dea61e17d7", + "shasum": "177bf08ae75c628c96a1a3a4fd1e788b1b798e1d" + }, + "require": { + "illuminate/console": "^10.0|^11.0|^12.0", + "illuminate/support": "^10.0|^11.0|^12.0", + "illuminate/view": "^10.0|^11.0|^12.0", + "laravel/prompts": "^0.1.24|^0.2|^0.3", + "livewire/flux": "2.10.2|dev-main", + "livewire/livewire": "^3.7.3|^4.0", + "php": "^8.1", + "symfony/console": "^6.0|^7.0" + }, + "require-dev": { + "livewire/volt": "*", + "orchestra/testbench": "^10.8" + }, + "type": "library", + "extra": { + "laravel": { + "aliases": { + "Flux": "FluxPro\\FluxPro" + }, + "providers": [ + "FluxPro\\FluxProServiceProvider" + ] + } + }, + "autoload": { + "files": [ + "src/helpers.php" + ], + "psr-4": { + "FluxPro\\": "src/" + } + }, + "autoload-dev": { + "psr-4": { + "App\\": "workbench/app/" + } + }, + "scripts": { + "serve": [ + "Composer\\Config::disableProcessTimeout", + "@php vendor/bin/testbench workbench:build --ansi", + "@php vendor/bin/testbench serve --port 3000 --ansi" + ] + }, + "license": [ + "proprietary" + ], + "authors": [ + { + "name": "Caleb Porzio", + "email": "calebporzio@gmail.com" + } + ], + "description": "The pro version of Flux, the official UI component library for Livewire.", + "keywords": [ + "components", + "flux", + "laravel", + "livewire", + "ui" + ], + "time": "2025-12-19T02:22:27+00:00" + }, { "name": "livewire/livewire", - "version": "v3.7.0", + "version": "v3.7.3", "source": { "type": "git", "url": "https://github.com/livewire/livewire.git", - "reference": "f5f9efe6d5a7059116bd695a89d95ceedf33f3cb" + "reference": "a5384df9fbd3eaf02e053bc49aabc8ace293fc1c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/livewire/livewire/zipball/f5f9efe6d5a7059116bd695a89d95ceedf33f3cb", - "reference": "f5f9efe6d5a7059116bd695a89d95ceedf33f3cb", + "url": "https://api.github.com/repos/livewire/livewire/zipball/a5384df9fbd3eaf02e053bc49aabc8ace293fc1c", + "reference": "a5384df9fbd3eaf02e053bc49aabc8ace293fc1c", "shasum": "" }, "require": { @@ -6385,7 +6334,7 @@ "description": "A front-end framework for Laravel.", "support": { "issues": "https://github.com/livewire/livewire/issues", - "source": "https://github.com/livewire/livewire/tree/v3.7.0" + "source": "https://github.com/livewire/livewire/tree/v3.7.3" }, "funding": [ { @@ -6393,7 +6342,7 @@ "type": "github" } ], - "time": "2025-11-12T17:58:16+00:00" + "time": "2025-12-19T02:00:29+00:00" }, { "name": "livewire/volt", @@ -6468,16 +6417,16 @@ }, { "name": "maennchen/zipstream-php", - "version": "3.2.0", + "version": "3.2.1", "source": { "type": "git", "url": "https://github.com/maennchen/ZipStream-PHP.git", - "reference": "9712d8fa4cdf9240380b01eb4be55ad8dcf71416" + "reference": "682f1098a8fddbaf43edac2306a691c7ad508ec5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/maennchen/ZipStream-PHP/zipball/9712d8fa4cdf9240380b01eb4be55ad8dcf71416", - "reference": "9712d8fa4cdf9240380b01eb4be55ad8dcf71416", + "url": "https://api.github.com/repos/maennchen/ZipStream-PHP/zipball/682f1098a8fddbaf43edac2306a691c7ad508ec5", + "reference": "682f1098a8fddbaf43edac2306a691c7ad508ec5", "shasum": "" }, "require": { @@ -6488,7 +6437,7 @@ "require-dev": { "brianium/paratest": "^7.7", "ext-zip": "*", - "friendsofphp/php-cs-fixer": "^3.16", + "friendsofphp/php-cs-fixer": "^3.86", "guzzlehttp/guzzle": "^7.5", "mikey179/vfsstream": "^1.6", "php-coveralls/php-coveralls": "^2.5", @@ -6534,7 +6483,7 @@ ], "support": { "issues": "https://github.com/maennchen/ZipStream-PHP/issues", - "source": "https://github.com/maennchen/ZipStream-PHP/tree/3.2.0" + "source": "https://github.com/maennchen/ZipStream-PHP/tree/3.2.1" }, "funding": [ { @@ -6542,7 +6491,7 @@ "type": "github" } ], - "time": "2025-07-17T11:15:13+00:00" + "time": "2025-12-10T09:58:31+00:00" }, { "name": "marc-mabe/php-enum", @@ -6832,7 +6781,65 @@ }, "autoload": { "psr-4": { - "Mckenziearts\\BladeUntitledUIIcons\\": "src" + "Mckenziearts\\BladeUntitledUIIcons\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Arthur Monney", + "homepage": "https://arthurmonney.me" + } + ], + "description": "A package to easily make use of UntitledUI icons in your Laravel Blade views.", + "homepage": "https://github.com/mckenziearts/blade-untitledui-icons", + "keywords": [ + "Untitled UI", + "blade", + "laravel" + ], + "support": { + "issues": "https://github.com/mckenziearts/blade-untitledui-icons/issues", + "source": "https://github.com/mckenziearts/blade-untitledui-icons/tree/v1.4.0" + }, + "funding": [ + { + "url": "https://github.com/mckenziearts", + "type": "github" + } + ], + "time": "2025-02-25T12:25:58+00:00" + }, + { + "name": "mckenziearts/filament-untitledui-icons", + "version": "v1.0", + "source": { + "type": "git", + "url": "https://github.com/mckenziearts/filament-untitledui-icons.git", + "reference": "9490462c2c78a77c748e18b289edbd7dab8082fb" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/mckenziearts/filament-untitledui-icons/zipball/9490462c2c78a77c748e18b289edbd7dab8082fb", + "reference": "9490462c2c78a77c748e18b289edbd7dab8082fb", + "shasum": "" + }, + "require": { + "filafly/filament-icons": "^2.0", + "filament/filament": "^4.0", + "mckenziearts/blade-untitledui-icons": "^1.4", + "php": "^8.2" + }, + "require-dev": { + "laravel/pint": "^1.22" + }, + "type": "library", + "autoload": { + "psr-4": { + "Mckenziearts\\Icons\\Untitledui\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", @@ -6842,27 +6849,22 @@ "authors": [ { "name": "Arthur Monney", - "homepage": "https://arthurmonney.me" + "email": "monneylobe@gmail.com", + "role": "Developer" } ], - "description": "A package to easily make use of UntitledUI icons in your Laravel Blade views.", - "homepage": "https://github.com/mckenziearts/blade-untitledui-icons", + "description": "Untitledui icon pack for Filament Icons", "keywords": [ - "Untitled UI", - "blade", + "Untitledui", + "filament", + "icons", "laravel" ], "support": { - "issues": "https://github.com/mckenziearts/blade-untitledui-icons/issues", - "source": "https://github.com/mckenziearts/blade-untitledui-icons/tree/v1.4.0" + "issues": "https://github.com/mckenziearts/filament-untitledui-icons/issues", + "source": "https://github.com/mckenziearts/filament-untitledui-icons/tree/v1.0" }, - "funding": [ - { - "url": "https://github.com/mckenziearts", - "type": "github" - } - ], - "time": "2025-02-25T12:25:58+00:00" + "time": "2025-12-18T22:31:54+00:00" }, { "name": "mobiledetect/mobiledetectlib", @@ -7482,16 +7484,16 @@ }, { "name": "nikic/php-parser", - "version": "v5.6.2", + "version": "v5.7.0", "source": { "type": "git", "url": "https://github.com/nikic/PHP-Parser.git", - "reference": "3a454ca033b9e06b63282ce19562e892747449bb" + "reference": "dca41cd15c2ac9d055ad70dbfd011130757d1f82" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/3a454ca033b9e06b63282ce19562e892747449bb", - "reference": "3a454ca033b9e06b63282ce19562e892747449bb", + "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/dca41cd15c2ac9d055ad70dbfd011130757d1f82", + "reference": "dca41cd15c2ac9d055ad70dbfd011130757d1f82", "shasum": "" }, "require": { @@ -7534,9 +7536,9 @@ ], "support": { "issues": "https://github.com/nikic/PHP-Parser/issues", - "source": "https://github.com/nikic/PHP-Parser/tree/v5.6.2" + "source": "https://github.com/nikic/PHP-Parser/tree/v5.7.0" }, - "time": "2025-10-21T19:32:17+00:00" + "time": "2025-12-06T11:56:16+00:00" }, { "name": "notchpay/notchpay-php", @@ -8645,55 +8647,6 @@ ], "time": "2025-07-05T09:03:01+00:00" }, - { - "name": "psr/cache", - "version": "3.0.0", - "source": { - "type": "git", - "url": "https://github.com/php-fig/cache.git", - "reference": "aa5030cfa5405eccfdcb1083ce040c2cb8d253bf" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/php-fig/cache/zipball/aa5030cfa5405eccfdcb1083ce040c2cb8d253bf", - "reference": "aa5030cfa5405eccfdcb1083ce040c2cb8d253bf", - "shasum": "" - }, - "require": { - "php": ">=8.0.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.0.x-dev" - } - }, - "autoload": { - "psr-4": { - "Psr\\Cache\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "PHP-FIG", - "homepage": "https://www.php-fig.org/" - } - ], - "description": "Common interface for caching libraries", - "keywords": [ - "cache", - "psr", - "psr-6" - ], - "support": { - "source": "https://github.com/php-fig/cache/tree/3.0.0" - }, - "time": "2021-02-03T23:26:27+00:00" - }, { "name": "psr/clock", "version": "1.0.0", @@ -9108,16 +9061,16 @@ }, { "name": "psy/psysh", - "version": "v0.12.15", + "version": "v0.12.18", "source": { "type": "git", "url": "https://github.com/bobthecow/psysh.git", - "reference": "38953bc71491c838fcb6ebcbdc41ab7483cd549c" + "reference": "ddff0ac01beddc251786fe70367cd8bbdb258196" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/bobthecow/psysh/zipball/38953bc71491c838fcb6ebcbdc41ab7483cd549c", - "reference": "38953bc71491c838fcb6ebcbdc41ab7483cd549c", + "url": "https://api.github.com/repos/bobthecow/psysh/zipball/ddff0ac01beddc251786fe70367cd8bbdb258196", + "reference": "ddff0ac01beddc251786fe70367cd8bbdb258196", "shasum": "" }, "require": { @@ -9125,8 +9078,8 @@ "ext-tokenizer": "*", "nikic/php-parser": "^5.0 || ^4.0", "php": "^8.0 || ^7.4", - "symfony/console": "^7.0 || ^6.0 || ^5.0 || ^4.0 || ^3.4", - "symfony/var-dumper": "^7.0 || ^6.0 || ^5.0 || ^4.0 || ^3.4" + "symfony/console": "^8.0 || ^7.0 || ^6.0 || ^5.0 || ^4.0 || ^3.4", + "symfony/var-dumper": "^8.0 || ^7.0 || ^6.0 || ^5.0 || ^4.0 || ^3.4" }, "conflict": { "symfony/console": "4.4.37 || 5.3.14 || 5.3.15 || 5.4.3 || 5.4.4 || 6.0.3 || 6.0.4" @@ -9181,9 +9134,9 @@ ], "support": { "issues": "https://github.com/bobthecow/psysh/issues", - "source": "https://github.com/bobthecow/psysh/tree/v0.12.15" + "source": "https://github.com/bobthecow/psysh/tree/v0.12.18" }, - "time": "2025-11-28T00:00:14+00:00" + "time": "2025-12-17T14:35:46+00:00" }, { "name": "ralouphie/getallheaders", @@ -10359,16 +10312,16 @@ }, { "name": "spatie/laravel-medialibrary", - "version": "11.17.5", + "version": "11.17.7", "source": { "type": "git", "url": "https://github.com/spatie/laravel-medialibrary.git", - "reference": "eef29bbc701d786f2f6233ca4c40deb61282ac36" + "reference": "237f34f70ae97523c1a99cad7176e229b8d6f0b6" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/spatie/laravel-medialibrary/zipball/eef29bbc701d786f2f6233ca4c40deb61282ac36", - "reference": "eef29bbc701d786f2f6233ca4c40deb61282ac36", + "url": "https://api.github.com/repos/spatie/laravel-medialibrary/zipball/237f34f70ae97523c1a99cad7176e229b8d6f0b6", + "reference": "237f34f70ae97523c1a99cad7176e229b8d6f0b6", "shasum": "" }, "require": { @@ -10401,8 +10354,8 @@ "larastan/larastan": "^2.7|^3.0", "league/flysystem-aws-s3-v3": "^3.22", "mockery/mockery": "^1.6.7", - "orchestra/testbench": "^7.0|^8.17|^9.0|^10.0", - "pestphp/pest": "^2.28|^3.5|^4.0", + "orchestra/testbench": "^8.36|^9.15|^10.8", + "pestphp/pest": "^2.36|^3.0|^4.0", "phpstan/extension-installer": "^1.3.1", "spatie/laravel-ray": "^1.33", "spatie/pdf-to-image": "^2.2|^3.0", @@ -10453,7 +10406,7 @@ ], "support": { "issues": "https://github.com/spatie/laravel-medialibrary/issues", - "source": "https://github.com/spatie/laravel-medialibrary/tree/11.17.5" + "source": "https://github.com/spatie/laravel-medialibrary/tree/11.17.7" }, "funding": [ { @@ -10465,7 +10418,7 @@ "type": "github" } ], - "time": "2025-11-13T11:36:18+00:00" + "time": "2025-12-15T08:51:55+00:00" }, { "name": "spatie/laravel-package-tools", @@ -11473,16 +11426,16 @@ }, { "name": "symfony/dom-crawler", - "version": "v8.0.0", + "version": "v8.0.1", "source": { "type": "git", "url": "https://github.com/symfony/dom-crawler.git", - "reference": "15d1f7555f3337ded097dbe01c6ea6c59564a64a" + "reference": "11a3dbe6f6c0ae03ae71f879cf5c2e28db107179" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/dom-crawler/zipball/15d1f7555f3337ded097dbe01c6ea6c59564a64a", - "reference": "15d1f7555f3337ded097dbe01c6ea6c59564a64a", + "url": "https://api.github.com/repos/symfony/dom-crawler/zipball/11a3dbe6f6c0ae03ae71f879cf5c2e28db107179", + "reference": "11a3dbe6f6c0ae03ae71f879cf5c2e28db107179", "shasum": "" }, "require": { @@ -11519,7 +11472,7 @@ "description": "Eases DOM navigation for HTML and XML documents", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/dom-crawler/tree/v8.0.0" + "source": "https://github.com/symfony/dom-crawler/tree/v8.0.1" }, "funding": [ { @@ -11539,7 +11492,7 @@ "type": "tidelift" } ], - "time": "2025-11-01T09:19:23+00:00" + "time": "2025-12-06T17:00:47+00:00" }, { "name": "symfony/error-handler", @@ -11786,16 +11739,16 @@ }, { "name": "symfony/filesystem", - "version": "v8.0.0", + "version": "v8.0.1", "source": { "type": "git", "url": "https://github.com/symfony/filesystem.git", - "reference": "7fc96ae83372620eaba3826874f46e26295768ca" + "reference": "d937d400b980523dc9ee946bb69972b5e619058d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/filesystem/zipball/7fc96ae83372620eaba3826874f46e26295768ca", - "reference": "7fc96ae83372620eaba3826874f46e26295768ca", + "url": "https://api.github.com/repos/symfony/filesystem/zipball/d937d400b980523dc9ee946bb69972b5e619058d", + "reference": "d937d400b980523dc9ee946bb69972b5e619058d", "shasum": "" }, "require": { @@ -11832,7 +11785,7 @@ "description": "Provides basic utilities for the filesystem", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/filesystem/tree/v8.0.0" + "source": "https://github.com/symfony/filesystem/tree/v8.0.1" }, "funding": [ { @@ -11852,7 +11805,7 @@ "type": "tidelift" } ], - "time": "2025-11-05T14:36:47+00:00" + "time": "2025-12-01T09:13:36+00:00" }, { "name": "symfony/finder", @@ -14461,80 +14414,6 @@ ], "time": "2024-11-21T01:49:47+00:00" }, - { - "name": "vormkracht10/filament-mails", - "version": "v3.0.6", - "source": { - "type": "git", - "url": "https://github.com/backstagephp/filament-mails.git", - "reference": "fda281287070f4eb79f97408e1a92b21cddb3daa" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/backstagephp/filament-mails/zipball/fda281287070f4eb79f97408e1a92b21cddb3daa", - "reference": "fda281287070f4eb79f97408e1a92b21cddb3daa", - "shasum": "" - }, - "require": { - "backstage/laravel-mails": "^2.0", - "filament/filament": "^4.0", - "php": "^8.2", - "spatie/laravel-package-tools": "^1.15.0" - }, - "require-dev": { - "laravel/pint": "^1.16", - "nunomaduro/collision": "^8.8.0", - "orchestra/testbench": "^9.0|^10.0", - "pestphp/pest": "^3.7", - "pestphp/pest-plugin-arch": "^3.1.0", - "pestphp/pest-plugin-laravel": "^3.0" - }, - "type": "library", - "extra": { - "laravel": { - "aliases": { - "FilamentMails": "Backstage\\FilamentMails\\Facades\\FilamentMails" - }, - "providers": [ - "Backstage\\FilamentMails\\FilamentMailsServiceProvider" - ] - } - }, - "autoload": { - "psr-4": { - "Backstage\\FilamentMails\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Baspa", - "email": "hello@baspa.dev", - "role": "Developer" - } - ], - "description": "View logged mails and events in a beautiful Filament UI.", - "homepage": "https://github.com/backstagephp/filament-mails", - "keywords": [ - "backstagephp", - "filament-mails", - "laravel" - ], - "support": { - "issues": "https://github.com/backstagephp/filament-mails/issues", - "source": "https://github.com/backstagephp/filament-mails" - }, - "funding": [ - { - "url": "https://github.com/vormkracht10", - "type": "github" - } - ], - "time": "2025-10-03T11:13:43+00:00" - }, { "name": "webmozart/assert", "version": "1.12.1", @@ -15188,53 +15067,6 @@ ], "time": "2025-08-14T07:29:31+00:00" }, - { - "name": "filament/upgrade", - "version": "v4.3.1", - "source": { - "type": "git", - "url": "https://github.com/filamentphp/upgrade.git", - "reference": "31b03a80a99e203ba0b7bfbfb9566974e0346be7" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/filamentphp/upgrade/zipball/31b03a80a99e203ba0b7bfbfb9566974e0346be7", - "reference": "31b03a80a99e203ba0b7bfbfb9566974e0346be7", - "shasum": "" - }, - "require": { - "nunomaduro/termwind": "^2.0", - "php": "^8.2", - "rector/rector": "^2.0" - }, - "bin": [ - "bin/filament-v4" - ], - "type": "library", - "extra": { - "laravel": { - "providers": [ - "Filament\\Upgrade\\UpgradeServiceProvider" - ] - } - }, - "autoload": { - "psr-4": { - "Filament\\Upgrade\\": "src" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "description": "Upgrade Filament v3 code to Filament v4.", - "homepage": "https://github.com/filamentphp/filament", - "support": { - "issues": "https://github.com/filamentphp/filament/issues", - "source": "https://github.com/filamentphp/filament" - }, - "time": "2025-11-28T11:21:29+00:00" - }, { "name": "filp/whoops", "version": "2.18.4", @@ -15550,25 +15382,25 @@ }, { "name": "laravel/boost", - "version": "v1.8.5", + "version": "v1.8.6", "source": { "type": "git", "url": "https://github.com/laravel/boost.git", - "reference": "99c15c392f3c6f049f0671dd5dc7b6e9a75cfe7e" + "reference": "eff873e0c9edb93b933ae56b2f6f2ba5fa08b522" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/boost/zipball/99c15c392f3c6f049f0671dd5dc7b6e9a75cfe7e", - "reference": "99c15c392f3c6f049f0671dd5dc7b6e9a75cfe7e", + "url": "https://api.github.com/repos/laravel/boost/zipball/eff873e0c9edb93b933ae56b2f6f2ba5fa08b522", + "reference": "eff873e0c9edb93b933ae56b2f6f2ba5fa08b522", "shasum": "" }, "require": { "guzzlehttp/guzzle": "^7.9", - "illuminate/console": "^10.49.0|^11.45.3|^12.28.1", - "illuminate/contracts": "^10.49.0|^11.45.3|^12.28.1", - "illuminate/routing": "^10.49.0|^11.45.3|^12.28.1", - "illuminate/support": "^10.49.0|^11.45.3|^12.28.1", - "laravel/mcp": "^0.4.1", + "illuminate/console": "^10.49.0|^11.45.3|^12.41.1", + "illuminate/contracts": "^10.49.0|^11.45.3|^12.41.1", + "illuminate/routing": "^10.49.0|^11.45.3|^12.41.1", + "illuminate/support": "^10.49.0|^11.45.3|^12.41.1", + "laravel/mcp": "^0.5.1", "laravel/prompts": "0.1.25|^0.3.6", "laravel/roster": "^0.2.9", "php": "^8.1" @@ -15612,7 +15444,7 @@ "issues": "https://github.com/laravel/boost/issues", "source": "https://github.com/laravel/boost" }, - "time": "2025-12-08T21:54:49+00:00" + "time": "2025-12-19T10:02:17+00:00" }, { "name": "laravel/breeze", @@ -15677,16 +15509,16 @@ }, { "name": "laravel/mcp", - "version": "v0.4.2", + "version": "v0.5.1", "source": { "type": "git", "url": "https://github.com/laravel/mcp.git", - "reference": "1c7878be3931a19768f791ddf141af29f43fb4ef" + "reference": "10dedea054fa4eeaa9ef2ccbfdad6c3e1dbd17a4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/mcp/zipball/1c7878be3931a19768f791ddf141af29f43fb4ef", - "reference": "1c7878be3931a19768f791ddf141af29f43fb4ef", + "url": "https://api.github.com/repos/laravel/mcp/zipball/10dedea054fa4eeaa9ef2ccbfdad6c3e1dbd17a4", + "reference": "10dedea054fa4eeaa9ef2ccbfdad6c3e1dbd17a4", "shasum": "" }, "require": { @@ -15746,7 +15578,7 @@ "issues": "https://github.com/laravel/mcp/issues", "source": "https://github.com/laravel/mcp" }, - "time": "2025-12-07T15:49:15+00:00" + "time": "2025-12-17T06:14:23+00:00" }, { "name": "laravel/pail", @@ -16724,6 +16556,74 @@ ], "time": "2024-09-22T07:54:40+00:00" }, + { + "name": "pestphp/pest-plugin-type-coverage", + "version": "v3.6.1", + "source": { + "type": "git", + "url": "https://github.com/pestphp/pest-plugin-type-coverage.git", + "reference": "86c4074d7213cbaba7accb13f6221533edbfb81e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/pestphp/pest-plugin-type-coverage/zipball/86c4074d7213cbaba7accb13f6221533edbfb81e", + "reference": "86c4074d7213cbaba7accb13f6221533edbfb81e", + "shasum": "" + }, + "require": { + "pestphp/pest-plugin": "^3.0.0", + "php": "^8.2", + "phpstan/phpstan": "^1.12.21|^2.1.19", + "tomasvotruba/type-coverage": "^1.0.0|^2.0.2" + }, + "require-dev": { + "pestphp/pest": "^3.8.2", + "pestphp/pest-dev-tools": "^3.4.0" + }, + "type": "library", + "extra": { + "pest": { + "plugins": [ + "Pest\\TypeCoverage\\Plugin" + ] + } + }, + "autoload": { + "psr-4": { + "Pest\\TypeCoverage\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "The Type Coverage plugin for Pest PHP.", + "keywords": [ + "coverage", + "framework", + "pest", + "php", + "plugin", + "test", + "testing", + "type-coverage", + "unit" + ], + "support": { + "source": "https://github.com/pestphp/pest-plugin-type-coverage/tree/v3.6.1" + }, + "funding": [ + { + "url": "https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=66BYDWAT92N6L", + "type": "custom" + }, + { + "url": "https://github.com/nunomaduro", + "type": "github" + } + ], + "time": "2025-07-22T11:55:56+00:00" + }, { "name": "phar-io/manifest", "version": "2.0.4", @@ -18761,6 +18661,63 @@ } ], "time": "2025-11-17T20:03:58+00:00" + }, + { + "name": "tomasvotruba/type-coverage", + "version": "2.1.0", + "source": { + "type": "git", + "url": "https://github.com/TomasVotruba/type-coverage.git", + "reference": "468354b3964120806a69890cbeb3fcf005876391" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/TomasVotruba/type-coverage/zipball/468354b3964120806a69890cbeb3fcf005876391", + "reference": "468354b3964120806a69890cbeb3fcf005876391", + "shasum": "" + }, + "require": { + "nette/utils": "^3.2 || ^4.0", + "php": "^7.4 || ^8.0", + "phpstan/phpstan": "^2.0" + }, + "type": "phpstan-extension", + "extra": { + "phpstan": { + "includes": [ + "config/extension.neon" + ] + } + }, + "autoload": { + "psr-4": { + "TomasVotruba\\TypeCoverage\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Measure type coverage of your project", + "keywords": [ + "phpstan-extension", + "static analysis" + ], + "support": { + "issues": "https://github.com/TomasVotruba/type-coverage/issues", + "source": "https://github.com/TomasVotruba/type-coverage/tree/2.1.0" + }, + "funding": [ + { + "url": "https://www.paypal.me/rectorphp", + "type": "custom" + }, + { + "url": "https://github.com/tomasvotruba", + "type": "github" + } + ], + "time": "2025-12-05T16:38:02+00:00" } ], "aliases": [], diff --git a/config/app-modules.php b/config/app-modules.php index 33a994a1..379cbf2b 100644 --- a/config/app-modules.php +++ b/config/app-modules.php @@ -59,7 +59,7 @@ | */ - 'tests_base' => 'Tests\TestCase', + 'tests_base' => Tests\TestCase::class, /* |-------------------------------------------------------------------------- diff --git a/config/database.php b/config/database.php index 6ba0a5d6..f15d284c 100644 --- a/config/database.php +++ b/config/database.php @@ -152,7 +152,7 @@ 'default' => [ 'url' => env('REDIS_URL'), 'host' => env('REDIS_HOST', '127.0.0.1'), - 'password' => env('REDIS_PASSWORD', null), + 'password' => env('REDIS_PASSWORD'), 'port' => env('REDIS_PORT', '6379'), 'database' => env('REDIS_DB', '0'), ], @@ -160,7 +160,7 @@ 'cache' => [ 'url' => env('REDIS_URL'), 'host' => env('REDIS_HOST', '127.0.0.1'), - 'password' => env('REDIS_PASSWORD', null), + 'password' => env('REDIS_PASSWORD'), 'port' => env('REDIS_PORT', '6379'), 'database' => env('REDIS_CACHE_DB', '1'), ], diff --git a/config/filament-mails.php b/config/filament-mails.php deleted file mode 100644 index 5d49be76..00000000 --- a/config/filament-mails.php +++ /dev/null @@ -1,19 +0,0 @@ - [ - 'mail' => MailResource::class, - 'event' => EventResource::class, - 'suppression' => SuppressionResource::class, - ], - - 'navigation' => [ - 'group' => null, - ], -]; diff --git a/config/filesystems.php b/config/filesystems.php index 279d4fbe..c3f1a409 100644 --- a/config/filesystems.php +++ b/config/filesystems.php @@ -46,7 +46,7 @@ 'throw' => false, ], - 's3' => [ + 'media' => [ 'driver' => 's3', 'key' => env('AWS_ACCESS_KEY_ID'), 'secret' => env('AWS_SECRET_ACCESS_KEY'), @@ -54,10 +54,14 @@ 'bucket' => env('AWS_BUCKET'), 'url' => env('AWS_URL'), 'endpoint' => env('AWS_ENDPOINT'), + 'use_path_style_endpoint' => env('AWS_USE_PATH_STYLE_ENDPOINT', false), + 'root' => env('AWS_ROOT_DIRECTORY', 'public'), + 'visibility' => 'public', + 'directory_visibility' => 'public', 'throw' => false, ], - 'media' => [ + 's3' => [ 'driver' => 's3', 'key' => env('AWS_ACCESS_KEY_ID'), 'secret' => env('AWS_SECRET_ACCESS_KEY'), @@ -65,10 +69,6 @@ 'bucket' => env('AWS_BUCKET'), 'url' => env('AWS_URL'), 'endpoint' => env('AWS_ENDPOINT'), - 'use_path_style_endpoint' => env('AWS_USE_PATH_STYLE_ENDPOINT', false), - 'root' => env('AWS_ROOT', 'public'), - 'visibility' => 'public', - 'directory_visibility' => 'public', 'throw' => false, ], ], diff --git a/config/lcm.php b/config/lcm.php index b6b4aa1e..e73bf998 100644 --- a/config/lcm.php +++ b/config/lcm.php @@ -56,7 +56,7 @@ 'supported_locales' => ['fr', 'en'], - 'notch-pay-public-token' => env('NOTCHPAY_PUBLIC_KEY', null), + 'notch-pay-public-token' => env('NOTCHPAY_PUBLIC_KEY'), /* |-------------------------------------------------------------------------- diff --git a/config/livewire-slide-over.php b/config/livewire-slide-over.php index f4707846..a3a44944 100644 --- a/config/livewire-slide-over.php +++ b/config/livewire-slide-over.php @@ -39,7 +39,7 @@ | */ - 'default_position' => \Laravelcm\LivewireSlideOvers\Position::Right, + 'default_position' => Laravelcm\LivewireSlideOvers\Position::Right, /* |-------------------------------------------------------------------------- diff --git a/config/mails.php b/config/mails.php deleted file mode 100644 index 9e5be8bf..00000000 --- a/config/mails.php +++ /dev/null @@ -1,143 +0,0 @@ - [ - 'mail' => Mail::class, - 'event' => MailEvent::class, - 'attachment' => MailAttachment::class, - ], - - // Table names for saving sent emails and polymorphic relations to database - 'database' => [ - 'tables' => [ - 'mails' => 'mails', - 'attachments' => 'mail_attachments', - 'events' => 'mail_events', - 'polymorph' => 'mailables', - ], - - 'pruning' => [ - 'enabled' => true, - 'after' => 30, // days - ], - ], - - 'headers' => [ - 'uuid' => 'X-Mails-UUID', - - 'associate' => 'X-Mails-Associated-Models', - ], - - 'webhooks' => [ - 'routes' => [ - 'prefix' => 'webhooks/mails', - ], - - 'queue' => env('MAILS_QUEUE_WEBHOOKS', false), - ], - - // Logging mails - 'logging' => [ - - // Enable logging of all sent mails to database - - 'enabled' => env('MAILS_LOGGING_ENABLED', true), - - // Specify attributes to log in database - - 'attributes' => [ - 'subject', - 'from', - 'to', - 'reply_to', - 'cc', - 'bcc', - 'html', - 'text', - ], - - // Encrypt all attributes saved to database - - 'encrypted' => env('MAILS_ENCRYPTED', true), - - // Track following events using webhooks from email provider - - 'tracking' => [ - 'bounces' => true, - 'clicks' => true, - 'complaints' => true, - 'deliveries' => true, - 'opens' => true, - 'unsubscribes' => true, - ], - - // Enable saving mail attachments to disk - - 'attachments' => [ - 'enabled' => env('MAILS_LOGGING_ATTACHMENTS_ENABLED', true), - 'disk' => env('FILESYSTEM_DISK', 'local'), - 'root' => 'mails/attachments', - ], - ], - - // Notifications for important mail events - - 'notifications' => [ - 'mail' => [ - 'to' => [ - env('MAIL_SUPPORT', 'support@laravel.cm'), - ], - ], - - 'discord' => [ - // 'to' => ['1234567890'], - ], - - 'slack' => [ - // 'to' => ['https://hooks.slack.com/services/...'], - ], - - 'telegram' => [ - // 'to' => ['1234567890'], - ], - ], - - 'events' => [ - 'soft_bounced' => [ - 'notify' => ['mail'], - ], - - 'hard_bounced' => [ - 'notify' => ['mail'], - ], - - 'bouncerate' => [ - 'notify' => [], - - 'retain' => 30, // days - - 'treshold' => 1, // % - ], - - 'deliveryrate' => [ - 'treshold' => 99, - ], - - 'complained' => [ - // - ], - - 'unsent' => [ - // - ], - ], - -]; diff --git a/config/permission.php b/config/permission.php index 8298e3a8..22a00e72 100644 --- a/config/permission.php +++ b/config/permission.php @@ -169,7 +169,7 @@ * When permissions or roles are updated the cache is flushed automatically. */ - 'expiration_time' => \DateInterval::createFromDateString('24 hours'), + 'expiration_time' => DateInterval::createFromDateString('24 hours'), /* * The cache key used to store all permissions. diff --git a/config/session.php b/config/session.php index 3660c2a2..65cebf5e 100644 --- a/config/session.php +++ b/config/session.php @@ -74,7 +74,7 @@ | */ - 'connection' => env('SESSION_CONNECTION', null), + 'connection' => env('SESSION_CONNECTION'), /* |-------------------------------------------------------------------------- @@ -102,7 +102,7 @@ | */ - 'store' => env('SESSION_STORE', null), + 'store' => env('SESSION_STORE'), /* |-------------------------------------------------------------------------- @@ -157,7 +157,7 @@ | */ - 'domain' => env('SESSION_DOMAIN', null), + 'domain' => env('SESSION_DOMAIN'), /* |-------------------------------------------------------------------------- diff --git a/cors.json b/cors.json new file mode 100644 index 00000000..c049ae2c --- /dev/null +++ b/cors.json @@ -0,0 +1,11 @@ +{ + "CORSRules": [ + { + "AllowedOrigins": ["https://laravel.cm", "https://staging.laravel.cm"], + "AllowedHeaders": ["*"], + "AllowedMethods": ["GET", "HEAD", "POST", "PUT", "DELETE"], + "MaxAgeSeconds": 3000, + "ExposeHeaders": ["ETag"] + } + ] +} diff --git a/database/factories/ActivityFactory.php b/database/factories/ActivityFactory.php index 3000df7d..f6fceb98 100644 --- a/database/factories/ActivityFactory.php +++ b/database/factories/ActivityFactory.php @@ -8,7 +8,7 @@ use Illuminate\Database\Eloquent\Factories\Factory; /** - * @extends \Illuminate\Database\Eloquent\Factories\Factory<\App\Models\Activity> + * @extends Factory */ final class ActivityFactory extends Factory { @@ -17,7 +17,7 @@ final class ActivityFactory extends Factory public function definition(): array { return [ - + // ]; } } diff --git a/database/factories/ArticleFactory.php b/database/factories/ArticleFactory.php index bc092f52..39564d58 100644 --- a/database/factories/ArticleFactory.php +++ b/database/factories/ArticleFactory.php @@ -8,6 +8,9 @@ use App\Models\User; use Illuminate\Database\Eloquent\Factories\Factory; +/** + * @extends Factory
+ */ final class ArticleFactory extends Factory { protected $model = Article::class; @@ -16,10 +19,10 @@ public function definition(): array { return [ 'user_id' => $attributes['user_id'] ?? User::factory(), - 'title' => $this->faker->sentence(), - 'body' => $this->faker->paragraphs(3, true), - 'slug' => $this->faker->unique()->slug(), - 'locale' => $this->faker->randomElement(['en', 'fr']), + 'title' => fake()->sentence(), + 'body' => fake()->paragraphs(3, true), + 'slug' => fake()->unique()->slug(), + 'locale' => fake()->randomElement(['en', 'fr']), ]; } diff --git a/database/factories/ChannelFactory.php b/database/factories/ChannelFactory.php index e05e4064..494006d6 100644 --- a/database/factories/ChannelFactory.php +++ b/database/factories/ChannelFactory.php @@ -4,15 +4,21 @@ namespace Database\Factories; +use App\Models\Channel; use Illuminate\Database\Eloquent\Factories\Factory; +/** + * @extends Factory + */ final class ChannelFactory extends Factory { + protected $model = Channel::class; + public function definition(): array { return [ - 'name' => $this->faker->text(15), - 'slug' => $this->faker->unique()->slug(), + 'name' => fake()->text(15), + 'slug' => fake()->unique()->slug(), ]; } } diff --git a/database/factories/DiscussionFactory.php b/database/factories/DiscussionFactory.php index 9f096400..a7f76d19 100644 --- a/database/factories/DiscussionFactory.php +++ b/database/factories/DiscussionFactory.php @@ -4,18 +4,24 @@ namespace Database\Factories; +use App\Models\Discussion; use App\Models\User; use Illuminate\Database\Eloquent\Factories\Factory; +/** + * @extends Factory + */ final class DiscussionFactory extends Factory { + protected $model = Discussion::class; + public function definition(): array { return [ 'user_id' => $attributes['user_id'] ?? User::factory(), - 'title' => $this->faker->sentence(), - 'body' => $this->faker->paragraphs(3, true), - 'slug' => $this->faker->unique()->slug(), + 'title' => fake()->sentence(), + 'body' => fake()->paragraphs(3, true), + 'slug' => fake()->unique()->slug(), ]; } } diff --git a/database/factories/EnterpriseFactory.php b/database/factories/EnterpriseFactory.php index 9ea02ac9..3707fee7 100644 --- a/database/factories/EnterpriseFactory.php +++ b/database/factories/EnterpriseFactory.php @@ -9,32 +9,34 @@ use Illuminate\Database\Eloquent\Factories\Factory; /** - * @extends \Illuminate\Database\Eloquent\Factories\Factory<\App\Models\Enterprise> + * @extends Factory */ final class EnterpriseFactory extends Factory { + protected $model = Enterprise::class; + public function definition(): array { return [ - 'name' => $this->faker->company(), - 'slug' => $this->faker->unique()->slug(), - 'website' => $this->faker->unique()->url(), - 'about' => $this->faker->words(250, true), - 'address' => $this->faker->address(), - 'description' => $this->faker->sentence(10), - 'founded_in' => $this->faker->date('Y'), - 'ceo' => $this->faker->firstName(), + 'name' => fake()->company(), + 'slug' => fake()->unique()->slug(), + 'website' => fake()->unique()->url(), + 'about' => fake()->words(250, true), + 'address' => fake()->address(), + 'description' => fake()->sentence(10), + 'founded_in' => fake()->date('Y'), + 'ceo' => fake()->firstName(), 'is_featured' => array_rand([true, false]), 'is_certified' => array_rand([true, false]), 'is_public' => array_rand([true, false]), - 'user_id' => array_rand(User::all()->modelKeys()), + 'user_id' => User::factory(), ]; } public function configure(): self { return $this->afterCreating(function (Enterprise $enterprise): void { - $enterprise->addMediaFromUrl("https://source.unsplash.com/random/800x800/?img={$enterprise->id}") + $enterprise->addMediaFromUrl('https://source.unsplash.com/random/800x800/?img='.$enterprise->id) ->toMediaCollection('logo'); }); } diff --git a/database/factories/ReactionFactory.php b/database/factories/ReactionFactory.php new file mode 100644 index 00000000..4dd40f1f --- /dev/null +++ b/database/factories/ReactionFactory.php @@ -0,0 +1,23 @@ + + */ +final class ReactionFactory extends Factory +{ + protected $model = Reaction::class; + + public function definition(): array + { + return [ + // + ]; + } +} diff --git a/database/factories/ReplyFactory.php b/database/factories/ReplyFactory.php index b243a8d3..77e1b2bc 100644 --- a/database/factories/ReplyFactory.php +++ b/database/factories/ReplyFactory.php @@ -4,16 +4,22 @@ namespace Database\Factories; +use App\Models\Reply; use App\Models\Thread; use App\Models\User; use Illuminate\Database\Eloquent\Factories\Factory; +/** + * @extends Factory + */ final class ReplyFactory extends Factory { + protected $model = Reply::class; + public function definition(): array { return [ - 'body' => $this->faker->text(), + 'body' => fake()->text(), 'user_id' => $attributes['user_id'] ?? User::factory(), 'replyable_id' => Thread::factory(), 'replyable_type' => Thread::class, diff --git a/database/factories/RoleFactory.php b/database/factories/RoleFactory.php deleted file mode 100644 index 98c83bc5..00000000 --- a/database/factories/RoleFactory.php +++ /dev/null @@ -1,35 +0,0 @@ - $this->faker->unique()->word(), - 'guard_name' => 'web', - ]; - } - - public function admin() - { - return $this->state([ - 'name' => 'admin', - ]); - } - - public function moderator() - { - return $this->state([ - 'name' => 'moderator', - ]); - } -} diff --git a/database/factories/SocialAccountFactory.php b/database/factories/SocialAccountFactory.php new file mode 100644 index 00000000..a0fe5ced --- /dev/null +++ b/database/factories/SocialAccountFactory.php @@ -0,0 +1,23 @@ + + */ +final class SocialAccountFactory extends Factory +{ + protected $model = SocialAccount::class; + + public function definition(): array + { + return [ + // + ]; + } +} diff --git a/database/factories/SpamReportFactory.php b/database/factories/SpamReportFactory.php new file mode 100644 index 00000000..dc6e3369 --- /dev/null +++ b/database/factories/SpamReportFactory.php @@ -0,0 +1,23 @@ + + */ +final class SpamReportFactory extends Factory +{ + protected $model = SpamReport::class; + + public function definition(): array + { + return [ + // + ]; + } +} diff --git a/database/factories/SubscribeFactory.php b/database/factories/SubscribeFactory.php new file mode 100644 index 00000000..404abdc7 --- /dev/null +++ b/database/factories/SubscribeFactory.php @@ -0,0 +1,23 @@ + + */ +final class SubscribeFactory extends Factory +{ + protected $model = Subscribe::class; + + public function definition(): array + { + return [ + // + ]; + } +} diff --git a/database/factories/TagFactory.php b/database/factories/TagFactory.php index 6be68373..6a36efee 100644 --- a/database/factories/TagFactory.php +++ b/database/factories/TagFactory.php @@ -7,6 +7,9 @@ use App\Models\Tag; use Illuminate\Database\Eloquent\Factories\Factory; +/** + * @extends Factory + */ final class TagFactory extends Factory { protected $model = Tag::class; @@ -14,8 +17,8 @@ final class TagFactory extends Factory public function definition(): array { return [ - 'name' => $this->faker->text(15), - 'slug' => $this->faker->slug(), + 'name' => fake()->text(15), + 'slug' => fake()->slug(), 'concerns' => ['post', 'discussion', 'tutorial'], ]; } diff --git a/database/factories/ThreadFactory.php b/database/factories/ThreadFactory.php index dbfaad8e..63ea068a 100644 --- a/database/factories/ThreadFactory.php +++ b/database/factories/ThreadFactory.php @@ -4,17 +4,23 @@ namespace Database\Factories; +use App\Models\Thread; use App\Models\User; use Illuminate\Database\Eloquent\Factories\Factory; +/** + * @extends Factory + */ final class ThreadFactory extends Factory { + protected $model = Thread::class; + public function definition(): array { return [ - 'title' => $this->faker->text(20), - 'body' => $this->faker->text(), - 'slug' => $this->faker->unique()->slug(), + 'title' => fake()->text(20), + 'body' => fake()->text(), + 'slug' => fake()->unique()->slug(), 'user_id' => $attributes['user_id'] ?? User::factory(), ]; } diff --git a/database/factories/TransactionFactory.php b/database/factories/TransactionFactory.php new file mode 100644 index 00000000..a98446dc --- /dev/null +++ b/database/factories/TransactionFactory.php @@ -0,0 +1,23 @@ + + */ +final class TransactionFactory extends Factory +{ + protected $model = Transaction::class; + + public function definition(): array + { + return [ + // + ]; + } +} diff --git a/database/factories/UserFactory.php b/database/factories/UserFactory.php index a68a0f8a..e5697716 100644 --- a/database/factories/UserFactory.php +++ b/database/factories/UserFactory.php @@ -8,6 +8,9 @@ use Illuminate\Database\Eloquent\Factories\Factory; use Illuminate\Support\Str; +/** + * @extends Factory + */ final class UserFactory extends Factory { protected $model = User::class; @@ -27,21 +30,21 @@ public function definition(): array public function unverified(): self { - return $this->state(fn (array $attributes) => [ + return $this->state(fn (array $attributes): array => [ 'email_verified_at' => null, ]); } public function lastMonth(): self { - return $this->state(fn (array $attributes) => [ + return $this->state(fn (array $attributes): array => [ 'created_at' => now()->subMonth(), ]); } public function banned(): self { - return $this->state(fn (array $attributes) => [ + return $this->state(fn (array $attributes): array => [ 'banned_at' => now(), 'banned_reason' => 'Violation des règles de la communauté', ]); @@ -49,7 +52,7 @@ public function banned(): self public function unbanned(): self { - return $this->state(fn (array $attributes) => [ + return $this->state(fn (array $attributes): array => [ 'banned_at' => null, 'banned_reason' => null, ]); diff --git a/database/migrations/2014_10_12_000000_create_users_table.php b/database/migrations/2014_10_12_000000_create_users_table.php index 7b41e0b4..9376bbd3 100644 --- a/database/migrations/2014_10_12_000000_create_users_table.php +++ b/database/migrations/2014_10_12_000000_create_users_table.php @@ -6,12 +6,13 @@ use Illuminate\Database\Schema\Blueprint; use Illuminate\Support\Facades\Schema; -final class CreateUsersTable extends Migration +return new class extends Migration { public function up(): void { - Schema::create('users', function (Blueprint $table): void { + Schema::create('users', static function (Blueprint $table): void { $table->id(); + $table->string('name'); $table->string('email')->unique()->index(); $table->string('username')->unique()->index(); @@ -30,6 +31,7 @@ public function up(): void $table->boolean('opt_in')->default(false); $table->json('settings')->nullable(); $table->rememberToken(); + $table->timestamps(); }); } @@ -38,4 +40,4 @@ public function down(): void { Schema::dropIfExists('users'); } -} +}; diff --git a/database/migrations/2014_10_12_100000_create_password_resets_table.php b/database/migrations/2014_10_12_100000_create_password_resets_table.php index d0a65406..7bc3a812 100644 --- a/database/migrations/2014_10_12_100000_create_password_resets_table.php +++ b/database/migrations/2014_10_12_100000_create_password_resets_table.php @@ -6,11 +6,11 @@ use Illuminate\Database\Schema\Blueprint; use Illuminate\Support\Facades\Schema; -final class CreatePasswordResetsTable extends Migration +return new class extends Migration { public function up(): void { - Schema::create('password_resets', function (Blueprint $table): void { + Schema::create('password_resets', static function (Blueprint $table): void { $table->string('email')->index(); $table->string('token'); $table->timestamp('created_at')->nullable(); @@ -21,4 +21,4 @@ public function down(): void { Schema::dropIfExists('password_resets'); } -} +}; diff --git a/database/migrations/2014_10_12_200000_add_two_factor_columns_to_users_table.php b/database/migrations/2014_10_12_200000_add_two_factor_columns_to_users_table.php index 630614ed..e7fd4d27 100644 --- a/database/migrations/2014_10_12_200000_add_two_factor_columns_to_users_table.php +++ b/database/migrations/2014_10_12_200000_add_two_factor_columns_to_users_table.php @@ -6,11 +6,11 @@ use Illuminate\Database\Schema\Blueprint; use Illuminate\Support\Facades\Schema; -final class AddTwoFactorColumnsToUsersTable extends Migration +return new class extends Migration { public function up(): void { - Schema::table('users', function (Blueprint $table): void { + Schema::table('users', static function (Blueprint $table): void { $table->text('two_factor_secret') ->after('password') ->nullable(); @@ -23,8 +23,8 @@ public function up(): void public function down(): void { - Schema::table('users', function (Blueprint $table): void { + Schema::table('users', static function (Blueprint $table): void { $table->dropColumn('two_factor_secret', 'two_factor_recovery_codes'); }); } -} +}; diff --git a/database/migrations/2019_08_19_000000_create_failed_jobs_table.php b/database/migrations/2019_08_19_000000_create_failed_jobs_table.php index 22c7b044..37b09bdc 100644 --- a/database/migrations/2019_08_19_000000_create_failed_jobs_table.php +++ b/database/migrations/2019_08_19_000000_create_failed_jobs_table.php @@ -6,11 +6,11 @@ use Illuminate\Database\Schema\Blueprint; use Illuminate\Support\Facades\Schema; -final class CreateFailedJobsTable extends Migration +return new class extends Migration { public function up(): void { - Schema::create('failed_jobs', function (Blueprint $table): void { + Schema::create('failed_jobs', static function (Blueprint $table): void { $table->id(); $table->string('uuid')->unique(); $table->text('connection'); @@ -25,4 +25,4 @@ public function down(): void { Schema::dropIfExists('failed_jobs'); } -} +}; diff --git a/database/migrations/2019_12_14_000001_create_personal_access_tokens_table.php b/database/migrations/2019_12_14_000001_create_personal_access_tokens_table.php index a986a0e8..614677ba 100644 --- a/database/migrations/2019_12_14_000001_create_personal_access_tokens_table.php +++ b/database/migrations/2019_12_14_000001_create_personal_access_tokens_table.php @@ -10,12 +10,14 @@ { public function up(): void { - Schema::create('personal_access_tokens', function (Blueprint $table): void { + Schema::create('personal_access_tokens', static function (Blueprint $table): void { $table->id(); + $table->morphs('tokenable'); $table->string('name'); $table->string('token', 64)->unique(); $table->text('abilities')->nullable(); + $table->timestamp('last_used_at')->nullable(); $table->timestamp('expires_at')->nullable(); $table->timestamps(); diff --git a/database/migrations/2021_06_05_182615_create_social_accounts_table.php b/database/migrations/2021_06_05_182615_create_social_accounts_table.php index 55659fd4..5ff34b8a 100644 --- a/database/migrations/2021_06_05_182615_create_social_accounts_table.php +++ b/database/migrations/2021_06_05_182615_create_social_accounts_table.php @@ -6,7 +6,7 @@ use Illuminate\Database\Schema\Blueprint; use Illuminate\Support\Facades\Schema; -final class CreateSocialAccountsTable extends Migration +return new class extends Migration { public function up(): void { @@ -27,4 +27,4 @@ public function down(): void { Schema::dropIfExists('social_accounts'); } -} +}; diff --git a/database/migrations/2021_07_29_164019_create_tags_table.php b/database/migrations/2021_07_29_164019_create_tags_table.php index e3545113..05b04f9a 100644 --- a/database/migrations/2021_07_29_164019_create_tags_table.php +++ b/database/migrations/2021_07_29_164019_create_tags_table.php @@ -6,19 +6,20 @@ use Illuminate\Database\Schema\Blueprint; use Illuminate\Support\Facades\Schema; -final class CreateTagsTable extends Migration +return new class extends Migration { public function up(): void { - Schema::create('tags', function (Blueprint $table): void { + Schema::create('tags', static function (Blueprint $table): void { $table->id(); + $table->string('name'); $table->string('slug'); $table->text('description')->nullable(); $table->json('concerns'); }); - Schema::create('taggables', function (Blueprint $table): void { + Schema::create('taggables', static function (Blueprint $table): void { $table->foreignId('tag_id'); $table->morphs('taggable'); }); @@ -29,4 +30,4 @@ public function down(): void Schema::dropIfExists('taggables'); Schema::dropIfExists('tags'); } -} +}; diff --git a/database/migrations/2021_08_18_115000_create_articles_table.php b/database/migrations/2021_08_18_115000_create_articles_table.php index 3def812e..1a4e5fb7 100644 --- a/database/migrations/2021_08_18_115000_create_articles_table.php +++ b/database/migrations/2021_08_18_115000_create_articles_table.php @@ -6,12 +6,14 @@ use Illuminate\Database\Schema\Blueprint; use Illuminate\Support\Facades\Schema; -final class CreateArticlesTable extends Migration +return new class extends Migration { public function up(): void { - Schema::create('articles', function (Blueprint $table): void { + Schema::create('articles', static function (Blueprint $table): void { $table->id(); + $table->foreignId('user_id')->constrained()->cascadeOnDelete(); + $table->string('title'); $table->text('body'); $table->string('slug')->unique(); @@ -20,7 +22,7 @@ public function up(): void $table->boolean('is_pinned')->default(false); $table->boolean('is_sponsored')->default(false); $table->unsignedBigInteger('tweet_id')->nullable(); - $table->foreignId('user_id')->constrained()->cascadeOnDelete(); + $table->timestamp('submitted_at')->nullable(); $table->timestamp('approved_at')->nullable(); $table->timestamp('shared_at')->nullable(); @@ -34,4 +36,4 @@ public function down(): void { Schema::dropIfExists('articles'); } -} +}; diff --git a/database/migrations/2021_09_14_172248_create_permission_tables.php b/database/migrations/2021_09_14_172248_create_permission_tables.php index db09a3dc..2940913d 100644 --- a/database/migrations/2021_09_14_172248_create_permission_tables.php +++ b/database/migrations/2021_09_14_172248_create_permission_tables.php @@ -2,28 +2,28 @@ declare(strict_types=1); +use Illuminate\Contracts\Cache\Factory; use Illuminate\Database\Migrations\Migration; use Illuminate\Database\Schema\Blueprint; use Illuminate\Support\Facades\Schema; -final class CreatePermissionTables extends Migration +return new class extends Migration { public function up(): void { + /** @var array $tableNames */ $tableNames = config('permission.table_names'); + /** @var array $tableNames */ $columnNames = config('permission.column_names'); + /** @var bool $teams */ $teams = config('permission.teams'); $pivotRole = $columnNames['role_pivot_key'] ?? 'role_id'; $pivotPermission = $columnNames['permission_pivot_key'] ?? 'permission_id'; + throw_if(blank($tableNames), Exception::class, 'Error: config/permission.php not loaded. Run [php artisan config:clear] and try again.'); - if (empty($tableNames)) { - throw new Exception('Error: config/permission.php not loaded. Run [php artisan config:clear] and try again.'); - } - if ($teams && empty($columnNames['team_foreign_key'] ?? null)) { - throw new Exception('Error: team_foreign_key on config/permission.php not loaded. Run [php artisan config:clear] and try again.'); - } + throw_if($teams && blank($columnNames['team_foreign_key'] ?? null), Exception::class, 'Error: team_foreign_key on config/permission.php not loaded. Run [php artisan config:clear] and try again.'); - Schema::create($tableNames['permissions'], function (Blueprint $table): void { + Schema::create($tableNames['permissions'], static function (Blueprint $table): void { $table->bigIncrements('id'); $table->string('name'); // For MySQL 8.0 use string('name', 125); $table->string('guard_name'); // For MySQL 8.0 use string('guard_name', 125); @@ -32,12 +32,13 @@ public function up(): void $table->unique(['name', 'guard_name']); }); - Schema::create($tableNames['roles'], function (Blueprint $table) use ($teams, $columnNames): void { + Schema::create($tableNames['roles'], static function (Blueprint $table) use ($teams, $columnNames): void { $table->bigIncrements('id'); if ($teams || config('permission.testing')) { // permission.testing is a fix for sqlite testing $table->unsignedBigInteger($columnNames['team_foreign_key'])->nullable(); $table->index($columnNames['team_foreign_key'], 'roles_team_foreign_key_index'); } + $table->string('name'); // For MySQL 8.0 use string('name', 125); $table->string('guard_name'); // For MySQL 8.0 use string('guard_name', 125); $table->timestamps(); @@ -48,7 +49,7 @@ public function up(): void } }); - Schema::create($tableNames['model_has_permissions'], function (Blueprint $table) use ($tableNames, $columnNames, $pivotPermission, $teams): void { + Schema::create($tableNames['model_has_permissions'], static function (Blueprint $table) use ($tableNames, $columnNames, $pivotPermission, $teams): void { $table->unsignedBigInteger($pivotPermission); $table->string('model_type'); @@ -75,7 +76,7 @@ public function up(): void } }); - Schema::create($tableNames['model_has_roles'], function (Blueprint $table) use ($tableNames, $columnNames, $pivotRole, $teams): void { + Schema::create($tableNames['model_has_roles'], static function (Blueprint $table) use ($tableNames, $columnNames, $pivotRole, $teams): void { $table->unsignedBigInteger($pivotRole); $table->string('model_type'); @@ -86,6 +87,7 @@ public function up(): void ->references('id') ->on($tableNames['roles']) ->onDelete('cascade'); + if ($teams) { $table->unsignedBigInteger($columnNames['team_foreign_key']); $table->index($columnNames['team_foreign_key'], 'model_has_roles_team_foreign_key_index'); @@ -102,7 +104,7 @@ public function up(): void } }); - Schema::create($tableNames['role_has_permissions'], function (Blueprint $table) use ($tableNames, $pivotRole, $pivotPermission): void { + Schema::create($tableNames['role_has_permissions'], static function (Blueprint $table) use ($tableNames, $pivotRole, $pivotPermission): void { $table->unsignedBigInteger($pivotPermission); $table->unsignedBigInteger($pivotRole); @@ -119,18 +121,17 @@ public function up(): void $table->primary([$pivotPermission, $pivotRole], 'role_has_permissions_permission_id_role_id_primary'); }); - app('cache') + resolve(Factory::class) ->store(config('permission.cache.store') !== 'default' ? config('permission.cache.store') : null) ->forget(config('permission.cache.key')); } public function down(): void { + /** @var array $tableNames */ $tableNames = config('permission.table_names'); - if (empty($tableNames)) { - throw new Exception('Error: config/permission.php not found and defaults could not be merged. Please publish the package configuration before proceeding, or drop the tables manually.'); - } + throw_if(blank($tableNames), Exception::class, 'Error: config/permission.php not found and defaults could not be merged. Please publish the package configuration before proceeding, or drop the tables manually.'); Schema::drop($tableNames['role_has_permissions']); Schema::drop($tableNames['model_has_roles']); @@ -138,4 +139,4 @@ public function down(): void Schema::drop($tableNames['roles']); Schema::drop($tableNames['permissions']); } -} +}; diff --git a/database/migrations/2021_10_02_062027_create_reactions_table.php b/database/migrations/2021_10_02_062027_create_reactions_table.php index 0c1e5220..1c3cbdbd 100644 --- a/database/migrations/2021_10_02_062027_create_reactions_table.php +++ b/database/migrations/2021_10_02_062027_create_reactions_table.php @@ -6,13 +6,15 @@ use Illuminate\Database\Schema\Blueprint; use Illuminate\Support\Facades\Schema; -final class CreateReactionsTable extends Migration +return new class extends Migration { public function up(): void { - Schema::create('reactions', function (Blueprint $table): void { + Schema::create('reactions', static function (Blueprint $table): void { $table->id(); + $table->string('name'); + $table->timestamps(); }); } @@ -21,4 +23,4 @@ public function down(): void { Schema::dropIfExists('reactions'); } -} +}; diff --git a/database/migrations/2021_10_02_062115_create_reactables_table.php b/database/migrations/2021_10_02_062115_create_reactables_table.php index 4511b875..41adbddd 100644 --- a/database/migrations/2021_10_02_062115_create_reactables_table.php +++ b/database/migrations/2021_10_02_062115_create_reactables_table.php @@ -6,13 +6,14 @@ use Illuminate\Database\Schema\Blueprint; use Illuminate\Support\Facades\Schema; -final class CreateReactablesTable extends Migration +return new class extends Migration { public function up(): void { - Schema::create('reactables', function (Blueprint $table): void { + Schema::create('reactables', static function (Blueprint $table): void { $table->id(); $table->foreignId('reaction_id')->constrained(); + $table->morphs('reactable'); $table->morphs('responder'); }); @@ -22,4 +23,4 @@ public function down(): void { Schema::dropIfExists('reactables'); } -} +}; diff --git a/database/migrations/2021_10_15_184151_create_media_table.php b/database/migrations/2021_10_15_184151_create_media_table.php index 5388c164..4883689a 100644 --- a/database/migrations/2021_10_15_184151_create_media_table.php +++ b/database/migrations/2021_10_15_184151_create_media_table.php @@ -6,15 +6,15 @@ use Illuminate\Database\Schema\Blueprint; use Illuminate\Support\Facades\Schema; -final class CreateMediaTable extends Migration +return new class extends Migration { public function up(): void { - Schema::create('media', function (Blueprint $table): void { + Schema::create('media', static function (Blueprint $table): void { $table->bigIncrements('id'); $table->morphs('model'); - $table->uuid('uuid')->nullable()->unique(); + $table->uuid()->nullable()->unique(); $table->string('collection_name'); $table->string('name'); $table->string('file_name'); @@ -36,4 +36,4 @@ public function down(): void { Schema::dropIfExists('media'); } -} +}; diff --git a/database/migrations/2021_10_19_220042_create_sessions_table.php b/database/migrations/2021_10_19_220042_create_sessions_table.php index c6a33aea..8f3ddb98 100644 --- a/database/migrations/2021_10_19_220042_create_sessions_table.php +++ b/database/migrations/2021_10_19_220042_create_sessions_table.php @@ -6,13 +6,14 @@ use Illuminate\Database\Schema\Blueprint; use Illuminate\Support\Facades\Schema; -final class CreateSessionsTable extends Migration +return new class extends Migration { public function up(): void { - Schema::create('sessions', function (Blueprint $table): void { + Schema::create('sessions', static function (Blueprint $table): void { $table->string('id')->primary(); $table->foreignId('user_id')->nullable()->index(); + $table->string('ip_address', 45)->nullable(); $table->text('user_agent')->nullable(); $table->text('payload'); @@ -24,4 +25,4 @@ public function down(): void { Schema::dropIfExists('sessions'); } -} +}; diff --git a/database/migrations/2021_11_01_153134_create_activities_table.php b/database/migrations/2021_11_01_153134_create_activities_table.php index 71bf22ce..26b80296 100644 --- a/database/migrations/2021_11_01_153134_create_activities_table.php +++ b/database/migrations/2021_11_01_153134_create_activities_table.php @@ -6,16 +6,18 @@ use Illuminate\Database\Schema\Blueprint; use Illuminate\Support\Facades\Schema; -final class CreateActivitiesTable extends Migration +return new class extends Migration { public function up(): void { - Schema::create('activities', function (Blueprint $table): void { + Schema::create('activities', static function (Blueprint $table): void { $table->id(); + $table->foreignId('user_id')->constrained(); + $table->morphs('subject'); $table->string('type', 50); $table->json('data')->nullable(); - $table->foreignId('user_id')->constrained(); + $table->timestamps(); }); } @@ -24,4 +26,4 @@ public function down(): void { Schema::dropIfExists('activities'); } -} +}; diff --git a/database/migrations/2021_11_04_094343_create_views_table.php b/database/migrations/2021_11_04_094343_create_views_table.php index 162db90f..0eb0a0db 100644 --- a/database/migrations/2021_11_04_094343_create_views_table.php +++ b/database/migrations/2021_11_04_094343_create_views_table.php @@ -6,7 +6,7 @@ use Illuminate\Database\Schema\Blueprint; use Illuminate\Support\Facades\Schema; -final class CreateViewsTable extends Migration +return new class extends Migration { /** * The database schema. @@ -15,9 +15,6 @@ final class CreateViewsTable extends Migration */ protected $schema; - /** - * The table name. - */ protected string $table; public function __construct() @@ -33,9 +30,11 @@ public function up(): void { $this->schema->create($this->table, function (Blueprint $table): void { $table->bigIncrements('id'); + $table->morphs('viewable'); $table->text('visitor')->nullable(); $table->string('collection')->nullable(); + $table->timestamp('viewed_at')->useCurrent(); }); } @@ -44,4 +43,4 @@ public function down(): void { Schema::dropIfExists($this->table); } -} +}; diff --git a/database/migrations/2021_11_05_132241_create_channels_table.php b/database/migrations/2021_11_05_132241_create_channels_table.php index d4731f55..f7aad0f8 100644 --- a/database/migrations/2021_11_05_132241_create_channels_table.php +++ b/database/migrations/2021_11_05_132241_create_channels_table.php @@ -6,16 +6,21 @@ use Illuminate\Database\Schema\Blueprint; use Illuminate\Support\Facades\Schema; -final class CreateChannelsTable extends Migration +return new class extends Migration { public function up(): void { - Schema::create('channels', function (Blueprint $table): void { + Schema::create('channels', static function (Blueprint $table): void { $table->id(); + $table->foreignId('parent_id') + ->nullable() + ->constrained('channels') + ->nullOnDelete(); + $table->string('name'); $table->string('slug')->unique(); - $table->foreignId('parent_id')->nullable()->constrained('channels')->nullOnDelete(); $table->string('color')->nullable(); + $table->timestamps(); }); } @@ -24,4 +29,4 @@ public function down(): void { Schema::dropIfExists('channels'); } -} +}; diff --git a/database/migrations/2021_11_06_062351_create_threads_table.php b/database/migrations/2021_11_06_062351_create_threads_table.php index 7d6535a7..bceb675a 100644 --- a/database/migrations/2021_11_06_062351_create_threads_table.php +++ b/database/migrations/2021_11_06_062351_create_threads_table.php @@ -6,24 +6,26 @@ use Illuminate\Database\Schema\Blueprint; use Illuminate\Support\Facades\Schema; -final class CreateThreadsTable extends Migration +return new class extends Migration { public function up(): void { - Schema::create('threads', function (Blueprint $table): void { + Schema::create('threads', static function (Blueprint $table): void { $table->id(); $table->foreignId('user_id')->constrained()->cascadeOnDelete(); + $table->unsignedBigInteger('solution_reply_id')->index()->nullable(); + $table->foreignId('resolved_by')->nullable()->constrained('users'); + $table->string('title'); $table->string('slug')->unique(); $table->text('body'); - $table->unsignedBigInteger('solution_reply_id')->index()->nullable(); - $table->foreignId('resolved_by')->nullable()->constrained('users'); $table->boolean('locked')->default(false); + $table->timestamp('last_posted_at')->useCurrent(); $table->timestamps(); }); - Schema::create('channel_thread', function (Blueprint $table): void { + Schema::create('channel_thread', static function (Blueprint $table): void { $table->foreignId('channel_id')->constrained(); $table->foreignId('thread_id')->constrained(); }); @@ -34,4 +36,4 @@ public function down(): void Schema::dropIfExists('channel_thread'); Schema::dropIfExists('threads'); } -} +}; diff --git a/database/migrations/2021_11_06_073610_create_replies_table.php b/database/migrations/2021_11_06_073610_create_replies_table.php index fcc9c597..57f2dfaa 100644 --- a/database/migrations/2021_11_06_073610_create_replies_table.php +++ b/database/migrations/2021_11_06_073610_create_replies_table.php @@ -6,15 +6,17 @@ use Illuminate\Database\Schema\Blueprint; use Illuminate\Support\Facades\Schema; -final class CreateRepliesTable extends Migration +return new class extends Migration { public function up(): void { - Schema::create('replies', function (Blueprint $table): void { + Schema::create('replies', static function (Blueprint $table): void { $table->id(); $table->foreignId('user_id')->constrained()->cascadeOnDelete(); + $table->morphs('replyable'); $table->text('body'); + $table->timestamps(); }); } @@ -23,4 +25,4 @@ public function down(): void { Schema::dropIfExists('replies'); } -} +}; diff --git a/database/migrations/2021_11_11_084729_create_subscribes_table.php b/database/migrations/2021_11_11_084729_create_subscribes_table.php index f5aed4c2..3ba7c14e 100644 --- a/database/migrations/2021_11_11_084729_create_subscribes_table.php +++ b/database/migrations/2021_11_11_084729_create_subscribes_table.php @@ -6,16 +6,17 @@ use Illuminate\Database\Schema\Blueprint; use Illuminate\Support\Facades\Schema; -final class CreateSubscribesTable extends Migration +return new class extends Migration { public function up(): void { - Schema::create('subscribes', function (Blueprint $table): void { - $table->uuid('uuid')->index()->unique(); - $table->primary('uuid'); + Schema::create('subscribes', static function (Blueprint $table): void { + $table->uuid()->primary(); $table->foreignId('user_id')->index()->constrained(); + $table->morphs('subscribeable'); $table->unique(['user_id', 'subscribeable_id', 'subscribeable_type'], 'subscribes_are_unique'); + $table->timestamps(); }); } @@ -24,4 +25,4 @@ public function down(): void { Schema::dropIfExists('subscribes'); } -} +}; diff --git a/database/migrations/2021_11_11_103416_create_notifications_table.php b/database/migrations/2021_11_11_103416_create_notifications_table.php index e7d96b65..7aa87eef 100644 --- a/database/migrations/2021_11_11_103416_create_notifications_table.php +++ b/database/migrations/2021_11_11_103416_create_notifications_table.php @@ -6,15 +6,17 @@ use Illuminate\Database\Schema\Blueprint; use Illuminate\Support\Facades\Schema; -final class CreateNotificationsTable extends Migration +return new class extends Migration { public function up(): void { - Schema::create('notifications', function (Blueprint $table): void { + Schema::create('notifications', static function (Blueprint $table): void { $table->uuid('id')->primary(); + $table->string('type'); $table->morphs('notifiable'); $table->text('data'); + $table->timestamp('read_at')->nullable(); $table->timestamps(); }); @@ -24,4 +26,4 @@ public function down(): void { Schema::dropIfExists('notifications'); } -} +}; diff --git a/database/migrations/2021_11_16_191729_create_discussions_table.php b/database/migrations/2021_11_16_191729_create_discussions_table.php index ce65076e..4ab6880a 100644 --- a/database/migrations/2021_11_16_191729_create_discussions_table.php +++ b/database/migrations/2021_11_16_191729_create_discussions_table.php @@ -6,18 +6,20 @@ use Illuminate\Database\Schema\Blueprint; use Illuminate\Support\Facades\Schema; -final class CreateDiscussionsTable extends Migration +return new class extends Migration { public function up(): void { - Schema::create('discussions', function (Blueprint $table): void { + Schema::create('discussions', static function (Blueprint $table): void { $table->id(); $table->foreignId('user_id')->constrained()->cascadeOnDelete(); + $table->string('title'); $table->string('slug')->unique(); $table->text('body'); $table->boolean('is_pinned')->default(false); $table->boolean('locked')->default(false); + $table->timestamps(); }); } @@ -26,4 +28,4 @@ public function down(): void { Schema::dropIfExists('discussions'); } -} +}; diff --git a/database/migrations/2021_11_29_114833_add_linkedin_profile_column_to_users_table.php b/database/migrations/2021_11_29_114833_add_linkedin_profile_column_to_users_table.php index 917a64e4..b6a1e9c8 100644 --- a/database/migrations/2021_11_29_114833_add_linkedin_profile_column_to_users_table.php +++ b/database/migrations/2021_11_29_114833_add_linkedin_profile_column_to_users_table.php @@ -6,11 +6,11 @@ use Illuminate\Database\Schema\Blueprint; use Illuminate\Support\Facades\Schema; -final class AddLinkedinProfileColumnToUsersTable extends Migration +return new class extends Migration { public function up(): void { - Schema::table('users', function (Blueprint $table): void { + Schema::table('users', static function (Blueprint $table): void { $table->string('linkedin_profile') ->after('twitter_profile') ->nullable(); @@ -19,8 +19,8 @@ public function up(): void public function down(): void { - Schema::table('users', function (Blueprint $table): void { + Schema::table('users', static function (Blueprint $table): void { $table->dropColumn('linkedin_profile'); }); } -} +}; diff --git a/database/migrations/2024_12_31_093231_1_create_mails_table.php b/database/migrations/2024_12_31_093231_1_create_mails_table.php deleted file mode 100644 index d74b4b3c..00000000 --- a/database/migrations/2024_12_31_093231_1_create_mails_table.php +++ /dev/null @@ -1,40 +0,0 @@ -id(); - $table->string('uuid')->nullable()->index(); - $table->string('mail_class')->nullable()->index(); - $table->string('subject')->nullable(); - $table->json('from')->nullable(); - $table->json('reply_to')->nullable(); - $table->json('to')->nullable(); - $table->json('cc')->nullable(); - $table->json('bcc')->nullable(); - $table->text('html')->nullable(); - $table->text('text')->nullable(); - $table->unsignedBigInteger('opens')->default(0); - $table->unsignedBigInteger('clicks')->default(0); - $table->timestamp('sent_at')->nullable(); - $table->timestamp('resent_at')->nullable(); - $table->timestamp('accepted_at')->nullable(); - $table->timestamp('delivered_at')->nullable(); - $table->timestamp('last_opened_at')->nullable(); - $table->timestamp('last_clicked_at')->nullable(); - $table->timestamp('complained_at')->nullable(); - $table->timestamp('soft_bounced_at')->nullable(); - $table->timestamp('hard_bounced_at')->nullable(); - $table->timestamp('unsubscribed_at')->nullable(); - $table->timestamps(); - }); - } -}; diff --git a/database/migrations/2024_12_31_093232_2_create_mail_attachments_table.php b/database/migrations/2024_12_31_093232_2_create_mail_attachments_table.php deleted file mode 100644 index 4a48d454..00000000 --- a/database/migrations/2024_12_31_093232_2_create_mail_attachments_table.php +++ /dev/null @@ -1,27 +0,0 @@ -id(); - $table->foreignIdFor(config('mails.models.mail')) - ->constrained() - ->cascadeOnDelete(); - $table->string('disk'); - $table->string('uuid'); - $table->string('filename'); - $table->string('mime'); - $table->boolean('inline', false); - $table->bigInteger('size'); - $table->timestamps(); - }); - } -}; diff --git a/database/migrations/2024_12_31_093233_2_create_mail_events_table.php b/database/migrations/2024_12_31_093233_2_create_mail_events_table.php deleted file mode 100644 index b6d31920..00000000 --- a/database/migrations/2024_12_31_093233_2_create_mail_events_table.php +++ /dev/null @@ -1,34 +0,0 @@ -id(); - $table->foreignIdFor(config('mails.models.mail')) - ->constrained() - ->cascadeOnDelete(); - $table->string('type'); - $table->string('ip_address')->nullable(); - $table->string('hostname')->nullable(); - $table->string('platform')->nullable(); - $table->string('os')->nullable(); - $table->string('browser')->nullable(); - $table->string('user_agent')->nullable(); - $table->string('city')->nullable(); - $table->char('country_code', 2)->nullable(); - $table->string('link')->nullable(); - $table->string('tag')->nullable(); - $table->json('payload')->nullable(); - $table->timestamps(); - $table->timestamp('occurred_at')->nullable(); - }); - } -}; diff --git a/database/migrations/2024_12_31_093234_2_create_mailables_table.php b/database/migrations/2024_12_31_093234_2_create_mailables_table.php deleted file mode 100644 index 7cf22afb..00000000 --- a/database/migrations/2024_12_31_093234_2_create_mailables_table.php +++ /dev/null @@ -1,21 +0,0 @@ -id(); - $table->foreignIdFor(config('mails.models.mail')) - ->constrained() - ->cascadeOnDelete(); - $table->morphs('mailable'); - }); - } -}; diff --git a/database/migrations/2025_04_09_074637_3_add_unsuppressed_at_to_mail_events.php b/database/migrations/2025_04_09_074637_3_add_unsuppressed_at_to_mail_events.php deleted file mode 100644 index 906280fe..00000000 --- a/database/migrations/2025_04_09_074637_3_add_unsuppressed_at_to_mail_events.php +++ /dev/null @@ -1,28 +0,0 @@ -timestamp('unsuppressed_at') - ->nullable() - ->after('occurred_at'); - }); - - Schema::table(config('mails.database.tables.mails', 'mails'), static function (Blueprint $table): void { - $table->string('mailer') - ->after('uuid'); - - $table->string('stream_id') - ->nullable() - ->after('mailer'); - }); - } -}; diff --git a/database/migrations/2025_04_09_074638_4_add_tags_to_mails_table.php b/database/migrations/2025_04_09_074638_4_add_tags_to_mails_table.php deleted file mode 100644 index caa41c3d..00000000 --- a/database/migrations/2025_04_09_074638_4_add_tags_to_mails_table.php +++ /dev/null @@ -1,19 +0,0 @@ -after('clicks', function (Blueprint $table): void { - $table->json('tags')->nullable(); - }); - }); - } -}; diff --git a/database/migrations/2025_04_09_074639_4_add_transport_column_to_mails_table.php b/database/migrations/2025_04_09_074639_4_add_transport_column_to_mails_table.php deleted file mode 100644 index b384911f..00000000 --- a/database/migrations/2025_04_09_074639_4_add_transport_column_to_mails_table.php +++ /dev/null @@ -1,19 +0,0 @@ -string('transport') - ->nullable() - ->after('mailer'); - }); - } -}; diff --git a/database/seeders/Fixtures/ArticleTableSeeder.php b/database/seeders/Fixtures/ArticleTableSeeder.php index c8f5c8d9..d23d33c0 100644 --- a/database/seeders/Fixtures/ArticleTableSeeder.php +++ b/database/seeders/Fixtures/ArticleTableSeeder.php @@ -123,10 +123,10 @@ public function run(): void 'approved_at' => now(), 'show_toc' => false, 'canonical_url' => null, - 'user_id' => (int) array_rand($usersIds), + 'user_id' => array_rand($usersIds), ]); $article1->syncTags(array_rand($tagsIds, 3)); - $article1->addMediaFromUrl("https://unsplash.it/1920/1080?random={$article1->id}") + $article1->addMediaFromUrl('https://unsplash.it/1920/1080?random='.$article1->id) ->toMediaCollection('media'); /** @var Article $article2 */ @@ -390,10 +390,10 @@ public function run(): void 'approved_at' => null, 'show_toc' => true, 'canonical_url' => null, - 'user_id' => (int) array_rand($usersIds), + 'user_id' => array_rand($usersIds), ]); $article2->syncTags(array_rand($tagsIds, 3)); - $article2->addMediaFromUrl("https://unsplash.it/1920/1080?random={$article2->id}") + $article2->addMediaFromUrl('https://unsplash.it/1920/1080?random='.$article2->id) ->toMediaCollection('media'); /** @var Article $article3 */ @@ -513,10 +513,10 @@ public function run(): void 'approved_at' => now()->addHours(3), 'show_toc' => array_rand([true, false]), 'canonical_url' => null, - 'user_id' => (int) array_rand($usersIds), + 'user_id' => array_rand($usersIds), ]); $article3->syncTags(array_rand($tagsIds, 3)); - $article3->addMediaFromUrl("https://unsplash.it/1920/1080?random={$article3->id}") + $article3->addMediaFromUrl('https://unsplash.it/1920/1080?random='.$article3->id) ->toMediaCollection('media'); /** @var Article $article4 */ @@ -1097,10 +1097,10 @@ public function run(): void 'approved_at' => now()->addDay()->addHours(2), 'show_toc' => true, 'canonical_url' => null, - 'user_id' => (int) array_rand($usersIds), + 'user_id' => array_rand($usersIds), ]); $article4->syncTags(array_rand($tagsIds, 3)); - $article4->addMediaFromUrl("https://unsplash.it/1920/1080?random={$article4->id}") + $article4->addMediaFromUrl('https://unsplash.it/1920/1080?random='.$article4->id) ->toMediaCollection('media'); /** @var Article $article5 */ @@ -1171,10 +1171,10 @@ public function run(): void 'approved_at' => now()->addHours(5), 'show_toc' => false, 'canonical_url' => null, - 'user_id' => (int) array_rand($usersIds), + 'user_id' => array_rand($usersIds), ]); $article5->syncTags(array_rand($tagsIds, 3)); - $article5->addMediaFromUrl("https://unsplash.it/1920/1080?random={$article5->id}") + $article5->addMediaFromUrl('https://unsplash.it/1920/1080?random='.$article5->id) ->toMediaCollection('media'); /** @var Article $article6 */ @@ -1249,10 +1249,10 @@ public function run(): void 'approved_at' => now()->subHour(), 'show_toc' => false, 'canonical_url' => null, - 'user_id' => (int) array_rand($usersIds), + 'user_id' => array_rand($usersIds), ]); $article6->syncTags(array_rand($tagsIds, 3)); - $article6->addMediaFromUrl("https://unsplash.it/1920/1080?random={$article6->id}") + $article6->addMediaFromUrl('https://unsplash.it/1920/1080?random='.$article6->id) ->toMediaCollection('media'); } } diff --git a/database/seeders/Fixtures/DiscussionTableSeeder.php b/database/seeders/Fixtures/DiscussionTableSeeder.php index 43270843..d5f26f4a 100644 --- a/database/seeders/Fixtures/DiscussionTableSeeder.php +++ b/database/seeders/Fixtures/DiscussionTableSeeder.php @@ -22,7 +22,7 @@ public function run(): void ->get() ->modelKeys(); $discussions = Discussion::factory()->count(20)->create([ - 'user_id' => (int) array_rand($usersIds), + 'user_id' => array_rand($usersIds), ]); foreach ($discussions as $discussion) { diff --git a/database/seeders/TagSeeder.php b/database/seeders/TagSeeder.php index ac0e7bf0..ce66d52d 100644 --- a/database/seeders/TagSeeder.php +++ b/database/seeders/TagSeeder.php @@ -44,6 +44,6 @@ public function run(): void private function createTag(string $name, string $slug, array $concerns = []): void { - Tag::query()->create(compact('name', 'slug', 'concerns')); + Tag::query()->create(['name' => $name, 'slug' => $slug, 'concerns' => $concerns]); } } diff --git a/docker-compose.prod.yml b/docker-compose.prod.yml index 8ba81b4b..45aa777c 100644 --- a/docker-compose.prod.yml +++ b/docker-compose.prod.yml @@ -9,9 +9,9 @@ services: image: laravelcm/php restart: always extra_hosts: - - 'host.docker.internal:host-gateway' + - "host.docker.internal:host-gateway" healthcheck: - test: ['CMD', 'curl', '-f', 'http://localhost:${APP_PORT:-8080}/up'] + test: ["CMD", "curl", "-f", "http://localhost:${APP_PORT:-8080}/up"] timeout: 30s environment: APP_NAME: ${APP_NAME} @@ -49,7 +49,7 @@ services: CACHE_STORE: ${CACHE_STORE} CACHE_PREFIX: ${CACHE_PREFIX} MEMCACHED_HOST: ${MEMCACHED_HOST} - REDIS_CLIENT: 'phpredis' + REDIS_CLIENT: "phpredis" REDIS_HOST: ${REDIS_HOST} REDIS_PASSWORD: ${REDIS_PASSWORD} REDIS_PORT: ${REDIS_PORT} @@ -103,24 +103,24 @@ services: UNSPLASH_ACCESS_KEY: ${UNSPLASH_ACCESS_KEY} NIGHTWATCH_TOKEN: ${NIGHTWATCH_TOKEN} NIGHTWATCH_REQUEST_SAMPLE_RATE: ${NIGHTWATCH_REQUEST_SAMPLE_RATE} - SSH_TUNNEL_USER: '${SSH_TUNNEL_USER:-}' - SSH_TUNNEL_HOSTNAME: '${SSH_TUNNEL_HOSTNAME:-}' - SSH_TUNNEL_PORT: '${SSH_TUNNEL_PORT:-22}' - SSH_TUNNEL_LOCAL_PORT: '${SSH_TUNNEL_LOCAL_PORT:-3307}' - SSH_TUNNEL_BIND_ADDRESS: '${SSH_TUNNEL_BIND_ADDRESS:-127.0.0.1}' - SSH_TUNNEL_BIND_PORT: '${SSH_TUNNEL_BIND_PORT:-3306}' - SSH_TUNNEL_VERIFY_PROCESS: '${SSH_TUNNEL_VERIFY_PROCESS:-bash}' - SSH_TUNNEL_IDENTITY_FILE: '${SSH_TUNNEL_IDENTITY_FILE:-}' - SSH_TUNNEL_PRIVATE_KEY: '${SSH_TUNNEL_PRIVATE_KEY:-}' - SSH_TUNNEL_VERBOSITY: '${SSH_TUNNEL_VERBOSITY:--vvv}' - DB_CONNECTION_SECOND: '${DB_CONNECTION_SECOND:-mysql}' - DB_HOST_SECOND: '${DB_HOST_SECOND:-127.0.0.1}' - DB_PORT_SECOND: '${DB_PORT_SECOND:-3307}' - DB_DATABASE_SECOND: '${DB_DATABASE_SECOND:-}' - DB_USERNAME_SECOND: '${DB_USERNAME_SECOND:-}' - DB_PASSWORD_SECOND: '${DB_PASSWORD_SECOND:-}' - BOOST_BROWSER_LOGS_WATCHER: '${BOOST_BROWSER_LOGS_WATCHER:-false}' - OCTANE_SERVER: 'frankenphp' + SSH_TUNNEL_USER: "${SSH_TUNNEL_USER:-}" + SSH_TUNNEL_HOSTNAME: "${SSH_TUNNEL_HOSTNAME:-}" + SSH_TUNNEL_PORT: "${SSH_TUNNEL_PORT:-22}" + SSH_TUNNEL_LOCAL_PORT: "${SSH_TUNNEL_LOCAL_PORT:-3307}" + SSH_TUNNEL_BIND_ADDRESS: "${SSH_TUNNEL_BIND_ADDRESS:-127.0.0.1}" + SSH_TUNNEL_BIND_PORT: "${SSH_TUNNEL_BIND_PORT:-3306}" + SSH_TUNNEL_VERIFY_PROCESS: "${SSH_TUNNEL_VERIFY_PROCESS:-bash}" + SSH_TUNNEL_IDENTITY_FILE: "${SSH_TUNNEL_IDENTITY_FILE:-}" + SSH_TUNNEL_PRIVATE_KEY: "${SSH_TUNNEL_PRIVATE_KEY:-}" + SSH_TUNNEL_VERBOSITY: "${SSH_TUNNEL_VERBOSITY:--vvv}" + DB_CONNECTION_SECOND: "${DB_CONNECTION_SECOND:-mysql}" + DB_HOST_SECOND: "${DB_HOST_SECOND:-127.0.0.1}" + DB_PORT_SECOND: "${DB_PORT_SECOND:-3307}" + DB_DATABASE_SECOND: "${DB_DATABASE_SECOND:-}" + DB_USERNAME_SECOND: "${DB_USERNAME_SECOND:-}" + DB_PASSWORD_SECOND: "${DB_PASSWORD_SECOND:-}" + BOOST_BROWSER_LOGS_WATCHER: "${BOOST_BROWSER_LOGS_WATCHER:-false}" + OCTANE_SERVER: "frankenphp" CADDY_GLOBAL_OPTIONS: | servers { trusted_proxies static private_ranges @@ -139,13 +139,14 @@ services: - storage:/var/www/html/storage networks: - dokploy-network + schedule: image: laravelcm/php restart: always - command: ['artisan', 'schedule:run'] + command: ["artisan", "schedule:run"] stop_signal: SIGTERM healthcheck: - test: ['CMD', 'healthcheck-schedule'] + test: ["CMD", "healthcheck-schedule"] start_period: 10s environment: APP_NAME: ${APP_NAME} @@ -183,7 +184,7 @@ services: CACHE_STORE: ${CACHE_STORE} CACHE_PREFIX: ${CACHE_PREFIX} MEMCACHED_HOST: ${MEMCACHED_HOST} - REDIS_CLIENT: 'phpredis' + REDIS_CLIENT: "phpredis" REDIS_HOST: ${REDIS_HOST} REDIS_PASSWORD: ${REDIS_PASSWORD} REDIS_PORT: ${REDIS_PORT} @@ -237,44 +238,45 @@ services: UNSPLASH_ACCESS_KEY: ${UNSPLASH_ACCESS_KEY} NIGHTWATCH_TOKEN: ${NIGHTWATCH_TOKEN} NIGHTWATCH_REQUEST_SAMPLE_RATE: ${NIGHTWATCH_REQUEST_SAMPLE_RATE} - SSH_TUNNEL_USER: '${SSH_TUNNEL_USER:-}' - SSH_TUNNEL_HOSTNAME: '${SSH_TUNNEL_HOSTNAME:-}' - SSH_TUNNEL_PORT: '${SSH_TUNNEL_PORT:-22}' - SSH_TUNNEL_LOCAL_PORT: '${SSH_TUNNEL_LOCAL_PORT:-3307}' - SSH_TUNNEL_BIND_ADDRESS: '${SSH_TUNNEL_BIND_ADDRESS:-127.0.0.1}' - SSH_TUNNEL_BIND_PORT: '${SSH_TUNNEL_BIND_PORT:-3306}' - SSH_TUNNEL_VERIFY_PROCESS: '${SSH_TUNNEL_VERIFY_PROCESS:-bash}' - SSH_TUNNEL_IDENTITY_FILE: '${SSH_TUNNEL_IDENTITY_FILE:-}' - SSH_TUNNEL_PRIVATE_KEY: '${SSH_TUNNEL_PRIVATE_KEY:-}' - SSH_TUNNEL_VERBOSITY: '${SSH_TUNNEL_VERBOSITY:--vvv}' - DB_CONNECTION_SECOND: '${DB_CONNECTION_SECOND:-mysql}' - DB_HOST_SECOND: '${DB_HOST_SECOND:-127.0.0.1}' - DB_PORT_SECOND: '${DB_PORT_SECOND:-3307}' - DB_DATABASE_SECOND: '${DB_DATABASE_SECOND:-}' - DB_USERNAME_SECOND: '${DB_USERNAME_SECOND:-}' - DB_PASSWORD_SECOND: '${DB_PASSWORD_SECOND:-}' - BOOST_BROWSER_LOGS_WATCHER: '${BOOST_BROWSER_LOGS_WATCHER:-false}' - OCTANE_SERVER: 'frankenphp' + SSH_TUNNEL_USER: "${SSH_TUNNEL_USER:-}" + SSH_TUNNEL_HOSTNAME: "${SSH_TUNNEL_HOSTNAME:-}" + SSH_TUNNEL_PORT: "${SSH_TUNNEL_PORT:-22}" + SSH_TUNNEL_LOCAL_PORT: "${SSH_TUNNEL_LOCAL_PORT:-3307}" + SSH_TUNNEL_BIND_ADDRESS: "${SSH_TUNNEL_BIND_ADDRESS:-127.0.0.1}" + SSH_TUNNEL_BIND_PORT: "${SSH_TUNNEL_BIND_PORT:-3306}" + SSH_TUNNEL_VERIFY_PROCESS: "${SSH_TUNNEL_VERIFY_PROCESS:-bash}" + SSH_TUNNEL_IDENTITY_FILE: "${SSH_TUNNEL_IDENTITY_FILE:-}" + SSH_TUNNEL_PRIVATE_KEY: "${SSH_TUNNEL_PRIVATE_KEY:-}" + SSH_TUNNEL_VERBOSITY: "${SSH_TUNNEL_VERBOSITY:--vvv}" + DB_CONNECTION_SECOND: "${DB_CONNECTION_SECOND:-mysql}" + DB_HOST_SECOND: "${DB_HOST_SECOND:-127.0.0.1}" + DB_PORT_SECOND: "${DB_PORT_SECOND:-3307}" + DB_DATABASE_SECOND: "${DB_DATABASE_SECOND:-}" + DB_USERNAME_SECOND: "${DB_USERNAME_SECOND:-}" + DB_PASSWORD_SECOND: "${DB_PASSWORD_SECOND:-}" + BOOST_BROWSER_LOGS_WATCHER: "${BOOST_BROWSER_LOGS_WATCHER:-false}" + OCTANE_SERVER: "frankenphp" volumes: - storage:/var/www/html/storage networks: - dokploy-network depends_on: - laravelcm + queue: image: laravelcm/php restart: always command: - - 'artisan' - - 'queue:listen' - - 'database' - - '--sleep=10' - - '--quiet' - - '--force' - - '--queue=default,media' + - "artisan" + - "queue:listen" + - "database" + - "--sleep=10" + - "--quiet" + - "--force" + - "--queue=default,media" stop_signal: SIGTERM healthcheck: - test: ['CMD', 'healthcheck-queue'] + test: ["CMD", "healthcheck-queue"] start_period: 10s environment: APP_NAME: ${APP_NAME} @@ -312,7 +314,7 @@ services: CACHE_STORE: ${CACHE_STORE} CACHE_PREFIX: ${CACHE_PREFIX} MEMCACHED_HOST: ${MEMCACHED_HOST} - REDIS_CLIENT: 'phpredis' + REDIS_CLIENT: "phpredis" REDIS_HOST: ${REDIS_HOST} REDIS_PASSWORD: ${REDIS_PASSWORD} REDIS_PORT: ${REDIS_PORT} @@ -366,30 +368,31 @@ services: UNSPLASH_ACCESS_KEY: ${UNSPLASH_ACCESS_KEY} NIGHTWATCH_TOKEN: ${NIGHTWATCH_TOKEN} NIGHTWATCH_REQUEST_SAMPLE_RATE: ${NIGHTWATCH_REQUEST_SAMPLE_RATE} - SSH_TUNNEL_USER: '${SSH_TUNNEL_USER:-}' - SSH_TUNNEL_HOSTNAME: '${SSH_TUNNEL_HOSTNAME:-}' - SSH_TUNNEL_PORT: '${SSH_TUNNEL_PORT:-22}' - SSH_TUNNEL_LOCAL_PORT: '${SSH_TUNNEL_LOCAL_PORT:-3307}' - SSH_TUNNEL_BIND_ADDRESS: '${SSH_TUNNEL_BIND_ADDRESS:-127.0.0.1}' - SSH_TUNNEL_BIND_PORT: '${SSH_TUNNEL_BIND_PORT:-3306}' - SSH_TUNNEL_VERIFY_PROCESS: '${SSH_TUNNEL_VERIFY_PROCESS:-bash}' - SSH_TUNNEL_IDENTITY_FILE: '${SSH_TUNNEL_IDENTITY_FILE:-}' - SSH_TUNNEL_PRIVATE_KEY: '${SSH_TUNNEL_PRIVATE_KEY:-}' - SSH_TUNNEL_VERBOSITY: '${SSH_TUNNEL_VERBOSITY:--vvv}' - DB_CONNECTION_SECOND: '${DB_CONNECTION_SECOND:-mysql}' - DB_HOST_SECOND: '${DB_HOST_SECOND:-127.0.0.1}' - DB_PORT_SECOND: '${DB_PORT_SECOND:-3307}' - DB_DATABASE_SECOND: '${DB_DATABASE_SECOND:-}' - DB_USERNAME_SECOND: '${DB_USERNAME_SECOND:-}' - DB_PASSWORD_SECOND: '${DB_PASSWORD_SECOND:-}' - BOOST_BROWSER_LOGS_WATCHER: '${BOOST_BROWSER_LOGS_WATCHER:-false}' - OCTANE_SERVER: 'frankenphp' + SSH_TUNNEL_USER: "${SSH_TUNNEL_USER:-}" + SSH_TUNNEL_HOSTNAME: "${SSH_TUNNEL_HOSTNAME:-}" + SSH_TUNNEL_PORT: "${SSH_TUNNEL_PORT:-22}" + SSH_TUNNEL_LOCAL_PORT: "${SSH_TUNNEL_LOCAL_PORT:-3307}" + SSH_TUNNEL_BIND_ADDRESS: "${SSH_TUNNEL_BIND_ADDRESS:-127.0.0.1}" + SSH_TUNNEL_BIND_PORT: "${SSH_TUNNEL_BIND_PORT:-3306}" + SSH_TUNNEL_VERIFY_PROCESS: "${SSH_TUNNEL_VERIFY_PROCESS:-bash}" + SSH_TUNNEL_IDENTITY_FILE: "${SSH_TUNNEL_IDENTITY_FILE:-}" + SSH_TUNNEL_PRIVATE_KEY: "${SSH_TUNNEL_PRIVATE_KEY:-}" + SSH_TUNNEL_VERBOSITY: "${SSH_TUNNEL_VERBOSITY:--vvv}" + DB_CONNECTION_SECOND: "${DB_CONNECTION_SECOND:-mysql}" + DB_HOST_SECOND: "${DB_HOST_SECOND:-127.0.0.1}" + DB_PORT_SECOND: "${DB_PORT_SECOND:-3307}" + DB_DATABASE_SECOND: "${DB_DATABASE_SECOND:-}" + DB_USERNAME_SECOND: "${DB_USERNAME_SECOND:-}" + DB_PASSWORD_SECOND: "${DB_PASSWORD_SECOND:-}" + BOOST_BROWSER_LOGS_WATCHER: "${BOOST_BROWSER_LOGS_WATCHER:-false}" + OCTANE_SERVER: "frankenphp" volumes: - storage:/var/www/html/storage networks: - dokploy-network depends_on: - laravelcm + networks: dokploy-network: external: true diff --git a/docker-compose.yml b/docker-compose.yml index 7d70476f..71268095 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,24 +1,35 @@ services: traefik: - image: traefik:v3.5 + image: traefik:v3.6 + security_opt: + - no-new-privileges:true command: - - '--api.dashboard=true' - - '--api.insecure=true' - - '--providers.docker=true' - - '--providers.docker.exposedbydefault=false' - - '--providers.docker.network=traefik' - - '--entrypoints.web.address=:80' - - '--entrypoints.websecure.address=:443' - - '--entrypoints.devtools.address=:8000' - - '--serverstransport.insecureskipverify=true' + - --api.dashboard=true + - --api.insecure=true + - --providers.docker=true + - --providers.docker.exposedbydefault=false + - --providers.docker.network=traefik + - --providers.file.directory=/etc/traefik/config + - --providers.file.watch=true + - --entrypoints.web.address=:80 + - --entrypoints.websecure.address=:443 + - --entrypoints.devtools.address=:8000 + - --entrypoints.vite.address=:5173 + - --entrypoints.minio.address=:9000 + - --entrypoints.minio-console.address=:9001 + - -serverstransport.insecureskipverify=true ports: - - '80:80' - - '443:443' - - '8080:8080' - - '8000:8000' + - "80:80" + - "443:443" + - "5173:5173" + - "9000:9000" + - "9001:9001" + - "8000:8000" + - "8080:8080" volumes: - - '/var/run/docker.sock:/var/run/docker.sock:ro' - - 'traefik-letsencrypt:/letsencrypt' + - "/var/run/docker.sock:/var/run/docker.sock:ro" + - "traefik-letsencrypt:/letsencrypt" + - "./.docker/traefik:/etc/traefik/config" networks: - traefik - sail @@ -26,207 +37,244 @@ services: - traefik.enable=true - traefik.http.routers.traefik.rule=Host(`traefik.local`) - traefik.http.services.traefik.loadbalancer.server.port=8080 + laravelcm: build: context: . dockerfile: Dockerfile target: development args: - WWWUSER: '${WWWUSER}' - WWWGROUP: '${WWWGROUP}' - NODE_VERSION: '22' + WWWUSER: "${WWWUSER}" + WWWGROUP: "${WWWGROUP}" + NODE_VERSION: 22 PHP_VERSION: 8.4 image: laravelcm/php extra_hosts: - - 'host.docker.internal:host-gateway' - ports: - - '${VITE_PORT:-5173}:${VITE_PORT:-5173}' + - "host.docker.internal:host-gateway" environment: LARAVEL_SAIL: 1 - XDEBUG_MODE: '${SAIL_XDEBUG_MODE:-off}' - XDEBUG_CONFIG: '${SAIL_XDEBUG_CONFIG:-client_host=host.docker.internal}' - IGNITION_LOCAL_SITES_PATH: '${PWD}' - SUPERVISOR_PHP_COMMAND: "/usr/bin/php -d variables_order=EGPCS /var/www/html/artisan octane:start --server=frankenphp --host=0.0.0.0 --admin-port=2019 --port='${APP_PORT:-80}'" - XDG_CONFIG_HOME: /var/www/html/config - XDG_DATA_HOME: /var/www/html/data - SSH_TUNNEL_USER: '${SSH_TUNNEL_USER:-}' - SSH_TUNNEL_HOSTNAME: '${SSH_TUNNEL_HOSTNAME:-}' - SSH_TUNNEL_PORT: '${SSH_TUNNEL_PORT:-22}' - SSH_TUNNEL_PRIVATE_KEY: '${SSH_TUNNEL_PRIVATE_KEY:-}' - SSH_TUNNEL_LOCAL_PORT: '${SSH_TUNNEL_LOCAL_PORT:-3307}' - SSH_TUNNEL_BIND_ADDRESS: '${SSH_TUNNEL_BIND_ADDRESS:-127.0.0.1}' - SSH_TUNNEL_BIND_PORT: '${SSH_TUNNEL_BIND_PORT:-3306}' - SSH_TUNNEL_VERIFY_PROCESS: '${SSH_TUNNEL_VERIFY_PROCESS:-bash}' - DB_CONNECTION_SECOND: '${DB_CONNECTION_SECOND:-mysql}' - DB_HOST_SECOND: '${DB_HOST_SECOND:-127.0.0.1}' - DB_PORT_SECOND: '${DB_PORT_SECOND:-3307}' - DB_DATABASE_SECOND: '${DB_DATABASE_SECOND:-}' - DB_USERNAME_SECOND: '${DB_USERNAME_SECOND:-}' - DB_PASSWORD_SECOND: '${DB_PASSWORD_SECOND:-}' + AUTORUN_ENABLED: true + OCTANE_SERVER: "${OCTANE_SERVER:-off}" + XDEBUG_MODE: "${SAIL_XDEBUG_MODE:-coverage,debug}" + XDEBUG_CONFIG: "${SAIL_XDEBUG_CONFIG:-client_host=host.docker.internal}" + IGNITION_LOCAL_SITES_PATH: "${PWD}" + FRANKENPHP_WORKER_CONFIG: | + watch app + watch bootstrap + watch {config,database,public,resources,app-modules}/**/*.php + watch routes + watch composer.lock + watch .env + SSH_TUNNEL_USER: "${SSH_TUNNEL_USER:-}" + SSH_TUNNEL_HOSTNAME: "${SSH_TUNNEL_HOSTNAME:-}" + SSH_TUNNEL_PORT: "${SSH_TUNNEL_PORT:-22}" + SSH_TUNNEL_PRIVATE_KEY: "${SSH_TUNNEL_PRIVATE_KEY:-}" + SSH_TUNNEL_LOCAL_PORT: "${SSH_TUNNEL_LOCAL_PORT:-3307}" + SSH_TUNNEL_BIND_ADDRESS: "${SSH_TUNNEL_BIND_ADDRESS:-127.0.0.1}" + SSH_TUNNEL_BIND_PORT: "${SSH_TUNNEL_BIND_PORT:-3306}" + SSH_TUNNEL_VERIFY_PROCESS: "${SSH_TUNNEL_VERIFY_PROCESS:-bash}" + DB_CONNECTION_SECOND: "${DB_CONNECTION_SECOND:-mysql}" + DB_HOST_SECOND: "${DB_HOST_SECOND:-127.0.0.1}" + DB_PORT_SECOND: "${DB_PORT_SECOND:-3307}" + DB_DATABASE_SECOND: "${DB_DATABASE_SECOND:-}" + DB_USERNAME_SECOND: "${DB_USERNAME_SECOND:-}" + DB_PASSWORD_SECOND: "${DB_PASSWORD_SECOND:-}" healthcheck: - test: ['CMD', 'curl', '-f', 'http://localhost:${APP_PORT:-8080}/up'] + test: ["CMD", "curl", "-f", "http://localhost:${APP_PORT:-8080}/up"] timeout: 30s volumes: - - '.:/var/www/html' + - ".:/var/www/html" networks: - sail - traefik depends_on: - - pgsql - - redis - - typesense - - minio - - mailpit - - soketi - - traefik + traefik: + condition: service_started + pgsql: + condition: service_started + redis: + condition: service_started + typesense: + condition: service_started + minio: + condition: service_started + memcached: + condition: service_started + # - soketi labels: - traefik.enable=true - traefik.http.routers.laravelcm.rule=Host(`${APP_DOMAIN:-laravelcm.local}`) - traefik.http.routers.laravelcm.entrypoints=websecure - traefik.http.routers.laravelcm.tls=true + - traefik.http.routers.laravelcm.service=laravelcm - traefik.http.services.laravelcm.loadbalancer.server.port=${APP_PORT:-8080} - traefik.http.routers.laravelcm-insecure.rule=Host(`${APP_DOMAIN:-laravelcm.local}`) - traefik.http.routers.laravelcm-insecure.entrypoints=web + - traefik.http.routers.laravelcm-insecure.service=laravelcm - traefik.http.routers.laravelcm-insecure.middlewares=redirect-to-https - traefik.http.middlewares.redirect-to-https.redirectscheme.scheme=https + - traefik.http.routers.laravelcm-vite.rule=Host(`${APP_DOMAIN}`) + - traefik.http.routers.laravelcm-vite.entrypoints=vite + - traefik.http.routers.laravelcm-vite.service=laravelcm-vite + - traefik.http.routers.laravelcm-vite.tls=true + - traefik.http.routers.laravelcm-vite.middlewares=vite-cors@file + - traefik.http.services.laravelcm-vite.loadbalancer.server.port=5173 + + pgsql: + image: "postgres:17-alpine" + ports: + - "${FORWARD_DB_PORT:-5432}:5432" + environment: + PGPASSWORD: "${DB_PASSWORD:-password}" + POSTGRES_DB: "${DB_DATABASE}" + POSTGRES_USER: "${DB_USERNAME}" + POSTGRES_PASSWORD: "${DB_PASSWORD:-password}" + volumes: + - "sail-pgsql:/var/lib/postgresql/data" + - "./vendor/laravel/sail/database/pgsql/create-testing-database.sql:/docker-entrypoint-initdb.d/10-create-testing-database.sql" + networks: + - sail + healthcheck: + test: + [ + "CMD", + "pg_isready", + "-q", + "-d", + "${DB_DATABASE}", + "-U", + "${DB_USERNAME}", + ] + retries: 3 + timeout: 5s + schedule: image: laravelcm/php - command: ['artisan', 'schedule:run'] + command: ["artisan", "schedule:work"] stop_signal: SIGTERM healthcheck: - test: ['CMD', 'healthcheck-schedule'] + test: ["CMD", "healthcheck-schedule"] start_period: 10s volumes: - - '.:/var/www/html' + - ".:/var/www/html" networks: - sail depends_on: - laravelcm - pgsql - redis + queue: image: laravelcm/php - command: - - 'artisan' - - 'queue:listen' - - 'database' - - '--sleep=10' - - '--quiet' - - '--force' - - '--queue=default,media' + command: ["artisan", "queue:listen", "--tries=3", "--verbose"] stop_signal: SIGTERM healthcheck: - test: ['CMD', 'healthcheck-queue'] + test: ["CMD", "healthcheck-queue"] start_period: 10s volumes: - - '.:/var/www/html' + - ".:/var/www/html" networks: - sail depends_on: - laravelcm - pgsql - redis - pgsql: - image: 'postgres:17-alpine' - ports: - - '${FORWARD_DB_PORT:-5432}:5432' - environment: - PGPASSWORD: '${DB_PASSWORD:-secret}' - POSTGRES_DB: '${DB_DATABASE}' - POSTGRES_USER: '${DB_USERNAME}' - POSTGRES_PASSWORD: '${DB_PASSWORD:-secret}' - volumes: - - 'sail-pgsql:/var/lib/postgresql/data' - - './vendor/laravel/sail/database/pgsql/create-testing-database.sql:/docker-entrypoint-initdb.d/10-create-testing-database.sql' - networks: - - sail - healthcheck: - test: - - CMD - - pg_isready - - '-q' - - '-d' - - '${DB_DATABASE}' - - '-U' - - '${DB_USERNAME}' - retries: 3 - timeout: 5s + redis: - image: 'redis:alpine' + image: "redis:alpine" ports: - - '${FORWARD_REDIS_PORT:-6379}:6379' + - "${FORWARD_REDIS_PORT:-6379}:6379" volumes: - - 'sail-redis:/data' + - "sail-redis:/data" networks: - sail healthcheck: - test: - - CMD - - redis-cli - - ping + test: ["CMD", "redis-cli", "ping"] retries: 3 timeout: 5s + typesense: - image: 'typesense/typesense:27.1' + image: "typesense/typesense:27.1" ports: - - '${FORWARD_TYPESENSE_PORT:-8108}:8108' + - "${FORWARD_TYPESENSE_PORT:-8108}:8108" environment: - TYPESENSE_DATA_DIR: '${TYPESENSE_DATA_DIR:-/typesense-data}' - TYPESENSE_API_KEY: '${TYPESENSE_API_KEY:-xyz}' - TYPESENSE_ENABLE_CORS: '${TYPESENSE_ENABLE_CORS:-true}' + TYPESENSE_DATA_DIR: "${TYPESENSE_DATA_DIR:-/typesense-data}" + TYPESENSE_API_KEY: "${TYPESENSE_API_KEY:-xyz}" + TYPESENSE_ENABLE_CORS: "${TYPESENSE_ENABLE_CORS:-true}" volumes: - - 'sail-typesense:/typesense-data' + - "sail-typesense:/typesense-data" networks: - sail healthcheck: test: - - CMD - - bash - - '-c' - - 'exec 3<>/dev/tcp/localhost/8108 && printf ''GET /health HTTP/1.1\r\nConnection: close\r\n\r\n'' >&3 && head -n1 <&3 | grep ''200'' && exec 3>&-' + [ + "CMD", + "bash", + "-c", + 'exec 3<>/dev/tcp/localhost/8108 && printf ''GET /health HTTP/1.1\r\nConnection: close\r\n\r\n'' >&3 && head -n1 <&3 | grep ''200'' && exec 3>&-', + ] retries: 5 timeout: 7s + minio: - image: 'minio/minio:latest' - ports: - - '${FORWARD_MINIO_PORT:-9000}:9000' - - '${FORWARD_MINIO_CONSOLE_PORT:-8900}:8900' + image: "minio/minio:latest" environment: MINIO_ROOT_USER: sail MINIO_ROOT_PASSWORD: password volumes: - - 'sail-minio:/data' + - "sail-minio:/data" networks: - sail - command: 'minio server /data --console-address ":8900"' + command: 'minio server /data --console-address ":9001"' + labels: + - traefik.enable=true + - traefik.http.routers.minio.rule=Host(`${APP_DOMAIN}`) + - traefik.http.routers.minio.entrypoints=minio + - traefik.http.routers.minio.service=minio + - traefik.http.services.minio.loadbalancer.server.port=9000 + - traefik.http.routers.minio-console.rule=Host(`${APP_DOMAIN}`) + - traefik.http.routers.minio-console.entrypoints=minio-console + - traefik.http.routers.minio-console.service=minio-console + - traefik.http.services.minio-console.loadbalancer.server.port=9001 healthcheck: - test: - - CMD - - mc - - ready - - local + test: ["CMD", "mc", "ready", "local"] retries: 3 timeout: 5s - mailpit: - image: 'axllent/mailpit:latest' - ports: - - '${FORWARD_MAILPIT_PORT:-1025}:1025' - - '${FORWARD_MAILPIT_DASHBOARD_PORT:-8025}:8025' + + minio-create-buckets: + image: minio/mc + depends_on: + minio: + condition: service_healthy + networks: + - sail + entrypoint: > + sh -c " + sleep 5; + mc alias set local http://minio:9000 ${AWS_ACCESS_KEY_ID:-sail} ${AWS_SECRET_ACCESS_KEY:-password}; + mc mb local/${AWS_BUCKET:-sail} || true; + mc anonymous set download local/${AWS_BUCKET:-sail} || true; + " + + memcached: + image: "memcached:alpine" networks: - sail + soketi: - image: 'quay.io/soketi/soketi:latest-16-alpine' + image: "quay.io/soketi/soketi:latest-16-alpine" environment: - SOKETI_DEBUG: '${SOKETI_DEBUG:-1}' - SOKETI_METRICS_SERVER_PORT: '9601' - SOKETI_DEFAULT_APP_ID: '${PUSHER_APP_ID}' - SOKETI_DEFAULT_APP_KEY: '${PUSHER_APP_KEY}' - SOKETI_DEFAULT_APP_SECRET: '${PUSHER_APP_SECRET}' + SOKETI_DEBUG: "${SOKETI_DEBUG:-1}" + SOKETI_METRICS_SERVER_PORT: "9601" + SOKETI_DEFAULT_APP_ID: "${PUSHER_APP_ID}" + SOKETI_DEFAULT_APP_KEY: "${PUSHER_APP_KEY}" + SOKETI_DEFAULT_APP_SECRET: "${PUSHER_APP_SECRET}" ports: - - '${PUSHER_PORT:-6001}:6001' - - '${PUSHER_METRICS_PORT:-9601}:9601' + - "${PUSHER_PORT:-6001}:6001" + - "${PUSHER_METRICS_PORT:-9601}:9601" networks: - sail + buggregator: image: ghcr.io/buggregator/server:latest labels: @@ -239,6 +287,7 @@ services: networks: - sail - traefik + networks: sail: driver: bridge diff --git a/package-lock.json b/package-lock.json index 8744403f..fb1b1a90 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,32 +1,33 @@ { - "name": "laravel.cm", + "name": "html", "lockfileVersion": 3, "requires": true, "packages": { "": { "devDependencies": { - "@alpinejs/collapse": "^3.14.3", - "@alpinejs/intersect": "^3.6.1", - "@awcodes/alpine-floating-ui": "^3.5.0", + "@alpinejs/collapse": "^3.15.3", + "@alpinejs/intersect": "^3.15.3", + "@awcodes/alpine-floating-ui": "^3.6.4", "@ryangjchandler/alpine-tooltip": "^2.0.1", - "@tailwindcss/forms": "^0.5.10", - "@tailwindcss/postcss": "^4.1.13", - "@tailwindcss/typography": "^0.5.18", - "@tailwindcss/vite": "^4.1.13", - "@tailwindplus/elements": "^1.0.13", - "alpinejs": "^3.12.0", - "autoprefixer": "^10.4.16", - "highlight.js": "^11.7.0", + "@tailwindcss/forms": "^0.5.11", + "@tailwindcss/postcss": "^4.1.18", + "@tailwindcss/typography": "^0.5.19", + "@tailwindcss/vite": "^4.1.18", + "@tailwindplus/elements": "^1.0.21", + "alpinejs": "^3.15.3", + "autoprefixer": "^10.4.23", + "concurrently": "^9.2.1", + "dotenv": "^17.2.3", + "highlight.js": "^11.11.1", "laravel-vite-plugin": "^2.0.1", - "lodash": "^4.17.19", - "postcss": "^8.4.32", - "postcss-loader": "^8.1.1", - "postcss-preset-env": "^10.1.0", - "prettier": "^3.2.5", - "prettier-plugin-tailwindcss": "^0.7.1", - "tailwindcss": "^4.1.13", + "lodash": "^4.17.21", + "npm-check-updates": "^19.2.0", + "prettier": "^3.7.4", + "prettier-plugin-organize-imports": "^4.3.0", + "prettier-plugin-tailwindcss": "^0.7.2", + "tailwindcss": "^4.1.18", "tippy.js": "^6.3.7", - "vite": "^7.1.7" + "vite": "^7.3.0" } }, "node_modules/@alloc/quick-lru": { @@ -43,16 +44,16 @@ } }, "node_modules/@alpinejs/collapse": { - "version": "3.15.2", - "resolved": "https://registry.npmjs.org/@alpinejs/collapse/-/collapse-3.15.2.tgz", - "integrity": "sha512-wQZ5vwz3dUuisssIcYgJGxkLl2EnkUmsHQxDvqGEEC3+3m662bbZXGlT3NFiTj7AqrbqHRgtwOdKi2umVFGimQ==", + "version": "3.15.3", + "resolved": "https://registry.npmjs.org/@alpinejs/collapse/-/collapse-3.15.3.tgz", + "integrity": "sha512-nheS20BsFY1Eh1nyW0YNs7RMOiO/LipCTltEplbWunTcgdCeZtD7YPUim5xtbhc+0nJP4SkR7G0axRXaRf4m1g==", "dev": true, "license": "MIT" }, "node_modules/@alpinejs/intersect": { - "version": "3.15.2", - "resolved": "https://registry.npmjs.org/@alpinejs/intersect/-/intersect-3.15.2.tgz", - "integrity": "sha512-Q2X1/u82QXoet4n9+EBpat+CeVZ3TWGkyX+tNPf9SFQER1Y8ou8IZLCEoNIywWoAAbiUtKB8SAeoAZwXhNX4SQ==", + "version": "3.15.3", + "resolved": "https://registry.npmjs.org/@alpinejs/intersect/-/intersect-3.15.3.tgz", + "integrity": "sha512-xzlI03dA+q83tOJHANPz+tobe8Um8wG2rmslTpolbRCrSRnwknvja++YDprn4jZZ9lFgUZz4TKUd7XOrdbvRYA==", "dev": true, "license": "MIT" }, @@ -62,3444 +63,1195 @@ "integrity": "sha512-IjOfHmId6v28we/x92O9ZmtSU37L6Xgtcug2d/JegUeW90M5mmKBz5ujU0r05SnCFJhXZw+nwZpY2um1jJjMlw==", "dev": true }, - "node_modules/@babel/code-frame": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.27.1.tgz", - "integrity": "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==", + "node_modules/@esbuild/aix-ppc64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.2.tgz", + "integrity": "sha512-GZMB+a0mOMZs4MpDbj8RJp4cw+w1WV5NYD6xzgvzUJ5Ek2jerwfO2eADyI6ExDSUED+1X8aMbegahsJi+8mgpw==", + "cpu": [ + "ppc64" + ], "dev": true, "license": "MIT", - "dependencies": { - "@babel/helper-validator-identifier": "^7.27.1", - "js-tokens": "^4.0.0", - "picocolors": "^1.1.1" - }, + "optional": true, + "os": [ + "aix" + ], "engines": { - "node": ">=6.9.0" + "node": ">=18" } }, - "node_modules/@babel/helper-validator-identifier": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.27.1.tgz", - "integrity": "sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow==", + "node_modules/@esbuild/android-arm": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.27.2.tgz", + "integrity": "sha512-DVNI8jlPa7Ujbr1yjU2PfUSRtAUZPG9I1RwW4F4xFB1Imiu2on0ADiI/c3td+KmDtVKNbi+nffGDQMfcIMkwIA==", + "cpu": [ + "arm" + ], "dev": true, "license": "MIT", + "optional": true, + "os": [ + "android" + ], "engines": { - "node": ">=6.9.0" + "node": ">=18" } }, - "node_modules/@csstools/cascade-layer-name-parser": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/@csstools/cascade-layer-name-parser/-/cascade-layer-name-parser-2.0.5.tgz", - "integrity": "sha512-p1ko5eHgV+MgXFVa4STPKpvPxr6ReS8oS2jzTukjR74i5zJNyWO1ZM1m8YKBXnzDKWfBN1ztLYlHxbVemDD88A==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } + "node_modules/@esbuild/android-arm64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.27.2.tgz", + "integrity": "sha512-pvz8ZZ7ot/RBphf8fv60ljmaoydPU12VuXHImtAs0XhLLw+EXBi2BLe3OYSBslR4rryHvweW5gmkKFwTiFy6KA==", + "cpu": [ + "arm64" ], + "dev": true, "license": "MIT", + "optional": true, + "os": [ + "android" + ], "engines": { "node": ">=18" - }, - "peerDependencies": { - "@csstools/css-parser-algorithms": "^3.0.5", - "@csstools/css-tokenizer": "^3.0.4" } }, - "node_modules/@csstools/color-helpers": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/@csstools/color-helpers/-/color-helpers-5.1.0.tgz", - "integrity": "sha512-S11EXWJyy0Mz5SYvRmY8nJYTFFd1LCNV+7cXyAgQtOOuzb4EsgfqDufL+9esx72/eLhsRdGZwaldu/h+E4t4BA==", + "node_modules/@esbuild/android-x64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.27.2.tgz", + "integrity": "sha512-z8Ank4Byh4TJJOh4wpz8g2vDy75zFL0TlZlkUkEwYXuPSgX8yzep596n6mT7905kA9uHZsf/o2OJZubl2l3M7A==", + "cpu": [ + "x64" + ], "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } + "license": "MIT", + "optional": true, + "os": [ + "android" ], - "license": "MIT-0", "engines": { "node": ">=18" } }, - "node_modules/@csstools/css-calc": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/@csstools/css-calc/-/css-calc-2.1.4.tgz", - "integrity": "sha512-3N8oaj+0juUw/1H3YwmDDJXCgTB1gKU6Hc/bB502u9zR0q2vd786XJH9QfrKIEgFlZmhZiq6epXl4rHqhzsIgQ==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } + "node_modules/@esbuild/darwin-arm64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.27.2.tgz", + "integrity": "sha512-davCD2Zc80nzDVRwXTcQP/28fiJbcOwvdolL0sOiOsbwBa72kegmVU0Wrh1MYrbuCL98Omp5dVhQFWRKR2ZAlg==", + "cpu": [ + "arm64" ], + "dev": true, "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], "engines": { "node": ">=18" - }, - "peerDependencies": { - "@csstools/css-parser-algorithms": "^3.0.5", - "@csstools/css-tokenizer": "^3.0.4" } }, - "node_modules/@csstools/css-color-parser": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/@csstools/css-color-parser/-/css-color-parser-3.1.0.tgz", - "integrity": "sha512-nbtKwh3a6xNVIp/VRuXV64yTKnb1IjTAEEh3irzS+HkKjAOYLTGNb9pmVNntZ8iVBHcWDA2Dof0QtPgFI1BaTA==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } + "node_modules/@esbuild/darwin-x64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.27.2.tgz", + "integrity": "sha512-ZxtijOmlQCBWGwbVmwOF/UCzuGIbUkqB1faQRf5akQmxRJ1ujusWsb3CVfk/9iZKr2L5SMU5wPBi1UWbvL+VQA==", + "cpu": [ + "x64" ], + "dev": true, "license": "MIT", - "dependencies": { - "@csstools/color-helpers": "^5.1.0", - "@csstools/css-calc": "^2.1.4" - }, + "optional": true, + "os": [ + "darwin" + ], "engines": { "node": ">=18" - }, - "peerDependencies": { - "@csstools/css-parser-algorithms": "^3.0.5", - "@csstools/css-tokenizer": "^3.0.4" } }, - "node_modules/@csstools/css-parser-algorithms": { - "version": "3.0.5", - "resolved": "https://registry.npmjs.org/@csstools/css-parser-algorithms/-/css-parser-algorithms-3.0.5.tgz", - "integrity": "sha512-DaDeUkXZKjdGhgYaHNJTV9pV7Y9B3b644jCLs9Upc3VeNGg6LWARAT6O+Q+/COo+2gg/bM5rhpMAtf70WqfBdQ==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.2.tgz", + "integrity": "sha512-lS/9CN+rgqQ9czogxlMcBMGd+l8Q3Nj1MFQwBZJyoEKI50XGxwuzznYdwcav6lpOGv5BqaZXqvBSiB/kJ5op+g==", + "cpu": [ + "arm64" ], + "dev": true, "license": "MIT", - "peer": true, + "optional": true, + "os": [ + "freebsd" + ], "engines": { "node": ">=18" - }, - "peerDependencies": { - "@csstools/css-tokenizer": "^3.0.4" } }, - "node_modules/@csstools/css-tokenizer": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/@csstools/css-tokenizer/-/css-tokenizer-3.0.4.tgz", - "integrity": "sha512-Vd/9EVDiu6PPJt9yAh6roZP6El1xHrdvIVGjyBsHR0RYwNHgL7FJPyIIW4fANJNG6FtyZfvlRPpFI4ZM/lubvw==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } + "node_modules/@esbuild/freebsd-x64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.27.2.tgz", + "integrity": "sha512-tAfqtNYb4YgPnJlEFu4c212HYjQWSO/w/h/lQaBK7RbwGIkBOuNKQI9tqWzx7Wtp7bTPaGC6MJvWI608P3wXYA==", + "cpu": [ + "x64" ], + "dev": true, "license": "MIT", - "peer": true, + "optional": true, + "os": [ + "freebsd" + ], "engines": { "node": ">=18" } }, - "node_modules/@csstools/media-query-list-parser": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/@csstools/media-query-list-parser/-/media-query-list-parser-4.0.3.tgz", - "integrity": "sha512-HAYH7d3TLRHDOUQK4mZKf9k9Ph/m8Akstg66ywKR4SFAigjs3yBiUeZtFxywiTm5moZMAp/5W/ZuFnNXXYLuuQ==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } + "node_modules/@esbuild/linux-arm": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.27.2.tgz", + "integrity": "sha512-vWfq4GaIMP9AIe4yj1ZUW18RDhx6EPQKjwe7n8BbIecFtCQG4CfHGaHuh7fdfq+y3LIA2vGS/o9ZBGVxIDi9hw==", + "cpu": [ + "arm" ], + "dev": true, "license": "MIT", + "optional": true, + "os": [ + "linux" + ], "engines": { "node": ">=18" - }, - "peerDependencies": { - "@csstools/css-parser-algorithms": "^3.0.5", - "@csstools/css-tokenizer": "^3.0.4" } }, - "node_modules/@csstools/postcss-alpha-function": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@csstools/postcss-alpha-function/-/postcss-alpha-function-1.0.1.tgz", - "integrity": "sha512-isfLLwksH3yHkFXfCI2Gcaqg7wGGHZZwunoJzEZk0yKYIokgre6hYVFibKL3SYAoR1kBXova8LB+JoO5vZzi9w==", + "node_modules/@esbuild/linux-arm64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.27.2.tgz", + "integrity": "sha512-hYxN8pr66NsCCiRFkHUAsxylNOcAQaxSSkHMMjcpx0si13t1LHFphxJZUiGwojB1a/Hd5OiPIqDdXONia6bhTw==", + "cpu": [ + "arm64" + ], "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } + "license": "MIT", + "optional": true, + "os": [ + "linux" ], - "license": "MIT-0", - "dependencies": { - "@csstools/css-color-parser": "^3.1.0", - "@csstools/css-parser-algorithms": "^3.0.5", - "@csstools/css-tokenizer": "^3.0.4", - "@csstools/postcss-progressive-custom-properties": "^4.2.1", - "@csstools/utilities": "^2.0.0" - }, "engines": { "node": ">=18" - }, - "peerDependencies": { - "postcss": "^8.4" } }, - "node_modules/@csstools/postcss-cascade-layers": { - "version": "5.0.2", - "resolved": "https://registry.npmjs.org/@csstools/postcss-cascade-layers/-/postcss-cascade-layers-5.0.2.tgz", - "integrity": "sha512-nWBE08nhO8uWl6kSAeCx4im7QfVko3zLrtgWZY4/bP87zrSPpSyN/3W3TDqz1jJuH+kbKOHXg5rJnK+ZVYcFFg==", + "node_modules/@esbuild/linux-ia32": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.27.2.tgz", + "integrity": "sha512-MJt5BRRSScPDwG2hLelYhAAKh9imjHK5+NE/tvnRLbIqUWa+0E9N4WNMjmp/kXXPHZGqPLxggwVhz7QP8CTR8w==", + "cpu": [ + "ia32" + ], "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } + "license": "MIT", + "optional": true, + "os": [ + "linux" ], - "license": "MIT-0", - "dependencies": { - "@csstools/selector-specificity": "^5.0.0", - "postcss-selector-parser": "^7.0.0" - }, "engines": { "node": ">=18" - }, - "peerDependencies": { - "postcss": "^8.4" } }, - "node_modules/@csstools/postcss-cascade-layers/node_modules/@csstools/selector-specificity": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/@csstools/selector-specificity/-/selector-specificity-5.0.0.tgz", - "integrity": "sha512-PCqQV3c4CoVm3kdPhyeZ07VmBRdH2EpMFA/pd9OASpOEC3aXNGoqPDAZ80D0cLpMBxnmk0+yNhGsEx31hq7Gtw==", + "node_modules/@esbuild/linux-loong64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.27.2.tgz", + "integrity": "sha512-lugyF1atnAT463aO6KPshVCJK5NgRnU4yb3FUumyVz+cGvZbontBgzeGFO1nF+dPueHD367a2ZXe1NtUkAjOtg==", + "cpu": [ + "loong64" + ], "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } + "license": "MIT", + "optional": true, + "os": [ + "linux" ], - "license": "MIT-0", "engines": { "node": ">=18" - }, - "peerDependencies": { - "postcss-selector-parser": "^7.0.0" } }, - "node_modules/@csstools/postcss-cascade-layers/node_modules/postcss-selector-parser": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.0.tgz", - "integrity": "sha512-8sLjZwK0R+JlxlYcTuVnyT2v+htpdrjDOKuMcOVdYjt52Lh8hWRYpxBPoKx/Zg+bcjc3wx6fmQevMmUztS/ccA==", + "node_modules/@esbuild/linux-mips64el": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.27.2.tgz", + "integrity": "sha512-nlP2I6ArEBewvJ2gjrrkESEZkB5mIoaTswuqNFRv/WYd+ATtUpe9Y09RnJvgvdag7he0OWgEZWhviS1OTOKixw==", + "cpu": [ + "mips64el" + ], "dev": true, "license": "MIT", - "peer": true, - "dependencies": { - "cssesc": "^3.0.0", - "util-deprecate": "^1.0.2" - }, + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">=4" + "node": ">=18" } }, - "node_modules/@csstools/postcss-color-function": { - "version": "4.0.12", - "resolved": "https://registry.npmjs.org/@csstools/postcss-color-function/-/postcss-color-function-4.0.12.tgz", - "integrity": "sha512-yx3cljQKRaSBc2hfh8rMZFZzChaFgwmO2JfFgFr1vMcF3C/uyy5I4RFIBOIWGq1D+XbKCG789CGkG6zzkLpagA==", + "node_modules/@esbuild/linux-ppc64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.27.2.tgz", + "integrity": "sha512-C92gnpey7tUQONqg1n6dKVbx3vphKtTHJaNG2Ok9lGwbZil6DrfyecMsp9CrmXGQJmZ7iiVXvvZH6Ml5hL6XdQ==", + "cpu": [ + "ppc64" + ], "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } + "license": "MIT", + "optional": true, + "os": [ + "linux" ], - "license": "MIT-0", - "dependencies": { - "@csstools/css-color-parser": "^3.1.0", - "@csstools/css-parser-algorithms": "^3.0.5", - "@csstools/css-tokenizer": "^3.0.4", - "@csstools/postcss-progressive-custom-properties": "^4.2.1", - "@csstools/utilities": "^2.0.0" - }, "engines": { "node": ">=18" - }, - "peerDependencies": { - "postcss": "^8.4" } }, - "node_modules/@csstools/postcss-color-function-display-p3-linear": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@csstools/postcss-color-function-display-p3-linear/-/postcss-color-function-display-p3-linear-1.0.1.tgz", - "integrity": "sha512-E5qusdzhlmO1TztYzDIi8XPdPoYOjoTY6HBYBCYSj+Gn4gQRBlvjgPQXzfzuPQqt8EhkC/SzPKObg4Mbn8/xMg==", + "node_modules/@esbuild/linux-riscv64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.27.2.tgz", + "integrity": "sha512-B5BOmojNtUyN8AXlK0QJyvjEZkWwy/FKvakkTDCziX95AowLZKR6aCDhG7LeF7uMCXEJqwa8Bejz5LTPYm8AvA==", + "cpu": [ + "riscv64" + ], "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } + "license": "MIT", + "optional": true, + "os": [ + "linux" ], - "license": "MIT-0", - "dependencies": { - "@csstools/css-color-parser": "^3.1.0", - "@csstools/css-parser-algorithms": "^3.0.5", - "@csstools/css-tokenizer": "^3.0.4", - "@csstools/postcss-progressive-custom-properties": "^4.2.1", - "@csstools/utilities": "^2.0.0" - }, "engines": { "node": ">=18" - }, - "peerDependencies": { - "postcss": "^8.4" } }, - "node_modules/@csstools/postcss-color-mix-function": { - "version": "3.0.12", - "resolved": "https://registry.npmjs.org/@csstools/postcss-color-mix-function/-/postcss-color-mix-function-3.0.12.tgz", - "integrity": "sha512-4STERZfCP5Jcs13P1U5pTvI9SkgLgfMUMhdXW8IlJWkzOOOqhZIjcNhWtNJZes2nkBDsIKJ0CJtFtuaZ00moag==", + "node_modules/@esbuild/linux-s390x": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.27.2.tgz", + "integrity": "sha512-p4bm9+wsPwup5Z8f4EpfN63qNagQ47Ua2znaqGH6bqLlmJ4bx97Y9JdqxgGZ6Y8xVTixUnEkoKSHcpRlDnNr5w==", + "cpu": [ + "s390x" + ], "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } + "license": "MIT", + "optional": true, + "os": [ + "linux" ], - "license": "MIT-0", - "dependencies": { - "@csstools/css-color-parser": "^3.1.0", - "@csstools/css-parser-algorithms": "^3.0.5", - "@csstools/css-tokenizer": "^3.0.4", - "@csstools/postcss-progressive-custom-properties": "^4.2.1", - "@csstools/utilities": "^2.0.0" - }, "engines": { "node": ">=18" - }, - "peerDependencies": { - "postcss": "^8.4" } }, - "node_modules/@csstools/postcss-color-mix-variadic-function-arguments": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@csstools/postcss-color-mix-variadic-function-arguments/-/postcss-color-mix-variadic-function-arguments-1.0.2.tgz", - "integrity": "sha512-rM67Gp9lRAkTo+X31DUqMEq+iK+EFqsidfecmhrteErxJZb6tUoJBVQca1Vn1GpDql1s1rD1pKcuYzMsg7Z1KQ==", + "node_modules/@esbuild/linux-x64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.27.2.tgz", + "integrity": "sha512-uwp2Tip5aPmH+NRUwTcfLb+W32WXjpFejTIOWZFw/v7/KnpCDKG66u4DLcurQpiYTiYwQ9B7KOeMJvLCu/OvbA==", + "cpu": [ + "x64" + ], "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } + "license": "MIT", + "optional": true, + "os": [ + "linux" ], - "license": "MIT-0", - "dependencies": { - "@csstools/css-color-parser": "^3.1.0", - "@csstools/css-parser-algorithms": "^3.0.5", - "@csstools/css-tokenizer": "^3.0.4", - "@csstools/postcss-progressive-custom-properties": "^4.2.1", - "@csstools/utilities": "^2.0.0" - }, "engines": { "node": ">=18" - }, - "peerDependencies": { - "postcss": "^8.4" } }, - "node_modules/@csstools/postcss-content-alt-text": { - "version": "2.0.8", - "resolved": "https://registry.npmjs.org/@csstools/postcss-content-alt-text/-/postcss-content-alt-text-2.0.8.tgz", - "integrity": "sha512-9SfEW9QCxEpTlNMnpSqFaHyzsiRpZ5J5+KqCu1u5/eEJAWsMhzT40qf0FIbeeglEvrGRMdDzAxMIz3wqoGSb+Q==", + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.2.tgz", + "integrity": "sha512-Kj6DiBlwXrPsCRDeRvGAUb/LNrBASrfqAIok+xB0LxK8CHqxZ037viF13ugfsIpePH93mX7xfJp97cyDuTZ3cw==", + "cpu": [ + "arm64" + ], "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } + "license": "MIT", + "optional": true, + "os": [ + "netbsd" ], - "license": "MIT-0", - "dependencies": { - "@csstools/css-parser-algorithms": "^3.0.5", - "@csstools/css-tokenizer": "^3.0.4", - "@csstools/postcss-progressive-custom-properties": "^4.2.1", - "@csstools/utilities": "^2.0.0" - }, "engines": { "node": ">=18" - }, - "peerDependencies": { - "postcss": "^8.4" } }, - "node_modules/@csstools/postcss-contrast-color-function": { - "version": "2.0.12", - "resolved": "https://registry.npmjs.org/@csstools/postcss-contrast-color-function/-/postcss-contrast-color-function-2.0.12.tgz", - "integrity": "sha512-YbwWckjK3qwKjeYz/CijgcS7WDUCtKTd8ShLztm3/i5dhh4NaqzsbYnhm4bjrpFpnLZ31jVcbK8YL77z3GBPzA==", + "node_modules/@esbuild/netbsd-x64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.27.2.tgz", + "integrity": "sha512-HwGDZ0VLVBY3Y+Nw0JexZy9o/nUAWq9MlV7cahpaXKW6TOzfVno3y3/M8Ga8u8Yr7GldLOov27xiCnqRZf0tCA==", + "cpu": [ + "x64" + ], "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } + "license": "MIT", + "optional": true, + "os": [ + "netbsd" ], - "license": "MIT-0", - "dependencies": { - "@csstools/css-color-parser": "^3.1.0", - "@csstools/css-parser-algorithms": "^3.0.5", - "@csstools/css-tokenizer": "^3.0.4", - "@csstools/postcss-progressive-custom-properties": "^4.2.1", - "@csstools/utilities": "^2.0.0" - }, "engines": { "node": ">=18" - }, - "peerDependencies": { - "postcss": "^8.4" } }, - "node_modules/@csstools/postcss-exponential-functions": { - "version": "2.0.9", - "resolved": "https://registry.npmjs.org/@csstools/postcss-exponential-functions/-/postcss-exponential-functions-2.0.9.tgz", - "integrity": "sha512-abg2W/PI3HXwS/CZshSa79kNWNZHdJPMBXeZNyPQFbbj8sKO3jXxOt/wF7juJVjyDTc6JrvaUZYFcSBZBhaxjw==", + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.2.tgz", + "integrity": "sha512-DNIHH2BPQ5551A7oSHD0CKbwIA/Ox7+78/AWkbS5QoRzaqlev2uFayfSxq68EkonB+IKjiuxBFoV8ESJy8bOHA==", + "cpu": [ + "arm64" + ], "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } + "license": "MIT", + "optional": true, + "os": [ + "openbsd" ], - "license": "MIT-0", - "dependencies": { - "@csstools/css-calc": "^2.1.4", - "@csstools/css-parser-algorithms": "^3.0.5", - "@csstools/css-tokenizer": "^3.0.4" - }, "engines": { "node": ">=18" - }, - "peerDependencies": { - "postcss": "^8.4" } }, - "node_modules/@csstools/postcss-font-format-keywords": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/@csstools/postcss-font-format-keywords/-/postcss-font-format-keywords-4.0.0.tgz", - "integrity": "sha512-usBzw9aCRDvchpok6C+4TXC57btc4bJtmKQWOHQxOVKen1ZfVqBUuCZ/wuqdX5GHsD0NRSr9XTP+5ID1ZZQBXw==", + "node_modules/@esbuild/openbsd-x64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.27.2.tgz", + "integrity": "sha512-/it7w9Nb7+0KFIzjalNJVR5bOzA9Vay+yIPLVHfIQYG/j+j9VTH84aNB8ExGKPU4AzfaEvN9/V4HV+F+vo8OEg==", + "cpu": [ + "x64" + ], "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } + "license": "MIT", + "optional": true, + "os": [ + "openbsd" ], - "license": "MIT-0", - "dependencies": { - "@csstools/utilities": "^2.0.0", - "postcss-value-parser": "^4.2.0" - }, "engines": { "node": ">=18" - }, - "peerDependencies": { - "postcss": "^8.4" } }, - "node_modules/@csstools/postcss-gamut-mapping": { - "version": "2.0.11", - "resolved": "https://registry.npmjs.org/@csstools/postcss-gamut-mapping/-/postcss-gamut-mapping-2.0.11.tgz", - "integrity": "sha512-fCpCUgZNE2piVJKC76zFsgVW1apF6dpYsqGyH8SIeCcM4pTEsRTWTLCaJIMKFEundsCKwY1rwfhtrio04RJ4Dw==", + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.2.tgz", + "integrity": "sha512-LRBbCmiU51IXfeXk59csuX/aSaToeG7w48nMwA6049Y4J4+VbWALAuXcs+qcD04rHDuSCSRKdmY63sruDS5qag==", + "cpu": [ + "arm64" + ], "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } + "license": "MIT", + "optional": true, + "os": [ + "openharmony" ], - "license": "MIT-0", - "dependencies": { - "@csstools/css-color-parser": "^3.1.0", - "@csstools/css-parser-algorithms": "^3.0.5", - "@csstools/css-tokenizer": "^3.0.4" - }, "engines": { "node": ">=18" - }, - "peerDependencies": { - "postcss": "^8.4" } }, - "node_modules/@csstools/postcss-gradients-interpolation-method": { - "version": "5.0.12", - "resolved": "https://registry.npmjs.org/@csstools/postcss-gradients-interpolation-method/-/postcss-gradients-interpolation-method-5.0.12.tgz", - "integrity": "sha512-jugzjwkUY0wtNrZlFeyXzimUL3hN4xMvoPnIXxoZqxDvjZRiSh+itgHcVUWzJ2VwD/VAMEgCLvtaJHX+4Vj3Ow==", + "node_modules/@esbuild/sunos-x64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.27.2.tgz", + "integrity": "sha512-kMtx1yqJHTmqaqHPAzKCAkDaKsffmXkPHThSfRwZGyuqyIeBvf08KSsYXl+abf5HDAPMJIPnbBfXvP2ZC2TfHg==", + "cpu": [ + "x64" + ], "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } + "license": "MIT", + "optional": true, + "os": [ + "sunos" ], - "license": "MIT-0", - "dependencies": { - "@csstools/css-color-parser": "^3.1.0", - "@csstools/css-parser-algorithms": "^3.0.5", - "@csstools/css-tokenizer": "^3.0.4", - "@csstools/postcss-progressive-custom-properties": "^4.2.1", - "@csstools/utilities": "^2.0.0" - }, "engines": { "node": ">=18" - }, - "peerDependencies": { - "postcss": "^8.4" } }, - "node_modules/@csstools/postcss-hwb-function": { - "version": "4.0.12", - "resolved": "https://registry.npmjs.org/@csstools/postcss-hwb-function/-/postcss-hwb-function-4.0.12.tgz", - "integrity": "sha512-mL/+88Z53KrE4JdePYFJAQWFrcADEqsLprExCM04GDNgHIztwFzj0Mbhd/yxMBngq0NIlz58VVxjt5abNs1VhA==", + "node_modules/@esbuild/win32-arm64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.27.2.tgz", + "integrity": "sha512-Yaf78O/B3Kkh+nKABUF++bvJv5Ijoy9AN1ww904rOXZFLWVc5OLOfL56W+C8F9xn5JQZa3UX6m+IktJnIb1Jjg==", + "cpu": [ + "arm64" + ], "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } + "license": "MIT", + "optional": true, + "os": [ + "win32" ], - "license": "MIT-0", - "dependencies": { - "@csstools/css-color-parser": "^3.1.0", - "@csstools/css-parser-algorithms": "^3.0.5", - "@csstools/css-tokenizer": "^3.0.4", - "@csstools/postcss-progressive-custom-properties": "^4.2.1", - "@csstools/utilities": "^2.0.0" - }, "engines": { "node": ">=18" - }, - "peerDependencies": { - "postcss": "^8.4" } }, - "node_modules/@csstools/postcss-ic-unit": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/@csstools/postcss-ic-unit/-/postcss-ic-unit-4.0.4.tgz", - "integrity": "sha512-yQ4VmossuOAql65sCPppVO1yfb7hDscf4GseF0VCA/DTDaBc0Wtf8MTqVPfjGYlT5+2buokG0Gp7y0atYZpwjg==", + "node_modules/@esbuild/win32-ia32": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.27.2.tgz", + "integrity": "sha512-Iuws0kxo4yusk7sw70Xa2E2imZU5HoixzxfGCdxwBdhiDgt9vX9VUCBhqcwY7/uh//78A1hMkkROMJq9l27oLQ==", + "cpu": [ + "ia32" + ], "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } + "license": "MIT", + "optional": true, + "os": [ + "win32" ], - "license": "MIT-0", - "dependencies": { - "@csstools/postcss-progressive-custom-properties": "^4.2.1", - "@csstools/utilities": "^2.0.0", - "postcss-value-parser": "^4.2.0" - }, "engines": { "node": ">=18" - }, - "peerDependencies": { - "postcss": "^8.4" } }, - "node_modules/@csstools/postcss-initial": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@csstools/postcss-initial/-/postcss-initial-2.0.1.tgz", - "integrity": "sha512-L1wLVMSAZ4wovznquK0xmC7QSctzO4D0Is590bxpGqhqjboLXYA16dWZpfwImkdOgACdQ9PqXsuRroW6qPlEsg==", + "node_modules/@esbuild/win32-x64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.27.2.tgz", + "integrity": "sha512-sRdU18mcKf7F+YgheI/zGf5alZatMUTKj/jNS6l744f9u3WFu4v7twcUI9vu4mknF4Y9aDlblIie0IM+5xxaqQ==", + "cpu": [ + "x64" + ], "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } + "license": "MIT", + "optional": true, + "os": [ + "win32" ], - "license": "MIT-0", "engines": { "node": ">=18" - }, - "peerDependencies": { - "postcss": "^8.4" } }, - "node_modules/@csstools/postcss-is-pseudo-class": { - "version": "5.0.3", - "resolved": "https://registry.npmjs.org/@csstools/postcss-is-pseudo-class/-/postcss-is-pseudo-class-5.0.3.tgz", - "integrity": "sha512-jS/TY4SpG4gszAtIg7Qnf3AS2pjcUM5SzxpApOrlndMeGhIbaTzWBzzP/IApXoNWEW7OhcjkRT48jnAUIFXhAQ==", + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.13", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", + "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "license": "MIT-0", + "license": "MIT", "dependencies": { - "@csstools/selector-specificity": "^5.0.0", - "postcss-selector-parser": "^7.0.0" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "postcss": "^8.4" + "@jridgewell/sourcemap-codec": "^1.5.0", + "@jridgewell/trace-mapping": "^0.3.24" } }, - "node_modules/@csstools/postcss-is-pseudo-class/node_modules/@csstools/selector-specificity": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/@csstools/selector-specificity/-/selector-specificity-5.0.0.tgz", - "integrity": "sha512-PCqQV3c4CoVm3kdPhyeZ07VmBRdH2EpMFA/pd9OASpOEC3aXNGoqPDAZ80D0cLpMBxnmk0+yNhGsEx31hq7Gtw==", + "node_modules/@jridgewell/remapping": { + "version": "2.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz", + "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "license": "MIT-0", - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "postcss-selector-parser": "^7.0.0" + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" } }, - "node_modules/@csstools/postcss-is-pseudo-class/node_modules/postcss-selector-parser": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.0.tgz", - "integrity": "sha512-8sLjZwK0R+JlxlYcTuVnyT2v+htpdrjDOKuMcOVdYjt52Lh8hWRYpxBPoKx/Zg+bcjc3wx6fmQevMmUztS/ccA==", + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", "dev": true, "license": "MIT", - "peer": true, - "dependencies": { - "cssesc": "^3.0.0", - "util-deprecate": "^1.0.2" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/@csstools/postcss-light-dark-function": { - "version": "2.0.11", - "resolved": "https://registry.npmjs.org/@csstools/postcss-light-dark-function/-/postcss-light-dark-function-2.0.11.tgz", - "integrity": "sha512-fNJcKXJdPM3Lyrbmgw2OBbaioU7yuKZtiXClf4sGdQttitijYlZMD5K7HrC/eF83VRWRrYq6OZ0Lx92leV2LFA==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "license": "MIT-0", - "dependencies": { - "@csstools/css-parser-algorithms": "^3.0.5", - "@csstools/css-tokenizer": "^3.0.4", - "@csstools/postcss-progressive-custom-properties": "^4.2.1", - "@csstools/utilities": "^2.0.0" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "postcss": "^8.4" - } - }, - "node_modules/@csstools/postcss-logical-float-and-clear": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@csstools/postcss-logical-float-and-clear/-/postcss-logical-float-and-clear-3.0.0.tgz", - "integrity": "sha512-SEmaHMszwakI2rqKRJgE+8rpotFfne1ZS6bZqBoQIicFyV+xT1UF42eORPxJkVJVrH9C0ctUgwMSn3BLOIZldQ==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "license": "MIT-0", - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "postcss": "^8.4" - } - }, - "node_modules/@csstools/postcss-logical-overflow": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@csstools/postcss-logical-overflow/-/postcss-logical-overflow-2.0.0.tgz", - "integrity": "sha512-spzR1MInxPuXKEX2csMamshR4LRaSZ3UXVaRGjeQxl70ySxOhMpP2252RAFsg8QyyBXBzuVOOdx1+bVO5bPIzA==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "license": "MIT-0", - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "postcss": "^8.4" - } - }, - "node_modules/@csstools/postcss-logical-overscroll-behavior": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@csstools/postcss-logical-overscroll-behavior/-/postcss-logical-overscroll-behavior-2.0.0.tgz", - "integrity": "sha512-e/webMjoGOSYfqLunyzByZj5KKe5oyVg/YSbie99VEaSDE2kimFm0q1f6t/6Jo+VVCQ/jbe2Xy+uX+C4xzWs4w==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "license": "MIT-0", - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "postcss": "^8.4" - } - }, - "node_modules/@csstools/postcss-logical-resize": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@csstools/postcss-logical-resize/-/postcss-logical-resize-3.0.0.tgz", - "integrity": "sha512-DFbHQOFW/+I+MY4Ycd/QN6Dg4Hcbb50elIJCfnwkRTCX05G11SwViI5BbBlg9iHRl4ytB7pmY5ieAFk3ws7yyg==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "license": "MIT-0", - "dependencies": { - "postcss-value-parser": "^4.2.0" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "postcss": "^8.4" - } - }, - "node_modules/@csstools/postcss-logical-viewport-units": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/@csstools/postcss-logical-viewport-units/-/postcss-logical-viewport-units-3.0.4.tgz", - "integrity": "sha512-q+eHV1haXA4w9xBwZLKjVKAWn3W2CMqmpNpZUk5kRprvSiBEGMgrNH3/sJZ8UA3JgyHaOt3jwT9uFa4wLX4EqQ==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "license": "MIT-0", - "dependencies": { - "@csstools/css-tokenizer": "^3.0.4", - "@csstools/utilities": "^2.0.0" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "postcss": "^8.4" - } - }, - "node_modules/@csstools/postcss-media-minmax": { - "version": "2.0.9", - "resolved": "https://registry.npmjs.org/@csstools/postcss-media-minmax/-/postcss-media-minmax-2.0.9.tgz", - "integrity": "sha512-af9Qw3uS3JhYLnCbqtZ9crTvvkR+0Se+bBqSr7ykAnl9yKhk6895z9rf+2F4dClIDJWxgn0iZZ1PSdkhrbs2ig==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "license": "MIT", - "dependencies": { - "@csstools/css-calc": "^2.1.4", - "@csstools/css-parser-algorithms": "^3.0.5", - "@csstools/css-tokenizer": "^3.0.4", - "@csstools/media-query-list-parser": "^4.0.3" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "postcss": "^8.4" - } - }, - "node_modules/@csstools/postcss-media-queries-aspect-ratio-number-values": { - "version": "3.0.5", - "resolved": "https://registry.npmjs.org/@csstools/postcss-media-queries-aspect-ratio-number-values/-/postcss-media-queries-aspect-ratio-number-values-3.0.5.tgz", - "integrity": "sha512-zhAe31xaaXOY2Px8IYfoVTB3wglbJUVigGphFLj6exb7cjZRH9A6adyE22XfFK3P2PzwRk0VDeTJmaxpluyrDg==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "license": "MIT-0", - "dependencies": { - "@csstools/css-parser-algorithms": "^3.0.5", - "@csstools/css-tokenizer": "^3.0.4", - "@csstools/media-query-list-parser": "^4.0.3" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "postcss": "^8.4" - } - }, - "node_modules/@csstools/postcss-nested-calc": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/@csstools/postcss-nested-calc/-/postcss-nested-calc-4.0.0.tgz", - "integrity": "sha512-jMYDdqrQQxE7k9+KjstC3NbsmC063n1FTPLCgCRS2/qHUbHM0mNy9pIn4QIiQGs9I/Bg98vMqw7mJXBxa0N88A==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "license": "MIT-0", - "dependencies": { - "@csstools/utilities": "^2.0.0", - "postcss-value-parser": "^4.2.0" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "postcss": "^8.4" - } - }, - "node_modules/@csstools/postcss-normalize-display-values": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/@csstools/postcss-normalize-display-values/-/postcss-normalize-display-values-4.0.0.tgz", - "integrity": "sha512-HlEoG0IDRoHXzXnkV4in47dzsxdsjdz6+j7MLjaACABX2NfvjFS6XVAnpaDyGesz9gK2SC7MbNwdCHusObKJ9Q==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "license": "MIT-0", - "dependencies": { - "postcss-value-parser": "^4.2.0" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "postcss": "^8.4" - } - }, - "node_modules/@csstools/postcss-oklab-function": { - "version": "4.0.12", - "resolved": "https://registry.npmjs.org/@csstools/postcss-oklab-function/-/postcss-oklab-function-4.0.12.tgz", - "integrity": "sha512-HhlSmnE1NKBhXsTnNGjxvhryKtO7tJd1w42DKOGFD6jSHtYOrsJTQDKPMwvOfrzUAk8t7GcpIfRyM7ssqHpFjg==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "license": "MIT-0", - "dependencies": { - "@csstools/css-color-parser": "^3.1.0", - "@csstools/css-parser-algorithms": "^3.0.5", - "@csstools/css-tokenizer": "^3.0.4", - "@csstools/postcss-progressive-custom-properties": "^4.2.1", - "@csstools/utilities": "^2.0.0" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "postcss": "^8.4" - } - }, - "node_modules/@csstools/postcss-progressive-custom-properties": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/@csstools/postcss-progressive-custom-properties/-/postcss-progressive-custom-properties-4.2.1.tgz", - "integrity": "sha512-uPiiXf7IEKtUQXsxu6uWtOlRMXd2QWWy5fhxHDnPdXKCQckPP3E34ZgDoZ62r2iT+UOgWsSbM4NvHE5m3mAEdw==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "license": "MIT-0", - "dependencies": { - "postcss-value-parser": "^4.2.0" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "postcss": "^8.4" - } - }, - "node_modules/@csstools/postcss-random-function": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@csstools/postcss-random-function/-/postcss-random-function-2.0.1.tgz", - "integrity": "sha512-q+FQaNiRBhnoSNo+GzqGOIBKoHQ43lYz0ICrV+UudfWnEF6ksS6DsBIJSISKQT2Bvu3g4k6r7t0zYrk5pDlo8w==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "license": "MIT-0", - "dependencies": { - "@csstools/css-calc": "^2.1.4", - "@csstools/css-parser-algorithms": "^3.0.5", - "@csstools/css-tokenizer": "^3.0.4" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "postcss": "^8.4" - } - }, - "node_modules/@csstools/postcss-relative-color-syntax": { - "version": "3.0.12", - "resolved": "https://registry.npmjs.org/@csstools/postcss-relative-color-syntax/-/postcss-relative-color-syntax-3.0.12.tgz", - "integrity": "sha512-0RLIeONxu/mtxRtf3o41Lq2ghLimw0w9ByLWnnEVuy89exmEEq8bynveBxNW3nyHqLAFEeNtVEmC1QK9MZ8Huw==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "license": "MIT-0", - "dependencies": { - "@csstools/css-color-parser": "^3.1.0", - "@csstools/css-parser-algorithms": "^3.0.5", - "@csstools/css-tokenizer": "^3.0.4", - "@csstools/postcss-progressive-custom-properties": "^4.2.1", - "@csstools/utilities": "^2.0.0" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "postcss": "^8.4" - } - }, - "node_modules/@csstools/postcss-scope-pseudo-class": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/@csstools/postcss-scope-pseudo-class/-/postcss-scope-pseudo-class-4.0.1.tgz", - "integrity": "sha512-IMi9FwtH6LMNuLea1bjVMQAsUhFxJnyLSgOp/cpv5hrzWmrUYU5fm0EguNDIIOHUqzXode8F/1qkC/tEo/qN8Q==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "license": "MIT-0", - "dependencies": { - "postcss-selector-parser": "^7.0.0" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "postcss": "^8.4" - } - }, - "node_modules/@csstools/postcss-scope-pseudo-class/node_modules/postcss-selector-parser": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.0.tgz", - "integrity": "sha512-8sLjZwK0R+JlxlYcTuVnyT2v+htpdrjDOKuMcOVdYjt52Lh8hWRYpxBPoKx/Zg+bcjc3wx6fmQevMmUztS/ccA==", - "dev": true, - "license": "MIT", - "dependencies": { - "cssesc": "^3.0.0", - "util-deprecate": "^1.0.2" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/@csstools/postcss-sign-functions": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/@csstools/postcss-sign-functions/-/postcss-sign-functions-1.1.4.tgz", - "integrity": "sha512-P97h1XqRPcfcJndFdG95Gv/6ZzxUBBISem0IDqPZ7WMvc/wlO+yU0c5D/OCpZ5TJoTt63Ok3knGk64N+o6L2Pg==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "license": "MIT-0", - "dependencies": { - "@csstools/css-calc": "^2.1.4", - "@csstools/css-parser-algorithms": "^3.0.5", - "@csstools/css-tokenizer": "^3.0.4" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "postcss": "^8.4" - } - }, - "node_modules/@csstools/postcss-stepped-value-functions": { - "version": "4.0.9", - "resolved": "https://registry.npmjs.org/@csstools/postcss-stepped-value-functions/-/postcss-stepped-value-functions-4.0.9.tgz", - "integrity": "sha512-h9btycWrsex4dNLeQfyU3y3w40LMQooJWFMm/SK9lrKguHDcFl4VMkncKKoXi2z5rM9YGWbUQABI8BT2UydIcA==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "license": "MIT-0", - "dependencies": { - "@csstools/css-calc": "^2.1.4", - "@csstools/css-parser-algorithms": "^3.0.5", - "@csstools/css-tokenizer": "^3.0.4" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "postcss": "^8.4" - } - }, - "node_modules/@csstools/postcss-text-decoration-shorthand": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/@csstools/postcss-text-decoration-shorthand/-/postcss-text-decoration-shorthand-4.0.3.tgz", - "integrity": "sha512-KSkGgZfx0kQjRIYnpsD7X2Om9BUXX/Kii77VBifQW9Ih929hK0KNjVngHDH0bFB9GmfWcR9vJYJJRvw/NQjkrA==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "license": "MIT-0", - "dependencies": { - "@csstools/color-helpers": "^5.1.0", - "postcss-value-parser": "^4.2.0" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "postcss": "^8.4" - } - }, - "node_modules/@csstools/postcss-trigonometric-functions": { - "version": "4.0.9", - "resolved": "https://registry.npmjs.org/@csstools/postcss-trigonometric-functions/-/postcss-trigonometric-functions-4.0.9.tgz", - "integrity": "sha512-Hnh5zJUdpNrJqK9v1/E3BbrQhaDTj5YiX7P61TOvUhoDHnUmsNNxcDAgkQ32RrcWx9GVUvfUNPcUkn8R3vIX6A==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "license": "MIT-0", - "dependencies": { - "@csstools/css-calc": "^2.1.4", - "@csstools/css-parser-algorithms": "^3.0.5", - "@csstools/css-tokenizer": "^3.0.4" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "postcss": "^8.4" - } - }, - "node_modules/@csstools/postcss-unset-value": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/@csstools/postcss-unset-value/-/postcss-unset-value-4.0.0.tgz", - "integrity": "sha512-cBz3tOCI5Fw6NIFEwU3RiwK6mn3nKegjpJuzCndoGq3BZPkUjnsq7uQmIeMNeMbMk7YD2MfKcgCpZwX5jyXqCA==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "license": "MIT-0", - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "postcss": "^8.4" - } - }, - "node_modules/@csstools/utilities": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@csstools/utilities/-/utilities-2.0.0.tgz", - "integrity": "sha512-5VdOr0Z71u+Yp3ozOx8T11N703wIFGVRgOWbOZMKgglPJsWA54MRIoMNVMa7shUToIhx5J8vX4sOZgD2XiihiQ==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "license": "MIT-0", - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "postcss": "^8.4" - } - }, - "node_modules/@esbuild/aix-ppc64": { - "version": "0.25.9", - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.9.tgz", - "integrity": "sha512-OaGtL73Jck6pBKjNIe24BnFE6agGl+6KxDtTfHhy1HmhthfKouEcOhqpSL64K4/0WCtbKFLOdzD/44cJ4k9opA==", - "cpu": [ - "ppc64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "aix" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/android-arm": { - "version": "0.25.9", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.9.tgz", - "integrity": "sha512-5WNI1DaMtxQ7t7B6xa572XMXpHAaI/9Hnhk8lcxF4zVN4xstUgTlvuGDorBguKEnZO70qwEcLpfifMLoxiPqHQ==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/android-arm64": { - "version": "0.25.9", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.9.tgz", - "integrity": "sha512-IDrddSmpSv51ftWslJMvl3Q2ZT98fUSL2/rlUXuVqRXHCs5EUF1/f+jbjF5+NG9UffUDMCiTyh8iec7u8RlTLg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/android-x64": { - "version": "0.25.9", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.9.tgz", - "integrity": "sha512-I853iMZ1hWZdNllhVZKm34f4wErd4lMyeV7BLzEExGEIZYsOzqDWDf+y082izYUE8gtJnYHdeDpN/6tUdwvfiw==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/darwin-arm64": { - "version": "0.25.9", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.9.tgz", - "integrity": "sha512-XIpIDMAjOELi/9PB30vEbVMs3GV1v2zkkPnuyRRURbhqjyzIINwj+nbQATh4H9GxUgH1kFsEyQMxwiLFKUS6Rg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/darwin-x64": { - "version": "0.25.9", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.9.tgz", - "integrity": "sha512-jhHfBzjYTA1IQu8VyrjCX4ApJDnH+ez+IYVEoJHeqJm9VhG9Dh2BYaJritkYK3vMaXrf7Ogr/0MQ8/MeIefsPQ==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/freebsd-arm64": { - "version": "0.25.9", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.9.tgz", - "integrity": "sha512-z93DmbnY6fX9+KdD4Ue/H6sYs+bhFQJNCPZsi4XWJoYblUqT06MQUdBCpcSfuiN72AbqeBFu5LVQTjfXDE2A6Q==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/freebsd-x64": { - "version": "0.25.9", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.9.tgz", - "integrity": "sha512-mrKX6H/vOyo5v71YfXWJxLVxgy1kyt1MQaD8wZJgJfG4gq4DpQGpgTB74e5yBeQdyMTbgxp0YtNj7NuHN0PoZg==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-arm": { - "version": "0.25.9", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.9.tgz", - "integrity": "sha512-HBU2Xv78SMgaydBmdor38lg8YDnFKSARg1Q6AT0/y2ezUAKiZvc211RDFHlEZRFNRVhcMamiToo7bDx3VEOYQw==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-arm64": { - "version": "0.25.9", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.9.tgz", - "integrity": "sha512-BlB7bIcLT3G26urh5Dmse7fiLmLXnRlopw4s8DalgZ8ef79Jj4aUcYbk90g8iCa2467HX8SAIidbL7gsqXHdRw==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-ia32": { - "version": "0.25.9", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.9.tgz", - "integrity": "sha512-e7S3MOJPZGp2QW6AK6+Ly81rC7oOSerQ+P8L0ta4FhVi+/j/v2yZzx5CqqDaWjtPFfYz21Vi1S0auHrap3Ma3A==", - "cpu": [ - "ia32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-loong64": { - "version": "0.25.9", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.9.tgz", - "integrity": "sha512-Sbe10Bnn0oUAB2AalYztvGcK+o6YFFA/9829PhOCUS9vkJElXGdphz0A3DbMdP8gmKkqPmPcMJmJOrI3VYB1JQ==", - "cpu": [ - "loong64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-mips64el": { - "version": "0.25.9", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.9.tgz", - "integrity": "sha512-YcM5br0mVyZw2jcQeLIkhWtKPeVfAerES5PvOzaDxVtIyZ2NUBZKNLjC5z3/fUlDgT6w89VsxP2qzNipOaaDyA==", - "cpu": [ - "mips64el" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-ppc64": { - "version": "0.25.9", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.9.tgz", - "integrity": "sha512-++0HQvasdo20JytyDpFvQtNrEsAgNG2CY1CLMwGXfFTKGBGQT3bOeLSYE2l1fYdvML5KUuwn9Z8L1EWe2tzs1w==", - "cpu": [ - "ppc64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-riscv64": { - "version": "0.25.9", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.9.tgz", - "integrity": "sha512-uNIBa279Y3fkjV+2cUjx36xkx7eSjb8IvnL01eXUKXez/CBHNRw5ekCGMPM0BcmqBxBcdgUWuUXmVWwm4CH9kg==", - "cpu": [ - "riscv64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-s390x": { - "version": "0.25.9", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.9.tgz", - "integrity": "sha512-Mfiphvp3MjC/lctb+7D287Xw1DGzqJPb/J2aHHcHxflUo+8tmN/6d4k6I2yFR7BVo5/g7x2Monq4+Yew0EHRIA==", - "cpu": [ - "s390x" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-x64": { - "version": "0.25.9", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.9.tgz", - "integrity": "sha512-iSwByxzRe48YVkmpbgoxVzn76BXjlYFXC7NvLYq+b+kDjyyk30J0JY47DIn8z1MO3K0oSl9fZoRmZPQI4Hklzg==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/netbsd-arm64": { - "version": "0.25.9", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.9.tgz", - "integrity": "sha512-9jNJl6FqaUG+COdQMjSCGW4QiMHH88xWbvZ+kRVblZsWrkXlABuGdFJ1E9L7HK+T0Yqd4akKNa/lO0+jDxQD4Q==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "netbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/netbsd-x64": { - "version": "0.25.9", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.9.tgz", - "integrity": "sha512-RLLdkflmqRG8KanPGOU7Rpg829ZHu8nFy5Pqdi9U01VYtG9Y0zOG6Vr2z4/S+/3zIyOxiK6cCeYNWOFR9QP87g==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "netbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/openbsd-arm64": { - "version": "0.25.9", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.9.tgz", - "integrity": "sha512-YaFBlPGeDasft5IIM+CQAhJAqS3St3nJzDEgsgFixcfZeyGPCd6eJBWzke5piZuZ7CtL656eOSYKk4Ls2C0FRQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/openbsd-x64": { - "version": "0.25.9", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.9.tgz", - "integrity": "sha512-1MkgTCuvMGWuqVtAvkpkXFmtL8XhWy+j4jaSO2wxfJtilVCi0ZE37b8uOdMItIHz4I6z1bWWtEX4CJwcKYLcuA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/openharmony-arm64": { - "version": "0.25.9", - "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.9.tgz", - "integrity": "sha512-4Xd0xNiMVXKh6Fa7HEJQbrpP3m3DDn43jKxMjxLLRjWnRsfxjORYJlXPO4JNcXtOyfajXorRKY9NkOpTHptErg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openharmony" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/sunos-x64": { - "version": "0.25.9", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.9.tgz", - "integrity": "sha512-WjH4s6hzo00nNezhp3wFIAfmGZ8U7KtrJNlFMRKxiI9mxEK1scOMAaa9i4crUtu+tBr+0IN6JCuAcSBJZfnphw==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "sunos" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/win32-arm64": { - "version": "0.25.9", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.9.tgz", - "integrity": "sha512-mGFrVJHmZiRqmP8xFOc6b84/7xa5y5YvR1x8djzXpJBSv/UsNK6aqec+6JDjConTgvvQefdGhFDAs2DLAds6gQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/win32-ia32": { - "version": "0.25.9", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.9.tgz", - "integrity": "sha512-b33gLVU2k11nVx1OhX3C8QQP6UHQK4ZtN56oFWvVXvz2VkDoe6fbG8TOgHFxEvqeqohmRnIHe5A1+HADk4OQww==", - "cpu": [ - "ia32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/win32-x64": { - "version": "0.25.9", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.9.tgz", - "integrity": "sha512-PPOl1mi6lpLNQxnGoyAfschAodRFYXJ+9fs6WHXz7CSWKbOqiMZsubC+BQsVKuul+3vKLuwTHsS2c2y9EoKwxQ==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@jridgewell/gen-mapping": { - "version": "0.3.13", - "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", - "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jridgewell/sourcemap-codec": "^1.5.0", - "@jridgewell/trace-mapping": "^0.3.24" - } - }, - "node_modules/@jridgewell/remapping": { - "version": "2.3.5", - "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz", - "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jridgewell/gen-mapping": "^0.3.5", - "@jridgewell/trace-mapping": "^0.3.24" - } - }, - "node_modules/@jridgewell/resolve-uri": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", - "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@jridgewell/sourcemap-codec": { - "version": "1.5.5", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", - "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", - "dev": true, - "license": "MIT" - }, - "node_modules/@jridgewell/trace-mapping": { - "version": "0.3.31", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", - "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jridgewell/resolve-uri": "^3.1.0", - "@jridgewell/sourcemap-codec": "^1.4.14" - } - }, - "node_modules/@popperjs/core": { - "version": "2.11.8", - "resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.8.tgz", - "integrity": "sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==", - "dev": true, - "license": "MIT", - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/popperjs" - } - }, - "node_modules/@rollup/rollup-android-arm-eabi": { - "version": "4.50.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.50.1.tgz", - "integrity": "sha512-HJXwzoZN4eYTdD8bVV22DN8gsPCAj3V20NHKOs8ezfXanGpmVPR7kalUHd+Y31IJp9stdB87VKPFbsGY3H/2ag==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ] - }, - "node_modules/@rollup/rollup-android-arm64": { - "version": "4.50.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.50.1.tgz", - "integrity": "sha512-PZlsJVcjHfcH53mOImyt3bc97Ep3FJDXRpk9sMdGX0qgLmY0EIWxCag6EigerGhLVuL8lDVYNnSo8qnTElO4xw==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ] - }, - "node_modules/@rollup/rollup-darwin-arm64": { - "version": "4.50.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.50.1.tgz", - "integrity": "sha512-xc6i2AuWh++oGi4ylOFPmzJOEeAa2lJeGUGb4MudOtgfyyjr4UPNK+eEWTPLvmPJIY/pgw6ssFIox23SyrkkJw==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ] - }, - "node_modules/@rollup/rollup-darwin-x64": { - "version": "4.50.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.50.1.tgz", - "integrity": "sha512-2ofU89lEpDYhdLAbRdeyz/kX3Y2lpYc6ShRnDjY35bZhd2ipuDMDi6ZTQ9NIag94K28nFMofdnKeHR7BT0CATw==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ] - }, - "node_modules/@rollup/rollup-freebsd-arm64": { - "version": "4.50.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.50.1.tgz", - "integrity": "sha512-wOsE6H2u6PxsHY/BeFHA4VGQN3KUJFZp7QJBmDYI983fgxq5Th8FDkVuERb2l9vDMs1D5XhOrhBrnqcEY6l8ZA==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ] - }, - "node_modules/@rollup/rollup-freebsd-x64": { - "version": "4.50.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.50.1.tgz", - "integrity": "sha512-A/xeqaHTlKbQggxCqispFAcNjycpUEHP52mwMQZUNqDUJFFYtPHCXS1VAG29uMlDzIVr+i00tSFWFLivMcoIBQ==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ] - }, - "node_modules/@rollup/rollup-linux-arm-gnueabihf": { - "version": "4.50.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.50.1.tgz", - "integrity": "sha512-54v4okehwl5TaSIkpp97rAHGp7t3ghinRd/vyC1iXqXMfjYUTm7TfYmCzXDoHUPTTf36L8pr0E7YsD3CfB3ZDg==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-arm-musleabihf": { - "version": "4.50.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.50.1.tgz", - "integrity": "sha512-p/LaFyajPN/0PUHjv8TNyxLiA7RwmDoVY3flXHPSzqrGcIp/c2FjwPPP5++u87DGHtw+5kSH5bCJz0mvXngYxw==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-arm64-gnu": { - "version": "4.50.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.50.1.tgz", - "integrity": "sha512-2AbMhFFkTo6Ptna1zO7kAXXDLi7H9fGTbVaIq2AAYO7yzcAsuTNWPHhb2aTA6GPiP+JXh85Y8CiS54iZoj4opw==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-arm64-musl": { - "version": "4.50.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.50.1.tgz", - "integrity": "sha512-Cgef+5aZwuvesQNw9eX7g19FfKX5/pQRIyhoXLCiBOrWopjo7ycfB292TX9MDcDijiuIJlx1IzJz3IoCPfqs9w==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-loongarch64-gnu": { - "version": "4.50.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.50.1.tgz", - "integrity": "sha512-RPhTwWMzpYYrHrJAS7CmpdtHNKtt2Ueo+BlLBjfZEhYBhK00OsEqM08/7f+eohiF6poe0YRDDd8nAvwtE/Y62Q==", - "cpu": [ - "loong64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-ppc64-gnu": { - "version": "4.50.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.50.1.tgz", - "integrity": "sha512-eSGMVQw9iekut62O7eBdbiccRguuDgiPMsw++BVUg+1K7WjZXHOg/YOT9SWMzPZA+w98G+Fa1VqJgHZOHHnY0Q==", - "cpu": [ - "ppc64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-riscv64-gnu": { - "version": "4.50.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.50.1.tgz", - "integrity": "sha512-S208ojx8a4ciIPrLgazF6AgdcNJzQE4+S9rsmOmDJkusvctii+ZvEuIC4v/xFqzbuP8yDjn73oBlNDgF6YGSXQ==", - "cpu": [ - "riscv64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-riscv64-musl": { - "version": "4.50.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.50.1.tgz", - "integrity": "sha512-3Ag8Ls1ggqkGUvSZWYcdgFwriy2lWo+0QlYgEFra/5JGtAd6C5Hw59oojx1DeqcA2Wds2ayRgvJ4qxVTzCHgzg==", - "cpu": [ - "riscv64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-s390x-gnu": { - "version": "4.50.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.50.1.tgz", - "integrity": "sha512-t9YrKfaxCYe7l7ldFERE1BRg/4TATxIg+YieHQ966jwvo7ddHJxPj9cNFWLAzhkVsbBvNA4qTbPVNsZKBO4NSg==", - "cpu": [ - "s390x" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-x64-gnu": { - "version": "4.50.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.50.1.tgz", - "integrity": "sha512-MCgtFB2+SVNuQmmjHf+wfI4CMxy3Tk8XjA5Z//A0AKD7QXUYFMQcns91K6dEHBvZPCnhJSyDWLApk40Iq/H3tA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-x64-musl": { - "version": "4.50.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.50.1.tgz", - "integrity": "sha512-nEvqG+0jeRmqaUMuwzlfMKwcIVffy/9KGbAGyoa26iu6eSngAYQ512bMXuqqPrlTyfqdlB9FVINs93j534UJrg==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-openharmony-arm64": { - "version": "4.50.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.50.1.tgz", - "integrity": "sha512-RDsLm+phmT3MJd9SNxA9MNuEAO/J2fhW8GXk62G/B4G7sLVumNFbRwDL6v5NrESb48k+QMqdGbHgEtfU0LCpbA==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openharmony" - ] - }, - "node_modules/@rollup/rollup-win32-arm64-msvc": { - "version": "4.50.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.50.1.tgz", - "integrity": "sha512-hpZB/TImk2FlAFAIsoElM3tLzq57uxnGYwplg6WDyAxbYczSi8O2eQ+H2Lx74504rwKtZ3N2g4bCUkiamzS6TQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ] - }, - "node_modules/@rollup/rollup-win32-ia32-msvc": { - "version": "4.50.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.50.1.tgz", - "integrity": "sha512-SXjv8JlbzKM0fTJidX4eVsH+Wmnp0/WcD8gJxIZyR6Gay5Qcsmdbi9zVtnbkGPG8v2vMR1AD06lGWy5FLMcG7A==", - "cpu": [ - "ia32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ] - }, - "node_modules/@rollup/rollup-win32-x64-msvc": { - "version": "4.50.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.50.1.tgz", - "integrity": "sha512-StxAO/8ts62KZVRAm4JZYq9+NqNsV7RvimNK+YM7ry//zebEH6meuugqW/P5OFUCjyQgui+9fUxT6d5NShvMvA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ] - }, - "node_modules/@ryangjchandler/alpine-tooltip": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@ryangjchandler/alpine-tooltip/-/alpine-tooltip-2.0.1.tgz", - "integrity": "sha512-Hv9C02bUE4JSewZhfceYSHw+oiBuUF3ITCMKqmiFWJ3bvjhHZzM8YEPpNQVMfEVRR4K4JJYJD16re4stGIjoyg==", - "dev": true, - "dependencies": { - "tippy.js": "^6.3.1" - } - }, - "node_modules/@tailwindcss/forms": { - "version": "0.5.10", - "resolved": "https://registry.npmjs.org/@tailwindcss/forms/-/forms-0.5.10.tgz", - "integrity": "sha512-utI1ONF6uf/pPNO68kmN1b8rEwNXv3czukalo8VtJH8ksIkZXr3Q3VYudZLkCsDd4Wku120uF02hYK25XGPorw==", - "dev": true, - "license": "MIT", - "dependencies": { - "mini-svg-data-uri": "^1.2.3" - }, - "peerDependencies": { - "tailwindcss": ">=3.0.0 || >= 3.0.0-alpha.1 || >= 4.0.0-alpha.20 || >= 4.0.0-beta.1" - } - }, - "node_modules/@tailwindcss/node": { - "version": "4.1.17", - "resolved": "https://registry.npmjs.org/@tailwindcss/node/-/node-4.1.17.tgz", - "integrity": "sha512-csIkHIgLb3JisEFQ0vxr2Y57GUNYh447C8xzwj89U/8fdW8LhProdxvnVH6U8M2Y73QKiTIH+LWbK3V2BBZsAg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jridgewell/remapping": "^2.3.4", - "enhanced-resolve": "^5.18.3", - "jiti": "^2.6.1", - "lightningcss": "1.30.2", - "magic-string": "^0.30.21", - "source-map-js": "^1.2.1", - "tailwindcss": "4.1.17" - } - }, - "node_modules/@tailwindcss/oxide": { - "version": "4.1.17", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide/-/oxide-4.1.17.tgz", - "integrity": "sha512-F0F7d01fmkQhsTjXezGBLdrl1KresJTcI3DB8EkScCldyKp3Msz4hub4uyYaVnk88BAS1g5DQjjF6F5qczheLA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 10" - }, - "optionalDependencies": { - "@tailwindcss/oxide-android-arm64": "4.1.17", - "@tailwindcss/oxide-darwin-arm64": "4.1.17", - "@tailwindcss/oxide-darwin-x64": "4.1.17", - "@tailwindcss/oxide-freebsd-x64": "4.1.17", - "@tailwindcss/oxide-linux-arm-gnueabihf": "4.1.17", - "@tailwindcss/oxide-linux-arm64-gnu": "4.1.17", - "@tailwindcss/oxide-linux-arm64-musl": "4.1.17", - "@tailwindcss/oxide-linux-x64-gnu": "4.1.17", - "@tailwindcss/oxide-linux-x64-musl": "4.1.17", - "@tailwindcss/oxide-wasm32-wasi": "4.1.17", - "@tailwindcss/oxide-win32-arm64-msvc": "4.1.17", - "@tailwindcss/oxide-win32-x64-msvc": "4.1.17" - } - }, - "node_modules/@tailwindcss/oxide-android-arm64": { - "version": "4.1.17", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-android-arm64/-/oxide-android-arm64-4.1.17.tgz", - "integrity": "sha512-BMqpkJHgOZ5z78qqiGE6ZIRExyaHyuxjgrJ6eBO5+hfrfGkuya0lYfw8fRHG77gdTjWkNWEEm+qeG2cDMxArLQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@tailwindcss/oxide-darwin-arm64": { - "version": "4.1.17", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-arm64/-/oxide-darwin-arm64-4.1.17.tgz", - "integrity": "sha512-EquyumkQweUBNk1zGEU/wfZo2qkp/nQKRZM8bUYO0J+Lums5+wl2CcG1f9BgAjn/u9pJzdYddHWBiFXJTcxmOg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@tailwindcss/oxide-darwin-x64": { - "version": "4.1.17", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-x64/-/oxide-darwin-x64-4.1.17.tgz", - "integrity": "sha512-gdhEPLzke2Pog8s12oADwYu0IAw04Y2tlmgVzIN0+046ytcgx8uZmCzEg4VcQh+AHKiS7xaL8kGo/QTiNEGRog==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@tailwindcss/oxide-freebsd-x64": { - "version": "4.1.17", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-freebsd-x64/-/oxide-freebsd-x64-4.1.17.tgz", - "integrity": "sha512-hxGS81KskMxML9DXsaXT1H0DyA+ZBIbyG/sSAjWNe2EDl7TkPOBI42GBV3u38itzGUOmFfCzk1iAjDXds8Oh0g==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@tailwindcss/oxide-linux-arm-gnueabihf": { - "version": "4.1.17", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm-gnueabihf/-/oxide-linux-arm-gnueabihf-4.1.17.tgz", - "integrity": "sha512-k7jWk5E3ldAdw0cNglhjSgv501u7yrMf8oeZ0cElhxU6Y2o7f8yqelOp3fhf7evjIS6ujTI3U8pKUXV2I4iXHQ==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@tailwindcss/oxide-linux-arm64-gnu": { - "version": "4.1.17", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-gnu/-/oxide-linux-arm64-gnu-4.1.17.tgz", - "integrity": "sha512-HVDOm/mxK6+TbARwdW17WrgDYEGzmoYayrCgmLEw7FxTPLcp/glBisuyWkFz/jb7ZfiAXAXUACfyItn+nTgsdQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@tailwindcss/oxide-linux-arm64-musl": { - "version": "4.1.17", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-musl/-/oxide-linux-arm64-musl-4.1.17.tgz", - "integrity": "sha512-HvZLfGr42i5anKtIeQzxdkw/wPqIbpeZqe7vd3V9vI3RQxe3xU1fLjss0TjyhxWcBaipk7NYwSrwTwK1hJARMg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@tailwindcss/oxide-linux-x64-gnu": { - "version": "4.1.17", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-gnu/-/oxide-linux-x64-gnu-4.1.17.tgz", - "integrity": "sha512-M3XZuORCGB7VPOEDH+nzpJ21XPvK5PyjlkSFkFziNHGLc5d6g3di2McAAblmaSUNl8IOmzYwLx9NsE7bplNkwQ==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@tailwindcss/oxide-linux-x64-musl": { - "version": "4.1.17", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-musl/-/oxide-linux-x64-musl-4.1.17.tgz", - "integrity": "sha512-k7f+pf9eXLEey4pBlw+8dgfJHY4PZ5qOUFDyNf7SI6lHjQ9Zt7+NcscjpwdCEbYi6FI5c2KDTDWyf2iHcCSyyQ==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@tailwindcss/oxide-wasm32-wasi": { - "version": "4.1.17", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-wasm32-wasi/-/oxide-wasm32-wasi-4.1.17.tgz", - "integrity": "sha512-cEytGqSSoy7zK4JRWiTCx43FsKP/zGr0CsuMawhH67ONlH+T79VteQeJQRO/X7L0juEUA8ZyuYikcRBf0vsxhg==", - "bundleDependencies": [ - "@napi-rs/wasm-runtime", - "@emnapi/core", - "@emnapi/runtime", - "@tybys/wasm-util", - "@emnapi/wasi-threads", - "tslib" - ], - "cpu": [ - "wasm32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "@emnapi/core": "^1.6.0", - "@emnapi/runtime": "^1.6.0", - "@emnapi/wasi-threads": "^1.1.0", - "@napi-rs/wasm-runtime": "^1.0.7", - "@tybys/wasm-util": "^0.10.1", - "tslib": "^2.4.0" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/@emnapi/core": { - "version": "1.6.0", - "dev": true, - "inBundle": true, - "license": "MIT", - "optional": true, - "dependencies": { - "@emnapi/wasi-threads": "1.1.0", - "tslib": "^2.4.0" - } - }, - "node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/@emnapi/runtime": { - "version": "1.6.0", - "dev": true, - "inBundle": true, - "license": "MIT", - "optional": true, - "dependencies": { - "tslib": "^2.4.0" - } - }, - "node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/@emnapi/wasi-threads": { - "version": "1.1.0", - "dev": true, - "inBundle": true, - "license": "MIT", - "optional": true, - "dependencies": { - "tslib": "^2.4.0" - } - }, - "node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/@napi-rs/wasm-runtime": { - "version": "1.0.7", - "dev": true, - "inBundle": true, - "license": "MIT", - "optional": true, - "dependencies": { - "@emnapi/core": "^1.5.0", - "@emnapi/runtime": "^1.5.0", - "@tybys/wasm-util": "^0.10.1" - } - }, - "node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/@tybys/wasm-util": { - "version": "0.10.1", - "dev": true, - "inBundle": true, - "license": "MIT", - "optional": true, - "dependencies": { - "tslib": "^2.4.0" - } - }, - "node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/tslib": { - "version": "2.8.1", - "dev": true, - "inBundle": true, - "license": "0BSD", - "optional": true - }, - "node_modules/@tailwindcss/oxide-win32-arm64-msvc": { - "version": "4.1.17", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-arm64-msvc/-/oxide-win32-arm64-msvc-4.1.17.tgz", - "integrity": "sha512-JU5AHr7gKbZlOGvMdb4722/0aYbU+tN6lv1kONx0JK2cGsh7g148zVWLM0IKR3NeKLv+L90chBVYcJ8uJWbC9A==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@tailwindcss/oxide-win32-x64-msvc": { - "version": "4.1.17", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-x64-msvc/-/oxide-win32-x64-msvc-4.1.17.tgz", - "integrity": "sha512-SKWM4waLuqx0IH+FMDUw6R66Hu4OuTALFgnleKbqhgGU30DY20NORZMZUKgLRjQXNN2TLzKvh48QXTig4h4bGw==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@tailwindcss/postcss": { - "version": "4.1.17", - "resolved": "https://registry.npmjs.org/@tailwindcss/postcss/-/postcss-4.1.17.tgz", - "integrity": "sha512-+nKl9N9mN5uJ+M7dBOOCzINw94MPstNR/GtIhz1fpZysxL/4a+No64jCBD6CPN+bIHWFx3KWuu8XJRrj/572Dw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@alloc/quick-lru": "^5.2.0", - "@tailwindcss/node": "4.1.17", - "@tailwindcss/oxide": "4.1.17", - "postcss": "^8.4.41", - "tailwindcss": "4.1.17" - } - }, - "node_modules/@tailwindcss/typography": { - "version": "0.5.19", - "resolved": "https://registry.npmjs.org/@tailwindcss/typography/-/typography-0.5.19.tgz", - "integrity": "sha512-w31dd8HOx3k9vPtcQh5QHP9GwKcgbMp87j58qi6xgiBnFFtKEAgCWnDw4qUT8aHwkCp8bKvb/KGKWWHedP0AAg==", - "dev": true, - "license": "MIT", - "dependencies": { - "postcss-selector-parser": "6.0.10" - }, - "peerDependencies": { - "tailwindcss": ">=3.0.0 || insiders || >=4.0.0-alpha.20 || >=4.0.0-beta.1" - } - }, - "node_modules/@tailwindcss/vite": { - "version": "4.1.17", - "resolved": "https://registry.npmjs.org/@tailwindcss/vite/-/vite-4.1.17.tgz", - "integrity": "sha512-4+9w8ZHOiGnpcGI6z1TVVfWaX/koK7fKeSYF3qlYg2xpBtbteP2ddBxiarL+HVgfSJGeK5RIxRQmKm4rTJJAwA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@tailwindcss/node": "4.1.17", - "@tailwindcss/oxide": "4.1.17", - "tailwindcss": "4.1.17" - }, - "peerDependencies": { - "vite": "^5.2.0 || ^6 || ^7" - } - }, - "node_modules/@tailwindplus/elements": { - "version": "1.0.19", - "resolved": "https://registry.npmjs.org/@tailwindplus/elements/-/elements-1.0.19.tgz", - "integrity": "sha512-JMtqgYmZYNXO3VWu+SFxt2DChD/FL2A42jEhkUC9maQpFjfi0rhLm8lBFFo39Xbe9dwH0rT3fOi6DhSNFFnuzA==", - "dev": true, - "license": "SEE LICENSE IN LICENSE.md" - }, - "node_modules/@types/estree": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", - "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", - "dev": true, - "license": "MIT" - }, - "node_modules/@vue/reactivity": { - "version": "3.1.5", - "resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.1.5.tgz", - "integrity": "sha512-1tdfLmNjWG6t/CsPldh+foumYFo3cpyCHgBYQ34ylaMsJ+SNHQ1kApMIa8jN+i593zQuaw3AdWH0nJTARzCFhg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@vue/shared": "3.1.5" - } - }, - "node_modules/@vue/shared": { - "version": "3.1.5", - "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.1.5.tgz", - "integrity": "sha512-oJ4F3TnvpXaQwZJNF3ZK+kLPHKarDmJjJ6jyzVNDKH9md1dptjC7lWR//jrGuLdek/U6iltWxqAnYOu8gCiOvA==", - "dev": true, - "license": "MIT" - }, - "node_modules/alpinejs": { - "version": "3.15.2", - "resolved": "https://registry.npmjs.org/alpinejs/-/alpinejs-3.15.2.tgz", - "integrity": "sha512-2kYF2aG+DTFkE6p0rHG5XmN4VEb6sO9b02aOdU4+i8QN6rL0DbRZQiypDE1gBcGO65yDcqMz5KKYUYgMUxgNkw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@vue/reactivity": "~3.1.1" - } - }, - "node_modules/argparse": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", - "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", - "dev": true, - "license": "Python-2.0" - }, - "node_modules/autoprefixer": { - "version": "10.4.22", - "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.22.tgz", - "integrity": "sha512-ARe0v/t9gO28Bznv6GgqARmVqcWOV3mfgUPn9becPHMiD3o9BwlRgaeccZnwTpZ7Zwqrm+c1sUSsMxIzQzc8Xg==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/postcss/" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/autoprefixer" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "dependencies": { - "browserslist": "^4.27.0", - "caniuse-lite": "^1.0.30001754", - "fraction.js": "^5.3.4", - "normalize-range": "^0.1.2", - "picocolors": "^1.1.1", - "postcss-value-parser": "^4.2.0" - }, - "bin": { - "autoprefixer": "bin/autoprefixer" - }, "engines": { - "node": "^10 || ^12 || >=14" - }, - "peerDependencies": { - "postcss": "^8.1.0" + "node": ">=6.0.0" } }, - "node_modules/baseline-browser-mapping": { - "version": "2.8.32", - "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.8.32.tgz", - "integrity": "sha512-OPz5aBThlyLFgxyhdwf/s2+8ab3OvT7AdTNvKHBwpXomIYeXqpUUuT8LrdtxZSsWJ4R4CU1un4XGh5Ez3nlTpw==", + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", "dev": true, - "license": "Apache-2.0", - "bin": { - "baseline-browser-mapping": "dist/cli.js" - } + "license": "MIT" }, - "node_modules/browserslist": { - "version": "4.28.0", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.0.tgz", - "integrity": "sha512-tbydkR/CxfMwelN0vwdP/pLkDwyAASZ+VfWm4EOwlB6SWhx1sYnWLqo8N5j0rAzPfzfRaxt0mM/4wPU/Su84RQ==", + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.31", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", + "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/browserslist" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], "license": "MIT", - "peer": true, "dependencies": { - "baseline-browser-mapping": "^2.8.25", - "caniuse-lite": "^1.0.30001754", - "electron-to-chromium": "^1.5.249", - "node-releases": "^2.0.27", - "update-browserslist-db": "^1.1.4" - }, - "bin": { - "browserslist": "cli.js" - }, - "engines": { - "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" - } - }, - "node_modules/callsites": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", - "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" } }, - "node_modules/caniuse-lite": { - "version": "1.0.30001757", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001757.tgz", - "integrity": "sha512-r0nnL/I28Zi/yjk1el6ilj27tKcdjLsNqAOZr0yVjWPrSQyHgKI2INaEWw21bAQSv2LXRt1XuCS/GomNpWOxsQ==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/caniuse-lite" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "CC-BY-4.0" - }, - "node_modules/cosmiconfig": { - "version": "9.0.0", - "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-9.0.0.tgz", - "integrity": "sha512-itvL5h8RETACmOTFc4UfIyB2RfEHi71Ax6E/PivVxq9NseKbOWpeyHEOIbmAw1rs8Ak0VursQNww7lf7YtUwzg==", + "node_modules/@popperjs/core": { + "version": "2.11.8", + "resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.8.tgz", + "integrity": "sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==", "dev": true, "license": "MIT", - "dependencies": { - "env-paths": "^2.2.1", - "import-fresh": "^3.3.0", - "js-yaml": "^4.1.0", - "parse-json": "^5.2.0" - }, - "engines": { - "node": ">=14" - }, "funding": { - "url": "https://github.com/sponsors/d-fischer" - }, - "peerDependencies": { - "typescript": ">=4.9.5" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } + "type": "opencollective", + "url": "https://opencollective.com/popperjs" } }, - "node_modules/css-blank-pseudo": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/css-blank-pseudo/-/css-blank-pseudo-7.0.1.tgz", - "integrity": "sha512-jf+twWGDf6LDoXDUode+nc7ZlrqfaNphrBIBrcmeP3D8yw1uPaix1gCC8LUQUGQ6CycuK2opkbFFWFuq/a94ag==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.50.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.50.1.tgz", + "integrity": "sha512-HJXwzoZN4eYTdD8bVV22DN8gsPCAj3V20NHKOs8ezfXanGpmVPR7kalUHd+Y31IJp9stdB87VKPFbsGY3H/2ag==", + "cpu": [ + "arm" ], - "license": "MIT-0", - "dependencies": { - "postcss-selector-parser": "^7.0.0" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "postcss": "^8.4" - } - }, - "node_modules/css-blank-pseudo/node_modules/postcss-selector-parser": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.0.tgz", - "integrity": "sha512-8sLjZwK0R+JlxlYcTuVnyT2v+htpdrjDOKuMcOVdYjt52Lh8hWRYpxBPoKx/Zg+bcjc3wx6fmQevMmUztS/ccA==", "dev": true, "license": "MIT", - "dependencies": { - "cssesc": "^3.0.0", - "util-deprecate": "^1.0.2" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/css-has-pseudo": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/css-has-pseudo/-/css-has-pseudo-7.0.3.tgz", - "integrity": "sha512-oG+vKuGyqe/xvEMoxAQrhi7uY16deJR3i7wwhBerVrGQKSqUC5GiOVxTpM9F9B9hw0J+eKeOWLH7E9gZ1Dr5rA==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "license": "MIT-0", - "dependencies": { - "@csstools/selector-specificity": "^5.0.0", - "postcss-selector-parser": "^7.0.0", - "postcss-value-parser": "^4.2.0" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "postcss": "^8.4" - } + "optional": true, + "os": [ + "android" + ] }, - "node_modules/css-has-pseudo/node_modules/@csstools/selector-specificity": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/@csstools/selector-specificity/-/selector-specificity-5.0.0.tgz", - "integrity": "sha512-PCqQV3c4CoVm3kdPhyeZ07VmBRdH2EpMFA/pd9OASpOEC3aXNGoqPDAZ80D0cLpMBxnmk0+yNhGsEx31hq7Gtw==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.50.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.50.1.tgz", + "integrity": "sha512-PZlsJVcjHfcH53mOImyt3bc97Ep3FJDXRpk9sMdGX0qgLmY0EIWxCag6EigerGhLVuL8lDVYNnSo8qnTElO4xw==", + "cpu": [ + "arm64" ], - "license": "MIT-0", - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "postcss-selector-parser": "^7.0.0" - } - }, - "node_modules/css-has-pseudo/node_modules/postcss-selector-parser": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.0.tgz", - "integrity": "sha512-8sLjZwK0R+JlxlYcTuVnyT2v+htpdrjDOKuMcOVdYjt52Lh8hWRYpxBPoKx/Zg+bcjc3wx6fmQevMmUztS/ccA==", "dev": true, "license": "MIT", - "peer": true, - "dependencies": { - "cssesc": "^3.0.0", - "util-deprecate": "^1.0.2" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/css-prefers-color-scheme": { - "version": "10.0.0", - "resolved": "https://registry.npmjs.org/css-prefers-color-scheme/-/css-prefers-color-scheme-10.0.0.tgz", - "integrity": "sha512-VCtXZAWivRglTZditUfB4StnsWr6YVZ2PRtuxQLKTNRdtAf8tpzaVPE9zXIF3VaSc7O70iK/j1+NXxyQCqdPjQ==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "license": "MIT-0", - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "postcss": "^8.4" - } + "optional": true, + "os": [ + "android" + ] }, - "node_modules/cssdb": { - "version": "8.4.2", - "resolved": "https://registry.npmjs.org/cssdb/-/cssdb-8.4.2.tgz", - "integrity": "sha512-PzjkRkRUS+IHDJohtxkIczlxPPZqRo0nXplsYXOMBRPjcVRjj1W4DfvRgshUYTVuUigU7ptVYkFJQ7abUB0nyg==", + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.50.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.50.1.tgz", + "integrity": "sha512-xc6i2AuWh++oGi4ylOFPmzJOEeAa2lJeGUGb4MudOtgfyyjr4UPNK+eEWTPLvmPJIY/pgw6ssFIox23SyrkkJw==", + "cpu": [ + "arm64" + ], "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - }, - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - } + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.50.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.50.1.tgz", + "integrity": "sha512-2ofU89lEpDYhdLAbRdeyz/kX3Y2lpYc6ShRnDjY35bZhd2ipuDMDi6ZTQ9NIag94K28nFMofdnKeHR7BT0CATw==", + "cpu": [ + "x64" ], - "license": "MIT-0" + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] }, - "node_modules/cssesc": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", - "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==", + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.50.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.50.1.tgz", + "integrity": "sha512-wOsE6H2u6PxsHY/BeFHA4VGQN3KUJFZp7QJBmDYI983fgxq5Th8FDkVuERb2l9vDMs1D5XhOrhBrnqcEY6l8ZA==", + "cpu": [ + "arm64" + ], "dev": true, "license": "MIT", - "bin": { - "cssesc": "bin/cssesc" - }, - "engines": { - "node": ">=4" - } + "optional": true, + "os": [ + "freebsd" + ] }, - "node_modules/detect-libc": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.0.tgz", - "integrity": "sha512-vEtk+OcP7VBRtQZ1EJ3bdgzSfBjgnEalLTp5zjJrS+2Z1w2KZly4SBdac/WDU3hhsNAZ9E8SC96ME4Ey8MZ7cg==", + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.50.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.50.1.tgz", + "integrity": "sha512-A/xeqaHTlKbQggxCqispFAcNjycpUEHP52mwMQZUNqDUJFFYtPHCXS1VAG29uMlDzIVr+i00tSFWFLivMcoIBQ==", + "cpu": [ + "x64" + ], "dev": true, - "license": "Apache-2.0", - "engines": { - "node": ">=8" - } + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] }, - "node_modules/electron-to-chromium": { - "version": "1.5.262", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.262.tgz", - "integrity": "sha512-NlAsMteRHek05jRUxUR0a5jpjYq9ykk6+kO0yRaMi5moe7u0fVIOeQ3Y30A8dIiWFBNUoQGi1ljb1i5VtS9WQQ==", + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.50.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.50.1.tgz", + "integrity": "sha512-54v4okehwl5TaSIkpp97rAHGp7t3ghinRd/vyC1iXqXMfjYUTm7TfYmCzXDoHUPTTf36L8pr0E7YsD3CfB3ZDg==", + "cpu": [ + "arm" + ], "dev": true, - "license": "ISC" + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] }, - "node_modules/enhanced-resolve": { - "version": "5.18.3", - "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.18.3.tgz", - "integrity": "sha512-d4lC8xfavMeBjzGr2vECC3fsGXziXZQyJxD868h2M/mBI3PwAuODxAkLkq5HYuvrPYcUtiLzsTo8U3PgX3Ocww==", + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.50.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.50.1.tgz", + "integrity": "sha512-p/LaFyajPN/0PUHjv8TNyxLiA7RwmDoVY3flXHPSzqrGcIp/c2FjwPPP5++u87DGHtw+5kSH5bCJz0mvXngYxw==", + "cpu": [ + "arm" + ], "dev": true, "license": "MIT", - "dependencies": { - "graceful-fs": "^4.2.4", - "tapable": "^2.2.0" - }, - "engines": { - "node": ">=10.13.0" - } + "optional": true, + "os": [ + "linux" + ] }, - "node_modules/env-paths": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-2.2.1.tgz", - "integrity": "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==", + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.50.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.50.1.tgz", + "integrity": "sha512-2AbMhFFkTo6Ptna1zO7kAXXDLi7H9fGTbVaIq2AAYO7yzcAsuTNWPHhb2aTA6GPiP+JXh85Y8CiS54iZoj4opw==", + "cpu": [ + "arm64" + ], "dev": true, "license": "MIT", - "engines": { - "node": ">=6" - } + "optional": true, + "os": [ + "linux" + ] }, - "node_modules/error-ex": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", - "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.50.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.50.1.tgz", + "integrity": "sha512-Cgef+5aZwuvesQNw9eX7g19FfKX5/pQRIyhoXLCiBOrWopjo7ycfB292TX9MDcDijiuIJlx1IzJz3IoCPfqs9w==", + "cpu": [ + "arm64" + ], "dev": true, "license": "MIT", - "dependencies": { - "is-arrayish": "^0.2.1" - } + "optional": true, + "os": [ + "linux" + ] }, - "node_modules/esbuild": { - "version": "0.25.9", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.9.tgz", - "integrity": "sha512-CRbODhYyQx3qp7ZEwzxOk4JBqmD/seJrzPa/cGjY1VtIn5E09Oi9/dB4JwctnfZ8Q8iT7rioVv5k/FNT/uf54g==", + "node_modules/@rollup/rollup-linux-loongarch64-gnu": { + "version": "4.50.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.50.1.tgz", + "integrity": "sha512-RPhTwWMzpYYrHrJAS7CmpdtHNKtt2Ueo+BlLBjfZEhYBhK00OsEqM08/7f+eohiF6poe0YRDDd8nAvwtE/Y62Q==", + "cpu": [ + "loong64" + ], "dev": true, - "hasInstallScript": true, "license": "MIT", - "bin": { - "esbuild": "bin/esbuild" - }, - "engines": { - "node": ">=18" - }, - "optionalDependencies": { - "@esbuild/aix-ppc64": "0.25.9", - "@esbuild/android-arm": "0.25.9", - "@esbuild/android-arm64": "0.25.9", - "@esbuild/android-x64": "0.25.9", - "@esbuild/darwin-arm64": "0.25.9", - "@esbuild/darwin-x64": "0.25.9", - "@esbuild/freebsd-arm64": "0.25.9", - "@esbuild/freebsd-x64": "0.25.9", - "@esbuild/linux-arm": "0.25.9", - "@esbuild/linux-arm64": "0.25.9", - "@esbuild/linux-ia32": "0.25.9", - "@esbuild/linux-loong64": "0.25.9", - "@esbuild/linux-mips64el": "0.25.9", - "@esbuild/linux-ppc64": "0.25.9", - "@esbuild/linux-riscv64": "0.25.9", - "@esbuild/linux-s390x": "0.25.9", - "@esbuild/linux-x64": "0.25.9", - "@esbuild/netbsd-arm64": "0.25.9", - "@esbuild/netbsd-x64": "0.25.9", - "@esbuild/openbsd-arm64": "0.25.9", - "@esbuild/openbsd-x64": "0.25.9", - "@esbuild/openharmony-arm64": "0.25.9", - "@esbuild/sunos-x64": "0.25.9", - "@esbuild/win32-arm64": "0.25.9", - "@esbuild/win32-ia32": "0.25.9", - "@esbuild/win32-x64": "0.25.9" - } + "optional": true, + "os": [ + "linux" + ] }, - "node_modules/escalade": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", - "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "node_modules/@rollup/rollup-linux-ppc64-gnu": { + "version": "4.50.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.50.1.tgz", + "integrity": "sha512-eSGMVQw9iekut62O7eBdbiccRguuDgiPMsw++BVUg+1K7WjZXHOg/YOT9SWMzPZA+w98G+Fa1VqJgHZOHHnY0Q==", + "cpu": [ + "ppc64" + ], "dev": true, "license": "MIT", - "engines": { - "node": ">=6" - } + "optional": true, + "os": [ + "linux" + ] }, - "node_modules/fraction.js": { - "version": "5.3.4", - "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-5.3.4.tgz", - "integrity": "sha512-1X1NTtiJphryn/uLQz3whtY6jK3fTqoE3ohKs0tT+Ujr1W59oopxmoEh7Lu5p6vBaPbgoM0bzveAW4Qi5RyWDQ==", + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.50.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.50.1.tgz", + "integrity": "sha512-S208ojx8a4ciIPrLgazF6AgdcNJzQE4+S9rsmOmDJkusvctii+ZvEuIC4v/xFqzbuP8yDjn73oBlNDgF6YGSXQ==", + "cpu": [ + "riscv64" + ], "dev": true, "license": "MIT", - "engines": { - "node": "*" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/rawify" - } + "optional": true, + "os": [ + "linux" + ] }, - "node_modules/fsevents": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", - "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.50.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.50.1.tgz", + "integrity": "sha512-3Ag8Ls1ggqkGUvSZWYcdgFwriy2lWo+0QlYgEFra/5JGtAd6C5Hw59oojx1DeqcA2Wds2ayRgvJ4qxVTzCHgzg==", + "cpu": [ + "riscv64" + ], "dev": true, - "hasInstallScript": true, "license": "MIT", "optional": true, "os": [ - "darwin" + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.50.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.50.1.tgz", + "integrity": "sha512-t9YrKfaxCYe7l7ldFERE1BRg/4TATxIg+YieHQ966jwvo7ddHJxPj9cNFWLAzhkVsbBvNA4qTbPVNsZKBO4NSg==", + "cpu": [ + "s390x" ], - "engines": { - "node": "^8.16.0 || ^10.6.0 || >=11.0.0" - } + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] }, - "node_modules/graceful-fs": { - "version": "4.2.11", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", - "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.50.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.50.1.tgz", + "integrity": "sha512-MCgtFB2+SVNuQmmjHf+wfI4CMxy3Tk8XjA5Z//A0AKD7QXUYFMQcns91K6dEHBvZPCnhJSyDWLApk40Iq/H3tA==", + "cpu": [ + "x64" + ], "dev": true, - "license": "ISC" + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] }, - "node_modules/highlight.js": { - "version": "11.11.1", - "resolved": "https://registry.npmjs.org/highlight.js/-/highlight.js-11.11.1.tgz", - "integrity": "sha512-Xwwo44whKBVCYoliBQwaPvtd/2tYFkRQtXDWj1nackaV2JPXx3L0+Jvd8/qCJ2p+ML0/XVkJ2q+Mr+UVdpJK5w==", + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.50.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.50.1.tgz", + "integrity": "sha512-nEvqG+0jeRmqaUMuwzlfMKwcIVffy/9KGbAGyoa26iu6eSngAYQ512bMXuqqPrlTyfqdlB9FVINs93j534UJrg==", + "cpu": [ + "x64" + ], "dev": true, - "license": "BSD-3-Clause", - "engines": { - "node": ">=12.0.0" - } + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] }, - "node_modules/import-fresh": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", - "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==", + "node_modules/@rollup/rollup-openharmony-arm64": { + "version": "4.50.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.50.1.tgz", + "integrity": "sha512-RDsLm+phmT3MJd9SNxA9MNuEAO/J2fhW8GXk62G/B4G7sLVumNFbRwDL6v5NrESb48k+QMqdGbHgEtfU0LCpbA==", + "cpu": [ + "arm64" + ], "dev": true, "license": "MIT", - "dependencies": { - "parent-module": "^1.0.0", - "resolve-from": "^4.0.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } + "optional": true, + "os": [ + "openharmony" + ] }, - "node_modules/is-arrayish": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", - "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.50.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.50.1.tgz", + "integrity": "sha512-hpZB/TImk2FlAFAIsoElM3tLzq57uxnGYwplg6WDyAxbYczSi8O2eQ+H2Lx74504rwKtZ3N2g4bCUkiamzS6TQ==", + "cpu": [ + "arm64" + ], "dev": true, - "license": "MIT" + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] }, - "node_modules/jiti": { - "version": "2.6.1", - "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.6.1.tgz", - "integrity": "sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==", + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.50.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.50.1.tgz", + "integrity": "sha512-SXjv8JlbzKM0fTJidX4eVsH+Wmnp0/WcD8gJxIZyR6Gay5Qcsmdbi9zVtnbkGPG8v2vMR1AD06lGWy5FLMcG7A==", + "cpu": [ + "ia32" + ], "dev": true, "license": "MIT", - "bin": { - "jiti": "lib/jiti-cli.mjs" - } + "optional": true, + "os": [ + "win32" + ] }, - "node_modules/js-tokens": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", - "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.50.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.50.1.tgz", + "integrity": "sha512-StxAO/8ts62KZVRAm4JZYq9+NqNsV7RvimNK+YM7ry//zebEH6meuugqW/P5OFUCjyQgui+9fUxT6d5NShvMvA==", + "cpu": [ + "x64" + ], "dev": true, - "license": "MIT" + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] }, - "node_modules/js-yaml": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", - "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "node_modules/@ryangjchandler/alpine-tooltip": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@ryangjchandler/alpine-tooltip/-/alpine-tooltip-2.0.1.tgz", + "integrity": "sha512-Hv9C02bUE4JSewZhfceYSHw+oiBuUF3ITCMKqmiFWJ3bvjhHZzM8YEPpNQVMfEVRR4K4JJYJD16re4stGIjoyg==", "dev": true, - "license": "MIT", "dependencies": { - "argparse": "^2.0.1" - }, - "bin": { - "js-yaml": "bin/js-yaml.js" + "tippy.js": "^6.3.1" } }, - "node_modules/json-parse-even-better-errors": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", - "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", - "dev": true, - "license": "MIT" - }, - "node_modules/laravel-vite-plugin": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/laravel-vite-plugin/-/laravel-vite-plugin-2.0.1.tgz", - "integrity": "sha512-zQuvzWfUKQu9oNVi1o0RZAJCwhGsdhx4NEOyrVQwJHaWDseGP9tl7XUPLY2T8Cj6+IrZ6lmyxlR1KC8unf3RLA==", + "node_modules/@tailwindcss/forms": { + "version": "0.5.11", + "resolved": "https://registry.npmjs.org/@tailwindcss/forms/-/forms-0.5.11.tgz", + "integrity": "sha512-h9wegbZDPurxG22xZSoWtdzc41/OlNEUQERNqI/0fOwa2aVlWGu7C35E/x6LDyD3lgtztFSSjKZyuVM0hxhbgA==", "dev": true, "license": "MIT", "dependencies": { - "picocolors": "^1.0.0", - "vite-plugin-full-reload": "^1.1.0" - }, - "bin": { - "clean-orphaned-assets": "bin/clean.js" - }, - "engines": { - "node": "^20.19.0 || >=22.12.0" + "mini-svg-data-uri": "^1.2.3" }, "peerDependencies": { - "vite": "^7.0.0" + "tailwindcss": ">=3.0.0 || >= 3.0.0-alpha.1 || >= 4.0.0-alpha.20 || >= 4.0.0-beta.1" } }, - "node_modules/lightningcss": { - "version": "1.30.2", - "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.30.2.tgz", - "integrity": "sha512-utfs7Pr5uJyyvDETitgsaqSyjCb2qNRAtuqUeWIAKztsOYdcACf2KtARYXg2pSvhkt+9NfoaNY7fxjl6nuMjIQ==", + "node_modules/@tailwindcss/node": { + "version": "4.1.18", + "resolved": "https://registry.npmjs.org/@tailwindcss/node/-/node-4.1.18.tgz", + "integrity": "sha512-DoR7U1P7iYhw16qJ49fgXUlry1t4CpXeErJHnQ44JgTSKMaZUdf17cfn5mHchfJ4KRBZRFA/Coo+MUF5+gOaCQ==", "dev": true, - "license": "MPL-2.0", + "license": "MIT", "dependencies": { - "detect-libc": "^2.0.3" - }, + "@jridgewell/remapping": "^2.3.4", + "enhanced-resolve": "^5.18.3", + "jiti": "^2.6.1", + "lightningcss": "1.30.2", + "magic-string": "^0.30.21", + "source-map-js": "^1.2.1", + "tailwindcss": "4.1.18" + } + }, + "node_modules/@tailwindcss/oxide": { + "version": "4.1.18", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide/-/oxide-4.1.18.tgz", + "integrity": "sha512-EgCR5tTS5bUSKQgzeMClT6iCY3ToqE1y+ZB0AKldj809QXk1Y+3jB0upOYZrn9aGIzPtUsP7sX4QQ4XtjBB95A==", + "dev": true, + "license": "MIT", "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" + "node": ">= 10" }, "optionalDependencies": { - "lightningcss-android-arm64": "1.30.2", - "lightningcss-darwin-arm64": "1.30.2", - "lightningcss-darwin-x64": "1.30.2", - "lightningcss-freebsd-x64": "1.30.2", - "lightningcss-linux-arm-gnueabihf": "1.30.2", - "lightningcss-linux-arm64-gnu": "1.30.2", - "lightningcss-linux-arm64-musl": "1.30.2", - "lightningcss-linux-x64-gnu": "1.30.2", - "lightningcss-linux-x64-musl": "1.30.2", - "lightningcss-win32-arm64-msvc": "1.30.2", - "lightningcss-win32-x64-msvc": "1.30.2" + "@tailwindcss/oxide-android-arm64": "4.1.18", + "@tailwindcss/oxide-darwin-arm64": "4.1.18", + "@tailwindcss/oxide-darwin-x64": "4.1.18", + "@tailwindcss/oxide-freebsd-x64": "4.1.18", + "@tailwindcss/oxide-linux-arm-gnueabihf": "4.1.18", + "@tailwindcss/oxide-linux-arm64-gnu": "4.1.18", + "@tailwindcss/oxide-linux-arm64-musl": "4.1.18", + "@tailwindcss/oxide-linux-x64-gnu": "4.1.18", + "@tailwindcss/oxide-linux-x64-musl": "4.1.18", + "@tailwindcss/oxide-wasm32-wasi": "4.1.18", + "@tailwindcss/oxide-win32-arm64-msvc": "4.1.18", + "@tailwindcss/oxide-win32-x64-msvc": "4.1.18" } }, - "node_modules/lightningcss-android-arm64": { - "version": "1.30.2", - "resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.30.2.tgz", - "integrity": "sha512-BH9sEdOCahSgmkVhBLeU7Hc9DWeZ1Eb6wNS6Da8igvUwAe0sqROHddIlvU06q3WyXVEOYDZ6ykBZQnjTbmo4+A==", + "node_modules/@tailwindcss/oxide-android-arm64": { + "version": "4.1.18", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-android-arm64/-/oxide-android-arm64-4.1.18.tgz", + "integrity": "sha512-dJHz7+Ugr9U/diKJA0W6N/6/cjI+ZTAoxPf9Iz9BFRF2GzEX8IvXxFIi/dZBloVJX/MZGvRuFA9rqwdiIEZQ0Q==", "cpu": [ "arm64" ], "dev": true, - "license": "MPL-2.0", + "license": "MIT", "optional": true, "os": [ "android" ], "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" + "node": ">= 10" } }, - "node_modules/lightningcss-darwin-arm64": { - "version": "1.30.2", - "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.30.2.tgz", - "integrity": "sha512-ylTcDJBN3Hp21TdhRT5zBOIi73P6/W0qwvlFEk22fkdXchtNTOU4Qc37SkzV+EKYxLouZ6M4LG9NfZ1qkhhBWA==", + "node_modules/@tailwindcss/oxide-darwin-arm64": { + "version": "4.1.18", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-arm64/-/oxide-darwin-arm64-4.1.18.tgz", + "integrity": "sha512-Gc2q4Qhs660bhjyBSKgq6BYvwDz4G+BuyJ5H1xfhmDR3D8HnHCmT/BSkvSL0vQLy/nkMLY20PQ2OoYMO15Jd0A==", "cpu": [ "arm64" ], "dev": true, - "license": "MPL-2.0", + "license": "MIT", "optional": true, "os": [ "darwin" ], "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" + "node": ">= 10" } }, - "node_modules/lightningcss-darwin-x64": { - "version": "1.30.2", - "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.30.2.tgz", - "integrity": "sha512-oBZgKchomuDYxr7ilwLcyms6BCyLn0z8J0+ZZmfpjwg9fRVZIR5/GMXd7r9RH94iDhld3UmSjBM6nXWM2TfZTQ==", + "node_modules/@tailwindcss/oxide-darwin-x64": { + "version": "4.1.18", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-x64/-/oxide-darwin-x64-4.1.18.tgz", + "integrity": "sha512-FL5oxr2xQsFrc3X9o1fjHKBYBMD1QZNyc1Xzw/h5Qu4XnEBi3dZn96HcHm41c/euGV+GRiXFfh2hUCyKi/e+yw==", "cpu": [ "x64" ], "dev": true, - "license": "MPL-2.0", + "license": "MIT", "optional": true, "os": [ "darwin" ], "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" + "node": ">= 10" } }, - "node_modules/lightningcss-freebsd-x64": { - "version": "1.30.2", - "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.30.2.tgz", - "integrity": "sha512-c2bH6xTrf4BDpK8MoGG4Bd6zAMZDAXS569UxCAGcA7IKbHNMlhGQ89eRmvpIUGfKWNVdbhSbkQaWhEoMGmGslA==", + "node_modules/@tailwindcss/oxide-freebsd-x64": { + "version": "4.1.18", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-freebsd-x64/-/oxide-freebsd-x64-4.1.18.tgz", + "integrity": "sha512-Fj+RHgu5bDodmV1dM9yAxlfJwkkWvLiRjbhuO2LEtwtlYlBgiAT4x/j5wQr1tC3SANAgD+0YcmWVrj8R9trVMA==", "cpu": [ "x64" ], "dev": true, - "license": "MPL-2.0", + "license": "MIT", "optional": true, "os": [ "freebsd" ], "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" + "node": ">= 10" } }, - "node_modules/lightningcss-linux-arm-gnueabihf": { - "version": "1.30.2", - "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.30.2.tgz", - "integrity": "sha512-eVdpxh4wYcm0PofJIZVuYuLiqBIakQ9uFZmipf6LF/HRj5Bgm0eb3qL/mr1smyXIS1twwOxNWndd8z0E374hiA==", + "node_modules/@tailwindcss/oxide-linux-arm-gnueabihf": { + "version": "4.1.18", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm-gnueabihf/-/oxide-linux-arm-gnueabihf-4.1.18.tgz", + "integrity": "sha512-Fp+Wzk/Ws4dZn+LV2Nqx3IilnhH51YZoRaYHQsVq3RQvEl+71VGKFpkfHrLM/Li+kt5c0DJe/bHXK1eHgDmdiA==", "cpu": [ "arm" ], "dev": true, - "license": "MPL-2.0", + "license": "MIT", "optional": true, "os": [ "linux" ], "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" + "node": ">= 10" } }, - "node_modules/lightningcss-linux-arm64-gnu": { - "version": "1.30.2", - "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.30.2.tgz", - "integrity": "sha512-UK65WJAbwIJbiBFXpxrbTNArtfuznvxAJw4Q2ZGlU8kPeDIWEX1dg3rn2veBVUylA2Ezg89ktszWbaQnxD/e3A==", + "node_modules/@tailwindcss/oxide-linux-arm64-gnu": { + "version": "4.1.18", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-gnu/-/oxide-linux-arm64-gnu-4.1.18.tgz", + "integrity": "sha512-S0n3jboLysNbh55Vrt7pk9wgpyTTPD0fdQeh7wQfMqLPM/Hrxi+dVsLsPrycQjGKEQk85Kgbx+6+QnYNiHalnw==", "cpu": [ "arm64" ], "dev": true, - "license": "MPL-2.0", + "license": "MIT", "optional": true, "os": [ "linux" ], "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" + "node": ">= 10" } }, - "node_modules/lightningcss-linux-arm64-musl": { - "version": "1.30.2", - "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.30.2.tgz", - "integrity": "sha512-5Vh9dGeblpTxWHpOx8iauV02popZDsCYMPIgiuw97OJ5uaDsL86cnqSFs5LZkG3ghHoX5isLgWzMs+eD1YzrnA==", + "node_modules/@tailwindcss/oxide-linux-arm64-musl": { + "version": "4.1.18", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-musl/-/oxide-linux-arm64-musl-4.1.18.tgz", + "integrity": "sha512-1px92582HkPQlaaCkdRcio71p8bc8i/ap5807tPRDK/uw953cauQBT8c5tVGkOwrHMfc2Yh6UuxaH4vtTjGvHg==", "cpu": [ "arm64" ], "dev": true, - "license": "MPL-2.0", + "license": "MIT", "optional": true, "os": [ "linux" ], "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" + "node": ">= 10" } }, - "node_modules/lightningcss-linux-x64-gnu": { - "version": "1.30.2", - "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.30.2.tgz", - "integrity": "sha512-Cfd46gdmj1vQ+lR6VRTTadNHu6ALuw2pKR9lYq4FnhvgBc4zWY1EtZcAc6EffShbb1MFrIPfLDXD6Xprbnni4w==", + "node_modules/@tailwindcss/oxide-linux-x64-gnu": { + "version": "4.1.18", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-gnu/-/oxide-linux-x64-gnu-4.1.18.tgz", + "integrity": "sha512-v3gyT0ivkfBLoZGF9LyHmts0Isc8jHZyVcbzio6Wpzifg/+5ZJpDiRiUhDLkcr7f/r38SWNe7ucxmGW3j3Kb/g==", "cpu": [ "x64" ], "dev": true, - "license": "MPL-2.0", + "license": "MIT", "optional": true, "os": [ "linux" ], "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" + "node": ">= 10" } }, - "node_modules/lightningcss-linux-x64-musl": { - "version": "1.30.2", - "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.30.2.tgz", - "integrity": "sha512-XJaLUUFXb6/QG2lGIW6aIk6jKdtjtcffUT0NKvIqhSBY3hh9Ch+1LCeH80dR9q9LBjG3ewbDjnumefsLsP6aiA==", + "node_modules/@tailwindcss/oxide-linux-x64-musl": { + "version": "4.1.18", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-musl/-/oxide-linux-x64-musl-4.1.18.tgz", + "integrity": "sha512-bhJ2y2OQNlcRwwgOAGMY0xTFStt4/wyU6pvI6LSuZpRgKQwxTec0/3Scu91O8ir7qCR3AuepQKLU/kX99FouqQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-wasm32-wasi": { + "version": "4.1.18", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-wasm32-wasi/-/oxide-wasm32-wasi-4.1.18.tgz", + "integrity": "sha512-LffYTvPjODiP6PT16oNeUQJzNVyJl1cjIebq/rWWBF+3eDst5JGEFSc5cWxyRCJ0Mxl+KyIkqRxk1XPEs9x8TA==", + "bundleDependencies": [ + "@napi-rs/wasm-runtime", + "@emnapi/core", + "@emnapi/runtime", + "@tybys/wasm-util", + "@emnapi/wasi-threads", + "tslib" + ], "cpu": [ - "x64" + "wasm32" ], "dev": true, - "license": "MPL-2.0", + "license": "MIT", "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 12.0.0" + "dependencies": { + "@emnapi/core": "^1.7.1", + "@emnapi/runtime": "^1.7.1", + "@emnapi/wasi-threads": "^1.1.0", + "@napi-rs/wasm-runtime": "^1.1.0", + "@tybys/wasm-util": "^0.10.1", + "tslib": "^2.4.0" }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" + "engines": { + "node": ">=14.0.0" } }, - "node_modules/lightningcss-win32-arm64-msvc": { - "version": "1.30.2", - "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.30.2.tgz", - "integrity": "sha512-FZn+vaj7zLv//D/192WFFVA0RgHawIcHqLX9xuWiQt7P0PtdFEVaxgF9rjM/IRYHQXNnk61/H/gb2Ei+kUQ4xQ==", + "node_modules/@tailwindcss/oxide-win32-arm64-msvc": { + "version": "4.1.18", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-arm64-msvc/-/oxide-win32-arm64-msvc-4.1.18.tgz", + "integrity": "sha512-HjSA7mr9HmC8fu6bdsZvZ+dhjyGCLdotjVOgLA2vEqxEBZaQo9YTX4kwgEvPCpRh8o4uWc4J/wEoFzhEmjvPbA==", "cpu": [ "arm64" ], "dev": true, - "license": "MPL-2.0", + "license": "MIT", "optional": true, "os": [ "win32" ], "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" + "node": ">= 10" } }, - "node_modules/lightningcss-win32-x64-msvc": { - "version": "1.30.2", - "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.30.2.tgz", - "integrity": "sha512-5g1yc73p+iAkid5phb4oVFMB45417DkRevRbt/El/gKXJk4jid+vPFF/AXbxn05Aky8PapwzZrdJShv5C0avjw==", + "node_modules/@tailwindcss/oxide-win32-x64-msvc": { + "version": "4.1.18", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-x64-msvc/-/oxide-win32-x64-msvc-4.1.18.tgz", + "integrity": "sha512-bJWbyYpUlqamC8dpR7pfjA0I7vdF6t5VpUGMWRkXVE3AXgIZjYUYAK7II1GNaxR8J1SSrSrppRar8G++JekE3Q==", "cpu": [ "x64" ], "dev": true, - "license": "MPL-2.0", + "license": "MIT", "optional": true, "os": [ "win32" ], "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" + "node": ">= 10" } }, - "node_modules/lines-and-columns": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", - "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", - "dev": true, - "license": "MIT" - }, - "node_modules/lodash": { - "version": "4.17.21", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", - "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", - "dev": true, - "license": "MIT" - }, - "node_modules/magic-string": { - "version": "0.30.21", - "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", - "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", + "node_modules/@tailwindcss/postcss": { + "version": "4.1.18", + "resolved": "https://registry.npmjs.org/@tailwindcss/postcss/-/postcss-4.1.18.tgz", + "integrity": "sha512-Ce0GFnzAOuPyfV5SxjXGn0CubwGcuDB0zcdaPuCSzAa/2vII24JTkH+I6jcbXLb1ctjZMZZI6OjDaLPJQL1S0g==", "dev": true, "license": "MIT", "dependencies": { - "@jridgewell/sourcemap-codec": "^1.5.5" + "@alloc/quick-lru": "^5.2.0", + "@tailwindcss/node": "4.1.18", + "@tailwindcss/oxide": "4.1.18", + "postcss": "^8.4.41", + "tailwindcss": "4.1.18" } }, - "node_modules/mini-svg-data-uri": { - "version": "1.4.4", - "resolved": "https://registry.npmjs.org/mini-svg-data-uri/-/mini-svg-data-uri-1.4.4.tgz", - "integrity": "sha512-r9deDe9p5FJUPZAk3A59wGH7Ii9YrjjWw0jmw/liSbHl2CHiyXj6FcDXDu2K3TjVAXqiJdaw3xxwlZZr9E6nHg==", + "node_modules/@tailwindcss/typography": { + "version": "0.5.19", + "resolved": "https://registry.npmjs.org/@tailwindcss/typography/-/typography-0.5.19.tgz", + "integrity": "sha512-w31dd8HOx3k9vPtcQh5QHP9GwKcgbMp87j58qi6xgiBnFFtKEAgCWnDw4qUT8aHwkCp8bKvb/KGKWWHedP0AAg==", "dev": true, "license": "MIT", - "bin": { - "mini-svg-data-uri": "cli.js" + "dependencies": { + "postcss-selector-parser": "6.0.10" + }, + "peerDependencies": { + "tailwindcss": ">=3.0.0 || insiders || >=4.0.0-alpha.20 || >=4.0.0-beta.1" } }, - "node_modules/nanoid": { - "version": "3.3.11", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", - "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", + "node_modules/@tailwindcss/vite": { + "version": "4.1.18", + "resolved": "https://registry.npmjs.org/@tailwindcss/vite/-/vite-4.1.18.tgz", + "integrity": "sha512-jVA+/UpKL1vRLg6Hkao5jldawNmRo7mQYrZtNHMIVpLfLhDml5nMRUo/8MwoX2vNXvnaXNNMedrMfMugAVX1nA==", "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], "license": "MIT", - "bin": { - "nanoid": "bin/nanoid.cjs" + "dependencies": { + "@tailwindcss/node": "4.1.18", + "@tailwindcss/oxide": "4.1.18", + "tailwindcss": "4.1.18" }, - "engines": { - "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + "peerDependencies": { + "vite": "^5.2.0 || ^6 || ^7" } }, - "node_modules/node-releases": { - "version": "2.0.27", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.27.tgz", - "integrity": "sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA==", + "node_modules/@tailwindplus/elements": { + "version": "1.0.21", + "resolved": "https://registry.npmjs.org/@tailwindplus/elements/-/elements-1.0.21.tgz", + "integrity": "sha512-CShPoilDolGpzT9J4MVTCexOijwURW+MHGNMoS5ijiD2FVNk1XHFvEuy6mTeKQ5dOAYNLGwK3VjMOQ8io42H+Q==", + "dev": true, + "license": "SEE LICENSE IN LICENSE.md" + }, + "node_modules/@types/estree": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", + "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", "dev": true, "license": "MIT" }, - "node_modules/normalize-range": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/normalize-range/-/normalize-range-0.1.2.tgz", - "integrity": "sha512-bdok/XvKII3nUpklnV6P2hxtMNrCboOjAcyBuQnWEhO665FwrSNRxU+AqpsyvO6LgGYPspN+lu5CLtw4jPRKNA==", + "node_modules/@vue/reactivity": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.1.5.tgz", + "integrity": "sha512-1tdfLmNjWG6t/CsPldh+foumYFo3cpyCHgBYQ34ylaMsJ+SNHQ1kApMIa8jN+i593zQuaw3AdWH0nJTARzCFhg==", "dev": true, "license": "MIT", - "engines": { - "node": ">=0.10.0" + "dependencies": { + "@vue/shared": "3.1.5" } }, - "node_modules/parent-module": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", - "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "node_modules/@vue/shared": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.1.5.tgz", + "integrity": "sha512-oJ4F3TnvpXaQwZJNF3ZK+kLPHKarDmJjJ6jyzVNDKH9md1dptjC7lWR//jrGuLdek/U6iltWxqAnYOu8gCiOvA==", + "dev": true, + "license": "MIT" + }, + "node_modules/alpinejs": { + "version": "3.15.3", + "resolved": "https://registry.npmjs.org/alpinejs/-/alpinejs-3.15.3.tgz", + "integrity": "sha512-fSI6F5213FdpMC4IWaup92KhuH3jBX0VVqajRJ6cOTCy1cL6888KyXdGO+seAAkn+g6fnrxBqQEx6gRpQ5EZoQ==", "dev": true, "license": "MIT", "dependencies": { - "callsites": "^3.0.0" - }, - "engines": { - "node": ">=6" + "@vue/reactivity": "~3.1.1" } }, - "node_modules/parse-json": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", - "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", "dev": true, "license": "MIT", - "dependencies": { - "@babel/code-frame": "^7.0.0", - "error-ex": "^1.3.1", - "json-parse-even-better-errors": "^2.3.0", - "lines-and-columns": "^1.1.6" - }, "engines": { "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/picocolors": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", - "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", - "dev": true, - "license": "ISC" - }, - "node_modules/picomatch": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", - "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", "dev": true, "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, "engines": { - "node": ">=8.6" + "node": ">=8" }, "funding": { - "url": "https://github.com/sponsors/jonschlinkert" + "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/postcss": { - "version": "8.5.6", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz", - "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==", + "node_modules/autoprefixer": { + "version": "10.4.23", + "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.23.tgz", + "integrity": "sha512-YYTXSFulfwytnjAPlw8QHncHJmlvFKtczb8InXaAx9Q0LbfDnfEYDE55omerIJKihhmU61Ft+cAOSzQVaBUmeA==", "dev": true, "funding": [ { @@ -3508,7 +1260,7 @@ }, { "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/postcss" + "url": "https://tidelift.com/funding/github/npm/autoprefixer" }, { "type": "github", @@ -3516,908 +1268,776 @@ } ], "license": "MIT", - "peer": true, "dependencies": { - "nanoid": "^3.3.11", + "browserslist": "^4.28.1", + "caniuse-lite": "^1.0.30001760", + "fraction.js": "^5.3.4", "picocolors": "^1.1.1", - "source-map-js": "^1.2.1" + "postcss-value-parser": "^4.2.0" + }, + "bin": { + "autoprefixer": "bin/autoprefixer" }, "engines": { "node": "^10 || ^12 || >=14" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, + "node_modules/baseline-browser-mapping": { + "version": "2.9.10", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.9.10.tgz", + "integrity": "sha512-2VIKvDx8Z1a9rTB2eCkdPE5nSe28XnA+qivGnWHoB40hMMt/h1hSz0960Zqsn6ZyxWXUie0EBdElKv8may20AA==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "baseline-browser-mapping": "dist/cli.js" } }, - "node_modules/postcss-attribute-case-insensitive": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/postcss-attribute-case-insensitive/-/postcss-attribute-case-insensitive-7.0.1.tgz", - "integrity": "sha512-Uai+SupNSqzlschRyNx3kbCTWgY/2hcwtHEI/ej2LJWc9JJ77qKgGptd8DHwY1mXtZ7Aoh4z4yxfwMBue9eNgw==", + "node_modules/browserslist": { + "version": "4.28.1", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.1.tgz", + "integrity": "sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA==", "dev": true, "funding": [ { - "type": "github", - "url": "https://github.com/sponsors/csstools" + "type": "opencollective", + "url": "https://opencollective.com/browserslist" }, { - "type": "opencollective", - "url": "https://opencollective.com/csstools" + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" } ], "license": "MIT", "dependencies": { - "postcss-selector-parser": "^7.0.0" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "postcss": "^8.4" - } - }, - "node_modules/postcss-attribute-case-insensitive/node_modules/postcss-selector-parser": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.0.tgz", - "integrity": "sha512-8sLjZwK0R+JlxlYcTuVnyT2v+htpdrjDOKuMcOVdYjt52Lh8hWRYpxBPoKx/Zg+bcjc3wx6fmQevMmUztS/ccA==", - "dev": true, - "license": "MIT", - "dependencies": { - "cssesc": "^3.0.0", - "util-deprecate": "^1.0.2" + "baseline-browser-mapping": "^2.9.0", + "caniuse-lite": "^1.0.30001759", + "electron-to-chromium": "^1.5.263", + "node-releases": "^2.0.27", + "update-browserslist-db": "^1.2.0" }, - "engines": { - "node": ">=4" - } - }, - "node_modules/postcss-clamp": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/postcss-clamp/-/postcss-clamp-4.1.0.tgz", - "integrity": "sha512-ry4b1Llo/9zz+PKC+030KUnPITTJAHeOwjfAyyB60eT0AorGLdzp52s31OsPRHRf8NchkgFoG2y6fCfn1IV1Ow==", - "dev": true, - "license": "MIT", - "dependencies": { - "postcss-value-parser": "^4.2.0" + "bin": { + "browserslist": "cli.js" }, "engines": { - "node": ">=7.6.0" - }, - "peerDependencies": { - "postcss": "^8.4.6" + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" } }, - "node_modules/postcss-color-functional-notation": { - "version": "7.0.12", - "resolved": "https://registry.npmjs.org/postcss-color-functional-notation/-/postcss-color-functional-notation-7.0.12.tgz", - "integrity": "sha512-TLCW9fN5kvO/u38/uesdpbx3e8AkTYhMvDZYa9JpmImWuTE99bDQ7GU7hdOADIZsiI9/zuxfAJxny/khknp1Zw==", + "node_modules/caniuse-lite": { + "version": "1.0.30001761", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001761.tgz", + "integrity": "sha512-JF9ptu1vP2coz98+5051jZ4PwQgd2ni8A+gYSN7EA7dPKIMf0pDlSUxhdmVOaV3/fYK5uWBkgSXJaRLr4+3A6g==", "dev": true, "funding": [ { - "type": "github", - "url": "https://github.com/sponsors/csstools" + "type": "opencollective", + "url": "https://opencollective.com/browserslist" }, { - "type": "opencollective", - "url": "https://opencollective.com/csstools" + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" } ], - "license": "MIT-0", + "license": "CC-BY-4.0" + }, + "node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "license": "MIT", "dependencies": { - "@csstools/css-color-parser": "^3.1.0", - "@csstools/css-parser-algorithms": "^3.0.5", - "@csstools/css-tokenizer": "^3.0.4", - "@csstools/postcss-progressive-custom-properties": "^4.2.1", - "@csstools/utilities": "^2.0.0" + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" }, "engines": { - "node": ">=18" + "node": ">=10" }, - "peerDependencies": { - "postcss": "^8.4" + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" } }, - "node_modules/postcss-color-hex-alpha": { - "version": "10.0.0", - "resolved": "https://registry.npmjs.org/postcss-color-hex-alpha/-/postcss-color-hex-alpha-10.0.0.tgz", - "integrity": "sha512-1kervM2cnlgPs2a8Vt/Qbe5cQ++N7rkYo/2rz2BkqJZIHQwaVuJgQH38REHrAi4uM0b1fqxMkWYmese94iMp3w==", + "node_modules/chalk/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], "license": "MIT", "dependencies": { - "@csstools/utilities": "^2.0.0", - "postcss-value-parser": "^4.2.0" + "has-flag": "^4.0.0" }, "engines": { - "node": ">=18" - }, - "peerDependencies": { - "postcss": "^8.4" + "node": ">=8" } }, - "node_modules/postcss-color-rebeccapurple": { - "version": "10.0.0", - "resolved": "https://registry.npmjs.org/postcss-color-rebeccapurple/-/postcss-color-rebeccapurple-10.0.0.tgz", - "integrity": "sha512-JFta737jSP+hdAIEhk1Vs0q0YF5P8fFcj+09pweS8ktuGuZ8pPlykHsk6mPxZ8awDl4TrcxUqJo9l1IhVr/OjQ==", + "node_modules/cliui": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", + "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "license": "MIT-0", + "license": "ISC", "dependencies": { - "@csstools/utilities": "^2.0.0", - "postcss-value-parser": "^4.2.0" + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" }, "engines": { - "node": ">=18" - }, - "peerDependencies": { - "postcss": "^8.4" + "node": ">=12" } }, - "node_modules/postcss-custom-media": { - "version": "11.0.6", - "resolved": "https://registry.npmjs.org/postcss-custom-media/-/postcss-custom-media-11.0.6.tgz", - "integrity": "sha512-C4lD4b7mUIw+RZhtY7qUbf4eADmb7Ey8BFA2px9jUbwg7pjTZDl4KY4bvlUV+/vXQvzQRfiGEVJyAbtOsCMInw==", + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], "license": "MIT", "dependencies": { - "@csstools/cascade-layer-name-parser": "^2.0.5", - "@csstools/css-parser-algorithms": "^3.0.5", - "@csstools/css-tokenizer": "^3.0.4", - "@csstools/media-query-list-parser": "^4.0.3" + "color-name": "~1.1.4" }, "engines": { - "node": ">=18" - }, - "peerDependencies": { - "postcss": "^8.4" + "node": ">=7.0.0" } }, - "node_modules/postcss-custom-properties": { - "version": "14.0.6", - "resolved": "https://registry.npmjs.org/postcss-custom-properties/-/postcss-custom-properties-14.0.6.tgz", - "integrity": "sha512-fTYSp3xuk4BUeVhxCSJdIPhDLpJfNakZKoiTDx7yRGCdlZrSJR7mWKVOBS4sBF+5poPQFMj2YdXx1VHItBGihQ==", + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true, + "license": "MIT" + }, + "node_modules/concurrently": { + "version": "9.2.1", + "resolved": "https://registry.npmjs.org/concurrently/-/concurrently-9.2.1.tgz", + "integrity": "sha512-fsfrO0MxV64Znoy8/l1vVIjjHa29SZyyqPgQBwhiDcaW8wJc2W3XWVOGx4M3oJBnv/zdUZIIp1gDeS98GzP8Ng==", "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], "license": "MIT", "dependencies": { - "@csstools/cascade-layer-name-parser": "^2.0.5", - "@csstools/css-parser-algorithms": "^3.0.5", - "@csstools/css-tokenizer": "^3.0.4", - "@csstools/utilities": "^2.0.0", - "postcss-value-parser": "^4.2.0" + "chalk": "4.1.2", + "rxjs": "7.8.2", + "shell-quote": "1.8.3", + "supports-color": "8.1.1", + "tree-kill": "1.2.2", + "yargs": "17.7.2" + }, + "bin": { + "conc": "dist/bin/concurrently.js", + "concurrently": "dist/bin/concurrently.js" }, "engines": { "node": ">=18" }, - "peerDependencies": { - "postcss": "^8.4" + "funding": { + "url": "https://github.com/open-cli-tools/concurrently?sponsor=1" } }, - "node_modules/postcss-custom-selectors": { - "version": "8.0.5", - "resolved": "https://registry.npmjs.org/postcss-custom-selectors/-/postcss-custom-selectors-8.0.5.tgz", - "integrity": "sha512-9PGmckHQswiB2usSO6XMSswO2yFWVoCAuih1yl9FVcwkscLjRKjwsjM3t+NIWpSU2Jx3eOiK2+t4vVTQaoCHHg==", + "node_modules/cssesc": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", + "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==", "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], "license": "MIT", - "dependencies": { - "@csstools/cascade-layer-name-parser": "^2.0.5", - "@csstools/css-parser-algorithms": "^3.0.5", - "@csstools/css-tokenizer": "^3.0.4", - "postcss-selector-parser": "^7.0.0" + "bin": { + "cssesc": "bin/cssesc" }, "engines": { - "node": ">=18" + "node": ">=4" + } + }, + "node_modules/detect-libc": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.0.tgz", + "integrity": "sha512-vEtk+OcP7VBRtQZ1EJ3bdgzSfBjgnEalLTp5zjJrS+2Z1w2KZly4SBdac/WDU3hhsNAZ9E8SC96ME4Ey8MZ7cg==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=8" + } + }, + "node_modules/dotenv": { + "version": "17.2.3", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-17.2.3.tgz", + "integrity": "sha512-JVUnt+DUIzu87TABbhPmNfVdBDt18BLOWjMUFJMSi/Qqg7NTYtabbvSNJGOJ7afbRuv9D/lngizHtP7QyLQ+9w==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=12" }, - "peerDependencies": { - "postcss": "^8.4" + "funding": { + "url": "https://dotenvx.com" } }, - "node_modules/postcss-custom-selectors/node_modules/postcss-selector-parser": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.0.tgz", - "integrity": "sha512-8sLjZwK0R+JlxlYcTuVnyT2v+htpdrjDOKuMcOVdYjt52Lh8hWRYpxBPoKx/Zg+bcjc3wx6fmQevMmUztS/ccA==", + "node_modules/electron-to-chromium": { + "version": "1.5.267", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.267.tgz", + "integrity": "sha512-0Drusm6MVRXSOJpGbaSVgcQsuB4hEkMpHXaVstcPmhu5LIedxs1xNK/nIxmQIU/RPC0+1/o0AVZfBTkTNJOdUw==", + "dev": true, + "license": "ISC" + }, + "node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, + "license": "MIT" + }, + "node_modules/enhanced-resolve": { + "version": "5.18.4", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.18.4.tgz", + "integrity": "sha512-LgQMM4WXU3QI+SYgEc2liRgznaD5ojbmY3sb8LxyguVkIg5FxdpTkvk72te2R38/TGKxH634oLxXRGY6d7AP+Q==", "dev": true, "license": "MIT", "dependencies": { - "cssesc": "^3.0.0", - "util-deprecate": "^1.0.2" + "graceful-fs": "^4.2.4", + "tapable": "^2.2.0" }, "engines": { - "node": ">=4" + "node": ">=10.13.0" } }, - "node_modules/postcss-dir-pseudo-class": { - "version": "9.0.1", - "resolved": "https://registry.npmjs.org/postcss-dir-pseudo-class/-/postcss-dir-pseudo-class-9.0.1.tgz", - "integrity": "sha512-tRBEK0MHYvcMUrAuYMEOa0zg9APqirBcgzi6P21OhxtJyJADo/SWBwY1CAwEohQ/6HDaa9jCjLRG7K3PVQYHEA==", + "node_modules/esbuild": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.2.tgz", + "integrity": "sha512-HyNQImnsOC7X9PMNaCIeAm4ISCQXs5a5YasTXVliKv4uuBo1dKrG0A+uQS8M5eXjVMnLg3WgXaKvprHlFJQffw==", "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "license": "MIT-0", - "dependencies": { - "postcss-selector-parser": "^7.0.0" + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" }, "engines": { "node": ">=18" }, - "peerDependencies": { - "postcss": "^8.4" + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.27.2", + "@esbuild/android-arm": "0.27.2", + "@esbuild/android-arm64": "0.27.2", + "@esbuild/android-x64": "0.27.2", + "@esbuild/darwin-arm64": "0.27.2", + "@esbuild/darwin-x64": "0.27.2", + "@esbuild/freebsd-arm64": "0.27.2", + "@esbuild/freebsd-x64": "0.27.2", + "@esbuild/linux-arm": "0.27.2", + "@esbuild/linux-arm64": "0.27.2", + "@esbuild/linux-ia32": "0.27.2", + "@esbuild/linux-loong64": "0.27.2", + "@esbuild/linux-mips64el": "0.27.2", + "@esbuild/linux-ppc64": "0.27.2", + "@esbuild/linux-riscv64": "0.27.2", + "@esbuild/linux-s390x": "0.27.2", + "@esbuild/linux-x64": "0.27.2", + "@esbuild/netbsd-arm64": "0.27.2", + "@esbuild/netbsd-x64": "0.27.2", + "@esbuild/openbsd-arm64": "0.27.2", + "@esbuild/openbsd-x64": "0.27.2", + "@esbuild/openharmony-arm64": "0.27.2", + "@esbuild/sunos-x64": "0.27.2", + "@esbuild/win32-arm64": "0.27.2", + "@esbuild/win32-ia32": "0.27.2", + "@esbuild/win32-x64": "0.27.2" } }, - "node_modules/postcss-dir-pseudo-class/node_modules/postcss-selector-parser": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.0.tgz", - "integrity": "sha512-8sLjZwK0R+JlxlYcTuVnyT2v+htpdrjDOKuMcOVdYjt52Lh8hWRYpxBPoKx/Zg+bcjc3wx6fmQevMmUztS/ccA==", + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", "dev": true, "license": "MIT", - "dependencies": { - "cssesc": "^3.0.0", - "util-deprecate": "^1.0.2" - }, "engines": { - "node": ">=4" + "node": ">=6" } }, - "node_modules/postcss-double-position-gradients": { - "version": "6.0.4", - "resolved": "https://registry.npmjs.org/postcss-double-position-gradients/-/postcss-double-position-gradients-6.0.4.tgz", - "integrity": "sha512-m6IKmxo7FxSP5nF2l63QbCC3r+bWpFUWmZXZf096WxG0m7Vl1Q1+ruFOhpdDRmKrRS+S3Jtk+TVk/7z0+BVK6g==", + "node_modules/fraction.js": { + "version": "5.3.4", + "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-5.3.4.tgz", + "integrity": "sha512-1X1NTtiJphryn/uLQz3whtY6jK3fTqoE3ohKs0tT+Ujr1W59oopxmoEh7Lu5p6vBaPbgoM0bzveAW4Qi5RyWDQ==", "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "license": "MIT-0", - "dependencies": { - "@csstools/postcss-progressive-custom-properties": "^4.2.1", - "@csstools/utilities": "^2.0.0", - "postcss-value-parser": "^4.2.0" - }, + "license": "MIT", "engines": { - "node": ">=18" + "node": "*" }, - "peerDependencies": { - "postcss": "^8.4" + "funding": { + "type": "github", + "url": "https://github.com/sponsors/rawify" } }, - "node_modules/postcss-focus-visible": { - "version": "10.0.1", - "resolved": "https://registry.npmjs.org/postcss-focus-visible/-/postcss-focus-visible-10.0.1.tgz", - "integrity": "sha512-U58wyjS/I1GZgjRok33aE8juW9qQgQUNwTSdxQGuShHzwuYdcklnvK/+qOWX1Q9kr7ysbraQ6ht6r+udansalA==", + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" ], - "license": "MIT-0", - "dependencies": { - "postcss-selector-parser": "^7.0.0" - }, "engines": { - "node": ">=18" - }, - "peerDependencies": { - "postcss": "^8.4" + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "dev": true, + "license": "ISC", + "engines": { + "node": "6.* || 8.* || >= 10.*" } }, - "node_modules/postcss-focus-visible/node_modules/postcss-selector-parser": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.0.tgz", - "integrity": "sha512-8sLjZwK0R+JlxlYcTuVnyT2v+htpdrjDOKuMcOVdYjt52Lh8hWRYpxBPoKx/Zg+bcjc3wx6fmQevMmUztS/ccA==", + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", "dev": true, "license": "MIT", - "dependencies": { - "cssesc": "^3.0.0", - "util-deprecate": "^1.0.2" - }, "engines": { - "node": ">=4" + "node": ">=8" + } + }, + "node_modules/highlight.js": { + "version": "11.11.1", + "resolved": "https://registry.npmjs.org/highlight.js/-/highlight.js-11.11.1.tgz", + "integrity": "sha512-Xwwo44whKBVCYoliBQwaPvtd/2tYFkRQtXDWj1nackaV2JPXx3L0+Jvd8/qCJ2p+ML0/XVkJ2q+Mr+UVdpJK5w==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=12.0.0" } }, - "node_modules/postcss-focus-within": { - "version": "9.0.1", - "resolved": "https://registry.npmjs.org/postcss-focus-within/-/postcss-focus-within-9.0.1.tgz", - "integrity": "sha512-fzNUyS1yOYa7mOjpci/bR+u+ESvdar6hk8XNK/TRR0fiGTp2QT5N+ducP0n3rfH/m9I7H/EQU6lsa2BrgxkEjw==", + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "license": "MIT-0", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/jiti": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.6.1.tgz", + "integrity": "sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==", + "dev": true, + "license": "MIT", + "bin": { + "jiti": "lib/jiti-cli.mjs" + } + }, + "node_modules/laravel-vite-plugin": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/laravel-vite-plugin/-/laravel-vite-plugin-2.0.1.tgz", + "integrity": "sha512-zQuvzWfUKQu9oNVi1o0RZAJCwhGsdhx4NEOyrVQwJHaWDseGP9tl7XUPLY2T8Cj6+IrZ6lmyxlR1KC8unf3RLA==", + "dev": true, + "license": "MIT", "dependencies": { - "postcss-selector-parser": "^7.0.0" + "picocolors": "^1.0.0", + "vite-plugin-full-reload": "^1.1.0" + }, + "bin": { + "clean-orphaned-assets": "bin/clean.js" }, "engines": { - "node": ">=18" + "node": "^20.19.0 || >=22.12.0" }, "peerDependencies": { - "postcss": "^8.4" + "vite": "^7.0.0" } }, - "node_modules/postcss-focus-within/node_modules/postcss-selector-parser": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.0.tgz", - "integrity": "sha512-8sLjZwK0R+JlxlYcTuVnyT2v+htpdrjDOKuMcOVdYjt52Lh8hWRYpxBPoKx/Zg+bcjc3wx6fmQevMmUztS/ccA==", + "node_modules/lightningcss": { + "version": "1.30.2", + "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.30.2.tgz", + "integrity": "sha512-utfs7Pr5uJyyvDETitgsaqSyjCb2qNRAtuqUeWIAKztsOYdcACf2KtARYXg2pSvhkt+9NfoaNY7fxjl6nuMjIQ==", "dev": true, - "license": "MIT", + "license": "MPL-2.0", "dependencies": { - "cssesc": "^3.0.0", - "util-deprecate": "^1.0.2" + "detect-libc": "^2.0.3" }, "engines": { - "node": ">=4" + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + }, + "optionalDependencies": { + "lightningcss-android-arm64": "1.30.2", + "lightningcss-darwin-arm64": "1.30.2", + "lightningcss-darwin-x64": "1.30.2", + "lightningcss-freebsd-x64": "1.30.2", + "lightningcss-linux-arm-gnueabihf": "1.30.2", + "lightningcss-linux-arm64-gnu": "1.30.2", + "lightningcss-linux-arm64-musl": "1.30.2", + "lightningcss-linux-x64-gnu": "1.30.2", + "lightningcss-linux-x64-musl": "1.30.2", + "lightningcss-win32-arm64-msvc": "1.30.2", + "lightningcss-win32-x64-msvc": "1.30.2" } }, - "node_modules/postcss-font-variant": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/postcss-font-variant/-/postcss-font-variant-5.0.0.tgz", - "integrity": "sha512-1fmkBaCALD72CK2a9i468mA/+tr9/1cBxRRMXOUaZqO43oWPR5imcyPjXwuv7PXbCid4ndlP5zWhidQVVa3hmA==", + "node_modules/lightningcss-android-arm64": { + "version": "1.30.2", + "resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.30.2.tgz", + "integrity": "sha512-BH9sEdOCahSgmkVhBLeU7Hc9DWeZ1Eb6wNS6Da8igvUwAe0sqROHddIlvU06q3WyXVEOYDZ6ykBZQnjTbmo4+A==", + "cpu": [ + "arm64" + ], "dev": true, - "license": "MIT", - "peerDependencies": { - "postcss": "^8.1.0" + "license": "MPL-2.0", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" } }, - "node_modules/postcss-gap-properties": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/postcss-gap-properties/-/postcss-gap-properties-6.0.0.tgz", - "integrity": "sha512-Om0WPjEwiM9Ru+VhfEDPZJAKWUd0mV1HmNXqp2C29z80aQ2uP9UVhLc7e3aYMIor/S5cVhoPgYQ7RtfeZpYTRw==", + "node_modules/lightningcss-darwin-arm64": { + "version": "1.30.2", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.30.2.tgz", + "integrity": "sha512-ylTcDJBN3Hp21TdhRT5zBOIi73P6/W0qwvlFEk22fkdXchtNTOU4Qc37SkzV+EKYxLouZ6M4LG9NfZ1qkhhBWA==", + "cpu": [ + "arm64" + ], "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" ], - "license": "MIT-0", "engines": { - "node": ">=18" + "node": ">= 12.0.0" }, - "peerDependencies": { - "postcss": "^8.4" + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" } }, - "node_modules/postcss-image-set-function": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/postcss-image-set-function/-/postcss-image-set-function-7.0.0.tgz", - "integrity": "sha512-QL7W7QNlZuzOwBTeXEmbVckNt1FSmhQtbMRvGGqqU4Nf4xk6KUEQhAoWuMzwbSv5jxiRiSZ5Tv7eiDB9U87znA==", + "node_modules/lightningcss-darwin-x64": { + "version": "1.30.2", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.30.2.tgz", + "integrity": "sha512-oBZgKchomuDYxr7ilwLcyms6BCyLn0z8J0+ZZmfpjwg9fRVZIR5/GMXd7r9RH94iDhld3UmSjBM6nXWM2TfZTQ==", + "cpu": [ + "x64" + ], "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" ], - "license": "MIT-0", - "dependencies": { - "@csstools/utilities": "^2.0.0", - "postcss-value-parser": "^4.2.0" - }, "engines": { - "node": ">=18" + "node": ">= 12.0.0" }, - "peerDependencies": { - "postcss": "^8.4" + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" } }, - "node_modules/postcss-lab-function": { - "version": "7.0.12", - "resolved": "https://registry.npmjs.org/postcss-lab-function/-/postcss-lab-function-7.0.12.tgz", - "integrity": "sha512-tUcyRk1ZTPec3OuKFsqtRzW2Go5lehW29XA21lZ65XmzQkz43VY2tyWEC202F7W3mILOjw0voOiuxRGTsN+J9w==", + "node_modules/lightningcss-freebsd-x64": { + "version": "1.30.2", + "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.30.2.tgz", + "integrity": "sha512-c2bH6xTrf4BDpK8MoGG4Bd6zAMZDAXS569UxCAGcA7IKbHNMlhGQ89eRmvpIUGfKWNVdbhSbkQaWhEoMGmGslA==", + "cpu": [ + "x64" + ], "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } + "license": "MPL-2.0", + "optional": true, + "os": [ + "freebsd" ], - "license": "MIT-0", - "dependencies": { - "@csstools/css-color-parser": "^3.1.0", - "@csstools/css-parser-algorithms": "^3.0.5", - "@csstools/css-tokenizer": "^3.0.4", - "@csstools/postcss-progressive-custom-properties": "^4.2.1", - "@csstools/utilities": "^2.0.0" - }, "engines": { - "node": ">=18" + "node": ">= 12.0.0" }, - "peerDependencies": { - "postcss": "^8.4" + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" } }, - "node_modules/postcss-loader": { - "version": "8.2.0", - "resolved": "https://registry.npmjs.org/postcss-loader/-/postcss-loader-8.2.0.tgz", - "integrity": "sha512-tHX+RkpsXVcc7st4dSdDGliI+r4aAQDuv+v3vFYHixb6YgjreG5AG4SEB0kDK8u2s6htqEEpKlkhSBUTvWKYnA==", + "node_modules/lightningcss-linux-arm-gnueabihf": { + "version": "1.30.2", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.30.2.tgz", + "integrity": "sha512-eVdpxh4wYcm0PofJIZVuYuLiqBIakQ9uFZmipf6LF/HRj5Bgm0eb3qL/mr1smyXIS1twwOxNWndd8z0E374hiA==", + "cpu": [ + "arm" + ], "dev": true, - "license": "MIT", - "dependencies": { - "cosmiconfig": "^9.0.0", - "jiti": "^2.5.1", - "semver": "^7.6.2" - }, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">= 18.12.0" + "node": ">= 12.0.0" }, "funding": { "type": "opencollective", - "url": "https://opencollective.com/webpack" - }, - "peerDependencies": { - "@rspack/core": "0.x || 1.x", - "postcss": "^7.0.0 || ^8.0.1", - "webpack": "^5.0.0" - }, - "peerDependenciesMeta": { - "@rspack/core": { - "optional": true - }, - "webpack": { - "optional": true - } + "url": "https://opencollective.com/parcel" } }, - "node_modules/postcss-logical": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/postcss-logical/-/postcss-logical-8.1.0.tgz", - "integrity": "sha512-pL1hXFQ2fEXNKiNiAgtfA005T9FBxky5zkX6s4GZM2D8RkVgRqz3f4g1JUoq925zXv495qk8UNldDwh8uGEDoA==", + "node_modules/lightningcss-linux-arm64-gnu": { + "version": "1.30.2", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.30.2.tgz", + "integrity": "sha512-UK65WJAbwIJbiBFXpxrbTNArtfuznvxAJw4Q2ZGlU8kPeDIWEX1dg3rn2veBVUylA2Ezg89ktszWbaQnxD/e3A==", + "cpu": [ + "arm64" + ], "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" ], - "license": "MIT-0", - "dependencies": { - "postcss-value-parser": "^4.2.0" - }, "engines": { - "node": ">=18" + "node": ">= 12.0.0" }, - "peerDependencies": { - "postcss": "^8.4" + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" } }, - "node_modules/postcss-nesting": { - "version": "13.0.2", - "resolved": "https://registry.npmjs.org/postcss-nesting/-/postcss-nesting-13.0.2.tgz", - "integrity": "sha512-1YCI290TX+VP0U/K/aFxzHzQWHWURL+CtHMSbex1lCdpXD1SoR2sYuxDu5aNI9lPoXpKTCggFZiDJbwylU0LEQ==", + "node_modules/lightningcss-linux-arm64-musl": { + "version": "1.30.2", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.30.2.tgz", + "integrity": "sha512-5Vh9dGeblpTxWHpOx8iauV02popZDsCYMPIgiuw97OJ5uaDsL86cnqSFs5LZkG3ghHoX5isLgWzMs+eD1YzrnA==", + "cpu": [ + "arm64" + ], "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" ], - "license": "MIT-0", - "dependencies": { - "@csstools/selector-resolve-nested": "^3.1.0", - "@csstools/selector-specificity": "^5.0.0", - "postcss-selector-parser": "^7.0.0" - }, "engines": { - "node": ">=18" + "node": ">= 12.0.0" }, - "peerDependencies": { - "postcss": "^8.4" + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" } }, - "node_modules/postcss-nesting/node_modules/@csstools/selector-resolve-nested": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/@csstools/selector-resolve-nested/-/selector-resolve-nested-3.1.0.tgz", - "integrity": "sha512-mf1LEW0tJLKfWyvn5KdDrhpxHyuxpbNwTIwOYLIvsTffeyOf85j5oIzfG0yosxDgx/sswlqBnESYUcQH0vgZ0g==", + "node_modules/lightningcss-linux-x64-gnu": { + "version": "1.30.2", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.30.2.tgz", + "integrity": "sha512-Cfd46gdmj1vQ+lR6VRTTadNHu6ALuw2pKR9lYq4FnhvgBc4zWY1EtZcAc6EffShbb1MFrIPfLDXD6Xprbnni4w==", + "cpu": [ + "x64" + ], "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" ], - "license": "MIT-0", "engines": { - "node": ">=18" + "node": ">= 12.0.0" }, - "peerDependencies": { - "postcss-selector-parser": "^7.0.0" + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" } }, - "node_modules/postcss-nesting/node_modules/@csstools/selector-specificity": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/@csstools/selector-specificity/-/selector-specificity-5.0.0.tgz", - "integrity": "sha512-PCqQV3c4CoVm3kdPhyeZ07VmBRdH2EpMFA/pd9OASpOEC3aXNGoqPDAZ80D0cLpMBxnmk0+yNhGsEx31hq7Gtw==", + "node_modules/lightningcss-linux-x64-musl": { + "version": "1.30.2", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.30.2.tgz", + "integrity": "sha512-XJaLUUFXb6/QG2lGIW6aIk6jKdtjtcffUT0NKvIqhSBY3hh9Ch+1LCeH80dR9q9LBjG3ewbDjnumefsLsP6aiA==", + "cpu": [ + "x64" + ], "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" ], - "license": "MIT-0", "engines": { - "node": ">=18" + "node": ">= 12.0.0" }, - "peerDependencies": { - "postcss-selector-parser": "^7.0.0" + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" } }, - "node_modules/postcss-nesting/node_modules/postcss-selector-parser": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.0.tgz", - "integrity": "sha512-8sLjZwK0R+JlxlYcTuVnyT2v+htpdrjDOKuMcOVdYjt52Lh8hWRYpxBPoKx/Zg+bcjc3wx6fmQevMmUztS/ccA==", + "node_modules/lightningcss-win32-arm64-msvc": { + "version": "1.30.2", + "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.30.2.tgz", + "integrity": "sha512-FZn+vaj7zLv//D/192WFFVA0RgHawIcHqLX9xuWiQt7P0PtdFEVaxgF9rjM/IRYHQXNnk61/H/gb2Ei+kUQ4xQ==", + "cpu": [ + "arm64" + ], "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "cssesc": "^3.0.0", - "util-deprecate": "^1.0.2" - }, + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], "engines": { - "node": ">=4" + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" } }, - "node_modules/postcss-opacity-percentage": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/postcss-opacity-percentage/-/postcss-opacity-percentage-3.0.0.tgz", - "integrity": "sha512-K6HGVzyxUxd/VgZdX04DCtdwWJ4NGLG212US4/LA1TLAbHgmAsTWVR86o+gGIbFtnTkfOpb9sCRBx8K7HO66qQ==", + "node_modules/lightningcss-win32-x64-msvc": { + "version": "1.30.2", + "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.30.2.tgz", + "integrity": "sha512-5g1yc73p+iAkid5phb4oVFMB45417DkRevRbt/El/gKXJk4jid+vPFF/AXbxn05Aky8PapwzZrdJShv5C0avjw==", + "cpu": [ + "x64" + ], "dev": true, - "funding": [ - { - "type": "kofi", - "url": "https://ko-fi.com/mrcgrtz" - }, - { - "type": "liberapay", - "url": "https://liberapay.com/mrcgrtz" - } + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" ], - "license": "MIT", "engines": { - "node": ">=18" + "node": ">= 12.0.0" }, - "peerDependencies": { - "postcss": "^8.4" + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" } }, - "node_modules/postcss-overflow-shorthand": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/postcss-overflow-shorthand/-/postcss-overflow-shorthand-6.0.0.tgz", - "integrity": "sha512-BdDl/AbVkDjoTofzDQnwDdm/Ym6oS9KgmO7Gr+LHYjNWJ6ExORe4+3pcLQsLA9gIROMkiGVjjwZNoL/mpXHd5Q==", + "node_modules/lodash": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "license": "MIT-0", + "license": "MIT" + }, + "node_modules/magic-string": { + "version": "0.30.21", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", + "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", + "dev": true, + "license": "MIT", "dependencies": { - "postcss-value-parser": "^4.2.0" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "postcss": "^8.4" + "@jridgewell/sourcemap-codec": "^1.5.5" } }, - "node_modules/postcss-page-break": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/postcss-page-break/-/postcss-page-break-3.0.4.tgz", - "integrity": "sha512-1JGu8oCjVXLa9q9rFTo4MbeeA5FMe00/9C7lN4va606Rdb+HkxXtXsmEDrIraQ11fGz/WvKWa8gMuCKkrXpTsQ==", + "node_modules/mini-svg-data-uri": { + "version": "1.4.4", + "resolved": "https://registry.npmjs.org/mini-svg-data-uri/-/mini-svg-data-uri-1.4.4.tgz", + "integrity": "sha512-r9deDe9p5FJUPZAk3A59wGH7Ii9YrjjWw0jmw/liSbHl2CHiyXj6FcDXDu2K3TjVAXqiJdaw3xxwlZZr9E6nHg==", "dev": true, "license": "MIT", - "peerDependencies": { - "postcss": "^8" + "bin": { + "mini-svg-data-uri": "cli.js" } }, - "node_modules/postcss-place": { - "version": "10.0.0", - "resolved": "https://registry.npmjs.org/postcss-place/-/postcss-place-10.0.0.tgz", - "integrity": "sha512-5EBrMzat2pPAxQNWYavwAfoKfYcTADJ8AXGVPcUZ2UkNloUTWzJQExgrzrDkh3EKzmAx1evfTAzF9I8NGcc+qw==", + "node_modules/nanoid": { + "version": "3.3.11", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", + "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", "dev": true, "funding": [ { "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" + "url": "https://github.com/sponsors/ai" } ], - "license": "MIT-0", - "dependencies": { - "postcss-value-parser": "^4.2.0" + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" }, "engines": { - "node": ">=18" - }, - "peerDependencies": { - "postcss": "^8.4" + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" } }, - "node_modules/postcss-preset-env": { - "version": "10.4.0", - "resolved": "https://registry.npmjs.org/postcss-preset-env/-/postcss-preset-env-10.4.0.tgz", - "integrity": "sha512-2kqpOthQ6JhxqQq1FSAAZGe9COQv75Aw8WbsOvQVNJ2nSevc9Yx/IKZGuZ7XJ+iOTtVon7LfO7ELRzg8AZ+sdw==", + "node_modules/node-releases": { + "version": "2.0.27", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.27.tgz", + "integrity": "sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA==", "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "license": "MIT-0", - "dependencies": { - "@csstools/postcss-alpha-function": "^1.0.1", - "@csstools/postcss-cascade-layers": "^5.0.2", - "@csstools/postcss-color-function": "^4.0.12", - "@csstools/postcss-color-function-display-p3-linear": "^1.0.1", - "@csstools/postcss-color-mix-function": "^3.0.12", - "@csstools/postcss-color-mix-variadic-function-arguments": "^1.0.2", - "@csstools/postcss-content-alt-text": "^2.0.8", - "@csstools/postcss-contrast-color-function": "^2.0.12", - "@csstools/postcss-exponential-functions": "^2.0.9", - "@csstools/postcss-font-format-keywords": "^4.0.0", - "@csstools/postcss-gamut-mapping": "^2.0.11", - "@csstools/postcss-gradients-interpolation-method": "^5.0.12", - "@csstools/postcss-hwb-function": "^4.0.12", - "@csstools/postcss-ic-unit": "^4.0.4", - "@csstools/postcss-initial": "^2.0.1", - "@csstools/postcss-is-pseudo-class": "^5.0.3", - "@csstools/postcss-light-dark-function": "^2.0.11", - "@csstools/postcss-logical-float-and-clear": "^3.0.0", - "@csstools/postcss-logical-overflow": "^2.0.0", - "@csstools/postcss-logical-overscroll-behavior": "^2.0.0", - "@csstools/postcss-logical-resize": "^3.0.0", - "@csstools/postcss-logical-viewport-units": "^3.0.4", - "@csstools/postcss-media-minmax": "^2.0.9", - "@csstools/postcss-media-queries-aspect-ratio-number-values": "^3.0.5", - "@csstools/postcss-nested-calc": "^4.0.0", - "@csstools/postcss-normalize-display-values": "^4.0.0", - "@csstools/postcss-oklab-function": "^4.0.12", - "@csstools/postcss-progressive-custom-properties": "^4.2.1", - "@csstools/postcss-random-function": "^2.0.1", - "@csstools/postcss-relative-color-syntax": "^3.0.12", - "@csstools/postcss-scope-pseudo-class": "^4.0.1", - "@csstools/postcss-sign-functions": "^1.1.4", - "@csstools/postcss-stepped-value-functions": "^4.0.9", - "@csstools/postcss-text-decoration-shorthand": "^4.0.3", - "@csstools/postcss-trigonometric-functions": "^4.0.9", - "@csstools/postcss-unset-value": "^4.0.0", - "autoprefixer": "^10.4.21", - "browserslist": "^4.26.0", - "css-blank-pseudo": "^7.0.1", - "css-has-pseudo": "^7.0.3", - "css-prefers-color-scheme": "^10.0.0", - "cssdb": "^8.4.2", - "postcss-attribute-case-insensitive": "^7.0.1", - "postcss-clamp": "^4.1.0", - "postcss-color-functional-notation": "^7.0.12", - "postcss-color-hex-alpha": "^10.0.0", - "postcss-color-rebeccapurple": "^10.0.0", - "postcss-custom-media": "^11.0.6", - "postcss-custom-properties": "^14.0.6", - "postcss-custom-selectors": "^8.0.5", - "postcss-dir-pseudo-class": "^9.0.1", - "postcss-double-position-gradients": "^6.0.4", - "postcss-focus-visible": "^10.0.1", - "postcss-focus-within": "^9.0.1", - "postcss-font-variant": "^5.0.0", - "postcss-gap-properties": "^6.0.0", - "postcss-image-set-function": "^7.0.0", - "postcss-lab-function": "^7.0.12", - "postcss-logical": "^8.1.0", - "postcss-nesting": "^13.0.2", - "postcss-opacity-percentage": "^3.0.0", - "postcss-overflow-shorthand": "^6.0.0", - "postcss-page-break": "^3.0.4", - "postcss-place": "^10.0.0", - "postcss-pseudo-class-any-link": "^10.0.1", - "postcss-replace-overflow-wrap": "^4.0.0", - "postcss-selector-not": "^8.0.1" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "postcss": "^8.4" - } + "license": "MIT" }, - "node_modules/postcss-pseudo-class-any-link": { - "version": "10.0.1", - "resolved": "https://registry.npmjs.org/postcss-pseudo-class-any-link/-/postcss-pseudo-class-any-link-10.0.1.tgz", - "integrity": "sha512-3el9rXlBOqTFaMFkWDOkHUTQekFIYnaQY55Rsp8As8QQkpiSgIYEcF/6Ond93oHiDsGb4kad8zjt+NPlOC1H0Q==", + "node_modules/npm-check-updates": { + "version": "19.2.0", + "resolved": "https://registry.npmjs.org/npm-check-updates/-/npm-check-updates-19.2.0.tgz", + "integrity": "sha512-XSIuL0FNgzXPDZa4lje7+OwHjiyEt84qQm6QMsQRbixNY5EHEM9nhgOjxjlK9jIbN+ysvSqOV8DKNS0zydwbdg==", "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "license": "MIT-0", - "dependencies": { - "postcss-selector-parser": "^7.0.0" + "license": "Apache-2.0", + "bin": { + "ncu": "build/cli.js", + "npm-check-updates": "build/cli.js" }, "engines": { - "node": ">=18" - }, - "peerDependencies": { - "postcss": "^8.4" + "node": ">=20.0.0", + "npm": ">=8.12.1" } }, - "node_modules/postcss-pseudo-class-any-link/node_modules/postcss-selector-parser": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.0.tgz", - "integrity": "sha512-8sLjZwK0R+JlxlYcTuVnyT2v+htpdrjDOKuMcOVdYjt52Lh8hWRYpxBPoKx/Zg+bcjc3wx6fmQevMmUztS/ccA==", + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", "dev": true, - "license": "MIT", - "dependencies": { - "cssesc": "^3.0.0", - "util-deprecate": "^1.0.2" - }, - "engines": { - "node": ">=4" - } + "license": "ISC" }, - "node_modules/postcss-replace-overflow-wrap": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/postcss-replace-overflow-wrap/-/postcss-replace-overflow-wrap-4.0.0.tgz", - "integrity": "sha512-KmF7SBPphT4gPPcKZc7aDkweHiKEEO8cla/GjcBK+ckKxiZslIu3C4GCRW3DNfL0o7yW7kMQu9xlZ1kXRXLXtw==", + "node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", "dev": true, "license": "MIT", - "peerDependencies": { - "postcss": "^8.0.3" + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" } }, - "node_modules/postcss-selector-not": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/postcss-selector-not/-/postcss-selector-not-8.0.1.tgz", - "integrity": "sha512-kmVy/5PYVb2UOhy0+LqUYAhKj7DUGDpSWa5LZqlkWJaaAV+dxxsOG3+St0yNLu6vsKD7Dmqx+nWQt0iil89+WA==", + "node_modules/postcss": { + "version": "8.5.6", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz", + "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==", "dev": true, "funding": [ { - "type": "github", - "url": "https://github.com/sponsors/csstools" + "type": "opencollective", + "url": "https://opencollective.com/postcss/" }, { - "type": "opencollective", - "url": "https://opencollective.com/csstools" + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" } ], "license": "MIT", "dependencies": { - "postcss-selector-parser": "^7.0.0" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "postcss": "^8.4" - } - }, - "node_modules/postcss-selector-not/node_modules/postcss-selector-parser": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.0.tgz", - "integrity": "sha512-8sLjZwK0R+JlxlYcTuVnyT2v+htpdrjDOKuMcOVdYjt52Lh8hWRYpxBPoKx/Zg+bcjc3wx6fmQevMmUztS/ccA==", - "dev": true, - "license": "MIT", - "dependencies": { - "cssesc": "^3.0.0", - "util-deprecate": "^1.0.2" + "nanoid": "^3.3.11", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" }, "engines": { - "node": ">=4" + "node": "^10 || ^12 || >=14" } }, "node_modules/postcss-selector-parser": { @@ -4442,12 +2062,11 @@ "license": "MIT" }, "node_modules/prettier": { - "version": "3.7.2", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.7.2.tgz", - "integrity": "sha512-n3HV2J6QhItCXndGa3oMWvWFAgN1ibnS7R9mt6iokScBOC0Ul9/iZORmU2IWUMcyAQaMPjTlY3uT34TqocUxMA==", + "version": "3.7.4", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.7.4.tgz", + "integrity": "sha512-v6UNi1+3hSlVvv8fSaoUbggEM5VErKmmpGA7Pl3HF8V6uKY7rvClBOJlH6yNwQtfTueNkGVpOv/mtWL9L4bgRA==", "dev": true, "license": "MIT", - "peer": true, "bin": { "prettier": "bin/prettier.cjs" }, @@ -4458,10 +2077,27 @@ "url": "https://github.com/prettier/prettier?sponsor=1" } }, + "node_modules/prettier-plugin-organize-imports": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/prettier-plugin-organize-imports/-/prettier-plugin-organize-imports-4.3.0.tgz", + "integrity": "sha512-FxFz0qFhyBsGdIsb697f/EkvHzi5SZOhWAjxcx2dLt+Q532bAlhswcXGYB1yzjZ69kW8UoadFBw7TyNwlq96Iw==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "prettier": ">=2.0", + "typescript": ">=2.9", + "vue-tsc": "^2.1.0 || 3" + }, + "peerDependenciesMeta": { + "vue-tsc": { + "optional": true + } + } + }, "node_modules/prettier-plugin-tailwindcss": { - "version": "0.7.1", - "resolved": "https://registry.npmjs.org/prettier-plugin-tailwindcss/-/prettier-plugin-tailwindcss-0.7.1.tgz", - "integrity": "sha512-Bzv1LZcuiR1Sk02iJTS1QzlFNp/o5l2p3xkopwOrbPmtMeh3fK9rVW5M3neBQzHq+kGKj/4LGQMTNcTH4NGPtQ==", + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/prettier-plugin-tailwindcss/-/prettier-plugin-tailwindcss-0.7.2.tgz", + "integrity": "sha512-LkphyK3Fw+q2HdMOoiEHWf93fNtYJwfamoKPl7UwtjFQdei/iIBoX11G6j706FzN3ymX9mPVi97qIY8328vdnA==", "dev": true, "license": "MIT", "engines": { @@ -4537,14 +2173,14 @@ } } }, - "node_modules/resolve-from": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", - "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "node_modules/require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", "dev": true, "license": "MIT", "engines": { - "node": ">=4" + "node": ">=0.10.0" } }, "node_modules/rollup": { @@ -4588,17 +2224,27 @@ "fsevents": "~2.3.2" } }, - "node_modules/semver": { - "version": "7.7.2", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", - "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", + "node_modules/rxjs": { + "version": "7.8.2", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.2.tgz", + "integrity": "sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA==", "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - }, + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.1.0" + } + }, + "node_modules/shell-quote": { + "version": "1.8.3", + "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.8.3.tgz", + "integrity": "sha512-ObmnIF4hXNg1BqhnHmgbDETF8dLPCggZWBjkQfhZpbszZnYur5DUljTcCHii5LC3J5E0yeO/1LIMyH+UvHQgyw==", + "dev": true, + "license": "MIT", "engines": { - "node": ">=10" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, "node_modules/source-map-js": { @@ -4611,18 +2257,61 @@ "node": ">=0.10.0" } }, - "node_modules/tailwindcss": { - "version": "4.1.17", - "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.1.17.tgz", - "integrity": "sha512-j9Ee2YjuQqYT9bbRTfTZht9W/ytp5H+jJpZKiYdP/bpnXARAuELt9ofP0lPnmHjbga7SNQIxdTAXCmtKVYjN+Q==", + "node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", "dev": true, "license": "MIT", - "peer": true + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, + "node_modules/tailwindcss": { + "version": "4.1.18", + "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.1.18.tgz", + "integrity": "sha512-4+Z+0yiYyEtUVCScyfHCxOYP06L5Ne+JiHhY2IjR2KWMIWhJOYZKLSGZaP5HkZ8+bY0cxfzwDE5uOmzFXyIwxw==", + "dev": true, + "license": "MIT" }, "node_modules/tapable": { - "version": "2.2.3", - "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.3.tgz", - "integrity": "sha512-ZL6DDuAlRlLGghwcfmSn9sK3Hr6ArtyudlSAiCqQ6IfE+b+HHbydbYDIG15IfS5do+7XQQBdBiubF/cV2dnDzg==", + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.3.0.tgz", + "integrity": "sha512-g9ljZiwki/LfxmQADO3dEY1CbpmXT5Hm2fJ+QaGKwSXUylMybePR7/67YW7jOrrvjEgL1Fmz5kzyAjWVWLlucg==", "dev": true, "license": "MIT", "engines": { @@ -4674,7 +2363,6 @@ "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=12" }, @@ -4692,10 +2380,42 @@ "@popperjs/core": "^2.9.0" } }, + "node_modules/tree-kill": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/tree-kill/-/tree-kill-1.2.2.tgz", + "integrity": "sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==", + "dev": true, + "license": "MIT", + "bin": { + "tree-kill": "cli.js" + } + }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "dev": true, + "license": "0BSD" + }, + "node_modules/typescript": { + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "dev": true, + "license": "Apache-2.0", + "peer": true, + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, "node_modules/update-browserslist-db": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.4.tgz", - "integrity": "sha512-q0SPT4xyU84saUX+tomz1WLkxUbuaJnR1xWt17M7fJtEJigJeWUNGUqrauFXsHnqev9y9JTRGwk13tFBuKby4A==", + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz", + "integrity": "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==", "dev": true, "funding": [ { @@ -4731,14 +2451,13 @@ "license": "MIT" }, "node_modules/vite": { - "version": "7.2.4", - "resolved": "https://registry.npmjs.org/vite/-/vite-7.2.4.tgz", - "integrity": "sha512-NL8jTlbo0Tn4dUEXEsUg8KeyG/Lkmc4Fnzb8JXN/Ykm9G4HNImjtABMJgkQoVjOBN/j2WAwDTRytdqJbZsah7w==", + "version": "7.3.0", + "resolved": "https://registry.npmjs.org/vite/-/vite-7.3.0.tgz", + "integrity": "sha512-dZwN5L1VlUBewiP6H9s2+B3e3Jg96D0vzN+Ry73sOefebhYr9f94wwkMNN/9ouoU8pV1BqA1d1zGk8928cx0rg==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { - "esbuild": "^0.25.0", + "esbuild": "^0.27.0", "fdir": "^6.5.0", "picomatch": "^4.0.3", "postcss": "^8.5.6", @@ -4841,13 +2560,69 @@ "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=12" }, "funding": { "url": "https://github.com/sponsors/jonschlinkert" } + }, + "node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=10" + } + }, + "node_modules/yargs": { + "version": "17.7.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", + "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "cliui": "^8.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.1.1" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/yargs-parser": { + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=12" + } } } } diff --git a/package.json b/package.json index 3a72eea3..f91c9361 100644 --- a/package.json +++ b/package.json @@ -4,30 +4,32 @@ "scripts": { "dev": "vite", "build": "vite build", - "prettier": "npx prettier --write ./resources" + "lint": "prettier --write resources/", + "test:lint": "prettier --check resources/" }, "devDependencies": { - "@alpinejs/collapse": "^3.14.3", - "@alpinejs/intersect": "^3.6.1", - "@awcodes/alpine-floating-ui": "^3.5.0", + "@alpinejs/collapse": "^3.15.3", + "@alpinejs/intersect": "^3.15.3", + "@awcodes/alpine-floating-ui": "^3.6.4", "@ryangjchandler/alpine-tooltip": "^2.0.1", - "@tailwindcss/forms": "^0.5.10", - "@tailwindcss/typography": "^0.5.18", - "@tailwindcss/vite": "^4.1.13", - "@tailwindcss/postcss": "^4.1.13", - "@tailwindplus/elements": "^1.0.13", - "alpinejs": "^3.12.0", - "autoprefixer": "^10.4.16", - "highlight.js": "^11.7.0", + "@tailwindcss/forms": "^0.5.11", + "@tailwindcss/typography": "^0.5.19", + "@tailwindcss/vite": "^4.1.18", + "@tailwindcss/postcss": "^4.1.18", + "@tailwindplus/elements": "^1.0.21", + "alpinejs": "^3.15.3", + "autoprefixer": "^10.4.23", + "concurrently": "^9.2.1", + "dotenv": "^17.2.3", + "highlight.js": "^11.11.1", "laravel-vite-plugin": "^2.0.1", - "lodash": "^4.17.19", - "postcss": "^8.4.32", - "postcss-loader": "^8.1.1", - "postcss-preset-env": "^10.1.0", - "prettier": "^3.2.5", - "prettier-plugin-tailwindcss": "^0.7.1", - "tailwindcss": "^4.1.13", + "lodash": "^4.17.21", + "npm-check-updates": "^19.2.0", + "prettier": "^3.7.4", + "prettier-plugin-organize-imports": "^4.3.0", + "prettier-plugin-tailwindcss": "^0.7.2", + "tailwindcss": "^4.1.18", "tippy.js": "^6.3.7", - "vite": "^7.1.7" + "vite": "^7.3.0" } } diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon new file mode 100644 index 00000000..ae15009a --- /dev/null +++ b/phpstan-baseline.neon @@ -0,0 +1,1675 @@ +parameters: + ignoreErrors: + - + rawMessage: 'Called ''env'' outside of the config directory which returns null when the config is cached, use ''config''.' + identifier: larastan.noEnvCallsOutsideOfConfig + count: 23 + path: app-modules/database-migration/config/ssh-tunnel.php + + - + rawMessage: Binary operation "." between mixed and '/' results in an error. + identifier: binaryOp.invalid + count: 1 + path: app-modules/database-migration/src/Commands/MigrateFilesToS3Command.php + + - + rawMessage: Cannot access offset 'driver' on mixed. + identifier: offsetAccess.nonOffsetAccessible + count: 1 + path: app-modules/database-migration/src/Commands/MigrateFilesToS3Command.php + + - + rawMessage: Cannot access offset 'root' on mixed. + identifier: offsetAccess.nonOffsetAccessible + count: 1 + path: app-modules/database-migration/src/Commands/MigrateFilesToS3Command.php + + - + rawMessage: Binary operation "+" between mixed and 1 results in an error. + identifier: binaryOp.invalid + count: 1 + path: app-modules/database-migration/src/Commands/ResetPostgresSequencesCommand.php + + - + rawMessage: Cannot access property $column_name on mixed. + identifier: property.nonObject + count: 1 + path: app-modules/database-migration/src/Commands/ResetPostgresSequencesCommand.php + + - + rawMessage: Cannot access property $sequence_name on mixed. + identifier: property.nonObject + count: 1 + path: app-modules/database-migration/src/Commands/ResetPostgresSequencesCommand.php + + - + rawMessage: Cannot access property $table_name on mixed. + identifier: property.nonObject + count: 2 + path: app-modules/database-migration/src/Commands/ResetPostgresSequencesCommand.php + + - + rawMessage: 'Parameter #5 ...$values of function sprintf expects bool|float|int|string|null, mixed given.' + identifier: argument.type + count: 1 + path: app-modules/database-migration/src/Commands/ResetPostgresSequencesCommand.php + + - + rawMessage: Cannot access offset 'bucket' on mixed. + identifier: offsetAccess.nonOffsetAccessible + count: 1 + path: app-modules/database-migration/src/Commands/UpdateStorageUrlsCommand.php + + - + rawMessage: Cannot access offset 'driver' on mixed. + identifier: offsetAccess.nonOffsetAccessible + count: 1 + path: app-modules/database-migration/src/Commands/UpdateStorageUrlsCommand.php + + - + rawMessage: Cannot access offset 'region' on mixed. + identifier: offsetAccess.nonOffsetAccessible + count: 1 + path: app-modules/database-migration/src/Commands/UpdateStorageUrlsCommand.php + + - + rawMessage: Cannot access offset 'root' on mixed. + identifier: offsetAccess.nonOffsetAccessible + count: 1 + path: app-modules/database-migration/src/Commands/UpdateStorageUrlsCommand.php + + - + rawMessage: Cannot access offset 'url' on mixed. + identifier: offsetAccess.nonOffsetAccessible + count: 1 + path: app-modules/database-migration/src/Commands/UpdateStorageUrlsCommand.php + + - + rawMessage: Cannot access property $id on mixed. + identifier: property.nonObject + count: 1 + path: app-modules/database-migration/src/Commands/UpdateStorageUrlsCommand.php + + - + rawMessage: 'Parameter #1 $string of function mb_rtrim expects string, mixed given.' + identifier: argument.type + count: 1 + path: app-modules/database-migration/src/Commands/UpdateStorageUrlsCommand.php + + - + rawMessage: 'Parameter #1 $string of function mb_trim expects string, mixed given.' + identifier: argument.type + count: 1 + path: app-modules/database-migration/src/Commands/UpdateStorageUrlsCommand.php + + - + rawMessage: 'Parameter #2 ...$values of function sprintf expects bool|float|int|string|null, mixed given.' + identifier: argument.type + count: 1 + path: app-modules/database-migration/src/Commands/UpdateStorageUrlsCommand.php + + - + rawMessage: 'Parameter #3 ...$values of function sprintf expects bool|float|int|string|null, mixed given.' + identifier: argument.type + count: 1 + path: app-modules/database-migration/src/Commands/UpdateStorageUrlsCommand.php + + - + rawMessage: 'Method Laravelcm\DatabaseMigration\Services\DatabaseMigrationService::getSourceTables() should return array but returns array.' + identifier: return.type + count: 1 + path: app-modules/database-migration/src/Services/DatabaseMigrationService.php + + - + rawMessage: Cannot access offset 'address' on mixed. + identifier: offsetAccess.nonOffsetAccessible + count: 2 + path: app-modules/database-migration/src/Services/SshTunnelService.php + + - + rawMessage: Cannot access offset 'bash' on mixed. + identifier: offsetAccess.nonOffsetAccessible + count: 1 + path: app-modules/database-migration/src/Services/SshTunnelService.php + + - + rawMessage: Cannot access offset 'bind_address' on mixed. + identifier: offsetAccess.nonOffsetAccessible + count: 1 + path: app-modules/database-migration/src/Services/SshTunnelService.php + + - + rawMessage: Cannot access offset 'bind_port' on mixed. + identifier: offsetAccess.nonOffsetAccessible + count: 1 + path: app-modules/database-migration/src/Services/SshTunnelService.php + + - + rawMessage: Cannot access offset 'executables' on mixed. + identifier: offsetAccess.nonOffsetAccessible + count: 3 + path: app-modules/database-migration/src/Services/SshTunnelService.php + + - + rawMessage: Cannot access offset 'hostname' on mixed. + identifier: offsetAccess.nonOffsetAccessible + count: 1 + path: app-modules/database-migration/src/Services/SshTunnelService.php + + - + rawMessage: Cannot access offset 'local' on mixed. + identifier: offsetAccess.nonOffsetAccessible + count: 5 + path: app-modules/database-migration/src/Services/SshTunnelService.php + + - + rawMessage: Cannot access offset 'nc' on mixed. + identifier: offsetAccess.nonOffsetAccessible + count: 1 + path: app-modules/database-migration/src/Services/SshTunnelService.php + + - + rawMessage: Cannot access offset 'options' on mixed. + identifier: offsetAccess.nonOffsetAccessible + count: 1 + path: app-modules/database-migration/src/Services/SshTunnelService.php + + - + rawMessage: Cannot access offset 'port' on mixed. + identifier: offsetAccess.nonOffsetAccessible + count: 4 + path: app-modules/database-migration/src/Services/SshTunnelService.php + + - + rawMessage: Cannot access offset 'remote' on mixed. + identifier: offsetAccess.nonOffsetAccessible + count: 2 + path: app-modules/database-migration/src/Services/SshTunnelService.php + + - + rawMessage: Cannot access offset 'ssh' on mixed. + identifier: offsetAccess.nonOffsetAccessible + count: 6 + path: app-modules/database-migration/src/Services/SshTunnelService.php + + - + rawMessage: Cannot access offset 'user' on mixed. + identifier: offsetAccess.nonOffsetAccessible + count: 1 + path: app-modules/database-migration/src/Services/SshTunnelService.php + + - + rawMessage: Cannot access offset 'verbosity' on mixed. + identifier: offsetAccess.nonOffsetAccessible + count: 1 + path: app-modules/database-migration/src/Services/SshTunnelService.php + + - + rawMessage: 'Parameter #1 $channel of static method Illuminate\Log\LogManager::channel() expects string|null, mixed given.' + identifier: argument.type + count: 1 + path: app-modules/database-migration/src/Services/SshTunnelService.php + + - + rawMessage: 'Parameter #1 $config of method Laravelcm\DatabaseMigration\Services\SshTunnelService::buildIdentityOption() expects array, mixed given.' + identifier: argument.type + count: 1 + path: app-modules/database-migration/src/Services/SshTunnelService.php + + - + rawMessage: 'Parameter #1 $duration of static method Illuminate\Support\Sleep::usleep() expects int, mixed given.' + identifier: argument.type + count: 2 + path: app-modules/database-migration/src/Services/SshTunnelService.php + + - + rawMessage: 'Parameter #10 ...$values of function sprintf expects bool|float|int|string|null, mixed given.' + identifier: argument.type + count: 1 + path: app-modules/database-migration/src/Services/SshTunnelService.php + + - + rawMessage: 'Parameter #11 ...$values of function sprintf expects bool|float|int|string|null, mixed given.' + identifier: argument.type + count: 1 + path: app-modules/database-migration/src/Services/SshTunnelService.php + + - + rawMessage: 'Parameter #2 ...$values of function sprintf expects bool|float|int|string|null, mixed given.' + identifier: argument.type + count: 4 + path: app-modules/database-migration/src/Services/SshTunnelService.php + + - + rawMessage: 'Parameter #3 ...$values of function sprintf expects bool|float|int|string|null, mixed given.' + identifier: argument.type + count: 3 + path: app-modules/database-migration/src/Services/SshTunnelService.php + + - + rawMessage: 'Parameter #4 ...$values of function sprintf expects bool|float|int|string|null, mixed given.' + identifier: argument.type + count: 4 + path: app-modules/database-migration/src/Services/SshTunnelService.php + + - + rawMessage: 'Parameter #6 ...$values of function sprintf expects bool|float|int|string|null, mixed given.' + identifier: argument.type + count: 1 + path: app-modules/database-migration/src/Services/SshTunnelService.php + + - + rawMessage: 'Parameter #7 ...$values of function sprintf expects bool|float|int|string|null, mixed given.' + identifier: argument.type + count: 1 + path: app-modules/database-migration/src/Services/SshTunnelService.php + + - + rawMessage: 'Parameter #8 ...$values of function sprintf expects bool|float|int|string|null, mixed given.' + identifier: argument.type + count: 1 + path: app-modules/database-migration/src/Services/SshTunnelService.php + + - + rawMessage: 'Parameter #9 ...$values of function sprintf expects bool|float|int|string|null, mixed given.' + identifier: argument.type + count: 1 + path: app-modules/database-migration/src/Services/SshTunnelService.php + + - + rawMessage: Access to an undefined property PHPUnit\Framework\TestCase::$app. + identifier: property.notFound + count: 7 + path: app-modules/database-migration/tests/Feature/MigrateDatabaseCommandTest.php + + - + rawMessage: 'Call to an undefined method PHPUnit\Framework\TestCase::artisan().' + identifier: method.notFound + count: 5 + path: app-modules/database-migration/tests/Feature/MigrateDatabaseCommandTest.php + + - + rawMessage: 'Call to an undefined method PHPUnit\Framework\TestCase::mock().' + identifier: method.notFound + count: 7 + path: app-modules/database-migration/tests/Feature/MigrateDatabaseCommandTest.php + + - + rawMessage: Access to an undefined property PHPUnit\Framework\TestCase::$app. + identifier: property.notFound + count: 2 + path: app-modules/database-migration/tests/Feature/SshTunnelCommandTest.php + + - + rawMessage: 'Call to an undefined method PHPUnit\Framework\TestCase::artisan().' + identifier: method.notFound + count: 3 + path: app-modules/database-migration/tests/Feature/SshTunnelCommandTest.php + + - + rawMessage: 'Call to an undefined method PHPUnit\Framework\TestCase::spy().' + identifier: method.notFound + count: 2 + path: app-modules/database-migration/tests/Feature/SshTunnelCommandTest.php + + - + rawMessage: Access to an undefined property PHPUnit\Framework\TestCase::$service. + identifier: property.notFound + count: 2 + path: app-modules/database-migration/tests/Unit/DatabaseMigrationServiceTest.php + + - + rawMessage: 'Call to an undefined method PHPUnit\Framework\TestCase::mock().' + identifier: method.notFound + count: 4 + path: app-modules/database-migration/tests/Unit/DatabaseMigrationServiceTest.php + + - + rawMessage: 'Call to method toBe() of internal class Pest\Mixins\Expectation from outside its root namespace Pest.' + identifier: method.internalClass + count: 1 + path: app-modules/database-migration/tests/Unit/DatabaseMigrationServiceTest.php + + - + rawMessage: 'Call to method toBeArray() of internal class Pest\Mixins\Expectation from outside its root namespace Pest.' + identifier: method.internalClass + count: 1 + path: app-modules/database-migration/tests/Unit/DatabaseMigrationServiceTest.php + + - + rawMessage: 'Call to method toBeInstanceOf() of internal class Pest\Mixins\Expectation from outside its root namespace Pest.' + identifier: method.internalClass + count: 1 + path: app-modules/database-migration/tests/Unit/DatabaseMigrationServiceTest.php + + - + rawMessage: 'Call to method toBeTrue() of internal class Pest\Mixins\Expectation from outside its root namespace Pest.' + identifier: method.internalClass + count: 4 + path: app-modules/database-migration/tests/Unit/DatabaseMigrationServiceTest.php + + - + rawMessage: 'Call to method toContain() of internal class Pest\Mixins\Expectation from outside its root namespace Pest.' + identifier: method.internalClass + count: 1 + path: app-modules/database-migration/tests/Unit/DatabaseMigrationServiceTest.php + + - + rawMessage: 'Call to method toHaveKey() of internal class Pest\Mixins\Expectation from outside its root namespace Pest.' + identifier: method.internalClass + count: 4 + path: app-modules/database-migration/tests/Unit/DatabaseMigrationServiceTest.php + + - + rawMessage: Unable to resolve the template type TValue in call to function expect + identifier: argument.templateType + count: 1 + path: app-modules/database-migration/tests/Unit/DatabaseMigrationServiceTest.php + + - + rawMessage: Access to an undefined property PHPUnit\Framework\TestCase::$service. + identifier: property.notFound + count: 2 + path: app-modules/database-migration/tests/Unit/SshTunnelServiceTest.php + + - + rawMessage: 'Call to an undefined method PHPUnit\Framework\TestCase::mock().' + identifier: method.notFound + count: 2 + path: app-modules/database-migration/tests/Unit/SshTunnelServiceTest.php + + - + rawMessage: 'Call to method toBeFalse() of internal class Pest\Mixins\Expectation from outside its root namespace Pest.' + identifier: method.internalClass + count: 1 + path: app-modules/database-migration/tests/Unit/SshTunnelServiceTest.php + + - + rawMessage: 'Call to method toBeInstanceOf() of internal class Pest\Mixins\Expectation from outside its root namespace Pest.' + identifier: method.internalClass + count: 1 + path: app-modules/database-migration/tests/Unit/SshTunnelServiceTest.php + + - + rawMessage: 'Call to method toThrow() of internal class Pest\Mixins\Expectation from outside its root namespace Pest.' + identifier: method.internalClass + count: 1 + path: app-modules/database-migration/tests/Unit/SshTunnelServiceTest.php + + - + rawMessage: Unable to resolve the template type TValue in call to function expect + identifier: argument.templateType + count: 1 + path: app-modules/database-migration/tests/Unit/SshTunnelServiceTest.php + + - + rawMessage: 'Call to function property_exists() with $this(Laravelcm\Gamify\PointType) and ''payee'' will always evaluate to true.' + identifier: function.alreadyNarrowedType + count: 1 + path: app-modules/gamify/src/PointType.php + + - + rawMessage: Property Laravelcm\Gamify\PointType::$name has no type specified. + identifier: missingType.property + count: 1 + path: app-modules/gamify/src/PointType.php + + - + rawMessage: Property Laravelcm\Gamify\PointType::$payee has no type specified. + identifier: missingType.property + count: 1 + path: app-modules/gamify/src/PointType.php + + - + rawMessage: Property Laravelcm\Gamify\PointType::$points has no type specified. + identifier: missingType.property + count: 1 + path: app-modules/gamify/src/PointType.php + + - + rawMessage: Property Laravelcm\Gamify\Tests\Fixtures\FakeCreatePostPoint::$points has no type specified. + identifier: missingType.property + count: 1 + path: app-modules/gamify/tests/Fixtures/FakeCreatePostPoint.php + + - + rawMessage: Property Laravelcm\Gamify\Tests\Fixtures\FakePayeeFieldPoint::$points has no type specified. + identifier: missingType.property + count: 1 + path: app-modules/gamify/tests/Fixtures/FakePayeeFieldPoint.php + + - + rawMessage: Property Laravelcm\Gamify\Tests\Fixtures\FakePointTypeWithoutPayee::$points has no type specified. + identifier: missingType.property + count: 1 + path: app-modules/gamify/tests/Fixtures/FakePointTypeWithoutPayee.php + + - + rawMessage: Property Laravelcm\Gamify\Tests\Fixtures\FakePointTypeWithoutSubject::$point has no type specified. + identifier: missingType.property + count: 1 + path: app-modules/gamify/tests/Fixtures/FakePointTypeWithoutSubject.php + + - + rawMessage: Property Laravelcm\Gamify\Tests\Fixtures\FakePointWithoutPoint::$payee has no type specified. + identifier: missingType.property + count: 1 + path: app-modules/gamify/tests/Fixtures/FakePointWithoutPoint.php + + - + rawMessage: Property Laravelcm\Gamify\Tests\Fixtures\FakeWelcomeUserWithFalseQualifier::$points has no type specified. + identifier: missingType.property + count: 1 + path: app-modules/gamify/tests/Fixtures/FakeWelcomeUserWithFalseQualifier.php + + - + rawMessage: Property Laravelcm\Gamify\Tests\Fixtures\FakeWelcomeUserWithNamePoint::$points has no type specified. + identifier: missingType.property + count: 1 + path: app-modules/gamify/tests/Fixtures/FakeWelcomeUserWithNamePoint.php + + - + rawMessage: Property Laravelcm\Gamify\Tests\Fixtures\FakeWelcomeUserWithNamePoint::$name has no type specified. + identifier: missingType.property + count: 1 + path: app-modules/gamify/tests/Fixtures/FakeWelcomeUserWithNamePoint.php + + - + rawMessage: Access to an undefined property PHPUnit\Framework\TestCase::$user. + identifier: property.notFound + count: 30 + path: app-modules/gamify/tests/Feature/PointTest.php + + - + rawMessage: 'Call to an undefined method PHPUnit\Framework\TestCase::createPost().' + identifier: method.notFound + count: 5 + path: app-modules/gamify/tests/Feature/PointTest.php + + - + rawMessage: 'Call to an undefined method PHPUnit\Framework\TestCase::createUser().' + identifier: method.notFound + count: 1 + path: app-modules/gamify/tests/Feature/PointTest.php + + - + rawMessage: 'Call to method skip() of internal class Pest\PendingCalls\TestCall from outside its root namespace Pest.' + identifier: method.internalClass + count: 1 + path: app-modules/gamify/tests/Feature/PointTest.php + + - + rawMessage: 'Call to method throws() of internal class Pest\PendingCalls\TestCall from outside its root namespace Pest.' + identifier: method.internalClass + count: 3 + path: app-modules/gamify/tests/Feature/PointTest.php + + - + rawMessage: 'Call to method toBe() of internal class Pest\Mixins\Expectation from outside its root namespace Pest.' + identifier: method.internalClass + count: 11 + path: app-modules/gamify/tests/Feature/PointTest.php + + - + rawMessage: 'Call to method toBeInstanceOf() of internal class Pest\Mixins\Expectation from outside its root namespace Pest.' + identifier: method.internalClass + count: 2 + path: app-modules/gamify/tests/Feature/PointTest.php + + - + rawMessage: 'Call to method toHaveCount() of internal class Pest\Mixins\Expectation from outside its root namespace Pest.' + identifier: method.internalClass + count: 2 + path: app-modules/gamify/tests/Feature/PointTest.php + + - + rawMessage: 'Call to an undefined method PHPUnit\Framework\TestCase::createUser().' + identifier: method.notFound + count: 4 + path: app-modules/gamify/tests/Feature/ReputationTest.php + + - + rawMessage: 'Call to method toBe() of internal class Pest\Mixins\Expectation from outside its root namespace Pest.' + identifier: method.internalClass + count: 7 + path: app-modules/gamify/tests/Feature/ReputationTest.php + + - + rawMessage: 'Property Laravelcm\Gamify\PointType::$subject (Illuminate\Database\Eloquent\Model|null) does not accept mixed.' + identifier: assign.propertyType + count: 1 + path: app-modules/gamify/tests/Fixtures/FakeCreatePostPoint.php + + - + rawMessage: 'Property Laravelcm\Gamify\PointType::$subject (Illuminate\Database\Eloquent\Model|null) does not accept mixed.' + identifier: assign.propertyType + count: 1 + path: app-modules/gamify/tests/Fixtures/FakePayeeFieldPoint.php + + - + rawMessage: 'Property Laravelcm\Gamify\PointType::$subject (Illuminate\Database\Eloquent\Model|null) does not accept mixed.' + identifier: assign.propertyType + count: 1 + path: app-modules/gamify/tests/Fixtures/FakePointTypeWithoutPayee.php + + - + rawMessage: 'Method Laravelcm\Gamify\Tests\Fixtures\FakePointTypeWithoutSubject::__construct() has parameter $subject with no type specified.' + identifier: missingType.parameter + count: 1 + path: app-modules/gamify/tests/Fixtures/FakePointTypeWithoutSubject.php + + - + rawMessage: 'Method Laravelcm\Gamify\Tests\Fixtures\FakePointWithoutPoint::__construct() has parameter $subject with no type specified.' + identifier: missingType.parameter + count: 1 + path: app-modules/gamify/tests/Fixtures/FakePointWithoutPoint.php + + - + rawMessage: Access to an undefined property Illuminate\Database\Eloquent\Model::$user. + identifier: property.notFound + count: 1 + path: app-modules/gamify/tests/Fixtures/FakeWelcomeUserWithFalseQualifier.php + + - + rawMessage: 'Method Laravelcm\Gamify\Tests\Fixtures\FakeWelcomeUserWithFalseQualifier::__construct() has parameter $subject with no type specified.' + identifier: missingType.parameter + count: 1 + path: app-modules/gamify/tests/Fixtures/FakeWelcomeUserWithFalseQualifier.php + + - + rawMessage: Constructor of class Laravelcm\Gamify\Tests\Fixtures\FakeWelcomeUserWithNamePoint has an unused parameter $subject. + identifier: constructor.unusedParameter + count: 1 + path: app-modules/gamify/tests/Fixtures/FakeWelcomeUserWithNamePoint.php + + - + rawMessage: Cannot access property $id on App\Models\User|null. + identifier: property.nonObject + count: 1 + path: app/Actions/Article/CreateArticleAction.php + + - + rawMessage: 'Cannot call method isAdmin() on App\Models\User|null.' + identifier: method.nonObject + count: 1 + path: app/Actions/Article/CreateArticleAction.php + + - + rawMessage: 'Cannot call method isModerator() on App\Models\User|null.' + identifier: method.nonObject + count: 1 + path: app/Actions/Article/CreateArticleAction.php + + - + rawMessage: 'Parameter $timezone of class Carbon\Carbon constructor expects DateTimeZone|int|string|null, mixed given.' + identifier: argument.type + count: 5 + path: app/Actions/Article/CreateArticleAction.php + + - + rawMessage: 'Parameter $timezone of class Carbon\Carbon constructor expects DateTimeZone|int|string|null, mixed given.' + identifier: argument.type + count: 2 + path: app/Actions/Article/UpdateArticleAction.php + + - + rawMessage: 'Parameter #1 $subject of class App\Gamify\Points\ReplyCreated constructor expects App\Models\Reply, Illuminate\Database\Eloquent\Model given.' + identifier: argument.type + count: 1 + path: app/Actions/Discussion/CreateDiscussionReplyAction.php + + - + rawMessage: 'Parameter #1 $subject of class App\Gamify\Points\ReplyCreated constructor expects App\Models\Reply, App\Contracts\ReplyInterface given.' + identifier: argument.type + count: 1 + path: app/Actions/Forum/CreateReplyAction.php + + - + rawMessage: Property App\Models\Subscribe::$uuid is not writable. + identifier: assign.propertyReadOnly + count: 1 + path: app/Actions/Forum/SubscribeToThreadAction.php + + - + rawMessage: 'Method App\Actions\GetGithubRepositoriesAction::__invoke() should return Illuminate\Support\Collection but returns mixed.' + identifier: return.type + count: 2 + path: app/Actions/GetGithubRepositoriesAction.php + + - + rawMessage: 'Parameter #1 $callback of method Illuminate\Support\Collection<(int|string),mixed>::map() expects callable(mixed, int|string): App\Data\RepositoryData, Closure(array): App\Data\RepositoryData given.' + identifier: argument.type + count: 1 + path: app/Actions/GetGithubRepositoriesAction.php + + - + rawMessage: 'Parameter #1 $token of static method Illuminate\Http\Client\PendingRequest::withToken() expects string, mixed given.' + identifier: argument.type + count: 1 + path: app/Actions/GetGithubRepositoriesAction.php + + - + rawMessage: 'Parameter #1 $value of function collect expects Illuminate\Contracts\Support\Arrayable<(int|string), mixed>|iterable<(int|string), mixed>|null, mixed given.' + identifier: argument.type + count: 1 + path: app/Actions/GetGithubRepositoriesAction.php + + - + rawMessage: Unable to resolve the template type TKey in call to function collect + identifier: argument.templateType + count: 1 + path: app/Actions/GetGithubRepositoriesAction.php + + - + rawMessage: Unable to resolve the template type TValue in call to function collect + identifier: argument.templateType + count: 1 + path: app/Actions/GetGithubRepositoriesAction.php + + - + rawMessage: 'Parameter #1 $spamReport of class App\Notifications\ReportedSpamToTelegram constructor expects App\Models\SpamReport, Illuminate\Database\Eloquent\Model given.' + identifier: argument.type + count: 1 + path: app/Actions/ReportSpamAction.php + + - + rawMessage: Property App\Models\User::$email_verified_at is not writable. + identifier: assign.propertyReadOnly + count: 1 + path: app/Actions/User/UpdateUserProfileAction.php + + - + rawMessage: 'Cannot call method get() on mixed.' + identifier: method.nonObject + count: 1 + path: app/Console/Commands/AssignUserRole.php + + - + rawMessage: 'Parameter #2 ...$values of function sprintf expects bool|float|int|string|null, mixed given.' + identifier: argument.type + count: 1 + path: app/Console/Commands/Cleanup/DeleteOldUnverifiedUsers.php + + - + rawMessage: 'Parameter #1 $value of static method Illuminate\Support\Facades\Hash::make() expects string, mixed given.' + identifier: argument.type + count: 1 + path: app/Console/Commands/CreateAdminUser.php + + - + rawMessage: 'Cannot call method get() on mixed.' + identifier: method.nonObject + count: 1 + path: app/Console/Commands/SendUnVerifiedMails.php + + - + rawMessage: 'Parameter #1 $callback of method Illuminate\Database\Eloquent\Builder::each() expects callable(Illuminate\Database\Eloquent\Model, int): mixed, Closure(iterable|Spatie\Sitemap\Contracts\Sitemapable|Spatie\Sitemap\Tags\Url|string): void given.' + identifier: argument.type + count: 1 + path: app/Console/Commands/Sitemap/GenerateArticlesSitemapCommand.php + + - + rawMessage: 'Parameter #1 $callback of method Illuminate\Database\Eloquent\Builder::each() expects callable(Illuminate\Database\Eloquent\Model, int): mixed, Closure(iterable|Spatie\Sitemap\Contracts\Sitemapable|Spatie\Sitemap\Tags\Url|string): void given.' + identifier: argument.type + count: 1 + path: app/Console/Commands/Sitemap/GenerateDiscussionsSitemapCommand.php + + - + rawMessage: Anonymous function should return string|null but returns mixed. + identifier: return.type + count: 1 + path: app/Filament/Resources/Articles/ArticleResource.php + + - + rawMessage: 'Parameter #1 $string of function mb_strlen expects string, mixed given.' + identifier: argument.type + count: 1 + path: app/Filament/Resources/Articles/ArticleResource.php + + - + rawMessage: 'Cannot call method filter() on mixed.' + identifier: method.nonObject + count: 1 + path: app/Filters/AbstractFilters.php + + - + rawMessage: 'Parameter #1 $key of method App\Filters\AbstractFilter::resolveFilterValue() expects string, mixed given.' + identifier: argument.type + count: 1 + path: app/Filters/Thread/SortByFilter.php + + - + rawMessage: 'Property Laravelcm\Gamify\PointType::$subject (Illuminate\Database\Eloquent\Model|null) does not accept mixed.' + identifier: assign.propertyType + count: 1 + path: app/Gamify/Points/AddPhone.php + + - + rawMessage: 'Property Laravelcm\Gamify\PointType::$subject (Illuminate\Database\Eloquent\Model|null) does not accept mixed.' + identifier: assign.propertyType + count: 1 + path: app/Gamify/Points/AddSocialLinks.php + + - + rawMessage: 'Call to an undefined method App\Models\User::createToken().' + identifier: method.notFound + count: 1 + path: app/Http/Controllers/Api/Auth/LoginController.php + + - + rawMessage: 'Call to an undefined method App\Models\User::tokens().' + identifier: method.notFound + count: 2 + path: app/Http/Controllers/Api/Auth/LoginController.php + + - + rawMessage: 'Cannot call method currentAccessToken() on App\Models\User|null.' + identifier: method.nonObject + count: 2 + path: app/Http/Controllers/Api/Auth/LoginController.php + + - + rawMessage: Negated boolean expression is always false. + identifier: booleanNot.alwaysFalse + count: 1 + path: app/Http/Controllers/Api/Auth/LoginController.php + + - + rawMessage: 'Parameter #1 $string of function mb_strtolower expects string, mixed given.' + identifier: argument.type + count: 2 + path: app/Http/Controllers/Api/Auth/LoginController.php + + - + rawMessage: Property App\Models\User::$last_login_at is not writable. + identifier: assign.propertyReadOnly + count: 1 + path: app/Http/Controllers/Api/Auth/LoginController.php + + - + rawMessage: 'Call to an undefined method App\Models\User::createToken().' + identifier: method.notFound + count: 1 + path: app/Http/Controllers/Api/Auth/RegisterController.php + + - + rawMessage: Cannot access offset 'email' on mixed. + identifier: offsetAccess.nonOffsetAccessible + count: 2 + path: app/Http/Controllers/Api/Auth/RegisterController.php + + - + rawMessage: Cannot access offset 'id' on mixed. + identifier: offsetAccess.nonOffsetAccessible + count: 2 + path: app/Http/Controllers/Api/Auth/RegisterController.php + + - + rawMessage: Cannot access offset 'idToken' on mixed. + identifier: offsetAccess.nonOffsetAccessible + count: 2 + path: app/Http/Controllers/Api/Auth/RegisterController.php + + - + rawMessage: Cannot access offset 'name' on mixed. + identifier: offsetAccess.nonOffsetAccessible + count: 1 + path: app/Http/Controllers/Api/Auth/RegisterController.php + + - + rawMessage: Cannot access offset 'photoUrl' on mixed. + identifier: offsetAccess.nonOffsetAccessible + count: 2 + path: app/Http/Controllers/Api/Auth/RegisterController.php + + - + rawMessage: Cannot access offset 'provider' on mixed. + identifier: offsetAccess.nonOffsetAccessible + count: 3 + path: app/Http/Controllers/Api/Auth/RegisterController.php + + - + rawMessage: 'Parameter #1 $provider of method App\Models\User::hasProvider() expects string, mixed given.' + identifier: argument.type + count: 1 + path: app/Http/Controllers/Api/Auth/RegisterController.php + + - + rawMessage: 'Parameter #1 $string of function mb_strtolower expects string, mixed given.' + identifier: argument.type + count: 3 + path: app/Http/Controllers/Api/Auth/RegisterController.php + + - + rawMessage: 'Parameter #1 $value of static method Illuminate\Support\Facades\Hash::make() expects string, mixed given.' + identifier: argument.type + count: 1 + path: app/Http/Controllers/Api/Auth/RegisterController.php + + - + rawMessage: 'Parameter #1 $value of static method Illuminate\Support\Facades\Hash::make() expects string, mixed given.' + identifier: argument.type + count: 1 + path: app/Http/Controllers/Api/Auth/ResetPasswordController.php + + - + rawMessage: Binary operation "." between mixed and '/email/verify…' results in an error. + identifier: binaryOp.invalid + count: 2 + path: app/Http/Controllers/Api/Auth/VerifyEmailController.php + + - + rawMessage: 'Cannot call method limit() on mixed.' + identifier: method.nonObject + count: 1 + path: app/Http/Controllers/Api/Enterprise/PublicController.php + + - + rawMessage: 'Cannot call method chunk() on mixed.' + identifier: method.nonObject + count: 1 + path: app/Http/Controllers/Api/PremiumController.php + + - + rawMessage: 'Parameter $apiKey of static method NotchPay\NotchPay::setApiKey() expects string, mixed given.' + identifier: argument.type + count: 1 + path: app/Http/Controllers/NotchPayCallBackController.php + + - + rawMessage: 'Parameter $reference of static method NotchPay\Payment::verify() expects string, mixed given.' + identifier: argument.type + count: 1 + path: app/Http/Controllers/NotchPayCallBackController.php + + - + rawMessage: Access to an undefined property Laravel\Socialite\Contracts\User::$avatar. + identifier: property.notFound + count: 2 + path: app/Http/Controllers/OAuthController.php + + - + rawMessage: Access to an undefined property Laravel\Socialite\Contracts\User::$id. + identifier: property.notFound + count: 1 + path: app/Http/Controllers/OAuthController.php + + - + rawMessage: Access to an undefined property Laravel\Socialite\Contracts\User::$token. + identifier: property.notFound + count: 2 + path: app/Http/Controllers/OAuthController.php + + - + rawMessage: 'Parameter #1 $replyAble of function route_to_reply_able expects App\Models\Discussion|App\Models\Thread, Illuminate\Database\Eloquent\Model|null given.' + identifier: argument.type + count: 1 + path: app/Http/Controllers/ReplyAbleController.php + + - + rawMessage: 'Parameter #1 $replyAble of function route_to_reply_able expects App\Models\Discussion|App\Models\Thread, Illuminate\Database\Eloquent\Model|null given.' + identifier: argument.type + count: 1 + path: app/Http/Controllers/SubscriptionController.php + + - + rawMessage: 'Parameter #1 $locale of method Illuminate\Foundation\Application::setLocale() expects string, mixed given.' + identifier: argument.type + count: 2 + path: app/Http/Middleware/LocaleMiddleware.php + + - + rawMessage: 'Parameter #2 $haystack of function in_array expects array, mixed given.' + identifier: argument.type + count: 1 + path: app/Http/Middleware/LocaleMiddleware.php + + - + rawMessage: Access to an undefined property App\Http\Resources\AuthenticateUserResource::$reputation. + identifier: property.notFound + count: 1 + path: app/Http/Resources/AuthenticateUserResource.php + + - + rawMessage: 'Call to an undefined method App\Http\Resources\DateTimeResource::diffForHumans().' + identifier: method.notFound + count: 1 + path: app/Http/Resources/DateTimeResource.php + + - + rawMessage: 'Call to an undefined method App\Http\Resources\DateTimeResource::toDateTimeString().' + identifier: method.notFound + count: 1 + path: app/Http/Resources/DateTimeResource.php + + - + rawMessage: Access to an undefined property App\Http\Resources\EnterpriseResource::$slug. + identifier: property.notFound + count: 1 + path: app/Http/Resources/EnterpriseResource.php + + - + rawMessage: Access to an undefined property App\Http\Resources\EnterpriseResourceCollection::$pagination. + identifier: property.notFound + count: 1 + path: app/Http/Resources/EnterpriseResourceCollection.php + + - + rawMessage: 'Cannot call method transform() on Illuminate\Support\Collection|null.' + identifier: method.nonObject + count: 1 + path: app/Http/Resources/EnterpriseResourceCollection.php + + - + rawMessage: Access to an undefined property App\Http\Resources\PaginationResourceCollection::$pagination. + identifier: property.notFound + count: 1 + path: app/Http/Resources/PaginationResourceCollection.php + + - + rawMessage: 'Cannot call method currentPage() on mixed.' + identifier: method.nonObject + count: 1 + path: app/Http/Resources/PaginationResourceCollection.php + + - + rawMessage: 'Cannot call method firstItem() on mixed.' + identifier: method.nonObject + count: 1 + path: app/Http/Resources/PaginationResourceCollection.php + + - + rawMessage: 'Cannot call method getCollection() on mixed.' + identifier: method.nonObject + count: 1 + path: app/Http/Resources/PaginationResourceCollection.php + + - + rawMessage: 'Cannot call method lastItem() on mixed.' + identifier: method.nonObject + count: 1 + path: app/Http/Resources/PaginationResourceCollection.php + + - + rawMessage: 'Cannot call method lastPage() on mixed.' + identifier: method.nonObject + count: 2 + path: app/Http/Resources/PaginationResourceCollection.php + + - + rawMessage: 'Cannot call method nextPageUrl() on mixed.' + identifier: method.nonObject + count: 1 + path: app/Http/Resources/PaginationResourceCollection.php + + - + rawMessage: 'Cannot call method perPage() on mixed.' + identifier: method.nonObject + count: 1 + path: app/Http/Resources/PaginationResourceCollection.php + + - + rawMessage: 'Cannot call method previousPageUrl() on mixed.' + identifier: method.nonObject + count: 1 + path: app/Http/Resources/PaginationResourceCollection.php + + - + rawMessage: 'Cannot call method total() on mixed.' + identifier: method.nonObject + count: 1 + path: app/Http/Resources/PaginationResourceCollection.php + + - + rawMessage: 'Cannot call method url() on mixed.' + identifier: method.nonObject + count: 2 + path: app/Http/Resources/PaginationResourceCollection.php + + - + rawMessage: 'Method App\Http\Resources\PaginationResourceCollection::__construct() has parameter $filters with no type specified.' + identifier: missingType.parameter + count: 1 + path: app/Http/Resources/PaginationResourceCollection.php + + - + rawMessage: 'Parameter #1 $time of class Carbon\Carbon constructor expects Carbon\Month|Carbon\WeekDay|DateTimeInterface|float|int|string|null, mixed given.' + identifier: argument.type + count: 1 + path: app/Livewire/Components/Slideovers/ArticleForm.php + + - + rawMessage: 'Cannot call method distinct() on mixed.' + identifier: method.nonObject + count: 1 + path: app/Livewire/Components/SponsorSubscription.php + + - + rawMessage: 'Parameter #1 $emailAddress of static method App\Models\User::findByEmailAddress() expects string, mixed given.' + identifier: argument.type + count: 1 + path: app/Livewire/Components/SponsorSubscription.php + + - + rawMessage: 'Parameter $apiKey of static method NotchPay\NotchPay::setApiKey() expects string, mixed given.' + identifier: argument.type + count: 1 + path: app/Livewire/Components/SponsorSubscription.php + + - + rawMessage: 'Method App\Livewire\Components\User\Activities::activities() should return Illuminate\Support\Collection but returns mixed.' + identifier: return.type + count: 1 + path: app/Livewire/Components/User/Activities.php + + - + rawMessage: 'Parameter $value of static method Illuminate\Support\Facades\Hash::make() expects string, mixed given.' + identifier: argument.type + count: 1 + path: app/Livewire/Components/User/Password.php + + - + rawMessage: 'Parameter #2 $default of method App\Models\User::setting() expects string, mixed given.' + identifier: argument.type + count: 1 + path: app/Livewire/Components/User/Preferences.php + + - + rawMessage: 'Cannot call method withCount() on mixed.' + identifier: method.nonObject + count: 1 + path: app/Livewire/Components/User/Threads.php + + - + rawMessage: 'Cannot call method find() on mixed.' + identifier: method.nonObject + count: 1 + path: app/Livewire/Pages/Account/Dashboard.php + + - + rawMessage: 'Method App\Livewire\Pages\Account\Profile::articles() should return Illuminate\Support\Collection but returns mixed.' + identifier: return.type + count: 1 + path: app/Livewire/Pages/Account/Profile.php + + - + rawMessage: 'Method App\Livewire\Pages\Account\Profile::discussions() should return Illuminate\Support\Collection but returns mixed.' + identifier: return.type + count: 1 + path: app/Livewire/Pages/Account/Profile.php + + - + rawMessage: 'Method App\Livewire\Pages\Account\Profile::threads() should return Illuminate\Support\Collection but returns mixed.' + identifier: return.type + count: 1 + path: app/Livewire/Pages/Account/Profile.php + + - + rawMessage: Cannot access property $title on mixed. + identifier: property.nonObject + count: 2 + path: app/Livewire/Pages/Articles/SinglePost.php + + - + rawMessage: 'Cannot call method canonicalUrl() on mixed.' + identifier: method.nonObject + count: 1 + path: app/Livewire/Pages/Articles/SinglePost.php + + - + rawMessage: 'Cannot call method excerpt() on mixed.' + identifier: method.nonObject + count: 2 + path: app/Livewire/Pages/Articles/SinglePost.php + + - + rawMessage: 'Cannot call method getFirstMediaUrl() on mixed.' + identifier: method.nonObject + count: 2 + path: app/Livewire/Pages/Articles/SinglePost.php + + - + rawMessage: 'Cannot call method isPublished() on mixed.' + identifier: method.nonObject + count: 1 + path: app/Livewire/Pages/Articles/SinglePost.php + + - + rawMessage: 'Property App\Livewire\Pages\Articles\SinglePost::$article (App\Models\Article) does not accept mixed.' + identifier: assign.propertyType + count: 1 + path: app/Livewire/Pages/Articles/SinglePost.php + + - + rawMessage: 'Cannot call method withCount() on mixed.' + identifier: method.nonObject + count: 1 + path: app/Livewire/Pages/Forum/Index.php + + - + rawMessage: 'Method App\Livewire\Pages\Forum\Index::applyChannel() should return Illuminate\Database\Eloquent\Builder but returns mixed.' + identifier: return.type + count: 1 + path: app/Livewire/Pages/Forum/Index.php + + - + rawMessage: 'Argument of an invalid type mixed supplied for foreach, only iterables are supported.' + identifier: foreach.nonIterable + count: 1 + path: app/Markdown/BaseExtension.php + + - + rawMessage: Cannot access offset 0 on mixed. + identifier: offsetAccess.nonOffsetAccessible + count: 1 + path: app/Markdown/BaseExtension.php + + - + rawMessage: 'Cannot call method getInfoWords() on class-string|object.' + identifier: method.nonObject + count: 1 + path: app/Markdown/BaseExtension.php + + - + rawMessage: 'Method App\Markdown\BaseExtension::bind() has parameter $environment with no type specified.' + identifier: missingType.parameter + count: 1 + path: app/Markdown/BaseExtension.php + + - + rawMessage: 'Method App\Markdown\BaseExtension::getContent() has parameter $node with no type specified.' + identifier: missingType.parameter + count: 1 + path: app/Markdown/BaseExtension.php + + - + rawMessage: 'Method App\Markdown\BaseExtension::getInfo() has parameter $node with no type specified.' + identifier: missingType.parameter + count: 1 + path: app/Markdown/BaseExtension.php + + - + rawMessage: 'Method App\Markdown\BaseExtension::getLanguage() has parameter $node with no type specified.' + identifier: missingType.parameter + count: 1 + path: app/Markdown/BaseExtension.php + + - + rawMessage: 'Method App\Markdown\BaseExtension::getLiteralContent() has parameter $node with no type specified.' + identifier: missingType.parameter + count: 1 + path: app/Markdown/BaseExtension.php + + - + rawMessage: 'Method App\Markdown\BaseExtension::getTheme() has parameter $node with no type specified.' + identifier: missingType.parameter + count: 1 + path: app/Markdown/BaseExtension.php + + - + rawMessage: 'Method App\Markdown\BaseExtension::isCodeNode() has parameter $node with no type specified.' + identifier: missingType.parameter + count: 1 + path: app/Markdown/BaseExtension.php + + - + rawMessage: 'Method App\Markdown\BaseExtension::makeTorchlightBlock() has parameter $node with no type specified.' + identifier: missingType.parameter + count: 1 + path: app/Markdown/BaseExtension.php + + - + rawMessage: 'Method App\Markdown\BaseExtension::renderNode() has parameter $node with no type specified.' + identifier: missingType.parameter + count: 1 + path: app/Markdown/BaseExtension.php + + - + rawMessage: 'Parameter #1 $haystack of static method Illuminate\Support\Str::startsWith() expects string, mixed given.' + identifier: argument.type + count: 1 + path: app/Markdown/BaseExtension.php + + - + rawMessage: 'Parameter #1 $string of static method League\CommonMark\Util\Xml::escape() expects string, mixed given.' + identifier: argument.type + count: 1 + path: app/Markdown/BaseExtension.php + + - + rawMessage: 'Parameter #1 $subject of static method Illuminate\Support\Str::after() expects string, mixed given.' + identifier: argument.type + count: 1 + path: app/Markdown/BaseExtension.php + + - + rawMessage: 'Property App\Markdown\BaseExtension::$customBlockRenderer (callable) on left side of ?? is not nullable.' + identifier: nullCoalesce.property + count: 1 + path: app/Markdown/BaseExtension.php + + - + rawMessage: 'Method App\Markdown\Extensions\TorchlightExtension::getLiteralContent() has parameter $node with no type specified.' + identifier: missingType.parameter + count: 1 + path: app/Markdown/Extensions/TorchlightExtension.php + + - + rawMessage: 'Offset 2 on array in isset() does not exist.' + identifier: isset.offset + count: 1 + path: app/Markdown/MarkdownHelper.php + + - + rawMessage: 'Parameter #2 $string of function explode expects string, string|null given.' + identifier: argument.type + count: 1 + path: app/Markdown/MarkdownHelper.php + + - + rawMessage: 'Parameter #2 $tagArray of static method App\Markdown\MarkdownHelper::replaceCodeSandboxTag() expects array, list given.' + identifier: argument.type + count: 1 + path: app/Markdown/MarkdownHelper.php + + - + rawMessage: Result of && is always false. + identifier: booleanAnd.alwaysFalse + count: 1 + path: app/Markdown/MarkdownHelper.php + + - + rawMessage: 'Method App\Models\Activity::feed() should return Illuminate\Support\Collection but returns Illuminate\Database\Eloquent\Collection>.' + identifier: return.type + count: 1 + path: app/Models/Activity.php + + - + rawMessage: 'Method App\Models\Activity::latestFeed() should return Illuminate\Support\Collection but returns Illuminate\Database\Eloquent\Collection.' + identifier: return.type + count: 1 + path: app/Models/Activity.php + + - + rawMessage: 'Method App\Models\Article::findBySlug() should return App\Models\Article but returns Illuminate\Database\Eloquent\Model.' + identifier: return.type + count: 1 + path: app/Models/Article.php + + - + rawMessage: 'Method App\Models\Discussion::findBySlug() should return App\Models\Discussion but returns Illuminate\Database\Eloquent\Model.' + identifier: return.type + count: 1 + path: app/Models/Discussion.php + + - + rawMessage: 'Method App\Models\Transaction::getMetadata() should return array|string but returns mixed.' + identifier: return.type + count: 1 + path: app/Models/Transaction.php + + - + rawMessage: Anonymous function should return string but returns mixed. + identifier: return.type + count: 1 + path: app/Models/User.php + + - + rawMessage: 'Parameter #1 $string of function ucwords expects string, mixed given.' + identifier: argument.type + count: 1 + path: app/Models/User.php + + - + rawMessage: 'Parameter #3 ...$values of function sprintf expects bool|float|int|string|null, mixed given.' + identifier: argument.type + count: 1 + path: app/Models/User.php + + - + rawMessage: 'Parameter #1 $chatId of method NotificationChannels\Telegram\TelegramBase::to() expects int|string, mixed given.' + identifier: argument.type + count: 1 + path: app/Notifications/ArticleSubmitted.php + + - + rawMessage: 'Cannot call method replyAbleSubject() on Illuminate\Database\Eloquent\Model|null.' + identifier: method.nonObject + count: 1 + path: app/Notifications/NewReplyNotification.php + + - + rawMessage: 'Binary operation "." between ''Auteur: '' and mixed results in an error.' + identifier: binaryOp.invalid + count: 1 + path: app/Notifications/NewSponsorPaymentNotification.php + + - + rawMessage: 'Offset ''laravel_cm_id'' might not exist on array|string.' + identifier: offsetAccess.notFound + count: 1 + path: app/Notifications/NewSponsorPaymentNotification.php + + - + rawMessage: 'Offset ''name'' might not exist on array|string.' + identifier: offsetAccess.notFound + count: 1 + path: app/Notifications/NewSponsorPaymentNotification.php + + - + rawMessage: 'Parameter #1 $chatId of method NotificationChannels\Telegram\TelegramBase::to() expects int|string, mixed given.' + identifier: argument.type + count: 1 + path: app/Notifications/NewSponsorPaymentNotification.php + + - + rawMessage: Access to an undefined property Illuminate\Database\Eloquent\Model::$slug. + identifier: property.notFound + count: 1 + path: app/Notifications/PendingArticlesNotification.php + + - + rawMessage: Access to an undefined property Illuminate\Database\Eloquent\Model::$submitted_at. + identifier: property.notFound + count: 1 + path: app/Notifications/PendingArticlesNotification.php + + - + rawMessage: Access to an undefined property Illuminate\Database\Eloquent\Model::$title. + identifier: property.notFound + count: 1 + path: app/Notifications/PendingArticlesNotification.php + + - + rawMessage: Access to an undefined property Illuminate\Database\Eloquent\Model::$user. + identifier: property.notFound + count: 2 + path: app/Notifications/PendingArticlesNotification.php + + - + rawMessage: 'Parameter #1 $chatId of method NotificationChannels\Telegram\TelegramBase::to() expects int|string, mixed given.' + identifier: argument.type + count: 1 + path: app/Notifications/PendingArticlesNotification.php + + - + rawMessage: 'Cannot call method subject() on mixed.' + identifier: method.nonObject + count: 1 + path: app/Notifications/PostThreadToTelegram.php + + - + rawMessage: Using nullsafe property access on non-nullable type App\Models\User. Use -> instead. + identifier: nullsafe.neverNull + count: 4 + path: app/Notifications/YouWereMentioned.php + + - + rawMessage: 'Parameter #1 $name of static method Illuminate\Redis\RedisManager::connection() expects string|UnitEnum|null, mixed given.' + identifier: argument.type + count: 2 + path: app/Services/CacheInvalidationService.php + + - + rawMessage: 'Parameter #1 $table of static method Illuminate\Database\Connection::table() expects Closure|Illuminate\Contracts\Database\Query\Expression|Illuminate\Database\Query\Builder|string|UnitEnum, mixed given.' + identifier: argument.type + count: 1 + path: app/Services/CacheInvalidationService.php + + - + rawMessage: 'Method App\Services\MediaCacheService::getCachedMediaUrl() should return string|null but returns mixed.' + identifier: return.type + count: 1 + path: app/Services/MediaCacheService.php + + - + rawMessage: 'Cannot call method get() on mixed.' + identifier: method.nonObject + count: 1 + path: app/View/Composers/ModeratorsComposer.php + + - + rawMessage: 'Cannot call method inRandomOrder() on mixed.' + identifier: method.nonObject + count: 1 + path: app/View/Composers/ProfileUsersComposer.php + + - + rawMessage: 'Cannot call method limit() on mixed.' + identifier: method.nonObject + count: 1 + path: app/View/Composers/TopContributorsComposer.php + + - + rawMessage: Cannot cast mixed to string. + identifier: cast.string + count: 1 + path: app/helpers.php + + - + rawMessage: 'Function get_current_theme() should return string but returns mixed.' + identifier: return.type + count: 1 + path: app/helpers.php + + - + rawMessage: 'Parameter #2 $string of function explode expects string, bool|string given.' + identifier: argument.type + count: 1 + path: config/app.php + + - + rawMessage: 'Parameter #1 $title of static method Illuminate\Support\Str::slug() expects string, bool|string given.' + identifier: argument.type + count: 1 + path: config/cache.php + + - + rawMessage: 'Parameter #1 $title of static method Illuminate\Support\Str::slug() expects string, bool|string given.' + identifier: argument.type + count: 1 + path: config/database.php + + - + rawMessage: Binary operation "." between mixed and '/@%%s' results in an error. + identifier: binaryOp.invalid + count: 1 + path: config/markdown.php + + - + rawMessage: 'Parameter #1 $title of static method Illuminate\Support\Str::slug() expects string, bool|string given.' + identifier: argument.type + count: 1 + path: config/session.php + + - + rawMessage: Variable $attributes on left side of ?? is never defined. + identifier: nullCoalesce.variable + count: 1 + path: database/factories/ArticleFactory.php + + - + rawMessage: Variable $attributes on left side of ?? is never defined. + identifier: nullCoalesce.variable + count: 1 + path: database/factories/DiscussionFactory.php + + - + rawMessage: Variable $attributes on left side of ?? is never defined. + identifier: nullCoalesce.variable + count: 1 + path: database/factories/ReplyFactory.php + + - + rawMessage: Variable $attributes on left side of ?? is never defined. + identifier: nullCoalesce.variable + count: 1 + path: database/factories/ThreadFactory.php + + - + rawMessage: Cannot access offset 'model_morph_key' on mixed. + identifier: offsetAccess.nonOffsetAccessible + count: 8 + path: database/migrations/2021_09_14_172248_create_permission_tables.php + + - + rawMessage: Cannot access offset 'permission_pivot_key' on mixed. + identifier: offsetAccess.nonOffsetAccessible + count: 1 + path: database/migrations/2021_09_14_172248_create_permission_tables.php + + - + rawMessage: Cannot access offset 'role_pivot_key' on mixed. + identifier: offsetAccess.nonOffsetAccessible + count: 1 + path: database/migrations/2021_09_14_172248_create_permission_tables.php + + - + rawMessage: Cannot access offset 'team_foreign_key' on mixed. + identifier: offsetAccess.nonOffsetAccessible + count: 10 + path: database/migrations/2021_09_14_172248_create_permission_tables.php + + - + rawMessage: 'Parameter #1 $column of method Illuminate\Database\Schema\Blueprint::unsignedBigInteger() expects string, mixed given.' + identifier: argument.type + count: 9 + path: database/migrations/2021_09_14_172248_create_permission_tables.php + + - + rawMessage: 'Parameter #1 $columns of method Illuminate\Database\Schema\Blueprint::foreign() expects array|string, mixed given.' + identifier: argument.type + count: 4 + path: database/migrations/2021_09_14_172248_create_permission_tables.php + + - + rawMessage: 'Parameter #1 $columns of method Illuminate\Database\Schema\Blueprint::index() expects array|string, mixed given.' + identifier: argument.type + count: 3 + path: database/migrations/2021_09_14_172248_create_permission_tables.php + + - + rawMessage: 'Parameter #1 $key of method Illuminate\Contracts\Cache\Repository::forget() expects string, mixed given.' + identifier: argument.type + count: 1 + path: database/migrations/2021_09_14_172248_create_permission_tables.php + + - + rawMessage: 'Parameter #1 $name of method Illuminate\Cache\CacheManager::store() expects string|null, mixed given.' + identifier: argument.type + count: 1 + path: database/migrations/2021_09_14_172248_create_permission_tables.php + + - + rawMessage: 'Parameter #1 $table of method Illuminate\Database\Schema\ForeignKeyDefinition::on() expects string, mixed given.' + identifier: argument.type + count: 4 + path: database/migrations/2021_09_14_172248_create_permission_tables.php + + - + rawMessage: 'Parameter #1 $table of static method Illuminate\Support\Facades\Schema::create() expects string, mixed given.' + identifier: argument.type + count: 5 + path: database/migrations/2021_09_14_172248_create_permission_tables.php + + - + rawMessage: 'Parameter #1 $name of static method Illuminate\Support\Facades\Schema::connection() expects string|null, mixed given.' + identifier: argument.type + count: 1 + path: database/migrations/2021_11_04_094343_create_views_table.php + + - + rawMessage: 'Property Illuminate\Database\Migrations\Migration@anonymous/database/migrations/2021_11_04_094343_create_views_table.php:9::$schema (Illuminate\Support\Facades\Schema) does not accept Illuminate\Database\Schema\Builder.' + identifier: assign.propertyType + count: 1 + path: database/migrations/2021_11_04_094343_create_views_table.php + + - + rawMessage: 'Property Illuminate\Database\Migrations\Migration@anonymous/database/migrations/2021_11_04_094343_create_views_table.php:9::$table (string) does not accept mixed.' + identifier: assign.propertyType + count: 1 + path: database/migrations/2021_11_04_094343_create_views_table.php + + - + rawMessage: 'Parameter #1 $table of static method Illuminate\Support\Facades\Schema::create() expects string, mixed given.' + identifier: argument.type + count: 1 + path: database/migrations/2023_10_03_064550_create_plans_table.php + + - + rawMessage: 'Parameter #1 $table of static method Illuminate\Support\Facades\Schema::dropIfExists() expects string, mixed given.' + identifier: argument.type + count: 1 + path: database/migrations/2023_10_03_064550_create_plans_table.php + + - + rawMessage: 'Parameter #1 $table of static method Illuminate\Support\Facades\Schema::create() expects string, mixed given.' + identifier: argument.type + count: 1 + path: database/migrations/2023_10_03_064551_create_plan_features_table.php + + - + rawMessage: 'Parameter #1 $table of static method Illuminate\Support\Facades\Schema::dropIfExists() expects string, mixed given.' + identifier: argument.type + count: 1 + path: database/migrations/2023_10_03_064551_create_plan_features_table.php + + - + rawMessage: 'Parameter #1 $table of static method Illuminate\Support\Facades\Schema::create() expects string, mixed given.' + identifier: argument.type + count: 1 + path: database/migrations/2023_10_03_064552_create_plan_subscriptions_table.php + + - + rawMessage: 'Parameter #1 $table of static method Illuminate\Support\Facades\Schema::dropIfExists() expects string, mixed given.' + identifier: argument.type + count: 1 + path: database/migrations/2023_10_03_064552_create_plan_subscriptions_table.php + + - + rawMessage: 'Parameter #1 $table of static method Illuminate\Support\Facades\Schema::create() expects string, mixed given.' + identifier: argument.type + count: 1 + path: database/migrations/2023_10_03_064553_create_plan_subscription_usage_table.php + + - + rawMessage: 'Parameter #1 $table of static method Illuminate\Support\Facades\Schema::dropIfExists() expects string, mixed given.' + identifier: argument.type + count: 1 + path: database/migrations/2023_10_03_064553_create_plan_subscription_usage_table.php + + - + rawMessage: 'Called ''modelKeys'' on Laravel collection, but could have been retrieved as a query.' + identifier: larastan.noUnnecessaryCollectionCall + count: 2 + path: database/seeders/Fixtures/ArticleTableSeeder.php + + - + rawMessage: 'Parameter #1 $input of function array_rand expects non-empty-array, array given.' + identifier: argument.type + count: 12 + path: database/seeders/Fixtures/ArticleTableSeeder.php + + - + rawMessage: 'Called ''modelKeys'' on Laravel collection, but could have been retrieved as a query.' + identifier: larastan.noUnnecessaryCollectionCall + count: 2 + path: database/seeders/Fixtures/DiscussionTableSeeder.php + + - + rawMessage: 'Parameter #1 $input of function array_rand expects non-empty-array, array given.' + identifier: argument.type + count: 2 + path: database/seeders/Fixtures/DiscussionTableSeeder.php + + - + rawMessage: 'Called ''modelKeys'' on Laravel collection, but could have been retrieved as a query.' + identifier: larastan.noUnnecessaryCollectionCall + count: 1 + path: database/seeders/Fixtures/ThreadTableSeeder.php + + - + rawMessage: 'Parameter #1 $input of function array_rand expects non-empty-array, array given.' + identifier: argument.type + count: 2 + path: database/seeders/Fixtures/ThreadTableSeeder.php diff --git a/phpstan.neon b/phpstan.neon index c5d206d0..c80954aa 100644 --- a/phpstan.neon +++ b/phpstan.neon @@ -1,21 +1,28 @@ includes: - - ./vendor/larastan/larastan/extension.neon + - vendor/larastan/larastan/extension.neon + - vendor/nesbot/carbon/extension.neon + - phar://phpstan.phar/conf/bleedingEdge.neon + - phpstan-baseline.neon parameters: + paths: - app - app-modules/ - level: 8 + - bootstrap/app.php + - config + - database + - public + - routes + + level: 9 + + tmpDir: /tmp/phpstan + excludePaths: - - app/Http/Resources/ - - app/Actions/ - - app/Notifications/ - - app/Http/Controllers/* - - app/Markdown/* - - app/Models/Traits/HasSlug.php - - app-modules/*/vendor/* - - app-modules/*/tests/* - - app-modules/*/config/* + - app-modules/*/*/tests/* + - tests + ignoreErrors: - "#^Cannot access property \\$transaction on array\\|object\\.$#" - identifier: missingType.iterableValue diff --git a/phpunit.ci.xml b/phpunit.ci.xml index 2ba3a8b8..15734cb1 100644 --- a/phpunit.ci.xml +++ b/phpunit.ci.xml @@ -1,36 +1,49 @@ - - - - ./tests/Feature - - - ./app-modules/*/tests - - - - - - - - - - - - - - - - - - - - - - - - - ./app - - + + + + ./tests/Feature + + + ./app-modules/*/tests + + + + + app + app-modules + + + app-modules/*/resources + app-modules/*/database + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/phpunit.xml b/phpunit.xml index 5f305fa2..86c3c18e 100644 --- a/phpunit.xml +++ b/phpunit.xml @@ -10,22 +10,22 @@ - + - - - - - - - - - - - - - - + + + + + + + + + + + + + + diff --git a/pint.json b/pint.json index d8fc6374..094eaba0 100644 --- a/pint.json +++ b/pint.json @@ -1,9 +1,10 @@ { "preset": "laravel", + "notPath": ["tests/TestCase.php", "tmp"], "rules": { + "array_push": true, "array_syntax": true, - "declare_parentheses": true, - "declare_strict_types": true, + "backtick_to_shell_exec": true, "blank_line_before_statement": { "statements": [ "break", @@ -14,12 +15,60 @@ "try" ] }, + "date_time_immutable": true, + "declare_parentheses": true, + "declare_strict_types": true, + "lowercase_keywords": true, + "lowercase_static_reference": true, + "fully_qualified_strict_types": true, + "global_namespace_import": { + "import_classes": true, + "import_constants": true, + "import_functions": true + }, + "mb_str_functions": true, "method_argument_space": { "on_multiline": "ensure_fully_multiline", "keep_multiple_spaces_after_comma": true }, - "use_arrow_functions": true, + "modernize_types_casting": true, + "new_with_parentheses": false, + "no_superfluous_elseif": true, + "no_useless_else": true, + "no_multiple_statements_per_line": true, + "ordered_class_elements": { + "order": [ + "use_trait", + "case", + "constant", + "constant_public", + "constant_protected", + "constant_private", + "property_public", + "property_protected", + "property_private", + "construct", + "destruct", + "magic", + "phpunit", + "method_abstract", + "method_public_static", + "method_public", + "method_protected_static", + "method_protected", + "method_private_static", + "method_private" + ], + "sort_algorithm": "none" + }, + "ordered_interfaces": true, "ordered_traits": true, + "protected_to_private": true, + "self_accessor": true, + "self_static_accessor": true, + "strict_comparison": true, + "use_arrow_functions": true, + "visibility_required": true, "void_return": true } } diff --git a/postcss.config.js b/postcss.config.js deleted file mode 100644 index a7f73a2d..00000000 --- a/postcss.config.js +++ /dev/null @@ -1,5 +0,0 @@ -export default { - plugins: { - '@tailwindcss/postcss': {}, - }, -} diff --git a/public/mix-manifest.json b/public/mix-manifest.json index 4963c088..35e18734 100755 --- a/public/mix-manifest.json +++ b/public/mix-manifest.json @@ -1,5 +1,5 @@ { - "/js/app.js": "/js/app.js?id=abaa45e6d1b47694248582d693424326", - "/css/app.css": "/css/app.css?id=370af16494f79a20bfbcd3b85f7250bd", - "/css/filament.css": "/css/filament.css?id=2710ed0ab403e5a91ba28487d688048e" + "/js/app.js": "/js/app.js?id=abaa45e6d1b47694248582d693424326", + "/css/app.css": "/css/app.css?id=370af16494f79a20bfbcd3b85f7250bd", + "/css/filament.css": "/css/filament.css?id=2710ed0ab403e5a91ba28487d688048e" } diff --git a/rector.php b/rector.php index 424c0eab..210dcac1 100644 --- a/rector.php +++ b/rector.php @@ -2,46 +2,54 @@ declare(strict_types=1); -use Rector\CodeQuality\Rector\Class_\CompleteDynamicPropertiesRector; +use Rector\Caching\ValueObject\Storage\FileCacheStorage; use Rector\Config\RectorConfig; -use Rector\EarlyReturn\Rector\Return_\ReturnBinaryOrToEarlyReturnRector; -use Rector\ValueObject\PhpVersion; use RectorLaravel\Rector\ClassMethod\AddGenericReturnTypeToRelationsRector; +use RectorLaravel\Rector\Empty_\EmptyToBlankAndFilledFuncRector; use RectorLaravel\Rector\MethodCall\AssertStatusToAssertMethodRector; -use RectorLaravel\Rector\StaticCall\DispatchToHelperFunctionsRector; use RectorLaravel\Rector\StaticCall\EloquentMagicMethodToQueryBuilderRector; use RectorLaravel\Set\LaravelLevelSetList; use RectorLaravel\Set\LaravelSetList; +use RectorLaravel\Set\LaravelSetProvider; return RectorConfig::configure() + ->withSetProviders(LaravelSetProvider::class) + ->withSets([ + LaravelLevelSetList::UP_TO_LARAVEL_120, + LaravelSetList::LARAVEL_ARRAY_STR_FUNCTION_TO_STATIC_CALL, + LaravelSetList::LARAVEL_CODE_QUALITY, + LaravelSetList::LARAVEL_COLLECTION, + LaravelSetList::LARAVEL_CONTAINER_STRING_TO_FULLY_QUALIFIED_NAME, + LaravelSetList::LARAVEL_ELOQUENT_MAGIC_METHOD_TO_QUERY_BUILDER, + LaravelSetList::LARAVEL_FACADE_ALIASES_TO_FULL_NAMES, + LaravelSetList::LARAVEL_FACTORIES, + LaravelSetList::LARAVEL_IF_HELPERS, + LaravelSetList::LARAVEL_LEGACY_FACTORIES_TO_CLASSES, + ]) + ->withComposerBased(laravel: true) + ->withCache( + cacheDirectory: '/tmp/rector', + cacheClass: FileCacheStorage::class, + ) ->withPaths([ __DIR__.'/app', __DIR__.'/app-modules', + __DIR__.'/bootstrap/app.php', __DIR__.'/config', + __DIR__.'/database', __DIR__.'/resources', __DIR__.'/tests', ]) - ->withSets([ - LaravelLevelSetList::UP_TO_LARAVEL_120, - LaravelSetList::LARAVEL_CODE_QUALITY, - LaravelSetList::LARAVEL_COLLECTION, - ]) ->withRules([ AddGenericReturnTypeToRelationsRector::class, AssertStatusToAssertMethodRector::class, - DispatchToHelperFunctionsRector::class, EloquentMagicMethodToQueryBuilderRector::class, + EmptyToBlankAndFilledFuncRector::class, ]) - ->withSkip([ - __DIR__.'/app/Listeners', - ReturnBinaryOrToEarlyReturnRector::class, - CompleteDynamicPropertiesRector::class, - ]) - ->withPhpVersion(PhpVersion::PHP_84) ->withPreparedSets( deadCode: true, codeQuality: true, + codingStyle: true, typeDeclarations: true, - earlyReturn: true, - strictBooleans: true, + earlyReturn: true ); diff --git a/resources/css/app.css b/resources/css/app.css index 5fb99ec6..b875c31c 100644 --- a/resources/css/app.css +++ b/resources/css/app.css @@ -1,12 +1,15 @@ -@import 'tailwindcss'; +@import "tailwindcss"; +@import "../../vendor/livewire/flux/dist/flux.css"; -@import './components/base.css'; -@import './components/forms.css'; -@import './components/forum.css'; -@import './components/header.css'; -@import './components/tag.css'; -@import './components/torchlight.css'; -@import './components/typography.css'; +@import "./components/base.css"; +@import "./components/forms.css"; +@import "./components/forum.css"; +@import "./components/header.css"; +@import "./components/tag.css"; +@import "./components/torchlight.css"; +@import "./components/typography.css"; + +@custom-variant dark (&:where(.dark, .dark *)); @plugin '@tailwindcss/typography'; @plugin '@tailwindcss/forms'; @@ -18,7 +21,7 @@ @source '../**/*.js'; :root { - --laravel: #F56857; + --laravel: #f56857; --livewire: #fb70a9; --inertia: #8b5cf6; --javascript: #f7df1e; @@ -53,7 +56,7 @@ --color-flag-green: #099170; --color-flag-red: #e21b30; --color-flag-yellow: #ffdc44; - --color-black: #161B22; + --color-black: #161b22; --color-gray-50: oklch(98.5% 0 0); --color-gray-100: oklch(96.7% 0.001 286.375); @@ -103,26 +106,42 @@ } @keyframes fade-in { - from { opacity: 0; } - to { opacity: 1; } + from { + opacity: 0; + } + to { + opacity: 1; + } } @keyframes marquee { - 100% { transform: translateY(-50%); } + 100% { + transform: translateY(-50%); + } } @keyframes spin-reverse { - to { transform: rotate(-360deg); } + to { + transform: rotate(-360deg); + } } @keyframes scroll { - from { transform: translateX(0); } - to { transform: translateX(-100%); } + from { + transform: translateX(0); + } + to { + transform: translateX(-100%); + } } @keyframes rotate { - 0% { transform: rotate(0deg) scale(10); } - 100% { transform: rotate(-360deg) scale(10); } + 0% { + transform: rotate(0deg) scale(10); + } + 100% { + transform: rotate(-360deg) scale(10); + } } @custom-variant dark (&:where(.dark, .dark *)); @@ -130,5 +149,5 @@ @property --border-angle { inherits: false; initial-value: 0deg; - syntax: ''; + syntax: ""; } diff --git a/resources/css/components/base.css b/resources/css/components/base.css index af4bf093..70085d33 100644 --- a/resources/css/components/base.css +++ b/resources/css/components/base.css @@ -1,59 +1,67 @@ html, -body { height: 100%; } -input { width: 100%; } +body { + height: 100%; +} +input { + width: 100%; +} [x-cloak] { - display: none !important; + display: none !important; } @font-face { - font-family: 'Rota'; - src: url('../../fonts/rota/rota-semibold-webfont.woff2') format('woff2'), - url('../../fonts/rota/rota-semibold-webfont.woff') format('woff'); + font-family: "Rota"; + src: + url("../../fonts/rota/rota-semibold-webfont.woff2") format("woff2"), + url("../../fonts/rota/rota-semibold-webfont.woff") format("woff"); font-weight: 600; font-style: swap; } @font-face { - font-family: 'Rota'; - src: url('../../fonts/rota/rota-regular-webfont.woff2') format('woff2'), - url('../../fonts/rota/rota-regular-webfont.woff') format('woff'); + font-family: "Rota"; + src: + url("../../fonts/rota/rota-regular-webfont.woff2") format("woff2"), + url("../../fonts/rota/rota-regular-webfont.woff") format("woff"); font-weight: 400; font-style: swap; } @font-face { - font-family: 'Rota'; - src: url('../../fonts/rota/rota-medium-webfont.woff2') format('woff2'), - url('../../fonts/rota/rota-medium-webfont.woff') format('woff'); + font-family: "Rota"; + src: + url("../../fonts/rota/rota-medium-webfont.woff2") format("woff2"), + url("../../fonts/rota/rota-medium-webfont.woff") format("woff"); font-weight: 500; font-style: swap; } @font-face { - font-family: 'Rota'; - src: url('../../fonts/rota/rota-bold-webfont.woff2') format('woff2'), - url('../../fonts/rota/rota-bold-webfont.woff') format('woff'); + font-family: "Rota"; + src: + url("../../fonts/rota/rota-bold-webfont.woff2") format("woff2"), + url("../../fonts/rota/rota-bold-webfont.woff") format("woff"); font-weight: 700; font-style: swap; } .hide-scroll::-webkit-scrollbar { - display: none; + display: none; } .prose iframe { - @apply w-full; + @apply w-full; } .animate-scroll-slow:nth-child(2) { - animation-duration: 40s; - animation-delay: 500ms; + animation-duration: 40s; + animation-delay: 500ms; } .animate-scroll-slow:nth-child(3) { - animation-duration: 50s; - animation-delay: 750ms; + animation-duration: 50s; + animation-delay: 750ms; } /* Fix style for the phone country input */ @@ -64,7 +72,7 @@ input { width: 100%; } /* Gradient */ .section-gradient { background: radial-gradient(60% 60% at 22% 0, #f4f4f5, #ffffff); - position: relative + position: relative; } .dark .section-gradient { diff --git a/resources/css/components/forms.css b/resources/css/components/forms.css index 76e2396a..b14025b8 100644 --- a/resources/css/components/forms.css +++ b/resources/css/components/forms.css @@ -1,18 +1,18 @@ .iti { - display: block; + display: block; } .iti__selected-flag { - padding: 0 10px; + padding: 0 10px; } -input[type='number'] { - -moz-appearance: textfield; /* Firefox */ +input[type="number"] { + -moz-appearance: textfield; /* Firefox */ } input::-webkit-outer-spin-button, input::-webkit-inner-spin-button { - /* display: none; <- Crashes Chrome on hover */ - -webkit-appearance: none; - margin: 0; /* <-- Apparently some margin are still there even though it's hidden */ + /* display: none; <- Crashes Chrome on hover */ + -webkit-appearance: none; + margin: 0; /* <-- Apparently some margin are still there even though it's hidden */ } diff --git a/resources/css/components/forum.css b/resources/css/components/forum.css index 27468651..ae76d762 100644 --- a/resources/css/components/forum.css +++ b/resources/css/components/forum.css @@ -1,5 +1,7 @@ #leaderboard { - *, *::before, *::after { + *, + *::before, + *::after { box-sizing: border-box; transform-style: preserve-3d; } diff --git a/resources/css/components/tag.css b/resources/css/components/tag.css index 9cea98ca..b67544e8 100644 --- a/resources/css/components/tag.css +++ b/resources/css/components/tag.css @@ -1,101 +1,101 @@ .brand-laravel { - color: var(--laravel); + color: var(--laravel); } .brand-alpinejs { - color: var(--alpinejs); + color: var(--alpinejs); } .brand-javascript { - color: var(--javascript); + color: var(--javascript); } .brand-typesript { - color: var(--typesript); + color: var(--typesript); } .brand-react { - color: var(--react); + color: var(--react); } .brand-vue-js { - color: var(--vue-js); + color: var(--vue-js); } .brand-php { - color: var(--php); + color: var(--php); } .brand-digital-ocean { - color: var(--digital-ocean); + color: var(--digital-ocean); } .brand-forge { - color: var(--forge); + color: var(--forge); } .brand-packages { - color: var(--packages); + color: var(--packages); } .brand-outils { - color: var(--outils); + color: var(--outils); } .brand-tailwindcss { - color: var(--tailwindcss); + color: var(--tailwindcss); } .brand-design { - color: var(--design); + color: var(--design); } .brand-open-source { - color: var(--open-source); + color: var(--open-source); } .brand-freelance { - color: var(--freelance); + color: var(--freelance); } .brand-salaire { - color: var(--salaire); + color: var(--salaire); } .brand-projets { - color: var(--projets); + color: var(--projets); } .brand-entrepreneuriat { - color: var(--entrepreneuriat); + color: var(--entrepreneuriat); } .brand-paiement-en-ligne { - color: var(--paiement-en-ligne); + color: var(--paiement-en-ligne); } .brand-branding { - color: var(--branding); + color: var(--branding); } .brand-applications { - color: var(--applications); + color: var(--applications); } .brand-event { - color: var(--event); + color: var(--event); } .brand-tutoriel { - color: var(--tutoriel); + color: var(--tutoriel); } .brand-communaute { - color: var(--communaute); + color: var(--communaute); } .brand-astuces { - color: var(--astuces); + color: var(--astuces); } .is-channel { diff --git a/resources/css/components/torchlight.css b/resources/css/components/torchlight.css index de7addb8..5f544cb8 100644 --- a/resources/css/components/torchlight.css +++ b/resources/css/components/torchlight.css @@ -65,17 +65,17 @@ pre code.torchlight .summary-caret { .torchlight .summary-caret-empty::after, .torchlight details .summary-caret-middle::after, .torchlight details .summary-caret-end::after { - content: ' '; + content: " "; } /* Show a minus sign when the block is open. */ .torchlight details[open] .summary-caret-start::after { - content: '-'; + content: "-"; } /* And a plus sign when the block is closed. */ .torchlight details:not([open]) .summary-caret-start::after { - content: '+'; + content: "+"; } /* Hide the [...] indicator when open. */ diff --git a/resources/css/components/typography.css b/resources/css/components/typography.css index 2e140ad5..1256ec2f 100644 --- a/resources/css/components/typography.css +++ b/resources/css/components/typography.css @@ -2,7 +2,10 @@ border-radius: theme(borderRadius.lg); } -.prose h1, .prose h2, .prose h3, .prose h4 { +.prose h1, +.prose h2, +.prose h3, +.prose h4 { font-family: Rota, sans-serif; } @@ -18,13 +21,16 @@ content: none; } -.prose pre, .prose code, .prose p > code { +.prose pre, +.prose code, +.prose p > code { font-weight: theme(fontWeight.medium); font-family: "Geist Mono", monospace; color: theme(colors.amber.500); } -.prose li strong, .prose strong { +.prose li strong, +.prose strong { color: theme(colors.gray.700); font-weight: 400; } diff --git a/resources/css/filament/admin/custom-theme.css b/resources/css/filament/admin/custom-theme.css index a78b547e..40d95bd5 100644 --- a/resources/css/filament/admin/custom-theme.css +++ b/resources/css/filament/admin/custom-theme.css @@ -19,7 +19,7 @@ .fi-icon-btn { @apply size-10 text-gray-400 rounded-lg -translate-x-2 translate-y-px hover:bg-gray-100 dark:text-gray-500 dark:hover:bg-gray-800; svg { - display:none; + display: none; } &::after { content: url('data:image/svg+xml;utf8,'); @@ -52,7 +52,8 @@ } } - .fi-sidebar-item-button, .fi-sidebar-group-button { + .fi-sidebar-item-button, + .fi-sidebar-group-button { @apply bg-transparent px-5; } } diff --git a/resources/css/filament/admin/theme.css b/resources/css/filament/admin/theme.css index af8a48f6..20d1c33b 100644 --- a/resources/css/filament/admin/theme.css +++ b/resources/css/filament/admin/theme.css @@ -1,4 +1,4 @@ -@import '../../../../vendor/filament/filament/resources/css/theme.css'; +@import "../../../../vendor/filament/filament/resources/css/theme.css"; @source '../../../../app/Filament'; @source '../../../../resources/views/filament'; @@ -14,20 +14,20 @@ } *::-webkit-scrollbar-thumb { - background-color: theme('colors.gray.100'); + background-color: theme("colors.gray.100"); border-radius: 8px; } *::-webkit-scrollbar-thumb:hover { - background-color: theme('colors.gray.300'); + background-color: theme("colors.gray.300"); } .dark *::-webkit-scrollbar-thumb { - background-color: theme('colors.gray.700'); + background-color: theme("colors.gray.700"); } .dark *::-webkit-scrollbar-thumb:hover { - background-color: theme('colors.gray.900'); + background-color: theme("colors.gray.900"); } .hide-scroll::-webkit-scrollbar { diff --git a/resources/js/app.js b/resources/js/app.js index e51fa292..72423634 100644 --- a/resources/js/app.js +++ b/resources/js/app.js @@ -1,66 +1,69 @@ -import { Livewire, Alpine } from '../../vendor/livewire/livewire/dist/livewire.esm' -import '../../vendor/laravelcm/livewire-slide-overs/resources/js/slide-over'; +import { + Livewire, + Alpine, +} from "../../vendor/livewire/livewire/dist/livewire.esm"; +import "../../vendor/laravelcm/livewire-slide-overs/resources/js/slide-over"; -import '@tailwindplus/elements' -import intersect from '@alpinejs/intersect' -import Tooltip from '@ryangjchandler/alpine-tooltip' -import collapse from '@alpinejs/collapse' +import "@tailwindplus/elements"; +import intersect from "@alpinejs/intersect"; +import Tooltip from "@ryangjchandler/alpine-tooltip"; +import collapse from "@alpinejs/collapse"; -import './utils/helpers' -import './utils/scrollspy' -import './utils/clipboard' +import "./utils/helpers"; +import "./utils/scrollspy"; +import "./utils/clipboard"; -Alpine.plugin(intersect) -Alpine.plugin(Tooltip) -Alpine.plugin(collapse) +Alpine.plugin(intersect); +Alpine.plugin(Tooltip); +Alpine.plugin(collapse); -window.Alpine = Alpine +window.Alpine = Alpine; -document.addEventListener('alpine:init', () => { +document.addEventListener("alpine:init", () => { const theme = - localStorage.getItem('theme') ?? + localStorage.getItem("theme") ?? getComputedStyle(document.documentElement).getPropertyValue( - '--default-theme-mode', - ) + "--default-theme-mode", + ); window.Alpine.store( - 'theme', - theme === 'dark' || - (theme === 'system' && - window.matchMedia('(prefers-color-scheme: dark)').matches) - ? 'dark' - : 'light', - ) + "theme", + theme === "dark" || + (theme === "system" && + window.matchMedia("(prefers-color-scheme: dark)").matches) + ? "dark" + : "light", + ); - window.addEventListener('theme-changed', (event) => { - let theme = event.detail + window.addEventListener("theme-changed", (event) => { + let theme = event.detail; - localStorage.setItem('theme', theme) + localStorage.setItem("theme", theme); - if (theme === 'system') { - theme = window.matchMedia('(prefers-color-scheme: dark)').matches - ? 'dark' - : 'light' + if (theme === "system") { + theme = window.matchMedia("(prefers-color-scheme: dark)").matches + ? "dark" + : "light"; } - window.Alpine.store('theme', theme) - }) + window.Alpine.store("theme", theme); + }); window - .matchMedia('(prefers-color-scheme: dark)') - .addEventListener('change', (event) => { - if (localStorage.getItem('theme') === 'system') { - window.Alpine.store('theme', event.matches ? 'dark' : 'light') + .matchMedia("(prefers-color-scheme: dark)") + .addEventListener("change", (event) => { + if (localStorage.getItem("theme") === "system") { + window.Alpine.store("theme", event.matches ? "dark" : "light"); } - }) + }); window.Alpine.effect(() => { - const theme = window.Alpine.store('theme') + const theme = window.Alpine.store("theme"); - theme === 'dark' - ? document.documentElement.classList.add('dark') - : document.documentElement.classList.remove('dark') - }) -}) + theme === "dark" + ? document.documentElement.classList.add("dark") + : document.documentElement.classList.remove("dark"); + }); +}); -Livewire.start() +Livewire.start(); diff --git a/resources/js/utils/clipboard.js b/resources/js/utils/clipboard.js index bd8987d4..bd8d6206 100644 --- a/resources/js/utils/clipboard.js +++ b/resources/js/utils/clipboard.js @@ -1,27 +1,27 @@ // Copy to Clipboard. -let codeBlocks = document.querySelectorAll('#content pre') +let codeBlocks = document.querySelectorAll("#content pre"); codeBlocks.forEach((element, key) => { - let wrapper = document.createElement('div') - wrapper.classList.add('relative', 'code-block') + let wrapper = document.createElement("div"); + wrapper.classList.add("relative", "code-block"); - element.parentNode.insertBefore(wrapper, element) - wrapper.appendChild(element) + element.parentNode.insertBefore(wrapper, element); + wrapper.appendChild(element); - let codeElement = element.querySelector('code') - codeElement.id = `clipText-${key}` + let codeElement = element.querySelector("code"); + codeElement.id = `clipText-${key}`; - // Copy to clipboard button. - const copyToClipboardContainer = document.createElement('div') + // Copy to clipboard button. + const copyToClipboardContainer = document.createElement("div"); - copyToClipboardContainer.innerHTML = ` + copyToClipboardContainer.innerHTML = `
{ setTimeout(function() { @@ -50,11 +50,11 @@ codeBlocks.forEach((element, key) => {
- ` + `; - copyToClipboardContainer.setAttribute('aria-label', 'Copy to Clipboard') - copyToClipboardContainer.setAttribute('title', 'Copy to Clipboard') - copyToClipboardContainer.classList.add('copyBtn') + copyToClipboardContainer.setAttribute("aria-label", "Copy to Clipboard"); + copyToClipboardContainer.setAttribute("title", "Copy to Clipboard"); + copyToClipboardContainer.classList.add("copyBtn"); - wrapper.append(copyToClipboardContainer) -}) + wrapper.append(copyToClipboardContainer); +}); diff --git a/resources/js/utils/helpers.js b/resources/js/utils/helpers.js index ddb2e2f8..9ef14547 100644 --- a/resources/js/utils/helpers.js +++ b/resources/js/utils/helpers.js @@ -1,61 +1,64 @@ const share = function () { - function popupCenter(url, title, width, height) { - let popupWidth = width || 640 - let popupHeight = height || 440 - let windowLeft = window.screenLeft || window.screenX - let windowTop = window.screenTop || window.screenY - let windowWidth = window.innerWidth || document.documentElement.clientWidth - let windowHeight = window.innerHeight || document.documentElement.clientHeight - let popupLeft = windowLeft + windowWidth / 2 - popupWidth / 2 - let popupTop = windowTop + windowHeight / 2 - popupHeight / 2 - window.open( - url, - title, - 'scrollbars=yes, width=' + - popupWidth + - ', height=' + - popupHeight + - ', top=' + - popupTop + - ', left=' + - popupLeft, - ) - } + function popupCenter(url, title, width, height) { + let popupWidth = width || 640; + let popupHeight = height || 440; + let windowLeft = window.screenLeft || window.screenX; + let windowTop = window.screenTop || window.screenY; + let windowWidth = window.innerWidth || document.documentElement.clientWidth; + let windowHeight = + window.innerHeight || document.documentElement.clientHeight; + let popupLeft = windowLeft + windowWidth / 2 - popupWidth / 2; + let popupTop = windowTop + windowHeight / 2 - popupHeight / 2; + window.open( + url, + title, + "scrollbars=yes, width=" + + popupWidth + + ", height=" + + popupHeight + + ", top=" + + popupTop + + ", left=" + + popupLeft, + ); + } - let twitter = document.querySelector('.share_twitter') - if (twitter) { - twitter.addEventListener('click', function (e) { - e.preventDefault() - let url = this.getAttribute('data-url') - let shareUrl = - 'https://twitter.com/intent/tweet?text=' + - encodeURIComponent(document.title) + - '&via=laravelcm' + - '&url=' + - encodeURIComponent(url) - popupCenter(shareUrl, 'Partager sur Twitter') - }) - } + let twitter = document.querySelector(".share_twitter"); + if (twitter) { + twitter.addEventListener("click", function (e) { + e.preventDefault(); + let url = this.getAttribute("data-url"); + let shareUrl = + "https://twitter.com/intent/tweet?text=" + + encodeURIComponent(document.title) + + "&via=laravelcm" + + "&url=" + + encodeURIComponent(url); + popupCenter(shareUrl, "Partager sur Twitter"); + }); + } - let facebook = document.querySelector('.share_facebook') - if (facebook) { - facebook.addEventListener('click', function (e) { - e.preventDefault() - let url = this.getAttribute('data-url') - let shareUrl = 'https://facebook.com/sharer/sharer.php?u=' + encodeURIComponent(url) - popupCenter(shareUrl, 'Partager sur Facebook') - }) - } + let facebook = document.querySelector(".share_facebook"); + if (facebook) { + facebook.addEventListener("click", function (e) { + e.preventDefault(); + let url = this.getAttribute("data-url"); + let shareUrl = + "https://facebook.com/sharer/sharer.php?u=" + encodeURIComponent(url); + popupCenter(shareUrl, "Partager sur Facebook"); + }); + } - let linkedin = document.querySelector('.share_linkedin') - if (linkedin) { - linkedin.addEventListener('click', function (e) { - e.preventDefault() - let url = this.getAttribute('data-url') - let shareUrl = 'https://www.linkedin.com/shareArticle?url=' + encodeURIComponent(url) - popupCenter(shareUrl, 'Partager sur LinkedIn') - }) - } -} + let linkedin = document.querySelector(".share_linkedin"); + if (linkedin) { + linkedin.addEventListener("click", function (e) { + e.preventDefault(); + let url = this.getAttribute("data-url"); + let shareUrl = + "https://www.linkedin.com/shareArticle?url=" + encodeURIComponent(url); + popupCenter(shareUrl, "Partager sur LinkedIn"); + }); + } +}; -share() +share(); diff --git a/resources/js/utils/scrollspy.js b/resources/js/utils/scrollspy.js index ae49ddd7..cf6f4e6c 100644 --- a/resources/js/utils/scrollspy.js +++ b/resources/js/utils/scrollspy.js @@ -1,30 +1,33 @@ -let tableOfContents = document.querySelector('.toc') +let tableOfContents = document.querySelector(".toc"); // console.log(tableOfContents) if (tableOfContents) { - let headers = [].slice - .call(document.querySelectorAll('.anchor')) - .map(({ name, offsetTop: position }) => ({ name, position })) - .reverse() + let headers = [].slice + .call(document.querySelectorAll(".anchor")) + .map(({ name, offsetTop: position }) => ({ name, position })) + .reverse(); - highlightLink(headers[headers.length - 1].id) + highlightLink(headers[headers.length - 1].id); - window.addEventListener('scroll', (event) => { - let position = (document.documentElement.scrollTop || document.body.scrollTop) + 34 - let current = headers.filter((header) => header.position < position)[0] || headers[headers.length - 1] - let active = document.querySelector('.toc .active') + window.addEventListener("scroll", (event) => { + let position = + (document.documentElement.scrollTop || document.body.scrollTop) + 34; + let current = + headers.filter((header) => header.position < position)[0] || + headers[headers.length - 1]; + let active = document.querySelector(".toc .active"); - if (active) { - active.classList.remove('active') - } + if (active) { + active.classList.remove("active"); + } - highlightLink(current.name) - }) + highlightLink(current.name); + }); } function highlightLink(name) { - let highlight = document.querySelector(`.toc a[href="#${name}"]`) + let highlight = document.querySelector(`.toc a[href="#${name}"]`); - if (highlight) { - highlight.parentNode.classList.add('active') - } + if (highlight) { + highlight.parentNode.classList.add("active"); + } } diff --git a/resources/views/components/join-sponsors.blade.php b/resources/views/components/join-sponsors.blade.php index 1a5501fe..2d2bdad5 100644 --- a/resources/views/components/join-sponsors.blade.php +++ b/resources/views/components/join-sponsors.blade.php @@ -6,8 +6,8 @@

{{ $title }}

-
-
+ -
+
+