diff --git a/.env.dusk.testing b/.env.dusk.testing new file mode 100644 index 00000000..756f4074 --- /dev/null +++ b/.env.dusk.testing @@ -0,0 +1,14 @@ +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 4eb61db5..43a5f376 100644 --- a/.env.example +++ b/.env.example @@ -4,6 +4,8 @@ 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 new file mode 100644 index 00000000..0ef2b89b --- /dev/null +++ b/.env.github @@ -0,0 +1,70 @@ +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 78f41d7a..fcb21d39 100644 --- a/.gitattributes +++ b/.gitattributes @@ -5,3 +5,7 @@ *.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 new file mode 100644 index 00000000..3ebccbd3 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,17 @@ +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 new file mode 100644 index 00000000..f66a77b4 --- /dev/null +++ b/.github/workflows/deploy.yml @@ -0,0 +1,144 @@ +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 new file mode 100644 index 00000000..29afebb9 --- /dev/null +++ b/.github/workflows/phpunit.yml @@ -0,0 +1,65 @@ +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 new file mode 100644 index 00000000..9b0956ad --- /dev/null +++ b/.github/workflows/pint.yml @@ -0,0 +1,38 @@ +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 a0c2459a..5f786e27 100644 --- a/.gitignore +++ b/.gitignore @@ -4,6 +4,7 @@ /public/coverage /public/hot /public/files +/public/fonts /public/storage /storage/*.key /vendor diff --git a/.phpactor.json b/.phpactor.json new file mode 100644 index 00000000..028bb10a --- /dev/null +++ b/.phpactor.json @@ -0,0 +1,5 @@ +{ + "$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 new file mode 100644 index 00000000..9daadf16 --- /dev/null +++ b/.styleci.yml @@ -0,0 +1,9 @@ +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 8d5d2c92..e0026150 100644 --- a/app/Console/Commands/MigratePlaceDataFromPostgis.php +++ b/app/Console/Commands/MigratePlaceDataFromPostgis.php @@ -8,6 +8,8 @@ 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 a6b29176..010a086a 100644 --- a/app/Console/Commands/ParseCachedWebMentions.php +++ b/app/Console/Commands/ParseCachedWebMentions.php @@ -9,6 +9,9 @@ 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 c6452ba9..b29e7da8 100644 --- a/app/Console/Commands/ReDownloadWebMentions.php +++ b/app/Console/Commands/ReDownloadWebMentions.php @@ -8,6 +8,9 @@ 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 deleted file mode 100644 index 5966bccd..00000000 --- a/app/Exceptions/InvalidTokenScopeException.php +++ /dev/null @@ -1,7 +0,0 @@ -user(); // RP Entity i.e. the application $rpEntity = PublicKeyCredentialRpEntity::create( - name: config('app.name'), - id: config('app.url'), + config('app.name'), + config('url.longurl'), ); // User Entity $userEntity = PublicKeyCredentialUserEntity::create( - name: $user->name, - id: (string) $user->id, - displayName: $user->name, + $user->name, + (string) $user->id, + $user->name, ); // Challenge @@ -87,38 +85,25 @@ class PasskeysController extends Controller $authenticatorSelectionCriteria = AuthenticatorSelectionCriteria::create( userVerification: AuthenticatorSelectionCriteria::USER_VERIFICATION_REQUIREMENT_REQUIRED, residentKey: AuthenticatorSelectionCriteria::RESIDENT_KEY_REQUIREMENT_REQUIRED, + requireResidentKey: true, ); - $publicKeyCredentialCreationOptions = PublicKeyCredentialCreationOptions::create( - rp: $rpEntity, - user: $userEntity, - challenge: $challenge, - pubKeyCredParams: $pubKeyCredParams, + $options = PublicKeyCredentialCreationOptions::create( + $rpEntity, + $userEntity, + $challenge, + $pubKeyCredParams, authenticatorSelection: $authenticatorSelectionCriteria, attestation: PublicKeyCredentialCreationOptions::ATTESTATION_CONVEYANCE_PREFERENCE_NONE ); - $attestationStatementSupportManager = new AttestationStatementSupportManager; - $attestationStatementSupportManager->add(new NoneAttestationStatementSupport); - $webauthnSerializerFactory = new WebauthnSerializerFactory( - attestationStatementSupportManager: $attestationStatementSupportManager - ); - $webauthnSerializer = $webauthnSerializerFactory->create(); - $publicKeyCredentialCreationOptions = $webauthnSerializer->serialize( - data: $publicKeyCredentialCreationOptions, - format: 'json' - ); + $options = json_encode($options, JSON_THROW_ON_ERROR); - $request->session()->put('create_options', $publicKeyCredentialCreationOptions); + session(['create_options' => $options]); - return JsonResponse::fromJsonString($publicKeyCredentialCreationOptions); + return JsonResponse::fromJsonString($options); } - /** - * @throws Throwable - * @throws WebauthnException - * @throws \JsonException - */ public function create(Request $request): JsonResponse { /** @var User $user */ @@ -126,17 +111,17 @@ class PasskeysController extends Controller $publicKeyCredentialCreationOptionsData = session('create_options'); // Unset session data to mitigate replay attacks - $request->session()->forget('create_options'); + session()->forget('create_options'); if (empty($publicKeyCredentialCreationOptionsData)) { throw new WebAuthnException('No public key credential request options found'); } $attestationStatementSupportManager = new AttestationStatementSupportManager; $attestationStatementSupportManager->add(new NoneAttestationStatementSupport); - $webauthnSerializerFactory = new WebauthnSerializerFactory( - attestationStatementSupportManager: $attestationStatementSupportManager - ); - $webauthnSerializer = $webauthnSerializerFactory->create(); + + $webauthnSerializer = (new WebauthnSerializerFactory( + $attestationStatementSupportManager + ))->create(); $publicKeyCredential = $webauthnSerializer->deserialize( json_encode($request->all(), JSON_THROW_ON_ERROR), @@ -161,11 +146,11 @@ class PasskeysController extends Controller $ceremonyStepManagerFactory->setExtensionOutputCheckerHandler( ExtensionOutputCheckerHandler::create() ); - $allowedOrigins = []; + $securedRelyingPartyId = []; if (App::environment('local', 'development')) { - $allowedOrigins = [config('app.url')]; + $securedRelyingPartyId = [config('url.longurl')]; } - $ceremonyStepManagerFactory->setAllowedOrigins($allowedOrigins); + $ceremonyStepManagerFactory->setSecuredRelyingPartyId($securedRelyingPartyId); $authenticatorAttestationResponseValidator = AuthenticatorAttestationResponseValidator::create( ceremonyStepManager: $ceremonyStepManagerFactory->creationCeremony() @@ -180,7 +165,8 @@ class PasskeysController extends Controller $publicKeyCredentialSource = $authenticatorAttestationResponseValidator->check( authenticatorAttestationResponse: $publicKeyCredential->response, publicKeyCredentialCreationOptions: $publicKeyCredentialCreationOptions, - host: config('app.url') + request: config('url.longurl'), + securedRelyingPartyId: $securedRelyingPartyId, ); $user->passkey()->create([ @@ -194,37 +180,24 @@ class PasskeysController extends Controller ]); } - /** - * @throws RandomException - * @throws \JsonException - */ - public function getRequestOptions(Request $request): JsonResponse + public function getRequestOptions(): JsonResponse { $publicKeyCredentialRequestOptions = PublicKeyCredentialRequestOptions::create( challenge: random_bytes(16), userVerification: PublicKeyCredentialRequestOptions::USER_VERIFICATION_REQUIREMENT_REQUIRED ); - $attestationStatementSupportManager = AttestationStatementSupportManager::create(); - $attestationStatementSupportManager->add(NoneAttestationStatementSupport::create()); - $factory = new WebauthnSerializerFactory( - attestationStatementSupportManager: $attestationStatementSupportManager - ); - $serializer = $factory->create(); - $publicKeyCredentialRequestOptions = $serializer->serialize(data: $publicKeyCredentialRequestOptions, format: 'json'); + $publicKeyCredentialRequestOptions = json_encode($publicKeyCredentialRequestOptions, JSON_THROW_ON_ERROR); - $request->session()->put('request_options', $publicKeyCredentialRequestOptions); + session(['request_options' => $publicKeyCredentialRequestOptions]); return JsonResponse::fromJsonString($publicKeyCredentialRequestOptions); } - /** - * @throws \JsonException - */ public function login(Request $request): JsonResponse { $requestOptions = session('request_options'); - $request->session()->forget('request_options'); + session()->forget('request_options'); if (empty($requestOptions)) { return response()->json([ @@ -236,10 +209,9 @@ class PasskeysController extends Controller $attestationStatementSupportManager = new AttestationStatementSupportManager; $attestationStatementSupportManager->add(new NoneAttestationStatementSupport); - $webauthnSerializerFactory = new WebauthnSerializerFactory( - attestationStatementSupportManager: $attestationStatementSupportManager - ); - $webauthnSerializer = $webauthnSerializerFactory->create(); + $webauthnSerializer = (new WebauthnSerializerFactory( + $attestationStatementSupportManager + ))->create(); $publicKeyCredential = $webauthnSerializer->deserialize( json_encode($request->all(), JSON_THROW_ON_ERROR), @@ -284,11 +256,11 @@ class PasskeysController extends Controller $ceremonyStepManagerFactory->setExtensionOutputCheckerHandler( ExtensionOutputCheckerHandler::create() ); - $allowedOrigins = []; + $securedRelyingPartyId = []; if (App::environment('local', 'development')) { - $allowedOrigins = [config('app.url')]; + $securedRelyingPartyId = [config('url.longurl')]; } - $ceremonyStepManagerFactory->setAllowedOrigins($allowedOrigins); + $ceremonyStepManagerFactory->setSecuredRelyingPartyId($securedRelyingPartyId); $authenticatorAssertionResponseValidator = AuthenticatorAssertionResponseValidator::create( ceremonyStepManager: $ceremonyStepManagerFactory->requestCeremony() @@ -302,11 +274,12 @@ class PasskeysController extends Controller try { $authenticatorAssertionResponseValidator->check( - publicKeyCredentialSource: $publicKeyCredentialSource, + credentialId: $publicKeyCredentialSource, authenticatorAssertionResponse: $publicKeyCredential->response, publicKeyCredentialRequestOptions: $publicKeyCredentialRequestOptions, - host: config('app.url'), + request: config('url.longurl'), 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 e5e82bcd..2b0d2e99 100644 --- a/app/Http/Controllers/Admin/PlacesController.php +++ b/app/Http/Controllers/Admin/PlacesController.php @@ -10,6 +10,9 @@ 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 dc14a2d2..6eb60f69 100644 --- a/app/Http/Controllers/Admin/SyndicationTargetsController.php +++ b/app/Http/Controllers/Admin/SyndicationTargetsController.php @@ -10,6 +10,9 @@ 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 9ab860d7..725c5b91 100644 --- a/app/Http/Controllers/ArticlesController.php +++ b/app/Http/Controllers/ArticlesController.php @@ -10,6 +10,9 @@ 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 bd0022d6..27f34eab 100644 --- a/app/Http/Controllers/AuthController.php +++ b/app/Http/Controllers/AuthController.php @@ -9,6 +9,9 @@ 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 b4bb3c13..ae9a0280 100644 --- a/app/Http/Controllers/BookmarksController.php +++ b/app/Http/Controllers/BookmarksController.php @@ -7,6 +7,9 @@ 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 280cc3ed..75b103a8 100644 --- a/app/Http/Controllers/ContactsController.php +++ b/app/Http/Controllers/ContactsController.php @@ -8,6 +8,9 @@ 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 eb0847a3..4e887105 100644 --- a/app/Http/Controllers/FeedsController.php +++ b/app/Http/Controllers/FeedsController.php @@ -9,6 +9,9 @@ use App\Models\Note; use Illuminate\Http\JsonResponse; use Illuminate\Http\Response; +/** + * @psalm-suppress UnusedClass + */ class FeedsController extends Controller { /** @@ -119,8 +122,8 @@ class FeedsController extends Controller foreach ($notes as $key => $note) { $data['items'][$key] = [ - 'id' => $note->uri, - 'url' => $note->uri, + 'id' => $note->longurl, + 'url' => $note->longurl, 'content_text' => $note->content, 'date_published' => $note->created_at->tz('UTC')->toRfc3339String(), 'date_modified' => $note->updated_at->tz('UTC')->toRfc3339String(), @@ -161,7 +164,7 @@ class FeedsController extends Controller 'author' => [ 'type' => 'card', 'name' => config('user.display_name'), - 'url' => config('app.url'), + 'url' => config('url.longurl'), ], 'children' => $items, ], 200, [ @@ -180,8 +183,8 @@ class FeedsController extends Controller $items[] = [ 'type' => 'entry', 'published' => $note->created_at, - 'uid' => $note->uri, - 'url' => $note->uri, + 'uid' => $note->longurl, + 'url' => $note->longurl, 'content' => [ 'text' => $note->getRawOriginal('note'), 'html' => $note->note, @@ -197,7 +200,7 @@ class FeedsController extends Controller 'author' => [ 'type' => 'card', 'name' => config('user.display_name'), - 'url' => config('app.url'), + 'url' => config('url.longurl'), ], 'children' => $items, ], 200, [ diff --git a/app/Http/Controllers/FrontPageController.php b/app/Http/Controllers/FrontPageController.php index 19537663..8ae9c3c6 100644 --- a/app/Http/Controllers/FrontPageController.php +++ b/app/Http/Controllers/FrontPageController.php @@ -10,6 +10,9 @@ 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 af1c483c..77d5f963 100644 --- a/app/Http/Controllers/LikesController.php +++ b/app/Http/Controllers/LikesController.php @@ -7,6 +7,9 @@ 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 758b3255..ac25a815 100644 --- a/app/Http/Controllers/MicropubController.php +++ b/app/Http/Controllers/MicropubController.php @@ -4,73 +4,123 @@ declare(strict_types=1); namespace App\Http\Controllers; -use App\Exceptions\InvalidTokenScopeException; -use App\Exceptions\MicropubHandlerException; -use App\Http\Requests\MicropubRequest; +use App\Http\Responses\MicropubResponses; use App\Models\Place; use App\Models\SyndicationTarget; -use App\Services\Micropub\MicropubHandlerRegistry; +use App\Services\Micropub\HCardService; +use App\Services\Micropub\HEntryService; +use App\Services\Micropub\UpdateService; +use App\Services\TokenService; use Illuminate\Http\JsonResponse; use Illuminate\Http\Request; -use Lcobucci\JWT\Token; +use Lcobucci\JWT\Encoding\CannotDecodeContent; +use Lcobucci\JWT\Token\InvalidTokenStructure; +use Lcobucci\JWT\Validation\RequiredConstraintsViolated; +use Monolog\Handler\StreamHandler; +use Monolog\Logger; +/** + * @psalm-suppress UnusedClass + */ class MicropubController extends Controller { - protected MicropubHandlerRegistry $handlerRegistry; + protected TokenService $tokenService; - public function __construct(MicropubHandlerRegistry $handlerRegistry) - { - $this->handlerRegistry = $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; } /** - * 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. + * This function receives an API request, verifies the authenticity + * then passes over the info to the relevant Service class. */ - public function post(MicropubRequest $request): JsonResponse + public function post(Request $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 { - $handler = $this->handlerRegistry->getHandler($type); - $result = $handler->handle($request->getMicropubData()); + $tokenData = $this->tokenService->validateToken($request->input('access_token')); + } catch (RequiredConstraintsViolated|InvalidTokenStructure|CannotDecodeContent) { + $micropubResponses = new MicropubResponses; - // Return appropriate response based on the handler result - return response()->json([ - 'response' => $result['response'], - 'location' => $result['url'] ?? null, - ], 201)->header('Location', $result['url']); - } catch (\InvalidArgumentException $e) { - return response()->json([ - '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); + 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()); + + 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()); + + return response()->json([ + 'response' => 'created', + 'location' => $location, + ], 201)->header('Location', $location); + } + + 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); } /** @@ -83,6 +133,12 @@ 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(), @@ -114,17 +170,36 @@ class MicropubController extends Controller ]); } - // the default response is just to return the token data - /** @var Token $tokenData */ - $tokenData = $request->input('token_data'); - + // default response is just to return the token data return response()->json([ 'response' => 'token', 'token' => [ - 'me' => $tokenData['me'], - 'scope' => $tokenData['scope'], - 'client_id' => $tokenData['client_id'], + 'me' => $tokenData->claims()->get('me'), + 'scope' => $tokenData->claims()->get('scope'), + 'client_id' => $tokenData->claims()->get('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 fc804ea2..a660f11a 100644 --- a/app/Http/Controllers/MicropubMediaController.php +++ b/app/Http/Controllers/MicropubMediaController.php @@ -7,8 +7,10 @@ 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; @@ -16,20 +18,46 @@ 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 { - $tokenData = $request->input('token_data'); + try { + $tokenData = $this->tokenService->validateToken($request->input('access_token')); + } catch (RequiredConstraintsViolated|InvalidTokenStructure) { + $micropubResponses = new MicropubResponses; - $scopes = $tokenData['scope']; + return $micropubResponses->invalidTokenResponse(); + } + + if ($tokenData->claims()->has('scope') === false) { + $micropubResponses = new MicropubResponses; + + return $micropubResponses->tokenHasNoScopeResponse(); + } + + $scopes = $tokenData->claims()->get('scope'); if (is_string($scopes)) { $scopes = explode(' ', $scopes); } - if (! in_array('create', $scopes, true)) { - return (new MicropubResponses)->insufficientScopeResponse(); + if (! in_array('create', $scopes)) { + $micropubResponses = new MicropubResponses; + + return $micropubResponses->insufficientScopeResponse(); } if ($request->input('q') === 'last') { @@ -80,14 +108,28 @@ class MicropubMediaController extends Controller */ public function media(Request $request): JsonResponse { - $tokenData = $request->input('token_data'); + try { + $tokenData = $this->tokenService->validateToken($request->input('access_token')); + } catch (RequiredConstraintsViolated|InvalidTokenStructure) { + $micropubResponses = new MicropubResponses; - $scopes = $tokenData['scope']; + return $micropubResponses->invalidTokenResponse(); + } + + if ($tokenData->claims()->has('scope') === false) { + $micropubResponses = new MicropubResponses; + + return $micropubResponses->tokenHasNoScopeResponse(); + } + + $scopes = $tokenData->claims()->get('scope'); if (is_string($scopes)) { $scopes = explode(' ', $scopes); } - if (! in_array('create', $scopes, true)) { - return (new MicropubResponses)->insufficientScopeResponse(); + if (! in_array('create', $scopes)) { + $micropubResponses = new MicropubResponses; + + return $micropubResponses->insufficientScopeResponse(); } if ($request->hasFile('file') === false) { @@ -122,7 +164,7 @@ class MicropubMediaController extends Controller } $media = Media::create([ - 'token' => $request->input('access_token'), + 'token' => $request->bearerToken(), '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 d5c9bc90..5c25771f 100644 --- a/app/Http/Controllers/NotesController.php +++ b/app/Http/Controllers/NotesController.php @@ -14,6 +14,8 @@ 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 b949ecde..b9bae93b 100644 --- a/app/Http/Controllers/PlacesController.php +++ b/app/Http/Controllers/PlacesController.php @@ -7,6 +7,9 @@ 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 3f366538..a8116c88 100644 --- a/app/Http/Controllers/SearchController.php +++ b/app/Http/Controllers/SearchController.php @@ -6,6 +6,9 @@ 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 new file mode 100644 index 00000000..a232fcdb --- /dev/null +++ b/app/Http/Controllers/ShortURLsController.php @@ -0,0 +1,55 @@ + 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 deleted file mode 100644 index a04e80de..00000000 --- a/app/Http/Middleware/LogMicropubRequest.php +++ /dev/null @@ -1,24 +0,0 @@ -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 b22e2b33..e455d181 100644 --- a/app/Http/Middleware/MyAuthMiddleware.php +++ b/app/Http/Middleware/MyAuthMiddleware.php @@ -13,6 +13,8 @@ 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 093bf64a..2beb3c93 100644 --- a/app/Http/Middleware/ValidateSignature.php +++ b/app/Http/Middleware/ValidateSignature.php @@ -10,6 +10,8 @@ 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 33d2cb12..b68e999b 100644 --- a/app/Http/Middleware/VerifyMicropubToken.php +++ b/app/Http/Middleware/VerifyMicropubToken.php @@ -4,14 +4,8 @@ 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 @@ -19,63 +13,24 @@ class VerifyMicropubToken /** * Handle an incoming request. * - * @param Closure(Request): (Response) $next + * @psalm-suppress PossiblyUnusedMethod */ public function handle(Request $request, Closure $next): Response { - $rawToken = null; - if ($request->input('access_token')) { - $rawToken = $request->input('access_token'); - } elseif ($request->bearerToken()) { - $rawToken = $request->bearerToken(); + return $next($request); } - if (! $rawToken) { - return response()->json([ - 'response' => 'error', - 'error' => 'unauthorized', - 'error_description' => 'No access token was provided in the request', - ], 401); + if ($request->bearerToken()) { + return $next($request->merge([ + 'access_token' => $request->bearerToken(), + ])); } - 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; + return response()->json([ + 'response' => 'error', + 'error' => 'unauthorized', + 'error_description' => 'No access token was provided in the request', + ], 401); } } diff --git a/app/Http/Requests/MicropubRequest.php b/app/Http/Requests/MicropubRequest.php deleted file mode 100644 index d931f139..00000000 --- a/app/Http/Requests/MicropubRequest.php +++ /dev/null @@ -1,106 +0,0 @@ -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 d92dfa18..24c7f477 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->uri) === false) { + if ($parser->checkInReplyTo($microformats, $this->note->longurl) === 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->uri) === false) { + if ($parser->checkLikeOf($microformats, $this->note->longurl) === 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->uri) === false) { + if ($parser->checkRepostOf($microformats, $this->note->longurl) === 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->uri; + $webmention->target = $this->note->longurl; $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 2ff5f2c6..51e5f162 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->uri, + 'source' => $this->note->longurl, '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) === parse_url(config('app.url'), PHP_URL_HOST)) { + if (parse_url($url, PHP_URL_HOST) === config('url.longurl')) { return null; } if (Str::startsWith($url, '/notes/tagged/')) { diff --git a/app/Models/Bookmark.php b/app/Models/Bookmark.php index 37027e40..29bd25ad 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 local_uri(): Attribute + protected function longurl(): 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 74533443..62b9fcea 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,11 +172,16 @@ class Note extends Model return (string) resolve(Numbers::class)->numto60($this->id); } - public function getUriAttribute(): string + public function getLongurlAttribute(): 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 550f234d..62c826ad 100644 --- a/app/Models/Place.php +++ b/app/Models/Place.php @@ -74,10 +74,24 @@ 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: static fn ($value, $attributes) => config('app.url') . '/places/' . $attributes['slug'], + get: fn () => $this->longurl, ); } diff --git a/app/Observers/NoteObserver.php b/app/Observers/NoteObserver.php index 95288b01..935fb27f 100644 --- a/app/Observers/NoteObserver.php +++ b/app/Observers/NoteObserver.php @@ -9,10 +9,15 @@ 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.= + * Listen to the Note created event. + * + * @psalm-suppress PossiblyUnusedMethod */ public function created(Note $note): void { @@ -34,7 +39,9 @@ class NoteObserver } /** - * Listen to the Note updated event.= + * Listen to the Note updated event. + * + * @psalm-suppress PossiblyUnusedMethod */ public function updated(Note $note): void { @@ -58,7 +65,9 @@ class NoteObserver } /** - * Listen to the Note deleting event.= + * Listen to the Note deleting event. + * + * @psalm-suppress PossiblyUnusedMethod */ public function deleting(Note $note): void { diff --git a/app/Providers/HorizonServiceProvider.php b/app/Providers/HorizonServiceProvider.php index bf4280ef..94c76a5a 100644 --- a/app/Providers/HorizonServiceProvider.php +++ b/app/Providers/HorizonServiceProvider.php @@ -5,6 +5,9 @@ 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 deleted file mode 100644 index 1002a26d..00000000 --- a/app/Providers/MicropubServiceProvider.php +++ /dev/null @@ -1,26 +0,0 @@ -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 3d5dcc56..195f7051 100644 --- a/app/Services/ArticleService.php +++ b/app/Services/ArticleService.php @@ -6,13 +6,13 @@ namespace App\Services; use App\Models\Article; -class ArticleService +class ArticleService extends Service { - public function create(array $data): Article + public function create(array $request, ?string $client = null): Article { return Article::create([ - 'title' => $data['name'], - 'main' => $data['content'], + 'title' => $this->getDataByKey($request, 'name'), + 'main' => $this->getDataByKey($request, 'content'), 'published' => true, ]); } diff --git a/app/Services/BookmarkService.php b/app/Services/BookmarkService.php index 9cbc0714..32ec7260 100644 --- a/app/Services/BookmarkService.php +++ b/app/Services/BookmarkService.php @@ -10,29 +10,28 @@ 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 +class BookmarkService extends Service { /** * Create a new Bookmark. */ - public function create(array $data): Bookmark + public function create(array $request, ?string $client = null): Bookmark { - if (Arr::get($data, 'properties.bookmark-of.0')) { + if (Arr::get($request, 'properties.bookmark-of.0')) { // micropub request - $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'); + $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'); } - 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'); + 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'); } $bookmark = Bookmark::create([ @@ -55,7 +54,6 @@ class BookmarkService * 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 e688561d..dd08e25b 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 +class LikeService extends Service { /** * Create a new Like. */ - public function create(array $data): Like + public function create(array $request, ?string $client = null): Like { - if (Arr::get($data, 'properties.like-of.0')) { + if (Arr::get($request, 'properties.like-of.0')) { // micropub request - $url = normalize_url(Arr::get($data, 'properties.like-of.0')); + $url = normalize_url(Arr::get($request, 'properties.like-of.0')); } - if (Arr::get($data, 'like-of')) { - $url = normalize_url(Arr::get($data, 'like-of')); + if (Arr::get($request, 'like-of')) { + $url = normalize_url(Arr::get($request, 'like-of')); } $like = Like::create(['url' => $url]); diff --git a/app/Services/Micropub/CardHandler.php b/app/Services/Micropub/CardHandler.php deleted file mode 100644 index 12e283be..00000000 --- a/app/Services/Micropub/CardHandler.php +++ /dev/null @@ -1,34 +0,0 @@ -createPlace($data)->uri; - - return [ - 'response' => 'created', - 'url' => $location, - ]; - } -} diff --git a/app/Services/Micropub/EntryHandler.php b/app/Services/Micropub/EntryHandler.php deleted file mode 100644 index 9cdbe789..00000000 --- a/app/Services/Micropub/EntryHandler.php +++ /dev/null @@ -1,41 +0,0 @@ - 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 new file mode 100644 index 00000000..7ab57a4e --- /dev/null +++ b/app/Services/Micropub/HCardService.php @@ -0,0 +1,32 @@ +createPlace($data)->longurl; + } +} diff --git a/app/Services/Micropub/HEntryService.php b/app/Services/Micropub/HEntryService.php new file mode 100644 index 00000000..807e6327 --- /dev/null +++ b/app/Services/Micropub/HEntryService.php @@ -0,0 +1,34 @@ +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 deleted file mode 100644 index 82040be9..00000000 --- a/app/Services/Micropub/MicropubHandlerInterface.php +++ /dev/null @@ -1,10 +0,0 @@ -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/UpdateHandler.php b/app/Services/Micropub/UpdateService.php similarity index 79% rename from app/Services/Micropub/UpdateHandler.php rename to app/Services/Micropub/UpdateService.php index ee018f19..f806361c 100644 --- a/app/Services/Micropub/UpdateHandler.php +++ b/app/Services/Micropub/UpdateService.php @@ -4,33 +4,21 @@ 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; -/* - * @todo Implement this properly - */ -class UpdateHandler implements MicropubHandlerInterface +class UpdateService { /** - * @throws InvalidTokenScopeException + * Process a micropub request to update an entry. */ - public function handle(array $data) + public function process(array $request): JsonResponse { - $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); + $urlPath = parse_url(Arr::get($request, 'url'), PHP_URL_PATH); // is it a note we are updating? if (mb_substr($urlPath, 1, 5) !== 'notes') { @@ -42,7 +30,7 @@ class UpdateHandler implements MicropubHandlerInterface try { $note = Note::nb60(basename($urlPath))->firstOrFail(); - } catch (ModelNotFoundException) { + } catch (ModelNotFoundException $exception) { return response()->json([ 'error' => 'invalid_request', 'error_description' => 'No known note with given ID', @@ -50,8 +38,8 @@ class UpdateHandler implements MicropubHandlerInterface } // got the note, are we dealing with a ā€œreplaceā€ request? - if (Arr::get($data, 'replace')) { - foreach (Arr::get($data, 'replace') as $property => $value) { + if (Arr::get($request, 'replace')) { + foreach (Arr::get($request, 'replace') as $property => $value) { if ($property === 'content') { $note->note = $value[0]; } @@ -71,14 +59,14 @@ class UpdateHandler implements MicropubHandlerInterface } $note->save(); - return [ + return response()->json([ 'response' => 'updated', - ]; + ]); } // how about ā€œaddā€ - if (Arr::get($data, 'add')) { - foreach (Arr::get($data, 'add') as $property => $value) { + if (Arr::get($request, 'add')) { + foreach (Arr::get($request, '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 d8c55507..b101498c 100644 --- a/app/Services/NoteService.php +++ b/app/Services/NoteService.php @@ -14,52 +14,49 @@ use App\Models\SyndicationTarget; use Illuminate\Support\Arr; use Illuminate\Support\Str; -class NoteService +class NoteService extends Service { /** * Create a new note. */ - public function create(array $data): Note + public function create(array $request, ?string $client = null): 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' => $content, - 'in_reply_to' => $data['in-reply-to'], - 'client_id' => $data['token_data']['client_id'], + 'note' => $this->getDataByKey($request, 'content'), + 'in_reply_to' => $this->getDataByKey($request, 'in-reply-to'), + 'client_id' => $client, ] ); - if ($published = $this->getPublished($data)) { - $note->created_at = $note->updated_at = $published; + if ($this->getPublished($request)) { + $note->created_at = $note->updated_at = $this->getPublished($request); } - $note->location = $this->getLocation($data); + $note->location = $this->getLocation($request); - if ($this->getCheckin($data)) { - $note->place()->associate($this->getCheckin($data)); - $note->swarm_url = $this->getSwarmUrl($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); } - // - // $note->instagram_url = $this->getInstagramUrl($request); - // - // foreach ($this->getMedia($request) as $media) { - // $note->media()->save($media); - // } $note->save(); dispatch(new SendWebMentions($note)); - $this->dispatchSyndicationJobs($note, $data); + if (in_array('mastodon', $this->getSyndicationTargets($request), true)) { + dispatch(new SyndicateNoteToMastodon($note)); + } + + if (in_array('bluesky', $this->getSyndicationTargets($request), true)) { + dispatch(new SyndicateNoteToBluesky($note)); + } return $note; } @@ -67,10 +64,14 @@ class NoteService /** * Get the published time from the request to create a new note. */ - private function getPublished(array $data): ?string + private function getPublished(array $request): ?string { - if ($data['published']) { - return carbon($data['published'])->toDateTimeString(); + 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(); } return null; @@ -79,13 +80,12 @@ class NoteService /** * Get the location data from the request to create a new note. */ - private function getLocation(array $data): ?string + private function getLocation(array $request): ?string { - $location = Arr::get($data, 'location'); - + $location = Arr::get($request, 'properties.location.0') ?? Arr::get($request, '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 /** * Get the checkin data from the request to create a new note. This will be a Place. */ - private function getCheckin(array $data): ?Place + private function getCheckin(array $request): ?Place { - $location = Arr::get($data, 'location'); + $location = Arr::get($request, 'location'); if (is_string($location) && Str::startsWith($location, config('app.url'))) { return Place::where( 'slug', @@ -113,12 +113,12 @@ class NoteService ) )->first(); } - if (Arr::get($data, 'checkin')) { + if (Arr::get($request, 'checkin')) { try { $place = resolve(PlaceService::class)->createPlaceFromCheckin( - Arr::get($data, 'checkin') + Arr::get($request, 'checkin') ); - } catch (\InvalidArgumentException) { + } catch (\InvalidArgumentException $e) { return null; } @@ -142,47 +142,34 @@ class NoteService /** * Get the Swarm URL from the syndication data in the request to create a new note. */ - private function getSwarmUrl(array $data): ?string + private function getSwarmUrl(array $request): ?string { - $syndication = Arr::get($data, 'syndication'); - if ($syndication === null) { - return null; - } - - if (str_contains($syndication, 'swarmapp')) { - return $syndication; + if (str_contains(Arr::get($request, 'properties.syndication.0', ''), 'swarmapp')) { + return Arr::get($request, 'properties.syndication.0'); } return null; } /** - * Dispatch syndication jobs based on the request data. + * Get the syndication targets from the request to create a new note. */ - private function dispatchSyndicationJobs(Note $note, array $request): void + private function getSyndicationTargets(array $request): array { - // If no syndication targets are specified, return early - if (empty($request['mp-syndicate-to'])) { - return; - } - - // 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; - } + $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'; } } + + return $syndication; } /** diff --git a/app/Services/Service.php b/app/Services/Service.php new file mode 100644 index 00000000..cb480d7c --- /dev/null +++ b/app/Services/Service.php @@ -0,0 +1,30 @@ +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 24821d29..4e3b4407 100644 --- a/bootstrap/providers.php +++ b/bootstrap/providers.php @@ -3,5 +3,4 @@ return [ App\Providers\AppServiceProvider::class, App\Providers\HorizonServiceProvider::class, - App\Providers\MicropubServiceProvider::class, ]; diff --git a/composer.json b/composer.json index 063e895a..e4ea6123 100644 --- a/composer.json +++ b/composer.json @@ -49,8 +49,7 @@ "openai-php/client": "^0.10.1", "phpunit/php-code-coverage": "^11.0", "phpunit/phpunit": "^11.0", - "spatie/laravel-ray": "^1.12", - "spatie/x-ray": "^1.2" + "spatie/laravel-ray": "^1.12" }, "autoload": { "psr-4": { diff --git a/composer.lock b/composer.lock index 730017c5..a7521ac8 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": "1076b46fccbfe2c22f51fa6e904cfedf", + "content-hash": "cd963bfd9cfb41beb4151e73ae98dc98", "packages": [ { "name": "aws/aws-crt-php", @@ -10079,133 +10079,6 @@ ], "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", @@ -12296,78 +12169,6 @@ ], "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 new file mode 100644 index 00000000..dfdffe6b --- /dev/null +++ b/config/url.php @@ -0,0 +1,32 @@ + 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 7695e27e..54c91aa0 100644 --- a/database/factories/ArticleFactory.php +++ b/database/factories/ArticleFactory.php @@ -7,6 +7,8 @@ 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 b2493014..7ed8cb6f 100644 --- a/database/factories/BioFactory.php +++ b/database/factories/BioFactory.php @@ -5,6 +5,8 @@ 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 ddfe0f97..0d10db4a 100644 --- a/database/factories/BookmarkFactory.php +++ b/database/factories/BookmarkFactory.php @@ -7,6 +7,8 @@ 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 1b0be43b..57efafd9 100644 --- a/database/factories/ContactFactory.php +++ b/database/factories/ContactFactory.php @@ -7,6 +7,8 @@ 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 8bf4f62f..92ad76ce 100644 --- a/database/factories/LikeFactory.php +++ b/database/factories/LikeFactory.php @@ -7,6 +7,8 @@ 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 ca253109..6f1e4838 100644 --- a/database/factories/MediaFactory.php +++ b/database/factories/MediaFactory.php @@ -7,6 +7,8 @@ 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 4916f404..923a9df9 100644 --- a/database/factories/MicropubClientFactory.php +++ b/database/factories/MicropubClientFactory.php @@ -6,6 +6,8 @@ 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 e2238a23..0ea928a6 100644 --- a/database/factories/NoteFactory.php +++ b/database/factories/NoteFactory.php @@ -8,6 +8,8 @@ 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 61bdd70f..a9f23f0b 100644 --- a/database/factories/PlaceFactory.php +++ b/database/factories/PlaceFactory.php @@ -6,6 +6,8 @@ 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 05243632..f409a3f6 100644 --- a/database/factories/SyndicationTargetFactory.php +++ b/database/factories/SyndicationTargetFactory.php @@ -5,6 +5,8 @@ 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 24cae028..b5478679 100644 --- a/database/factories/TagFactory.php +++ b/database/factories/TagFactory.php @@ -6,6 +6,8 @@ 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 ba1ff997..e456f5d8 100644 --- a/database/factories/UserFactory.php +++ b/database/factories/UserFactory.php @@ -7,6 +7,8 @@ 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 65dbb92f..08829bad 100644 --- a/database/factories/WebMentionFactory.php +++ b/database/factories/WebMentionFactory.php @@ -6,6 +6,8 @@ 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 cbfd7a36..898471fc 100644 --- a/database/seeders/ArticlesTableSeeder.php +++ b/database/seeders/ArticlesTableSeeder.php @@ -11,6 +11,8 @@ 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 093d0609..05a60c33 100644 --- a/database/seeders/BioSeeder.php +++ b/database/seeders/BioSeeder.php @@ -5,6 +5,9 @@ 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 baa3580f..a8c48f69 100644 --- a/database/seeders/BookmarksTableSeeder.php +++ b/database/seeders/BookmarksTableSeeder.php @@ -10,6 +10,8 @@ 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 35dcb296..98d403b0 100644 --- a/database/seeders/ClientsTableSeeder.php +++ b/database/seeders/ClientsTableSeeder.php @@ -11,6 +11,8 @@ 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 3d191ec1..577fe153 100644 --- a/database/seeders/ContactsTableSeeder.php +++ b/database/seeders/ContactsTableSeeder.php @@ -10,6 +10,8 @@ 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 5117a89d..c91e2edb 100644 --- a/database/seeders/DatabaseSeeder.php +++ b/database/seeders/DatabaseSeeder.php @@ -4,6 +4,9 @@ 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 4600fb69..0436ad64 100644 --- a/database/seeders/LikesTableSeeder.php +++ b/database/seeders/LikesTableSeeder.php @@ -12,6 +12,8 @@ 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 630cfda6..a9fbab12 100644 --- a/database/seeders/NotesTableSeeder.php +++ b/database/seeders/NotesTableSeeder.php @@ -14,6 +14,8 @@ 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 70324ff8..90a63ff8 100644 --- a/database/seeders/PlacesTableSeeder.php +++ b/database/seeders/PlacesTableSeeder.php @@ -9,6 +9,8 @@ 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 d9e608e2..dfc155cd 100644 --- a/database/seeders/UsersTableSeeder.php +++ b/database/seeders/UsersTableSeeder.php @@ -9,6 +9,8 @@ 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 46da28d5..b7118343 100644 --- a/database/seeders/WebMentionsTableSeeder.php +++ b/database/seeders/WebMentionsTableSeeder.php @@ -9,6 +9,8 @@ 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 96abf554..aa3f3839 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,16 +8,27 @@ "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": "^4.2.0", - "esbuild": "^0.25.2", + "@stylistic/eslint-plugin": "^3.0.0", "eslint": "^9.7.0", - "globals": "^16.0.0", - "lightningcss": "^1.29.3", - "lightningcss-cli": "^1.29.3", + "globals": "^15.8.0", "stylelint": "^16.7.0", - "stylelint-config-standard": "^38.0.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" } }, "node_modules/@babel/code-frame": { @@ -146,435 +157,10 @@ "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.5.1", - "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.5.1.tgz", - "integrity": "sha512-soEIOALTfTK6EjmKMMoLugwaP0rzkad90iIWd1hMO9ARkSAyjfMfkRRhLvD5qH7vvM0Cg72pieUfR6yh6XxC4w==", + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.1.tgz", + "integrity": "sha512-s3O3waFUrMV8P/XaF/+ZTp1X9XBZW1a4B97ZnjQF2KYWaFD2A8KyFBsrsfSjEmjn3RGWAIuvlneuZm3CUK3jbA==", "dev": true, "license": "MIT", "dependencies": { @@ -614,13 +200,13 @@ } }, "node_modules/@eslint/config-array": { - "version": "0.20.0", - "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.20.0.tgz", - "integrity": "sha512-fxlS1kkIjx8+vy2SjuCB94q3htSNrufYTXubwiBFeaQHbH6Ipi43gFJq2zCMt6PHhImH3Xmr0NksKDvchWlpQQ==", + "version": "0.19.1", + "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.19.1.tgz", + "integrity": "sha512-fo6Mtm5mWyKjA/Chy1BYTdn5mGJoDNjC7C64ug20ADsRDGrA85bN3uK3MaKbeRkRuuIEAR5N33Jr1pbm411/PA==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@eslint/object-schema": "^2.1.6", + "@eslint/object-schema": "^2.1.5", "debug": "^4.3.1", "minimatch": "^3.1.2" }, @@ -652,20 +238,10 @@ "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.12.0", - "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.12.0.tgz", - "integrity": "sha512-cmrR6pytBuSMTaBweKoGMwu3EiHiEC+DoyupPmlZ0HxBJBtIxwe+j/E4XPIKNx+Q74c8lXKPwYawBf5glsTkHg==", + "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==", "dev": true, "license": "Apache-2.0", "dependencies": { @@ -676,9 +252,9 @@ } }, "node_modules/@eslint/eslintrc": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.1.tgz", - "integrity": "sha512-gtF186CXhIl1p4pJNGZw8Yc6RlshoePRvE0X91oPGb3vZ8pM3qOS9W9NGPat9LziaBV7XrJWGylNQXkGcnM3IQ==", + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.2.0.tgz", + "integrity": "sha512-grOjVNN8P3hjJn/eIETF1wwd12DdnwFDoyceUJLYYdkpbwq3nLi+4fqrTAONx7XDALqlL220wC/RHSC/QTI/0w==", "dev": true, "license": "MIT", "dependencies": { @@ -737,9 +313,9 @@ } }, "node_modules/@eslint/js": { - "version": "9.24.0", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.24.0.tgz", - "integrity": "sha512-uIY/y3z0uvOGX8cp1C2fiC4+ZmBhp6yZWkojtHL1YEMnRt1Y63HB9TM17proGEmeG7HeUY+UP36F0aknKYTpYA==", + "version": "9.19.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.19.0.tgz", + "integrity": "sha512-rbq9/g38qjfqFLOVPvwjIvFFdNziEC5S65jmjPw5r6A//QH+W91akh9irMwjDN8zKUTak6W9EsAv4m/7Wnw0UQ==", "dev": true, "license": "MIT", "engines": { @@ -747,9 +323,9 @@ } }, "node_modules/@eslint/object-schema": { - "version": "2.1.6", - "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.6.tgz", - "integrity": "sha512-RBMg5FRL0I0gs51M/guSAj5/e14VQ4tpZnQNWwuDT66P14I43ItmPfIZRhO9fUVIPOAQXU47atlywZ/czoqFPA==", + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.5.tgz", + "integrity": "sha512-o0bhxnL89h5Bae5T318nFoFzGy+YE5i/gGkoPAgkmTVdRKTiv3p8JHevPiPaMwoloKfEiiaHlawCqaZMqRm+XQ==", "dev": true, "license": "Apache-2.0", "engines": { @@ -757,32 +333,19 @@ } }, "node_modules/@eslint/plugin-kit": { - "version": "0.2.8", - "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.2.8.tgz", - "integrity": "sha512-ZAoA40rNMPwSm+AeHpCq8STiNAwzWLJuP8Xv4CHIc9wv/PSuExjMrmjfYNj682vW0OOiZ1HKxzvjQr9XZIisQA==", + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.2.5.tgz", + "integrity": "sha512-lB05FkqEdUg2AA0xEbUz0SnkXT1LcCTa438W4IWTUh4hdOnVbQyOJ81OrDXsJk/LSiJHubgGEFoR5EHq1NsH1A==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@eslint/core": "^0.13.0", + "@eslint/core": "^0.10.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", @@ -836,9 +399,9 @@ } }, "node_modules/@humanwhocodes/retry": { - "version": "0.4.2", - "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.2.tgz", - "integrity": "sha512-xeO57FpIu4p1Ri3Jq/EXq4ClRm86dVF2z/+kvFnyqVYRavTZmaFaUBbWCOuuTh0o/g7DSsk6kc2vrS4Vl5oPOQ==", + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.1.tgz", + "integrity": "sha512-c7hNEllBlenFTHBky65mhq8WD2kbN9Q6gk0bTk8lSBvc554jpXSkST1iePudpt7+A/AQvuHs9EMqjHDXMY1lrA==", "dev": true, "license": "Apache-2.0", "engines": { @@ -850,9 +413,9 @@ } }, "node_modules/@keyv/serialize": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@keyv/serialize/-/serialize-1.0.3.tgz", - "integrity": "sha512-qnEovoOp5Np2JDGonIDL6Ayihw0RhnRh6vxPuHo4RDn1UOzwEo4AeIfpL6UGIrsceWrCMiVPgwRjbHu4vYFc3g==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@keyv/serialize/-/serialize-1.0.2.tgz", + "integrity": "sha512-+E/LyaAeuABniD/RvUezWVXKpeuvwLEA9//nE9952zBaOdBd2mQ3pPoM8cUe2X6IcMByfuSLzmYqnYshG60+HQ==", "dev": true, "license": "MIT", "dependencies": { @@ -898,13 +461,13 @@ } }, "node_modules/@stylistic/eslint-plugin": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/@stylistic/eslint-plugin/-/eslint-plugin-4.2.0.tgz", - "integrity": "sha512-8hXezgz7jexGHdo5WN6JBEIPHCSFyyU4vgbxevu4YLVS5vl+sxqAAGyXSzfNDyR6xMNSH5H1x67nsXcYMOHtZA==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@stylistic/eslint-plugin/-/eslint-plugin-3.0.0.tgz", + "integrity": "sha512-9GJI6iBtGjOqSsyCKUvE6Vn7qDT52hbQaoq/SwxH6A1bciymZfvBfHIIrD3E7Koi2sjzOa/MNQ2XOguHtVJOyw==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/utils": "^8.23.0", + "@typescript-eslint/utils": "8.13.0", "eslint-visitor-keys": "^4.2.0", "espree": "^10.3.0", "estraverse": "^5.3.0", @@ -914,13 +477,13 @@ "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "peerDependencies": { - "eslint": ">=9.0.0" + "eslint": ">=8.40.0" } }, "node_modules/@types/estree": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.7.tgz", - "integrity": "sha512-w28IoSUCJpidD/TGviZwwMJckNESJZXFu7NBZ5YJ4mEUnNraUn9Pm8HSZm/jDF1pDWYKspWE7oVphigUPRakIQ==", + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.6.tgz", + "integrity": "sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==", "dev": true, "license": "MIT" }, @@ -932,14 +495,14 @@ "license": "MIT" }, "node_modules/@typescript-eslint/scope-manager": { - "version": "8.29.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.29.1.tgz", - "integrity": "sha512-2nggXGX5F3YrsGN08pw4XpMLO1Rgtnn4AzTegC2MDesv6q3QaTU5yU7IbS1tf1IwCR0Hv/1EFygLn9ms6LIpDA==", + "version": "8.13.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.13.0.tgz", + "integrity": "sha512-XsGWww0odcUT0gJoBZ1DeulY1+jkaHUciUq4jKNv4cpInbvvrtDoyBH9rE/n2V29wQJPk8iCH1wipra9BhmiMA==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.29.1", - "@typescript-eslint/visitor-keys": "8.29.1" + "@typescript-eslint/types": "8.13.0", + "@typescript-eslint/visitor-keys": "8.13.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -950,9 +513,9 @@ } }, "node_modules/@typescript-eslint/types": { - "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==", + "version": "8.13.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.13.0.tgz", + "integrity": "sha512-4cyFErJetFLckcThRUFdReWJjVsPCqyBlJTi6IDEpc1GWCIIZRFxVppjWLIMcQhNGhdWJJRYFHpHoDWvMlDzng==", "dev": true, "license": "MIT", "engines": { @@ -964,20 +527,20 @@ } }, "node_modules/@typescript-eslint/typescript-estree": { - "version": "8.29.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.29.1.tgz", - "integrity": "sha512-l1enRoSaUkQxOQnbi0KPUtqeZkSiFlqrx9/3ns2rEDhGKfTa+88RmXqedC1zmVTOWrLc2e6DEJrTA51C9iLH5g==", + "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==", "dev": true, - "license": "MIT", + "license": "BSD-2-Clause", "dependencies": { - "@typescript-eslint/types": "8.29.1", - "@typescript-eslint/visitor-keys": "8.29.1", + "@typescript-eslint/types": "8.13.0", + "@typescript-eslint/visitor-keys": "8.13.0", "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": "^2.0.1" + "ts-api-utils": "^1.3.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -986,21 +549,23 @@ "type": "opencollective", "url": "https://opencollective.com/typescript-eslint" }, - "peerDependencies": { - "typescript": ">=4.8.4 <5.9.0" + "peerDependenciesMeta": { + "typescript": { + "optional": true + } } }, "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==", + "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.29.1", - "@typescript-eslint/types": "8.29.1", - "@typescript-eslint/typescript-estree": "8.29.1" + "@typescript-eslint/scope-manager": "8.13.0", + "@typescript-eslint/types": "8.13.0", + "@typescript-eslint/typescript-estree": "8.13.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -1010,19 +575,18 @@ "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "eslint": "^8.57.0 || ^9.0.0", - "typescript": ">=4.8.4 <5.9.0" + "eslint": "^8.57.0 || ^9.0.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==", + "version": "8.13.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.13.0.tgz", + "integrity": "sha512-7N/+lztJqH4Mrf0lb10R/CbI1EaAMMGyF5y0oJvFoAhafwgiRA7TXyd8TFn8FC8k5y2dTsYogg238qavRGNnlw==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.29.1", - "eslint-visitor-keys": "^4.2.0" + "@typescript-eslint/types": "8.13.0", + "eslint-visitor-keys": "^3.4.3" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -1032,10 +596,29 @@ "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.1", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.1.tgz", - "integrity": "sha512-OvQ/2pUDKmgfCg++xsTX1wGxfTaszcHVcTctW4UJB4hibJx2HXxxO5UmVgyjMa+ZDsiaf5wWLXYpRWMmBI0QHg==", + "version": "8.14.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.0.tgz", + "integrity": "sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA==", "dev": true, "license": "MIT", "bin": { @@ -1202,24 +785,24 @@ } }, "node_modules/cacheable": { - "version": "1.8.10", - "resolved": "https://registry.npmjs.org/cacheable/-/cacheable-1.8.10.tgz", - "integrity": "sha512-0ZnbicB/N2R6uziva8l6O6BieBklArWyiGx4GkwAhLKhSHyQtRfM9T1nx7HHuHDKkYB/efJQhz3QJ6x/YqoZzA==", + "version": "1.8.8", + "resolved": "https://registry.npmjs.org/cacheable/-/cacheable-1.8.8.tgz", + "integrity": "sha512-OE1/jlarWxROUIpd0qGBSKFLkNsotY8pt4GeiVErUYh/NUeTNrT+SBksUgllQv4m6a0W/VZsLuiHb88maavqEw==", "dev": true, "license": "MIT", "dependencies": { - "hookified": "^1.8.1", - "keyv": "^5.3.2" + "hookified": "^1.7.0", + "keyv": "^5.2.3" } }, "node_modules/cacheable/node_modules/keyv": { - "version": "5.3.2", - "resolved": "https://registry.npmjs.org/keyv/-/keyv-5.3.2.tgz", - "integrity": "sha512-Lji2XRxqqa5Wg+CHLVfFKBImfJZ4pCSccu9eVWK6w4c2SDFLd8JAn1zqTuSFnsxb7ope6rMsnIHfp+eBbRBRZQ==", + "version": "5.2.3", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-5.2.3.tgz", + "integrity": "sha512-AGKecUfzrowabUv0bH1RIR5Vf7w+l4S3xtQAypKaUpTdIR1EbrAcTxHCrpo9Q+IWeUlFE2palRtgIQcgm+PQJw==", "dev": true, "license": "MIT", "dependencies": { - "@keyv/serialize": "^1.0.3" + "@keyv/serialize": "^1.0.2" } }, "node_modules/callsites": { @@ -1387,16 +970,6 @@ "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", @@ -1437,47 +1010,6 @@ "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", @@ -1492,23 +1024,22 @@ } }, "node_modules/eslint": { - "version": "9.24.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.24.0.tgz", - "integrity": "sha512-eh/jxIEJyZrvbWRe4XuVclLPDYSYYYgLy5zXGGxD6j8zjSAxFEzI2fL/8xNq6O2yKqVt+eF2YhV+hxjV6UKXwQ==", + "version": "9.19.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.19.0.tgz", + "integrity": "sha512-ug92j0LepKlbbEv6hD911THhoRHmbdXt2gX+VDABAW/Ir7D3nqKdv5Pf5vtlyY6HQMTEP2skXY43ueqTCWssEA==", "dev": true, "license": "MIT", "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.12.1", - "@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", + "@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", "@humanfs/node": "^0.16.6", "@humanwhocodes/module-importer": "^1.0.1", - "@humanwhocodes/retry": "^0.4.2", + "@humanwhocodes/retry": "^0.4.1", "@types/estree": "^1.0.6", "@types/json-schema": "^7.0.15", "ajv": "^6.12.4", @@ -1516,7 +1047,7 @@ "cross-spawn": "^7.0.6", "debug": "^4.3.2", "escape-string-regexp": "^4.0.0", - "eslint-scope": "^8.3.0", + "eslint-scope": "^8.2.0", "eslint-visitor-keys": "^4.2.0", "espree": "^10.3.0", "esquery": "^1.5.0", @@ -1553,9 +1084,9 @@ } }, "node_modules/eslint-scope": { - "version": "8.3.0", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.3.0.tgz", - "integrity": "sha512-pUNxi75F8MJ/GdeKtVLSbYg4ZI34J6C0C7sbL4YOp2exGwen7ZsuBqKzUhXd0qMQ362yET3z+uPwKeg/0C2XCQ==", + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.2.0.tgz", + "integrity": "sha512-PHlWUfG6lvPc3yvP5A4PNyBL1W8fkDUccmI21JUu/+GKZBoH/W5u6usENXUrWFRsyoW5ACUjFGgAFQp5gUlb/A==", "dev": true, "license": "BSD-2-Clause", "dependencies": { @@ -1749,9 +1280,9 @@ } }, "node_modules/fastq": { - "version": "1.19.1", - "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.19.1.tgz", - "integrity": "sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ==", + "version": "1.18.0", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.18.0.tgz", + "integrity": "sha512-QKHXPW0hD8g4UET03SdOdunzSouc9N4AuHdsX8XNcTsuz+yYFILVNIX4l9yHABMhiEI9Db0JTTIpu0wB+Y1QQw==", "dev": true, "license": "ISC", "dependencies": { @@ -1816,9 +1347,9 @@ } }, "node_modules/flatted": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.3.tgz", - "integrity": "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==", + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.2.tgz", + "integrity": "sha512-AiwGJM8YcNOaobumgtng+6NHuOqC3A7MixFeDafM3X9cIUM+xUXoS5Vfgf+OihAYe20fxqNM9yPBXJzRtZ/4eA==", "dev": true, "license": "ISC" }, @@ -1877,9 +1408,9 @@ } }, "node_modules/globals": { - "version": "16.0.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-16.0.0.tgz", - "integrity": "sha512-iInW14XItCXET01CQFqudPOWP2jYMl7T+QRQT+UNcR/iQncN/F0UNpgd76iFkBPgNQb4+X3LV9tLJYzwh+Gl3A==", + "version": "15.14.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-15.14.0.tgz", + "integrity": "sha512-OkToC372DtlQeje9/zHIo5CT8lRP/FUgEOKBEhU4e0abL7J7CD24fD9ohiLN5hagG/kWCYj4K5oaxxtj2Z0Dig==", "dev": true, "license": "MIT", "engines": { @@ -1928,9 +1459,9 @@ } }, "node_modules/hookified": { - "version": "1.8.1", - "resolved": "https://registry.npmjs.org/hookified/-/hookified-1.8.1.tgz", - "integrity": "sha512-GrO2l93P8xCWBSTBX9l2BxI78VU/MAAYag+pG8curS3aBGy0++ZlxrQ7PdUOUVMbn5BwkGb6+eRrnf43ipnFEA==", + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/hookified/-/hookified-1.7.0.tgz", + "integrity": "sha512-XQdMjqC1AyeOzfs+17cnIk7Wdfu1hh2JtcyNfBf5u9jHrT3iZUlGHxLTntFBuk5lwkqJ6l3+daeQdHK5yByHVA==", "dev": true, "license": "MIT" }, @@ -1979,9 +1510,9 @@ } }, "node_modules/import-fresh": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", - "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==", + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", + "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", "dev": true, "license": "MIT", "dependencies": { @@ -2168,488 +1699,6 @@ "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", @@ -2779,9 +1828,9 @@ "license": "MIT" }, "node_modules/nanoid": { - "version": "3.3.11", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", - "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", + "version": "3.3.8", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.8.tgz", + "integrity": "sha512-WNLf5Sd8oZxOm+TzppcYk8gVOgP+l58xNy58D0nbUnOxOWRWvlcCV4kUF7ltmI6PsrLl/BgKEyS4mqsGChFN0w==", "dev": true, "funding": [ { @@ -2947,9 +1996,9 @@ } }, "node_modules/postcss": { - "version": "8.5.3", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.3.tgz", - "integrity": "sha512-dle9A3yYxlBSrt8Fu+IpjGT8SY8hN0mlaA6GY8t0P5PjIOZemULz/E2Bnm/2dcUOena75OTNkHI76uZBNUUq3A==", + "version": "8.5.1", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.1.tgz", + "integrity": "sha512-6oz2beyjc5VMn/KV1pPw8fliQkhBXrVn1Z3TVyqZxU8kZpzEKhBdmCFqI6ZbmGtamQvQGuU1sgPTk8ZrXDD7jQ==", "dev": true, "funding": [ { @@ -3010,9 +2059,9 @@ } }, "node_modules/postcss-selector-parser": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.0.tgz", - "integrity": "sha512-8sLjZwK0R+JlxlYcTuVnyT2v+htpdrjDOKuMcOVdYjt52Lh8hWRYpxBPoKx/Zg+bcjc3wx6fmQevMmUztS/ccA==", + "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==", "dev": true, "license": "MIT", "dependencies": { @@ -3092,9 +2141,9 @@ } }, "node_modules/reusify": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz", - "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==", + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", + "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", "dev": true, "license": "MIT", "engines": { @@ -3127,9 +2176,9 @@ } }, "node_modules/semver": { - "version": "7.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.1.tgz", - "integrity": "sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA==", + "version": "7.6.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", + "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", "dev": true, "license": "ISC", "bin": { @@ -3255,9 +2304,9 @@ } }, "node_modules/stylelint": { - "version": "16.18.0", - "resolved": "https://registry.npmjs.org/stylelint/-/stylelint-16.18.0.tgz", - "integrity": "sha512-OXb68qzesv7J70BSbFwfK3yTVLEVXiQ/ro6wUE4UrSbKCMjLLA02S8Qq3LC01DxKyVjk7z8xh35aB4JzO3/sNA==", + "version": "16.13.2", + "resolved": "https://registry.npmjs.org/stylelint/-/stylelint-16.13.2.tgz", + "integrity": "sha512-wDlgh0mRO9RtSa3TdidqHd0nOG8MmUyVKl+dxA6C1j8aZRzpNeEgdhFmU5y4sZx4Fc6r46p0fI7p1vR5O2DZqA==", "dev": true, "funding": [ { @@ -3284,12 +2333,12 @@ "debug": "^4.3.7", "fast-glob": "^3.3.3", "fastest-levenshtein": "^1.0.16", - "file-entry-cache": "^10.0.7", + "file-entry-cache": "^10.0.5", "global-modules": "^2.0.0", "globby": "^11.1.0", "globjoin": "^0.1.4", "html-tags": "^3.3.1", - "ignore": "^7.0.3", + "ignore": "^7.0.1", "imurmurhash": "^0.1.4", "is-plain-object": "^5.0.0", "known-css-properties": "^0.35.0", @@ -3298,14 +2347,14 @@ "micromatch": "^4.0.8", "normalize-path": "^3.0.0", "picocolors": "^1.1.1", - "postcss": "^8.5.3", + "postcss": "^8.4.49", "postcss-resolve-nested-selector": "^0.1.6", "postcss-safe-parser": "^7.0.1", - "postcss-selector-parser": "^7.1.0", + "postcss-selector-parser": "^7.0.0", "postcss-value-parser": "^4.2.0", "resolve-from": "^5.0.0", "string-width": "^4.2.3", - "supports-hyperlinks": "^3.2.0", + "supports-hyperlinks": "^3.1.0", "svg-tags": "^1.0.0", "table": "^6.9.0", "write-file-atomic": "^5.0.1" @@ -3318,9 +2367,9 @@ } }, "node_modules/stylelint-config-recommended": { - "version": "16.0.0", - "resolved": "https://registry.npmjs.org/stylelint-config-recommended/-/stylelint-config-recommended-16.0.0.tgz", - "integrity": "sha512-4RSmPjQegF34wNcK1e1O3Uz91HN8P1aFdFzio90wNK9mjgAI19u5vsU868cVZboKzCaa5XbpvtTzAAGQAxpcXA==", + "version": "15.0.0", + "resolved": "https://registry.npmjs.org/stylelint-config-recommended/-/stylelint-config-recommended-15.0.0.tgz", + "integrity": "sha512-9LejMFsat7L+NXttdHdTq94byn25TD+82bzGRiV1Pgasl99pWnwipXS5DguTpp3nP1XjvLXVnEJIuYBfsRjRkA==", "dev": true, "funding": [ { @@ -3337,13 +2386,13 @@ "node": ">=18.12.0" }, "peerDependencies": { - "stylelint": "^16.16.0" + "stylelint": "^16.13.0" } }, "node_modules/stylelint-config-standard": { - "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==", + "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==", "dev": true, "funding": [ { @@ -3357,13 +2406,13 @@ ], "license": "MIT", "dependencies": { - "stylelint-config-recommended": "^16.0.0" + "stylelint-config-recommended": "^15.0.0" }, "engines": { "node": ">=18.12.0" }, "peerDependencies": { - "stylelint": "^16.18.0" + "stylelint": "^16.13.0" } }, "node_modules/stylelint/node_modules/balanced-match": { @@ -3374,25 +2423,25 @@ "license": "MIT" }, "node_modules/stylelint/node_modules/file-entry-cache": { - "version": "10.0.8", - "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-10.0.8.tgz", - "integrity": "sha512-FGXHpfmI4XyzbLd3HQ8cbUcsFGohJpZtmQRHr8z8FxxtCe2PcpgIlVLwIgunqjvRmXypBETvwhV4ptJizA+Y1Q==", + "version": "10.0.5", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-10.0.5.tgz", + "integrity": "sha512-umpQsJrBNsdMDgreSryMEXvJh66XeLtZUwA8Gj7rHGearGufUFv6rB/bcXRFsiGWw/VeSUgUofF4Rf2UKEOrTA==", "dev": true, "license": "MIT", "dependencies": { - "flat-cache": "^6.1.8" + "flat-cache": "^6.1.5" } }, "node_modules/stylelint/node_modules/flat-cache": { - "version": "6.1.8", - "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-6.1.8.tgz", - "integrity": "sha512-R6MaD3nrJAtO7C3QOuS79ficm2pEAy++TgEUD8ii1LVlbcgZ9DtASLkt9B+RZSFCzm7QHDMlXPsqqB6W2Pfr1Q==", + "version": "6.1.5", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-6.1.5.tgz", + "integrity": "sha512-QR+2kN38f8nMfiIQ1LHYjuDEmZNZVjxuxY+HufbS3BW0EX01Q5OnH7iduOYRutmgiXb797HAKcXUeXrvRjjgSQ==", "dev": true, "license": "MIT", "dependencies": { - "cacheable": "^1.8.9", - "flatted": "^3.3.3", - "hookified": "^1.8.1" + "cacheable": "^1.8.7", + "flatted": "^3.3.2", + "hookified": "^1.6.0" } }, "node_modules/stylelint/node_modules/ignore": { @@ -3429,9 +2478,9 @@ } }, "node_modules/supports-hyperlinks": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/supports-hyperlinks/-/supports-hyperlinks-3.2.0.tgz", - "integrity": "sha512-zFObLMyZeEwzAoKCyu1B91U79K2t7ApXuQfo8OuxwXLDgcKxuwM+YvcbIhm6QWqz7mHUH1TVytR1PwVVjEuMig==", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/supports-hyperlinks/-/supports-hyperlinks-3.1.0.tgz", + "integrity": "sha512-2rn0BZ+/f7puLOHZm1HOJfwBggfaHXUpPUSSG/SWM4TWp5KCfmNYwnC3hruy2rZlMnmWZ+QAGpZfchu3f3695A==", "dev": true, "license": "MIT", "dependencies": { @@ -3442,7 +2491,7 @@ "node": ">=14.18" }, "funding": { - "url": "https://github.com/chalk/supports-hyperlinks?sponsor=1" + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/svg-tags": { @@ -3506,16 +2555,16 @@ } }, "node_modules/ts-api-utils": { - "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==", + "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==", "dev": true, "license": "MIT", "engines": { - "node": ">=18.12" + "node": ">=16" }, "peerDependencies": { - "typescript": ">=4.8.4" + "typescript": ">=4.2.0" } }, "node_modules/type-check": { @@ -3532,9 +2581,9 @@ } }, "node_modules/typescript": { - "version": "5.8.3", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.3.tgz", - "integrity": "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==", + "version": "5.7.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.7.3.tgz", + "integrity": "sha512-84MVSjMEHP+FQRPy3pX9sTVV/INIex71s9TL2Gm5FG/WG1SqXeKyZ0k7/blY/4FdOzI12CBy1vGc4og/eus0fw==", "dev": true, "license": "Apache-2.0", "peer": true, diff --git a/package.json b/package.json index a6e358f3..c70f52f0 100644 --- a/package.json +++ b/package.json @@ -7,23 +7,21 @@ "license": "CC0-1.0", "devDependencies": { "@eslint/js": "^9.6.0", - "@stylistic/eslint-plugin": "^4.2.0", - "esbuild": "^0.25.2", + "@stylistic/eslint-plugin": "^3.0.0", "eslint": "^9.7.0", - "globals": "^16.0.0", - "lightningcss": "^1.29.3", - "lightningcss-cli": "^1.29.3", + "globals": "^15.8.0", "stylelint": "^16.7.0", - "stylelint-config-standard": "^38.0.0" + "stylelint-config-standard": "^37.0.0" }, "scripts": { "eslint": "eslint public/assets/js/*.js", - "stylelint": "stylelint resources/css/*.css", + "stylelint": "stylelint public/assets/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 new file mode 100644 index 00000000..f0ba5a6f --- /dev/null +++ b/postcss.config.js @@ -0,0 +1,16 @@ +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 new file mode 100644 index 00000000..da693b64 --- /dev/null +++ b/psalm.xml @@ -0,0 +1,18 @@ + + + + + + + + + + + diff --git a/public/assets/css/app.css b/public/assets/css/app.css index 07bfbf3d..12bddcc2 100644 --- a/public/assets/css/app.css +++ b/public/assets/css/app.css @@ -1,2 +1,8 @@ -: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 */ +@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'); diff --git a/public/assets/css/app.css.br b/public/assets/css/app.css.br index 405c55ea..8a4bcff6 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 deleted file mode 100644 index 877478ee..00000000 --- a/public/assets/css/app.css.map +++ /dev/null @@ -1 +0,0 @@ -{"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 460b37b6..f27d4f69 100644 Binary files a/public/assets/css/app.css.zst and b/public/assets/css/app.css.zst differ diff --git a/resources/css/code.css b/public/assets/css/code.css similarity index 100% rename from resources/css/code.css rename to public/assets/css/code.css diff --git a/resources/css/colours.css b/public/assets/css/colours.css similarity index 100% rename from resources/css/colours.css rename to public/assets/css/colours.css diff --git a/public/assets/css/colours.css.br b/public/assets/css/colours.css.br new file mode 100644 index 00000000..ddedc7a3 Binary files /dev/null and b/public/assets/css/colours.css.br differ diff --git a/public/assets/css/colours.css.zst b/public/assets/css/colours.css.zst new file mode 100644 index 00000000..fd30b78c Binary files /dev/null and b/public/assets/css/colours.css.zst differ diff --git a/resources/css/content.css b/public/assets/css/content.css similarity index 100% rename from resources/css/content.css rename to public/assets/css/content.css diff --git a/public/assets/css/content.css.br b/public/assets/css/content.css.br new file mode 100644 index 00000000..ae9d299d Binary files /dev/null and b/public/assets/css/content.css.br differ diff --git a/public/assets/css/content.css.zst b/public/assets/css/content.css.zst new file mode 100644 index 00000000..01cfa187 Binary files /dev/null and b/public/assets/css/content.css.zst differ diff --git a/resources/css/fonts.css b/public/assets/css/fonts.css similarity index 100% rename from resources/css/fonts.css rename to public/assets/css/fonts.css diff --git a/public/assets/css/fonts.css.br b/public/assets/css/fonts.css.br new file mode 100644 index 00000000..a99e5684 Binary files /dev/null and b/public/assets/css/fonts.css.br differ diff --git a/public/assets/css/fonts.css.zst b/public/assets/css/fonts.css.zst new file mode 100644 index 00000000..c8ec55f1 Binary files /dev/null and b/public/assets/css/fonts.css.zst differ diff --git a/resources/css/h-card.css b/public/assets/css/h-card.css similarity index 100% rename from resources/css/h-card.css rename to public/assets/css/h-card.css diff --git a/public/assets/css/h-card.css.br b/public/assets/css/h-card.css.br new file mode 100644 index 00000000..8dd3d22d Binary files /dev/null and b/public/assets/css/h-card.css.br differ diff --git a/public/assets/css/h-card.css.zst b/public/assets/css/h-card.css.zst new file mode 100644 index 00000000..b54e6a24 Binary files /dev/null and b/public/assets/css/h-card.css.zst differ diff --git a/resources/css/indieauth.css b/public/assets/css/indieauth.css similarity index 100% rename from resources/css/indieauth.css rename to public/assets/css/indieauth.css diff --git a/public/assets/css/indieauth.css.br b/public/assets/css/indieauth.css.br new file mode 100644 index 00000000..abb4c9c7 Binary files /dev/null and b/public/assets/css/indieauth.css.br differ diff --git a/public/assets/css/indieauth.css.zst b/public/assets/css/indieauth.css.zst new file mode 100644 index 00000000..5e9e979f Binary files /dev/null and b/public/assets/css/indieauth.css.zst differ diff --git a/resources/css/layout.css b/public/assets/css/layout.css similarity index 100% rename from resources/css/layout.css rename to public/assets/css/layout.css diff --git a/public/assets/css/layout.css.br b/public/assets/css/layout.css.br new file mode 100644 index 00000000..2fe4608b Binary files /dev/null and b/public/assets/css/layout.css.br differ diff --git a/public/assets/css/layout.css.zst b/public/assets/css/layout.css.zst new file mode 100644 index 00000000..aa9bded4 Binary files /dev/null and b/public/assets/css/layout.css.zst differ diff --git a/resources/css/notes.css b/public/assets/css/notes.css similarity index 100% rename from resources/css/notes.css rename to public/assets/css/notes.css diff --git a/public/assets/css/notes.css.br b/public/assets/css/notes.css.br new file mode 100644 index 00000000..686ddf2f Binary files /dev/null and b/public/assets/css/notes.css.br differ diff --git a/public/assets/css/notes.css.zst b/public/assets/css/notes.css.zst new file mode 100644 index 00000000..f9cdbf82 Binary files /dev/null and b/public/assets/css/notes.css.zst differ diff --git a/resources/css/variables.css b/public/assets/css/variables.css similarity index 100% rename from resources/css/variables.css rename to public/assets/css/variables.css diff --git a/public/assets/css/variables.css.br b/public/assets/css/variables.css.br new file mode 100644 index 00000000..4ff480f4 Binary files /dev/null and b/public/assets/css/variables.css.br differ diff --git a/public/assets/css/variables.css.zst b/public/assets/css/variables.css.zst new file mode 100644 index 00000000..e1ccda8b Binary files /dev/null and b/public/assets/css/variables.css.zst differ diff --git a/public/assets/frontend/is-land.js b/public/assets/frontend/is-land.js new file mode 100644 index 00000000..435a13b9 --- /dev/null +++ b/public/assets/frontend/is-land.js @@ -0,0 +1,338 @@ +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