A minimal AT Protocol Personal Data Server written in JavaScript.
atproto pds

feat: add MinIO S3 E2E testing support

- Add MinIO service to docker-compose with health checks
- Node & Deno adapters accept optional `blobs` parameter for custom storage
- Test helper uses `minio` client for S3 blob operations
- Set `BLOB_STORAGE=s3` to enable S3 testing via MinIO
- Remove Deno package tsconfigs (excluded from build at root level)

Usage:
PLATFORM=node npm test # filesystem blobs (default)
PLATFORM=node BLOB_STORAGE=s3 npm test # S3 blobs via MinIO

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

+911 -34
+18
docker-compose.yml
··· 78 78 timeout: 5s 79 79 retries: 10 80 80 81 + minio: 82 + image: minio/minio:latest 83 + ports: 84 + - "9000:9000" 85 + - "9001:9001" 86 + environment: 87 + - MINIO_ROOT_USER=minioadmin 88 + - MINIO_ROOT_PASSWORD=minioadmin 89 + command: server /data --console-address ":9001" 90 + volumes: 91 + - minio_data:/data 92 + healthcheck: 93 + test: ["CMD", "mc", "ready", "local"] 94 + interval: 2s 95 + timeout: 5s 96 + retries: 10 97 + 81 98 volumes: 82 99 plc_data: 83 100 relay_data: 84 101 caddy_data: 102 + minio_data:
+356
docs/plans/2026-01-12-minio-s3-e2e-testing.md
··· 1 + # MinIO S3 E2E Testing Implementation Plan 2 + 3 + > **For Claude:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task. 4 + 5 + **Goal:** Add MinIO to docker-compose and enable E2E testing of the `@pds/blobs-s3` package via `BLOB_STORAGE=s3` environment variable. 6 + 7 + **Architecture:** MinIO runs alongside existing docker services. The `@pds/node` package accepts an optional `blobs` adapter parameter. Test helper configures S3 when `BLOB_STORAGE=s3` is set, otherwise uses filesystem. 8 + 9 + **Tech Stack:** MinIO (S3-compatible storage), @aws-sdk/client-s3, @pds/blobs-s3 10 + 11 + --- 12 + 13 + ### Task 1: Add MinIO to Docker Compose 14 + 15 + **Files:** 16 + - Modify: `docker-compose.yml` 17 + 18 + **Step 1: Add MinIO service** 19 + 20 + Add after the `postgres` service: 21 + 22 + ```yaml 23 + minio: 24 + image: minio/minio:latest 25 + ports: 26 + - "9000:9000" 27 + - "9001:9001" 28 + environment: 29 + - MINIO_ROOT_USER=minioadmin 30 + - MINIO_ROOT_PASSWORD=minioadmin 31 + command: server /data --console-address ":9001" 32 + volumes: 33 + - minio_data:/data 34 + healthcheck: 35 + test: ["CMD", "mc", "ready", "local"] 36 + interval: 2s 37 + timeout: 5s 38 + retries: 10 39 + ``` 40 + 41 + **Step 2: Add minio_data volume** 42 + 43 + Add to `volumes:` section: 44 + 45 + ```yaml 46 + minio_data: 47 + ``` 48 + 49 + **Step 3: Verify MinIO starts** 50 + 51 + Run: `docker compose up minio -d && docker compose logs -f minio` 52 + Expected: "API: http://0.0.0.0:9000" and "Console: http://0.0.0.0:9001" 53 + 54 + **Step 4: Commit** 55 + 56 + ```bash 57 + git add docker-compose.yml 58 + git commit -m "feat: add MinIO to docker-compose for S3 testing" 59 + ``` 60 + 61 + --- 62 + 63 + ### Task 2: Add AWS SDK Dev Dependency 64 + 65 + **Files:** 66 + - Modify: `package.json` 67 + 68 + **Step 1: Install @aws-sdk/client-s3** 69 + 70 + Run: `pnpm add -D @aws-sdk/client-s3` 71 + Expected: Package added to devDependencies 72 + 73 + **Step 2: Commit** 74 + 75 + ```bash 76 + git add package.json pnpm-lock.yaml 77 + git commit -m "chore: add @aws-sdk/client-s3 dev dependency" 78 + ``` 79 + 80 + --- 81 + 82 + ### Task 3: Update @pds/node to Accept Blobs Adapter 83 + 84 + **Files:** 85 + - Modify: `packages/node/index.js` 86 + - Modify: `packages/node/index.d.ts` 87 + 88 + **Step 1: Update createServer to accept blobs parameter** 89 + 90 + In `packages/node/index.js`, change the function signature and body: 91 + 92 + ```javascript 93 + export async function createServer({ 94 + dbPath, 95 + blobsDir, 96 + blobs: blobsArg, 97 + jwtSecret, 98 + port = 3000, 99 + hostname, 100 + appviewUrl, 101 + appviewDid, 102 + relayUrl, 103 + password, 104 + }) { 105 + ``` 106 + 107 + Then change line 106 from: 108 + 109 + ```javascript 110 + const blobs = createFsBlobs(blobsDir); 111 + ``` 112 + 113 + To: 114 + 115 + ```javascript 116 + const blobs = blobsArg ?? createFsBlobs(blobsDir); 117 + ``` 118 + 119 + **Step 2: Update type definitions** 120 + 121 + In `packages/node/index.d.ts`, update ServerOptions interface to add blobs parameter: 122 + 123 + ```typescript 124 + export function createServer(options: { 125 + dbPath: string; 126 + blobsDir?: string; 127 + blobs?: import('@pds/core/ports').BlobPort; 128 + jwtSecret: string; 129 + port?: number; 130 + hostname?: string; 131 + appviewUrl?: string; 132 + appviewDid?: string; 133 + relayUrl?: string; 134 + password?: string; 135 + }): Promise<{ 136 + pds: import('@pds/core').PersonalDataServer; 137 + server: import('node:http').Server; 138 + db: unknown; 139 + listen: () => Promise<import('node:http').Server>; 140 + close: () => Promise<void>; 141 + }>; 142 + ``` 143 + 144 + **Step 3: Verify existing tests still pass** 145 + 146 + Run: `PLATFORM=node npm test -- --run` 147 + Expected: All tests pass (no S3 configured, uses blobsDir fallback) 148 + 149 + **Step 4: Commit** 150 + 151 + ```bash 152 + git add packages/node/index.js packages/node/index.d.ts 153 + git commit -m "feat(node): accept optional blobs adapter parameter" 154 + ``` 155 + 156 + --- 157 + 158 + ### Task 4: Update Docker Services Helper for MinIO 159 + 160 + **Files:** 161 + - Modify: `test/helpers/docker-services.js` 162 + 163 + **Step 1: Add MinIO constants and readiness check** 164 + 165 + Add after line 4 (`const PLC_URL = ...`): 166 + 167 + ```javascript 168 + const MINIO_URL = 'http://localhost:9000'; 169 + const USE_S3 = process.env.BLOB_STORAGE === 's3'; 170 + ``` 171 + 172 + **Step 2: Add MinIO wait in ensureDockerServices** 173 + 174 + Add after `await waitForService(RELAY_URL, 'Relay');` (around line 42): 175 + 176 + ```javascript 177 + if (USE_S3) { 178 + await waitForService(`${MINIO_URL}/minio/health/ready`, 'MinIO'); 179 + } 180 + ``` 181 + 182 + **Step 3: Export USE_S3** 183 + 184 + Update the export at the end of the file: 185 + 186 + ```javascript 187 + export { RELAY_URL, PLC_URL, MINIO_URL, USE_S3 }; 188 + ``` 189 + 190 + **Step 4: Commit** 191 + 192 + ```bash 193 + git add test/helpers/docker-services.js 194 + git commit -m "feat(test): add MinIO readiness check when BLOB_STORAGE=s3" 195 + ``` 196 + 197 + --- 198 + 199 + ### Task 5: Update Node Server Helper for S3 Blobs 200 + 201 + **Files:** 202 + - Modify: `test/helpers/node-server.js` 203 + 204 + **Step 1: Add USE_S3 import and S3 configuration** 205 + 206 + Replace the entire file with: 207 + 208 + ```javascript 209 + // test/helpers/node-server.js 210 + import { mkdirSync, rmSync } from 'node:fs'; 211 + import { createServer } from '@pds/node'; 212 + 213 + const TEST_DATA_DIR = './test-data'; 214 + const TEST_PORT = 3000; 215 + const USE_LOCAL_INFRA = process.env.USE_LOCAL_INFRA !== 'false'; 216 + const USE_S3 = process.env.BLOB_STORAGE === 's3'; 217 + 218 + /** 219 + * Create S3 blobs adapter for MinIO 220 + * @returns {Promise<import('@pds/core/ports').BlobPort>} 221 + */ 222 + async function createMinioBlobs() { 223 + const { 224 + S3Client, 225 + GetObjectCommand, 226 + PutObjectCommand, 227 + DeleteObjectCommand, 228 + CreateBucketCommand, 229 + } = await import('@aws-sdk/client-s3'); 230 + const { createS3Blobs } = await import('@pds/blobs-s3'); 231 + 232 + const client = new S3Client({ 233 + endpoint: 'http://localhost:9000', 234 + region: 'us-east-1', 235 + credentials: { 236 + accessKeyId: 'minioadmin', 237 + secretAccessKey: 'minioadmin', 238 + }, 239 + forcePathStyle: true, 240 + }); 241 + 242 + // Ensure bucket exists 243 + try { 244 + await client.send(new CreateBucketCommand({ Bucket: 'pds-blobs' })); 245 + console.log('Created S3 bucket: pds-blobs'); 246 + } catch (e) { 247 + if (e.name !== 'BucketAlreadyOwnedByYou' && e.name !== 'BucketAlreadyExists') { 248 + throw e; 249 + } 250 + } 251 + 252 + return createS3Blobs({ 253 + client, 254 + bucket: 'pds-blobs', 255 + GetObjectCommand, 256 + PutObjectCommand, 257 + DeleteObjectCommand, 258 + }); 259 + } 260 + 261 + /** 262 + * Start Node.js PDS server for e2e tests 263 + * Cleans test data directory for fresh state each run 264 + * @returns {Promise<{close: () => Promise<void>}>} Server instance with close() method 265 + */ 266 + export async function startNodeServer() { 267 + // Fresh data each run 268 + rmSync(TEST_DATA_DIR, { recursive: true, force: true }); 269 + mkdirSync(TEST_DATA_DIR, { recursive: true }); 270 + 271 + // Configure blobs adapter 272 + const blobs = USE_S3 ? await createMinioBlobs() : undefined; 273 + if (USE_S3) { 274 + console.log('Using S3 blob storage (MinIO)'); 275 + } 276 + 277 + const server = await createServer({ 278 + port: TEST_PORT, 279 + dbPath: `${TEST_DATA_DIR}/pds.db`, 280 + blobsDir: `${TEST_DATA_DIR}/blobs`, 281 + blobs, 282 + jwtSecret: 'test-secret-for-e2e', 283 + // Use HTTPS hostname (via Caddy proxy) when docker infra is enabled 284 + hostname: USE_LOCAL_INFRA 285 + ? 'host.docker.internal:3443' 286 + : `localhost:${TEST_PORT}`, 287 + password: 'test-password', 288 + // Use local relay when docker infrastructure is available 289 + relayUrl: USE_LOCAL_INFRA ? 'http://localhost:2470' : undefined, 290 + // Keep appview pointing to production (for proxy tests) 291 + appviewUrl: 'https://api.bsky.app', 292 + appviewDid: 'did:web:api.bsky.app', 293 + }); 294 + 295 + await server.listen(); 296 + return server; 297 + } 298 + 299 + /** 300 + * Stop Node.js PDS server 301 + * @param {{close: () => Promise<void>}} server - Server instance from startNodeServer 302 + */ 303 + export async function stopNodeServer(server) { 304 + await server.close(); 305 + } 306 + 307 + export { USE_LOCAL_INFRA, TEST_PORT, USE_S3 }; 308 + ``` 309 + 310 + **Step 2: Verify tests pass with filesystem** 311 + 312 + Run: `docker compose up -d && PLATFORM=node USE_LOCAL_INFRA=true npm test -- --run` 313 + Expected: All tests pass using filesystem blobs 314 + 315 + **Step 3: Commit** 316 + 317 + ```bash 318 + git add test/helpers/node-server.js 319 + git commit -m "feat(test): configure S3 blobs when BLOB_STORAGE=s3" 320 + ``` 321 + 322 + --- 323 + 324 + ### Task 6: End-to-End Verification 325 + 326 + **Step 1: Start all docker services** 327 + 328 + Run: `docker compose down -v && docker compose up -d` 329 + Expected: All services start including MinIO 330 + 331 + **Step 2: Run tests with S3 backend** 332 + 333 + Run: `PLATFORM=node USE_LOCAL_INFRA=true BLOB_STORAGE=s3 npm test -- --run` 334 + Expected: All tests pass, console shows "Using S3 blob storage (MinIO)" 335 + 336 + **Step 3: Verify blobs in MinIO console (optional)** 337 + 338 + Open: `http://localhost:9001` (login: minioadmin/minioadmin) 339 + Expected: `pds-blobs` bucket exists with blob files after test run 340 + 341 + **Step 4: Final commit (if any cleanup needed)** 342 + 343 + ```bash 344 + git status 345 + # If clean, no commit needed 346 + ``` 347 + 348 + --- 349 + 350 + ## Summary 351 + 352 + After completing all tasks: 353 + - `docker compose up -d` starts MinIO alongside other services 354 + - `PLATFORM=node npm test` runs with filesystem blobs (default) 355 + - `PLATFORM=node BLOB_STORAGE=s3 npm test` runs with S3 blobs via MinIO 356 + - Existing tests cover blob operations, validating the S3 adapter works E2E
+3 -1
package.json
··· 10 10 "scripts": { 11 11 "dev:node": "npm run dev --workspace=pds-node-example", 12 12 "dev:cloudflare": "npm run dev --workspace=pds-cloudflare-example", 13 - "build": "tsc -p tsconfig.build.json", 13 + "clean": "find packages -name '*.d.ts' -delete", 14 + "build": "npm run clean && tsc -p tsconfig.build.json", 14 15 "test": "vitest run", 15 16 "test:watch": "vitest", 16 17 "test:coverage": "vitest run --coverage", ··· 35 36 "@types/node": "^25.0.6", 36 37 "@types/ws": "^8.18.1", 37 38 "@vitest/coverage-v8": "^4.0.16", 39 + "minio": "^8.0.6", 38 40 "typescript": "^5.9.3", 39 41 "vitest": "^4.0.16", 40 42 "wrangler": "^4.54.0",
-13
packages/blobs-deno/tsconfig.json
··· 1 - { 2 - "extends": "../../tsconfig.json", 3 - "compilerOptions": { 4 - "types": ["@types/deno", "node"], 5 - "baseUrl": ".", 6 - "paths": { 7 - "@pds/core": ["../core/index.js"], 8 - "@pds/core/*": ["../core/*"] 9 - } 10 - }, 11 - "include": ["./**/*.js"], 12 - "exclude": [] 13 - }
+7 -2
packages/deno/index.js
··· 70 70 * Create Deno PDS server 71 71 * @param {Object} options 72 72 * @param {string} options.dbPath - Path to SQLite database 73 - * @param {string} options.blobsDir - Directory for blob storage 73 + * @param {string} [options.blobsDir] - Directory for blob storage (used if blobs not provided) 74 + * @param {import('@pds/core/ports').BlobPort} [options.blobs] - Custom blob storage adapter 74 75 * @param {string} options.jwtSecret - JWT signing secret 75 76 * @param {number} [options.port=3000] - Server port 76 77 * @param {string} [options.hostname] - PDS hostname ··· 83 84 export async function createServer({ 84 85 dbPath, 85 86 blobsDir, 87 + blobs: blobsArg, 86 88 jwtSecret, 87 89 port = 3000, 88 90 hostname, ··· 94 96 const db = new DatabaseSync(dbPath); 95 97 const actorStorage = createActorStorage(db); 96 98 const sharedStorage = createSharedStorage(db); 97 - const blobs = createDenoBlobs(blobsDir); 99 + if (!blobsArg && !blobsDir) { 100 + throw new Error('Either blobs or blobsDir must be provided'); 101 + } 102 + const blobs = blobsArg ?? createDenoBlobs(/** @type {string} */ (blobsDir)); 98 103 const webSocket = createWebSocketPort(); 99 104 100 105 const pds = new PersonalDataServer({
-13
packages/deno/tsconfig.json
··· 1 - { 2 - "extends": "../../tsconfig.json", 3 - "compilerOptions": { 4 - "types": ["@types/deno"], 5 - "baseUrl": ".", 6 - "paths": { 7 - "@pds/core": ["../core/index.js"], 8 - "@pds/core/*": ["../core/*"] 9 - } 10 - }, 11 - "include": ["./**/*.js"], 12 - "exclude": [] 13 - }
+7 -2
packages/node/index.js
··· 71 71 * Create Node.js PDS server 72 72 * @param {Object} options 73 73 * @param {string} options.dbPath - Path to SQLite database 74 - * @param {string} options.blobsDir - Directory for blob storage 74 + * @param {string} [options.blobsDir] - Directory for blob storage (used if blobs not provided) 75 + * @param {import('@pds/core/ports').BlobPort} [options.blobs] - Custom blob storage adapter 75 76 * @param {string} options.jwtSecret - JWT signing secret 76 77 * @param {number} [options.port=3000] - Server port 77 78 * @param {string} [options.hostname] - PDS hostname ··· 84 85 export async function createServer({ 85 86 dbPath, 86 87 blobsDir, 88 + blobs: blobsArg, 87 89 jwtSecret, 88 90 port = 3000, 89 91 hostname, ··· 103 105 // Create shared storage (global data) - can use same db for single-tenant 104 106 const sharedStorage = createSharedStorage(db); 105 107 106 - const blobs = createFsBlobs(blobsDir); 108 + if (!blobsArg && !blobsDir) { 109 + throw new Error('Either blobs or blobsDir must be provided'); 110 + } 111 + const blobs = blobsArg ?? createFsBlobs(/** @type {string} */ (blobsDir)); 107 112 108 113 // WebSocket infrastructure 109 114 const upgradeMap = new WeakMap();
+443
pnpm-lock.yaml
··· 36 36 '@vitest/coverage-v8': 37 37 specifier: ^4.0.16 38 38 version: 4.0.16(vitest@4.0.16(@types/node@25.0.6)) 39 + minio: 40 + specifier: ^8.0.6 41 + version: 8.0.6 39 42 typescript: 40 43 specifier: ^5.9.3 41 44 version: 5.9.3 ··· 888 891 '@vitest/utils@4.0.16': 889 892 resolution: {integrity: sha512-h8z9yYhV3e1LEfaQ3zdypIrnAg/9hguReGZoS7Gl0aBG5xgA410zBqECqmaF/+RkTggRsfnzc1XaAHA6bmUufA==} 890 893 894 + '@zxing/text-encoding@0.9.0': 895 + resolution: {integrity: sha512-U/4aVJ2mxI0aDNI8Uq0wEhMgY+u4CNtEb0om3+y3+niDAsoTCOB33UF0sxpzqzdqXLqmvc+vZyAt4O8pPdfkwA==} 896 + 891 897 acorn-walk@8.3.2: 892 898 resolution: {integrity: sha512-cjkyv4OtNCIeqhHrfS81QWXoCBPExR/J62oyEqepVw8WaQeSqpW2uhuLPh1m9eWhDuOo/jUXVTlifvesOWp/4A==} 893 899 engines: {node: '>=0.4.0'} ··· 904 910 ast-v8-to-istanbul@0.3.10: 905 911 resolution: {integrity: sha512-p4K7vMz2ZSk3wN8l5o3y2bJAoZXT3VuJI5OLTATY/01CYWumWvwkUw0SqDBnNq6IiTO3qDa1eSQDibAV8g7XOQ==} 906 912 913 + async@3.2.6: 914 + resolution: {integrity: sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==} 915 + 916 + available-typed-arrays@1.0.7: 917 + resolution: {integrity: sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==} 918 + engines: {node: '>= 0.4'} 919 + 907 920 base64-js@1.5.1: 908 921 resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==} 909 922 ··· 920 933 blake3-wasm@2.1.5: 921 934 resolution: {integrity: sha512-F1+K8EbfOZE49dtoPtmxUQrpXaBIl3ICvasLh+nJta0xkz+9kF/7uet9fLnwKqhDrmj6g+6K3Tw9yQPUg2ka5g==} 922 935 936 + block-stream2@2.1.0: 937 + resolution: {integrity: sha512-suhjmLI57Ewpmq00qaygS8UgEq2ly2PCItenIyhMqVjo4t4pGzqMvfgJuX8iWTeSDdfSSqS6j38fL4ToNL7Pfg==} 938 + 939 + browser-or-node@2.1.1: 940 + resolution: {integrity: sha512-8CVjaLJGuSKMVTxJ2DpBl5XnlNDiT4cQFeuCJJrvJmts9YrTZDizTX7PjC2s6W4x+MBGZeEY6dGMrF04/6Hgqg==} 941 + 942 + buffer-crc32@1.0.0: 943 + resolution: {integrity: sha512-Db1SbgBS/fg/392AblrMJk97KggmvYhr4pB5ZIMTWtaivCPMWLkmb7m21cJvpvgK+J3nsU2CmmixNBZx4vFj/w==} 944 + engines: {node: '>=8.0.0'} 945 + 923 946 buffer@5.7.1: 924 947 resolution: {integrity: sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==} 925 948 949 + call-bind-apply-helpers@1.0.2: 950 + resolution: {integrity: sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==} 951 + engines: {node: '>= 0.4'} 952 + 953 + call-bind@1.0.8: 954 + resolution: {integrity: sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww==} 955 + engines: {node: '>= 0.4'} 956 + 957 + call-bound@1.0.4: 958 + resolution: {integrity: sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==} 959 + engines: {node: '>= 0.4'} 960 + 926 961 chai@6.2.2: 927 962 resolution: {integrity: sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg==} 928 963 engines: {node: '>=18'} ··· 957 992 supports-color: 958 993 optional: true 959 994 995 + decode-uri-component@0.2.2: 996 + resolution: {integrity: sha512-FqUYQ+8o158GyGTrMFJms9qh3CqTKvAqgqsTnkLI8sKu0028orqBhxNMFkFen0zGyg6epACD32pjVk58ngIErQ==} 997 + engines: {node: '>=0.10'} 998 + 960 999 decompress-response@6.0.0: 961 1000 resolution: {integrity: sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==} 962 1001 engines: {node: '>=10'} ··· 965 1004 resolution: {integrity: sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==} 966 1005 engines: {node: '>=4.0.0'} 967 1006 1007 + define-data-property@1.1.4: 1008 + resolution: {integrity: sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==} 1009 + engines: {node: '>= 0.4'} 1010 + 968 1011 detect-libc@2.1.2: 969 1012 resolution: {integrity: sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==} 970 1013 engines: {node: '>=8'} 971 1014 1015 + dunder-proto@1.0.1: 1016 + resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==} 1017 + engines: {node: '>= 0.4'} 1018 + 972 1019 end-of-stream@1.4.5: 973 1020 resolution: {integrity: sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==} 974 1021 975 1022 error-stack-parser-es@1.0.5: 976 1023 resolution: {integrity: sha512-5qucVt2XcuGMcEGgWI7i+yZpmpByQ8J1lHhcL7PwqCwu9FPP3VUXzT4ltHe5i2z9dePwEHcDVOAfSnHsOlCXRA==} 977 1024 1025 + es-define-property@1.0.1: 1026 + resolution: {integrity: sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==} 1027 + engines: {node: '>= 0.4'} 1028 + 1029 + es-errors@1.3.0: 1030 + resolution: {integrity: sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==} 1031 + engines: {node: '>= 0.4'} 1032 + 978 1033 es-module-lexer@1.7.0: 979 1034 resolution: {integrity: sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==} 980 1035 1036 + es-object-atoms@1.1.1: 1037 + resolution: {integrity: sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==} 1038 + engines: {node: '>= 0.4'} 1039 + 981 1040 esbuild@0.27.0: 982 1041 resolution: {integrity: sha512-jd0f4NHbD6cALCyGElNpGAOtWxSq46l9X/sWB0Nzd5er4Kz2YTm+Vl0qKFT9KUJvD8+fiO8AvoHhFvEatfVixA==} 983 1042 engines: {node: '>=18'} ··· 991 1050 estree-walker@3.0.3: 992 1051 resolution: {integrity: sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==} 993 1052 1053 + eventemitter3@5.0.1: 1054 + resolution: {integrity: sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA==} 1055 + 994 1056 exit-hook@2.2.1: 995 1057 resolution: {integrity: sha512-eNTPlAD67BmP31LDINZ3U7HSF8l57TxOY2PmBJ1shpCvpnxBF93mWCE8YHBnXs8qiUZJc9WDcWIeC3a2HIAMfw==} 996 1058 engines: {node: '>=6'} ··· 1003 1065 resolution: {integrity: sha512-knvyeauYhqjOYvQ66MznSMs83wmHrCycNEN6Ao+2AeYEfxUIkuiVxdEa1qlGEPK+We3n0THiDciYSsCcgW/DoA==} 1004 1066 engines: {node: '>=12.0.0'} 1005 1067 1068 + fast-xml-parser@4.5.3: 1069 + resolution: {integrity: sha512-RKihhV+SHsIUGXObeVy9AXiBbFwkVk7Syp8XgwN5U3JV416+Gwp/GO9i0JYKmikykgz/UHRrrV4ROuZEo/T0ig==} 1070 + hasBin: true 1071 + 1006 1072 fdir@6.5.0: 1007 1073 resolution: {integrity: sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==} 1008 1074 engines: {node: '>=12.0.0'} ··· 1015 1081 file-uri-to-path@1.0.0: 1016 1082 resolution: {integrity: sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==} 1017 1083 1084 + filter-obj@1.1.0: 1085 + resolution: {integrity: sha512-8rXg1ZnX7xzy2NGDVkBVaAy+lSlPNwad13BtgSlLuxfIslyt5Vg64U7tFcCt4WS1R0hvtnQybT/IyCkGZ3DpXQ==} 1086 + engines: {node: '>=0.10.0'} 1087 + 1088 + for-each@0.3.5: 1089 + resolution: {integrity: sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg==} 1090 + engines: {node: '>= 0.4'} 1091 + 1018 1092 fs-constants@1.0.0: 1019 1093 resolution: {integrity: sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==} 1020 1094 ··· 1023 1097 engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} 1024 1098 os: [darwin] 1025 1099 1100 + function-bind@1.1.2: 1101 + resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==} 1102 + 1103 + generator-function@2.0.1: 1104 + resolution: {integrity: sha512-SFdFmIJi+ybC0vjlHN0ZGVGHc3lgE0DxPAT0djjVg+kjOnSqclqmj0KQ7ykTOLP6YxoqOvuAODGdcHJn+43q3g==} 1105 + engines: {node: '>= 0.4'} 1106 + 1107 + get-intrinsic@1.3.0: 1108 + resolution: {integrity: sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==} 1109 + engines: {node: '>= 0.4'} 1110 + 1111 + get-proto@1.0.1: 1112 + resolution: {integrity: sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==} 1113 + engines: {node: '>= 0.4'} 1114 + 1026 1115 github-from-package@0.0.0: 1027 1116 resolution: {integrity: sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw==} 1028 1117 1029 1118 glob-to-regexp@0.4.1: 1030 1119 resolution: {integrity: sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==} 1031 1120 1121 + gopd@1.2.0: 1122 + resolution: {integrity: sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==} 1123 + engines: {node: '>= 0.4'} 1124 + 1032 1125 has-flag@4.0.0: 1033 1126 resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} 1034 1127 engines: {node: '>=8'} 1035 1128 1129 + has-property-descriptors@1.0.2: 1130 + resolution: {integrity: sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==} 1131 + 1132 + has-symbols@1.1.0: 1133 + resolution: {integrity: sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==} 1134 + engines: {node: '>= 0.4'} 1135 + 1136 + has-tostringtag@1.0.2: 1137 + resolution: {integrity: sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==} 1138 + engines: {node: '>= 0.4'} 1139 + 1140 + hasown@2.0.2: 1141 + resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==} 1142 + engines: {node: '>= 0.4'} 1143 + 1036 1144 html-escaper@2.0.2: 1037 1145 resolution: {integrity: sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==} 1038 1146 ··· 1045 1153 ini@1.3.8: 1046 1154 resolution: {integrity: sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==} 1047 1155 1156 + ipaddr.js@2.3.0: 1157 + resolution: {integrity: sha512-Zv/pA+ciVFbCSBBjGfaKUya/CcGmUHzTydLMaTwrUUEM2DIEO3iZvueGxmacvmN50fGpGVKeTXpb2LcYQxeVdg==} 1158 + engines: {node: '>= 10'} 1159 + 1160 + is-arguments@1.2.0: 1161 + resolution: {integrity: sha512-7bVbi0huj/wrIAOzb8U1aszg9kdi3KN/CyU19CTI7tAoZYEZoL9yCDXpbXN+uPsuWnP02cyug1gleqq+TU+YCA==} 1162 + engines: {node: '>= 0.4'} 1163 + 1048 1164 is-arrayish@0.3.4: 1049 1165 resolution: {integrity: sha512-m6UrgzFVUYawGBh1dUsWR5M2Clqic9RVXC/9f8ceNlv2IcO9j9J/z8UoCLPqtsPBFNzEpfR3xftohbfqDx8EQA==} 1050 1166 1167 + is-callable@1.2.7: 1168 + resolution: {integrity: sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==} 1169 + engines: {node: '>= 0.4'} 1170 + 1171 + is-generator-function@1.1.2: 1172 + resolution: {integrity: sha512-upqt1SkGkODW9tsGNG5mtXTXtECizwtS2kA161M+gJPc1xdb/Ax629af6YrTwcOeQHbewrPNlE5Dx7kzvXTizA==} 1173 + engines: {node: '>= 0.4'} 1174 + 1175 + is-regex@1.2.1: 1176 + resolution: {integrity: sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g==} 1177 + engines: {node: '>= 0.4'} 1178 + 1179 + is-typed-array@1.1.15: 1180 + resolution: {integrity: sha512-p3EcsicXjit7SaskXHs1hA91QxgTw46Fv6EFKKGS5DRFLD8yKnohjF3hxoju94b/OcMZoQukzpPpBE9uLVKzgQ==} 1181 + engines: {node: '>= 0.4'} 1182 + 1051 1183 istanbul-lib-coverage@3.2.2: 1052 1184 resolution: {integrity: sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==} 1053 1185 engines: {node: '>=8'} ··· 1071 1203 resolution: {integrity: sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ==} 1072 1204 engines: {node: '>=6'} 1073 1205 1206 + lodash@4.17.21: 1207 + resolution: {integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==} 1208 + 1074 1209 magic-string@0.30.21: 1075 1210 resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==} 1076 1211 ··· 1081 1216 resolution: {integrity: sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==} 1082 1217 engines: {node: '>=10'} 1083 1218 1219 + math-intrinsics@1.1.0: 1220 + resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==} 1221 + engines: {node: '>= 0.4'} 1222 + 1223 + mime-db@1.52.0: 1224 + resolution: {integrity: sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==} 1225 + engines: {node: '>= 0.6'} 1226 + 1227 + mime-types@2.1.35: 1228 + resolution: {integrity: sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==} 1229 + engines: {node: '>= 0.6'} 1230 + 1084 1231 mime@3.0.0: 1085 1232 resolution: {integrity: sha512-jSCU7/VB1loIWBZe14aEYHU/+1UMEHoaO7qxCOVJOw9GgH72VAWppxNcjU+x9a2k3GSIBXNKxXQFqRvvZ7vr3A==} 1086 1233 engines: {node: '>=10.0.0'} ··· 1098 1245 minimist@1.2.8: 1099 1246 resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==} 1100 1247 1248 + minio@8.0.6: 1249 + resolution: {integrity: sha512-sOeh2/b/XprRmEtYsnNRFtOqNRTPDvYtMWh+spWlfsuCV/+IdxNeKVUMKLqI7b5Dr07ZqCPuaRGU/rB9pZYVdQ==} 1250 + engines: {node: ^16 || ^18 || >=20} 1251 + 1101 1252 mkdirp-classic@0.5.3: 1102 1253 resolution: {integrity: sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==} 1103 1254 ··· 1135 1286 resolution: {integrity: sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==} 1136 1287 engines: {node: '>=12'} 1137 1288 1289 + possible-typed-array-names@1.1.0: 1290 + resolution: {integrity: sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg==} 1291 + engines: {node: '>= 0.4'} 1292 + 1138 1293 postcss@8.5.6: 1139 1294 resolution: {integrity: sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==} 1140 1295 engines: {node: ^10 || ^12 || >=14} ··· 1146 1301 1147 1302 pump@3.0.3: 1148 1303 resolution: {integrity: sha512-todwxLMY7/heScKmntwQG8CXVkWUOdYxIvY2s0VWAAMh/nd8SoYiRaKjlr7+iCs984f2P8zvrfWcDDYVb73NfA==} 1304 + 1305 + query-string@7.1.3: 1306 + resolution: {integrity: sha512-hh2WYhq4fi8+b+/2Kg9CEge4fDPvHS534aOOvOZeQ3+Vf2mCFsaFBYj0i+iXcAq6I9Vzp5fjMFBlONvayDC1qg==} 1307 + engines: {node: '>=6'} 1149 1308 1150 1309 rc@1.2.8: 1151 1310 resolution: {integrity: sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==} ··· 1163 1322 safe-buffer@5.2.1: 1164 1323 resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==} 1165 1324 1325 + safe-regex-test@1.1.0: 1326 + resolution: {integrity: sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw==} 1327 + engines: {node: '>= 0.4'} 1328 + 1329 + sax@1.4.4: 1330 + resolution: {integrity: sha512-1n3r/tGXO6b6VXMdFT54SHzT9ytu9yr7TaELowdYpMqY/Ao7EnlQGmAQ1+RatX7Tkkdm6hONI2owqNx2aZj5Sw==} 1331 + engines: {node: '>=11.0.0'} 1332 + 1166 1333 semver@7.7.3: 1167 1334 resolution: {integrity: sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==} 1168 1335 engines: {node: '>=10'} 1169 1336 hasBin: true 1337 + 1338 + set-function-length@1.2.2: 1339 + resolution: {integrity: sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==} 1340 + engines: {node: '>= 0.4'} 1170 1341 1171 1342 sharp@0.33.5: 1172 1343 resolution: {integrity: sha512-haPVm1EkS9pgvHrQ/F3Xy+hgcuMV0Wm9vfIBSiwZ05k+xgb0PkBQpGsAA/oWdDobNaZTH5ppvHtzCFbnSEwHVw==} ··· 1188 1359 resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} 1189 1360 engines: {node: '>=0.10.0'} 1190 1361 1362 + split-on-first@1.1.0: 1363 + resolution: {integrity: sha512-43ZssAJaMusuKWL8sKUBQXHWOpq8d6CfN/u1p4gUzfJkM05C8rxTmYrkIPTXapZpORA6LkkzcUulJ8FqA7Uudw==} 1364 + engines: {node: '>=6'} 1365 + 1191 1366 stackback@0.0.2: 1192 1367 resolution: {integrity: sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==} 1193 1368 ··· 1197 1372 stoppable@1.1.0: 1198 1373 resolution: {integrity: sha512-KXDYZ9dszj6bzvnEMRYvxgeTHU74QBFL54XKtP3nyMuJ81CFYtABZ3bAzL2EdFUaEwJOBOgENyFj3R7oTzDyyw==} 1199 1374 engines: {node: '>=4', npm: '>=6'} 1375 + 1376 + stream-chain@2.2.5: 1377 + resolution: {integrity: sha512-1TJmBx6aSWqZ4tx7aTpBDXK0/e2hhcNSTV8+CbFJtDjbb+I1mZ8lHit0Grw9GRT+6JbIrrDd8esncgBi8aBXGA==} 1378 + 1379 + stream-json@1.9.1: 1380 + resolution: {integrity: sha512-uWkjJ+2Nt/LO9Z/JyKZbMusL8Dkh97uUBTv3AJQ74y07lVahLY4eEFsPsE97pxYBwr8nnjMAIch5eqI0gPShyw==} 1381 + 1382 + strict-uri-encode@2.0.0: 1383 + resolution: {integrity: sha512-QwiXZgpRcKkhTj2Scnn++4PKtWsH0kpzZ62L2R6c/LUVYv7hVnZqcg2+sMuT6R7Jusu1vviK/MFsu6kNJfWlEQ==} 1384 + engines: {node: '>=4'} 1200 1385 1201 1386 string_decoder@1.3.0: 1202 1387 resolution: {integrity: sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==} ··· 1205 1390 resolution: {integrity: sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==} 1206 1391 engines: {node: '>=0.10.0'} 1207 1392 1393 + strnum@1.1.2: 1394 + resolution: {integrity: sha512-vrN+B7DBIoTTZjnPNewwhx6cBA/H+IS7rfW68n7XxC1y7uoiGQBxaKzqucGUgavX15dJgiGztLJ8vxuEzwqBdA==} 1395 + 1208 1396 supports-color@10.2.2: 1209 1397 resolution: {integrity: sha512-SS+jx45GF1QjgEXQx4NJZV9ImqmO2NPz5FNsIHrsDjh2YsHnawpan7SNQ1o8NuhrbHZy9AZhIoCUiCeaW/C80g==} 1210 1398 engines: {node: '>=18'} ··· 1219 1407 tar-stream@2.2.0: 1220 1408 resolution: {integrity: sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==} 1221 1409 engines: {node: '>=6'} 1410 + 1411 + through2@4.0.2: 1412 + resolution: {integrity: sha512-iOqSav00cVxEEICeD7TjLB1sueEL+81Wpzp2bY17uZjZN0pWZPuo4suZ/61VujxmqSGFfgOcNuTZ85QJwNZQpw==} 1222 1413 1223 1414 tinybench@2.9.0: 1224 1415 resolution: {integrity: sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==} ··· 1258 1449 1259 1450 util-deprecate@1.0.2: 1260 1451 resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} 1452 + 1453 + util@0.12.5: 1454 + resolution: {integrity: sha512-kZf/K6hEIrWHI6XqOFUiiMa+79wE/D8Q+NCNAWclkyg3b4d2k7s0QGepNjiABc+aR3N1PAyHL7p6UcLY6LmrnA==} 1261 1455 1262 1456 vite@7.3.1: 1263 1457 resolution: {integrity: sha512-w+N7Hifpc3gRjZ63vYBXA56dvvRlNWRczTdmCBBa+CotUzAPf5b7YMdMR/8CQoeYE5LX3W4wj6RYTgonm1b9DA==} ··· 1333 1527 jsdom: 1334 1528 optional: true 1335 1529 1530 + web-encoding@1.1.5: 1531 + resolution: {integrity: sha512-HYLeVCdJ0+lBYV2FvNZmv3HJ2Nt0QYXqZojk3d9FJOLkwnuhzM9tmamh8d7HPM8QqjKH8DeHkFTx+CFlWpZZDA==} 1532 + 1533 + which-typed-array@1.1.19: 1534 + resolution: {integrity: sha512-rEvr90Bck4WZt9HHFC4DJMsjvu7x+r6bImz0/BrbWb7A2djJ8hnZMrWnHo9F8ssv0OMErasDhftrfROTyqSDrw==} 1535 + engines: {node: '>= 0.4'} 1536 + 1336 1537 why-is-node-running@2.3.0: 1337 1538 resolution: {integrity: sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==} 1338 1539 engines: {node: '>=8'} ··· 1379 1580 optional: true 1380 1581 utf-8-validate: 1381 1582 optional: true 1583 + 1584 + xml2js@0.6.2: 1585 + resolution: {integrity: sha512-T4rieHaC1EXcES0Kxxj4JWgaUQHDk+qwHcYOCFHfiwKz7tOVPLq7Hjq9dM1WCMhylqMEfP7hMcOIChvotiZegA==} 1586 + engines: {node: '>=4.0.0'} 1587 + 1588 + xmlbuilder@11.0.1: 1589 + resolution: {integrity: sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA==} 1590 + engines: {node: '>=4.0'} 1382 1591 1383 1592 youch-core@0.3.3: 1384 1593 resolution: {integrity: sha512-ho7XuGjLaJ2hWHoK8yFnsUGy2Y5uDpqSTq1FkHLK4/oqKtyUU1AFbOOxY4IpC9f0fTLjwYbslUz0Po5BpD1wrA==} ··· 1894 2103 '@vitest/pretty-format': 4.0.16 1895 2104 tinyrainbow: 3.0.3 1896 2105 2106 + '@zxing/text-encoding@0.9.0': 2107 + optional: true 2108 + 1897 2109 acorn-walk@8.3.2: {} 1898 2110 1899 2111 acorn@8.14.0: {} ··· 1906 2118 estree-walker: 3.0.3 1907 2119 js-tokens: 9.0.1 1908 2120 2121 + async@3.2.6: {} 2122 + 2123 + available-typed-arrays@1.0.7: 2124 + dependencies: 2125 + possible-typed-array-names: 1.1.0 2126 + 1909 2127 base64-js@1.5.1: {} 1910 2128 1911 2129 better-sqlite3@12.6.0: ··· 1925 2143 1926 2144 blake3-wasm@2.1.5: {} 1927 2145 2146 + block-stream2@2.1.0: 2147 + dependencies: 2148 + readable-stream: 3.6.2 2149 + 2150 + browser-or-node@2.1.1: {} 2151 + 2152 + buffer-crc32@1.0.0: {} 2153 + 1928 2154 buffer@5.7.1: 1929 2155 dependencies: 1930 2156 base64-js: 1.5.1 1931 2157 ieee754: 1.2.1 1932 2158 2159 + call-bind-apply-helpers@1.0.2: 2160 + dependencies: 2161 + es-errors: 1.3.0 2162 + function-bind: 1.1.2 2163 + 2164 + call-bind@1.0.8: 2165 + dependencies: 2166 + call-bind-apply-helpers: 1.0.2 2167 + es-define-property: 1.0.1 2168 + get-intrinsic: 1.3.0 2169 + set-function-length: 1.2.2 2170 + 2171 + call-bound@1.0.4: 2172 + dependencies: 2173 + call-bind-apply-helpers: 1.0.2 2174 + get-intrinsic: 1.3.0 2175 + 1933 2176 chai@6.2.2: {} 1934 2177 1935 2178 chownr@1.1.4: {} ··· 1956 2199 dependencies: 1957 2200 ms: 2.1.3 1958 2201 2202 + decode-uri-component@0.2.2: {} 2203 + 1959 2204 decompress-response@6.0.0: 1960 2205 dependencies: 1961 2206 mimic-response: 3.1.0 1962 2207 1963 2208 deep-extend@0.6.0: {} 1964 2209 2210 + define-data-property@1.1.4: 2211 + dependencies: 2212 + es-define-property: 1.0.1 2213 + es-errors: 1.3.0 2214 + gopd: 1.2.0 2215 + 1965 2216 detect-libc@2.1.2: {} 1966 2217 2218 + dunder-proto@1.0.1: 2219 + dependencies: 2220 + call-bind-apply-helpers: 1.0.2 2221 + es-errors: 1.3.0 2222 + gopd: 1.2.0 2223 + 1967 2224 end-of-stream@1.4.5: 1968 2225 dependencies: 1969 2226 once: 1.4.0 1970 2227 1971 2228 error-stack-parser-es@1.0.5: {} 1972 2229 2230 + es-define-property@1.0.1: {} 2231 + 2232 + es-errors@1.3.0: {} 2233 + 1973 2234 es-module-lexer@1.7.0: {} 2235 + 2236 + es-object-atoms@1.1.1: 2237 + dependencies: 2238 + es-errors: 1.3.0 1974 2239 1975 2240 esbuild@0.27.0: 1976 2241 optionalDependencies: ··· 2034 2299 dependencies: 2035 2300 '@types/estree': 1.0.8 2036 2301 2302 + eventemitter3@5.0.1: {} 2303 + 2037 2304 exit-hook@2.2.1: {} 2038 2305 2039 2306 expand-template@2.0.3: {} 2040 2307 2041 2308 expect-type@1.3.0: {} 2042 2309 2310 + fast-xml-parser@4.5.3: 2311 + dependencies: 2312 + strnum: 1.1.2 2313 + 2043 2314 fdir@6.5.0(picomatch@4.0.3): 2044 2315 optionalDependencies: 2045 2316 picomatch: 4.0.3 2046 2317 2047 2318 file-uri-to-path@1.0.0: {} 2048 2319 2320 + filter-obj@1.1.0: {} 2321 + 2322 + for-each@0.3.5: 2323 + dependencies: 2324 + is-callable: 1.2.7 2325 + 2049 2326 fs-constants@1.0.0: {} 2050 2327 2051 2328 fsevents@2.3.3: 2052 2329 optional: true 2053 2330 2331 + function-bind@1.1.2: {} 2332 + 2333 + generator-function@2.0.1: {} 2334 + 2335 + get-intrinsic@1.3.0: 2336 + dependencies: 2337 + call-bind-apply-helpers: 1.0.2 2338 + es-define-property: 1.0.1 2339 + es-errors: 1.3.0 2340 + es-object-atoms: 1.1.1 2341 + function-bind: 1.1.2 2342 + get-proto: 1.0.1 2343 + gopd: 1.2.0 2344 + has-symbols: 1.1.0 2345 + hasown: 2.0.2 2346 + math-intrinsics: 1.1.0 2347 + 2348 + get-proto@1.0.1: 2349 + dependencies: 2350 + dunder-proto: 1.0.1 2351 + es-object-atoms: 1.1.1 2352 + 2054 2353 github-from-package@0.0.0: {} 2055 2354 2056 2355 glob-to-regexp@0.4.1: {} 2057 2356 2357 + gopd@1.2.0: {} 2358 + 2058 2359 has-flag@4.0.0: {} 2059 2360 2361 + has-property-descriptors@1.0.2: 2362 + dependencies: 2363 + es-define-property: 1.0.1 2364 + 2365 + has-symbols@1.1.0: {} 2366 + 2367 + has-tostringtag@1.0.2: 2368 + dependencies: 2369 + has-symbols: 1.1.0 2370 + 2371 + hasown@2.0.2: 2372 + dependencies: 2373 + function-bind: 1.1.2 2374 + 2060 2375 html-escaper@2.0.2: {} 2061 2376 2062 2377 ieee754@1.2.1: {} ··· 2065 2380 2066 2381 ini@1.3.8: {} 2067 2382 2383 + ipaddr.js@2.3.0: {} 2384 + 2385 + is-arguments@1.2.0: 2386 + dependencies: 2387 + call-bound: 1.0.4 2388 + has-tostringtag: 1.0.2 2389 + 2068 2390 is-arrayish@0.3.4: {} 2069 2391 2392 + is-callable@1.2.7: {} 2393 + 2394 + is-generator-function@1.1.2: 2395 + dependencies: 2396 + call-bound: 1.0.4 2397 + generator-function: 2.0.1 2398 + get-proto: 1.0.1 2399 + has-tostringtag: 1.0.2 2400 + safe-regex-test: 1.1.0 2401 + 2402 + is-regex@1.2.1: 2403 + dependencies: 2404 + call-bound: 1.0.4 2405 + gopd: 1.2.0 2406 + has-tostringtag: 1.0.2 2407 + hasown: 2.0.2 2408 + 2409 + is-typed-array@1.1.15: 2410 + dependencies: 2411 + which-typed-array: 1.1.19 2412 + 2070 2413 istanbul-lib-coverage@3.2.2: {} 2071 2414 2072 2415 istanbul-lib-report@3.0.1: ··· 2092 2435 2093 2436 kleur@4.1.5: {} 2094 2437 2438 + lodash@4.17.21: {} 2439 + 2095 2440 magic-string@0.30.21: 2096 2441 dependencies: 2097 2442 '@jridgewell/sourcemap-codec': 1.5.5 ··· 2106 2451 dependencies: 2107 2452 semver: 7.7.3 2108 2453 2454 + math-intrinsics@1.1.0: {} 2455 + 2456 + mime-db@1.52.0: {} 2457 + 2458 + mime-types@2.1.35: 2459 + dependencies: 2460 + mime-db: 1.52.0 2461 + 2109 2462 mime@3.0.0: {} 2110 2463 2111 2464 mimic-response@3.1.0: {} ··· 2130 2483 2131 2484 minimist@1.2.8: {} 2132 2485 2486 + minio@8.0.6: 2487 + dependencies: 2488 + async: 3.2.6 2489 + block-stream2: 2.1.0 2490 + browser-or-node: 2.1.1 2491 + buffer-crc32: 1.0.0 2492 + eventemitter3: 5.0.1 2493 + fast-xml-parser: 4.5.3 2494 + ipaddr.js: 2.3.0 2495 + lodash: 4.17.21 2496 + mime-types: 2.1.35 2497 + query-string: 7.1.3 2498 + stream-json: 1.9.1 2499 + through2: 4.0.2 2500 + web-encoding: 1.1.5 2501 + xml2js: 0.6.2 2502 + 2133 2503 mkdirp-classic@0.5.3: {} 2134 2504 2135 2505 ms@2.1.3: {} ··· 2155 2525 picocolors@1.1.1: {} 2156 2526 2157 2527 picomatch@4.0.3: {} 2528 + 2529 + possible-typed-array-names@1.1.0: {} 2158 2530 2159 2531 postcss@8.5.6: 2160 2532 dependencies: ··· 2182 2554 end-of-stream: 1.4.5 2183 2555 once: 1.4.0 2184 2556 2557 + query-string@7.1.3: 2558 + dependencies: 2559 + decode-uri-component: 0.2.2 2560 + filter-obj: 1.1.0 2561 + split-on-first: 1.1.0 2562 + strict-uri-encode: 2.0.0 2563 + 2185 2564 rc@1.2.8: 2186 2565 dependencies: 2187 2566 deep-extend: 0.6.0 ··· 2228 2607 2229 2608 safe-buffer@5.2.1: {} 2230 2609 2610 + safe-regex-test@1.1.0: 2611 + dependencies: 2612 + call-bound: 1.0.4 2613 + es-errors: 1.3.0 2614 + is-regex: 1.2.1 2615 + 2616 + sax@1.4.4: {} 2617 + 2231 2618 semver@7.7.3: {} 2232 2619 2620 + set-function-length@1.2.2: 2621 + dependencies: 2622 + define-data-property: 1.1.4 2623 + es-errors: 1.3.0 2624 + function-bind: 1.1.2 2625 + get-intrinsic: 1.3.0 2626 + gopd: 1.2.0 2627 + has-property-descriptors: 1.0.2 2628 + 2233 2629 sharp@0.33.5: 2234 2630 dependencies: 2235 2631 color: 4.2.3 ··· 2272 2668 2273 2669 source-map-js@1.2.1: {} 2274 2670 2671 + split-on-first@1.1.0: {} 2672 + 2275 2673 stackback@0.0.2: {} 2276 2674 2277 2675 std-env@3.10.0: {} 2278 2676 2279 2677 stoppable@1.1.0: {} 2280 2678 2679 + stream-chain@2.2.5: {} 2680 + 2681 + stream-json@1.9.1: 2682 + dependencies: 2683 + stream-chain: 2.2.5 2684 + 2685 + strict-uri-encode@2.0.0: {} 2686 + 2281 2687 string_decoder@1.3.0: 2282 2688 dependencies: 2283 2689 safe-buffer: 5.2.1 2284 2690 2285 2691 strip-json-comments@2.0.1: {} 2692 + 2693 + strnum@1.1.2: {} 2286 2694 2287 2695 supports-color@10.2.2: {} 2288 2696 ··· 2305 2713 inherits: 2.0.4 2306 2714 readable-stream: 3.6.2 2307 2715 2716 + through2@4.0.2: 2717 + dependencies: 2718 + readable-stream: 3.6.2 2719 + 2308 2720 tinybench@2.9.0: {} 2309 2721 2310 2722 tinyexec@1.0.2: {} ··· 2335 2747 2336 2748 util-deprecate@1.0.2: {} 2337 2749 2750 + util@0.12.5: 2751 + dependencies: 2752 + inherits: 2.0.4 2753 + is-arguments: 1.2.0 2754 + is-generator-function: 1.1.2 2755 + is-typed-array: 1.1.15 2756 + which-typed-array: 1.1.19 2757 + 2338 2758 vite@7.3.1(@types/node@25.0.6): 2339 2759 dependencies: 2340 2760 esbuild: 0.27.2 ··· 2384 2804 - tsx 2385 2805 - yaml 2386 2806 2807 + web-encoding@1.1.5: 2808 + dependencies: 2809 + util: 0.12.5 2810 + optionalDependencies: 2811 + '@zxing/text-encoding': 0.9.0 2812 + 2813 + which-typed-array@1.1.19: 2814 + dependencies: 2815 + available-typed-arrays: 1.0.7 2816 + call-bind: 1.0.8 2817 + call-bound: 1.0.4 2818 + for-each: 0.3.5 2819 + get-proto: 1.0.1 2820 + gopd: 1.2.0 2821 + has-tostringtag: 1.0.2 2822 + 2387 2823 why-is-node-running@2.3.0: 2388 2824 dependencies: 2389 2825 siginfo: 2.0.0 ··· 2419 2855 ws@8.18.0: {} 2420 2856 2421 2857 ws@8.19.0: {} 2858 + 2859 + xml2js@0.6.2: 2860 + dependencies: 2861 + sax: 1.4.4 2862 + xmlbuilder: 11.0.1 2863 + 2864 + xmlbuilder@11.0.1: {} 2422 2865 2423 2866 youch-core@0.3.3: 2424 2867 dependencies:
+6 -1
test/helpers/docker-services.js
··· 3 3 4 4 const RELAY_URL = 'http://localhost:2470'; 5 5 const PLC_URL = 'http://localhost:2582'; 6 + const MINIO_URL = 'http://localhost:9000'; 7 + const USE_S3 = process.env.BLOB_STORAGE === 's3'; 6 8 7 9 /** 8 10 * Wait for a service to respond ··· 40 42 // Wait for services to be ready 41 43 await waitForService(PLC_URL, 'PLC'); 42 44 await waitForService(RELAY_URL, 'Relay'); 45 + if (USE_S3) { 46 + await waitForService(`${MINIO_URL}/minio/health/ready`, 'MinIO'); 47 + } 43 48 44 49 // Configure relay to accept new PDS subscriptions 45 50 // Fresh relay database defaults perDayLimit to 0, blocking all subscriptions ··· 83 88 } 84 89 } 85 90 86 - export { RELAY_URL, PLC_URL }; 91 + export { RELAY_URL, PLC_URL, MINIO_URL, USE_S3 };
+70 -1
test/helpers/node-server.js
··· 5 5 const TEST_DATA_DIR = './test-data'; 6 6 const TEST_PORT = 3000; 7 7 const USE_LOCAL_INFRA = process.env.USE_LOCAL_INFRA !== 'false'; 8 + const USE_S3 = process.env.BLOB_STORAGE === 's3'; 9 + 10 + const MINIO_BUCKET = 'pds-blobs'; 11 + 12 + /** 13 + * Create blob adapter using MinIO client 14 + * @returns {Promise<import('@pds/core/ports').BlobPort>} 15 + */ 16 + async function createMinioBlobs() { 17 + const { Client } = await import('minio'); 18 + 19 + const client = new Client({ 20 + endPoint: 'localhost', 21 + port: 9000, 22 + useSSL: false, 23 + accessKey: 'minioadmin', 24 + secretKey: 'minioadmin', 25 + }); 26 + 27 + // Ensure bucket exists 28 + const exists = await client.bucketExists(MINIO_BUCKET); 29 + if (!exists) { 30 + await client.makeBucket(MINIO_BUCKET); 31 + console.log('Created MinIO bucket:', MINIO_BUCKET); 32 + } 33 + 34 + return { 35 + async get(did, cid) { 36 + try { 37 + const objectName = `${did}/${cid}`; 38 + const stat = await client.statObject(MINIO_BUCKET, objectName); 39 + const stream = await client.getObject(MINIO_BUCKET, objectName); 40 + 41 + // Collect stream into buffer 42 + const chunks = []; 43 + for await (const chunk of stream) { 44 + chunks.push(chunk); 45 + } 46 + const data = new Uint8Array(Buffer.concat(chunks)); 47 + const mimeType = stat.metaData?.mime_type || 'application/octet-stream'; 48 + 49 + return { data, mimeType }; 50 + } catch (e) { 51 + if (e.code === 'NotFound') return null; 52 + throw e; 53 + } 54 + }, 55 + 56 + async put(did, cid, data, mimeType) { 57 + const objectName = `${did}/${cid}`; 58 + await client.putObject(MINIO_BUCKET, objectName, Buffer.from(data), data.length, { 59 + 'Content-Type': mimeType, 60 + mime_type: mimeType, 61 + }); 62 + }, 63 + 64 + async delete(did, cid) { 65 + const objectName = `${did}/${cid}`; 66 + await client.removeObject(MINIO_BUCKET, objectName); 67 + }, 68 + }; 69 + } 8 70 9 71 /** 10 72 * Start Node.js PDS server for e2e tests ··· 16 78 rmSync(TEST_DATA_DIR, { recursive: true, force: true }); 17 79 mkdirSync(TEST_DATA_DIR, { recursive: true }); 18 80 81 + // Configure blobs adapter 82 + const blobs = USE_S3 ? await createMinioBlobs() : undefined; 83 + if (USE_S3) { 84 + console.log('Using S3 blob storage (MinIO)'); 85 + } 86 + 19 87 const server = await createServer({ 20 88 port: TEST_PORT, 21 89 dbPath: `${TEST_DATA_DIR}/pds.db`, 22 90 blobsDir: `${TEST_DATA_DIR}/blobs`, 91 + blobs, 23 92 jwtSecret: 'test-secret-for-e2e', 24 93 // Use HTTPS hostname (via Caddy proxy) when docker infra is enabled 25 94 hostname: USE_LOCAL_INFRA ··· 45 114 await server.close(); 46 115 } 47 116 48 - export { USE_LOCAL_INFRA, TEST_PORT }; 117 + export { USE_LOCAL_INFRA, TEST_PORT, USE_S3 };
+1 -1
tsconfig.build.json
··· 6 6 "emitDeclarationOnly": true 7 7 }, 8 8 "include": ["packages/**/*.js"], 9 - "exclude": ["node_modules", "test", "examples"] 9 + "exclude": ["node_modules", "test", "examples", "packages/deno", "packages/blobs-deno"] 10 10 }