diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..9fc4e22 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,126 @@ +name: CI + +on: + push: + branches: + - master + - main + pull_request: + branches: + - master + - main + workflow_dispatch: + +permissions: + contents: read + +concurrency: + group: ci-${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + relay: + name: Relay tests and coverage + runs-on: ubuntu-latest + defaults: + run: + working-directory: relay + steps: + - name: Checkout repository + uses: actions/checkout@fbc6f3992d24b796d5a048ff273f7fcc4a7b6c09 # v5.0.0 + + - name: Set up Go + uses: actions/setup-go@d35c59abb061a4a6fb18e82ac0862c26744d6ab5 # v5.5.0 + with: + go-version-file: relay/go.mod + cache: true + + - name: Download Go dependencies + run: go mod download + + - name: Run Go tests with coverage + run: go test -covermode=atomic -coverprofile=coverage.out ./... + + - name: Upload Go coverage + uses: actions/upload-artifact@v4.6.2 + with: + name: relay-coverage + path: relay/coverage.out + if-no-files-found: error + + blossom: + name: Blossom tests and build + runs-on: ubuntu-latest + defaults: + run: + working-directory: blossom + steps: + - name: Checkout repository + uses: actions/checkout@fbc6f3992d24b796d5a048ff273f7fcc4a7b6c09 # v5.0.0 + + - name: Set up pnpm + uses: pnpm/action-setup@b906affcce14559ad1aafd4ab0e942779e9f58b1 # v4.0.0 + + - name: Set up Node.js + uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0 + with: + node-version: 22 + cache: pnpm + cache-dependency-path: blossom/pnpm-lock.yaml + + - name: Install dependencies + run: pnpm install --frozen-lockfile + + - name: Run Blossom rules tests + run: node --test src/rules/index.test.mjs + + - name: Build Blossom and admin application + run: pnpm build + + dashboard: + name: Dashboard JavaScript syntax + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@fbc6f3992d24b796d5a048ff273f7fcc4a7b6c09 # v5.0.0 + + - name: Set up Node.js + uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0 + with: + node-version: 22 + + - name: Check dashboard JavaScript syntax + run: node --check relay/web/static/script.js + + nip-coverage: + name: Validate NIP coverage matrix + runs-on: ubuntu-latest + defaults: + run: + working-directory: relay + steps: + - name: Checkout repository + uses: actions/checkout@fbc6f3992d24b796d5a048ff273f7fcc4a7b6c09 # v5.0.0 + + - name: Validate NIP coverage data + shell: bash + run: | + set -euo pipefail + matrix=tests/nips/coverage.tsv + test -s "$matrix" + awk -F '\t' ' + $1 ~ /^#/ { next } + NF < 5 || $1 !~ /^NIP-[0-9A-Z]+$/ || ($3 != "integration" && $3 != "contract" && $3 != "manual" && $3 != "external") { + print "invalid NIP coverage row " NR ": " $0 > "/dev/stderr" + exit 1 + } + END { + if (NR < 1) { + print "coverage.tsv contains no NIP entries" > "/dev/stderr" + exit 1 + } + } + ' "$matrix" + + test -x tests/nips/run_all.sh + test -x tests/nips/run_coverage.sh diff --git a/blossom/admin/package.json b/blossom/admin/package.json index 3e26fa6..53a64db 100644 --- a/blossom/admin/package.json +++ b/blossom/admin/package.json @@ -28,5 +28,10 @@ "prettier": "^3.9.6", "typescript": "^7.0.2", "vite": "^8.2.2" + }, + "pnpm": { + "overrides": { + "decode-uri-component": "^0.5.0" + } } } diff --git a/blossom/admin/pnpm-lock.yaml b/blossom/admin/pnpm-lock.yaml index 957b200..c9e13b3 100644 --- a/blossom/admin/pnpm-lock.yaml +++ b/blossom/admin/pnpm-lock.yaml @@ -4,6 +4,9 @@ settings: autoInstallPeers: true excludeLinksFromLockfile: false +overrides: + decode-uri-component: ^0.5.0 + importers: .: @@ -313,42 +316,36 @@ packages: engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [linux] - libc: [glibc] '@rolldown/binding-linux-arm64-musl@1.2.5': resolution: {integrity: sha512-xMvZgnbZg4YVnR/AX2b3oOPDTFYJvUVaJg5FedA/LuvexAtXibZQej4cnTkw3rjsJ/ggUROB64TdtETiim+FYA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [linux] - libc: [musl] '@rolldown/binding-linux-ppc64-gnu@1.2.5': resolution: {integrity: sha512-GRjeqTUDHTo5GwntsLaAMcBahG3nlpjftXWZLN73HiYQlhwEowvarFgQnRnQZtIp4keXX7quXFbG38uPZBa2EA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [ppc64] os: [linux] - libc: [glibc] '@rolldown/binding-linux-s390x-gnu@1.2.5': resolution: {integrity: sha512-vLNTR45F2Uwc8AufkNXPmB4VliaXs+FvcheEogIzOXzO4l+LzieXF5A/TWxLy5HtqpsRCHUfd0lPVrrdgXdLHQ==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [s390x] os: [linux] - libc: [glibc] '@rolldown/binding-linux-x64-gnu@1.2.5': resolution: {integrity: sha512-Mgj59/HTuYeK9Gz2MA+mBWKnHsAgkBSec15ZMb1st3oIfFbX7gCjOae7GydHhzcyQi9Z/7M1QuN9bR3oFqF0jQ==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [linux] - libc: [glibc] '@rolldown/binding-linux-x64-musl@1.2.5': resolution: {integrity: sha512-mY8AP0/ichsbhAxGnLa3d3+MwV0EfgrPND2bplI3Ym8T6R2pJ0N87bvrKVwNXmdy3jnr6eQBecdqx/HMknBmpA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [linux] - libc: [musl] '@rolldown/binding-openharmony-arm64@1.2.5': resolution: {integrity: sha512-8SLssA2oweAxyRgDp789ACfRb/3P+zNRJpzZxSizxF9m8NUDQ4+3xjo8ttjhVGGw6Qxb70oZiEtIjaKikCO7Yw==} @@ -619,9 +616,9 @@ packages: supports-color: optional: true - decode-uri-component@0.2.2: - resolution: {integrity: sha512-FqUYQ+8o158GyGTrMFJms9qh3CqTKvAqgqsTnkLI8sKu0028orqBhxNMFkFen0zGyg6epACD32pjVk58ngIErQ==} - engines: {node: '>=0.10'} + decode-uri-component@0.5.0: + resolution: {integrity: sha512-1BiQVoK8C9gUbQU6NzAtO/tkz2qOFpEObMWpcFvhx4fYnj4Oc5yzaJN/LD36ihkVUdXyh5ZekzX+yM+ty/SrPg==} + engines: {node: '>=14.16'} define-data-property@1.1.4: resolution: {integrity: sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==} @@ -809,28 +806,24 @@ packages: engines: {node: '>= 12.0.0'} cpu: [arm64] os: [linux] - libc: [glibc] lightningcss-linux-arm64-musl@1.33.0: resolution: {integrity: sha512-yiO5ROMuYQgXbC60yjZU5CYSFZGKXL0HFATXt9mHJn1+zW55oCtMI9NfcVhYLMFDL7gV7oBPon/EmMMGg2OvtQ==} engines: {node: '>= 12.0.0'} cpu: [arm64] os: [linux] - libc: [musl] lightningcss-linux-x64-gnu@1.33.0: resolution: {integrity: sha512-ar+Ju7LmcN0Jo4FpL4hpFybwNG9/3A/Br5KW2n2jyODg3MEZXaDYADdemoNS+BDNfMgKvylJLj4S5tyRActuAg==} engines: {node: '>= 12.0.0'} cpu: [x64] os: [linux] - libc: [glibc] lightningcss-linux-x64-musl@1.33.0: resolution: {integrity: sha512-RYiYbkokw0trfKqqzfF55lginwEPrD3OJDfTuJzFs1MK6iFnDenaz1fqLLtX4ITG3OktJQXOeTaw1awrBAlZPw==} engines: {node: '>= 12.0.0'} cpu: [x64] os: [linux] - libc: [musl] lightningcss-win32-arm64-msvc@1.33.0: resolution: {integrity: sha512-1K+MPfLSFVpphzpdbfkhlWk6wBrTObBzS2T6db10PNOZgR9GoVsAWzwNyuhUYYbTp23j+4RrncfujZ4uAzXvwA==} @@ -1667,7 +1660,7 @@ snapshots: dependencies: ms: 2.1.3 - decode-uri-component@0.2.2: {} + decode-uri-component@0.5.0: {} define-data-property@1.1.4: dependencies: @@ -1956,7 +1949,7 @@ snapshots: query-string@7.1.3: dependencies: - decode-uri-component: 0.2.2 + decode-uri-component: 0.5.0 filter-obj: 1.1.0 split-on-first: 1.1.0 strict-uri-encode: 2.0.0 @@ -2006,7 +1999,7 @@ snapshots: - react-router - react-router-dom - ra-ui-materialui@5.15.1(1cbb299f6a02d7eac93eb1bb46c412f1): + ra-ui-materialui@5.15.1(222zx5jzkydzyigvq67pq3ecju): dependencies: '@mui/icons-material': 9.3.1(@mui/material@9.3.1(@emotion/react@11.14.0(@types/react@19.2.18)(react@19.2.8))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.2.18)(react@19.2.8))(@types/react@19.2.18)(react@19.2.8))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8))(@types/react@19.2.18)(react@19.2.8) '@mui/material': 9.3.1(@emotion/react@11.14.0(@types/react@19.2.18)(react@19.2.8))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.2.18)(react@19.2.8))(@types/react@19.2.18)(react@19.2.8))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) @@ -2045,7 +2038,7 @@ snapshots: ra-core: 5.15.0(@tanstack/react-query@5.101.4(react@19.2.8))(react-dom@19.2.8(react@19.2.8))(react-hook-form@7.85.0(react@19.2.8))(react-router-dom@7.18.2(react-dom@19.2.8(react@19.2.8))(react@19.2.8))(react-router@7.18.2(react-dom@19.2.8(react@19.2.8))(react@19.2.8))(react@19.2.8) ra-i18n-polyglot: 5.15.0(@tanstack/react-query@5.101.4(react@19.2.8))(react-dom@19.2.8(react@19.2.8))(react-hook-form@7.85.0(react@19.2.8))(react-router-dom@7.18.2(react-dom@19.2.8(react@19.2.8))(react@19.2.8))(react-router@7.18.2(react-dom@19.2.8(react@19.2.8))(react@19.2.8))(react@19.2.8) ra-language-english: 5.15.0(@tanstack/react-query@5.101.4(react@19.2.8))(react-dom@19.2.8(react@19.2.8))(react-hook-form@7.85.0(react@19.2.8))(react-router-dom@7.18.2(react-dom@19.2.8(react@19.2.8))(react@19.2.8))(react-router@7.18.2(react-dom@19.2.8(react@19.2.8))(react@19.2.8))(react@19.2.8) - ra-ui-materialui: 5.15.1(1cbb299f6a02d7eac93eb1bb46c412f1) + ra-ui-materialui: 5.15.1(222zx5jzkydzyigvq67pq3ecju) react: 19.2.8 react-dom: 19.2.8(react@19.2.8) react-hook-form: 7.85.0(react@19.2.8) diff --git a/blossom/admin/pnpm-workspace.yaml b/blossom/admin/pnpm-workspace.yaml new file mode 100644 index 0000000..2a3d1b0 --- /dev/null +++ b/blossom/admin/pnpm-workspace.yaml @@ -0,0 +1,3 @@ +packages: [] +overrides: + decode-uri-component: ^0.5.0 diff --git a/blossom/package.json b/blossom/package.json index a327e55..5416fbf 100644 --- a/blossom/package.json +++ b/blossom/package.json @@ -46,7 +46,7 @@ "mkdirp": "^3.0.1", "nanoid": "^6.0.1", "nostr-tools": "^2.24.3", - "sharp": "^0.35.3", + "sharp": "^0.35.4", "socks-proxy-agent": "^10.1.0", "ws": "^8.21.3", "yaml": "^2.9.0" @@ -74,5 +74,16 @@ "prettier": "^3.9.6", "typescript": "^7.0.2" }, - "packageManager": "pnpm@9.12.0" + "packageManager": "pnpm@9.12.0", + "pnpm": { + "overrides": { + "qs": "^6.16.0", + "decode-uri-component": "^0.5.0", + "stream-json": "^3.5.0" + }, + "patchedDependencies": { + "minio@8.0.7": "patches/minio@8.0.7.patch", + "stream-json@3.6.0": "patches/stream-json@3.6.0.patch" + } + } } diff --git a/blossom/patches/minio@8.0.7.patch b/blossom/patches/minio@8.0.7.patch new file mode 100644 index 0000000..d8bd35e --- /dev/null +++ b/blossom/patches/minio@8.0.7.patch @@ -0,0 +1,39 @@ +diff --git a/dist/esm/notification.mjs b/dist/esm/notification.mjs +index 2fe075b73a9f98ece945993ebc151f601e0d52ee..b6b2b6a60f3b2e699a351540bd9f6aa8259b2e33 100644 +--- a/dist/esm/notification.mjs ++++ b/dist/esm/notification.mjs +@@ -15,7 +15,7 @@ + */ +  + import { EventEmitter } from 'eventemitter3'; +-import jsonLineParser from 'stream-json/jsonl/Parser.js'; ++import jsonLineParser from 'stream-json/jsonl/parser.js'; + import { DEFAULT_REGION } from "./helpers.mjs"; + import { pipesetup, uriEscape } from "./internal/helper.mjs"; +  +diff --git a/dist/main/notification.js b/dist/main/notification.js +index 8d8e8b389bc313de072c603b302e4c39d373d345..84abcb1b303e5e8e648b56cfbfb20c54b696bf31 100644 +--- a/dist/main/notification.js ++++ b/dist/main/notification.js +@@ -4,7 +4,7 @@ Object.defineProperty(exports, "__esModule", { + value: true + }); + var _eventemitter = require("eventemitter3"); +-var _Parser = require("stream-json/jsonl/Parser.js"); ++var _Parser = require("stream-json/jsonl/parser.js"); + var _helpers = require("./helpers.js"); + var _helper = require("./internal/helper.js"); + /* +diff --git a/src/notification.ts b/src/notification.ts +index 64c24112d225a234d3b5acf06043020e9fe414af..debce83dc4708745137abc4eb45218a7c7d40f0c 100644 +--- a/src/notification.ts ++++ b/src/notification.ts +@@ -15,7 +15,7 @@ + */ +  + import { EventEmitter } from 'eventemitter3' +-import jsonLineParser from 'stream-json/jsonl/Parser.js' ++import jsonLineParser from 'stream-json/jsonl/parser.js' +  + import { DEFAULT_REGION } from './helpers.ts' + import type { TypedClient } from './internal/client.ts' diff --git a/blossom/patches/stream-json@3.6.0.patch b/blossom/patches/stream-json@3.6.0.patch new file mode 100644 index 0000000..2cf54b4 --- /dev/null +++ b/blossom/patches/stream-json@3.6.0.patch @@ -0,0 +1,7 @@ +diff --git a/src/jsonl/Parser.js b/src/jsonl/Parser.js +new file mode 100644 +index 0000000000000000000000000000000000000000..84d579544cf1c21bba8fbaab5465eac1c1175f86 +--- /dev/null ++++ b/src/jsonl/Parser.js +@@ -0,0 +1 @@ ++export { default } from "./parser.js"; diff --git a/blossom/pnpm-lock.yaml b/blossom/pnpm-lock.yaml index 7e0e263..d16a01c 100644 --- a/blossom/pnpm-lock.yaml +++ b/blossom/pnpm-lock.yaml @@ -4,6 +4,19 @@ settings: autoInstallPeers: true excludeLinksFromLockfile: false +overrides: + qs: ^6.16.0 + decode-uri-component: ^0.5.0 + stream-json: ^3.5.0 + +patchedDependencies: + minio@8.0.7: + hash: aqyn4mvirmcqlyuzxvk4llalu4 + path: patches/minio@8.0.7.patch + stream-json@3.6.0: + hash: 55awxyx7amzdxcxf7zny3nhfma + path: patches/stream-json@3.6.0.patch + importers: .: @@ -70,7 +83,7 @@ importers: version: 4.1.0 minio: specifier: ^8.0.7 - version: 8.0.7 + version: 8.0.7(patch_hash=aqyn4mvirmcqlyuzxvk4llalu4) mkdirp: specifier: ^3.0.1 version: 3.0.1 @@ -81,8 +94,8 @@ importers: specifier: ^2.24.3 version: 2.24.3(typescript@7.0.2) sharp: - specifier: ^0.35.3 - version: 0.35.3(@types/node@26.2.0) + specifier: ^0.35.4 + version: 0.35.4(@types/node@26.2.0) socks-proxy-agent: specifier: ^10.1.0 version: 10.1.0 @@ -256,144 +269,144 @@ packages: resolution: {integrity: sha512-Td76q7j57o/tLVdgS746cYARfSyxk8iEfRxewL9h4OMzYhbW4TAcppl0mT4eyqXddh6L/jwoM75mo7ixa/pCeQ==} engines: {node: '>=18'} - '@img/sharp-darwin-arm64@0.35.3': - resolution: {integrity: sha512-RMnFX7YQsMoh7lWfcM4NEHHymBX/rLuKNPVM84XE9ONPcaSCDgE7CHIHpSgPcO2xcRthgBy1HfNO319mwhIAkg==} + '@img/sharp-darwin-arm64@0.35.4': + resolution: {integrity: sha512-Uhfl4V4lhP2nbUVF9+hyH1+luj86f1gUFeo8ALYxFoULoU+G87D43BfeMP8XHsk9boxAnCY/bf2EHwhA7MuGsA==} engines: {node: '>=20.9.0'} cpu: [arm64] os: [darwin] - '@img/sharp-darwin-x64@0.35.3': - resolution: {integrity: sha512-Xo+5uFBtLN0BKqieTxiFzFPQAUlBbbH5iBKyRX/z1JrbnYsHTfKJnUfL8+p2TPXr1pXqao4eeL4Rl144uDpK9w==} + '@img/sharp-darwin-x64@0.35.4': + resolution: {integrity: sha512-hWniXY3bG5qKpkKrAwPe4y+VTPmf086YQAnkxWh7uA1YrlRouWGa0M0Mxj3ZjnXFkv7/TD1bTy9lGUK26vRvWw==} engines: {node: '>=20.9.0'} cpu: [x64] os: [darwin] - '@img/sharp-freebsd-wasm32@0.35.3': - resolution: {integrity: sha512-lUxcqWIj2wMQ9BrwNjngcr1gWUr5xgaGThBRqPPalIC2n67Cqj1uPh8NnA/ZhAg8hUbKl+kVHKwgUIwe6ZYPrg==} + '@img/sharp-freebsd-wasm32@0.35.4': + resolution: {integrity: sha512-lIsKw/BU+kjB4eZjxrYrZmwOJYi3Ajrv66iAlBmUPyKc3HpnloevB1g3wxGD9P/5BbQ1brBGl65VRRrCvQDEqA==} engines: {node: '>=20.9.0'} os: [freebsd] - '@img/sharp-libvips-darwin-arm64@1.3.2': - resolution: {integrity: sha512-9J6ypZFpQBj4YnePGoq/S38w6nz+vqg5WZLrLGY4YuSemdMq47GMLBPO42MzwdGwpg/agZ7xzZcFHa48xlywfg==} + '@img/sharp-libvips-darwin-arm64@1.3.3': + resolution: {integrity: sha512-suTBPTDGrI9WodccaDdwZItTSaBYASlBk1NSfElSHrUfzu3szG6lvIF58+WiFvnfzuK8ZBFS5zE00PxqxnRiPg==} cpu: [arm64] os: [darwin] - '@img/sharp-libvips-darwin-x64@1.3.2': - resolution: {integrity: sha512-m2pW1n6cns9VaubNwsZ+c3CRYjxNQWgJ5gPlnL1nbBcpkBvFm6SCFN5o0psFHI8w9n11NKhFkeEDns98tiqbEw==} + '@img/sharp-libvips-darwin-x64@1.3.3': + resolution: {integrity: sha512-FVJZ5mITMobmXIz/hPDTw0EintTW5H3WfrxwLqEqjiIihlu+hVRyGrFQ60xl0Lxn7Bt3zdpevPaQi0HEzqz9fw==} cpu: [x64] os: [darwin] - '@img/sharp-libvips-linux-arm64@1.3.2': - resolution: {integrity: sha512-dqVSFynCox4C/J8kT16V7SIFAns0IjgLwkvYT7p8LQVmJ5OS5b6tI9IGflxTeuBS//zXeFIUbwt5dwxyZ17cnA==} + '@img/sharp-libvips-linux-arm64@1.3.3': + resolution: {integrity: sha512-0DaL0A6Xu6sQSQFwe4iVCrKWU2cCTItnRsYsCdxAMm9NF6twAA9BKnoqy4hqz4+azQ0JHuA26qiUKsf1XJ/v5A==} cpu: [arm64] os: [linux] - '@img/sharp-libvips-linux-arm@1.3.2': - resolution: {integrity: sha512-1eMLzy92I4J6rmi4mAT8yC3HxOtniyGELlzGbNMLLeqe052ahFQ0h6LFq+lh5DsDIdYViIDst08abvSbcEdLXQ==} + '@img/sharp-libvips-linux-arm@1.3.3': + resolution: {integrity: sha512-3rbU4vqXXc3hY/OiXdl52xZvT0F1yEngWfvqudtPJg/KkyiaQw2DRsFrNzpmLvfavbwOq3qXn36GP8obHRULQA==} cpu: [arm] os: [linux] - '@img/sharp-libvips-linux-ppc64@1.3.2': - resolution: {integrity: sha512-3z0NHDxD6n5I9gc05U1eW1AyRm+Gznzq3naMrthPNqE6oYykcogW0l/jfpJdjYnuNl8R7yI9pNbE1XiUeyq0Aw==} + '@img/sharp-libvips-linux-ppc64@1.3.3': + resolution: {integrity: sha512-cdn1OvUBwsXhbC0zSzJnNzf5MZ/mTrobawDvNXBTxe8VtqKAm0sRuEY2Evzovb/w9JMk4TvRxqt1mekSuJz64w==} cpu: [ppc64] os: [linux] - '@img/sharp-libvips-linux-riscv64@1.3.2': - resolution: {integrity: sha512-bsb4rI+NldGOsXuej2r8OdSS8+zXDVaCWxyWrcv6kneTOlgAHtZABRzBBCwdsPiD90J4myNJuHpg6kA20ImW/w==} + '@img/sharp-libvips-linux-riscv64@1.3.3': + resolution: {integrity: sha512-HjPVx7yKz+0lqdhDlTw1tt90wamBoxhiXpvl1XZpJLiHH4RCJ5yDTqH+VlYPv2fwFs89JFw4c1IexYOcQUi4IQ==} cpu: [riscv64] os: [linux] - '@img/sharp-libvips-linux-s390x@1.3.2': - resolution: {integrity: sha512-/ABshyj8gCpyIrNXnHn4LorDJ0HHm1VhXPBlxZ8zAtfVPAaSafXPGn+sUSIRiwaSBy0mmFjSjiXI5mkcwdChKQ==} + '@img/sharp-libvips-linux-s390x@1.3.3': + resolution: {integrity: sha512-neWLh+3yCNThxnfy3c4BbVBeGgt9aftno+XbT56iK28RgeDs3UOFWviLWlUu0bArYVYJaFDK+RRohbicUNCm8Q==} cpu: [s390x] os: [linux] - '@img/sharp-libvips-linux-x64@1.3.2': - resolution: {integrity: sha512-ITPEtgffGJ0S6G9dRyw/366tJQqFRcHWPHhC+Stpg3Z8AEMrDrTr2lhdz4f/Y/HMbRh//7Z5mBzEpVdi62Oc3w==} + '@img/sharp-libvips-linux-x64@1.3.3': + resolution: {integrity: sha512-4vKmvAst9nrowcqquKFAyZJUDolUaIp8uRiN0mWFguJ1IplC9/pitXtlnnlU4aa/eJw3J7i67V+pwUL+wZGdsA==} cpu: [x64] os: [linux] - '@img/sharp-libvips-linuxmusl-arm64@1.3.2': - resolution: {integrity: sha512-zE9EdiUzUmg5mDT5a1rk5fYJ6GWPloTwWBYDS14naqHsL+EaMpDj1AWnpLgh3u0YCORv2Tt50wrcrpYqkP97Kw==} + '@img/sharp-libvips-linuxmusl-arm64@1.3.3': + resolution: {integrity: sha512-Y9kQaLMuNoB0bPYOOdcZMaseNrFpPodIWWMrx+CZyydf2xn68j9WYc6sWWRrDwNkzCQjKYfc68L7jKjGlHMibw==} cpu: [arm64] os: [linux] - '@img/sharp-libvips-linuxmusl-x64@1.3.2': - resolution: {integrity: sha512-m0lrLiUt+lBYnCFr8qV/65yMR4E/c7/wf78I5eKTdkEakFAlZ9QlzEM3QIhhAwVeUhLAHLcCq7a7Vszq/oFNZQ==} + '@img/sharp-libvips-linuxmusl-x64@1.3.3': + resolution: {integrity: sha512-fj8Mv0HHfD1Rr+4I68+3agJynxDWtBFgicTbSOb9Bke6pIwzGcJ+RX/yHjmiEGFMCavY/dxvem7MyNaJF+wDiw==} cpu: [x64] os: [linux] - '@img/sharp-linux-arm64@0.35.3': - resolution: {integrity: sha512-QgKDspHPnrU+GQ55XPhGwyhC8acLVOOSyAvo1oVfFmrIXLkDNmGWzAfDZ4xK8oSA1qBQrALcHX0G5UZni/SuFQ==} + '@img/sharp-linux-arm64@0.35.4': + resolution: {integrity: sha512-De4jpEnAU8Hd5oT0j1G3uL4ZvTuipVMn7YC6vPaJhy6/7EwEae0SVAoBrUMYQbkLGDm85taVWwuPc1a44LTzCQ==} engines: {node: '>=20.9.0'} cpu: [arm64] os: [linux] - '@img/sharp-linux-arm@0.35.3': - resolution: {integrity: sha512-affVWCTLooy8TSxbDx2qkzuDeaWLNVBA+P//FNBirHsXpP2fuBhk5AuboYUnrDnzoXes8GFjpTx0SBFOCRg+FA==} + '@img/sharp-linux-arm@0.35.4': + resolution: {integrity: sha512-7OAS8gI0EReKGVN2HssHlM6umJgxF5VI3xN0p9FA91p/YO+ou5hiNghLdZ5BEHztwaaK5+bLKRf8x/o2L2nk9A==} engines: {node: '>=20.9.0'} cpu: [arm] os: [linux] - '@img/sharp-linux-ppc64@0.35.3': - resolution: {integrity: sha512-sMd8rDxmpLOwv/7N44klFjOD5DUO7FLdjiXDI0hoxYaf7Ar262dQIEkosE98bps+5HPLtp/EvNqeqQtOycP/IA==} + '@img/sharp-linux-ppc64@0.35.4': + resolution: {integrity: sha512-2oYZJeIl4kCcMGk4ouZVjnkCtFrpQFlNEtJ6GbxzhHQchwH0NH/qEb9ykmOl29dqwMq+JhFdZn+1ak2FKhI9fQ==} engines: {node: '>=20.9.0'} cpu: [ppc64] os: [linux] - '@img/sharp-linux-riscv64@0.35.3': - resolution: {integrity: sha512-0Eob78yjlYPfL5vMNWAW55l3R9Y6BQS/gOfe0ZcP9mEz9ohhKSt4im1hayiknXgf8AWrFqMvJcKIdmLmEe7yeQ==} + '@img/sharp-linux-riscv64@0.35.4': + resolution: {integrity: sha512-cPbNChoRURAWdebDIHSenxRpgEdy7JkPydSnUxRm9VvKD7m0/xVaR/8Fzlu81pk5nHEvHH87UZUA7cTtwnbJSA==} engines: {node: '>=20.9.0'} cpu: [riscv64] os: [linux] - '@img/sharp-linux-s390x@0.35.3': - resolution: {integrity: sha512-KgAxQ0DxpNOq1rG2t5cgTgShJFGSuU7XO45cqC+1NVOuZnP6tlgZRuSYOfNupGkHID0o3cJOsw4DVeJpMovcGw==} + '@img/sharp-linux-s390x@0.35.4': + resolution: {integrity: sha512-RY0JFY8Fd6RonCBtHz+DvadaPkXDSI1AUn6yWL9TipqkZ1vY8w8evqdgyDFnkm4/K1ve1TvZiaePP5oSd4+WVQ==} engines: {node: '>=20.9.0'} cpu: [s390x] os: [linux] - '@img/sharp-linux-x64@0.35.3': - resolution: {integrity: sha512-8pqvxubL2PGdhlPy6GLqzDYMUjyRmKAwKHYKixpdJYBUK7PJ0C029XdsnpFIdgRZG68fZiGdHVWcKPvtiPB4cA==} + '@img/sharp-linux-x64@0.35.4': + resolution: {integrity: sha512-9qvvEAuk8k89TfWUoX2htWjbAMX8p+NxCppjpcg5k6xMsjhBQPTsoIh36h9Qde4WRuGpJeYnOjdosDn/cnv+OA==} engines: {node: '>=20.9.0'} cpu: [x64] os: [linux] - '@img/sharp-linuxmusl-arm64@0.35.3': - resolution: {integrity: sha512-Vz0iQjzzcSX3HCbfwFfCSG/9SCIqyO0mH2sXyiHaAYfBk0cRsCWXRyQYX0ovCK/PAQBbTzQ0dsPQHh5MAFL59w==} + '@img/sharp-linuxmusl-arm64@0.35.4': + resolution: {integrity: sha512-KB5jxpfWQTr0nc3xdHtWChdbifHrBGsd2SM62Eyxrl8afikm+f5qGBU75SJIZBT/S1MC8XyacdlXBMSWq6OURA==} engines: {node: '>=20.9.0'} cpu: [arm64] os: [linux] - '@img/sharp-linuxmusl-x64@0.35.3': - resolution: {integrity: sha512-6O1NPKcDVj9QEdg7Hx549EX8U0rp6yXQERqru6yRN7fGBn32UvIRJUlWnk+8xDCiG76hXVBbX82NZ/ZKr0euIg==} + '@img/sharp-linuxmusl-x64@0.35.4': + resolution: {integrity: sha512-f+eZJZIQNEEd26RPSW+76chwOf1XtA2Y/O+5ocVyLliHkeih3e+jhLVBdNTd2rS3IbNXK8+ug93Vf5ZXtF5Lxg==} engines: {node: '>=20.9.0'} cpu: [x64] os: [linux] - '@img/sharp-wasm32@0.35.3': - resolution: {integrity: sha512-cZ0XkcYGpHZkqW6iCkqTcmUC0CD9DhD5d/qeZlZkfRBn6GnHniZXLUo5+9xw8Iv76YE6LQFN9YNBlKREcCG76w==} + '@img/sharp-wasm32@0.35.4': + resolution: {integrity: sha512-zQnl4Kwp7Q6NHsENtU2T/00Zi+w3AQNwz3+UaTyVBy2FpXrzXzGjndpK61onhZjRtRpQXxCTeqw19bVyXOh7jA==} engines: {node: '>=20.9.0'} - '@img/sharp-webcontainers-wasm32@0.35.3': - resolution: {integrity: sha512-2rnq7bX3NzeR2T4YWgz8qiG4h3TSdMe+vN1iQXpJleSJ3SM5zQ8Fy2SyyXAWlbxpEZ2Y+Z4u1BePgJEYbSy80Q==} + '@img/sharp-webcontainers-wasm32@0.35.4': + resolution: {integrity: sha512-ESfNkywmCfPNyaZjxooddJQiQ+l/nTpGEOGthxiLnIHXC/CmcBixnfwUleX9mCz9ovrUUvKMap/pm8RYbzfwaA==} engines: {node: '>=20.9.0'} cpu: [wasm32] - '@img/sharp-win32-arm64@0.35.3': - resolution: {integrity: sha512-4bPwFdMbeC4JQ8L8LOyWp6nsHcboP5fxkp6iPOXz2Vg49R42TuMs2whkJ5OAP4/Ul035qOzy0AecOF9VOscn4w==} + '@img/sharp-win32-arm64@0.35.4': + resolution: {integrity: sha512-iNdlBX9gLVvqe2I3uIJSIKTq6wckP/DYxZtcqxm09x5Gi24DnFBmPAWZmr60ZyYMG0xlzo6goG3670ar+RXvRw==} engines: {node: '>=20.9.0'} cpu: [arm64] os: [win32] - '@img/sharp-win32-ia32@0.35.3': - resolution: {integrity: sha512-r53mXsBN6lFUDiST764SvgwUdHAqM4rPAiDzAmf4fLoB6X/rkfyTrLCg6+g17wJJiCmB3JYgHuUldCWUIRFSXw==} + '@img/sharp-win32-ia32@0.35.4': + resolution: {integrity: sha512-kqRsbaa5CS6KHlpxnN7WhE6vAAugXyZButpRdvDWetlv6Qv4N9WTcrWzF7tXfB9T7MsoadqdI8hmwLq6UlLvtw==} engines: {node: ^20.9.0} cpu: [ia32] os: [win32] - '@img/sharp-win32-x64@0.35.3': - resolution: {integrity: sha512-D4y1vNeZrIIJCN+uHaWVtH86B+aCrdMYYjicy9pXHvbGZeGYLLSd3wdVuC37FxVXlU1ARsk84eKWfWMXGYEqvA==} + '@img/sharp-win32-x64@0.35.4': + resolution: {integrity: sha512-XtmnYhBcrORsJ4XJngyzr/EWP0hRZLAZRFaApdKuviyqF78+ylxh2y06ZmtULAMOnObJ3ucpN0AcwSWnMowTRg==} engines: {node: '>=20.9.0'} cpu: [x64] os: [win32] @@ -1188,9 +1201,9 @@ packages: supports-color: optional: true - decode-uri-component@0.2.2: - resolution: {integrity: sha512-FqUYQ+8o158GyGTrMFJms9qh3CqTKvAqgqsTnkLI8sKu0028orqBhxNMFkFen0zGyg6epACD32pjVk58ngIErQ==} - engines: {node: '>=0.10'} + decode-uri-component@0.5.0: + resolution: {integrity: sha512-1BiQVoK8C9gUbQU6NzAtO/tkz2qOFpEObMWpcFvhx4fYnj4Oc5yzaJN/LD36ihkVUdXyh5ZekzX+yM+ty/SrPg==} + engines: {node: '>=14.16'} decompress-response@6.0.0: resolution: {integrity: sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==} @@ -1692,6 +1705,10 @@ packages: resolution: {integrity: sha512-RvwwcruNjI1ncT5xRakeyS9Lf8lcItv34KD+aif+VH9kduAyfYBipGh12274xtenIPZ119/R9BdTBa8gAwSh0A==} engines: {node: '>=12'} + picomatch@4.0.7: + resolution: {integrity: sha512-qcJu88Q2IWqJsDD529JKMdwGm/dvInW4HvQnRwiH9JtihJvzGOscDtHE3x1pBKeUOTysQ8kVmLnJ2kJu7yhcGA==} + engines: {node: '>=12'} + pirates@4.0.7: resolution: {integrity: sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA==} engines: {node: '>= 6'} @@ -1716,8 +1733,8 @@ packages: pump@3.0.4: resolution: {integrity: sha512-VS7sjc6KR7e1ukRFhQSY5LM2uBWAUPiOPa/A3mkKmiMwSmRFUITt0xuj+/lesgnCv+dPIEYlkzrcyXgquIHMcA==} - qs@6.15.3: - resolution: {integrity: sha512-O9gl3zCl5h5blw1KGUzQKhA5oUXSl8rwUIM5o0S3nCXMliSvy5Dzx7/DJcI+SwgICv+IneSZwhBh1oSyEHA71A==} + qs@6.16.0: + resolution: {integrity: sha512-h6fhOIaRrID2CbEY2fqs+7t+UXZo+MLAnU5gRIq85uFtdiUPCdsApMlHhXogKVM4HM2DVbIjGNTTYH2OcmP1vA==} engines: {node: '>=0.6'} query-string@7.1.3: @@ -1777,8 +1794,8 @@ packages: setprototypeof@1.2.0: resolution: {integrity: sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==} - sharp@0.35.3: - resolution: {integrity: sha512-ej0zVHuZGHCiABXcNxeYhpRnPNPAcvbG8RMdBAhDAxLKkCRVSpK3Iyu7qbqw3JMzoj0REeM6f3tJLtVwl0023Q==} + sharp@0.35.4: + resolution: {integrity: sha512-n++8XWcj+jCOr2IOl7h8LbKnGBDY4aPbmprMONBNFdn0ImXqpGVv5zliDs0V9HbmbCQLpbuo2ej9rAoOQTvMDA==} engines: {node: '>=20.9.0'} peerDependencies: '@types/node': '*' @@ -1859,11 +1876,12 @@ packages: resolution: {integrity: sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==} engines: {node: '>= 0.8'} - stream-chain@2.2.5: - resolution: {integrity: sha512-1TJmBx6aSWqZ4tx7aTpBDXK0/e2hhcNSTV8+CbFJtDjbb+I1mZ8lHit0Grw9GRT+6JbIrrDd8esncgBi8aBXGA==} + stream-chain@4.2.5: + resolution: {integrity: sha512-Wtyq3bNE3ggLR0v2vftqvuhltym3WbZAkZpfIrkr5F/6vpeUmWmwTgXa16zD87gpahwJ/Qulq3zVfUlgIc0J2A==} + engines: {node: '>=22'} - stream-json@1.9.1: - resolution: {integrity: sha512-uWkjJ+2Nt/LO9Z/JyKZbMusL8Dkh97uUBTv3AJQ74y07lVahLY4eEFsPsE97pxYBwr8nnjMAIch5eqI0gPShyw==} + stream-json@3.6.0: + resolution: {integrity: sha512-NiJdqxKyau579z/E8vfqcjWfSDWxW/AT99javFXdPXF147Z5za85LRXSHEmSX9TKOakB7gaIccfD0fOIctb7KQ==} stream-slice@0.1.2: resolution: {integrity: sha512-QzQxpoacatkreL6jsxnVb7X5R/pGw9OUv2qWTYWnmLpg4NdN31snPy/f3TdQE1ZUXaThRvj1Zw4/OGg0ZkaLMA==} @@ -2217,108 +2235,108 @@ snapshots: '@img/colour@1.1.0': {} - '@img/sharp-darwin-arm64@0.35.3': + '@img/sharp-darwin-arm64@0.35.4': optionalDependencies: - '@img/sharp-libvips-darwin-arm64': 1.3.2 + '@img/sharp-libvips-darwin-arm64': 1.3.3 optional: true - '@img/sharp-darwin-x64@0.35.3': + '@img/sharp-darwin-x64@0.35.4': optionalDependencies: - '@img/sharp-libvips-darwin-x64': 1.3.2 + '@img/sharp-libvips-darwin-x64': 1.3.3 optional: true - '@img/sharp-freebsd-wasm32@0.35.3': + '@img/sharp-freebsd-wasm32@0.35.4': dependencies: - '@img/sharp-wasm32': 0.35.3 + '@img/sharp-wasm32': 0.35.4 optional: true - '@img/sharp-libvips-darwin-arm64@1.3.2': + '@img/sharp-libvips-darwin-arm64@1.3.3': optional: true - '@img/sharp-libvips-darwin-x64@1.3.2': + '@img/sharp-libvips-darwin-x64@1.3.3': optional: true - '@img/sharp-libvips-linux-arm64@1.3.2': + '@img/sharp-libvips-linux-arm64@1.3.3': optional: true - '@img/sharp-libvips-linux-arm@1.3.2': + '@img/sharp-libvips-linux-arm@1.3.3': optional: true - '@img/sharp-libvips-linux-ppc64@1.3.2': + '@img/sharp-libvips-linux-ppc64@1.3.3': optional: true - '@img/sharp-libvips-linux-riscv64@1.3.2': + '@img/sharp-libvips-linux-riscv64@1.3.3': optional: true - '@img/sharp-libvips-linux-s390x@1.3.2': + '@img/sharp-libvips-linux-s390x@1.3.3': optional: true - '@img/sharp-libvips-linux-x64@1.3.2': + '@img/sharp-libvips-linux-x64@1.3.3': optional: true - '@img/sharp-libvips-linuxmusl-arm64@1.3.2': + '@img/sharp-libvips-linuxmusl-arm64@1.3.3': optional: true - '@img/sharp-libvips-linuxmusl-x64@1.3.2': + '@img/sharp-libvips-linuxmusl-x64@1.3.3': optional: true - '@img/sharp-linux-arm64@0.35.3': + '@img/sharp-linux-arm64@0.35.4': optionalDependencies: - '@img/sharp-libvips-linux-arm64': 1.3.2 + '@img/sharp-libvips-linux-arm64': 1.3.3 optional: true - '@img/sharp-linux-arm@0.35.3': + '@img/sharp-linux-arm@0.35.4': optionalDependencies: - '@img/sharp-libvips-linux-arm': 1.3.2 + '@img/sharp-libvips-linux-arm': 1.3.3 optional: true - '@img/sharp-linux-ppc64@0.35.3': + '@img/sharp-linux-ppc64@0.35.4': optionalDependencies: - '@img/sharp-libvips-linux-ppc64': 1.3.2 + '@img/sharp-libvips-linux-ppc64': 1.3.3 optional: true - '@img/sharp-linux-riscv64@0.35.3': + '@img/sharp-linux-riscv64@0.35.4': optionalDependencies: - '@img/sharp-libvips-linux-riscv64': 1.3.2 + '@img/sharp-libvips-linux-riscv64': 1.3.3 optional: true - '@img/sharp-linux-s390x@0.35.3': + '@img/sharp-linux-s390x@0.35.4': optionalDependencies: - '@img/sharp-libvips-linux-s390x': 1.3.2 + '@img/sharp-libvips-linux-s390x': 1.3.3 optional: true - '@img/sharp-linux-x64@0.35.3': + '@img/sharp-linux-x64@0.35.4': optionalDependencies: - '@img/sharp-libvips-linux-x64': 1.3.2 + '@img/sharp-libvips-linux-x64': 1.3.3 optional: true - '@img/sharp-linuxmusl-arm64@0.35.3': + '@img/sharp-linuxmusl-arm64@0.35.4': optionalDependencies: - '@img/sharp-libvips-linuxmusl-arm64': 1.3.2 + '@img/sharp-libvips-linuxmusl-arm64': 1.3.3 optional: true - '@img/sharp-linuxmusl-x64@0.35.3': + '@img/sharp-linuxmusl-x64@0.35.4': optionalDependencies: - '@img/sharp-libvips-linuxmusl-x64': 1.3.2 + '@img/sharp-libvips-linuxmusl-x64': 1.3.3 optional: true - '@img/sharp-wasm32@0.35.3': + '@img/sharp-wasm32@0.35.4': dependencies: '@emnapi/runtime': 1.11.3 optional: true - '@img/sharp-webcontainers-wasm32@0.35.3': + '@img/sharp-webcontainers-wasm32@0.35.4': dependencies: - '@img/sharp-wasm32': 0.35.3 + '@img/sharp-wasm32': 0.35.4 optional: true - '@img/sharp-win32-arm64@0.35.3': + '@img/sharp-win32-arm64@0.35.4': optional: true - '@img/sharp-win32-ia32@0.35.3': + '@img/sharp-win32-ia32@0.35.4': optional: true - '@img/sharp-win32-x64@0.35.3': + '@img/sharp-win32-x64@0.35.4': optional: true '@koa/cors@5.0.0': @@ -2929,7 +2947,7 @@ snapshots: better-sqlite3: 11.10.0 debug: 4.4.3(supports-color@5.5.0) mime: 4.1.0 - minio: 8.0.7 + minio: 8.0.7(patch_hash=aqyn4mvirmcqlyuzxvk4llalu4) transitivePeerDependencies: - supports-color @@ -3000,7 +3018,7 @@ snapshots: dependencies: '@hapi/bourne': 3.0.0 inflation: 2.1.0 - qs: 6.15.3 + qs: 6.16.0 raw-body: 2.5.3 type-is: 1.6.18 @@ -3031,7 +3049,7 @@ snapshots: optionalDependencies: supports-color: 5.5.0 - decode-uri-component@0.2.2: {} + decode-uri-component@0.5.0: {} decompress-response@6.0.0: dependencies: @@ -3118,9 +3136,9 @@ snapshots: strnum: 2.4.2 xml-naming: 0.3.0 - fdir@6.5.0(picomatch@4.0.5): + fdir@6.5.0(picomatch@4.0.7): optionalDependencies: - picomatch: 4.0.5 + picomatch: 4.0.7 file-type@22.0.2: dependencies: @@ -3429,7 +3447,7 @@ snapshots: minimist@1.2.8: {} - minio@8.0.7: + minio@8.0.7(patch_hash=aqyn4mvirmcqlyuzxvk4llalu4): dependencies: async: 3.2.6 block-stream2: 2.1.0 @@ -3441,7 +3459,7 @@ snapshots: lodash: 4.18.1 mime-types: 2.1.35 query-string: 7.1.3 - stream-json: 1.9.1 + stream-json: 3.6.0(patch_hash=55awxyx7amzdxcxf7zny3nhfma) through2: 4.0.2 xml2js: 0.6.2 @@ -3553,6 +3571,8 @@ snapshots: picomatch@4.0.5: {} + picomatch@4.0.7: {} + pirates@4.0.7: {} prebuild-install@7.1.3: @@ -3581,14 +3601,14 @@ snapshots: end-of-stream: 1.4.5 once: 1.4.0 - qs@6.15.3: + qs@6.16.0: dependencies: es-define-property: 1.0.1 side-channel: 1.1.1 query-string@7.1.3: dependencies: - decode-uri-component: 0.2.2 + decode-uri-component: 0.5.0 filter-obj: 1.1.0 split-on-first: 1.1.0 strict-uri-encode: 2.0.0 @@ -3646,37 +3666,37 @@ snapshots: setprototypeof@1.2.0: {} - sharp@0.35.3(@types/node@26.2.0): + sharp@0.35.4(@types/node@26.2.0): dependencies: '@img/colour': 1.1.0 detect-libc: 2.1.2 semver: 7.8.5 optionalDependencies: - '@img/sharp-darwin-arm64': 0.35.3 - '@img/sharp-darwin-x64': 0.35.3 - '@img/sharp-freebsd-wasm32': 0.35.3 - '@img/sharp-libvips-darwin-arm64': 1.3.2 - '@img/sharp-libvips-darwin-x64': 1.3.2 - '@img/sharp-libvips-linux-arm': 1.3.2 - '@img/sharp-libvips-linux-arm64': 1.3.2 - '@img/sharp-libvips-linux-ppc64': 1.3.2 - '@img/sharp-libvips-linux-riscv64': 1.3.2 - '@img/sharp-libvips-linux-s390x': 1.3.2 - '@img/sharp-libvips-linux-x64': 1.3.2 - '@img/sharp-libvips-linuxmusl-arm64': 1.3.2 - '@img/sharp-libvips-linuxmusl-x64': 1.3.2 - '@img/sharp-linux-arm': 0.35.3 - '@img/sharp-linux-arm64': 0.35.3 - '@img/sharp-linux-ppc64': 0.35.3 - '@img/sharp-linux-riscv64': 0.35.3 - '@img/sharp-linux-s390x': 0.35.3 - '@img/sharp-linux-x64': 0.35.3 - '@img/sharp-linuxmusl-arm64': 0.35.3 - '@img/sharp-linuxmusl-x64': 0.35.3 - '@img/sharp-webcontainers-wasm32': 0.35.3 - '@img/sharp-win32-arm64': 0.35.3 - '@img/sharp-win32-ia32': 0.35.3 - '@img/sharp-win32-x64': 0.35.3 + '@img/sharp-darwin-arm64': 0.35.4 + '@img/sharp-darwin-x64': 0.35.4 + '@img/sharp-freebsd-wasm32': 0.35.4 + '@img/sharp-libvips-darwin-arm64': 1.3.3 + '@img/sharp-libvips-darwin-x64': 1.3.3 + '@img/sharp-libvips-linux-arm': 1.3.3 + '@img/sharp-libvips-linux-arm64': 1.3.3 + '@img/sharp-libvips-linux-ppc64': 1.3.3 + '@img/sharp-libvips-linux-riscv64': 1.3.3 + '@img/sharp-libvips-linux-s390x': 1.3.3 + '@img/sharp-libvips-linux-x64': 1.3.3 + '@img/sharp-libvips-linuxmusl-arm64': 1.3.3 + '@img/sharp-libvips-linuxmusl-x64': 1.3.3 + '@img/sharp-linux-arm': 0.35.4 + '@img/sharp-linux-arm64': 0.35.4 + '@img/sharp-linux-ppc64': 0.35.4 + '@img/sharp-linux-riscv64': 0.35.4 + '@img/sharp-linux-s390x': 0.35.4 + '@img/sharp-linux-x64': 0.35.4 + '@img/sharp-linuxmusl-arm64': 0.35.4 + '@img/sharp-linuxmusl-x64': 0.35.4 + '@img/sharp-webcontainers-wasm32': 0.35.4 + '@img/sharp-win32-arm64': 0.35.4 + '@img/sharp-win32-ia32': 0.35.4 + '@img/sharp-win32-x64': 0.35.4 '@types/node': 26.2.0 shell-quote@1.10.0: {} @@ -3771,11 +3791,11 @@ snapshots: statuses@2.0.2: {} - stream-chain@2.2.5: {} + stream-chain@4.2.5: {} - stream-json@1.9.1: + stream-json@3.6.0(patch_hash=55awxyx7amzdxcxf7zny3nhfma): dependencies: - stream-chain: 2.2.5 + stream-chain: 4.2.5 stream-slice@0.1.2: {} @@ -3831,8 +3851,8 @@ snapshots: tinyglobby@0.2.17: dependencies: - fdir: 6.5.0(picomatch@4.0.5) - picomatch: 4.0.5 + fdir: 6.5.0(picomatch@4.0.7) + picomatch: 4.0.7 to-regex-range@5.0.1: dependencies: diff --git a/blossom/pnpm-workspace.yaml b/blossom/pnpm-workspace.yaml new file mode 100644 index 0000000..e696ad4 --- /dev/null +++ b/blossom/pnpm-workspace.yaml @@ -0,0 +1,5 @@ +packages: [] +overrides: + qs: ^6.16.0 + decode-uri-component: ^0.5.0 + stream-json: ^3.5.0 diff --git a/blossom/src/helpers/security.test.mjs b/blossom/src/helpers/security.test.mjs new file mode 100644 index 0000000..2cbdf06 --- /dev/null +++ b/blossom/src/helpers/security.test.mjs @@ -0,0 +1,37 @@ +import { test, describe } from "node:test"; +import assert from "node:assert/strict"; +import HttpErrors from "http-errors"; +import { isBlockedAddress, resolvePublicAddresses } from "../../build/helpers/ssrf.js"; +import { getBlobURL } from "../../build/helpers/blob.js"; +import { isHttpError } from "../../build/helpers/error.js"; + +await import("../../build/config.js"); + + describe("SSRF address filtering", () => { + test("blocks private, loopback, link-local, and multicast addresses", () => { + for (const address of ["10.0.0.1", "127.0.0.1", "169.254.1.1", "192.168.1.10", "::1", "fc00::1", "ff02::1"]) { + assert.equal(isBlockedAddress(address), true, address); + } + }); + + test("allows a public literal address and returns its family", async () => { + assert.deepEqual(await resolvePublicAddresses("8.8.8.8"), [{ address: "8.8.8.8", family: 4 }]); + }); + + test("rejects localhost before DNS resolution", async () => { + await assert.rejects(resolvePublicAddresses("localhost"), /SSRF blocked/); + }); +}); + +describe("blob URL and error helpers", () => { + test("builds a URL using the configured public domain", () => { + const url = getBlobURL({ sha256: "a".repeat(64), type: "image/png" }); + assert.match(url, /a{64}\.png$/); + }); + + test("recognizes HTTP errors and rejects plain errors", () => { + assert.equal(isHttpError(new HttpErrors.NotFound()), true); + assert.equal(isHttpError(new Error("plain")), false); + assert.equal(isHttpError(null), false); + }); +}); diff --git a/relay/internal/errors/middleware_test.go b/relay/internal/errors/middleware_test.go new file mode 100644 index 0000000..be31f70 --- /dev/null +++ b/relay/internal/errors/middleware_test.go @@ -0,0 +1,49 @@ +package errors + +import ( + "errors" + "testing" +) + +func TestAppErrorWrapPreservesCauseAndMetadata(t *testing.T) { + cause := errors.New("database unavailable") + err := Wrap(cause, ErrorTypeDatabase, "DB_DOWN", "database read failed").WithRequestID("req-1").WithDetails("retryable") + if err.Error() != "[database:DB_DOWN] database read failed: retryable" { + t.Fatalf("Error() = %q", err.Error()) + } + if !errors.Is(err, cause) || err.Type != ErrorTypeDatabase || err.Code != "DB_DOWN" || err.RequestID != "req-1" || err.Details != "retryable" { + t.Fatalf("wrapped error metadata is incorrect: %#v", err) + } +} + +func TestErrorHelpersSetExpectedTypes(t *testing.T) { + cases := []struct { + name string + err *AppError + kind ErrorType + }{ + {"validation", ValidationError("BAD_INPUT", "invalid event"), ErrorTypeValidation}, + {"not found", NotFoundError("event"), ErrorTypeNotFound}, + {"rate limit", RateLimitError("relay"), ErrorTypeRateLimit}, + {"timeout", TimeoutError("query"), ErrorTypeTimeout}, + } + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + if tc.err.Type != tc.kind || tc.err.Code == "" || tc.err.Message == "" { + t.Fatalf("unexpected helper error: %#v", tc.err) + } + }) + } +} + +func TestIsRecoverable(t *testing.T) { + if !IsRecoverable(TimeoutError("request")) { + t.Fatal("timeout should be recoverable") + } + if IsRecoverable(ValidationError("BAD", "invalid")) { + t.Fatal("validation error should not be recoverable") + } + if IsRecoverable(errors.New("plain error")) { + t.Fatal("plain errors should not be classified as recoverable") + } +} diff --git a/relay/internal/health/health_test.go b/relay/internal/health/health_test.go new file mode 100644 index 0000000..a2050f4 --- /dev/null +++ b/relay/internal/health/health_test.go @@ -0,0 +1,48 @@ +package health + +import ( + "testing" + "time" +) + +func TestDetermineOverallStatus(t *testing.T) { + h := &HealthChecker{} + if got := h.determineOverallStatus([]*ComponentStatus{{Status: StatusHealthy}}); got != StatusHealthy { + t.Fatalf("healthy status = %s", got) + } + if got := h.determineOverallStatus([]*ComponentStatus{{Status: StatusHealthy}, {Status: StatusDegraded}}); got != StatusDegraded { + t.Fatalf("degraded status = %s", got) + } + if got := h.determineOverallStatus([]*ComponentStatus{{Status: StatusDegraded}, {Status: StatusUnhealthy}}); got != StatusUnhealthy { + t.Fatalf("unhealthy status = %s", got) + } +} + +func TestCountComponentsByStatus(t *testing.T) { + h := &HealthChecker{} + components := []*ComponentStatus{{Status: StatusHealthy}, {Status: StatusHealthy}, {Status: StatusDegraded}} + if got := h.countComponentsByStatus(components, StatusHealthy); got != 2 { + t.Fatalf("healthy count = %d, want 2", got) + } + if got := h.countComponentsByStatus(components, StatusUnhealthy); got != 0 { + t.Fatalf("unhealthy count = %d, want 0", got) + } +} + +func TestFormatUptime(t *testing.T) { + h := &HealthChecker{} + cases := []struct { + input time.Duration + want string + }{ + {5 * time.Second, "5s"}, + {2*time.Minute + 3*time.Second, "2m 3s"}, + {time.Hour + 2*time.Minute + 3*time.Second, "1h 2m 3s"}, + {time.Hour*25 + 2*time.Minute + 3*time.Second, "1d 1h 2m 3s"}, + } + for _, tc := range cases { + if got := h.formatUptime(tc.input); got != tc.want { + t.Errorf("formatUptime(%s) = %q, want %q", tc.input, got, tc.want) + } + } +} diff --git a/relay/internal/identity/identity_test.go b/relay/internal/identity/identity_test.go new file mode 100644 index 0000000..05c288b --- /dev/null +++ b/relay/internal/identity/identity_test.go @@ -0,0 +1,51 @@ +package identity + +import ( + "os" + "path/filepath" + "testing" +) + +func TestGenerateRelayIdentity(t *testing.T) { + identity, err := GenerateRelayIdentity() + if err != nil { + t.Fatalf("GenerateRelayIdentity() error = %v", err) + } + if len(identity.PublicKey) != 64 || len(identity.PrivateKey) != 128 { + t.Fatalf("unexpected key lengths: public=%d private=%d", len(identity.PublicKey), len(identity.PrivateKey)) + } + if identity.RelayID != "relay-"+identity.PublicKey[:16] { + t.Fatalf("RelayID = %q, want prefix derived from public key", identity.RelayID) + } +} + +func TestSaveAndLoadRelayIdentity(t *testing.T) { + identity, err := GenerateRelayIdentity() + if err != nil { + t.Fatal(err) + } + path := filepath.Join(t.TempDir(), RelayIDFileName) + if err := saveRelayIdentity(identity, path); err != nil { + t.Fatalf("saveRelayIdentity() error = %v", err) + } + info, err := os.Stat(path) + if err != nil { + t.Fatal(err) + } + if got := info.Mode().Perm(); got != 0o600 { + t.Fatalf("identity file mode = %o, want 600", got) + } + loaded, err := loadRelayIdentity(path) + if err != nil { + t.Fatalf("loadRelayIdentity() error = %v", err) + } + if loaded.PublicKey != identity.PublicKey || loaded.PrivateKey != identity.PrivateKey || loaded.RelayID != identity.RelayID { + t.Fatalf("loaded identity does not match generated identity") + } +} + +func TestLoadRelayIdentityRejectsTraversal(t *testing.T) { + if _, err := loadRelayIdentity(filepath.Join(t.TempDir(), "..", "secret")); err == nil { + t.Fatal("loadRelayIdentity() accepted a traversal path") + } +} diff --git a/relay/internal/limiter/limiter_test.go b/relay/internal/limiter/limiter_test.go new file mode 100644 index 0000000..f512b52 --- /dev/null +++ b/relay/internal/limiter/limiter_test.go @@ -0,0 +1,52 @@ +package limiter + +import ( + "testing" + "time" + + "github.com/Shugur-Network/relay/internal/config" +) + +func testLimit() RateLimit { + return RateLimit{MaxEvents: 2, WindowSize: time.Minute, BurstSize: 0, BanThreshold: 2, BanDuration: time.Hour} +} + +func TestRateLimiterEnforcesLimitAndReset(t *testing.T) { + limiter := NewRateLimiter(&config.Config{}) + limit := testLimit() + limiter.SetLimit("client", limit) + if !limiter.Allow("client", limit) || !limiter.Allow("client", limit) { + t.Fatal("first two events should be allowed") + } + if limiter.Allow("client", limit) { + t.Fatal("event beyond the configured limit was allowed") + } + limiter.Reset("client") + if !limiter.Allow("client", limit) { + t.Fatal("event should be allowed after reset") + } +} + +func TestRateLimiterMapsEmptyKeyToDefaultBucket(t *testing.T) { + limiter := NewRateLimiter(&config.Config{}) + limit := testLimit() + if !limiter.Allow("", limit) { + t.Fatal("first empty-key event should be allowed") + } + if limiter.GetCounter("default") == nil { + t.Fatal("empty key did not use the default bucket") + } +} + +func TestRateLimiterReturnsConfiguredAndFallbackLimits(t *testing.T) { + limiter := NewRateLimiter(&config.Config{}) + configured := RateLimit{MaxEvents: 9, WindowSize: time.Second} + limiter.SetLimit("special", configured) + if got := limiter.GetLimit("special"); got != configured { + t.Fatalf("configured limit = %#v, want %#v", got, configured) + } + fallback := limiter.GetLimit("missing") + if fallback.WindowSize != time.Minute { + t.Fatalf("fallback window = %s, want 1m", fallback.WindowSize) + } +} diff --git a/relay/internal/logger/logger_test.go b/relay/internal/logger/logger_test.go new file mode 100644 index 0000000..61e61ae --- /dev/null +++ b/relay/internal/logger/logger_test.go @@ -0,0 +1,31 @@ +package logger + +import ( + "context" + "testing" +) + +func TestBuildEncoderRejectsUnknownFormat(t *testing.T) { + if _, err := buildEncoder("unknown"); err == nil { + t.Fatal("buildEncoder accepted an unknown format") + } + for _, format := range []string{"json", "console"} { + if encoder, err := buildEncoder(format); err != nil || encoder == nil { + t.Fatalf("buildEncoder(%q) = (%v, %v)", format, encoder, err) + } + } +} + +func TestFromContextReturnsNopWhenInactive(t *testing.T) { + oldActive := active + oldRoot := root + active = false + root = nil + t.Cleanup(func() { + active = oldActive + root = oldRoot + }) + if FromContext(context.Background()) == nil { + t.Fatal("FromContext returned nil logger") + } +} diff --git a/relay/internal/models/models_test.go b/relay/internal/models/models_test.go new file mode 100644 index 0000000..394e41c --- /dev/null +++ b/relay/internal/models/models_test.go @@ -0,0 +1,47 @@ +package models + +import ( + "encoding/json" + "reflect" + "testing" + "time" + + nostr "github.com/nbd-wtf/go-nostr" +) + +func TestRelayInfoJSONRoundTrip(t *testing.T) { + want := RelayInfo{ + Address: "wss://relay.example", + PeerID: "peer-1", + PublicKey: "pubkey", + IsActive: true, + IsSynced: true, + LastSeen: time.Unix(123, 0).UTC(), + } + encoded, err := json.Marshal(want) + if err != nil { + t.Fatal(err) + } + var got RelayInfo + if err := json.Unmarshal(encoded, &got); err != nil { + t.Fatal(err) + } + if !reflect.DeepEqual(got, want) { + t.Fatalf("round trip = %#v, want %#v", got, want) + } +} + +func TestSyncMessageJSONRoundTrip(t *testing.T) { + want := SyncMessage{Cmd: "SyncChunk", Since: 10, Limit: 2, Kinds: []int{1, 7}, Events: []nostr.Event{{Kind: 1, Content: "hello"}}} + encoded, err := json.Marshal(want) + if err != nil { + t.Fatal(err) + } + var got SyncMessage + if err := json.Unmarshal(encoded, &got); err != nil { + t.Fatal(err) + } + if got.Cmd != want.Cmd || got.Since != want.Since || got.Limit != want.Limit || !reflect.DeepEqual(got.Kinds, want.Kinds) || len(got.Events) != 1 || got.Events[0].Content != "hello" { + t.Fatalf("round trip = %#v, want %#v", got, want) + } +} diff --git a/relay/internal/storage/schema.go b/relay/internal/storage/schema.go index b32d99d..0a17da3 100644 --- a/relay/internal/storage/schema.go +++ b/relay/internal/storage/schema.go @@ -7,6 +7,7 @@ import ( "strings" "github.com/Shugur-Network/relay/internal/logger" + "github.com/jackc/pgx/v5" "go.uber.org/zap" ) @@ -34,7 +35,7 @@ func (db *DB) CreateDatabaseIfNotExists(ctx context.Context, dbName string) erro if !exists { // Create database logger.Info("Creating database...", zap.String("database", dbName)) - _, err = db.Pool.Exec(ctx, fmt.Sprintf("CREATE DATABASE %s", dbName)) + _, err = db.Pool.Exec(ctx, "CREATE DATABASE "+pgx.Identifier{dbName}.Sanitize()) if err != nil { return fmt.Errorf("failed to create database %s: %w", dbName, err) } diff --git a/relay/internal/workers/workerpool_test.go b/relay/internal/workers/workerpool_test.go new file mode 100644 index 0000000..510653c --- /dev/null +++ b/relay/internal/workers/workerpool_test.go @@ -0,0 +1,43 @@ +package workers + +import ( + "sync/atomic" + "testing" +) + +func TestWorkerPoolRunsQueuedJobs(t *testing.T) { + pool := NewWorkerPool(2, 4) + var completed atomic.Int32 + for i := 0; i < 4; i++ { + if !pool.AddJob(func() { completed.Add(1) }) { + t.Fatalf("job %d was unexpectedly rejected", i) + } + } + pool.Wait() + pool.Stop() + if got := completed.Load(); got != 4 { + t.Fatalf("completed jobs = %d, want 4", got) + } +} + +func TestWorkerPoolRejectsJobsWhenQueueIsFull(t *testing.T) { + pool := NewWorkerPool(1, 1) + started := make(chan struct{}) + release := make(chan struct{}) + if !pool.AddJob(func() { + close(started) + <-release + }) { + t.Fatal("first job was unexpectedly rejected") + } + <-started + if !pool.AddJob(func() {}) { + t.Fatal("second job was unexpectedly rejected while the worker was active") + } + if pool.AddJob(func() {}) { + t.Fatal("third job was accepted despite a full queue") + } + close(release) + pool.Wait() + pool.Stop() +}