diff --git a/.env.dusk.testing b/.env.dusk.testing deleted file mode 100644 index 756f4074..00000000 --- a/.env.dusk.testing +++ /dev/null @@ -1,14 +0,0 @@ -APP_ENV=testing -APP_DEBUG=true -APP_KEY=base64:6DJhvZLVjE6dD4Cqrteh+6Z5vZlG+v/soCKcDHLOAH0= -APP_URL=http://localhost:8000 -APP_LONGURL=localhost -APP_SHORTURL=local - -DB_CONNECTION=travis - -CACHE_DRIVER=array -SESSION_DRIVER=file -QUEUE_DRIVER=sync - -SCOUT_DRIVER=pgsql diff --git a/.env.example b/.env.example index 43a5f376..4eb61db5 100644 --- a/.env.example +++ b/.env.example @@ -4,8 +4,6 @@ APP_KEY= APP_DEBUG=true APP_TIMEZONE=UTC APP_URL=https://example.com -APP_LONGURL=example.com -APP_SHORTURL=examp.le APP_LOCALE=en APP_FALLBACK_LOCALE=en diff --git a/.env.github b/.env.github deleted file mode 100644 index 0ef2b89b..00000000 --- a/.env.github +++ /dev/null @@ -1,70 +0,0 @@ -APP_NAME=Laravel -APP_ENV=testing -APP_KEY=SomeRandomString # Leave this -APP_DEBUG=false -APP_LOG_LEVEL=warning - -DB_CONNECTION=pgsql -DB_HOST=127.0.0.1 -DB_PORT=5432 -DB_DATABASE=jbukdev_testing -DB_USERNAME=postgres -DB_PASSWORD=postgres - -BROADCAST_DRIVER=log -CACHE_DRIVER=file -SESSION_DRIVER=file -QUEUE_DRIVER=sync - -REDIS_HOST=127.0.0.1 -REDIS_PASSWORD=null -REDIS_PORT=6379 - -MAIL_DRIVER=smtp -MAIL_HOST=smtp.mailtrap.io -MAIL_PORT=2525 -MAIL_USERNAME=null -MAIL_PASSWORD=null -MAIL_ENCRYPTION=null - -PUSHER_APP_ID= -PUSHER_APP_KEY= -PUSHER_APP_SECRET= - -AWS_S3_KEY=your-key -AWS_S3_SECRET=your-secret -AWS_S3_REGION=region -AWS_S3_BUCKET=your-bucket -AWS_S3_URL=https://xxxxxxx.s3-region.amazonaws.com - -APP_URL=https://example.com # This one is necessary -APP_LONGURL=example.com -APP_SHORTURL=examp.le - -ADMIN_USER=admin # pick something better, this is used for `/admin` -ADMIN_PASS=password -DISPLAY_NAME="Joe Bloggs" # This is used for example in the header and titles - -TWITTER_CONSUMER_KEY= -TWITTER_CONSUMER_SECRET= -TWITTER_ACCESS_TOKEN= -TWITTER_ACCESS_TOKEN_SECRET= - -SCOUT_DRIVER=database -SCOUT_QUEUE=false - -PIWIK=false - -FATHOM_ID= - -APP_TIMEZONE=UTC -APP_LANG=en -APP_LOG=daily -SECURE_SESSION_COOKIE=true - -LOG_SLACK_WEBHOOK_URL= -FLARE_KEY= - -FONT_LINK= - -BRIDGY_MASTODON_TOKEN= diff --git a/.gitattributes b/.gitattributes index fcb21d39..78f41d7a 100644 --- a/.gitattributes +++ b/.gitattributes @@ -5,7 +5,3 @@ *.html diff=html *.md diff=markdown *.php diff=php - -/.github export-ignore -CHANGELOG.md export-ignore -.styleci.yml export-ignore diff --git a/.github/dependabot.yml b/.github/dependabot.yml deleted file mode 100644 index 3ebccbd3..00000000 --- a/.github/dependabot.yml +++ /dev/null @@ -1,17 +0,0 @@ -version: 2 - -updates: - - package-ecosystem: "composer" - directory: "/" - schedule: - interval: "daily" - - - package-ecosystem: "npm" - directory: "/" - schedule: - interval: "daily" - - - package-ecosystem: "github-actions" - directory: "/" - schedule: - interval: "weekly" diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml deleted file mode 100644 index f66a77b4..00000000 --- a/.github/workflows/deploy.yml +++ /dev/null @@ -1,144 +0,0 @@ -name: Deploy - -on: - workflow_dispatch: - release: - types: [published] - -jobs: - deploy: - name: Deploy - runs-on: ubuntu-latest - environment: Hetzner - env: - repository: 'jonnybarnes/jonnybarnes.uk' - newReleaseName: '${{ github.run_id }}' - - steps: - - name: šŸŒ Set Environment Variables - run: | - echo "releasesDir=${{ secrets.DEPLOYMENT_BASE_DIR }}/releases" >> $GITHUB_ENV - echo "persistentDir=${{ secrets.DEPLOYMENT_BASE_DIR }}/persistent" >> $GITHUB_ENV - echo "currentDir=${{ secrets.DEPLOYMENT_BASE_DIR }}/current" >> $GITHUB_ENV - - name: šŸŒŽ Set Environment Variables Part 2 - run: | - echo "newReleaseDir=${{ env.releasesDir }}/${{ env.newReleaseName }}" >> $GITHUB_ENV - - name: šŸ”„ Clone Repository - uses: appleboy/ssh-action@master - with: - host: ${{ secrets.DEPLOYMENT_HOST }} - port: ${{ secrets.DEPLOYMENT_PORT }} - username: ${{ secrets.DEPLOYMENT_USER }} - key: ${{ secrets.DEPLOYMENT_KEY }} - script: | - [ -d ${{ env.releasesDir }} ] || mkdir ${{ env.releasesDir }} - [ -d ${{ env.persistentDir }} ] || mkdir ${{ env.persistentDir }} - [ -d ${{ env.persistentDir }}/storage ] || mkdir ${{ env.persistentDir }}/storage - - cd ${{ env.releasesDir }} - - # Create new release directory - mkdir ${{ env.newReleaseDir }} - - # Clone app - git clone --depth 1 --branch ${{ github.ref_name }} https://github.com/${{ env.repository }} ${{ env.newReleaseName }} - - # Mark release - cd ${{ env.newReleaseDir }} - echo "${{ env.newReleaseName }}" > public/release-name.txt - - # Fix cache directory permissions - sudo chown -R ${{ secrets.HTTP_USER }}:${{ secrets.HTTP_USER }} bootstrap/cache - - - name: šŸŽµ Run Composer - uses: appleboy/ssh-action@master - with: - host: ${{ secrets.DEPLOYMENT_HOST }} - port: ${{ secrets.DEPLOYMENT_PORT }} - username: ${{ secrets.DEPLOYMENT_USER }} - key: ${{ secrets.DEPLOYMENT_KEY }} - script: | - cd ${{ env.newReleaseDir }} - composer install --prefer-dist --no-scripts --no-dev --no-progress --optimize-autoloader --quiet --no-interaction - - - name: šŸ”— Update Symlinks - uses: appleboy/ssh-action@master - with: - host: ${{ secrets.DEPLOYMENT_HOST }} - port: ${{ secrets.DEPLOYMENT_PORT }} - username: ${{ secrets.DEPLOYMENT_USER }} - key: ${{ secrets.DEPLOYMENT_KEY }} - script: | - # Import the environment config - cd ${{ env.newReleaseDir }}; - ln -nfs ${{ secrets.DEPLOYMENT_BASE_DIR }}/.env .env; - - # Remove the storage directory and replace with persistent data - rm -rf ${{ env.newReleaseDir }}/storage; - cd ${{ env.newReleaseDir }}; - ln -nfs ${{ secrets.DEPLOYMENT_BASE_DIR }}/persistent/storage storage; - - # Remove the public/profile-images directory and replace with persistent data - rm -rf ${{ env.newReleaseDir }}/public/assets/profile-images; - cd ${{ env.newReleaseDir }}; - ln -nfs ${{ secrets.DEPLOYMENT_BASE_DIR }}/persistent/profile-images public/assets/profile-images; - - # Add the persistent files data - cd ${{ env.newReleaseDir }}; - ln -nfs ${{ secrets.DEPLOYMENT_BASE_DIR }}/persistent/files public/files; - - # Add the persistent fonts data - cd ${{ env.newReleaseDir }}; - ln -nfs ${{ secrets.DEPLOYMENT_BASE_DIR }}/persistent/fonts public/fonts; - - - name: ✨ Optimize Installation - uses: appleboy/ssh-action@master - with: - host: ${{ secrets.DEPLOYMENT_HOST }} - port: ${{ secrets.DEPLOYMENT_PORT }} - username: ${{ secrets.DEPLOYMENT_USER }} - key: ${{ secrets.DEPLOYMENT_KEY }} - script: | - cd ${{ env.newReleaseDir }}; - sudo runuser -u ${{ secrets.HTTP_USER }} -- php artisan clear-compiled; - - - name: šŸ™ˆ Migrate database - uses: appleboy/ssh-action@master - with: - host: ${{ secrets.DEPLOYMENT_HOST }} - port: ${{ secrets.DEPLOYMENT_PORT }} - username: ${{ secrets.DEPLOYMENT_USER }} - key: ${{ secrets.DEPLOYMENT_KEY }} - script: | - cd ${{ env.newReleaseDir }} - sudo runuser -u ${{ secrets.HTTP_USER }} -- php artisan migrate --force - - - name: šŸ™ Bless release - uses: appleboy/ssh-action@master - with: - host: ${{ secrets.DEPLOYMENT_HOST }} - port: ${{ secrets.DEPLOYMENT_PORT }} - username: ${{ secrets.DEPLOYMENT_USER }} - key: ${{ secrets.DEPLOYMENT_KEY }} - script: | - ln -nfs ${{ env.newReleaseDir }} ${{ env.currentDir }}; - cd ${{ env.newReleaseDir }} - sudo runuser -u ${{ secrets.HTTP_USER }} -- php artisan horizon:terminate - sudo runuser -u ${{ secrets.HTTP_USER }} -- php artisan config:cache - sudo runuser -u ${{ secrets.HTTP_USER }} -- php artisan event:cache - sudo runuser -u ${{ secrets.HTTP_USER }} -- php artisan route:cache - sudo runuser -u ${{ secrets.HTTP_USER }} -- php artisan view:cache - - sudo systemctl restart php-fpm.service - sudo systemctl restart jbuk-horizon.service - - - name: 🚾 Clean up old releases - uses: appleboy/ssh-action@master - with: - host: ${{ secrets.DEPLOYMENT_HOST }} - port: ${{ secrets.DEPLOYMENT_PORT }} - username: ${{ secrets.DEPLOYMENT_USER }} - key: ${{ secrets.DEPLOYMENT_KEY }} - script: | - fd '.+' ${{ env.releasesDir }} -d 1 | head -n -3 | xargs -d "\n" -I'{}' sudo chown -R ${{ secrets.DEPLOYMENT_USER }}:${{ secrets.DEPLOYMENT_USER }} {} - fd '.+' ${{ env.releasesDir }} -d 1 | head -n -3 | xargs -d "\n" -I'{}' rm -rf {} diff --git a/.github/workflows/phpunit.yml b/.github/workflows/phpunit.yml deleted file mode 100644 index 29afebb9..00000000 --- a/.github/workflows/phpunit.yml +++ /dev/null @@ -1,65 +0,0 @@ -name: PHP Unit - -on: - pull_request: - -jobs: - phpunit: - runs-on: ubuntu-latest - - name: PHPUnit test suite - - services: - postgres: - image: postgres:latest - env: - POSTGRES_USER: postgres - POSTGRES_PASSWORD: postgres - POSTGRES_DB: jbukdev_testing - ports: - - 5432:5432 - - steps: - - name: Checkout code - uses: actions/checkout@v4 - - - name: Setup PHP - uses: shivammathur/setup-php@v2 - with: - php-version: '8.3' - extensions: mbstring, intl, phpredis, imagick - coverage: xdebug - tools: phpunit - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - - - name: Copy .env - run: php -r "file_exists('.env') || copy('.env.github', '.env');" - - - name: Get Composer Cache Directory - id: composer-cache - run: | - echo "::set-output name=dir::$(composer config cache-files-dir)" - - - name: Cache composer dependencies - uses: actions/cache@v4 - with: - path: ${{ steps.composer-cache.outputs.dir }} - key: ${{ runner.os }}-php-8.3-composer-${{ hashFiles('**/composer.lock') }} - restore-keys: | - ${{ runner.os }}-php-8.3-composer- - - - name: Install Composer Dependencies - run: composer install --quiet --no-ansi --no-interaction --no-progress - - - name: Generate Key - run: php artisan key:generate - - - name: Setup Directory Permissions - run: chmod -R 777 storage bootstrap/cache - - - name: Setup Database - run: php artisan migrate - - - name: Execute PHPUnit Tests - run: vendor/bin/phpunit diff --git a/.github/workflows/pint.yml b/.github/workflows/pint.yml deleted file mode 100644 index 9b0956ad..00000000 --- a/.github/workflows/pint.yml +++ /dev/null @@ -1,38 +0,0 @@ -name: Laravel Pint - -on: - pull_request: - -jobs: - pint: - runs-on: ubuntu-latest - - name: Laravel Pint - - steps: - - name: Checkout code - uses: actions/checkout@v4 - - - name: Setup PHP with pecl extensions - uses: shivammathur/setup-php@v2 - with: - php-version: '8.2' - - - name: Get Composer Cache Directory - id: composer-cache - run: | - echo "::set-output name=dir::$(composer config cache-files-dir)" - - - name: Cache composer dependencies - uses: actions/cache@v4 - with: - path: ${{ steps.composer-cache.outputs.dir }} - key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.lock') }} - restore-keys: | - ${{ runner.os }}-composer- - - - name: Install Composer Dependencies - run: composer install --quiet --no-ansi --no-interaction --no-progress - - - name: Check Files with Laravel Pint - run: vendor/bin/pint --test diff --git a/.gitignore b/.gitignore index 5f786e27..a0c2459a 100644 --- a/.gitignore +++ b/.gitignore @@ -4,7 +4,6 @@ /public/coverage /public/hot /public/files -/public/fonts /public/storage /storage/*.key /vendor diff --git a/.phpactor.json b/.phpactor.json deleted file mode 100644 index 028bb10a..00000000 --- a/.phpactor.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "$schema": "/Users/jonny/git/phpactor/phpactor.schema.json", - "language_server_phpstan.enabled": false, - "language_server_psalm.enabled": true -} diff --git a/.styleci.yml b/.styleci.yml deleted file mode 100644 index 9daadf16..00000000 --- a/.styleci.yml +++ /dev/null @@ -1,9 +0,0 @@ -php: - preset: laravel - disabled: - - no_unused_imports - finder: - not-name: - - index.php -js: true -css: true diff --git a/app/Console/Commands/MigratePlaceDataFromPostgis.php b/app/Console/Commands/MigratePlaceDataFromPostgis.php index e0026150..8d5d2c92 100644 --- a/app/Console/Commands/MigratePlaceDataFromPostgis.php +++ b/app/Console/Commands/MigratePlaceDataFromPostgis.php @@ -8,8 +8,6 @@ use Illuminate\Support\Facades\DB; /** * @codeCoverageIgnore - * - * @psalm-suppress UnusedClass */ class MigratePlaceDataFromPostgis extends Command { diff --git a/app/Console/Commands/ParseCachedWebMentions.php b/app/Console/Commands/ParseCachedWebMentions.php index 010a086a..a6b29176 100644 --- a/app/Console/Commands/ParseCachedWebMentions.php +++ b/app/Console/Commands/ParseCachedWebMentions.php @@ -9,9 +9,6 @@ use Illuminate\Console\Command; use Illuminate\Contracts\Filesystem\FileNotFoundException; use Illuminate\FileSystem\FileSystem; -/** - * @psalm-suppress UnusedClass - */ class ParseCachedWebMentions extends Command { /** diff --git a/app/Console/Commands/ReDownloadWebMentions.php b/app/Console/Commands/ReDownloadWebMentions.php index b29e7da8..c6452ba9 100644 --- a/app/Console/Commands/ReDownloadWebMentions.php +++ b/app/Console/Commands/ReDownloadWebMentions.php @@ -8,9 +8,6 @@ use App\Jobs\DownloadWebMention; use App\Models\WebMention; use Illuminate\Console\Command; -/** - * @psalm-suppress UnusedClass - */ class ReDownloadWebMentions extends Command { /** diff --git a/app/Exceptions/InvalidTokenScopeException.php b/app/Exceptions/InvalidTokenScopeException.php new file mode 100644 index 00000000..5966bccd --- /dev/null +++ b/app/Exceptions/InvalidTokenScopeException.php @@ -0,0 +1,7 @@ +user(); // RP Entity i.e. the application $rpEntity = PublicKeyCredentialRpEntity::create( - config('app.name'), - config('url.longurl'), + name: config('app.name'), + id: config('app.url'), ); // User Entity $userEntity = PublicKeyCredentialUserEntity::create( - $user->name, - (string) $user->id, - $user->name, + name: $user->name, + id: (string) $user->id, + displayName: $user->name, ); // Challenge @@ -85,25 +87,38 @@ class PasskeysController extends Controller $authenticatorSelectionCriteria = AuthenticatorSelectionCriteria::create( userVerification: AuthenticatorSelectionCriteria::USER_VERIFICATION_REQUIREMENT_REQUIRED, residentKey: AuthenticatorSelectionCriteria::RESIDENT_KEY_REQUIREMENT_REQUIRED, - requireResidentKey: true, ); - $options = PublicKeyCredentialCreationOptions::create( - $rpEntity, - $userEntity, - $challenge, - $pubKeyCredParams, + $publicKeyCredentialCreationOptions = PublicKeyCredentialCreationOptions::create( + rp: $rpEntity, + user: $userEntity, + challenge: $challenge, + pubKeyCredParams: $pubKeyCredParams, authenticatorSelection: $authenticatorSelectionCriteria, attestation: PublicKeyCredentialCreationOptions::ATTESTATION_CONVEYANCE_PREFERENCE_NONE ); - $options = json_encode($options, JSON_THROW_ON_ERROR); + $attestationStatementSupportManager = new AttestationStatementSupportManager; + $attestationStatementSupportManager->add(new NoneAttestationStatementSupport); + $webauthnSerializerFactory = new WebauthnSerializerFactory( + attestationStatementSupportManager: $attestationStatementSupportManager + ); + $webauthnSerializer = $webauthnSerializerFactory->create(); + $publicKeyCredentialCreationOptions = $webauthnSerializer->serialize( + data: $publicKeyCredentialCreationOptions, + format: 'json' + ); - session(['create_options' => $options]); + $request->session()->put('create_options', $publicKeyCredentialCreationOptions); - return JsonResponse::fromJsonString($options); + return JsonResponse::fromJsonString($publicKeyCredentialCreationOptions); } + /** + * @throws Throwable + * @throws WebauthnException + * @throws \JsonException + */ public function create(Request $request): JsonResponse { /** @var User $user */ @@ -111,17 +126,17 @@ class PasskeysController extends Controller $publicKeyCredentialCreationOptionsData = session('create_options'); // Unset session data to mitigate replay attacks - session()->forget('create_options'); + $request->session()->forget('create_options'); if (empty($publicKeyCredentialCreationOptionsData)) { throw new WebAuthnException('No public key credential request options found'); } $attestationStatementSupportManager = new AttestationStatementSupportManager; $attestationStatementSupportManager->add(new NoneAttestationStatementSupport); - - $webauthnSerializer = (new WebauthnSerializerFactory( - $attestationStatementSupportManager - ))->create(); + $webauthnSerializerFactory = new WebauthnSerializerFactory( + attestationStatementSupportManager: $attestationStatementSupportManager + ); + $webauthnSerializer = $webauthnSerializerFactory->create(); $publicKeyCredential = $webauthnSerializer->deserialize( json_encode($request->all(), JSON_THROW_ON_ERROR), @@ -146,11 +161,11 @@ class PasskeysController extends Controller $ceremonyStepManagerFactory->setExtensionOutputCheckerHandler( ExtensionOutputCheckerHandler::create() ); - $securedRelyingPartyId = []; + $allowedOrigins = []; if (App::environment('local', 'development')) { - $securedRelyingPartyId = [config('url.longurl')]; + $allowedOrigins = [config('app.url')]; } - $ceremonyStepManagerFactory->setSecuredRelyingPartyId($securedRelyingPartyId); + $ceremonyStepManagerFactory->setAllowedOrigins($allowedOrigins); $authenticatorAttestationResponseValidator = AuthenticatorAttestationResponseValidator::create( ceremonyStepManager: $ceremonyStepManagerFactory->creationCeremony() @@ -165,8 +180,7 @@ class PasskeysController extends Controller $publicKeyCredentialSource = $authenticatorAttestationResponseValidator->check( authenticatorAttestationResponse: $publicKeyCredential->response, publicKeyCredentialCreationOptions: $publicKeyCredentialCreationOptions, - request: config('url.longurl'), - securedRelyingPartyId: $securedRelyingPartyId, + host: config('app.url') ); $user->passkey()->create([ @@ -180,24 +194,37 @@ class PasskeysController extends Controller ]); } - public function getRequestOptions(): JsonResponse + /** + * @throws RandomException + * @throws \JsonException + */ + public function getRequestOptions(Request $request): JsonResponse { $publicKeyCredentialRequestOptions = PublicKeyCredentialRequestOptions::create( challenge: random_bytes(16), userVerification: PublicKeyCredentialRequestOptions::USER_VERIFICATION_REQUIREMENT_REQUIRED ); - $publicKeyCredentialRequestOptions = json_encode($publicKeyCredentialRequestOptions, JSON_THROW_ON_ERROR); + $attestationStatementSupportManager = AttestationStatementSupportManager::create(); + $attestationStatementSupportManager->add(NoneAttestationStatementSupport::create()); + $factory = new WebauthnSerializerFactory( + attestationStatementSupportManager: $attestationStatementSupportManager + ); + $serializer = $factory->create(); + $publicKeyCredentialRequestOptions = $serializer->serialize(data: $publicKeyCredentialRequestOptions, format: 'json'); - session(['request_options' => $publicKeyCredentialRequestOptions]); + $request->session()->put('request_options', $publicKeyCredentialRequestOptions); return JsonResponse::fromJsonString($publicKeyCredentialRequestOptions); } + /** + * @throws \JsonException + */ public function login(Request $request): JsonResponse { $requestOptions = session('request_options'); - session()->forget('request_options'); + $request->session()->forget('request_options'); if (empty($requestOptions)) { return response()->json([ @@ -209,9 +236,10 @@ class PasskeysController extends Controller $attestationStatementSupportManager = new AttestationStatementSupportManager; $attestationStatementSupportManager->add(new NoneAttestationStatementSupport); - $webauthnSerializer = (new WebauthnSerializerFactory( - $attestationStatementSupportManager - ))->create(); + $webauthnSerializerFactory = new WebauthnSerializerFactory( + attestationStatementSupportManager: $attestationStatementSupportManager + ); + $webauthnSerializer = $webauthnSerializerFactory->create(); $publicKeyCredential = $webauthnSerializer->deserialize( json_encode($request->all(), JSON_THROW_ON_ERROR), @@ -256,11 +284,11 @@ class PasskeysController extends Controller $ceremonyStepManagerFactory->setExtensionOutputCheckerHandler( ExtensionOutputCheckerHandler::create() ); - $securedRelyingPartyId = []; + $allowedOrigins = []; if (App::environment('local', 'development')) { - $securedRelyingPartyId = [config('url.longurl')]; + $allowedOrigins = [config('app.url')]; } - $ceremonyStepManagerFactory->setSecuredRelyingPartyId($securedRelyingPartyId); + $ceremonyStepManagerFactory->setAllowedOrigins($allowedOrigins); $authenticatorAssertionResponseValidator = AuthenticatorAssertionResponseValidator::create( ceremonyStepManager: $ceremonyStepManagerFactory->requestCeremony() @@ -274,12 +302,11 @@ class PasskeysController extends Controller try { $authenticatorAssertionResponseValidator->check( - credentialId: $publicKeyCredentialSource, + publicKeyCredentialSource: $publicKeyCredentialSource, authenticatorAssertionResponse: $publicKeyCredential->response, publicKeyCredentialRequestOptions: $publicKeyCredentialRequestOptions, - request: config('url.longurl'), + host: config('app.url'), userHandle: null, - securedRelyingPartyId: $securedRelyingPartyId, ); } catch (Throwable) { return response()->json([ diff --git a/app/Http/Controllers/Admin/PlacesController.php b/app/Http/Controllers/Admin/PlacesController.php index 2b0d2e99..e5e82bcd 100644 --- a/app/Http/Controllers/Admin/PlacesController.php +++ b/app/Http/Controllers/Admin/PlacesController.php @@ -10,9 +10,6 @@ use App\Services\PlaceService; use Illuminate\Http\RedirectResponse; use Illuminate\View\View; -/** - * @psalm-suppress UnusedClass - */ class PlacesController extends Controller { protected PlaceService $placeService; diff --git a/app/Http/Controllers/Admin/SyndicationTargetsController.php b/app/Http/Controllers/Admin/SyndicationTargetsController.php index 6eb60f69..dc14a2d2 100644 --- a/app/Http/Controllers/Admin/SyndicationTargetsController.php +++ b/app/Http/Controllers/Admin/SyndicationTargetsController.php @@ -10,9 +10,6 @@ use Illuminate\Http\RedirectResponse; use Illuminate\Http\Request; use Illuminate\View\View; -/** - * @psalm-suppress UnusedClass - */ class SyndicationTargetsController extends Controller { /** diff --git a/app/Http/Controllers/ArticlesController.php b/app/Http/Controllers/ArticlesController.php index 725c5b91..9ab860d7 100644 --- a/app/Http/Controllers/ArticlesController.php +++ b/app/Http/Controllers/ArticlesController.php @@ -10,9 +10,6 @@ use Illuminate\Http\RedirectResponse; use Illuminate\View\View; use Jonnybarnes\IndieWeb\Numbers; -/** - * @psalm-suppress UnusedClass - */ class ArticlesController extends Controller { /** diff --git a/app/Http/Controllers/AuthController.php b/app/Http/Controllers/AuthController.php index 27f34eab..bd0022d6 100644 --- a/app/Http/Controllers/AuthController.php +++ b/app/Http/Controllers/AuthController.php @@ -9,9 +9,6 @@ use Illuminate\Http\Request; use Illuminate\Support\Facades\Auth; use Illuminate\View\View; -/** - * @psalm-suppress UnusedClass - */ class AuthController extends Controller { /** diff --git a/app/Http/Controllers/BookmarksController.php b/app/Http/Controllers/BookmarksController.php index ae9a0280..b4bb3c13 100644 --- a/app/Http/Controllers/BookmarksController.php +++ b/app/Http/Controllers/BookmarksController.php @@ -7,9 +7,6 @@ namespace App\Http\Controllers; use App\Models\Bookmark; use Illuminate\View\View; -/** - * @psalm-suppress UnusedClass - */ class BookmarksController extends Controller { /** diff --git a/app/Http/Controllers/ContactsController.php b/app/Http/Controllers/ContactsController.php index 75b103a8..280cc3ed 100644 --- a/app/Http/Controllers/ContactsController.php +++ b/app/Http/Controllers/ContactsController.php @@ -8,9 +8,6 @@ use App\Models\Contact; use Illuminate\Filesystem\Filesystem; use Illuminate\View\View; -/** - * @psalm-suppress UnusedClass - */ class ContactsController extends Controller { /** diff --git a/app/Http/Controllers/FeedsController.php b/app/Http/Controllers/FeedsController.php index 4e887105..eb0847a3 100644 --- a/app/Http/Controllers/FeedsController.php +++ b/app/Http/Controllers/FeedsController.php @@ -9,9 +9,6 @@ use App\Models\Note; use Illuminate\Http\JsonResponse; use Illuminate\Http\Response; -/** - * @psalm-suppress UnusedClass - */ class FeedsController extends Controller { /** @@ -122,8 +119,8 @@ class FeedsController extends Controller foreach ($notes as $key => $note) { $data['items'][$key] = [ - 'id' => $note->longurl, - 'url' => $note->longurl, + 'id' => $note->uri, + 'url' => $note->uri, 'content_text' => $note->content, 'date_published' => $note->created_at->tz('UTC')->toRfc3339String(), 'date_modified' => $note->updated_at->tz('UTC')->toRfc3339String(), @@ -164,7 +161,7 @@ class FeedsController extends Controller 'author' => [ 'type' => 'card', 'name' => config('user.display_name'), - 'url' => config('url.longurl'), + 'url' => config('app.url'), ], 'children' => $items, ], 200, [ @@ -183,8 +180,8 @@ class FeedsController extends Controller $items[] = [ 'type' => 'entry', 'published' => $note->created_at, - 'uid' => $note->longurl, - 'url' => $note->longurl, + 'uid' => $note->uri, + 'url' => $note->uri, 'content' => [ 'text' => $note->getRawOriginal('note'), 'html' => $note->note, @@ -200,7 +197,7 @@ class FeedsController extends Controller 'author' => [ 'type' => 'card', 'name' => config('user.display_name'), - 'url' => config('url.longurl'), + 'url' => config('app.url'), ], 'children' => $items, ], 200, [ diff --git a/app/Http/Controllers/FrontPageController.php b/app/Http/Controllers/FrontPageController.php index 8ae9c3c6..19537663 100644 --- a/app/Http/Controllers/FrontPageController.php +++ b/app/Http/Controllers/FrontPageController.php @@ -10,9 +10,6 @@ use App\Models\Note; use Illuminate\Http\Response; use Illuminate\View\View; -/** - * @psalm-suppress UnusedClass - */ class FrontPageController extends Controller { /** diff --git a/app/Http/Controllers/LikesController.php b/app/Http/Controllers/LikesController.php index 77d5f963..af1c483c 100644 --- a/app/Http/Controllers/LikesController.php +++ b/app/Http/Controllers/LikesController.php @@ -7,9 +7,6 @@ namespace App\Http\Controllers; use App\Models\Like; use Illuminate\View\View; -/** - * @psalm-suppress UnusedClass - */ class LikesController extends Controller { /** diff --git a/app/Http/Controllers/MicropubController.php b/app/Http/Controllers/MicropubController.php index ac25a815..758b3255 100644 --- a/app/Http/Controllers/MicropubController.php +++ b/app/Http/Controllers/MicropubController.php @@ -4,123 +4,73 @@ declare(strict_types=1); namespace App\Http\Controllers; -use App\Http\Responses\MicropubResponses; +use App\Exceptions\InvalidTokenScopeException; +use App\Exceptions\MicropubHandlerException; +use App\Http\Requests\MicropubRequest; use App\Models\Place; use App\Models\SyndicationTarget; -use App\Services\Micropub\HCardService; -use App\Services\Micropub\HEntryService; -use App\Services\Micropub\UpdateService; -use App\Services\TokenService; +use App\Services\Micropub\MicropubHandlerRegistry; use Illuminate\Http\JsonResponse; use Illuminate\Http\Request; -use Lcobucci\JWT\Encoding\CannotDecodeContent; -use Lcobucci\JWT\Token\InvalidTokenStructure; -use Lcobucci\JWT\Validation\RequiredConstraintsViolated; -use Monolog\Handler\StreamHandler; -use Monolog\Logger; +use Lcobucci\JWT\Token; -/** - * @psalm-suppress UnusedClass - */ class MicropubController extends Controller { - protected TokenService $tokenService; + protected MicropubHandlerRegistry $handlerRegistry; - protected HEntryService $hentryService; - - protected HCardService $hcardService; - - protected UpdateService $updateService; - - public function __construct( - TokenService $tokenService, - HEntryService $hentryService, - HCardService $hcardService, - UpdateService $updateService - ) { - $this->tokenService = $tokenService; - $this->hentryService = $hentryService; - $this->hcardService = $hcardService; - $this->updateService = $updateService; + public function __construct(MicropubHandlerRegistry $handlerRegistry) + { + $this->handlerRegistry = $handlerRegistry; } /** - * This function receives an API request, verifies the authenticity - * then passes over the info to the relevant Service class. + * Respond to a POST request to the micropub endpoint. + * + * The request is initially processed by the MicropubRequest form request + * class. The normalizes the data, so we can pass it into the handlers for + * the different micropub requests, h-entry or h-card, for example. */ - public function post(Request $request): JsonResponse + public function post(MicropubRequest $request): JsonResponse { + $type = $request->getType(); + + if (! $type) { + return response()->json([ + 'error' => 'invalid_request', + 'error_description' => 'Microformat object type is missing, for example: h-entry or h-card', + ], 400); + } + try { - $tokenData = $this->tokenService->validateToken($request->input('access_token')); - } catch (RequiredConstraintsViolated|InvalidTokenStructure|CannotDecodeContent) { - $micropubResponses = new MicropubResponses; - - return $micropubResponses->invalidTokenResponse(); - } - - if ($tokenData->claims()->has('scope') === false) { - $micropubResponses = new MicropubResponses; - - return $micropubResponses->tokenHasNoScopeResponse(); - } - - $this->logMicropubRequest($request->all()); - - if (($request->input('h') === 'entry') || ($request->input('type.0') === 'h-entry')) { - $scopes = $tokenData->claims()->get('scope'); - if (is_string($scopes)) { - $scopes = explode(' ', $scopes); - } - - if (! in_array('create', $scopes)) { - $micropubResponses = new MicropubResponses; - - return $micropubResponses->insufficientScopeResponse(); - } - $location = $this->hentryService->process($request->all(), $this->getCLientId()); + $handler = $this->handlerRegistry->getHandler($type); + $result = $handler->handle($request->getMicropubData()); + // Return appropriate response based on the handler result return response()->json([ - 'response' => 'created', - 'location' => $location, - ], 201)->header('Location', $location); - } - - if ($request->input('h') === 'card' || $request->input('type.0') === 'h-card') { - $scopes = $tokenData->claims()->get('scope'); - if (is_string($scopes)) { - $scopes = explode(' ', $scopes); - } - if (! in_array('create', $scopes)) { - $micropubResponses = new MicropubResponses; - - return $micropubResponses->insufficientScopeResponse(); - } - $location = $this->hcardService->process($request->all()); - + 'response' => $result['response'], + 'location' => $result['url'] ?? null, + ], 201)->header('Location', $result['url']); + } catch (\InvalidArgumentException $e) { return response()->json([ - 'response' => 'created', - 'location' => $location, - ], 201)->header('Location', $location); + 'error' => 'invalid_request', + 'error_description' => $e->getMessage(), + ], 400); + } catch (MicropubHandlerException) { + return response()->json([ + 'error' => 'Unknown Micropub type', + 'error_description' => 'The request could not be processed by this server', + ], 500); + } catch (InvalidTokenScopeException) { + return response()->json([ + 'error' => 'invalid_scope', + 'error_description' => 'The token does not have the required scope for this request', + ], 403); + } catch (\Exception) { + return response()->json([ + 'error' => 'server_error', + 'error_description' => 'An error occurred processing the request', + ], 500); } - - if ($request->input('action') === 'update') { - $scopes = $tokenData->claims()->get('scope'); - if (is_string($scopes)) { - $scopes = explode(' ', $scopes); - } - if (! in_array('update', $scopes)) { - $micropubResponses = new MicropubResponses; - - return $micropubResponses->insufficientScopeResponse(); - } - - return $this->updateService->process($request->all()); - } - - return response()->json([ - 'response' => 'error', - 'error_description' => 'unsupported_request_type', - ], 500); } /** @@ -133,12 +83,6 @@ class MicropubController extends Controller */ public function get(Request $request): JsonResponse { - try { - $tokenData = $this->tokenService->validateToken($request->input('access_token')); - } catch (RequiredConstraintsViolated|InvalidTokenStructure) { - return (new MicropubResponses)->invalidTokenResponse(); - } - if ($request->input('q') === 'syndicate-to') { return response()->json([ 'syndicate-to' => SyndicationTarget::all(), @@ -170,36 +114,17 @@ class MicropubController extends Controller ]); } - // default response is just to return the token data + // the default response is just to return the token data + /** @var Token $tokenData */ + $tokenData = $request->input('token_data'); + return response()->json([ 'response' => 'token', 'token' => [ - 'me' => $tokenData->claims()->get('me'), - 'scope' => $tokenData->claims()->get('scope'), - 'client_id' => $tokenData->claims()->get('client_id'), + 'me' => $tokenData['me'], + 'scope' => $tokenData['scope'], + 'client_id' => $tokenData['client_id'], ], ]); } - - /** - * Determine the client id from the access token sent with the request. - * - * @throws RequiredConstraintsViolated - */ - private function getClientId(): string - { - return resolve(TokenService::class) - ->validateToken(app('request')->input('access_token')) - ->claims()->get('client_id'); - } - - /** - * Save the details of the micropub request to a log file. - */ - private function logMicropubRequest(array $request): void - { - $logger = new Logger('micropub'); - $logger->pushHandler(new StreamHandler(storage_path('logs/micropub.log'))); - $logger->debug('MicropubLog', $request); - } } diff --git a/app/Http/Controllers/MicropubMediaController.php b/app/Http/Controllers/MicropubMediaController.php index a660f11a..fc804ea2 100644 --- a/app/Http/Controllers/MicropubMediaController.php +++ b/app/Http/Controllers/MicropubMediaController.php @@ -7,10 +7,8 @@ namespace App\Http\Controllers; use App\Http\Responses\MicropubResponses; use App\Jobs\ProcessMedia; use App\Models\Media; -use App\Services\TokenService; use Exception; use Illuminate\Contracts\Container\BindingResolutionException; -use Illuminate\Http\File; use Illuminate\Http\JsonResponse; use Illuminate\Http\Request; use Illuminate\Http\Response; @@ -18,46 +16,20 @@ use Illuminate\Http\UploadedFile; use Illuminate\Support\Carbon; use Illuminate\Support\Facades\Storage; use Intervention\Image\ImageManager; -use Lcobucci\JWT\Token\InvalidTokenStructure; -use Lcobucci\JWT\Validation\RequiredConstraintsViolated; use Ramsey\Uuid\Uuid; -/** - * @psalm-suppress UnusedClass - */ class MicropubMediaController extends Controller { - protected TokenService $tokenService; - - public function __construct(TokenService $tokenService) - { - $this->tokenService = $tokenService; - } - public function getHandler(Request $request): JsonResponse { - try { - $tokenData = $this->tokenService->validateToken($request->input('access_token')); - } catch (RequiredConstraintsViolated|InvalidTokenStructure) { - $micropubResponses = new MicropubResponses; + $tokenData = $request->input('token_data'); - return $micropubResponses->invalidTokenResponse(); - } - - if ($tokenData->claims()->has('scope') === false) { - $micropubResponses = new MicropubResponses; - - return $micropubResponses->tokenHasNoScopeResponse(); - } - - $scopes = $tokenData->claims()->get('scope'); + $scopes = $tokenData['scope']; if (is_string($scopes)) { $scopes = explode(' ', $scopes); } - if (! in_array('create', $scopes)) { - $micropubResponses = new MicropubResponses; - - return $micropubResponses->insufficientScopeResponse(); + if (! in_array('create', $scopes, true)) { + return (new MicropubResponses)->insufficientScopeResponse(); } if ($request->input('q') === 'last') { @@ -108,28 +80,14 @@ class MicropubMediaController extends Controller */ public function media(Request $request): JsonResponse { - try { - $tokenData = $this->tokenService->validateToken($request->input('access_token')); - } catch (RequiredConstraintsViolated|InvalidTokenStructure) { - $micropubResponses = new MicropubResponses; + $tokenData = $request->input('token_data'); - return $micropubResponses->invalidTokenResponse(); - } - - if ($tokenData->claims()->has('scope') === false) { - $micropubResponses = new MicropubResponses; - - return $micropubResponses->tokenHasNoScopeResponse(); - } - - $scopes = $tokenData->claims()->get('scope'); + $scopes = $tokenData['scope']; if (is_string($scopes)) { $scopes = explode(' ', $scopes); } - if (! in_array('create', $scopes)) { - $micropubResponses = new MicropubResponses; - - return $micropubResponses->insufficientScopeResponse(); + if (! in_array('create', $scopes, true)) { + return (new MicropubResponses)->insufficientScopeResponse(); } if ($request->hasFile('file') === false) { @@ -164,7 +122,7 @@ class MicropubMediaController extends Controller } $media = Media::create([ - 'token' => $request->bearerToken(), + 'token' => $request->input('access_token'), 'path' => $filename, 'type' => $this->getFileTypeFromMimeType($request->file('file')->getMimeType()), 'image_widths' => $width, diff --git a/app/Http/Controllers/NotesController.php b/app/Http/Controllers/NotesController.php index 5c25771f..d5c9bc90 100644 --- a/app/Http/Controllers/NotesController.php +++ b/app/Http/Controllers/NotesController.php @@ -14,8 +14,6 @@ use Jonnybarnes\IndieWeb\Numbers; /** * @todo Need to sort out Twitter and webmentions! - * - * @psalm-suppress UnusedClass */ class NotesController extends Controller { diff --git a/app/Http/Controllers/PlacesController.php b/app/Http/Controllers/PlacesController.php index b9bae93b..b949ecde 100644 --- a/app/Http/Controllers/PlacesController.php +++ b/app/Http/Controllers/PlacesController.php @@ -7,9 +7,6 @@ namespace App\Http\Controllers; use App\Models\Place; use Illuminate\View\View; -/** - * @psalm-suppress UnusedClass - */ class PlacesController extends Controller { /** diff --git a/app/Http/Controllers/SearchController.php b/app/Http/Controllers/SearchController.php index a8116c88..3f366538 100644 --- a/app/Http/Controllers/SearchController.php +++ b/app/Http/Controllers/SearchController.php @@ -6,9 +6,6 @@ use App\Models\Note; use Illuminate\Http\Request; use Illuminate\View\View; -/** - * @psalm-suppress UnusedClass - */ class SearchController extends Controller { public function search(Request $request): View diff --git a/app/Http/Controllers/ShortURLsController.php b/app/Http/Controllers/ShortURLsController.php deleted file mode 100644 index a232fcdb..00000000 --- a/app/Http/Controllers/ShortURLsController.php +++ /dev/null @@ -1,55 +0,0 @@ - config('app.url')]` as I can’t manually log in as * a .localhost domain. - * - * @psalm-suppress PossiblyUnusedMethod */ public function handle(Request $request, Closure $next): Response { diff --git a/app/Http/Middleware/LogMicropubRequest.php b/app/Http/Middleware/LogMicropubRequest.php new file mode 100644 index 00000000..a04e80de --- /dev/null +++ b/app/Http/Middleware/LogMicropubRequest.php @@ -0,0 +1,24 @@ +pushHandler(new StreamHandler(storage_path('logs/micropub.log'))); + $logger->debug('MicropubLog', $request->all()); + + return $next($request); + } +} diff --git a/app/Http/Middleware/MyAuthMiddleware.php b/app/Http/Middleware/MyAuthMiddleware.php index e455d181..b22e2b33 100644 --- a/app/Http/Middleware/MyAuthMiddleware.php +++ b/app/Http/Middleware/MyAuthMiddleware.php @@ -13,8 +13,6 @@ class MyAuthMiddleware { /** * Check the user is logged in. - * - * @psalm-suppress PossiblyUnusedMethod */ public function handle(Request $request, Closure $next): Response { diff --git a/app/Http/Middleware/ValidateSignature.php b/app/Http/Middleware/ValidateSignature.php index 2beb3c93..093bf64a 100644 --- a/app/Http/Middleware/ValidateSignature.php +++ b/app/Http/Middleware/ValidateSignature.php @@ -10,8 +10,6 @@ class ValidateSignature extends Middleware * The names of the query string parameters that should be ignored. * * @var array - * - * @psalm-suppress PossiblyUnusedProperty */ protected $except = [ // 'fbclid', diff --git a/app/Http/Middleware/VerifyMicropubToken.php b/app/Http/Middleware/VerifyMicropubToken.php index b68e999b..33d2cb12 100644 --- a/app/Http/Middleware/VerifyMicropubToken.php +++ b/app/Http/Middleware/VerifyMicropubToken.php @@ -4,8 +4,14 @@ declare(strict_types=1); namespace App\Http\Middleware; +use App\Http\Responses\MicropubResponses; use Closure; use Illuminate\Http\Request; +use Lcobucci\JWT\Configuration; +use Lcobucci\JWT\Encoding\CannotDecodeContent; +use Lcobucci\JWT\Token; +use Lcobucci\JWT\Token\InvalidTokenStructure; +use Lcobucci\JWT\Validation\RequiredConstraintsViolated; use Symfony\Component\HttpFoundation\Response; class VerifyMicropubToken @@ -13,24 +19,63 @@ class VerifyMicropubToken /** * Handle an incoming request. * - * @psalm-suppress PossiblyUnusedMethod + * @param Closure(Request): (Response) $next */ public function handle(Request $request, Closure $next): Response { + $rawToken = null; + if ($request->input('access_token')) { - return $next($request); + $rawToken = $request->input('access_token'); + } elseif ($request->bearerToken()) { + $rawToken = $request->bearerToken(); } - if ($request->bearerToken()) { - return $next($request->merge([ - 'access_token' => $request->bearerToken(), - ])); + if (! $rawToken) { + return response()->json([ + 'response' => 'error', + 'error' => 'unauthorized', + 'error_description' => 'No access token was provided in the request', + ], 401); } - return response()->json([ - 'response' => 'error', - 'error' => 'unauthorized', - 'error_description' => 'No access token was provided in the request', - ], 401); + try { + $tokenData = $this->validateToken($rawToken); + } catch (RequiredConstraintsViolated|InvalidTokenStructure|CannotDecodeContent) { + $micropubResponses = new MicropubResponses; + + return $micropubResponses->invalidTokenResponse(); + } + + if ($tokenData->claims()->has('scope') === false) { + $micropubResponses = new MicropubResponses; + + return $micropubResponses->tokenHasNoScopeResponse(); + } + + return $next($request->merge([ + 'access_token' => $rawToken, + 'token_data' => [ + 'me' => $tokenData->claims()->get('me'), + 'scope' => $tokenData->claims()->get('scope'), + 'client_id' => $tokenData->claims()->get('client_id'), + ], + ])); + } + + /** + * Check the token signature is valid. + */ + private function validateToken(string $bearerToken): Token + { + $config = resolve(Configuration::class); + + $token = $config->parser()->parse($bearerToken); + + $constraints = $config->validationConstraints(); + + $config->validator()->assert($token, ...$constraints); + + return $token; } } diff --git a/app/Http/Requests/MicropubRequest.php b/app/Http/Requests/MicropubRequest.php new file mode 100644 index 00000000..d931f139 --- /dev/null +++ b/app/Http/Requests/MicropubRequest.php @@ -0,0 +1,106 @@ +micropubData; + } + + public function getType(): ?string + { + // Return consistent type regardless of input format + return $this->micropubData['type'] ?? null; + } + + protected function prepareForValidation(): void + { + // Normalize the request data based on content type + if ($this->isJson()) { + $this->normalizeMicropubJson(); + } else { + $this->normalizeMicropubForm(); + } + } + + private function normalizeMicropubJson(): void + { + $json = $this->json(); + if ($json === null) { + throw new \InvalidArgumentException('`isJson()` passed but there is no json data'); + } + + $data = $json->all(); + + // Convert JSON type (h-entry) to simple type (entry) + if (isset($data['type']) && is_array($data['type'])) { + $type = current($data['type']); + if (strpos($type, 'h-') === 0) { + $this->micropubData['type'] = substr($type, 2); + } + } + // Or set the type to update + elseif (isset($data['action']) && $data['action'] === 'update') { + $this->micropubData['type'] = 'update'; + } + + // Add in the token data + $this->micropubData['token_data'] = $data['token_data']; + + // Add h-entry values + $this->micropubData['content'] = Arr::get($data, 'properties.content.0'); + $this->micropubData['in-reply-to'] = Arr::get($data, 'properties.in-reply-to.0'); + $this->micropubData['published'] = Arr::get($data, 'properties.published.0'); + $this->micropubData['location'] = Arr::get($data, 'location'); + $this->micropubData['bookmark-of'] = Arr::get($data, 'properties.bookmark-of.0'); + $this->micropubData['like-of'] = Arr::get($data, 'properties.like-of.0'); + $this->micropubData['mp-syndicate-to'] = Arr::get($data, 'properties.mp-syndicate-to'); + + // Add h-card values + $this->micropubData['name'] = Arr::get($data, 'properties.name.0'); + $this->micropubData['description'] = Arr::get($data, 'properties.description.0'); + $this->micropubData['geo'] = Arr::get($data, 'properties.geo.0'); + + // Add checkin value + $this->micropubData['checkin'] = Arr::get($data, 'checkin'); + $this->micropubData['syndication'] = Arr::get($data, 'properties.syndication.0'); + } + + private function normalizeMicropubForm(): void + { + // Convert form h=entry to type=entry + if ($h = $this->input('h')) { + $this->micropubData['type'] = $h; + } + + // Add some fields to the micropub data with default null values + $this->micropubData['in-reply-to'] = null; + $this->micropubData['published'] = null; + $this->micropubData['location'] = null; + $this->micropubData['description'] = null; + $this->micropubData['geo'] = null; + $this->micropubData['latitude'] = null; + $this->micropubData['longitude'] = null; + + // Map form fields to micropub data + foreach ($this->except(['h', 'access_token']) as $key => $value) { + $this->micropubData[$key] = $value; + } + } +} diff --git a/app/Jobs/ProcessWebMention.php b/app/Jobs/ProcessWebMention.php index 24c7f477..d92dfa18 100644 --- a/app/Jobs/ProcessWebMention.php +++ b/app/Jobs/ProcessWebMention.php @@ -53,7 +53,7 @@ class ProcessWebMention implements ShouldQueue // check webmention still references target // we try each type of mention (reply/like/repost) if ($webmention->type === 'in-reply-to') { - if ($parser->checkInReplyTo($microformats, $this->note->longurl) === false) { + if ($parser->checkInReplyTo($microformats, $this->note->uri) === false) { // it doesn’t so delete $webmention->delete(); @@ -67,7 +67,7 @@ class ProcessWebMention implements ShouldQueue return; } if ($webmention->type === 'like-of') { - if ($parser->checkLikeOf($microformats, $this->note->longurl) === false) { + if ($parser->checkLikeOf($microformats, $this->note->uri) === false) { // it doesn’t so delete $webmention->delete(); @@ -75,7 +75,7 @@ class ProcessWebMention implements ShouldQueue } // note we don’t need to do anything if it still is a like } if ($webmention->type === 'repost-of') { - if ($parser->checkRepostOf($microformats, $this->note->longurl) === false) { + if ($parser->checkRepostOf($microformats, $this->note->uri) === false) { // it doesn’t so delete $webmention->delete(); @@ -89,7 +89,7 @@ class ProcessWebMention implements ShouldQueue $type = $parser->getMentionType($microformats); // throw error here? dispatch(new SaveProfileImage($microformats)); $webmention->source = $this->source; - $webmention->target = $this->note->longurl; + $webmention->target = $this->note->uri; $webmention->commentable_id = $this->note->id; $webmention->commentable_type = Note::class; $webmention->type = $type; diff --git a/app/Jobs/SendWebMentions.php b/app/Jobs/SendWebMentions.php index 51e5f162..2ff5f2c6 100644 --- a/app/Jobs/SendWebMentions.php +++ b/app/Jobs/SendWebMentions.php @@ -45,7 +45,7 @@ class SendWebMentions implements ShouldQueue $guzzle = resolve(Client::class); $guzzle->post($endpoint, [ 'form_params' => [ - 'source' => $this->note->longurl, + 'source' => $this->note->uri, 'target' => $url, ], ]); @@ -61,7 +61,7 @@ class SendWebMentions implements ShouldQueue public function discoverWebmentionEndpoint(string $url): ?string { // let’s not send webmentions to myself - if (parse_url($url, PHP_URL_HOST) === config('url.longurl')) { + if (parse_url($url, PHP_URL_HOST) === parse_url(config('app.url'), PHP_URL_HOST)) { return null; } if (Str::startsWith($url, '/notes/tagged/')) { diff --git a/app/Models/Bookmark.php b/app/Models/Bookmark.php index 29bd25ad..37027e40 100644 --- a/app/Models/Bookmark.php +++ b/app/Models/Bookmark.php @@ -26,7 +26,7 @@ class Bookmark extends Model return $this->belongsToMany('App\Models\Tag'); } - protected function longurl(): Attribute + protected function local_uri(): Attribute { return Attribute::get( get: fn () => config('app.url') . '/bookmarks/' . $this->id, diff --git a/app/Models/Note.php b/app/Models/Note.php index 62b9fcea..74533443 100644 --- a/app/Models/Note.php +++ b/app/Models/Note.php @@ -124,7 +124,7 @@ class Note extends Model public function getNoteAttribute(?string $value): ?string { if ($value === null && $this->place !== null) { - $value = 'šŸ“: ' . $this->place->name . ''; + $value = 'šŸ“: ' . $this->place->name . ''; } // if $value is still null, just return null @@ -172,16 +172,11 @@ class Note extends Model return (string) resolve(Numbers::class)->numto60($this->id); } - public function getLongurlAttribute(): string + public function getUriAttribute(): string { return config('app.url') . '/notes/' . $this->nb60id; } - public function getShorturlAttribute(): string - { - return config('url.shorturl') . '/notes/' . $this->nb60id; - } - public function getIso8601Attribute(): string { return $this->updated_at->toISO8601String(); diff --git a/app/Models/Place.php b/app/Models/Place.php index 62c826ad..550f234d 100644 --- a/app/Models/Place.php +++ b/app/Models/Place.php @@ -74,24 +74,10 @@ class Place extends Model ])); } - protected function longurl(): Attribute - { - return Attribute::get( - get: fn ($value, $attributes) => config('app.url') . '/places/' . $attributes['slug'], - ); - } - - protected function shorturl(): Attribute - { - return Attribute::get( - get: fn ($value, $attributes) => config('url.shorturl') . '/places/' . $attributes['slug'], - ); - } - protected function uri(): Attribute { return Attribute::get( - get: fn () => $this->longurl, + get: static fn ($value, $attributes) => config('app.url') . '/places/' . $attributes['slug'], ); } diff --git a/app/Observers/NoteObserver.php b/app/Observers/NoteObserver.php index 935fb27f..95288b01 100644 --- a/app/Observers/NoteObserver.php +++ b/app/Observers/NoteObserver.php @@ -9,15 +9,10 @@ use App\Models\Tag; use Illuminate\Support\Arr; use Illuminate\Support\Collection; -/** - * @todo Do we need psalm-suppress for these observer methods? - */ class NoteObserver { /** - * Listen to the Note created event. - * - * @psalm-suppress PossiblyUnusedMethod + * Listen to the Note created event.= */ public function created(Note $note): void { @@ -39,9 +34,7 @@ class NoteObserver } /** - * Listen to the Note updated event. - * - * @psalm-suppress PossiblyUnusedMethod + * Listen to the Note updated event.= */ public function updated(Note $note): void { @@ -65,9 +58,7 @@ class NoteObserver } /** - * Listen to the Note deleting event. - * - * @psalm-suppress PossiblyUnusedMethod + * Listen to the Note deleting event.= */ public function deleting(Note $note): void { diff --git a/app/Providers/HorizonServiceProvider.php b/app/Providers/HorizonServiceProvider.php index 94c76a5a..bf4280ef 100644 --- a/app/Providers/HorizonServiceProvider.php +++ b/app/Providers/HorizonServiceProvider.php @@ -5,9 +5,6 @@ namespace App\Providers; use Illuminate\Support\Facades\Gate; use Laravel\Horizon\HorizonApplicationServiceProvider; -/** - * @psalm-suppress UnusedClass - */ class HorizonServiceProvider extends HorizonApplicationServiceProvider { /** diff --git a/app/Providers/MicropubServiceProvider.php b/app/Providers/MicropubServiceProvider.php new file mode 100644 index 00000000..1002a26d --- /dev/null +++ b/app/Providers/MicropubServiceProvider.php @@ -0,0 +1,26 @@ +app->singleton(MicropubHandlerRegistry::class, function () { + $registry = new MicropubHandlerRegistry; + + // Register handlers + $registry->register('card', new CardHandler); + $registry->register('entry', new EntryHandler); + + return $registry; + }); + } +} diff --git a/app/Services/ArticleService.php b/app/Services/ArticleService.php index 195f7051..3d5dcc56 100644 --- a/app/Services/ArticleService.php +++ b/app/Services/ArticleService.php @@ -6,13 +6,13 @@ namespace App\Services; use App\Models\Article; -class ArticleService extends Service +class ArticleService { - public function create(array $request, ?string $client = null): Article + public function create(array $data): Article { return Article::create([ - 'title' => $this->getDataByKey($request, 'name'), - 'main' => $this->getDataByKey($request, 'content'), + 'title' => $data['name'], + 'main' => $data['content'], 'published' => true, ]); } diff --git a/app/Services/BookmarkService.php b/app/Services/BookmarkService.php index 32ec7260..9cbc0714 100644 --- a/app/Services/BookmarkService.php +++ b/app/Services/BookmarkService.php @@ -10,28 +10,29 @@ use App\Models\Bookmark; use App\Models\Tag; use GuzzleHttp\Client; use GuzzleHttp\Exception\ClientException; +use GuzzleHttp\Exception\GuzzleException; use Illuminate\Support\Arr; use Illuminate\Support\Str; -class BookmarkService extends Service +class BookmarkService { /** * Create a new Bookmark. */ - public function create(array $request, ?string $client = null): Bookmark + public function create(array $data): Bookmark { - if (Arr::get($request, 'properties.bookmark-of.0')) { + if (Arr::get($data, 'properties.bookmark-of.0')) { // micropub request - $url = normalize_url(Arr::get($request, 'properties.bookmark-of.0')); - $name = Arr::get($request, 'properties.name.0'); - $content = Arr::get($request, 'properties.content.0'); - $categories = Arr::get($request, 'properties.category'); + $url = normalize_url(Arr::get($data, 'properties.bookmark-of.0')); + $name = Arr::get($data, 'properties.name.0'); + $content = Arr::get($data, 'properties.content.0'); + $categories = Arr::get($data, 'properties.category'); } - if (Arr::get($request, 'bookmark-of')) { - $url = normalize_url(Arr::get($request, 'bookmark-of')); - $name = Arr::get($request, 'name'); - $content = Arr::get($request, 'content'); - $categories = Arr::get($request, 'category'); + if (Arr::get($data, 'bookmark-of')) { + $url = normalize_url(Arr::get($data, 'bookmark-of')); + $name = Arr::get($data, 'name'); + $content = Arr::get($data, 'content'); + $categories = Arr::get($data, 'category'); } $bookmark = Bookmark::create([ @@ -54,6 +55,7 @@ class BookmarkService extends Service * Given a URL, attempt to save it to the Internet Archive. * * @throws InternetArchiveException + * @throws GuzzleException */ public function getArchiveLink(string $url): string { diff --git a/app/Services/LikeService.php b/app/Services/LikeService.php index dd08e25b..e688561d 100644 --- a/app/Services/LikeService.php +++ b/app/Services/LikeService.php @@ -8,19 +8,19 @@ use App\Jobs\ProcessLike; use App\Models\Like; use Illuminate\Support\Arr; -class LikeService extends Service +class LikeService { /** * Create a new Like. */ - public function create(array $request, ?string $client = null): Like + public function create(array $data): Like { - if (Arr::get($request, 'properties.like-of.0')) { + if (Arr::get($data, 'properties.like-of.0')) { // micropub request - $url = normalize_url(Arr::get($request, 'properties.like-of.0')); + $url = normalize_url(Arr::get($data, 'properties.like-of.0')); } - if (Arr::get($request, 'like-of')) { - $url = normalize_url(Arr::get($request, 'like-of')); + if (Arr::get($data, 'like-of')) { + $url = normalize_url(Arr::get($data, 'like-of')); } $like = Like::create(['url' => $url]); diff --git a/app/Services/Micropub/CardHandler.php b/app/Services/Micropub/CardHandler.php new file mode 100644 index 00000000..12e283be --- /dev/null +++ b/app/Services/Micropub/CardHandler.php @@ -0,0 +1,34 @@ +createPlace($data)->uri; + + return [ + 'response' => 'created', + 'url' => $location, + ]; + } +} diff --git a/app/Services/Micropub/EntryHandler.php b/app/Services/Micropub/EntryHandler.php new file mode 100644 index 00000000..9cdbe789 --- /dev/null +++ b/app/Services/Micropub/EntryHandler.php @@ -0,0 +1,41 @@ + resolve(LikeService::class)->create($data)->url, + isset($data['bookmark-of']) => resolve(BookmarkService::class)->create($data)->uri, + isset($data['name']) => resolve(ArticleService::class)->create($data)->link, + default => resolve(NoteService::class)->create($data)->uri, + }; + + return [ + 'response' => 'created', + 'url' => $location, + ]; + } +} diff --git a/app/Services/Micropub/HCardService.php b/app/Services/Micropub/HCardService.php deleted file mode 100644 index 7ab57a4e..00000000 --- a/app/Services/Micropub/HCardService.php +++ /dev/null @@ -1,32 +0,0 @@ -createPlace($data)->longurl; - } -} diff --git a/app/Services/Micropub/HEntryService.php b/app/Services/Micropub/HEntryService.php deleted file mode 100644 index 807e6327..00000000 --- a/app/Services/Micropub/HEntryService.php +++ /dev/null @@ -1,34 +0,0 @@ -create($request)->longurl; - } - - if (Arr::get($request, 'properties.bookmark-of') || Arr::get($request, 'bookmark-of')) { - return resolve(BookmarkService::class)->create($request)->longurl; - } - - if (Arr::get($request, 'properties.name') || Arr::get($request, 'name')) { - return resolve(ArticleService::class)->create($request)->longurl; - } - - return resolve(NoteService::class)->create($request, $client)->longurl; - } -} diff --git a/app/Services/Micropub/MicropubHandlerInterface.php b/app/Services/Micropub/MicropubHandlerInterface.php new file mode 100644 index 00000000..82040be9 --- /dev/null +++ b/app/Services/Micropub/MicropubHandlerInterface.php @@ -0,0 +1,10 @@ +handlers[$type] = $handler; + + return $this; + } + + /** + * @throws MicropubHandlerException + */ + public function getHandler(string $type): MicropubHandlerInterface + { + if (! isset($this->handlers[$type])) { + throw new MicropubHandlerException("No handler registered for '{$type}'"); + } + + return $this->handlers[$type]; + } +} diff --git a/app/Services/Micropub/UpdateService.php b/app/Services/Micropub/UpdateHandler.php similarity index 79% rename from app/Services/Micropub/UpdateService.php rename to app/Services/Micropub/UpdateHandler.php index f806361c..ee018f19 100644 --- a/app/Services/Micropub/UpdateService.php +++ b/app/Services/Micropub/UpdateHandler.php @@ -4,21 +4,33 @@ declare(strict_types=1); namespace App\Services\Micropub; +use App\Exceptions\InvalidTokenScopeException; use App\Models\Media; use App\Models\Note; use Illuminate\Database\Eloquent\ModelNotFoundException; -use Illuminate\Http\JsonResponse; use Illuminate\Support\Arr; use Illuminate\Support\Str; -class UpdateService +/* + * @todo Implement this properly + */ +class UpdateHandler implements MicropubHandlerInterface { /** - * Process a micropub request to update an entry. + * @throws InvalidTokenScopeException */ - public function process(array $request): JsonResponse + public function handle(array $data) { - $urlPath = parse_url(Arr::get($request, 'url'), PHP_URL_PATH); + $scopes = $data['token_data']['scope']; + if (is_string($scopes)) { + $scopes = explode(' ', $scopes); + } + + if (! in_array('update', $scopes, true)) { + throw new InvalidTokenScopeException; + } + + $urlPath = parse_url(Arr::get($data, 'url'), PHP_URL_PATH); // is it a note we are updating? if (mb_substr($urlPath, 1, 5) !== 'notes') { @@ -30,7 +42,7 @@ class UpdateService try { $note = Note::nb60(basename($urlPath))->firstOrFail(); - } catch (ModelNotFoundException $exception) { + } catch (ModelNotFoundException) { return response()->json([ 'error' => 'invalid_request', 'error_description' => 'No known note with given ID', @@ -38,8 +50,8 @@ class UpdateService } // got the note, are we dealing with a ā€œreplaceā€ request? - if (Arr::get($request, 'replace')) { - foreach (Arr::get($request, 'replace') as $property => $value) { + if (Arr::get($data, 'replace')) { + foreach (Arr::get($data, 'replace') as $property => $value) { if ($property === 'content') { $note->note = $value[0]; } @@ -59,14 +71,14 @@ class UpdateService } $note->save(); - return response()->json([ + return [ 'response' => 'updated', - ]); + ]; } // how about ā€œaddā€ - if (Arr::get($request, 'add')) { - foreach (Arr::get($request, 'add') as $property => $value) { + if (Arr::get($data, 'add')) { + foreach (Arr::get($data, 'add') as $property => $value) { if ($property === 'syndication') { foreach ($value as $syndicationURL) { if (Str::startsWith($syndicationURL, 'https://www.facebook.com')) { diff --git a/app/Services/NoteService.php b/app/Services/NoteService.php index b101498c..d8c55507 100644 --- a/app/Services/NoteService.php +++ b/app/Services/NoteService.php @@ -14,49 +14,52 @@ use App\Models\SyndicationTarget; use Illuminate\Support\Arr; use Illuminate\Support\Str; -class NoteService extends Service +class NoteService { /** * Create a new note. */ - public function create(array $request, ?string $client = null): Note + public function create(array $data): Note { + // Get the content we want to save + if (is_string($data['content'])) { + $content = $data['content']; + } elseif (isset($data['content']['html'])) { + $content = $data['content']['html']; + } else { + $content = null; + } + $note = Note::create( [ - 'note' => $this->getDataByKey($request, 'content'), - 'in_reply_to' => $this->getDataByKey($request, 'in-reply-to'), - 'client_id' => $client, + 'note' => $content, + 'in_reply_to' => $data['in-reply-to'], + 'client_id' => $data['token_data']['client_id'], ] ); - if ($this->getPublished($request)) { - $note->created_at = $note->updated_at = $this->getPublished($request); + if ($published = $this->getPublished($data)) { + $note->created_at = $note->updated_at = $published; } - $note->location = $this->getLocation($request); + $note->location = $this->getLocation($data); - if ($this->getCheckin($request)) { - $note->place()->associate($this->getCheckin($request)); - $note->swarm_url = $this->getSwarmUrl($request); - } - - $note->instagram_url = $this->getInstagramUrl($request); - - foreach ($this->getMedia($request) as $media) { - $note->media()->save($media); + if ($this->getCheckin($data)) { + $note->place()->associate($this->getCheckin($data)); + $note->swarm_url = $this->getSwarmUrl($data); } + // + // $note->instagram_url = $this->getInstagramUrl($request); + // + // foreach ($this->getMedia($request) as $media) { + // $note->media()->save($media); + // } $note->save(); dispatch(new SendWebMentions($note)); - if (in_array('mastodon', $this->getSyndicationTargets($request), true)) { - dispatch(new SyndicateNoteToMastodon($note)); - } - - if (in_array('bluesky', $this->getSyndicationTargets($request), true)) { - dispatch(new SyndicateNoteToBluesky($note)); - } + $this->dispatchSyndicationJobs($note, $data); return $note; } @@ -64,14 +67,10 @@ class NoteService extends Service /** * Get the published time from the request to create a new note. */ - private function getPublished(array $request): ?string + private function getPublished(array $data): ?string { - if (Arr::get($request, 'properties.published.0')) { - return carbon(Arr::get($request, 'properties.published.0')) - ->toDateTimeString(); - } - if (Arr::get($request, 'published')) { - return carbon(Arr::get($request, 'published'))->toDateTimeString(); + if ($data['published']) { + return carbon($data['published'])->toDateTimeString(); } return null; @@ -80,12 +79,13 @@ class NoteService extends Service /** * Get the location data from the request to create a new note. */ - private function getLocation(array $request): ?string + private function getLocation(array $data): ?string { - $location = Arr::get($request, 'properties.location.0') ?? Arr::get($request, 'location'); + $location = Arr::get($data, 'location'); + if (is_string($location) && str_starts_with($location, 'geo:')) { preg_match_all( - '/([0-9\.\-]+)/', + '/([0-9.\-]+)/', $location, $matches ); @@ -99,9 +99,9 @@ class NoteService extends Service /** * Get the checkin data from the request to create a new note. This will be a Place. */ - private function getCheckin(array $request): ?Place + private function getCheckin(array $data): ?Place { - $location = Arr::get($request, 'location'); + $location = Arr::get($data, 'location'); if (is_string($location) && Str::startsWith($location, config('app.url'))) { return Place::where( 'slug', @@ -113,12 +113,12 @@ class NoteService extends Service ) )->first(); } - if (Arr::get($request, 'checkin')) { + if (Arr::get($data, 'checkin')) { try { $place = resolve(PlaceService::class)->createPlaceFromCheckin( - Arr::get($request, 'checkin') + Arr::get($data, 'checkin') ); - } catch (\InvalidArgumentException $e) { + } catch (\InvalidArgumentException) { return null; } @@ -142,34 +142,47 @@ class NoteService extends Service /** * Get the Swarm URL from the syndication data in the request to create a new note. */ - private function getSwarmUrl(array $request): ?string + private function getSwarmUrl(array $data): ?string { - if (str_contains(Arr::get($request, 'properties.syndication.0', ''), 'swarmapp')) { - return Arr::get($request, 'properties.syndication.0'); + $syndication = Arr::get($data, 'syndication'); + if ($syndication === null) { + return null; + } + + if (str_contains($syndication, 'swarmapp')) { + return $syndication; } return null; } /** - * Get the syndication targets from the request to create a new note. + * Dispatch syndication jobs based on the request data. */ - private function getSyndicationTargets(array $request): array + private function dispatchSyndicationJobs(Note $note, array $request): void { - $syndication = []; - $mpSyndicateTo = Arr::get($request, 'mp-syndicate-to') ?? Arr::get($request, 'properties.mp-syndicate-to'); - $mpSyndicateTo = Arr::wrap($mpSyndicateTo); - foreach ($mpSyndicateTo as $uid) { - $target = SyndicationTarget::where('uid', $uid)->first(); - if ($target && $target->service_name === 'Mastodon') { - $syndication[] = 'mastodon'; - } - if ($target && $target->service_name === 'Bluesky') { - $syndication[] = 'bluesky'; - } + // If no syndication targets are specified, return early + if (empty($request['mp-syndicate-to'])) { + return; } - return $syndication; + // Get the configured syndication targets + $syndicationTargets = SyndicationTarget::all(); + + foreach ($syndicationTargets as $target) { + // Check if the target is in the request data + if (in_array($target->uid, $request['mp-syndicate-to'], true)) { + // Dispatch the appropriate job based on the target service name + switch ($target->service_name) { + case 'Mastodon': + dispatch(new SyndicateNoteToMastodon($note)); + break; + case 'Bluesky': + dispatch(new SyndicateNoteToBluesky($note)); + break; + } + } + } } /** diff --git a/app/Services/Service.php b/app/Services/Service.php deleted file mode 100644 index cb480d7c..00000000 --- a/app/Services/Service.php +++ /dev/null @@ -1,30 +0,0 @@ -toString(); } - - /** - * Check the token signature is valid. - */ - public function validateToken(string $bearerToken): Token - { - $config = resolve('Lcobucci\JWT\Configuration'); - - $token = $config->parser()->parse($bearerToken); - - $constraints = $config->validationConstraints(); - - $config->validator()->assert($token, ...$constraints); - - return $token; - } } diff --git a/bootstrap/providers.php b/bootstrap/providers.php index 4e3b4407..24821d29 100644 --- a/bootstrap/providers.php +++ b/bootstrap/providers.php @@ -3,4 +3,5 @@ return [ App\Providers\AppServiceProvider::class, App\Providers\HorizonServiceProvider::class, + App\Providers\MicropubServiceProvider::class, ]; diff --git a/composer.json b/composer.json index e4ea6123..063e895a 100644 --- a/composer.json +++ b/composer.json @@ -49,7 +49,8 @@ "openai-php/client": "^0.10.1", "phpunit/php-code-coverage": "^11.0", "phpunit/phpunit": "^11.0", - "spatie/laravel-ray": "^1.12" + "spatie/laravel-ray": "^1.12", + "spatie/x-ray": "^1.2" }, "autoload": { "psr-4": { diff --git a/composer.lock b/composer.lock index a7521ac8..730017c5 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": "cd963bfd9cfb41beb4151e73ae98dc98", + "content-hash": "1076b46fccbfe2c22f51fa6e904cfedf", "packages": [ { "name": "aws/aws-crt-php", @@ -10079,6 +10079,133 @@ ], "time": "2024-11-12T20:51:16+00:00" }, + { + "name": "permafrost-dev/code-snippets", + "version": "1.2.0", + "source": { + "type": "git", + "url": "https://github.com/permafrost-dev/code-snippets.git", + "reference": "639827ba7118a6b5521c861a265358ce5bd2b0c5" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/permafrost-dev/code-snippets/zipball/639827ba7118a6b5521c861a265358ce5bd2b0c5", + "reference": "639827ba7118a6b5521c861a265358ce5bd2b0c5", + "shasum": "" + }, + "require": { + "php": "^7.3|^8.0" + }, + "require-dev": { + "phpunit/phpunit": "^9.5", + "spatie/phpunit-snapshot-assertions": "^4.2" + }, + "type": "library", + "autoload": { + "psr-4": { + "Permafrost\\CodeSnippets\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Patrick Organ", + "email": "patrick@permafrost.dev", + "role": "Developer" + } + ], + "description": "Easily work with code snippets in PHP", + "homepage": "https://github.com/permafrost-dev/code-snippets", + "keywords": [ + "code", + "code-snippets", + "permafrost", + "snippets" + ], + "support": { + "issues": "https://github.com/permafrost-dev/code-snippets/issues", + "source": "https://github.com/permafrost-dev/code-snippets/tree/1.2.0" + }, + "funding": [ + { + "url": "https://permafrost.dev/open-source", + "type": "custom" + }, + { + "url": "https://github.com/permafrost-dev", + "type": "github" + } + ], + "time": "2021-07-27T05:15:06+00:00" + }, + { + "name": "permafrost-dev/php-code-search", + "version": "1.12.0", + "source": { + "type": "git", + "url": "https://github.com/permafrost-dev/php-code-search.git", + "reference": "dbbca18f7dc2950e88121bb62f8ed2c697df799a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/permafrost-dev/php-code-search/zipball/dbbca18f7dc2950e88121bb62f8ed2c697df799a", + "reference": "dbbca18f7dc2950e88121bb62f8ed2c697df799a", + "shasum": "" + }, + "require": { + "nikic/php-parser": "^5.0", + "permafrost-dev/code-snippets": "^1.2.0", + "php": "^7.4|^8.0" + }, + "require-dev": { + "phpunit/phpunit": "^9.5", + "spatie/phpunit-snapshot-assertions": "^4.2" + }, + "type": "library", + "autoload": { + "files": [ + "src/Support/helpers.php" + ], + "psr-4": { + "Permafrost\\PhpCodeSearch\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Patrick Organ", + "email": "patrick@permafrost.dev", + "homepage": "https://permafrost.dev", + "role": "Developer" + } + ], + "description": "Search PHP code for function & method calls, variable assignments, and more", + "homepage": "https://github.com/permafrost-dev/php-code-search", + "keywords": [ + "code", + "permafrost", + "php", + "search", + "sourcecode" + ], + "support": { + "issues": "https://github.com/permafrost-dev/php-code-search/issues", + "source": "https://github.com/permafrost-dev/php-code-search/tree/1.12.0" + }, + "funding": [ + { + "url": "https://github.com/sponsors/permafrost-dev", + "type": "github" + } + ], + "time": "2024-09-03T04:33:45+00:00" + }, { "name": "phar-io/manifest", "version": "2.0.4", @@ -12169,6 +12296,78 @@ ], "time": "2025-03-21T08:56:30+00:00" }, + { + "name": "spatie/x-ray", + "version": "1.2.0", + "source": { + "type": "git", + "url": "https://github.com/spatie/x-ray.git", + "reference": "c1d8fe19951b752422d058fc911f14066e4ac346" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/spatie/x-ray/zipball/c1d8fe19951b752422d058fc911f14066e4ac346", + "reference": "c1d8fe19951b752422d058fc911f14066e4ac346", + "shasum": "" + }, + "require": { + "permafrost-dev/code-snippets": "^1.2.0", + "permafrost-dev/php-code-search": "^1.10.5", + "php": "^8.0", + "symfony/console": "^5.3|^6.0|^7.0", + "symfony/finder": "^5.3|^6.0|^7.0", + "symfony/yaml": "^5.3|^6.0|^7.0" + }, + "require-dev": { + "phpstan/phpstan": "^2.0.0", + "phpunit/phpunit": "^9.5", + "spatie/phpunit-snapshot-assertions": "^4.2" + }, + "bin": [ + "bin/x-ray" + ], + "type": "library", + "autoload": { + "psr-4": { + "Spatie\\XRay\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Patrick Organ", + "email": "patrick@permafrost.dev", + "homepage": "https://permafrost.dev", + "role": "Developer" + } + ], + "description": "Quickly scan source code for calls to Ray", + "homepage": "https://github.com/spatie/x-ray", + "keywords": [ + "permafrost", + "ray", + "search", + "spatie" + ], + "support": { + "issues": "https://github.com/spatie/x-ray/issues", + "source": "https://github.com/spatie/x-ray/tree/1.2.0" + }, + "funding": [ + { + "url": "https://github.com/sponsors/permafrost-dev", + "type": "github" + }, + { + "url": "https://github.com/sponsors/spatie", + "type": "github" + } + ], + "time": "2024-11-12T13:23:31+00:00" + }, { "name": "staabm/side-effects-detector", "version": "1.0.5", diff --git a/config/url.php b/config/url.php deleted file mode 100644 index dfdffe6b..00000000 --- a/config/url.php +++ /dev/null @@ -1,32 +0,0 @@ - env('APP_LONGURL', 'longurl.local'), - - /* - |-------------------------------------------------------------------------- - | Application Short URL - |-------------------------------------------------------------------------- - | - | The short URL for the application - | - */ - - 'shorturl' => env('APP_SHORTURL', 'shorturl.local'), - -]; diff --git a/database/factories/ArticleFactory.php b/database/factories/ArticleFactory.php index 54c91aa0..7695e27e 100644 --- a/database/factories/ArticleFactory.php +++ b/database/factories/ArticleFactory.php @@ -7,8 +7,6 @@ use Illuminate\Database\Eloquent\Factories\Factory; use Illuminate\Support\Carbon; /** - * @psalm-suppress UnusedClass - * * @extends \Illuminate\Database\Eloquent\Factories\Factory<\App\Models\Article> */ class ArticleFactory extends Factory diff --git a/database/factories/BioFactory.php b/database/factories/BioFactory.php index 7ed8cb6f..b2493014 100644 --- a/database/factories/BioFactory.php +++ b/database/factories/BioFactory.php @@ -5,8 +5,6 @@ namespace Database\Factories; use Illuminate\Database\Eloquent\Factories\Factory; /** - * @psalm-suppress UnusedClass - * * @extends \Illuminate\Database\Eloquent\Factories\Factory<\App\Models\Bio> */ class BioFactory extends Factory diff --git a/database/factories/BookmarkFactory.php b/database/factories/BookmarkFactory.php index 0d10db4a..ddfe0f97 100644 --- a/database/factories/BookmarkFactory.php +++ b/database/factories/BookmarkFactory.php @@ -7,8 +7,6 @@ use Illuminate\Database\Eloquent\Factories\Factory; use Illuminate\Support\Carbon; /** - * @psalm-suppress UnusedClass - * * @extends \Illuminate\Database\Eloquent\Factories\Factory<\App\Models\Bookmark> */ class BookmarkFactory extends Factory diff --git a/database/factories/ContactFactory.php b/database/factories/ContactFactory.php index 57efafd9..1b0be43b 100644 --- a/database/factories/ContactFactory.php +++ b/database/factories/ContactFactory.php @@ -7,8 +7,6 @@ use Illuminate\Database\Eloquent\Factories\Factory; use Illuminate\Support\Carbon; /** - * @psalm-suppress UnusedClass - * * @extends \Illuminate\Database\Eloquent\Factories\Factory<\App\Models\Contact> */ class ContactFactory extends Factory diff --git a/database/factories/LikeFactory.php b/database/factories/LikeFactory.php index 92ad76ce..8bf4f62f 100644 --- a/database/factories/LikeFactory.php +++ b/database/factories/LikeFactory.php @@ -7,8 +7,6 @@ use Illuminate\Database\Eloquent\Factories\Factory; use Illuminate\Support\Carbon; /** - * @psalm-suppress UnusedClass - * * @extends \Illuminate\Database\Eloquent\Factories\Factory<\App\Models\Like> */ class LikeFactory extends Factory diff --git a/database/factories/MediaFactory.php b/database/factories/MediaFactory.php index 6f1e4838..ca253109 100644 --- a/database/factories/MediaFactory.php +++ b/database/factories/MediaFactory.php @@ -7,8 +7,6 @@ use Illuminate\Database\Eloquent\Factories\Factory; use Illuminate\Support\Carbon; /** - * @psalm-suppress UnusedClass - * * @extends \Illuminate\Database\Eloquent\Factories\Factory<\App\Models\Media> */ class MediaFactory extends Factory diff --git a/database/factories/MicropubClientFactory.php b/database/factories/MicropubClientFactory.php index 923a9df9..4916f404 100644 --- a/database/factories/MicropubClientFactory.php +++ b/database/factories/MicropubClientFactory.php @@ -6,8 +6,6 @@ use App\Models\MicropubClient; use Illuminate\Database\Eloquent\Factories\Factory; /** - * @psalm-suppress UnusedClass - * * @extends \Illuminate\Database\Eloquent\Factories\Factory<\App\Models\MicropubClient> */ class MicropubClientFactory extends Factory diff --git a/database/factories/NoteFactory.php b/database/factories/NoteFactory.php index 0ea928a6..e2238a23 100644 --- a/database/factories/NoteFactory.php +++ b/database/factories/NoteFactory.php @@ -8,8 +8,6 @@ use Illuminate\Database\Eloquent\Factories\Factory; use Illuminate\Support\Carbon; /** - * @psalm-suppress UnusedClass - * * @extends \Illuminate\Database\Eloquent\Factories\Factory<\App\Models\Note> */ class NoteFactory extends Factory diff --git a/database/factories/PlaceFactory.php b/database/factories/PlaceFactory.php index a9f23f0b..61bdd70f 100644 --- a/database/factories/PlaceFactory.php +++ b/database/factories/PlaceFactory.php @@ -6,8 +6,6 @@ use App\Models\Place; use Illuminate\Database\Eloquent\Factories\Factory; /** - * @psalm-suppress UnusedClass - * * @extends \Illuminate\Database\Eloquent\Factories\Factory<\App\Models\Place> */ class PlaceFactory extends Factory diff --git a/database/factories/SyndicationTargetFactory.php b/database/factories/SyndicationTargetFactory.php index f409a3f6..05243632 100644 --- a/database/factories/SyndicationTargetFactory.php +++ b/database/factories/SyndicationTargetFactory.php @@ -5,8 +5,6 @@ namespace Database\Factories; use Illuminate\Database\Eloquent\Factories\Factory; /** - * @psalm-suppress UnusedClass - * * @extends \Illuminate\Database\Eloquent\Factories\Factory<\App\Models\SyndicationTarget> */ class SyndicationTargetFactory extends Factory diff --git a/database/factories/TagFactory.php b/database/factories/TagFactory.php index b5478679..24cae028 100644 --- a/database/factories/TagFactory.php +++ b/database/factories/TagFactory.php @@ -6,8 +6,6 @@ use App\Models\Tag; use Illuminate\Database\Eloquent\Factories\Factory; /** - * @psalm-suppress UnusedClass - * * @extends \Illuminate\Database\Eloquent\Factories\Factory<\App\Models\Tag> */ class TagFactory extends Factory diff --git a/database/factories/UserFactory.php b/database/factories/UserFactory.php index e456f5d8..ba1ff997 100644 --- a/database/factories/UserFactory.php +++ b/database/factories/UserFactory.php @@ -7,8 +7,6 @@ use Illuminate\Database\Eloquent\Factories\Factory; use Illuminate\Support\Str; /** - * @psalm-suppress UnusedClass - * * @extends \Illuminate\Database\Eloquent\Factories\Factory<\App\Models\User> */ class UserFactory extends Factory diff --git a/database/factories/WebMentionFactory.php b/database/factories/WebMentionFactory.php index 08829bad..65dbb92f 100644 --- a/database/factories/WebMentionFactory.php +++ b/database/factories/WebMentionFactory.php @@ -6,8 +6,6 @@ use App\Models\WebMention; use Illuminate\Database\Eloquent\Factories\Factory; /** - * @psalm-suppress UnusedClass - * * @extends \Illuminate\Database\Eloquent\Factories\Factory<\App\Models\WebMention> */ class WebMentionFactory extends Factory diff --git a/database/seeders/ArticlesTableSeeder.php b/database/seeders/ArticlesTableSeeder.php index 898471fc..cbfd7a36 100644 --- a/database/seeders/ArticlesTableSeeder.php +++ b/database/seeders/ArticlesTableSeeder.php @@ -11,8 +11,6 @@ class ArticlesTableSeeder extends Seeder { /** * Seed the articles table. - * - * @psalm-suppress PossiblyUnusedMethod */ public function run(): void { diff --git a/database/seeders/BioSeeder.php b/database/seeders/BioSeeder.php index 05a60c33..093d0609 100644 --- a/database/seeders/BioSeeder.php +++ b/database/seeders/BioSeeder.php @@ -5,9 +5,6 @@ namespace Database\Seeders; use App\Models\Bio; use Illuminate\Database\Seeder; -/** - * @psalm-suppress UnusedClass - */ class BioSeeder extends Seeder { /** diff --git a/database/seeders/BookmarksTableSeeder.php b/database/seeders/BookmarksTableSeeder.php index a8c48f69..baa3580f 100644 --- a/database/seeders/BookmarksTableSeeder.php +++ b/database/seeders/BookmarksTableSeeder.php @@ -10,8 +10,6 @@ class BookmarksTableSeeder extends Seeder { /** * Seed the bookmarks table. - * - * @psalm-suppress PossiblyUnusedMethod */ public function run(): void { diff --git a/database/seeders/ClientsTableSeeder.php b/database/seeders/ClientsTableSeeder.php index 98d403b0..35dcb296 100644 --- a/database/seeders/ClientsTableSeeder.php +++ b/database/seeders/ClientsTableSeeder.php @@ -11,8 +11,6 @@ class ClientsTableSeeder extends Seeder { /** * Seed the clients table. - * - * @psalm-suppress PossiblyUnusedMethod */ public function run(): void { diff --git a/database/seeders/ContactsTableSeeder.php b/database/seeders/ContactsTableSeeder.php index 577fe153..3d191ec1 100644 --- a/database/seeders/ContactsTableSeeder.php +++ b/database/seeders/ContactsTableSeeder.php @@ -10,8 +10,6 @@ class ContactsTableSeeder extends Seeder { /** * Seed the contacts table. - * - * @psalm-suppress PossiblyUnusedMethod */ public function run(): void { diff --git a/database/seeders/DatabaseSeeder.php b/database/seeders/DatabaseSeeder.php index c91e2edb..5117a89d 100644 --- a/database/seeders/DatabaseSeeder.php +++ b/database/seeders/DatabaseSeeder.php @@ -4,9 +4,6 @@ namespace Database\Seeders; use Illuminate\Database\Seeder; -/** - * @psalm-suppress UnusedClass - */ class DatabaseSeeder extends Seeder { /** diff --git a/database/seeders/LikesTableSeeder.php b/database/seeders/LikesTableSeeder.php index 0436ad64..4600fb69 100644 --- a/database/seeders/LikesTableSeeder.php +++ b/database/seeders/LikesTableSeeder.php @@ -12,8 +12,6 @@ class LikesTableSeeder extends Seeder { /** * Seed the likes table. - * - * @psalm-suppress PossiblyUnusedMethod */ public function run(): void { diff --git a/database/seeders/NotesTableSeeder.php b/database/seeders/NotesTableSeeder.php index a9fbab12..630cfda6 100644 --- a/database/seeders/NotesTableSeeder.php +++ b/database/seeders/NotesTableSeeder.php @@ -14,8 +14,6 @@ class NotesTableSeeder extends Seeder { /** * Seed the notes table. - * - * @psalm-suppress PossiblyUnusedMethod */ public function run(): void { diff --git a/database/seeders/PlacesTableSeeder.php b/database/seeders/PlacesTableSeeder.php index 90a63ff8..70324ff8 100644 --- a/database/seeders/PlacesTableSeeder.php +++ b/database/seeders/PlacesTableSeeder.php @@ -9,8 +9,6 @@ class PlacesTableSeeder extends Seeder { /** * Seed the places table. - * - * @psalm-suppress PossiblyUnusedMethod */ public function run(): void { diff --git a/database/seeders/UsersTableSeeder.php b/database/seeders/UsersTableSeeder.php index dfc155cd..d9e608e2 100644 --- a/database/seeders/UsersTableSeeder.php +++ b/database/seeders/UsersTableSeeder.php @@ -9,8 +9,6 @@ class UsersTableSeeder extends Seeder { /** * Seed the users table. - * - * @psalm-suppress PossiblyUnusedMethod */ public function run(): void { diff --git a/database/seeders/WebMentionsTableSeeder.php b/database/seeders/WebMentionsTableSeeder.php index b7118343..46da28d5 100644 --- a/database/seeders/WebMentionsTableSeeder.php +++ b/database/seeders/WebMentionsTableSeeder.php @@ -9,8 +9,6 @@ class WebMentionsTableSeeder extends Seeder { /** * Seed the webmentions table. - * - * @psalm-suppress PossiblyUnusedMethod */ public function run(): void { diff --git a/package-lock.json b/package-lock.json index aa3f3839..96abf554 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,27 +8,16 @@ "name": "jbuk-frontend", "version": "0.0.1", "license": "CC0-1.0", - "dependencies": { - "@11ty/is-land": "^4.0.0", - "@zachleat/snow-fall": "^1.0.2" - }, "devDependencies": { "@eslint/js": "^9.6.0", - "@stylistic/eslint-plugin": "^3.0.0", + "@stylistic/eslint-plugin": "^4.2.0", + "esbuild": "^0.25.2", "eslint": "^9.7.0", - "globals": "^15.8.0", + "globals": "^16.0.0", + "lightningcss": "^1.29.3", + "lightningcss-cli": "^1.29.3", "stylelint": "^16.7.0", - "stylelint-config-standard": "^37.0.0" - } - }, - "node_modules/@11ty/is-land": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/@11ty/is-land/-/is-land-4.0.0.tgz", - "integrity": "sha512-RxbjF2+FzSu3rerHrWLRsvsPX2YM47RwXpdWCCzLhwRSsz5sJe9TnK7mphEld1gZnp2GeD5ByvhqjIc4CqidsQ==", - "license": "MIT", - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/11ty" + "stylelint-config-standard": "^38.0.0" } }, "node_modules/@babel/code-frame": { @@ -157,10 +146,435 @@ "url": "https://github.com/sponsors/wooorm" } }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.2.tgz", + "integrity": "sha512-wCIboOL2yXZym2cgm6mlA742s9QeJ8DjGVaL39dLN4rRwrOgOyYSnOaFPhKZGLb2ngj4EyfAFjsNJwPXZvseag==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.2.tgz", + "integrity": "sha512-NQhH7jFstVY5x8CKbcfa166GoV0EFkaPkCKBQkdPJFvo5u+nGXLEH/ooniLb3QI8Fk58YAx7nsPLozUWfCBOJA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.2.tgz", + "integrity": "sha512-5ZAX5xOmTligeBaeNEPnPaeEuah53Id2tX4c2CVP3JaROTH+j4fnfHCkr1PjXMd78hMst+TlkfKcW/DlTq0i4w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.2.tgz", + "integrity": "sha512-Ffcx+nnma8Sge4jzddPHCZVRvIfQ0kMsUsCMcJRHkGJ1cDmhe4SsrYIjLUKn1xpHZybmOqCWwB0zQvsjdEHtkg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.2.tgz", + "integrity": "sha512-MpM6LUVTXAzOvN4KbjzU/q5smzryuoNjlriAIx+06RpecwCkL9JpenNzpKd2YMzLJFOdPqBpuub6eVRP5IgiSA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.2.tgz", + "integrity": "sha512-5eRPrTX7wFyuWe8FqEFPG2cU0+butQQVNcT4sVipqjLYQjjh8a8+vUTfgBKM88ObB85ahsnTwF7PSIt6PG+QkA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.2.tgz", + "integrity": "sha512-mLwm4vXKiQ2UTSX4+ImyiPdiHjiZhIaE9QvC7sw0tZ6HoNMjYAqQpGyui5VRIi5sGd+uWq940gdCbY3VLvsO1w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.2.tgz", + "integrity": "sha512-6qyyn6TjayJSwGpm8J9QYYGQcRgc90nmfdUb0O7pp1s4lTY+9D0H9O02v5JqGApUyiHOtkz6+1hZNvNtEhbwRQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.2.tgz", + "integrity": "sha512-UHBRgJcmjJv5oeQF8EpTRZs/1knq6loLxTsjc3nxO9eXAPDLcWW55flrMVc97qFPbmZP31ta1AZVUKQzKTzb0g==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.2.tgz", + "integrity": "sha512-gq/sjLsOyMT19I8obBISvhoYiZIAaGF8JpeXu1u8yPv8BE5HlWYobmlsfijFIZ9hIVGYkbdFhEqC0NvM4kNO0g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.2.tgz", + "integrity": "sha512-bBYCv9obgW2cBP+2ZWfjYTU+f5cxRoGGQ5SeDbYdFCAZpYWrfjjfYwvUpP8MlKbP0nwZ5gyOU/0aUzZ5HWPuvQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.2.tgz", + "integrity": "sha512-SHNGiKtvnU2dBlM5D8CXRFdd+6etgZ9dXfaPCeJtz+37PIUlixvlIhI23L5khKXs3DIzAn9V8v+qb1TRKrgT5w==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.2.tgz", + "integrity": "sha512-hDDRlzE6rPeoj+5fsADqdUZl1OzqDYow4TB4Y/3PlKBD0ph1e6uPHzIQcv2Z65u2K0kpeByIyAjCmjn1hJgG0Q==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.2.tgz", + "integrity": "sha512-tsHu2RRSWzipmUi9UBDEzc0nLc4HtpZEI5Ba+Omms5456x5WaNuiG3u7xh5AO6sipnJ9r4cRWQB2tUjPyIkc6g==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.2.tgz", + "integrity": "sha512-k4LtpgV7NJQOml/10uPU0s4SAXGnowi5qBSjaLWMojNCUICNu7TshqHLAEbkBdAszL5TabfvQ48kK84hyFzjnw==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.2.tgz", + "integrity": "sha512-GRa4IshOdvKY7M/rDpRR3gkiTNp34M0eLTaC1a08gNrh4u488aPhuZOCpkF6+2wl3zAN7L7XIpOFBhnaE3/Q8Q==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.2.tgz", + "integrity": "sha512-QInHERlqpTTZ4FRB0fROQWXcYRD64lAoiegezDunLpalZMjcUcld3YzZmVJ2H/Cp0wJRZ8Xtjtj0cEHhYc/uUg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.2.tgz", + "integrity": "sha512-talAIBoY5M8vHc6EeI2WW9d/CkiO9MQJ0IOWX8hrLhxGbro/vBXJvaQXefW2cP0z0nQVTdQ/eNyGFV1GSKrxfw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.2.tgz", + "integrity": "sha512-voZT9Z+tpOxrvfKFyfDYPc4DO4rk06qamv1a/fkuzHpiVBMOhpjK+vBmWM8J1eiB3OLSMFYNaOaBNLXGChf5tg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.2.tgz", + "integrity": "sha512-dcXYOC6NXOqcykeDlwId9kB6OkPUxOEqU+rkrYVqJbK2hagWOMrsTGsMr8+rW02M+d5Op5NNlgMmjzecaRf7Tg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.2.tgz", + "integrity": "sha512-t/TkWwahkH0Tsgoq1Ju7QfgGhArkGLkF1uYz8nQS/PPFlXbP5YgRpqQR3ARRiC2iXoLTWFxc6DJMSK10dVXluw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.2.tgz", + "integrity": "sha512-cfZH1co2+imVdWCjd+D1gf9NjkchVhhdpgb1q5y6Hcv9TP6Zi9ZG/beI3ig8TvwT9lH9dlxLq5MQBBgwuj4xvA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.2.tgz", + "integrity": "sha512-7Loyjh+D/Nx/sOTzV8vfbB3GJuHdOQyrOryFdZvPHLf42Tk9ivBU5Aedi7iyX+x6rbn2Mh68T4qq1SDqJBQO5Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.2.tgz", + "integrity": "sha512-WRJgsz9un0nqZJ4MfhabxaD9Ft8KioqU3JMinOTvobbX6MOSUigSBlogP8QB3uxpJDsFS6yN+3FDBdqE5lg9kg==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.2.tgz", + "integrity": "sha512-kM3HKb16VIXZyIeVrM1ygYmZBKybX8N4p754bw390wGO3Tf2j4L2/WYL+4suWujpgf6GBYs3jv7TyUivdd05JA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, "node_modules/@eslint-community/eslint-utils": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.1.tgz", - "integrity": "sha512-s3O3waFUrMV8P/XaF/+ZTp1X9XBZW1a4B97ZnjQF2KYWaFD2A8KyFBsrsfSjEmjn3RGWAIuvlneuZm3CUK3jbA==", + "version": "4.5.1", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.5.1.tgz", + "integrity": "sha512-soEIOALTfTK6EjmKMMoLugwaP0rzkad90iIWd1hMO9ARkSAyjfMfkRRhLvD5qH7vvM0Cg72pieUfR6yh6XxC4w==", "dev": true, "license": "MIT", "dependencies": { @@ -200,13 +614,13 @@ } }, "node_modules/@eslint/config-array": { - "version": "0.19.1", - "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.19.1.tgz", - "integrity": "sha512-fo6Mtm5mWyKjA/Chy1BYTdn5mGJoDNjC7C64ug20ADsRDGrA85bN3uK3MaKbeRkRuuIEAR5N33Jr1pbm411/PA==", + "version": "0.20.0", + "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.20.0.tgz", + "integrity": "sha512-fxlS1kkIjx8+vy2SjuCB94q3htSNrufYTXubwiBFeaQHbH6Ipi43gFJq2zCMt6PHhImH3Xmr0NksKDvchWlpQQ==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@eslint/object-schema": "^2.1.5", + "@eslint/object-schema": "^2.1.6", "debug": "^4.3.1", "minimatch": "^3.1.2" }, @@ -238,10 +652,20 @@ "node": "*" } }, + "node_modules/@eslint/config-helpers": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.2.1.tgz", + "integrity": "sha512-RI17tsD2frtDu/3dmI7QRrD4bedNKPM08ziRYaC5AhkGrzIAJelm9kJU1TznK+apx6V+cqRz8tfpEeG3oIyjxw==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, "node_modules/@eslint/core": { - "version": "0.10.0", - "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.10.0.tgz", - "integrity": "sha512-gFHJ+xBOo4G3WRlR1e/3G8A6/KZAH6zcE/hkLRCZTi/B9avAG365QhFA8uOGzTMqgTghpn7/fSnscW++dpMSAw==", + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.12.0.tgz", + "integrity": "sha512-cmrR6pytBuSMTaBweKoGMwu3EiHiEC+DoyupPmlZ0HxBJBtIxwe+j/E4XPIKNx+Q74c8lXKPwYawBf5glsTkHg==", "dev": true, "license": "Apache-2.0", "dependencies": { @@ -252,9 +676,9 @@ } }, "node_modules/@eslint/eslintrc": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.2.0.tgz", - "integrity": "sha512-grOjVNN8P3hjJn/eIETF1wwd12DdnwFDoyceUJLYYdkpbwq3nLi+4fqrTAONx7XDALqlL220wC/RHSC/QTI/0w==", + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.1.tgz", + "integrity": "sha512-gtF186CXhIl1p4pJNGZw8Yc6RlshoePRvE0X91oPGb3vZ8pM3qOS9W9NGPat9LziaBV7XrJWGylNQXkGcnM3IQ==", "dev": true, "license": "MIT", "dependencies": { @@ -313,9 +737,9 @@ } }, "node_modules/@eslint/js": { - "version": "9.19.0", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.19.0.tgz", - "integrity": "sha512-rbq9/g38qjfqFLOVPvwjIvFFdNziEC5S65jmjPw5r6A//QH+W91akh9irMwjDN8zKUTak6W9EsAv4m/7Wnw0UQ==", + "version": "9.24.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.24.0.tgz", + "integrity": "sha512-uIY/y3z0uvOGX8cp1C2fiC4+ZmBhp6yZWkojtHL1YEMnRt1Y63HB9TM17proGEmeG7HeUY+UP36F0aknKYTpYA==", "dev": true, "license": "MIT", "engines": { @@ -323,9 +747,9 @@ } }, "node_modules/@eslint/object-schema": { - "version": "2.1.5", - "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.5.tgz", - "integrity": "sha512-o0bhxnL89h5Bae5T318nFoFzGy+YE5i/gGkoPAgkmTVdRKTiv3p8JHevPiPaMwoloKfEiiaHlawCqaZMqRm+XQ==", + "version": "2.1.6", + "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.6.tgz", + "integrity": "sha512-RBMg5FRL0I0gs51M/guSAj5/e14VQ4tpZnQNWwuDT66P14I43ItmPfIZRhO9fUVIPOAQXU47atlywZ/czoqFPA==", "dev": true, "license": "Apache-2.0", "engines": { @@ -333,19 +757,32 @@ } }, "node_modules/@eslint/plugin-kit": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.2.5.tgz", - "integrity": "sha512-lB05FkqEdUg2AA0xEbUz0SnkXT1LcCTa438W4IWTUh4hdOnVbQyOJ81OrDXsJk/LSiJHubgGEFoR5EHq1NsH1A==", + "version": "0.2.8", + "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.2.8.tgz", + "integrity": "sha512-ZAoA40rNMPwSm+AeHpCq8STiNAwzWLJuP8Xv4CHIc9wv/PSuExjMrmjfYNj682vW0OOiZ1HKxzvjQr9XZIisQA==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@eslint/core": "^0.10.0", + "@eslint/core": "^0.13.0", "levn": "^0.4.1" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" } }, + "node_modules/@eslint/plugin-kit/node_modules/@eslint/core": { + "version": "0.13.0", + "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.13.0.tgz", + "integrity": "sha512-yfkgDw1KR66rkT5A8ci4irzDysN7FRpq3ttJolR88OqQikAWqwA8j5VZyas+vjyBNFIJ7MfybJ9plMILI2UrCw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@types/json-schema": "^7.0.15" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, "node_modules/@humanfs/core": { "version": "0.19.1", "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz", @@ -399,9 +836,9 @@ } }, "node_modules/@humanwhocodes/retry": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.1.tgz", - "integrity": "sha512-c7hNEllBlenFTHBky65mhq8WD2kbN9Q6gk0bTk8lSBvc554jpXSkST1iePudpt7+A/AQvuHs9EMqjHDXMY1lrA==", + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.2.tgz", + "integrity": "sha512-xeO57FpIu4p1Ri3Jq/EXq4ClRm86dVF2z/+kvFnyqVYRavTZmaFaUBbWCOuuTh0o/g7DSsk6kc2vrS4Vl5oPOQ==", "dev": true, "license": "Apache-2.0", "engines": { @@ -413,9 +850,9 @@ } }, "node_modules/@keyv/serialize": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@keyv/serialize/-/serialize-1.0.2.tgz", - "integrity": "sha512-+E/LyaAeuABniD/RvUezWVXKpeuvwLEA9//nE9952zBaOdBd2mQ3pPoM8cUe2X6IcMByfuSLzmYqnYshG60+HQ==", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@keyv/serialize/-/serialize-1.0.3.tgz", + "integrity": "sha512-qnEovoOp5Np2JDGonIDL6Ayihw0RhnRh6vxPuHo4RDn1UOzwEo4AeIfpL6UGIrsceWrCMiVPgwRjbHu4vYFc3g==", "dev": true, "license": "MIT", "dependencies": { @@ -461,13 +898,13 @@ } }, "node_modules/@stylistic/eslint-plugin": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@stylistic/eslint-plugin/-/eslint-plugin-3.0.0.tgz", - "integrity": "sha512-9GJI6iBtGjOqSsyCKUvE6Vn7qDT52hbQaoq/SwxH6A1bciymZfvBfHIIrD3E7Koi2sjzOa/MNQ2XOguHtVJOyw==", + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@stylistic/eslint-plugin/-/eslint-plugin-4.2.0.tgz", + "integrity": "sha512-8hXezgz7jexGHdo5WN6JBEIPHCSFyyU4vgbxevu4YLVS5vl+sxqAAGyXSzfNDyR6xMNSH5H1x67nsXcYMOHtZA==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/utils": "8.13.0", + "@typescript-eslint/utils": "^8.23.0", "eslint-visitor-keys": "^4.2.0", "espree": "^10.3.0", "estraverse": "^5.3.0", @@ -477,13 +914,13 @@ "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "peerDependencies": { - "eslint": ">=8.40.0" + "eslint": ">=9.0.0" } }, "node_modules/@types/estree": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.6.tgz", - "integrity": "sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==", + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.7.tgz", + "integrity": "sha512-w28IoSUCJpidD/TGviZwwMJckNESJZXFu7NBZ5YJ4mEUnNraUn9Pm8HSZm/jDF1pDWYKspWE7oVphigUPRakIQ==", "dev": true, "license": "MIT" }, @@ -495,14 +932,14 @@ "license": "MIT" }, "node_modules/@typescript-eslint/scope-manager": { - "version": "8.13.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.13.0.tgz", - "integrity": "sha512-XsGWww0odcUT0gJoBZ1DeulY1+jkaHUciUq4jKNv4cpInbvvrtDoyBH9rE/n2V29wQJPk8iCH1wipra9BhmiMA==", + "version": "8.29.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.29.1.tgz", + "integrity": "sha512-2nggXGX5F3YrsGN08pw4XpMLO1Rgtnn4AzTegC2MDesv6q3QaTU5yU7IbS1tf1IwCR0Hv/1EFygLn9ms6LIpDA==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.13.0", - "@typescript-eslint/visitor-keys": "8.13.0" + "@typescript-eslint/types": "8.29.1", + "@typescript-eslint/visitor-keys": "8.29.1" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -513,9 +950,9 @@ } }, "node_modules/@typescript-eslint/types": { - "version": "8.13.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.13.0.tgz", - "integrity": "sha512-4cyFErJetFLckcThRUFdReWJjVsPCqyBlJTi6IDEpc1GWCIIZRFxVppjWLIMcQhNGhdWJJRYFHpHoDWvMlDzng==", + "version": "8.29.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.29.1.tgz", + "integrity": "sha512-VT7T1PuJF1hpYC3AGm2rCgJBjHL3nc+A/bhOp9sGMKfi5v0WufsX/sHCFBfNTx2F+zA6qBc/PD0/kLRLjdt8mQ==", "dev": true, "license": "MIT", "engines": { @@ -527,45 +964,20 @@ } }, "node_modules/@typescript-eslint/typescript-estree": { - "version": "8.13.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.13.0.tgz", - "integrity": "sha512-v7SCIGmVsRK2Cy/LTLGN22uea6SaUIlpBcO/gnMGT/7zPtxp90bphcGf4fyrCQl3ZtiBKqVTG32hb668oIYy1g==", + "version": "8.29.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.29.1.tgz", + "integrity": "sha512-l1enRoSaUkQxOQnbi0KPUtqeZkSiFlqrx9/3ns2rEDhGKfTa+88RmXqedC1zmVTOWrLc2e6DEJrTA51C9iLH5g==", "dev": true, - "license": "BSD-2-Clause", + "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.13.0", - "@typescript-eslint/visitor-keys": "8.13.0", + "@typescript-eslint/types": "8.29.1", + "@typescript-eslint/visitor-keys": "8.29.1", "debug": "^4.3.4", "fast-glob": "^3.3.2", "is-glob": "^4.0.3", "minimatch": "^9.0.4", "semver": "^7.6.0", - "ts-api-utils": "^1.3.0" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "node_modules/@typescript-eslint/utils": { - "version": "8.13.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.13.0.tgz", - "integrity": "sha512-A1EeYOND6Uv250nybnLZapeXpYMl8tkzYUxqmoKAWnI4sei3ihf2XdZVd+vVOmHGcp3t+P7yRrNsyyiXTvShFQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@eslint-community/eslint-utils": "^4.4.0", - "@typescript-eslint/scope-manager": "8.13.0", - "@typescript-eslint/types": "8.13.0", - "@typescript-eslint/typescript-estree": "8.13.0" + "ts-api-utils": "^2.0.1" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -575,18 +987,42 @@ "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "eslint": "^8.57.0 || ^9.0.0" + "typescript": ">=4.8.4 <5.9.0" } }, - "node_modules/@typescript-eslint/visitor-keys": { - "version": "8.13.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.13.0.tgz", - "integrity": "sha512-7N/+lztJqH4Mrf0lb10R/CbI1EaAMMGyF5y0oJvFoAhafwgiRA7TXyd8TFn8FC8k5y2dTsYogg238qavRGNnlw==", + "node_modules/@typescript-eslint/utils": { + "version": "8.29.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.29.1.tgz", + "integrity": "sha512-QAkFEbytSaB8wnmB+DflhUPz6CLbFWE2SnSCrRMEa+KnXIzDYbpsn++1HGvnfAsUY44doDXmvRkO5shlM/3UfA==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.13.0", - "eslint-visitor-keys": "^3.4.3" + "@eslint-community/eslint-utils": "^4.4.0", + "@typescript-eslint/scope-manager": "8.29.1", + "@typescript-eslint/types": "8.29.1", + "@typescript-eslint/typescript-estree": "8.29.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <5.9.0" + } + }, + "node_modules/@typescript-eslint/visitor-keys": { + "version": "8.29.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.29.1.tgz", + "integrity": "sha512-RGLh5CRaUEf02viP5c1Vh1cMGffQscyHe7HPAzGpfmfflFg1wUz2rYxd+OZqwpeypYvZ8UxSxuIpF++fmOzEcg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.29.1", + "eslint-visitor-keys": "^4.2.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -596,29 +1032,10 @@ "url": "https://opencollective.com/typescript-eslint" } }, - "node_modules/@typescript-eslint/visitor-keys/node_modules/eslint-visitor-keys": { - "version": "3.4.3", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", - "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/@zachleat/snow-fall": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@zachleat/snow-fall/-/snow-fall-1.0.3.tgz", - "integrity": "sha512-Y9srRbmO+k31vSm+eINYRV9DRoeWGV5/hlAn9o34bLpoWo+T5945v6XGBrFzQYjhyEGB4j/4zXuTW1zTxp2Reg==", - "license": "MIT" - }, "node_modules/acorn": { - "version": "8.14.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.0.tgz", - "integrity": "sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA==", + "version": "8.14.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.1.tgz", + "integrity": "sha512-OvQ/2pUDKmgfCg++xsTX1wGxfTaszcHVcTctW4UJB4hibJx2HXxxO5UmVgyjMa+ZDsiaf5wWLXYpRWMmBI0QHg==", "dev": true, "license": "MIT", "bin": { @@ -785,24 +1202,24 @@ } }, "node_modules/cacheable": { - "version": "1.8.8", - "resolved": "https://registry.npmjs.org/cacheable/-/cacheable-1.8.8.tgz", - "integrity": "sha512-OE1/jlarWxROUIpd0qGBSKFLkNsotY8pt4GeiVErUYh/NUeTNrT+SBksUgllQv4m6a0W/VZsLuiHb88maavqEw==", + "version": "1.8.10", + "resolved": "https://registry.npmjs.org/cacheable/-/cacheable-1.8.10.tgz", + "integrity": "sha512-0ZnbicB/N2R6uziva8l6O6BieBklArWyiGx4GkwAhLKhSHyQtRfM9T1nx7HHuHDKkYB/efJQhz3QJ6x/YqoZzA==", "dev": true, "license": "MIT", "dependencies": { - "hookified": "^1.7.0", - "keyv": "^5.2.3" + "hookified": "^1.8.1", + "keyv": "^5.3.2" } }, "node_modules/cacheable/node_modules/keyv": { - "version": "5.2.3", - "resolved": "https://registry.npmjs.org/keyv/-/keyv-5.2.3.tgz", - "integrity": "sha512-AGKecUfzrowabUv0bH1RIR5Vf7w+l4S3xtQAypKaUpTdIR1EbrAcTxHCrpo9Q+IWeUlFE2palRtgIQcgm+PQJw==", + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-5.3.2.tgz", + "integrity": "sha512-Lji2XRxqqa5Wg+CHLVfFKBImfJZ4pCSccu9eVWK6w4c2SDFLd8JAn1zqTuSFnsxb7ope6rMsnIHfp+eBbRBRZQ==", "dev": true, "license": "MIT", "dependencies": { - "@keyv/serialize": "^1.0.2" + "@keyv/serialize": "^1.0.3" } }, "node_modules/callsites": { @@ -970,6 +1387,16 @@ "dev": true, "license": "MIT" }, + "node_modules/detect-libc": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.3.tgz", + "integrity": "sha512-bwy0MGW55bG41VqxxypOsdSdGqLwXPI/focwgTYCFMbdUiBAxLg9CFzG08sz2aqzknwiX7Hkl0bQENjg8iLByw==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=8" + } + }, "node_modules/dir-glob": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", @@ -1010,6 +1437,47 @@ "is-arrayish": "^0.2.1" } }, + "node_modules/esbuild": { + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.2.tgz", + "integrity": "sha512-16854zccKPnC+toMywC+uKNeYSv+/eXkevRAfwRD/G9Cleq66m8XFIrigkbvauLLlCfDL45Q2cWegSg53gGBnQ==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.25.2", + "@esbuild/android-arm": "0.25.2", + "@esbuild/android-arm64": "0.25.2", + "@esbuild/android-x64": "0.25.2", + "@esbuild/darwin-arm64": "0.25.2", + "@esbuild/darwin-x64": "0.25.2", + "@esbuild/freebsd-arm64": "0.25.2", + "@esbuild/freebsd-x64": "0.25.2", + "@esbuild/linux-arm": "0.25.2", + "@esbuild/linux-arm64": "0.25.2", + "@esbuild/linux-ia32": "0.25.2", + "@esbuild/linux-loong64": "0.25.2", + "@esbuild/linux-mips64el": "0.25.2", + "@esbuild/linux-ppc64": "0.25.2", + "@esbuild/linux-riscv64": "0.25.2", + "@esbuild/linux-s390x": "0.25.2", + "@esbuild/linux-x64": "0.25.2", + "@esbuild/netbsd-arm64": "0.25.2", + "@esbuild/netbsd-x64": "0.25.2", + "@esbuild/openbsd-arm64": "0.25.2", + "@esbuild/openbsd-x64": "0.25.2", + "@esbuild/sunos-x64": "0.25.2", + "@esbuild/win32-arm64": "0.25.2", + "@esbuild/win32-ia32": "0.25.2", + "@esbuild/win32-x64": "0.25.2" + } + }, "node_modules/escape-string-regexp": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", @@ -1024,22 +1492,23 @@ } }, "node_modules/eslint": { - "version": "9.19.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.19.0.tgz", - "integrity": "sha512-ug92j0LepKlbbEv6hD911THhoRHmbdXt2gX+VDABAW/Ir7D3nqKdv5Pf5vtlyY6HQMTEP2skXY43ueqTCWssEA==", + "version": "9.24.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.24.0.tgz", + "integrity": "sha512-eh/jxIEJyZrvbWRe4XuVclLPDYSYYYgLy5zXGGxD6j8zjSAxFEzI2fL/8xNq6O2yKqVt+eF2YhV+hxjV6UKXwQ==", "dev": true, "license": "MIT", "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.12.1", - "@eslint/config-array": "^0.19.0", - "@eslint/core": "^0.10.0", - "@eslint/eslintrc": "^3.2.0", - "@eslint/js": "9.19.0", - "@eslint/plugin-kit": "^0.2.5", + "@eslint/config-array": "^0.20.0", + "@eslint/config-helpers": "^0.2.0", + "@eslint/core": "^0.12.0", + "@eslint/eslintrc": "^3.3.1", + "@eslint/js": "9.24.0", + "@eslint/plugin-kit": "^0.2.7", "@humanfs/node": "^0.16.6", "@humanwhocodes/module-importer": "^1.0.1", - "@humanwhocodes/retry": "^0.4.1", + "@humanwhocodes/retry": "^0.4.2", "@types/estree": "^1.0.6", "@types/json-schema": "^7.0.15", "ajv": "^6.12.4", @@ -1047,7 +1516,7 @@ "cross-spawn": "^7.0.6", "debug": "^4.3.2", "escape-string-regexp": "^4.0.0", - "eslint-scope": "^8.2.0", + "eslint-scope": "^8.3.0", "eslint-visitor-keys": "^4.2.0", "espree": "^10.3.0", "esquery": "^1.5.0", @@ -1084,9 +1553,9 @@ } }, "node_modules/eslint-scope": { - "version": "8.2.0", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.2.0.tgz", - "integrity": "sha512-PHlWUfG6lvPc3yvP5A4PNyBL1W8fkDUccmI21JUu/+GKZBoH/W5u6usENXUrWFRsyoW5ACUjFGgAFQp5gUlb/A==", + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.3.0.tgz", + "integrity": "sha512-pUNxi75F8MJ/GdeKtVLSbYg4ZI34J6C0C7sbL4YOp2exGwen7ZsuBqKzUhXd0qMQ362yET3z+uPwKeg/0C2XCQ==", "dev": true, "license": "BSD-2-Clause", "dependencies": { @@ -1280,9 +1749,9 @@ } }, "node_modules/fastq": { - "version": "1.18.0", - "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.18.0.tgz", - "integrity": "sha512-QKHXPW0hD8g4UET03SdOdunzSouc9N4AuHdsX8XNcTsuz+yYFILVNIX4l9yHABMhiEI9Db0JTTIpu0wB+Y1QQw==", + "version": "1.19.1", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.19.1.tgz", + "integrity": "sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ==", "dev": true, "license": "ISC", "dependencies": { @@ -1347,9 +1816,9 @@ } }, "node_modules/flatted": { - "version": "3.3.2", - "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.2.tgz", - "integrity": "sha512-AiwGJM8YcNOaobumgtng+6NHuOqC3A7MixFeDafM3X9cIUM+xUXoS5Vfgf+OihAYe20fxqNM9yPBXJzRtZ/4eA==", + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.3.tgz", + "integrity": "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==", "dev": true, "license": "ISC" }, @@ -1408,9 +1877,9 @@ } }, "node_modules/globals": { - "version": "15.14.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-15.14.0.tgz", - "integrity": "sha512-OkToC372DtlQeje9/zHIo5CT8lRP/FUgEOKBEhU4e0abL7J7CD24fD9ohiLN5hagG/kWCYj4K5oaxxtj2Z0Dig==", + "version": "16.0.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-16.0.0.tgz", + "integrity": "sha512-iInW14XItCXET01CQFqudPOWP2jYMl7T+QRQT+UNcR/iQncN/F0UNpgd76iFkBPgNQb4+X3LV9tLJYzwh+Gl3A==", "dev": true, "license": "MIT", "engines": { @@ -1459,9 +1928,9 @@ } }, "node_modules/hookified": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/hookified/-/hookified-1.7.0.tgz", - "integrity": "sha512-XQdMjqC1AyeOzfs+17cnIk7Wdfu1hh2JtcyNfBf5u9jHrT3iZUlGHxLTntFBuk5lwkqJ6l3+daeQdHK5yByHVA==", + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/hookified/-/hookified-1.8.1.tgz", + "integrity": "sha512-GrO2l93P8xCWBSTBX9l2BxI78VU/MAAYag+pG8curS3aBGy0++ZlxrQ7PdUOUVMbn5BwkGb6+eRrnf43ipnFEA==", "dev": true, "license": "MIT" }, @@ -1510,9 +1979,9 @@ } }, "node_modules/import-fresh": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", - "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", + "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==", "dev": true, "license": "MIT", "dependencies": { @@ -1699,6 +2168,488 @@ "node": ">= 0.8.0" } }, + "node_modules/lightningcss": { + "version": "1.29.3", + "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.29.3.tgz", + "integrity": "sha512-GlOJwTIP6TMIlrTFsxTerwC0W6OpQpCGuX1ECRLBUVRh6fpJH3xTqjCjRgQHTb4ZXexH9rtHou1Lf03GKzmhhQ==", + "dev": true, + "license": "MPL-2.0", + "dependencies": { + "detect-libc": "^2.0.3" + }, + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + }, + "optionalDependencies": { + "lightningcss-darwin-arm64": "1.29.3", + "lightningcss-darwin-x64": "1.29.3", + "lightningcss-freebsd-x64": "1.29.3", + "lightningcss-linux-arm-gnueabihf": "1.29.3", + "lightningcss-linux-arm64-gnu": "1.29.3", + "lightningcss-linux-arm64-musl": "1.29.3", + "lightningcss-linux-x64-gnu": "1.29.3", + "lightningcss-linux-x64-musl": "1.29.3", + "lightningcss-win32-arm64-msvc": "1.29.3", + "lightningcss-win32-x64-msvc": "1.29.3" + } + }, + "node_modules/lightningcss-cli": { + "version": "1.29.3", + "resolved": "https://registry.npmjs.org/lightningcss-cli/-/lightningcss-cli-1.29.3.tgz", + "integrity": "sha512-mgxjqu/XmceiHel6ihNShWM+KTx8wctg4zkQmcBmd0fTqkIENHq1yRWqMAIZQe3wzp75XP8CcA/cGmazlsDawg==", + "dev": true, + "hasInstallScript": true, + "license": "MPL-2.0", + "dependencies": { + "detect-libc": "^2.0.3" + }, + "bin": { + "lightningcss": "lightningcss" + }, + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + }, + "optionalDependencies": { + "lightningcss-cli-darwin-arm64": "1.29.3", + "lightningcss-cli-darwin-x64": "1.29.3", + "lightningcss-cli-freebsd-x64": "1.29.3", + "lightningcss-cli-linux-arm-gnueabihf": "1.29.3", + "lightningcss-cli-linux-arm64-gnu": "1.29.3", + "lightningcss-cli-linux-arm64-musl": "1.29.3", + "lightningcss-cli-linux-x64-gnu": "1.29.3", + "lightningcss-cli-linux-x64-musl": "1.29.3", + "lightningcss-cli-win32-arm64-msvc": "1.29.3", + "lightningcss-cli-win32-x64-msvc": "1.29.3" + } + }, + "node_modules/lightningcss-cli-darwin-arm64": { + "version": "1.29.3", + "resolved": "https://registry.npmjs.org/lightningcss-cli-darwin-arm64/-/lightningcss-cli-darwin-arm64-1.29.3.tgz", + "integrity": "sha512-brAybxeiv8K/16stzjguhmLhM9oUvLBlhWoPGzeR5DfdJudGiaC4AJ4jGWx5rMmh9Q85zlkgovi9VgJTbSUY0g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-cli-darwin-x64": { + "version": "1.29.3", + "resolved": "https://registry.npmjs.org/lightningcss-cli-darwin-x64/-/lightningcss-cli-darwin-x64-1.29.3.tgz", + "integrity": "sha512-C2ngmmhapdlWHi1pbZzvt39l3cqno/6sOOox3uHuK1Cwig0lTIXTdkr05ScBeysc7esOfyNVtK8W0BbUi03U4w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-cli-freebsd-x64": { + "version": "1.29.3", + "resolved": "https://registry.npmjs.org/lightningcss-cli-freebsd-x64/-/lightningcss-cli-freebsd-x64-1.29.3.tgz", + "integrity": "sha512-BATAVaW1x/LJxxIujgLVorEewHvoXfS8DJaHTmu+9n6a6f+VRagc1Mm4KS4Hr0gZ/qlZpuAjxwkbRaJ+szmp7g==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-cli-linux-arm-gnueabihf": { + "version": "1.29.3", + "resolved": "https://registry.npmjs.org/lightningcss-cli-linux-arm-gnueabihf/-/lightningcss-cli-linux-arm-gnueabihf-1.29.3.tgz", + "integrity": "sha512-bCIlYm8g+c5+yUihyC3KynQSPMu6jb2JsGJ5dalkcSQfDZ4A4lVQovTGU6MI0q/idU5aYuRY+FMoWJ4Vca+H6A==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-cli-linux-arm64-gnu": { + "version": "1.29.3", + "resolved": "https://registry.npmjs.org/lightningcss-cli-linux-arm64-gnu/-/lightningcss-cli-linux-arm64-gnu-1.29.3.tgz", + "integrity": "sha512-BgaeC9QR7BMlg2Kunoeb+ldQMoGPhbHooXoktWfPRzOACQxKCBrfQhh3DWaI0GPq+OtgjHU5WHvUk/dNBgwwdw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-cli-linux-arm64-musl": { + "version": "1.29.3", + "resolved": "https://registry.npmjs.org/lightningcss-cli-linux-arm64-musl/-/lightningcss-cli-linux-arm64-musl-1.29.3.tgz", + "integrity": "sha512-7rsfCyLytIV8Kevs2TmdnAxfF6QINXgXSN0RKVU0Z+JLOwS+Rtq311D9bD9TbvflJbY5kVGfwnqlhO3emKxCrw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-cli-linux-x64-gnu": { + "version": "1.29.3", + "resolved": "https://registry.npmjs.org/lightningcss-cli-linux-x64-gnu/-/lightningcss-cli-linux-x64-gnu-1.29.3.tgz", + "integrity": "sha512-QG6MRwt2P12F+Nn6Q3T1yUkVFp2o9KfiR3TFRQHlJ9CW097p5Rn9LpdhiLG5+451mZYHSvUIfFeIf4agv4URcQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-cli-linux-x64-musl": { + "version": "1.29.3", + "resolved": "https://registry.npmjs.org/lightningcss-cli-linux-x64-musl/-/lightningcss-cli-linux-x64-musl-1.29.3.tgz", + "integrity": "sha512-qyfNGsXDorHq66HdVaeqDp2lC/r74AbC3yJv8w3UorBaLNo9wr/fPKLE/JHPp9/Gf4z90jsTQtMr/KRGTQocLA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-cli-win32-arm64-msvc": { + "version": "1.29.3", + "resolved": "https://registry.npmjs.org/lightningcss-cli-win32-arm64-msvc/-/lightningcss-cli-win32-arm64-msvc-1.29.3.tgz", + "integrity": "sha512-jkX/RGUZC9DjNV65caDebQfDXFwyuA0fRe1eyhL/Ltc21RrRYoc9Hr4UiwZQ2WGHvRAsiarx4GhGMp4mIgTa0g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-cli-win32-x64-msvc": { + "version": "1.29.3", + "resolved": "https://registry.npmjs.org/lightningcss-cli-win32-x64-msvc/-/lightningcss-cli-win32-x64-msvc-1.29.3.tgz", + "integrity": "sha512-TaCZkB1TWkhpNnn7Wyu/hycgQNllHZFAe8fQSxu3aZmwDNyQ9rkfTUETbSe9GEfTSxzp5x4VOPxIA/E0auWciQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-darwin-arm64": { + "version": "1.29.3", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.29.3.tgz", + "integrity": "sha512-fb7raKO3pXtlNbQbiMeEu8RbBVHnpyqAoxTyTRMEWFQWmscGC2wZxoHzZ+YKAepUuKT9uIW5vL2QbFivTgprZg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-darwin-x64": { + "version": "1.29.3", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.29.3.tgz", + "integrity": "sha512-KF2XZ4ZdmDGGtEYmx5wpzn6u8vg7AdBHaEOvDKu8GOs7xDL/vcU2vMKtTeNe1d4dogkDdi3B9zC77jkatWBwEQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-freebsd-x64": { + "version": "1.29.3", + "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.29.3.tgz", + "integrity": "sha512-VUWeVf+V1UM54jv9M4wen9vMlIAyT69Krl9XjI8SsRxz4tdNV/7QEPlW6JASev/pYdiynUCW0pwaFquDRYdxMw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm-gnueabihf": { + "version": "1.29.3", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.29.3.tgz", + "integrity": "sha512-UhgZ/XVNfXQVEJrMIWeK1Laj8KbhjbIz7F4znUk7G4zeGw7TRoJxhb66uWrEsonn1+O45w//0i0Fu0wIovYdYg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm64-gnu": { + "version": "1.29.3", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.29.3.tgz", + "integrity": "sha512-Pqau7jtgJNmQ/esugfmAT1aCFy/Gxc92FOxI+3n+LbMHBheBnk41xHDhc0HeYlx9G0xP5tK4t0Koy3QGGNqypw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm64-musl": { + "version": "1.29.3", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.29.3.tgz", + "integrity": "sha512-dxakOk66pf7KLS7VRYFO7B8WOJLecE5OPL2YOk52eriFd/yeyxt2Km5H0BjLfElokIaR+qWi33gB8MQLrdAY3A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-x64-gnu": { + "version": "1.29.3", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.29.3.tgz", + "integrity": "sha512-ySZTNCpbfbK8rqpKJeJR2S0g/8UqqV3QnzcuWvpI60LWxnFN91nxpSSwCbzfOXkzKfar9j5eOuOplf+klKtINg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-x64-musl": { + "version": "1.29.3", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.29.3.tgz", + "integrity": "sha512-3pVZhIzW09nzi10usAXfIGTTSTYQ141dk88vGFNCgawIzayiIzZQxEcxVtIkdvlEq2YuFsL9Wcj/h61JHHzuFQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-win32-arm64-msvc": { + "version": "1.29.3", + "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.29.3.tgz", + "integrity": "sha512-VRnkAvtIkeWuoBJeGOTrZxsNp4HogXtcaaLm8agmbYtLDOhQdpgxW6NjZZjDXbvGF+eOehGulXZ3C1TiwHY4QQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-win32-x64-msvc": { + "version": "1.29.3", + "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.29.3.tgz", + "integrity": "sha512-IszwRPu2cPnDQsZpd7/EAr0x2W7jkaWqQ1SwCVIZ/tSbZVXPLt6k8s6FkcyBjViCzvB5CW0We0QbbP7zp2aBjQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, "node_modules/lines-and-columns": { "version": "1.2.4", "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", @@ -1828,9 +2779,9 @@ "license": "MIT" }, "node_modules/nanoid": { - "version": "3.3.8", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.8.tgz", - "integrity": "sha512-WNLf5Sd8oZxOm+TzppcYk8gVOgP+l58xNy58D0nbUnOxOWRWvlcCV4kUF7ltmI6PsrLl/BgKEyS4mqsGChFN0w==", + "version": "3.3.11", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", + "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", "dev": true, "funding": [ { @@ -1996,9 +2947,9 @@ } }, "node_modules/postcss": { - "version": "8.5.1", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.1.tgz", - "integrity": "sha512-6oz2beyjc5VMn/KV1pPw8fliQkhBXrVn1Z3TVyqZxU8kZpzEKhBdmCFqI6ZbmGtamQvQGuU1sgPTk8ZrXDD7jQ==", + "version": "8.5.3", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.3.tgz", + "integrity": "sha512-dle9A3yYxlBSrt8Fu+IpjGT8SY8hN0mlaA6GY8t0P5PjIOZemULz/E2Bnm/2dcUOena75OTNkHI76uZBNUUq3A==", "dev": true, "funding": [ { @@ -2059,9 +3010,9 @@ } }, "node_modules/postcss-selector-parser": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.0.0.tgz", - "integrity": "sha512-9RbEr1Y7FFfptd/1eEdntyjMwLeghW1bHX9GWjXo19vx4ytPQhANltvVxDggzJl7mnWM+dX28kb6cyS/4iQjlQ==", + "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": { @@ -2141,9 +3092,9 @@ } }, "node_modules/reusify": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", - "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz", + "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==", "dev": true, "license": "MIT", "engines": { @@ -2176,9 +3127,9 @@ } }, "node_modules/semver": { - "version": "7.6.3", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", - "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", + "version": "7.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.1.tgz", + "integrity": "sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA==", "dev": true, "license": "ISC", "bin": { @@ -2304,9 +3255,9 @@ } }, "node_modules/stylelint": { - "version": "16.13.2", - "resolved": "https://registry.npmjs.org/stylelint/-/stylelint-16.13.2.tgz", - "integrity": "sha512-wDlgh0mRO9RtSa3TdidqHd0nOG8MmUyVKl+dxA6C1j8aZRzpNeEgdhFmU5y4sZx4Fc6r46p0fI7p1vR5O2DZqA==", + "version": "16.18.0", + "resolved": "https://registry.npmjs.org/stylelint/-/stylelint-16.18.0.tgz", + "integrity": "sha512-OXb68qzesv7J70BSbFwfK3yTVLEVXiQ/ro6wUE4UrSbKCMjLLA02S8Qq3LC01DxKyVjk7z8xh35aB4JzO3/sNA==", "dev": true, "funding": [ { @@ -2333,12 +3284,12 @@ "debug": "^4.3.7", "fast-glob": "^3.3.3", "fastest-levenshtein": "^1.0.16", - "file-entry-cache": "^10.0.5", + "file-entry-cache": "^10.0.7", "global-modules": "^2.0.0", "globby": "^11.1.0", "globjoin": "^0.1.4", "html-tags": "^3.3.1", - "ignore": "^7.0.1", + "ignore": "^7.0.3", "imurmurhash": "^0.1.4", "is-plain-object": "^5.0.0", "known-css-properties": "^0.35.0", @@ -2347,14 +3298,14 @@ "micromatch": "^4.0.8", "normalize-path": "^3.0.0", "picocolors": "^1.1.1", - "postcss": "^8.4.49", + "postcss": "^8.5.3", "postcss-resolve-nested-selector": "^0.1.6", "postcss-safe-parser": "^7.0.1", - "postcss-selector-parser": "^7.0.0", + "postcss-selector-parser": "^7.1.0", "postcss-value-parser": "^4.2.0", "resolve-from": "^5.0.0", "string-width": "^4.2.3", - "supports-hyperlinks": "^3.1.0", + "supports-hyperlinks": "^3.2.0", "svg-tags": "^1.0.0", "table": "^6.9.0", "write-file-atomic": "^5.0.1" @@ -2367,9 +3318,9 @@ } }, "node_modules/stylelint-config-recommended": { - "version": "15.0.0", - "resolved": "https://registry.npmjs.org/stylelint-config-recommended/-/stylelint-config-recommended-15.0.0.tgz", - "integrity": "sha512-9LejMFsat7L+NXttdHdTq94byn25TD+82bzGRiV1Pgasl99pWnwipXS5DguTpp3nP1XjvLXVnEJIuYBfsRjRkA==", + "version": "16.0.0", + "resolved": "https://registry.npmjs.org/stylelint-config-recommended/-/stylelint-config-recommended-16.0.0.tgz", + "integrity": "sha512-4RSmPjQegF34wNcK1e1O3Uz91HN8P1aFdFzio90wNK9mjgAI19u5vsU868cVZboKzCaa5XbpvtTzAAGQAxpcXA==", "dev": true, "funding": [ { @@ -2386,13 +3337,13 @@ "node": ">=18.12.0" }, "peerDependencies": { - "stylelint": "^16.13.0" + "stylelint": "^16.16.0" } }, "node_modules/stylelint-config-standard": { - "version": "37.0.0", - "resolved": "https://registry.npmjs.org/stylelint-config-standard/-/stylelint-config-standard-37.0.0.tgz", - "integrity": "sha512-+6eBlbSTrOn/il2RlV0zYGQwRTkr+WtzuVSs1reaWGObxnxLpbcspCUYajVQHonVfxVw2U+h42azGhrBvcg8OA==", + "version": "38.0.0", + "resolved": "https://registry.npmjs.org/stylelint-config-standard/-/stylelint-config-standard-38.0.0.tgz", + "integrity": "sha512-uj3JIX+dpFseqd/DJx8Gy3PcRAJhlEZ2IrlFOc4LUxBX/PNMEQ198x7LCOE2Q5oT9Vw8nyc4CIL78xSqPr6iag==", "dev": true, "funding": [ { @@ -2406,13 +3357,13 @@ ], "license": "MIT", "dependencies": { - "stylelint-config-recommended": "^15.0.0" + "stylelint-config-recommended": "^16.0.0" }, "engines": { "node": ">=18.12.0" }, "peerDependencies": { - "stylelint": "^16.13.0" + "stylelint": "^16.18.0" } }, "node_modules/stylelint/node_modules/balanced-match": { @@ -2423,25 +3374,25 @@ "license": "MIT" }, "node_modules/stylelint/node_modules/file-entry-cache": { - "version": "10.0.5", - "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-10.0.5.tgz", - "integrity": "sha512-umpQsJrBNsdMDgreSryMEXvJh66XeLtZUwA8Gj7rHGearGufUFv6rB/bcXRFsiGWw/VeSUgUofF4Rf2UKEOrTA==", + "version": "10.0.8", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-10.0.8.tgz", + "integrity": "sha512-FGXHpfmI4XyzbLd3HQ8cbUcsFGohJpZtmQRHr8z8FxxtCe2PcpgIlVLwIgunqjvRmXypBETvwhV4ptJizA+Y1Q==", "dev": true, "license": "MIT", "dependencies": { - "flat-cache": "^6.1.5" + "flat-cache": "^6.1.8" } }, "node_modules/stylelint/node_modules/flat-cache": { - "version": "6.1.5", - "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-6.1.5.tgz", - "integrity": "sha512-QR+2kN38f8nMfiIQ1LHYjuDEmZNZVjxuxY+HufbS3BW0EX01Q5OnH7iduOYRutmgiXb797HAKcXUeXrvRjjgSQ==", + "version": "6.1.8", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-6.1.8.tgz", + "integrity": "sha512-R6MaD3nrJAtO7C3QOuS79ficm2pEAy++TgEUD8ii1LVlbcgZ9DtASLkt9B+RZSFCzm7QHDMlXPsqqB6W2Pfr1Q==", "dev": true, "license": "MIT", "dependencies": { - "cacheable": "^1.8.7", - "flatted": "^3.3.2", - "hookified": "^1.6.0" + "cacheable": "^1.8.9", + "flatted": "^3.3.3", + "hookified": "^1.8.1" } }, "node_modules/stylelint/node_modules/ignore": { @@ -2478,9 +3429,9 @@ } }, "node_modules/supports-hyperlinks": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/supports-hyperlinks/-/supports-hyperlinks-3.1.0.tgz", - "integrity": "sha512-2rn0BZ+/f7puLOHZm1HOJfwBggfaHXUpPUSSG/SWM4TWp5KCfmNYwnC3hruy2rZlMnmWZ+QAGpZfchu3f3695A==", + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/supports-hyperlinks/-/supports-hyperlinks-3.2.0.tgz", + "integrity": "sha512-zFObLMyZeEwzAoKCyu1B91U79K2t7ApXuQfo8OuxwXLDgcKxuwM+YvcbIhm6QWqz7mHUH1TVytR1PwVVjEuMig==", "dev": true, "license": "MIT", "dependencies": { @@ -2491,7 +3442,7 @@ "node": ">=14.18" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "url": "https://github.com/chalk/supports-hyperlinks?sponsor=1" } }, "node_modules/svg-tags": { @@ -2555,16 +3506,16 @@ } }, "node_modules/ts-api-utils": { - "version": "1.4.3", - "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.4.3.tgz", - "integrity": "sha512-i3eMG77UTMD0hZhgRS562pv83RC6ukSAC2GMNWc+9dieh/+jDM5u5YG+NHX6VNDRHQcHwmsTHctP9LhbC3WxVw==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.1.0.tgz", + "integrity": "sha512-CUgTZL1irw8u29bzrOD/nH85jqyc74D6SshFgujOIA7osm2Rz7dYH77agkx7H4FBNxDq7Cjf+IjaX/8zwFW+ZQ==", "dev": true, "license": "MIT", "engines": { - "node": ">=16" + "node": ">=18.12" }, "peerDependencies": { - "typescript": ">=4.2.0" + "typescript": ">=4.8.4" } }, "node_modules/type-check": { @@ -2581,9 +3532,9 @@ } }, "node_modules/typescript": { - "version": "5.7.3", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.7.3.tgz", - "integrity": "sha512-84MVSjMEHP+FQRPy3pX9sTVV/INIex71s9TL2Gm5FG/WG1SqXeKyZ0k7/blY/4FdOzI12CBy1vGc4og/eus0fw==", + "version": "5.8.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.3.tgz", + "integrity": "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==", "dev": true, "license": "Apache-2.0", "peer": true, diff --git a/package.json b/package.json index c70f52f0..a6e358f3 100644 --- a/package.json +++ b/package.json @@ -7,21 +7,23 @@ "license": "CC0-1.0", "devDependencies": { "@eslint/js": "^9.6.0", - "@stylistic/eslint-plugin": "^3.0.0", + "@stylistic/eslint-plugin": "^4.2.0", + "esbuild": "^0.25.2", "eslint": "^9.7.0", - "globals": "^15.8.0", + "globals": "^16.0.0", + "lightningcss": "^1.29.3", + "lightningcss-cli": "^1.29.3", "stylelint": "^16.7.0", - "stylelint-config-standard": "^37.0.0" + "stylelint-config-standard": "^38.0.0" }, "scripts": { "eslint": "eslint public/assets/js/*.js", - "stylelint": "stylelint public/assets/css/*.css", + "stylelint": "stylelint resources/css/*.css", "lint": "npm run eslint && npm run stylelint", + "lightningcss": "lightningcss --output-dir public/assets/css --sourcemap --bundle --minify resources/css/app.css", + "fix-sourcemap": "./scripts/fix-sourcemap.sh", + "build-css": "npm run lightningcss && npm run fix-sourcemap", "compress": "./scripts/compress.sh", "build": "npm run lint && npm run compress" - }, - "dependencies": { - "@11ty/is-land": "^4.0.0", - "@zachleat/snow-fall": "^1.0.2" } } diff --git a/postcss.config.js b/postcss.config.js deleted file mode 100644 index f0ba5a6f..00000000 --- a/postcss.config.js +++ /dev/null @@ -1,16 +0,0 @@ -module.exports = { - plugins: { - 'postcss-import': {}, - 'autoprefixer': {}, - '@csstools/postcss-oklab-function': { - preserve: true - }, - 'postcss-nesting': {}, - 'postcss-combine-media-query': {}, - 'postcss-combine-duplicated-selectors': { - removeDuplicatedProperties: true, - removeDuplicatedValues: true - }, - 'cssnano': { preset: 'default' }, - } -}; diff --git a/psalm.xml b/psalm.xml deleted file mode 100644 index da693b64..00000000 --- a/psalm.xml +++ /dev/null @@ -1,18 +0,0 @@ - - - - - - - - - - - diff --git a/public/assets/css/app.css b/public/assets/css/app.css index 12bddcc2..07bfbf3d 100644 --- a/public/assets/css/app.css +++ b/public/assets/css/app.css @@ -1,8 +1,2 @@ -@import url('variables.css'); -@import url('fonts.css'); -@import url('layout.css'); -@import url('colours.css'); -@import url('code.css'); -@import url('content.css'); -@import url('notes.css'); -@import url('indieauth.css'); +:root{--font-family-headings:"Rockwell","Rockwell Nova","Roboto Slab","DejaVu Serif","Sitka Small",serif;--font-family-body:"Charter","Bitstream Charter","Sitka Text","Cambria",serif;--font-family-monospace:ui-monospace,"Cascadia Code","Source Code Pro","Menlo","Consolas","DejaVu Sans Mono",monospace;--font-size-sm:.75rem;--font-size-base:1rem;--font-size-md:1.25rem;--font-size-lg:1.5rem;--font-size-xl:1.75rem;--font-size-xxl:2rem;--font-size-xxxl:2.25rem;--color-primary:oklch(36.8% .1 125.505);--color-secondary:oklch(96.3% .1 125.505);--color-link:oklch(48.09% .146 241.41);--color-link-visited:oklch(70.44% .21 304.41);--color-primary-shadow:oklch(19.56% .054 125.505/.4);--rss-color-link:oklch(67.59% .189 42.04);--color-danger:oklch(64.41% .281 23.29);--color-danger-shadow:oklch(64.41% .281 23.29/.1)}body{font-family:var(--font-family-body);font-size:var(--font-size-md)}code{font-family:var(--font-family-monospace)}h1,h2,h3,h4,h5,h6{font-family:var(--font-family-headings)}.grid{grid-template-rows:min-content 1fr min-content;grid-template-columns:5vw 1fr 5vw;row-gap:1rem;display:grid}#site-header{grid-area:1/2/2/3;& .rss-icon{& svg{width:auto;height:1rem}}}main{grid-area:2/2/3/3}.h-feed{flex-direction:column;gap:2rem;display:flex}.h-entry{& p:first-of-type,& h1:first-of-type{margin-block-start:0}}.pagination{margin-block-start:1rem}footer{grid-area:3/2/4/3;& .iwc-logo{max-width:85vw}& .footer-actions{flex-direction:row;gap:1rem;display:flex}}body{background-color:var(--color-secondary);color:var(--color-primary)}a{color:var(--color-link);&:visited{color:var(--color-link-visited)}&.auth:visited{color:var(--color-link)}}#site-header{& a:visited{color:var(--color-link)}& .rss-icon{& svg{color:var(--rss-color-link)}}}.hljs{border-radius:.5rem}.h-card{& .hovercard{z-index:100;box-shadow:0 .5rem .5rem .5rem var(--color-primary-shadow);background-color:var(--color-secondary);opacity:0;border-radius:1rem;flex-direction:column;gap:.5rem;width:fit-content;padding:1rem;transition:opacity .5s ease-in-out;display:none;position:absolute;& .u-photo{max-width:6rem}& .social-icon{width:1rem;height:1rem}}&:hover{& .hovercard{opacity:1;display:flex}}}.h-entry{border-inline-start:1px solid var(--color-primary);padding-inline-start:.5rem;& .reply-to{font-style:italic}& .post-info{& a{text-decoration:none}}& .note-metadata{flex-direction:row;gap:1rem;display:flex;& .replies,& .likes,& .reposts{flex-direction:row;align-items:center;gap:.5rem;display:inline-flex}& .syndication-links{flex-flow:wrap;& a{text-decoration:none;& svg{width:1rem;height:1rem}}}}}.feather{stroke:currentColor;stroke-width:2px;stroke-linecap:round;stroke-linejoin:round;fill:none;width:24px;height:24px}.sr-only{clip:rect(0 0 0 0);clip-path:inset(50%);white-space:nowrap;width:1px;height:1px;position:absolute;overflow:hidden}main{&>.u-comment{border-inline-start:1px solid var(--color-primary);margin-block-start:2rem;margin-inline-start:2rem;padding-inline-start:.5rem;& .mini-h-card{flex-direction:row;align-items:baseline;display:inline-flex;& .u-photo{border-radius:50%;width:2rem;height:2rem;margin-block-end:.5rem}}}& .notes-subtitle{font-size:1.2rem;font-weight:600}& .webmentions-author-list{flex-flow:wrap;gap:1rem;display:flex;& img{border-radius:50%;width:4rem;height:4rem}}}.indieauth{& .error{color:var(--color-danger);background-color:var(--color-danger-shadow);border:1px solid var(--color-danger);border-radius:.5rem;width:fit-content;margin-block-end:1rem;padding-block:.5rem;padding-inline:1rem;display:flex}} +/*# sourceMappingURL=/assets/css/app.css.map */ diff --git a/public/assets/css/app.css.br b/public/assets/css/app.css.br index 8a4bcff6..405c55ea 100644 Binary files a/public/assets/css/app.css.br and b/public/assets/css/app.css.br differ diff --git a/public/assets/css/app.css.map b/public/assets/css/app.css.map new file mode 100644 index 00000000..877478ee --- /dev/null +++ b/public/assets/css/app.css.map @@ -0,0 +1 @@ +{"version":3,"mappings":"AGAA,4yBEAA,uEAKA,8CAIA,0DJTA,iHAOA,+BAIE,YACE,+BAOJ,uBAKA,oDAMA,SACE,2DAMF,oCAIA,yBAIE,2BAIA,4DKjDF,wEAKA,0BAGE,0CAIA,wCAKF,aACE,oCAIA,YACE,oCEvBJ,0BCAA,QACE,0RAcE,0BAIA,uCAMF,QACE,sCFxBJ,uFAIE,8BAIA,aACE,0BAKF,0DAKE,mGASA,oCAGE,yBAGE,iCASR,0HAUA,2HHvDA,KACE,4IAME,2EAKE,6EASJ,mDAKA,gEAKE,iDF/BJ,WACE","sources":["resources/css/app.css","resources/css/layout.css","resources/css/indieauth.css","resources/css/variables.css","resources/css/notes.css","resources/css/colours.css","resources/css/content.css","resources/css/code.css","resources/css/fonts.css","resources/css/h-card.css"],"sourcesContent":["@import url('variables.css');\n@import url('fonts.css');\n@import url('layout.css');\n@import url('colours.css');\n@import url('code.css');\n@import url('content.css');\n@import url('notes.css');\n@import url('indieauth.css');\n",".grid {\n display: grid;\n grid-template-columns: 5vw 1fr 5vw;\n grid-template-rows: min-content 1fr min-content;\n row-gap: 1rem;\n}\n\n#site-header {\n grid-column: 2 / 3;\n grid-row: 1 / 2;\n\n & .rss-icon {\n & svg {\n width: auto;\n height: 1rem;\n }\n }\n}\n\nmain {\n grid-column: 2 / 3;\n grid-row: 2 / 3;\n}\n\n.h-feed {\n display: flex;\n flex-direction: column;\n gap: 2rem;\n}\n\n.h-entry {\n & p:first-of-type,\n & h1:first-of-type {\n margin-block-start: 0;\n }\n}\n\n.pagination {\n margin-block-start: 1rem;\n}\n\nfooter {\n grid-column: 2 / 3;\n grid-row: 3 / 4;\n\n & .iwc-logo {\n max-width: 85vw;\n }\n\n & .footer-actions {\n display: flex;\n flex-direction: row;\n gap: 1rem;\n }\n}\n",".indieauth {\n .error {\n color: var(--color-danger);\n background-color: var(--color-danger-shadow);\n border: 1px solid var(--color-danger);\n border-radius: .5rem;\n display: flex;\n padding-inline: 1rem;\n padding-block: .5rem;\n width: fit-content;\n margin-block-end: 1rem;\n }\n}\n",":root {\n /* Font Family */\n --font-family-headings: 'Rockwell', 'Rockwell Nova', 'Roboto Slab', 'DejaVu Serif', 'Sitka Small', serif;\n --font-family-body: 'Charter', 'Bitstream Charter', 'Sitka Text', 'Cambria', serif;\n --font-family-monospace: ui-monospace, 'Cascadia Code', 'Source Code Pro', 'Menlo', 'Consolas', 'DejaVu Sans Mono', monospace;\n\n /* Font Size */\n --font-size-sm: 0.75rem; /* 12px */\n --font-size-base: 1rem; /* 16px, base */\n --font-size-md: 1.25rem; /* 20px */\n --font-size-lg: 1.5rem; /* 24px */\n --font-size-xl: 1.75rem; /* 28px */\n --font-size-xxl: 2rem; /* 32px */\n --font-size-xxxl: 2.25rem; /* 36px */\n\n /* Colours */\n --color-primary: oklch(36.8% 0.1 125.505deg);\n --color-secondary: oklch(96.3% 0.1 125.505deg);\n --color-link: oklch(48.09% 0.146 241.41deg);\n --color-link-visited: oklch(70.44% 0.21 304.41deg);\n --color-primary-shadow: oklch(19.56% 0.054 125.505deg / 40%);\n --rss-color-link: oklch(67.59% 0.189 42.04deg);\n --color-danger: oklch(64.41% 0.281 23.29deg);\n --color-danger-shadow: oklch(64.41% 0.281 23.29deg / 10%);\n}\n","main {\n & > .u-comment {\n margin-block-start: 2rem;\n margin-inline-start: 2rem;\n border-inline-start: 1px solid var(--color-primary);\n padding-inline-start: .5rem;\n\n & .mini-h-card {\n display: inline-flex;\n flex-direction: row;\n align-items: baseline;\n\n & .u-photo {\n width: 2rem;\n height: 2rem;\n border-radius: 50%;\n margin-block-end: 0.5rem;\n }\n }\n }\n\n & .notes-subtitle {\n font-size: 1.2rem;\n font-weight: 600;\n }\n\n & .webmentions-author-list {\n display: flex;\n flex-flow: row wrap;\n gap: 1rem;\n\n & img {\n width: 4rem;\n height: 4rem;\n border-radius: 50%;\n }\n }\n}\n","body {\n background-color: var(--color-secondary);\n color: var(--color-primary);\n}\n\na {\n color: var(--color-link);\n\n &:visited {\n color: var(--color-link-visited);\n }\n\n &.auth:visited {\n color: var(--color-link);\n }\n}\n\n#site-header {\n & a:visited {\n color: var(--color-link);\n }\n\n & .rss-icon {\n & svg {\n color: var(--rss-color-link);\n }\n }\n}\n","@import url('h-card.css');\n\n.h-entry {\n border-inline-start: 1px solid var(--color-primary);\n padding-inline-start: .5rem;\n\n & .reply-to {\n font-style: italic;\n }\n\n & .post-info {\n & a {\n text-decoration: none;\n }\n }\n\n & .note-metadata {\n display: flex;\n flex-direction: row;\n gap: 1rem;\n\n & .replies,\n & .likes,\n & .reposts {\n display: inline-flex;\n flex-direction: row;\n gap: .5rem;\n align-items: center;\n }\n\n & .syndication-links {\n flex-flow: row wrap;\n\n & a {\n text-decoration: none;\n\n & svg {\n width: 1rem;\n height: 1rem;\n }\n }\n }\n }\n}\n\n.feather {\n width: 24px;\n height: 24px;\n stroke: currentcolor;\n stroke-width: 2;\n stroke-linecap: round;\n stroke-linejoin: round;\n fill: none;\n}\n\n.sr-only {\n clip: rect(0 0 0 0);\n clip-path: inset(50%);\n height: 1px;\n overflow: hidden;\n position: absolute;\n white-space: nowrap;\n width: 1px;\n}\n",".hljs {\n border-radius: .5rem;\n}\n","body {\n font-family: var(--font-family-body);\n font-size: var(--font-size-md);\n}\n\ncode {\n font-family: var(--font-family-monospace);\n}\n\nh1,\nh2,\nh3,\nh4,\nh5,\nh6 {\n font-family: var(--font-family-headings);\n}\n",".h-card {\n & .hovercard {\n display: none;\n position: absolute;\n z-index: 100;\n padding: 1rem;\n border-radius: 1rem;\n box-shadow: 0 .5rem .5rem .5rem var(--color-primary-shadow);\n background-color: var(--color-secondary);\n width: fit-content;\n transition: opacity 0.5s ease-in-out;\n opacity: 0;\n flex-direction: column;\n gap: .5rem;\n\n & .u-photo {\n max-width: 6rem;\n }\n\n & .social-icon {\n width: 1rem;\n height: 1rem;\n }\n }\n\n &:hover {\n & .hovercard {\n display: flex;\n opacity: 1;\n }\n }\n}\n"],"names":[]} \ No newline at end of file diff --git a/public/assets/css/app.css.zst b/public/assets/css/app.css.zst index f27d4f69..460b37b6 100644 Binary files a/public/assets/css/app.css.zst and b/public/assets/css/app.css.zst differ diff --git a/public/assets/css/colours.css.br b/public/assets/css/colours.css.br deleted file mode 100644 index ddedc7a3..00000000 Binary files a/public/assets/css/colours.css.br and /dev/null differ diff --git a/public/assets/css/colours.css.zst b/public/assets/css/colours.css.zst deleted file mode 100644 index fd30b78c..00000000 Binary files a/public/assets/css/colours.css.zst and /dev/null differ diff --git a/public/assets/css/content.css.br b/public/assets/css/content.css.br deleted file mode 100644 index ae9d299d..00000000 Binary files a/public/assets/css/content.css.br and /dev/null differ diff --git a/public/assets/css/content.css.zst b/public/assets/css/content.css.zst deleted file mode 100644 index 01cfa187..00000000 Binary files a/public/assets/css/content.css.zst and /dev/null differ diff --git a/public/assets/css/fonts.css.br b/public/assets/css/fonts.css.br deleted file mode 100644 index a99e5684..00000000 Binary files a/public/assets/css/fonts.css.br and /dev/null differ diff --git a/public/assets/css/fonts.css.zst b/public/assets/css/fonts.css.zst deleted file mode 100644 index c8ec55f1..00000000 Binary files a/public/assets/css/fonts.css.zst and /dev/null differ diff --git a/public/assets/css/h-card.css.br b/public/assets/css/h-card.css.br deleted file mode 100644 index 8dd3d22d..00000000 Binary files a/public/assets/css/h-card.css.br and /dev/null differ diff --git a/public/assets/css/h-card.css.zst b/public/assets/css/h-card.css.zst deleted file mode 100644 index b54e6a24..00000000 Binary files a/public/assets/css/h-card.css.zst and /dev/null differ diff --git a/public/assets/css/indieauth.css.br b/public/assets/css/indieauth.css.br deleted file mode 100644 index abb4c9c7..00000000 Binary files a/public/assets/css/indieauth.css.br and /dev/null differ diff --git a/public/assets/css/indieauth.css.zst b/public/assets/css/indieauth.css.zst deleted file mode 100644 index 5e9e979f..00000000 Binary files a/public/assets/css/indieauth.css.zst and /dev/null differ diff --git a/public/assets/css/layout.css.br b/public/assets/css/layout.css.br deleted file mode 100644 index 2fe4608b..00000000 Binary files a/public/assets/css/layout.css.br and /dev/null differ diff --git a/public/assets/css/layout.css.zst b/public/assets/css/layout.css.zst deleted file mode 100644 index aa9bded4..00000000 Binary files a/public/assets/css/layout.css.zst and /dev/null differ diff --git a/public/assets/css/notes.css.br b/public/assets/css/notes.css.br deleted file mode 100644 index 686ddf2f..00000000 Binary files a/public/assets/css/notes.css.br and /dev/null differ diff --git a/public/assets/css/notes.css.zst b/public/assets/css/notes.css.zst deleted file mode 100644 index f9cdbf82..00000000 Binary files a/public/assets/css/notes.css.zst and /dev/null differ diff --git a/public/assets/css/variables.css.br b/public/assets/css/variables.css.br deleted file mode 100644 index 4ff480f4..00000000 Binary files a/public/assets/css/variables.css.br and /dev/null differ diff --git a/public/assets/css/variables.css.zst b/public/assets/css/variables.css.zst deleted file mode 100644 index e1ccda8b..00000000 Binary files a/public/assets/css/variables.css.zst and /dev/null differ diff --git a/public/assets/frontend/is-land.js b/public/assets/frontend/is-land.js deleted file mode 100644 index 435a13b9..00000000 --- a/public/assets/frontend/is-land.js +++ /dev/null @@ -1,338 +0,0 @@ -class Island extends HTMLElement { - static tagName = "is-land"; - static prefix = "is-land--"; - static attr = { - template: "data-island", - ready: "ready", - defer: "defer-hydration", - }; - - static onceCache = new Map(); - static onReady = new Map(); - - static fallback = { - ":not(is-land,:defined,[defer-hydration])": (readyPromise, node, prefix) => { - // remove from document to prevent web component init - let cloned = document.createElement(prefix + node.localName); - for(let attr of node.getAttributeNames()) { - cloned.setAttribute(attr, node.getAttribute(attr)); - } - - // Declarative Shadow DOM (with polyfill) - let shadowroot = node.shadowRoot; - if(!shadowroot) { - let tmpl = node.querySelector(":scope > template:is([shadowrootmode], [shadowroot])"); - if(tmpl) { - let mode = tmpl.getAttribute("shadowrootmode") || tmpl.getAttribute("shadowroot") || "closed"; - shadowroot = node.attachShadow({ mode }); // default is closed - shadowroot.appendChild(tmpl.content.cloneNode(true)); - } - } - - // Cheers to https://gist.github.com/developit/45c85e9be01e8c3f1a0ec073d600d01e - if(shadowroot) { - cloned.attachShadow({ mode: shadowroot.mode }).append(...shadowroot.childNodes); - } - - // Keep *same* child nodes to preserve state of children (e.g. details->summary) - cloned.append(...node.childNodes); - node.replaceWith(cloned); - - return readyPromise.then(() => { - // Restore original children and shadow DOM - if(cloned.shadowRoot) { - node.shadowRoot.append(...cloned.shadowRoot.childNodes); - } - node.append(...cloned.childNodes); - cloned.replaceWith(node); - }); - } - } - - constructor() { - super(); - - // Internal promises - this.ready = new Promise(resolve => { - this.readyResolve = resolve; - }); - } - - // any parents of `el` that are (with conditions) - static getParents(el, stopAt = false) { - let nodes = []; - while(el) { - if(el.matches && el.matches(Island.tagName)) { - if(stopAt && el === stopAt) { - break; - } - - if(Conditions.hasConditions(el)) { - nodes.push(el); - } - } - el = el.parentNode; - } - return nodes; - } - - static async ready(el, parents) { - if(!parents) { - parents = Island.getParents(el); - } - if(parents.length === 0) { - return; - } - let imports = await Promise.all(parents.map(p => p.wait())); - // return innermost module import - if(imports.length) { - return imports[0]; - } - } - - forceFallback() { - if(window.Island) { - Object.assign(Island.fallback, window.Island.fallback); - } - - for(let selector in Island.fallback) { - // Reverse here as a cheap way to get the deepest nodes first - let components = Array.from(this.querySelectorAll(selector)).reverse(); - - // with thanks to https://gist.github.com/cowboy/938767 - for(let node of components) { - if(!node.isConnected) { - continue; - } - - let parents = Island.getParents(node); - // must be in a leaf island (not nested deep) - if(parents.length === 1) { - let p = Island.ready(node, parents); - Island.fallback[selector](p, node, Island.prefix); - } - } - } - } - - wait() { - return this.ready; - } - - async connectedCallback() { - // Only use fallback content with loading conditions - if(Conditions.hasConditions(this)) { - // Keep fallback content without initializing the components - this.forceFallback(); - } - - await this.hydrate(); - } - - getTemplates() { - return this.querySelectorAll(`template[${Island.attr.template}]`); - } - - replaceTemplates(templates) { - // replace