feat: add support for remaining leaflet blocks (#12)

* add horizontal rule lexicon

* add horizontal rule tag to block converter code

* add unordered lists

* fix formatting issues with lists

* add code and math blocks

* massive refactor

* explicitly allow specific tags and attributes

* fix test that was changed for some reason

* add changeset

authored by Dane Miller and committed by GitHub 6d70cc64 44ba46f9

+13
.changeset/plain-beans-taste.md
··· 1 + --- 2 + "@nulfrost/leaflet-loader-astro": minor 3 + --- 4 + 5 + Added support for these leaflet blocks: 6 + 7 + - ul/li 8 + - math 9 + - code 10 + - img 11 + - hr 12 + 13 + the only remaining block to implement is "website", though I haven't thought of a good way to output that yet. stay tuned for a further release
+1 -1
lex.config.js
··· 2 2 3 3 export default defineLexiconConfig({ 4 4 files: ["lexicons/**/*.json"], 5 - outdir: "src/lexicons/", 5 + outdir: "lib/lexicons/", 6 6 });
+11
lexicons/pub/leaflet/blocks/horizontalRule.json
··· 1 + { 2 + "lexicon": 1, 3 + "id": "pub.leaflet.blocks.horizontalRule", 4 + "defs": { 5 + "main": { 6 + "type": "object", 7 + "required": [], 8 + "properties": {} 9 + } 10 + } 11 + }
+13 -15
lib/leaflet-live-loader.ts
··· 1 - import { Agent } from "@atproto/api"; 2 1 import { isDid } from "@atproto/did"; 3 2 import type { LiveLoader } from "astro/loaders"; 4 3 import type { ··· 17 16 resolveMiniDoc, 18 17 uriToRkey, 19 18 } from "./utils.js"; 19 + import { Client, simpleFetchHandler } from "@atcute/client"; 20 20 21 21 export function leafletLiveLoader( 22 22 options: LiveLeafletLoaderOptions, ··· 44 44 name: "leaflet-loader-astro", 45 45 loadCollection: async ({ filter }) => { 46 46 try { 47 - const pds_url = await resolveMiniDoc(repo); 48 - const agent = new Agent({ service: pds_url }); 47 + const { pds, did } = await resolveMiniDoc(repo); 48 + const handler = simpleFetchHandler({ service: pds }); 49 + const rpc = new Client({ handler }); 49 50 50 51 const { documents } = await getLeafletDocuments({ 51 - agent, 52 + rpc, 52 53 repo, 53 54 reverse: filter?.reverse, 54 55 cursor: filter?.cursor, ··· 67 68 }), 68 69 rendered: { 69 70 html: leafletBlocksToHTML({ 70 - id, 71 - uri: document.uri, 72 - cid: document.cid, 73 - value: document.value as unknown as LeafletDocumentRecord, 71 + record: document.value as unknown as LeafletDocumentRecord, 72 + did, 74 73 }), 75 74 }, 76 75 }; ··· 95 94 }; 96 95 } 97 96 try { 98 - const pds_url = await resolveMiniDoc(repo); 99 - const agent = new Agent({ service: pds_url }); 97 + const { pds, did } = await resolveMiniDoc(repo); 98 + const handler = simpleFetchHandler({ service: pds }); 99 + const rpc = new Client({ handler }); 100 100 const document = await getSingleLeafletDocument({ 101 - agent, 101 + rpc, 102 102 id: filter.id, 103 103 repo, 104 104 }); ··· 114 114 }), 115 115 rendered: { 116 116 html: leafletBlocksToHTML({ 117 - id: filter.id, 118 - uri: document.uri, 119 - cid, 120 - value: document.value as unknown as LeafletDocumentRecord, 117 + record: document.value as unknown as LeafletDocumentRecord, 118 + did, 121 119 }), 122 120 }, 123 121 };
+10 -11
lib/leaftlet-static-loader.ts
··· 1 - import { Agent } from "@atproto/api"; 1 + import { Client, simpleFetchHandler } from "@atcute/client"; 2 2 import { isDid } from "@atproto/did"; 3 3 import type { Loader, LoaderContext } from "astro/loaders"; 4 4 import { LeafletDocumentSchema } from "schema.js"; ··· 7 7 StaticLeafletLoaderOptions, 8 8 } from "types.js"; 9 9 import { 10 + getLeafletDocuments, 11 + leafletBlocksToHTML, 12 + leafletDocumentRecordToView, 10 13 LiveLoaderError, 11 14 resolveMiniDoc, 12 - getLeafletDocuments, 13 15 uriToRkey, 14 - leafletDocumentRecordToView, 15 - leafletBlocksToHTML, 16 16 } from "utils.js"; 17 17 18 18 export function leafletStaticLoader( ··· 43 43 }: LoaderContext) => { 44 44 try { 45 45 logger.info("fetching latest leaflet documents"); 46 - const pds_url = await resolveMiniDoc(repo); 47 - const agent = new Agent({ service: pds_url }); 46 + const { pds, did } = await resolveMiniDoc(repo); 47 + const handler = simpleFetchHandler({ service: pds }); 48 + const rpc = new Client({ handler }); 48 49 49 50 let cursor: string | undefined; 50 51 let count = 0; ··· 52 53 fetching: do { 53 54 const { documents, cursor: documentsCursor } = 54 55 await getLeafletDocuments({ 55 - agent, 56 + rpc, 56 57 repo, 57 58 cursor, 58 59 limit: 100, ··· 83 84 digest, 84 85 rendered: { 85 86 html: leafletBlocksToHTML({ 86 - id, 87 - uri: document.uri, 88 - cid: document.cid, 89 - value: document.value as unknown as LeafletDocumentRecord, 87 + record: document.value as unknown as LeafletDocumentRecord, 88 + did, 90 89 }), 91 90 }, 92 91 });
+1
lib/lexicons/index.ts
··· 1 1 export * as ComAtprotoRepoStrongRef from "./types/com/atproto/repo/strongRef.js"; 2 2 export * as PubLeafletBlocksCode from "./types/pub/leaflet/blocks/code.js"; 3 3 export * as PubLeafletBlocksHeader from "./types/pub/leaflet/blocks/header.js"; 4 + export * as PubLeafletBlocksHorizontalRule from "./types/pub/leaflet/blocks/horizontalRule.js"; 4 5 export * as PubLeafletBlocksImage from "./types/pub/leaflet/blocks/image.js"; 5 6 export * as PubLeafletBlocksMath from "./types/pub/leaflet/blocks/math.js"; 6 7 export * as PubLeafletBlocksText from "./types/pub/leaflet/blocks/text.js";
+16
lib/lexicons/types/pub/leaflet/blocks/horizontalRule.ts
··· 1 + import type {} from "@atcute/lexicons"; 2 + import * as v from "@atcute/lexicons/validations"; 3 + 4 + const _mainSchema = /*#__PURE__*/ v.object({ 5 + $type: /*#__PURE__*/ v.optional( 6 + /*#__PURE__*/ v.literal("pub.leaflet.blocks.horizontalRule"), 7 + ), 8 + }); 9 + 10 + type main$schematype = typeof _mainSchema; 11 + 12 + export interface mainSchema extends main$schematype {} 13 + 14 + export const mainSchema = _mainSchema as mainSchema; 15 + 16 + export interface Main extends v.InferInput<typeof mainSchema> {}
+1 -1
lib/lexicons/types/pub/leaflet/blocks/image.ts
··· 14 14 ), 15 15 alt: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.string()), 16 16 get aspectRatio() { 17 - return aspectRatioSchema; 17 + return _aspectRatioSchema; 18 18 }, 19 19 image: /*#__PURE__*/ v.blob(), 20 20 });
+7 -5
lib/types.ts
··· 1 - import type { Agent } from "@atproto/api"; 1 + import type { Client } from "@atcute/client"; 2 + import type { ActorIdentifier } from "@atcute/lexicons"; 3 + import type { XRPCProcedures, XRPCQueries } from "@atcute/lexicons/ambient"; 2 4 import type { PubLeafletRichtextFacet } from "./lexicons/index.js"; 3 5 4 6 export interface LiveLeafletLoaderOptions { ··· 58 60 } 59 61 60 62 export interface GetLeafletDocumentsParams { 61 - repo: string; 62 - agent: Agent; 63 + repo: ActorIdentifier; 64 + rpc: Client<XRPCQueries, XRPCProcedures>; 63 65 cursor?: string; 64 66 limit?: number; 65 67 reverse?: boolean; 66 68 } 67 69 68 70 export interface GetSingleLeafletDocumentParams { 69 - repo: string; 70 - agent: Agent; 71 + repo: ActorIdentifier; 72 + rpc: Client<XRPCQueries, XRPCProcedures>; 71 73 id: string; 72 74 } 73 75
+187 -107
lib/utils.ts
··· 1 + import type {} from "@atcute/atproto"; 1 2 import { is } from "@atcute/lexicons"; 2 3 import { AtUri, UnicodeString } from "@atproto/api"; 4 + import katex from "katex"; 3 5 import sanitizeHTML from "sanitize-html"; 4 6 import { 7 + PubLeafletBlocksCode, 5 8 PubLeafletBlocksHeader, 9 + PubLeafletBlocksHorizontalRule, 10 + PubLeafletBlocksImage, 11 + PubLeafletBlocksMath, 6 12 PubLeafletBlocksText, 13 + PubLeafletBlocksUnorderedList, 7 14 PubLeafletPagesLinearDocument, 8 15 } from "./lexicons/index.js"; 9 16 import type { ··· 29 36 export function uriToRkey(uri: string): string { 30 37 const u = AtUri.make(uri); 31 38 if (!u.rkey) { 32 - throw new Error("Failed to get rkey from uri."); 39 + throw new Error("failed to get rkey"); 33 40 } 34 41 return u.rkey; 35 42 } ··· 47 54 } 48 55 const data = (await response.json()) as MiniDoc; 49 56 50 - return data.pds; 57 + return { 58 + pds: data.pds, 59 + did: data.did, 60 + }; 51 61 } catch { 52 62 throw new Error(`failed to resolve handle: ${handleOrDid}`); 53 63 } ··· 57 67 repo, 58 68 reverse, 59 69 cursor, 60 - agent, 70 + rpc, 61 71 limit, 62 72 }: GetLeafletDocumentsParams) { 63 - const response = await agent.com.atproto.repo.listRecords({ 64 - repo, 65 - collection: "pub.leaflet.document", 66 - cursor, 67 - reverse, 68 - limit, 73 + const { ok, data } = await rpc.get("com.atproto.repo.listRecords", { 74 + params: { 75 + collection: "pub.leaflet.document", 76 + cursor, 77 + reverse, 78 + limit, 79 + repo, 80 + }, 69 81 }); 70 82 71 - if (response.success === false) { 83 + if (!ok) { 72 84 throw new LiveLoaderError( 73 85 "error fetching leaflet documents", 74 86 "DOCUMENT_FETCH_ERROR", ··· 76 88 } 77 89 78 90 return { 79 - documents: response?.data?.records, 80 - cursor: response?.data?.cursor, 91 + documents: data?.records, 92 + cursor: data?.cursor, 81 93 }; 82 94 } 83 95 84 96 export async function getSingleLeafletDocument({ 85 - agent, 97 + rpc, 86 98 repo, 87 99 id, 88 100 }: GetSingleLeafletDocumentParams) { 89 - const response = await agent.com.atproto.repo.getRecord({ 90 - repo, 91 - collection: "pub.leaflet.document", 92 - rkey: id, 101 + const { ok, data } = await rpc.get("com.atproto.repo.getRecord", { 102 + params: { 103 + collection: "pub.leaflet.document", 104 + repo, 105 + rkey: id, 106 + }, 93 107 }); 94 108 95 - if (response.success === false) { 109 + if (!ok) { 96 110 throw new LiveLoaderError( 97 111 "error fetching single document", 98 112 "DOCUMENT_FETCH_ERROR", 99 113 ); 100 114 } 101 115 102 - return response?.data; 116 + return data; 103 117 } 104 118 105 119 export function leafletDocumentRecordToView({ ··· 122 136 }; 123 137 } 124 138 125 - export function leafletBlocksToHTML(record: { 126 - id: string; 127 - uri: string; 128 - cid: string; 129 - value: LeafletDocumentRecord; 139 + export function leafletBlocksToHTML({ 140 + record, 141 + did, 142 + }: { 143 + record: LeafletDocumentRecord; 144 + did: string; 130 145 }) { 131 146 let html = ""; 132 - const firstPage = record.value.pages[0]; 147 + const firstPage = record.pages[0]; 133 148 let blocks: PubLeafletPagesLinearDocument.Block[] = []; 149 + 134 150 if (is(PubLeafletPagesLinearDocument.mainSchema, firstPage)) { 135 151 blocks = firstPage.blocks || []; 136 152 } 137 153 138 154 for (const block of blocks) { 139 - if (is(PubLeafletBlocksText.mainSchema, block.block)) { 140 - const rt = new RichText({ 141 - text: block.block.plaintext, 142 - facets: block.block.facets || [], 143 - }); 144 - const children = []; 145 - for (const segment of rt.segments()) { 146 - const link = segment.facet?.find( 147 - (segment) => segment.$type === "pub.leaflet.richtext.facet#link", 148 - ); 149 - const isBold = segment.facet?.find( 150 - (segment) => segment.$type === "pub.leaflet.richtext.facet#bold", 151 - ); 152 - const isCode = segment.facet?.find( 153 - (segment) => segment.$type === "pub.leaflet.richtext.facet#code", 154 - ); 155 - const isStrikethrough = segment.facet?.find( 156 - (segment) => 157 - segment.$type === "pub.leaflet.richtext.facet#strikethrough", 158 - ); 159 - const isUnderline = segment.facet?.find( 160 - (segment) => segment.$type === "pub.leaflet.richtext.facet#underline", 161 - ); 162 - const isItalic = segment.facet?.find( 163 - (segment) => segment.$type === "pub.leaflet.richtext.facet#italic", 164 - ); 165 - if (isCode) { 166 - children.push(` <code> 167 - ${segment.text} 168 - </code>`); 169 - } else if (link) { 170 - children.push( 171 - ` <a 172 - href="${link.uri}" 173 - target="_blank" 174 - > 175 - ${segment.text} 176 - </a>`, 177 - ); 178 - } else if (isBold) { 179 - children.push(`<b>${segment.text}</b>`); 180 - } else if (isStrikethrough) { 181 - children.push(`<s>${segment.text}</s>`); 182 - } else if (isUnderline) { 183 - children.push( 184 - `<span style="text-decoration:underline;">${segment.text}</span>`, 185 - ); 186 - } else if (isItalic) { 187 - children.push(`<i>${segment.text}</i>`); 188 - } else { 189 - children.push( 190 - ` 191 - ${segment.text} 192 - `, 193 - ); 194 - } 195 - } 196 - html += `<p>${children.join("\n")}</p>`; 197 - } 198 - 199 - if (is(PubLeafletBlocksHeader.mainSchema, block.block)) { 200 - if (block.block.level === 1) { 201 - html += `<h2>${block.block.plaintext}</h2>`; 202 - } 203 - } 204 - if (is(PubLeafletBlocksHeader.mainSchema, block.block)) { 205 - if (block.block.level === 2) { 206 - html += `<h3>${block.block.plaintext}</h3>`; 207 - } 208 - } 209 - if (is(PubLeafletBlocksHeader.mainSchema, block.block)) { 210 - if (block.block.level === 3) { 211 - html += `<h4>${block.block.plaintext}</h4>`; 212 - } 213 - } 214 - if (is(PubLeafletBlocksHeader.mainSchema, block.block)) { 215 - if (!block.block.level) { 216 - html += `<h6>${block.block.plaintext}</h6>`; 217 - } 218 - } 155 + html += parseBlocks({ block, did }); 219 156 } 220 157 221 - return sanitizeHTML(html); 158 + return sanitizeHTML(html, { 159 + allowedAttributes: { 160 + "*": ["class", "style"], 161 + img: ["src", "height", "width", "alt"], 162 + a: ["href", "target", "rel"], 163 + }, 164 + allowedTags: [ 165 + "img", 166 + "pre", 167 + "code", 168 + "p", 169 + "a", 170 + "b", 171 + "s", 172 + "ul", 173 + "li", 174 + "i", 175 + "h1", 176 + "h2", 177 + "h3", 178 + "h4", 179 + "h5", 180 + "h6", 181 + "hr", 182 + "div", 183 + "span", 184 + ], 185 + selfClosing: ["img"], 186 + }); 222 187 } 223 188 224 189 export class RichText { 225 190 unicodeText: UnicodeString; 226 191 facets?: Facet[]; 227 - 228 192 constructor(props: { text: string; facets: Facet[] }) { 229 193 this.unicodeText = new UnicodeString(props.text); 230 194 this.facets = props.facets; ··· 278 242 } 279 243 } 280 244 } 245 + 246 + function parseBlocks({ 247 + block, 248 + did, 249 + }: { 250 + block: PubLeafletPagesLinearDocument.Block; 251 + did: string; 252 + }): string { 253 + let html = ""; 254 + 255 + if (is(PubLeafletBlocksText.mainSchema, block.block)) { 256 + const rt = new RichText({ 257 + text: block.block.plaintext, 258 + facets: block.block.facets || [], 259 + }); 260 + const children = []; 261 + for (const segment of rt.segments()) { 262 + const link = segment.facet?.find( 263 + (segment) => segment.$type === "pub.leaflet.richtext.facet#link", 264 + ); 265 + const isBold = segment.facet?.find( 266 + (segment) => segment.$type === "pub.leaflet.richtext.facet#bold", 267 + ); 268 + const isCode = segment.facet?.find( 269 + (segment) => segment.$type === "pub.leaflet.richtext.facet#code", 270 + ); 271 + const isStrikethrough = segment.facet?.find( 272 + (segment) => 273 + segment.$type === "pub.leaflet.richtext.facet#strikethrough", 274 + ); 275 + const isUnderline = segment.facet?.find( 276 + (segment) => segment.$type === "pub.leaflet.richtext.facet#underline", 277 + ); 278 + const isItalic = segment.facet?.find( 279 + (segment) => segment.$type === "pub.leaflet.richtext.facet#italic", 280 + ); 281 + if (isCode) { 282 + children.push(`<pre><code>${segment.text}</code></pre>`); 283 + } else if (link) { 284 + children.push( 285 + `<a href="${link.uri}" target="_blank" rel="noopener noreferrer">${segment.text}</a>`, 286 + ); 287 + } else if (isBold) { 288 + children.push(`<b>${segment.text}</b>`); 289 + } else if (isStrikethrough) { 290 + children.push(`<s>${segment.text}</s>`); 291 + } else if (isUnderline) { 292 + children.push( 293 + `<span style="text-decoration:underline;">${segment.text}</span>`, 294 + ); 295 + } else if (isItalic) { 296 + children.push(`<i>${segment.text}</i>`); 297 + } else { 298 + children.push(`${segment.text}`); 299 + } 300 + } 301 + html += `<p>${children.join("")}</p>`; 302 + } 303 + 304 + if (is(PubLeafletBlocksHeader.mainSchema, block.block)) { 305 + if (block.block.level === 1) { 306 + html += `<h2>${block.block.plaintext}</h2>`; 307 + } 308 + } 309 + if (is(PubLeafletBlocksHeader.mainSchema, block.block)) { 310 + if (block.block.level === 2) { 311 + html += `<h3>${block.block.plaintext}</h3>`; 312 + } 313 + } 314 + if (is(PubLeafletBlocksHeader.mainSchema, block.block)) { 315 + if (block.block.level === 3) { 316 + html += `<h4>${block.block.plaintext}</h4>`; 317 + } 318 + } 319 + if (is(PubLeafletBlocksHeader.mainSchema, block.block)) { 320 + if (!block.block.level) { 321 + html += `<h6>${block.block.plaintext}</h6>`; 322 + } 323 + } 324 + 325 + if (is(PubLeafletBlocksHorizontalRule.mainSchema, block.block)) { 326 + html += `<hr />`; 327 + } 328 + if (is(PubLeafletBlocksUnorderedList.mainSchema, block.block)) { 329 + html += `<ul>${block.block.children.map((child) => renderListItem({ item: child, did })).join("")}</ul>`; 330 + } 331 + 332 + if (is(PubLeafletBlocksMath.mainSchema, block.block)) { 333 + html += `<div>${katex.renderToString(block.block.tex, { displayMode: true, output: "html", throwOnError: false })}</div>`; 334 + } 335 + 336 + if (is(PubLeafletBlocksCode.mainSchema, block.block)) { 337 + html += `<pre><code data-language=${block.block.language}>${block.block.plaintext}</code></pre>`; 338 + } 339 + 340 + if (is(PubLeafletBlocksImage.mainSchema, block.block)) { 341 + // @ts-ignore 342 + html += `<div><img src="https://cdn.bsky.app/img/feed_fullsize/plain/${did}/${block.block.image.ref.$link}@jpeg" height="${block.block.aspectRatio.height}" width="${block.block.aspectRatio.width}" alt="${block.block.alt}" /></div>`; 343 + } 344 + 345 + return html.trim(); 346 + } 347 + 348 + function renderListItem({ 349 + item, 350 + did, 351 + }: { 352 + item: PubLeafletBlocksUnorderedList.ListItem; 353 + did: string; 354 + }): string { 355 + const children: string | null = item.children?.length 356 + ? `<ul>${item.children.map((child) => renderListItem({ item: child, did }))}</ul>` 357 + : ""; 358 + 359 + return `<li>${parseBlocks({ block: { block: item.content }, did })}${children}</li>`; 360 + }
+6 -2
package.json
··· 25 25 "typecheck": "tsc", 26 26 "release": "pnpm run build && changeset publish", 27 27 "build": "rm -rf dist && tsup --format esm --dts", 28 - "pack": "pnpm build && pnpm pack" 28 + "pack": "rm -rf *.tgz && pnpm build && pnpm pack" 29 29 }, 30 30 "license": "MIT", 31 31 "files": [ ··· 45 45 } 46 46 }, 47 47 "devDependencies": { 48 + "@atcute/atproto": "^3.1.1", 48 49 "@atcute/lex-cli": "^2.1.1", 49 50 "@biomejs/biome": "2.1.3", 50 51 "@changesets/cli": "^2.29.5", 52 + "@types/bun": "^1.2.19", 51 53 "@types/sanitize-html": "^2.16.0", 52 54 "astro": "^5.12.8", 53 55 "tsup": "^8.5.0", ··· 55 57 "vitest": "^3.2.4" 56 58 }, 57 59 "dependencies": { 60 + "@atcute/client": "^4.0.3", 58 61 "@atcute/lexicons": "^1.1.0", 59 - "@atproto/api": "^0.16.1", 62 + "@atproto/api": "^0.16.2", 60 63 "@atproto/did": "^0.1.5", 64 + "katex": "^0.16.22", 61 65 "sanitize-html": "^2.17.0" 62 66 } 63 67 }
+118 -38
pnpm-lock.yaml
··· 8 8 9 9 .: 10 10 dependencies: 11 + '@atcute/client': 12 + specifier: ^4.0.3 13 + version: 4.0.3 11 14 '@atcute/lexicons': 12 15 specifier: ^1.1.0 13 16 version: 1.1.0 14 17 '@atproto/api': 15 - specifier: ^0.16.1 16 - version: 0.16.1 18 + specifier: ^0.16.2 19 + version: 0.16.2 17 20 '@atproto/did': 18 21 specifier: ^0.1.5 19 22 version: 0.1.5 23 + katex: 24 + specifier: ^0.16.22 25 + version: 0.16.22 20 26 sanitize-html: 21 27 specifier: ^2.17.0 22 28 version: 2.17.0 23 29 devDependencies: 30 + '@atcute/atproto': 31 + specifier: ^3.1.1 32 + version: 3.1.1 24 33 '@atcute/lex-cli': 25 34 specifier: ^2.1.1 26 35 version: 2.1.1 ··· 30 39 '@changesets/cli': 31 40 specifier: ^2.29.5 32 41 version: 2.29.5 42 + '@types/bun': 43 + specifier: ^1.2.19 44 + version: 1.2.19(@types/react@19.1.9) 33 45 '@types/sanitize-html': 34 46 specifier: ^2.16.0 35 47 version: 2.16.0 36 48 astro: 37 49 specifier: ^5.12.8 38 - version: 5.12.8(@types/node@24.2.0)(rollup@4.46.2)(typescript@5.9.2) 50 + version: 5.12.9(@types/node@24.2.1)(rollup@4.46.2)(typescript@5.9.2) 39 51 tsup: 40 52 specifier: ^8.5.0 41 53 version: 8.5.0(postcss@8.5.6)(typescript@5.9.2) ··· 44 56 version: 5.9.2 45 57 vitest: 46 58 specifier: ^3.2.4 47 - version: 3.2.4(@types/debug@4.1.12)(@types/node@24.2.0) 59 + version: 3.2.4(@types/debug@4.1.12)(@types/node@24.2.1) 48 60 49 61 packages: 50 62 ··· 65 77 resolution: {integrity: sha512-UFBgfeldP06qu6khs/yY+q1cDAaArM2/7AEIqQ9Cuvf7B1hNLq0xDrZkct+QoIGyjq56y8IaE2I3CTvG99mlhQ==} 66 78 engines: {node: 18.20.8 || ^20.3.0 || >=22.0.0} 67 79 80 + '@atcute/atproto@3.1.1': 81 + resolution: {integrity: sha512-D+RLTIPF0xLu7BPZY8KSewAPemJFh+3n3zeQ3ROsLxbTtCHbrTDMAmAFexaVRAPGcPYrwXaBUlv7yZjScJolMg==} 82 + 83 + '@atcute/client@4.0.3': 84 + resolution: {integrity: sha512-RIOZWFVLca/HiPAAUDqQPOdOreCxTbL5cb+WUf5yqQOKIu5yEAP3eksinmlLmgIrlr5qVOE7brazUUzaskFCfw==} 85 + 86 + '@atcute/identity@1.0.3': 87 + resolution: {integrity: sha512-mNMxbKHFGys03A8JXKk0KfMBzdd0vrYMzZZWjpw1nYTs0+ea6bo5S1hwqVUZxHdo1gFHSe/t63jxQIF4yL9aKw==} 88 + 68 89 '@atcute/lex-cli@2.1.1': 69 90 resolution: {integrity: sha512-QaR0sOP8Z24opGHKsSfleDbP/ahUb6HECkVaOqSwG7ORZzbLK1w0265o1BRjCVr2dT6FxlsMUa2Ge85JMA9bxg==} 70 91 hasBin: true ··· 75 96 '@atcute/lexicons@1.1.0': 76 97 resolution: {integrity: sha512-LFqwnria78xLYb62Ri/+WwQpUTgZp2DuyolNGIIOV1dpiKhFFFh//nscHMA6IExFLQRqWDs3tTjy7zv0h3sf1Q==} 77 98 78 - '@atproto/api@0.16.1': 79 - resolution: {integrity: sha512-w48BlTmzKym7nZETWxgiuUX/wwRXU3xsLLKORWo/xtGnwlvpchUFnHKI3k4ttYJ2/JQE59+/4C16BaLzDyiU2w==} 99 + '@atproto/api@0.16.2': 100 + resolution: {integrity: sha512-sSTg31J8ws8DNaoiizp+/uJideRxRaJsq+Nyl8rnSxGw0w3oCvoeRU19iRWh2t0jZEmiRJAGkveGu23NKmPYEQ==} 80 101 81 102 '@atproto/common-web@0.4.2': 82 103 resolution: {integrity: sha512-vrXwGNoFGogodjQvJDxAeP3QbGtawgZute2ed1XdRO0wMixLk3qewtikZm06H259QDJVu6voKC5mubml+WgQUw==} ··· 673 694 '@swc/helpers@0.5.17': 674 695 resolution: {integrity: sha512-5IKx/Y13RsYd+sauPb2x+U/xZikHjolzfuDgTAl/Tdf3Q8rslRvC19NKDLgAJQ6wsqADk10ntlv08nPFw/gO/A==} 675 696 697 + '@types/bun@1.2.19': 698 + resolution: {integrity: sha512-d9ZCmrH3CJ2uYKXQIUuZ/pUnTqIvLDS0SK7pFmbx8ma+ziH/FRMoAq5bYpRG7y+w1gl+HgyNZbtqgMq4W4e2Lg==} 699 + 676 700 '@types/chai@5.2.2': 677 701 resolution: {integrity: sha512-8kB30R7Hwqf40JPiKhVzodJs2Qc1ZJ5zuT3uzw5Hq/dhNCl3G3l83jfpdI1e20BP348+fV7VIL/+FxaXkqBmWg==} 678 702 ··· 703 727 '@types/node@12.20.55': 704 728 resolution: {integrity: sha512-J8xLz7q2OFulZ2cyGTLE1TbbZcjpno7FaN6zdJNrgAdrJ+DZzh/uFR6YrTb4C+nXakvud8Q4+rbhoIWlYQbUFQ==} 705 729 706 - '@types/node@24.2.0': 707 - resolution: {integrity: sha512-3xyG3pMCq3oYCNg7/ZP+E1ooTaGB4cG8JWRsqqOYQdbWNY4zbaV0Ennrd7stjiJEFZCaybcIgpTjJWHRfBSIDw==} 730 + '@types/node@24.2.1': 731 + resolution: {integrity: sha512-DRh5K+ka5eJic8CjH7td8QpYEV6Zo10gfRkjHCO3weqZHWDtAaSTFtl4+VMqOJ4N5jcuhZ9/l+yy8rVgw7BQeQ==} 732 + 733 + '@types/react@19.1.9': 734 + resolution: {integrity: sha512-WmdoynAX8Stew/36uTSVMcLJJ1KRh6L3IZRx1PZ7qJtBqT3dYTgyDTx8H1qoRghErydW7xw9mSJ3wS//tCRpFA==} 708 735 709 736 '@types/sanitize-html@2.16.0': 710 737 resolution: {integrity: sha512-l6rX1MUXje5ztPT0cAFtUayXF06DqPhRyfVXareEN5gGCFaP/iwsxIyKODr9XDhfxPpN6vXUFNfo5kZMXCxBtw==} ··· 800 827 resolution: {integrity: sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==} 801 828 engines: {node: '>=12'} 802 829 803 - astro@5.12.8: 804 - resolution: {integrity: sha512-KkJ7FR+c2SyZYlpakm48XBiuQcRsrVtdjG5LN5an0givI/tLik+ePJ4/g3qrAVhYMjJOxBA2YgFQxANPiWB+Mw==} 830 + astro@5.12.9: 831 + resolution: {integrity: sha512-cZ7kZ61jyE5nwSrFKSRyf5Gds+uJELqQxJFqMkcgiWQvhWZJUSShn8Uz3yc9WLyLw5Kim5P5un9SkJSGogfEZQ==} 805 832 engines: {node: 18.20.8 || ^20.3.0 || >=22.0.0, npm: '>=9.6.5', pnpm: '>=7.1.0'} 806 833 hasBin: true 807 834 ··· 845 872 brotli@1.3.3: 846 873 resolution: {integrity: sha512-oTKjJdShmDuGW94SyyaoQvAjf30dZaHnjJ8uAF+u2/vGJkJbJPJAT1gDiOJP5v1Zb6f9KEyW/1HpuaWIXtGHPg==} 847 874 875 + bun-types@1.2.19: 876 + resolution: {integrity: sha512-uAOTaZSPuYsWIXRpj7o56Let0g/wjihKCkeRqUBhlLVM/Bt+Fj9xTo+LhC1OV1XDaGkz4hNC80et5xgy+9KTHQ==} 877 + peerDependencies: 878 + '@types/react': ^19 879 + 848 880 bundle-require@5.1.0: 849 881 resolution: {integrity: sha512-3WrrOuZiyaaZPWiEt4G3+IffISVC9HYlWueJEBWED4ZH4aIAC2PnkdnuRrR94M+w6yGWn4AglWtJtBI8YqvgoA==} 850 882 engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} ··· 931 963 resolution: {integrity: sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==} 932 964 engines: {node: '>= 6'} 933 965 966 + commander@8.3.0: 967 + resolution: {integrity: sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==} 968 + engines: {node: '>= 12'} 969 + 934 970 common-ancestor-path@1.0.1: 935 971 resolution: {integrity: sha512-L3sHRo1pXXEqX8VU28kfgUY+YGsk09hPqZiZmLacNib6XNTCM8ubYeT7ryXQw8asB1sKgcU5lkB7ONug08aB8w==} 936 972 ··· 966 1002 resolution: {integrity: sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==} 967 1003 engines: {node: '>=4'} 968 1004 hasBin: true 1005 + 1006 + csstype@3.1.3: 1007 + resolution: {integrity: sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==} 969 1008 970 1009 debug@4.4.1: 971 1010 resolution: {integrity: sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==} ··· 1337 1376 jsonfile@4.0.0: 1338 1377 resolution: {integrity: sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==} 1339 1378 1379 + katex@0.16.22: 1380 + resolution: {integrity: sha512-XCHRdUw4lf3SKBaJe4EvgqIuWwkPSo9XoeO8GjQW94Bp7TWv9hNhzZjZ+OH9yf1UmLygb7DIT5GSFQiyt16zYg==} 1381 + hasBin: true 1382 + 1340 1383 kleur@3.0.3: 1341 1384 resolution: {integrity: sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==} 1342 1385 engines: {node: '>=6'} ··· 1876 1919 resolution: {integrity: sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==} 1877 1920 engines: {node: '>=8'} 1878 1921 1879 - smol-toml@1.4.1: 1880 - resolution: {integrity: sha512-CxdwHXyYTONGHThDbq5XdwbFsuY4wlClRGejfE2NtwUtiHYsP1QtNsHb/hnj31jKYSchztJsaA8pSQoVzkfCFg==} 1922 + smol-toml@1.4.2: 1923 + resolution: {integrity: sha512-rInDH6lCNiEyn3+hH8KVGFdbjc099j47+OSgbMrfDYX1CmXLfdKd7qi6IfcWj2wFxvSVkuI46M+wPGYfEOEj6g==} 1881 1924 engines: {node: '>= 18'} 1882 1925 1883 1926 source-map-js@1.2.1: ··· 2218 2261 yaml: 2219 2262 optional: true 2220 2263 2221 - vite@7.0.6: 2222 - resolution: {integrity: sha512-MHFiOENNBd+Bd9uvc8GEsIzdkn1JxMmEeYX35tI3fv0sJBUTfW5tQsoaOwuY4KhBI09A3dUJ/DXf2yxPVPUceg==} 2264 + vite@7.1.1: 2265 + resolution: {integrity: sha512-yJ+Mp7OyV+4S+afWo+QyoL9jFWD11QFH0i5i7JypnfTcA1rmgxCbiA8WwAICDEtZ1Z1hzrVhN8R8rGTqkTY8ZQ==} 2223 2266 engines: {node: ^20.19.0 || >=22.12.0} 2224 2267 hasBin: true 2225 2268 peerDependencies: ··· 2398 2441 remark-rehype: 11.1.2 2399 2442 remark-smartypants: 3.0.2 2400 2443 shiki: 3.9.2 2401 - smol-toml: 1.4.1 2444 + smol-toml: 1.4.2 2402 2445 unified: 11.0.5 2403 2446 unist-util-remove-position: 5.0.0 2404 2447 unist-util-visit: 5.0.0 ··· 2423 2466 transitivePeerDependencies: 2424 2467 - supports-color 2425 2468 2469 + '@atcute/atproto@3.1.1': 2470 + dependencies: 2471 + '@atcute/lexicons': 1.1.0 2472 + 2473 + '@atcute/client@4.0.3': 2474 + dependencies: 2475 + '@atcute/identity': 1.0.3 2476 + '@atcute/lexicons': 1.1.0 2477 + 2478 + '@atcute/identity@1.0.3': 2479 + dependencies: 2480 + '@atcute/lexicons': 1.1.0 2481 + '@badrap/valita': 0.4.6 2482 + 2426 2483 '@atcute/lex-cli@2.1.1': 2427 2484 dependencies: 2428 2485 '@atcute/lexicon-doc': 1.0.3 ··· 2439 2496 dependencies: 2440 2497 esm-env: 1.2.2 2441 2498 2442 - '@atproto/api@0.16.1': 2499 + '@atproto/api@0.16.2': 2443 2500 dependencies: 2444 2501 '@atproto/common-web': 0.4.2 2445 2502 '@atproto/lexicon': 0.4.12 ··· 3001 3058 dependencies: 3002 3059 tslib: 2.8.1 3003 3060 3061 + '@types/bun@1.2.19(@types/react@19.1.9)': 3062 + dependencies: 3063 + bun-types: 1.2.19(@types/react@19.1.9) 3064 + transitivePeerDependencies: 3065 + - '@types/react' 3066 + 3004 3067 '@types/chai@5.2.2': 3005 3068 dependencies: 3006 3069 '@types/deep-eql': 4.0.2 ··· 3015 3078 3016 3079 '@types/fontkit@2.0.8': 3017 3080 dependencies: 3018 - '@types/node': 24.2.0 3081 + '@types/node': 24.2.1 3019 3082 3020 3083 '@types/hast@3.0.4': 3021 3084 dependencies: ··· 3033 3096 3034 3097 '@types/node@12.20.55': {} 3035 3098 3036 - '@types/node@24.2.0': 3099 + '@types/node@24.2.1': 3037 3100 dependencies: 3038 3101 undici-types: 7.10.0 3102 + 3103 + '@types/react@19.1.9': 3104 + dependencies: 3105 + csstype: 3.1.3 3039 3106 3040 3107 '@types/sanitize-html@2.16.0': 3041 3108 dependencies: ··· 3053 3120 chai: 5.2.1 3054 3121 tinyrainbow: 2.0.0 3055 3122 3056 - '@vitest/mocker@3.2.4(vite@7.0.6(@types/node@24.2.0))': 3123 + '@vitest/mocker@3.2.4(vite@7.1.1(@types/node@24.2.1))': 3057 3124 dependencies: 3058 3125 '@vitest/spy': 3.2.4 3059 3126 estree-walker: 3.0.3 3060 3127 magic-string: 0.30.17 3061 3128 optionalDependencies: 3062 - vite: 7.0.6(@types/node@24.2.0) 3129 + vite: 7.1.1(@types/node@24.2.1) 3063 3130 3064 3131 '@vitest/pretty-format@3.2.4': 3065 3132 dependencies: ··· 3126 3193 3127 3194 assertion-error@2.0.1: {} 3128 3195 3129 - astro@5.12.8(@types/node@24.2.0)(rollup@4.46.2)(typescript@5.9.2): 3196 + astro@5.12.9(@types/node@24.2.1)(rollup@4.46.2)(typescript@5.9.2): 3130 3197 dependencies: 3131 3198 '@astrojs/compiler': 2.12.2 3132 3199 '@astrojs/internal-helpers': 0.7.1 ··· 3173 3240 rehype: 13.0.2 3174 3241 semver: 7.7.2 3175 3242 shiki: 3.9.2 3176 - smol-toml: 1.4.1 3243 + smol-toml: 1.4.2 3177 3244 tinyexec: 0.3.2 3178 3245 tinyglobby: 0.2.14 3179 3246 tsconfck: 3.1.6(typescript@5.9.2) ··· 3182 3249 unist-util-visit: 5.0.0 3183 3250 unstorage: 1.16.1 3184 3251 vfile: 6.0.3 3185 - vite: 6.3.5(@types/node@24.2.0) 3186 - vitefu: 1.1.1(vite@6.3.5(@types/node@24.2.0)) 3252 + vite: 6.3.5(@types/node@24.2.1) 3253 + vitefu: 1.1.1(vite@6.3.5(@types/node@24.2.1)) 3187 3254 xxhash-wasm: 1.1.0 3188 3255 yargs-parser: 21.1.1 3189 3256 yocto-spinner: 0.2.3 ··· 3268 3335 dependencies: 3269 3336 base64-js: 1.5.1 3270 3337 3338 + bun-types@1.2.19(@types/react@19.1.9): 3339 + dependencies: 3340 + '@types/node': 24.2.1 3341 + '@types/react': 19.1.9 3342 + 3271 3343 bundle-require@5.1.0(esbuild@0.25.8): 3272 3344 dependencies: 3273 3345 esbuild: 0.25.8 ··· 3335 3407 3336 3408 commander@4.1.1: {} 3337 3409 3410 + commander@8.3.0: {} 3411 + 3338 3412 common-ancestor-path@1.0.1: {} 3339 3413 3340 3414 confbox@0.1.8: {} ··· 3367 3441 source-map-js: 1.2.1 3368 3442 3369 3443 cssesc@3.0.0: {} 3444 + 3445 + csstype@3.1.3: {} 3370 3446 3371 3447 debug@4.4.1: 3372 3448 dependencies: ··· 3793 3869 optionalDependencies: 3794 3870 graceful-fs: 4.2.11 3795 3871 3872 + katex@0.16.22: 3873 + dependencies: 3874 + commander: 8.3.0 3875 + 3796 3876 kleur@3.0.3: {} 3797 3877 3798 3878 kleur@4.1.5: {} ··· 4550 4630 4551 4631 slash@3.0.0: {} 4552 4632 4553 - smol-toml@1.4.1: {} 4633 + smol-toml@1.4.2: {} 4554 4634 4555 4635 source-map-js@1.2.1: {} 4556 4636 ··· 4815 4895 '@types/unist': 3.0.3 4816 4896 vfile-message: 4.0.3 4817 4897 4818 - vite-node@3.2.4(@types/node@24.2.0): 4898 + vite-node@3.2.4(@types/node@24.2.1): 4819 4899 dependencies: 4820 4900 cac: 6.7.14 4821 4901 debug: 4.4.1 4822 4902 es-module-lexer: 1.7.0 4823 4903 pathe: 2.0.3 4824 - vite: 7.0.6(@types/node@24.2.0) 4904 + vite: 7.1.1(@types/node@24.2.1) 4825 4905 transitivePeerDependencies: 4826 4906 - '@types/node' 4827 4907 - jiti ··· 4836 4916 - tsx 4837 4917 - yaml 4838 4918 4839 - vite@6.3.5(@types/node@24.2.0): 4919 + vite@6.3.5(@types/node@24.2.1): 4840 4920 dependencies: 4841 4921 esbuild: 0.25.8 4842 4922 fdir: 6.4.6(picomatch@4.0.3) ··· 4845 4925 rollup: 4.46.2 4846 4926 tinyglobby: 0.2.14 4847 4927 optionalDependencies: 4848 - '@types/node': 24.2.0 4928 + '@types/node': 24.2.1 4849 4929 fsevents: 2.3.3 4850 4930 4851 - vite@7.0.6(@types/node@24.2.0): 4931 + vite@7.1.1(@types/node@24.2.1): 4852 4932 dependencies: 4853 4933 esbuild: 0.25.8 4854 4934 fdir: 6.4.6(picomatch@4.0.3) ··· 4857 4937 rollup: 4.46.2 4858 4938 tinyglobby: 0.2.14 4859 4939 optionalDependencies: 4860 - '@types/node': 24.2.0 4940 + '@types/node': 24.2.1 4861 4941 fsevents: 2.3.3 4862 4942 4863 - vitefu@1.1.1(vite@6.3.5(@types/node@24.2.0)): 4943 + vitefu@1.1.1(vite@6.3.5(@types/node@24.2.1)): 4864 4944 optionalDependencies: 4865 - vite: 6.3.5(@types/node@24.2.0) 4945 + vite: 6.3.5(@types/node@24.2.1) 4866 4946 4867 - vitest@3.2.4(@types/debug@4.1.12)(@types/node@24.2.0): 4947 + vitest@3.2.4(@types/debug@4.1.12)(@types/node@24.2.1): 4868 4948 dependencies: 4869 4949 '@types/chai': 5.2.2 4870 4950 '@vitest/expect': 3.2.4 4871 - '@vitest/mocker': 3.2.4(vite@7.0.6(@types/node@24.2.0)) 4951 + '@vitest/mocker': 3.2.4(vite@7.1.1(@types/node@24.2.1)) 4872 4952 '@vitest/pretty-format': 3.2.4 4873 4953 '@vitest/runner': 3.2.4 4874 4954 '@vitest/snapshot': 3.2.4 ··· 4886 4966 tinyglobby: 0.2.14 4887 4967 tinypool: 1.1.1 4888 4968 tinyrainbow: 2.0.0 4889 - vite: 7.0.6(@types/node@24.2.0) 4890 - vite-node: 3.2.4(@types/node@24.2.0) 4969 + vite: 7.1.1(@types/node@24.2.1) 4970 + vite-node: 3.2.4(@types/node@24.2.1) 4891 4971 why-is-node-running: 2.3.0 4892 4972 optionalDependencies: 4893 4973 '@types/debug': 4.1.12 4894 - '@types/node': 24.2.0 4974 + '@types/node': 24.2.1 4895 4975 transitivePeerDependencies: 4896 4976 - jiti 4897 4977 - less
-5
pnpm-workspace.yaml
··· 1 - ignoredBuiltDependencies: 2 - - sharp 3 - 4 - onlyBuiltDependencies: 5 - - esbuild