Monorepo for wisp.place. A static site hosting service built on top of the AT Protocol. wisp.place

start enforcing limits on hosting service, clean up logging in cli

nekomimi.pet 804d61c0 66c7dfa2

verified
Changed files
+228 -59
apps
hosting-service
main-app
cli
packages
@wisp
atproto-utils
constants
src
lexicons
+4 -2
apps/hosting-service/package.json
··· 6 6 "dev": "tsx --env-file=.env src/index.ts", 7 7 "build": "bun run build.ts", 8 8 "start": "tsx src/index.ts", 9 + "check": "tsc --noEmit", 9 10 "backfill": "tsx src/index.ts --backfill" 10 11 }, 11 12 "dependencies": { ··· 18 19 "@wisp/safe-fetch": "workspace:*", 19 20 "@atproto/api": "^0.17.4", 20 21 "@atproto/identity": "^0.4.9", 21 - "@atproto/lexicon": "^0.5.1", 22 + "@atproto/lexicon": "^0.5.2", 22 23 "@atproto/sync": "^0.1.36", 23 24 "@atproto/xrpc": "^0.7.5", 24 25 "@hono/node-server": "^1.19.6", ··· 31 32 "@types/bun": "^1.3.1", 32 33 "@types/mime-types": "^2.1.4", 33 34 "@types/node": "^22.10.5", 34 - "tsx": "^4.19.2" 35 + "tsx": "^4.19.2", 36 + "typescript": "^5.9.3" 35 37 } 36 38 }
+53 -3
apps/hosting-service/src/lib/utils.ts
··· 7 7 import { safeFetchJson, safeFetchBlob } from '@wisp/safe-fetch'; 8 8 import { CID } from 'multiformats'; 9 9 import { extractBlobCid } from '@wisp/atproto-utils'; 10 - import { sanitizePath, collectFileCidsFromEntries } from '@wisp/fs-utils'; 10 + import { sanitizePath, collectFileCidsFromEntries, countFilesInDirectory } from '@wisp/fs-utils'; 11 11 import { shouldCompressMimeType } from '@wisp/atproto-utils/compression'; 12 + import { MAX_BLOB_SIZE, MAX_FILE_COUNT, MAX_SITE_SIZE } from '@wisp/constants'; 12 13 13 14 // Re-export shared utilities for local usage and tests 14 15 export { extractBlobCid, sanitizePath }; ··· 120 121 } 121 122 122 123 /** 124 + * Calculate total size of all blobs in a directory tree from manifest metadata 125 + */ 126 + function calculateTotalBlobSize(directory: Directory): number { 127 + let totalSize = 0; 128 + 129 + function sumBlobSizes(entries: Entry[]) { 130 + for (const entry of entries) { 131 + const node = entry.node; 132 + 133 + if ('type' in node && node.type === 'directory' && 'entries' in node) { 134 + // Recursively sum subdirectories 135 + sumBlobSizes(node.entries); 136 + } else if ('type' in node && node.type === 'file' && 'blob' in node) { 137 + // Add blob size from manifest 138 + const fileNode = node as File; 139 + const blobSize = (fileNode.blob as any)?.size || 0; 140 + totalSize += blobSize; 141 + } 142 + } 143 + } 144 + 145 + sumBlobSizes(directory.entries); 146 + return totalSize; 147 + } 148 + 149 + /** 123 150 * Extract all subfs URIs from a directory tree with their mount paths 124 151 */ 125 152 function extractSubfsUris(directory: Directory, currentPath: string = ''): Array<{ uri: string; path: string }> { ··· 300 327 // Expand subfs nodes before caching 301 328 const expandedRoot = await expandSubfsNodes(record.root, pdsEndpoint); 302 329 330 + // Verify all subfs nodes were expanded (defensive check) 331 + const remainingSubfs = extractSubfsUris(expandedRoot); 332 + if (remainingSubfs.length > 0) { 333 + console.warn(`[Cache] Warning: ${remainingSubfs.length} subfs nodes remain unexpanded after expansion`, remainingSubfs); 334 + } 335 + 336 + // ===== VALIDATE LIMITS BEFORE DOWNLOADING ANY BLOBS ===== 337 + 338 + // 1. Validate file count limit 339 + const fileCount = countFilesInDirectory(expandedRoot); 340 + if (fileCount > MAX_FILE_COUNT) { 341 + throw new Error(`Site exceeds file count limit: ${fileCount} files (max ${MAX_FILE_COUNT})`); 342 + } 343 + console.log(`[Cache] File count validation passed: ${fileCount} files (limit: ${MAX_FILE_COUNT})`); 344 + 345 + // 2. Validate total size from blob metadata in manifest (before downloading) 346 + const totalBlobSize = calculateTotalBlobSize(expandedRoot); 347 + if (totalBlobSize > MAX_SITE_SIZE) { 348 + throw new Error(`Site exceeds size limit: ${(totalBlobSize / 1024 / 1024).toFixed(2)}MB (max ${(MAX_SITE_SIZE / 1024 / 1024).toFixed(0)}MB)`); 349 + } 350 + console.log(`[Cache] Size validation passed: ${(totalBlobSize / 1024 / 1024).toFixed(2)}MB (limit: ${(MAX_SITE_SIZE / 1024 / 1024).toFixed(0)}MB)`); 351 + 352 + // All validations passed, proceed with caching 303 353 // Get existing cache metadata to check for incremental updates 304 354 const existingMetadata = await getCacheMetadata(did, rkey); 305 355 const existingFileCids = existingMetadata?.fileCids || {}; ··· 514 564 515 565 console.log(`[Cache] Fetching blob for file: ${filePath}, CID: ${cid}`); 516 566 517 - // Allow up to 500MB per file blob, with 5 minute timeout 518 - let content = await safeFetchBlob(blobUrl, { maxSize: 500 * 1024 * 1024, timeout: 300000 }); 567 + // Allow up to MAX_BLOB_SIZE per file blob, with 5 minute timeout 568 + let content = await safeFetchBlob(blobUrl, { maxSize: MAX_BLOB_SIZE, timeout: 300000 }); 519 569 520 570 // If content is base64-encoded, decode it back to raw binary (gzipped or not) 521 571 if (base64) {
+3
apps/main-app/package.json
··· 7 7 "dev": "bun run --watch src/index.ts", 8 8 "start": "bun run src/index.ts", 9 9 "build": "bun run build.ts", 10 + "check": "tsc --noEmit", 10 11 "screenshot": "bun run scripts/screenshot-sites.ts" 11 12 }, 12 13 "dependencies": { ··· 53 54 "zlib": "^1.0.5" 54 55 }, 55 56 "devDependencies": { 57 + "@atproto-labs/handle-resolver": "^0.3.4", 58 + "@atproto/did": "^0.2.3", 56 59 "@types/react": "^19.2.2", 57 60 "@types/react-dom": "^19.2.1", 58 61 "bun-types": "latest",
+53 -22
bun.lock
··· 17 17 "dependencies": { 18 18 "@atproto/api": "^0.17.4", 19 19 "@atproto/identity": "^0.4.9", 20 - "@atproto/lexicon": "^0.5.1", 20 + "@atproto/lexicon": "^0.5.2", 21 21 "@atproto/sync": "^0.1.36", 22 22 "@atproto/xrpc": "^0.7.5", 23 23 "@hono/node-server": "^1.19.6", ··· 38 38 "@types/mime-types": "^2.1.4", 39 39 "@types/node": "^22.10.5", 40 40 "tsx": "^4.19.2", 41 + "typescript": "^5.9.3", 41 42 }, 42 43 }, 43 44 "apps/main-app": { ··· 87 88 "zlib": "^1.0.5", 88 89 }, 89 90 "devDependencies": { 91 + "@atproto-labs/handle-resolver": "^0.3.4", 92 + "@atproto/did": "^0.2.3", 90 93 "@types/react": "^19.2.2", 91 94 "@types/react-dom": "^19.2.1", 92 95 "bun-types": "latest", ··· 103 106 "@wisp/lexicons": "workspace:*", 104 107 "multiformats": "^13.3.1", 105 108 }, 109 + "devDependencies": { 110 + "@atproto/lexicon": "^0.5.2", 111 + }, 106 112 }, 107 113 "packages/@wisp/constants": { 108 114 "name": "@wisp/constants", ··· 132 138 }, 133 139 "devDependencies": { 134 140 "@atproto/lex-cli": "^0.9.5", 141 + "multiformats": "^13.4.1", 135 142 }, 136 143 }, 137 144 "packages/@wisp/observability": { ··· 180 187 181 188 "@atproto-labs/fetch-node": ["@atproto-labs/fetch-node@0.2.0", "", { "dependencies": { "@atproto-labs/fetch": "0.2.3", "@atproto-labs/pipe": "0.1.1", "ipaddr.js": "^2.1.0", "undici": "^6.14.1" } }, ""], 182 189 183 - "@atproto-labs/handle-resolver": ["@atproto-labs/handle-resolver@0.3.2", "", { "dependencies": { "@atproto-labs/simple-store": "0.3.0", "@atproto-labs/simple-store-memory": "0.1.4", "@atproto/did": "0.2.1", "zod": "^3.23.8" } }, ""], 190 + "@atproto-labs/handle-resolver": ["@atproto-labs/handle-resolver@0.3.4", "", { "dependencies": { "@atproto-labs/simple-store": "0.3.0", "@atproto-labs/simple-store-memory": "0.1.4", "@atproto/did": "0.2.3", "zod": "^3.23.8" } }, "sha512-wsNopfzfgO3uPvfnFDgNeXgDufXxSXhjBjp2WEiSzEiLrMy0Jodnqggw4OzD9MJKf0a4Iu2/ydd537qdy91LrQ=="], 184 191 185 192 "@atproto-labs/handle-resolver-node": ["@atproto-labs/handle-resolver-node@0.1.21", "", { "dependencies": { "@atproto-labs/fetch-node": "0.2.0", "@atproto-labs/handle-resolver": "0.3.2", "@atproto/did": "0.2.1" } }, ""], 186 193 ··· 200 207 201 208 "@atproto/crypto": ["@atproto/crypto@0.4.4", "", { "dependencies": { "@noble/curves": "^1.7.0", "@noble/hashes": "^1.6.1", "uint8arrays": "3.0.0" } }, ""], 202 209 203 - "@atproto/did": ["@atproto/did@0.2.1", "", { "dependencies": { "zod": "^3.23.8" } }, ""], 210 + "@atproto/did": ["@atproto/did@0.2.3", "", { "dependencies": { "zod": "^3.23.8" } }, "sha512-VI8JJkSizvM2cHYJa37WlbzeCm5tWpojyc1/Zy8q8OOjyoy6X4S4BEfoP941oJcpxpMTObamibQIXQDo7tnIjg=="], 204 211 205 212 "@atproto/identity": ["@atproto/identity@0.4.10", "", { "dependencies": { "@atproto/common-web": "^0.4.4", "@atproto/crypto": "^0.4.4" } }, "sha512-nQbzDLXOhM8p/wo0cTh5DfMSOSHzj6jizpodX37LJ4S1TZzumSxAjHEZa5Rev3JaoD5uSWMVE0MmKEGWkPPvfQ=="], 206 213 ··· 230 237 231 238 "@atproto/sync": ["@atproto/sync@0.1.38", "", { "dependencies": { "@atproto/common": "^0.5.0", "@atproto/identity": "^0.4.10", "@atproto/lexicon": "^0.5.2", "@atproto/repo": "^0.8.11", "@atproto/syntax": "^0.4.1", "@atproto/xrpc-server": "^0.10.0", "multiformats": "^9.9.0", "p-queue": "^6.6.2", "ws": "^8.12.0" } }, "sha512-2rE0SM21Nk4hWw/XcIYFnzlWO6/gBg8mrzuWbOvDhD49sA/wW4zyjaHZ5t1gvk28/SLok2VZiIR8nYBdbf7F5Q=="], 232 239 233 - "@atproto/syntax": ["@atproto/syntax@0.4.1", "", {}, ""], 240 + "@atproto/syntax": ["@atproto/syntax@0.4.2", "", {}, "sha512-X9XSRPinBy/0VQ677j8VXlBsYSsUXaiqxWVpGGxJYsAhugdQRb0jqaVKJFtm6RskeNkV6y9xclSUi9UYG/COrA=="], 234 241 235 242 "@atproto/ws-client": ["@atproto/ws-client@0.0.3", "", { "dependencies": { "@atproto/common": "^0.5.0", "ws": "^8.12.0" } }, "sha512-eKqkTWBk6zuMY+6gs02eT7mS8Btewm8/qaL/Dp00NDCqpNC+U59MWvQsOWT3xkNGfd9Eip+V6VI4oyPvAfsfTA=="], 236 243 237 - "@atproto/xrpc": ["@atproto/xrpc@0.7.5", "", { "dependencies": { "@atproto/lexicon": "^0.5.1", "zod": "^3.23.8" } }, ""], 244 + "@atproto/xrpc": ["@atproto/xrpc@0.7.6", "", { "dependencies": { "@atproto/lexicon": "^0.5.2", "zod": "^3.23.8" } }, "sha512-RvCf4j0JnKYWuz3QzsYCntJi3VuiAAybQsMIUw2wLWcHhchO9F7UaBZINLL2z0qc/cYWPv5NSwcVydMseoCZLA=="], 238 245 239 246 "@atproto/xrpc-server": ["@atproto/xrpc-server@0.9.5", "", { "dependencies": { "@atproto/common": "^0.4.12", "@atproto/crypto": "^0.4.4", "@atproto/lexicon": "^0.5.1", "@atproto/xrpc": "^0.7.5", "cbor-x": "^1.5.1", "express": "^4.17.2", "http-errors": "^2.0.0", "mime-types": "^2.1.35", "rate-limiter-flexible": "^2.4.1", "uint8arrays": "3.0.0", "ws": "^8.12.0", "zod": "^3.23.8" } }, ""], 240 247 ··· 656 663 657 664 "ee-first": ["ee-first@1.1.1", "", {}, ""], 658 665 659 - "elysia": ["elysia@1.4.17", "", { "dependencies": { "cookie": "^1.1.1", "exact-mirror": "0.2.5", "fast-decode-uri-component": "^1.0.1", "memoirist": "^0.4.0" }, "peerDependencies": { "@sinclair/typebox": ">= 0.34.0 < 1", "@types/bun": ">= 1.2.0", "file-type": ">= 20.0.0", "openapi-types": ">= 12.0.0", "typescript": ">= 5.0.0" }, "optionalPeers": ["@types/bun", "typescript"] }, "sha512-GcR7tgxk0+NgMCEqmXMs/xgND4XpmIzUdSdwchcQbYFeFisBcw9cmsvSpI10i160idwtlVyaRXX9K9IZBqnA7Q=="], 666 + "elysia": ["elysia@1.4.18", "", { "dependencies": { "cookie": "^1.1.1", "exact-mirror": "0.2.5", "fast-decode-uri-component": "^1.0.1", "memoirist": "^0.4.0" }, "peerDependencies": { "@sinclair/typebox": ">= 0.34.0 < 1", "@types/bun": ">= 1.2.0", "file-type": ">= 20.0.0", "openapi-types": ">= 12.0.0", "typescript": ">= 5.0.0" }, "optionalPeers": ["@types/bun", "typescript"] }, "sha512-A6BhlipmSvgCy69SBgWADYZSdDIj3fT2gk8/9iMAC8iD+aGcnCr0fitziX0xr36MFDs/fsvVp8dWqxeq1VCgKg=="], 660 667 661 668 "emoji-regex": ["emoji-regex@8.0.0", "", {}, ""], 662 669 ··· 1036 1043 1037 1044 "zod": ["zod@3.25.76", "", {}, ""], 1038 1045 1046 + "@atproto-labs/did-resolver/@atproto/did": ["@atproto/did@0.2.1", "", { "dependencies": { "zod": "^3.23.8" } }, ""], 1047 + 1039 1048 "@atproto-labs/fetch-node/ipaddr.js": ["ipaddr.js@2.2.0", "", {}, ""], 1040 1049 1050 + "@atproto-labs/handle-resolver-node/@atproto-labs/handle-resolver": ["@atproto-labs/handle-resolver@0.3.2", "", { "dependencies": { "@atproto-labs/simple-store": "0.3.0", "@atproto-labs/simple-store-memory": "0.1.4", "@atproto/did": "0.2.1", "zod": "^3.23.8" } }, ""], 1051 + 1052 + "@atproto-labs/handle-resolver-node/@atproto/did": ["@atproto/did@0.2.1", "", { "dependencies": { "zod": "^3.23.8" } }, ""], 1053 + 1054 + "@atproto-labs/identity-resolver/@atproto-labs/handle-resolver": ["@atproto-labs/handle-resolver@0.3.2", "", { "dependencies": { "@atproto-labs/simple-store": "0.3.0", "@atproto-labs/simple-store-memory": "0.1.4", "@atproto/did": "0.2.1", "zod": "^3.23.8" } }, ""], 1055 + 1041 1056 "@atproto/api/@atproto/lexicon": ["@atproto/lexicon@0.4.14", "", { "dependencies": { "@atproto/common-web": "^0.4.2", "@atproto/syntax": "^0.4.0", "iso-datestring-validator": "^2.2.2", "multiformats": "^9.9.0", "zod": "^3.23.8" } }, "sha512-jiKpmH1QER3Gvc7JVY5brwrfo+etFoe57tKPQX/SmPwjvUsFnJAow5xLIryuBaJgFAhnTZViXKs41t//pahGHQ=="], 1057 + 1058 + "@atproto/api/@atproto/syntax": ["@atproto/syntax@0.4.1", "", {}, ""], 1042 1059 1043 1060 "@atproto/api/@atproto/xrpc": ["@atproto/xrpc@0.6.12", "", { "dependencies": { "@atproto/lexicon": "^0.4.10", "zod": "^3.23.8" } }, "sha512-Ut3iISNLujlmY9Gu8sNU+SPDJDvqlVzWddU8qUr0Yae5oD4SguaUFjjhireMGhQ3M5E0KljQgDbTmnBo1kIZ3w=="], 1044 1061 ··· 1054 1071 1055 1072 "@atproto/lex-cli/@atproto/lexicon": ["@atproto/lexicon@0.5.1", "", { "dependencies": { "@atproto/common-web": "^0.4.3", "@atproto/syntax": "^0.4.1", "iso-datestring-validator": "^2.2.2", "multiformats": "^9.9.0", "zod": "^3.23.8" } }, ""], 1056 1073 1057 - "@atproto/lex-data/@atproto/syntax": ["@atproto/syntax@0.4.2", "", {}, "sha512-X9XSRPinBy/0VQ677j8VXlBsYSsUXaiqxWVpGGxJYsAhugdQRb0jqaVKJFtm6RskeNkV6y9xclSUi9UYG/COrA=="], 1074 + "@atproto/lex-cli/@atproto/syntax": ["@atproto/syntax@0.4.1", "", {}, ""], 1058 1075 1059 1076 "@atproto/lex-data/multiformats": ["multiformats@9.9.0", "", {}, ""], 1060 1077 1061 - "@atproto/lexicon/@atproto/syntax": ["@atproto/syntax@0.4.2", "", {}, "sha512-X9XSRPinBy/0VQ677j8VXlBsYSsUXaiqxWVpGGxJYsAhugdQRb0jqaVKJFtm6RskeNkV6y9xclSUi9UYG/COrA=="], 1078 + "@atproto/lexicon/multiformats": ["multiformats@9.9.0", "", {}, ""], 1079 + 1080 + "@atproto/oauth-client/@atproto-labs/handle-resolver": ["@atproto-labs/handle-resolver@0.3.2", "", { "dependencies": { "@atproto-labs/simple-store": "0.3.0", "@atproto-labs/simple-store-memory": "0.1.4", "@atproto/did": "0.2.1", "zod": "^3.23.8" } }, ""], 1062 1081 1063 - "@atproto/lexicon/multiformats": ["multiformats@9.9.0", "", {}, ""], 1082 + "@atproto/oauth-client/@atproto/did": ["@atproto/did@0.2.1", "", { "dependencies": { "zod": "^3.23.8" } }, ""], 1083 + 1084 + "@atproto/oauth-client/@atproto/xrpc": ["@atproto/xrpc@0.7.5", "", { "dependencies": { "@atproto/lexicon": "^0.5.1", "zod": "^3.23.8" } }, ""], 1064 1085 1065 1086 "@atproto/oauth-client/multiformats": ["multiformats@9.9.0", "", {}, ""], 1087 + 1088 + "@atproto/oauth-client-node/@atproto/did": ["@atproto/did@0.2.1", "", { "dependencies": { "zod": "^3.23.8" } }, ""], 1089 + 1090 + "@atproto/oauth-types/@atproto/did": ["@atproto/did@0.2.1", "", { "dependencies": { "zod": "^3.23.8" } }, ""], 1066 1091 1067 1092 "@atproto/repo/@atproto/common": ["@atproto/common@0.5.2", "", { "dependencies": { "@atproto/common-web": "^0.4.6", "@atproto/lex-cbor": "0.0.2", "@atproto/lex-data": "0.0.2", "iso-datestring-validator": "^2.2.2", "multiformats": "^9.9.0", "pino": "^8.21.0" } }, "sha512-7KdU8FcIfnwS2kmv7M86pKxtw/fLvPY2bSI1rXpG+AmA8O++IUGlSCujBGzbrPwnQvY/z++f6Le4rdBzu8bFaA=="], 1068 1093 ··· 1072 1097 1073 1098 "@atproto/sync/@atproto/common": ["@atproto/common@0.5.2", "", { "dependencies": { "@atproto/common-web": "^0.4.6", "@atproto/lex-cbor": "0.0.2", "@atproto/lex-data": "0.0.2", "iso-datestring-validator": "^2.2.2", "multiformats": "^9.9.0", "pino": "^8.21.0" } }, "sha512-7KdU8FcIfnwS2kmv7M86pKxtw/fLvPY2bSI1rXpG+AmA8O++IUGlSCujBGzbrPwnQvY/z++f6Le4rdBzu8bFaA=="], 1074 1099 1075 - "@atproto/sync/@atproto/syntax": ["@atproto/syntax@0.4.2", "", {}, "sha512-X9XSRPinBy/0VQ677j8VXlBsYSsUXaiqxWVpGGxJYsAhugdQRb0jqaVKJFtm6RskeNkV6y9xclSUi9UYG/COrA=="], 1076 - 1077 1100 "@atproto/sync/@atproto/xrpc-server": ["@atproto/xrpc-server@0.10.2", "", { "dependencies": { "@atproto/common": "^0.5.2", "@atproto/crypto": "^0.4.5", "@atproto/lex-cbor": "0.0.2", "@atproto/lex-data": "0.0.2", "@atproto/lexicon": "^0.5.2", "@atproto/ws-client": "^0.0.3", "@atproto/xrpc": "^0.7.6", "express": "^4.17.2", "http-errors": "^2.0.0", "mime-types": "^2.1.35", "rate-limiter-flexible": "^2.4.1", "ws": "^8.12.0", "zod": "^3.23.8" } }, "sha512-5AzN8xoV8K1Omn45z6qKH414+B3Z35D536rrScwF3aQGDEdpObAS+vya9UoSg+Gvm2+oOtVEbVri7riLTBW3Vg=="], 1078 1101 1079 1102 "@atproto/sync/multiformats": ["multiformats@9.9.0", "", {}, ""], 1080 1103 1081 1104 "@atproto/ws-client/@atproto/common": ["@atproto/common@0.5.2", "", { "dependencies": { "@atproto/common-web": "^0.4.6", "@atproto/lex-cbor": "0.0.2", "@atproto/lex-data": "0.0.2", "iso-datestring-validator": "^2.2.2", "multiformats": "^9.9.0", "pino": "^8.21.0" } }, "sha512-7KdU8FcIfnwS2kmv7M86pKxtw/fLvPY2bSI1rXpG+AmA8O++IUGlSCujBGzbrPwnQvY/z++f6Le4rdBzu8bFaA=="], 1082 1105 1083 - "@atproto/xrpc/@atproto/lexicon": ["@atproto/lexicon@0.5.1", "", { "dependencies": { "@atproto/common-web": "^0.4.3", "@atproto/syntax": "^0.4.1", "iso-datestring-validator": "^2.2.2", "multiformats": "^9.9.0", "zod": "^3.23.8" } }, ""], 1084 - 1085 1106 "@atproto/xrpc-server/@atproto/lexicon": ["@atproto/lexicon@0.5.1", "", { "dependencies": { "@atproto/common-web": "^0.4.3", "@atproto/syntax": "^0.4.1", "iso-datestring-validator": "^2.2.2", "multiformats": "^9.9.0", "zod": "^3.23.8" } }, ""], 1107 + 1108 + "@atproto/xrpc-server/@atproto/xrpc": ["@atproto/xrpc@0.7.5", "", { "dependencies": { "@atproto/lexicon": "^0.5.1", "zod": "^3.23.8" } }, ""], 1086 1109 1087 1110 "@ipld/dag-cbor/multiformats": ["multiformats@9.9.0", "", {}, ""], 1088 1111 ··· 1140 1163 1141 1164 "wisp-hosting-service/@atproto/api": ["@atproto/api@0.17.7", "", { "dependencies": { "@atproto/common-web": "^0.4.3", "@atproto/lexicon": "^0.5.1", "@atproto/syntax": "^0.4.1", "@atproto/xrpc": "^0.7.5", "await-lock": "^2.2.2", "multiformats": "^9.9.0", "tlds": "^1.234.0", "zod": "^3.23.8" } }, ""], 1142 1165 1143 - "wisp-hosting-service/@atproto/lexicon": ["@atproto/lexicon@0.5.1", "", { "dependencies": { "@atproto/common-web": "^0.4.3", "@atproto/syntax": "^0.4.1", "iso-datestring-validator": "^2.2.2", "multiformats": "^9.9.0", "zod": "^3.23.8" } }, ""], 1166 + "@atproto-labs/identity-resolver/@atproto-labs/handle-resolver/@atproto/did": ["@atproto/did@0.2.1", "", { "dependencies": { "zod": "^3.23.8" } }, ""], 1144 1167 1145 1168 "@atproto/lex-cli/@atproto/lexicon/@atproto/common-web": ["@atproto/common-web@0.4.3", "", { "dependencies": { "graphemer": "^1.4.0", "multiformats": "^9.9.0", "uint8arrays": "3.0.0", "zod": "^3.23.8" } }, ""], 1146 1169 1147 1170 "@atproto/lex-cli/@atproto/lexicon/multiformats": ["multiformats@9.9.0", "", {}, ""], 1148 1171 1149 - "@atproto/sync/@atproto/xrpc-server/@atproto/crypto": ["@atproto/crypto@0.4.5", "", { "dependencies": { "@noble/curves": "^1.7.0", "@noble/hashes": "^1.6.1", "uint8arrays": "3.0.0" } }, "sha512-n40aKkMoCatP0u9Yvhrdk6fXyOHFDDbkdm4h4HCyWW+KlKl8iXfD5iV+ECq+w5BM+QH25aIpt3/j6EUNerhLxw=="], 1172 + "@atproto/oauth-client/@atproto/xrpc/@atproto/lexicon": ["@atproto/lexicon@0.5.1", "", { "dependencies": { "@atproto/common-web": "^0.4.3", "@atproto/syntax": "^0.4.1", "iso-datestring-validator": "^2.2.2", "multiformats": "^9.9.0", "zod": "^3.23.8" } }, ""], 1150 1173 1151 - "@atproto/sync/@atproto/xrpc-server/@atproto/xrpc": ["@atproto/xrpc@0.7.6", "", { "dependencies": { "@atproto/lexicon": "^0.5.2", "zod": "^3.23.8" } }, "sha512-RvCf4j0JnKYWuz3QzsYCntJi3VuiAAybQsMIUw2wLWcHhchO9F7UaBZINLL2z0qc/cYWPv5NSwcVydMseoCZLA=="], 1174 + "@atproto/sync/@atproto/xrpc-server/@atproto/crypto": ["@atproto/crypto@0.4.5", "", { "dependencies": { "@noble/curves": "^1.7.0", "@noble/hashes": "^1.6.1", "uint8arrays": "3.0.0" } }, "sha512-n40aKkMoCatP0u9Yvhrdk6fXyOHFDDbkdm4h4HCyWW+KlKl8iXfD5iV+ECq+w5BM+QH25aIpt3/j6EUNerhLxw=="], 1152 1175 1153 1176 "@atproto/ws-client/@atproto/common/multiformats": ["multiformats@9.9.0", "", {}, ""], 1154 1177 1155 1178 "@atproto/xrpc-server/@atproto/lexicon/@atproto/common-web": ["@atproto/common-web@0.4.3", "", { "dependencies": { "graphemer": "^1.4.0", "multiformats": "^9.9.0", "uint8arrays": "3.0.0", "zod": "^3.23.8" } }, ""], 1179 + 1180 + "@atproto/xrpc-server/@atproto/lexicon/@atproto/syntax": ["@atproto/syntax@0.4.1", "", {}, ""], 1156 1181 1157 1182 "@atproto/xrpc-server/@atproto/lexicon/multiformats": ["multiformats@9.9.0", "", {}, ""], 1158 1183 1159 - "@atproto/xrpc/@atproto/lexicon/@atproto/common-web": ["@atproto/common-web@0.4.3", "", { "dependencies": { "graphemer": "^1.4.0", "multiformats": "^9.9.0", "uint8arrays": "3.0.0", "zod": "^3.23.8" } }, ""], 1160 - 1161 - "@atproto/xrpc/@atproto/lexicon/multiformats": ["multiformats@9.9.0", "", {}, ""], 1162 - 1163 1184 "@tokenizer/inflate/debug/ms": ["ms@2.1.3", "", {}, ""], 1164 1185 1165 1186 "@wisp/main-app/@atproto/api/@atproto/common-web": ["@atproto/common-web@0.4.3", "", { "dependencies": { "graphemer": "^1.4.0", "multiformats": "^9.9.0", "uint8arrays": "3.0.0", "zod": "^3.23.8" } }, ""], 1166 1187 1167 1188 "@wisp/main-app/@atproto/api/@atproto/lexicon": ["@atproto/lexicon@0.5.1", "", { "dependencies": { "@atproto/common-web": "^0.4.3", "@atproto/syntax": "^0.4.1", "iso-datestring-validator": "^2.2.2", "multiformats": "^9.9.0", "zod": "^3.23.8" } }, ""], 1189 + 1190 + "@wisp/main-app/@atproto/api/@atproto/syntax": ["@atproto/syntax@0.4.1", "", {}, ""], 1191 + 1192 + "@wisp/main-app/@atproto/api/@atproto/xrpc": ["@atproto/xrpc@0.7.5", "", { "dependencies": { "@atproto/lexicon": "^0.5.1", "zod": "^3.23.8" } }, ""], 1168 1193 1169 1194 "@wisp/main-app/@atproto/api/multiformats": ["multiformats@9.9.0", "", {}, ""], 1170 1195 ··· 1228 1253 1229 1254 "wisp-hosting-service/@atproto/api/@atproto/common-web": ["@atproto/common-web@0.4.3", "", { "dependencies": { "graphemer": "^1.4.0", "multiformats": "^9.9.0", "uint8arrays": "3.0.0", "zod": "^3.23.8" } }, ""], 1230 1255 1256 + "wisp-hosting-service/@atproto/api/@atproto/lexicon": ["@atproto/lexicon@0.5.1", "", { "dependencies": { "@atproto/common-web": "^0.4.3", "@atproto/syntax": "^0.4.1", "iso-datestring-validator": "^2.2.2", "multiformats": "^9.9.0", "zod": "^3.23.8" } }, ""], 1257 + 1258 + "wisp-hosting-service/@atproto/api/@atproto/syntax": ["@atproto/syntax@0.4.1", "", {}, ""], 1259 + 1260 + "wisp-hosting-service/@atproto/api/@atproto/xrpc": ["@atproto/xrpc@0.7.5", "", { "dependencies": { "@atproto/lexicon": "^0.5.1", "zod": "^3.23.8" } }, ""], 1261 + 1231 1262 "wisp-hosting-service/@atproto/api/multiformats": ["multiformats@9.9.0", "", {}, ""], 1232 1263 1233 - "wisp-hosting-service/@atproto/lexicon/@atproto/common-web": ["@atproto/common-web@0.4.3", "", { "dependencies": { "graphemer": "^1.4.0", "multiformats": "^9.9.0", "uint8arrays": "3.0.0", "zod": "^3.23.8" } }, ""], 1264 + "@atproto/oauth-client/@atproto/xrpc/@atproto/lexicon/@atproto/common-web": ["@atproto/common-web@0.4.3", "", { "dependencies": { "graphemer": "^1.4.0", "multiformats": "^9.9.0", "uint8arrays": "3.0.0", "zod": "^3.23.8" } }, ""], 1234 1265 1235 - "wisp-hosting-service/@atproto/lexicon/multiformats": ["multiformats@9.9.0", "", {}, ""], 1266 + "@atproto/oauth-client/@atproto/xrpc/@atproto/lexicon/@atproto/syntax": ["@atproto/syntax@0.4.1", "", {}, ""], 1236 1267 } 1237 1268 }
+54 -8
cli/Cargo.lock
··· 605 605 checksum = "75984efb6ed102a0d42db99afb6c1948f0380d1d91808d5529916e6c08b49d8d" 606 606 607 607 [[package]] 608 + name = "console" 609 + version = "0.15.11" 610 + source = "registry+https://github.com/rust-lang/crates.io-index" 611 + checksum = "054ccb5b10f9f2cbf51eb355ca1d05c2d279ce1804688d0db74b4733a5aeafd8" 612 + dependencies = [ 613 + "encode_unicode", 614 + "libc", 615 + "once_cell", 616 + "unicode-width 0.2.2", 617 + "windows-sys 0.59.0", 618 + ] 619 + 620 + [[package]] 608 621 name = "const-oid" 609 622 version = "0.9.6" 610 623 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 957 970 "subtle", 958 971 "zeroize", 959 972 ] 973 + 974 + [[package]] 975 + name = "encode_unicode" 976 + version = "1.0.0" 977 + source = "registry+https://github.com/rust-lang/crates.io-index" 978 + checksum = "34aa73646ffb006b8f5147f3dc182bd4bcb190227ce861fc4a4844bf8e3cb2c0" 960 979 961 980 [[package]] 962 981 name = "encoding_rs" ··· 1733 1752 ] 1734 1753 1735 1754 [[package]] 1755 + name = "indicatif" 1756 + version = "0.17.11" 1757 + source = "registry+https://github.com/rust-lang/crates.io-index" 1758 + checksum = "183b3088984b400f4cfac3620d5e076c84da5364016b4f49473de574b2586235" 1759 + dependencies = [ 1760 + "console", 1761 + "number_prefix", 1762 + "portable-atomic", 1763 + "unicode-width 0.2.2", 1764 + "web-time", 1765 + ] 1766 + 1767 + [[package]] 1736 1768 name = "indoc" 1737 1769 version = "2.0.7" 1738 1770 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 1810 1842 [[package]] 1811 1843 name = "jacquard" 1812 1844 version = "0.9.3" 1813 - source = "git+https://tangled.org/nekomimi.pet/jacquard#e1b90160d4026e036ab5b797e56ddd7ae5c5537e" 1814 1845 dependencies = [ 1815 1846 "bytes", 1816 1847 "getrandom 0.2.16", ··· 1840 1871 [[package]] 1841 1872 name = "jacquard-api" 1842 1873 version = "0.9.2" 1843 - source = "git+https://tangled.org/nekomimi.pet/jacquard#e1b90160d4026e036ab5b797e56ddd7ae5c5537e" 1844 1874 dependencies = [ 1845 1875 "bon", 1846 1876 "bytes", ··· 1858 1888 [[package]] 1859 1889 name = "jacquard-common" 1860 1890 version = "0.9.2" 1861 - source = "git+https://tangled.org/nekomimi.pet/jacquard#e1b90160d4026e036ab5b797e56ddd7ae5c5537e" 1862 1891 dependencies = [ 1863 1892 "base64 0.22.1", 1864 1893 "bon", ··· 1900 1929 [[package]] 1901 1930 name = "jacquard-derive" 1902 1931 version = "0.9.3" 1903 - source = "git+https://tangled.org/nekomimi.pet/jacquard#e1b90160d4026e036ab5b797e56ddd7ae5c5537e" 1904 1932 dependencies = [ 1905 1933 "heck 0.5.0", 1906 1934 "jacquard-lexicon", ··· 1912 1940 [[package]] 1913 1941 name = "jacquard-identity" 1914 1942 version = "0.9.2" 1915 - source = "git+https://tangled.org/nekomimi.pet/jacquard#e1b90160d4026e036ab5b797e56ddd7ae5c5537e" 1916 1943 dependencies = [ 1917 1944 "bon", 1918 1945 "bytes", ··· 1938 1965 [[package]] 1939 1966 name = "jacquard-lexicon" 1940 1967 version = "0.9.2" 1941 - source = "git+https://tangled.org/nekomimi.pet/jacquard#e1b90160d4026e036ab5b797e56ddd7ae5c5537e" 1942 1968 dependencies = [ 1943 1969 "cid", 1944 1970 "dashmap", ··· 1964 1990 [[package]] 1965 1991 name = "jacquard-oauth" 1966 1992 version = "0.9.2" 1967 - source = "git+https://tangled.org/nekomimi.pet/jacquard#e1b90160d4026e036ab5b797e56ddd7ae5c5537e" 1968 1993 dependencies = [ 1969 1994 "base64 0.22.1", 1970 1995 "bytes", ··· 2289 2314 [[package]] 2290 2315 name = "mini-moka" 2291 2316 version = "0.10.99" 2292 - source = "git+https://tangled.org/nekomimi.pet/jacquard#e1b90160d4026e036ab5b797e56ddd7ae5c5537e" 2293 2317 dependencies = [ 2294 2318 "crossbeam-channel", 2295 2319 "crossbeam-utils", ··· 2513 2537 ] 2514 2538 2515 2539 [[package]] 2540 + name = "number_prefix" 2541 + version = "0.4.0" 2542 + source = "registry+https://github.com/rust-lang/crates.io-index" 2543 + checksum = "830b246a0e5f20af87141b25c173cd1b609bd7779a4617d6ec582abaf90870f3" 2544 + 2545 + [[package]] 2516 2546 name = "objc2" 2517 2547 version = "0.6.3" 2518 2548 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 2756 2786 "der", 2757 2787 "spki", 2758 2788 ] 2789 + 2790 + [[package]] 2791 + name = "portable-atomic" 2792 + version = "1.11.1" 2793 + source = "registry+https://github.com/rust-lang/crates.io-index" 2794 + checksum = "f84267b20a16ea918e43c6a88433c2d54fa145c92a811b5b047ccbe153674483" 2759 2795 2760 2796 [[package]] 2761 2797 name = "potential_utf" ··· 4717 4753 4718 4754 [[package]] 4719 4755 name = "windows-sys" 4756 + version = "0.59.0" 4757 + source = "registry+https://github.com/rust-lang/crates.io-index" 4758 + checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" 4759 + dependencies = [ 4760 + "windows-targets 0.52.6", 4761 + ] 4762 + 4763 + [[package]] 4764 + name = "windows-sys" 4720 4765 version = "0.60.2" 4721 4766 source = "registry+https://github.com/rust-lang/crates.io-index" 4722 4767 checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb" ··· 5008 5053 "futures", 5009 5054 "globset", 5010 5055 "ignore", 5056 + "indicatif", 5011 5057 "jacquard", 5012 5058 "jacquard-api", 5013 5059 "jacquard-common",
+15 -7
cli/Cargo.toml
··· 8 8 place_wisp = [] 9 9 10 10 [dependencies] 11 - jacquard = { git = "https://tangled.org/nekomimi.pet/jacquard", features = ["loopback"] } 12 - jacquard-oauth = { git = "https://tangled.org/nekomimi.pet/jacquard" } 13 - jacquard-api = { git = "https://tangled.org/nekomimi.pet/jacquard", features = ["streaming"] } 14 - jacquard-common = { git = "https://tangled.org/nekomimi.pet/jacquard", features = ["websocket"] } 15 - jacquard-identity = { git = "https://tangled.org/nekomimi.pet/jacquard", features = ["dns"] } 16 - jacquard-derive = { git = "https://tangled.org/nekomimi.pet/jacquard" } 17 - jacquard-lexicon = { git = "https://tangled.org/nekomimi.pet/jacquard" } 11 + # jacquard = { git = "https://tangled.org/nekomimi.pet/jacquard", features = ["loopback"] } 12 + # jacquard-oauth = { git = "https://tangled.org/nekomimi.pet/jacquard" } 13 + # jacquard-api = { git = "https://tangled.org/nekomimi.pet/jacquard", features = ["streaming"] } 14 + # jacquard-common = { git = "https://tangled.org/nekomimi.pet/jacquard", features = ["websocket"] } 15 + # jacquard-identity = { git = "https://tangled.org/nekomimi.pet/jacquard", features = ["dns"] } 16 + # jacquard-derive = { git = "https://tangled.org/nekomimi.pet/jacquard" } 17 + # jacquard-lexicon = { git = "https://tangled.org/nekomimi.pet/jacquard" } 18 + jacquard = { path = "../../jacquard/crates/jacquard", features = ["loopback"] } 19 + jacquard-oauth = { path = "../../jacquard/crates/jacquard-oauth" } 20 + jacquard-api = { path = "../../jacquard/crates/jacquard-api", features = ["streaming"] } 21 + jacquard-common = { path = "../../jacquard/crates/jacquard-common", features = ["websocket"] } 22 + jacquard-identity = { path = "../../jacquard/crates/jacquard-identity", features = ["dns"] } 23 + jacquard-derive = { path = "../../jacquard/crates/jacquard-derive" } 24 + jacquard-lexicon = { path = "../../jacquard/crates/jacquard-lexicon" } 18 25 clap = { version = "4.5.51", features = ["derive"] } 19 26 tokio = { version = "1.48", features = ["full"] } 20 27 miette = { version = "7.6.0", features = ["fancy"] } ··· 42 49 regex = "1.11" 43 50 ignore = "0.4" 44 51 globset = "0.4" 52 + indicatif = "0.17"
+39 -15
cli/src/main.rs
··· 26 26 use std::io::Write; 27 27 use base64::Engine; 28 28 use futures::stream::{self, StreamExt}; 29 + use indicatif::{ProgressBar, ProgressStyle, MultiProgress}; 29 30 30 31 use place_wisp::fs::*; 31 32 use place_wisp::settings::*; 33 + 34 + /// Maximum number of concurrent file uploads to the PDS 35 + const MAX_CONCURRENT_UPLOADS: usize = 2; 32 36 33 37 #[derive(Parser, Debug)] 34 38 #[command(author, version, about = "wisp.place CLI tool")] ··· 326 330 327 331 // Build directory tree with ignore patterns 328 332 let ignore_matcher = ignore_patterns::IgnoreMatcher::new(&path)?; 329 - let (root_dir, total_files, reused_count) = build_directory(agent, &path, &existing_blob_map, String::new(), &ignore_matcher).await?; 333 + 334 + // Create progress tracking (spinner style since we don't know total count upfront) 335 + let multi_progress = MultiProgress::new(); 336 + let progress = multi_progress.add(ProgressBar::new_spinner()); 337 + progress.set_style( 338 + ProgressStyle::default_spinner() 339 + .template("[{elapsed_precise}] {spinner:.cyan} {pos} files {msg}") 340 + .into_diagnostic()? 341 + .tick_chars("⠁⠂⠄⡀⢀⠠⠐⠈ ") 342 + ); 343 + progress.set_message("Scanning files..."); 344 + progress.enable_steady_tick(std::time::Duration::from_millis(100)); 345 + 346 + let (root_dir, total_files, reused_count) = build_directory(agent, &path, &existing_blob_map, String::new(), &ignore_matcher, &progress).await?; 330 347 let uploaded_count = total_files - reused_count; 348 + 349 + progress.finish_with_message(format!("✓ {} files ({} uploaded, {} reused)", total_files, uploaded_count, reused_count)); 331 350 332 351 // Check if we need to split into subfs records 333 352 const MAX_MANIFEST_SIZE: usize = 140 * 1024; // 140KB (PDS limit is 150KB) ··· 606 625 existing_blobs: &'a HashMap<String, (jacquard_common::types::blob::BlobRef<'static>, String)>, 607 626 current_path: String, 608 627 ignore_matcher: &'a ignore_patterns::IgnoreMatcher, 628 + progress: &'a ProgressBar, 609 629 ) -> std::pin::Pin<Box<dyn std::future::Future<Output = miette::Result<(Directory<'static>, usize, usize)>> + 'a>> 610 630 { 611 631 Box::pin(async move { ··· 653 673 } 654 674 } 655 675 656 - // Process files concurrently with a limit of 5 676 + // Process files concurrently with a limit of 2 657 677 let file_results: Vec<(Entry<'static>, bool)> = stream::iter(file_tasks) 658 678 .map(|(name, path, full_path)| async move { 659 - let (file_node, reused) = process_file(agent, &path, &full_path, existing_blobs).await?; 679 + let (file_node, reused) = process_file(agent, &path, &full_path, existing_blobs, progress).await?; 680 + progress.inc(1); 660 681 let entry = Entry::new() 661 682 .name(CowStr::from(name)) 662 683 .node(EntryNode::File(Box::new(file_node))) 663 684 .build(); 664 685 Ok::<_, miette::Report>((entry, reused)) 665 686 }) 666 - .buffer_unordered(5) 687 + .buffer_unordered(MAX_CONCURRENT_UPLOADS) 667 688 .collect::<Vec<_>>() 668 689 .await 669 690 .into_iter() ··· 690 711 } else { 691 712 format!("{}/{}", current_path, name) 692 713 }; 693 - let (subdir, sub_total, sub_reused) = build_directory(agent, &path, existing_blobs, subdir_path, ignore_matcher).await?; 714 + let (subdir, sub_total, sub_reused) = build_directory(agent, &path, existing_blobs, subdir_path, ignore_matcher, progress).await?; 694 715 dir_entries.push(Entry::new() 695 716 .name(CowStr::from(name)) 696 717 .node(EntryNode::Directory(Box::new(subdir))) ··· 722 743 file_path: &Path, 723 744 file_path_key: &str, 724 745 existing_blobs: &HashMap<String, (jacquard_common::types::blob::BlobRef<'static>, String)>, 746 + progress: &ProgressBar, 725 747 ) -> miette::Result<(File<'static>, bool)> 726 748 { 727 749 // Read file ··· 761 783 if let Some((existing_blob_ref, existing_cid)) = existing_blob { 762 784 if existing_cid == &file_cid { 763 785 // CIDs match - reuse existing blob 764 - println!(" ✓ Reusing blob for {} (CID: {})", file_path_key, file_cid); 786 + progress.set_message(format!("✓ Reused {}", file_path_key)); 765 787 let mut file_builder = File::new() 766 788 .r#type(CowStr::from("file")) 767 789 .blob(existing_blob_ref.clone()) ··· 775 797 } 776 798 777 799 return Ok((file_builder.build(), true)); 778 - } else { 779 - // CID mismatch - file changed 780 - println!(" → File changed: {} (old CID: {}, new CID: {})", file_path_key, existing_cid, file_cid); 781 - } 782 - } else { 783 - // File not in existing blob map 784 - if file_path_key.starts_with("imgs/") { 785 - println!(" → New file (not in blob map): {}", file_path_key); 786 800 } 787 801 } 788 802 ··· 793 807 MimeType::new_static("application/octet-stream") 794 808 }; 795 809 796 - println!(" ↑ Uploading {} ({} bytes, CID: {})", file_path_key, upload_bytes.len(), file_cid); 810 + // Format file size nicely 811 + let size_str = if upload_bytes.len() < 1024 { 812 + format!("{} B", upload_bytes.len()) 813 + } else if upload_bytes.len() < 1024 * 1024 { 814 + format!("{:.1} KB", upload_bytes.len() as f64 / 1024.0) 815 + } else { 816 + format!("{:.1} MB", upload_bytes.len() as f64 / (1024.0 * 1024.0)) 817 + }; 818 + 819 + progress.set_message(format!("↑ Uploading {} ({})", file_path_key, size_str)); 797 820 let blob = agent.upload_blob(upload_bytes, mime_type).await?; 821 + progress.set_message(format!("✓ Uploaded {}", file_path_key)); 798 822 799 823 let mut file_builder = File::new() 800 824 .r#type(CowStr::from("file"))
+1
package.json
··· 20 20 "build": "cd apps/main-app && bun run build.ts", 21 21 "build:hosting": "cd apps/hosting-service && npm run build", 22 22 "build:all": "bun run build && npm run build:hosting", 23 + "check": "cd apps/main-app && npm run check && cd ../hosting-service && npm run check", 23 24 "screenshot": "bun run apps/main-app/scripts/screenshot-sites.ts", 24 25 "hosting:dev": "cd apps/hosting-service && npm run dev", 25 26 "hosting:start": "cd apps/hosting-service && npm run start"
+3
packages/@wisp/atproto-utils/package.json
··· 27 27 "@atproto/api": "^0.14.1", 28 28 "@wisp/lexicons": "workspace:*", 29 29 "multiformats": "^13.3.1" 30 + }, 31 + "devDependencies": { 32 + "@atproto/lexicon": "^0.5.2" 30 33 } 31 34 }
+1 -1
packages/@wisp/constants/src/index.ts
··· 14 14 15 15 // File size limits 16 16 export const MAX_SITE_SIZE = 300 * 1024 * 1024; // 300MB 17 - export const MAX_FILE_SIZE = 100 * 1024 * 1024; // 100MB 17 + export const MAX_FILE_SIZE = 200 * 1024 * 1024; // 200MB 18 18 export const MAX_FILE_COUNT = 1000; 19 19 20 20 // Cache configuration
+2 -1
packages/@wisp/lexicons/package.json
··· 39 39 "@atproto/xrpc-server": "^0.9.5" 40 40 }, 41 41 "devDependencies": { 42 - "@atproto/lex-cli": "^0.9.5" 42 + "@atproto/lex-cli": "^0.9.5", 43 + "multiformats": "^13.4.1" 43 44 } 44 45 }