commit af417c5d7d15a8da58d1fb7cbdca7db45b83a332 Author: Silberengel Date: Tue Mar 17 17:22:31 2026 +0100 initial commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..d77b7de --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +node_modules +dist +integration-output diff --git a/README.md b/README.md new file mode 100644 index 0000000..0a5dcf7 --- /dev/null +++ b/README.md @@ -0,0 +1,75 @@ +# gc-parser-asciidoc + +Lightweight AsciiDoc parser (no asciidoctor dependency) and publication helpers for Nostr. Accepts Nostr events as JSON and returns HTML. + +- **Single events (30818, 30041)**: pass one event JSON → get HTML. +- **Publications (30040)**: pass one 30040 index event + section event JSONs → get one HTML page. + +**Size**: ~100 KB built output, zero runtime dependencies — about 80× smaller than `@asciidoctor/core` (~8.5 MB). + +## Features + +- **Headings** `=` … `======` with optional TOC +- **Nostr links** — full bech32 (`naddr1`, `npub1`, `nevent1`, `nprofile1`, `note1`) and `nostr:...` in text become links +- **#hashtags** — rendered as `#tag`. Style with a different color and no underline in your CSS, e.g. `.hashtag { color: var(--tag-color); text-decoration: none; }` +- **Wikilinks** `[[id]]`, `[[id|label]]` +- **URLs** — bare `https://...` and `link:url[text]`; backticked URLs stay plaintext +- **Inline** — *bold*, _italic_, `code`, ~~strikethrough~~, ~sub~, ^sup^ +- **Lists** — nested `*` / `**`, ordered `.` / `..`, mixed + +## Usage + +All APIs accept Nostr events as JSON: `{ kind, content, tags, pubkey?, id? }`. + +### Single events (30818, 30041) + +Pass one event (e.g. from a relay) and get HTML: + +```ts +import { renderEvent } from 'gc-parser-asciidoc'; + +const event = { kind: 30041, content: '= Section\n\nBody.', tags: [['title', 'Section']], pubkey: '...' }; +const html = renderEvent(event, { + wikilinkUrl: '/events?d={dtag}', + nostrBaseUrl: 'https://app.example.com/', + hashtagClass: 'hashtag', +}); +``` + +For raw content (no event object), use `renderEventContent(content, options)` or `parseAsciiDoc(content, options)`. + +### Publications (kind 30040) + +Pass the 30040 index event and an array of section events (30041, 30818); get one HTML page: + +```ts +import { renderPublicationFromEvents } from 'gc-parser-asciidoc'; + +const indexEvent = { kind: 30040, content: '', tags: [['title', 'My Book'], ['d', 'my-book'], ['a', '30041:...:ch1'], ['a', '30041:...:ch2']], pubkey: '...' }; +const sectionEvents = [ + { kind: 30041, content: '...', tags: [['title', 'Chapter 1'], ['d', 'ch1']], pubkey: '...' }, + { kind: 30041, content: '...', tags: [['title', 'Chapter 2'], ['d', 'ch2']], pubkey: '...' }, +]; + +const { html, tableOfContents, asciidoc } = renderPublicationFromEvents(indexEvent, sectionEvents, { toc: true }); +``` + +If you already have structured data (not raw events), use `renderPublication({ index, sections }, options)`. Helpers: `toPublicationIndexLike(event)` to get `PublicationIndexLike` from a 30040 event; `buildPublicationAsciiDoc(input)` for the combined AsciiDoc string. + +## Integration tests + +Integration tests render events from **fixture files** in `integration-fixtures/` (no relay required). They are skipped unless `RUN_INTEGRATION=1`: + +```bash +RUN_INTEGRATION=1 npm run test:integration +``` + +Rendered HTML is written to `integration-output/single-event.html` and `integration-output/publication.html` for inspection in a browser. + +To **download events and save/refresh the fixtures** (fetch from `wss://thecitadel.nostr1.com` and overwrite `integration-fixtures/*.json`): + +```bash +npm run test:integration:record +``` + +Commit the updated `integration-fixtures/*.json` so others can run the integration test without a relay. Requires `nostr-tools` (devDependency) only when recording. diff --git a/asciidoc_testdoc.adoc b/asciidoc_testdoc.adoc new file mode 100644 index 0000000..d9c3322 --- /dev/null +++ b/asciidoc_testdoc.adoc @@ -0,0 +1,519 @@ += AsciiDoc Test Document +Kismet Lee +2.9, October 31, 2021: Fall incarnation +:description: Test description +:author: Kismet Lee +:date: 2021-10-31 +:version: 2.9 +:status: Draft +:keywords: AsciiDoc, Test, Document +:category: Test +:language: English + +== Bullet list + +This is a test unordered list with mixed bullets: + +* First item with a number 2. in it +* Second item +* Third item +** Indented item +** Indented item +* Fourth item + +Another unordered list: + +* 1st item +* 2nd item +* third item containing _italic_ text +** indented item +** second indented item +* fourth item + +This is a test ordered list with indented items: + +. First item +. Second item +. Third item +.. Indented item +.. Indented item +. Fourth item + +Ordered list where everything has no number: + +. First item +. Second item +. Third item +. Fourth item + +This is a mixed list with indented items: + +. First item +. Second item +. Third item +* Indented item +* Indented item +. Fourth item + +This is another mixed list with indented items: + +* First item +* Second item +* Third item +. Indented item +. Indented item +* Fourth item + +== Headers + +=== Third-level header + +==== Fourth-level header + +===== Fifth-level header + +[discrete] +====== Sixth-level header + +This discrete header shouldn't become a section. It should just be rendered in the header style. + +== Media and Links + +=== Nostr address + +This should be ignored and rendered as plaintext: naddr1qvzqqqr4gupzplfq3m5v3u5r0q9f255fdeyz8nyac6lagss... + +This is also plaintext: npub1gv069uwhateverbunchofnumbers + +And this should stay plaintext, as it is just a prefix with no address: nostr: + +This nostr address should be ignored, since it is in a URL https://notanostraddress/nevent1qvzqqqqqqypzp382htsmu08k277ps40wqhnfm60st89h5pvjyutghq9cjasuh38qqythwumn8ghj7un9d3shjtnswf5k6ctv9ehx2ap0qqsysletg3lqnl4uy59xsj4rp9rgw67wg23l827f4uvn5ckn20fuxcq45d8pj + +These should be turned into links: + +naddr1qvzqqqr4gupzplfq3m5v3u5r0q9f255fdeyz8nyac6lagssx8zy4wugxjs8ajf7pqyghwumn8ghj7mn0wd68ytnvv9hxgtcqy4sj6ar9wd6xv6tvv5kkvmmj94kkzuntv3hhwm3dvfuj6enyxgcrset98p3nsve2v5l at the start of the line + +even this one npub1l5sga6xg72phsz5422ykujprejwud075ggrr3z2hwyrfgr7eylqstegx9z in the middle of the line + +These should be turned into links, but should only have one "nostr:" prefix: + +nostr:naddr1qvzqqqr4gupzplfq3m5v3u5r0q9f255fdeyz8nyac6lagssx8zy4wugxjs8ajf7pqyghwumn8ghj7mn0wd68ytnvv9hxgtcqy4sj6ar9wd6xv6tvv5kkvmmj94kkzuntv3hhwm3dvfuj6enyxgcrset98p3nsve2v5l + +nostr:npub1l5sga6xg72phsz5422ykujprejwud075ggrr3z2hwyrfgr7eylqstegx9z + +nostr:nevent1qqs07p6r33p7yslp6lzk59k2fwlkudhnm4mg7chz5a0apeqfeluuyqsyay40e + +nostr:nprofile1qqsd6ejdteqpvse63ntf7qz6u9yqspp4z7ymt8094urzwm0x2ceaxxg5g7l0j + +nostr:note1uuw73zj2hmrtfqqzzff526msjvnl90n24q7z2lz6k4a4gknz94rqvl2846 + +=== Hashtag + +#testhashtag at the start of the line and #inlinehashtag in the middle + +=== Wikilinks + +[[NKBIP-01|NKBIP-01 Specification]] and [[mirepoix]] + +=== URL + +https://www.welt.de/politik/ausland/article69a7ca00ad41f3cd65a1bc63/iran-drohte-jedes-schiff-zu-verbrennen-trump-will-oel-tanker-durch-strasse-von-hormus-eskortieren.html + +link:https://www.welt.de/politik/ausland/article69a7ca00ad41f3cd65a1bc63/iran-drohte-jedes-schiff-zu-verbrennen-trump-will-oel-tanker-durch-strasse-von-hormus-eskortieren.html[Welt Online link] + +this should render as plaintext: `http://www.example.com` + +this should be a hyperlink to the http URL with the same address link:https://theforest.nostr1.com[wss://theforest.nostr1.com] + +=== Images + +https://blog.ronin.cloud/content/images/size/w2000/2022/02/markdown.png + +image::https://blog.ronin.cloud/content/images/size/w2000/2022/02/markdown.png[Markdown example,width=400] + +Here is an inline image:https://upload.wikimedia.org/wikipedia/commons/3/35/Tux.svg[Linux,25,35]. This only works in AsciiDoc, not Markdown. + +=== Media + +==== YouTube + +Normal + +https://www.youtube.com/watch?v=KGIAS0cslSU + +https://youtu.be/KGIAS0cslSU + +video::KGIAS0cslSU[youtube] + +Shorts + +https://www.youtube.com/shorts/s-BQhXdCs8Y + +video::s-BQhXdCs8Y[youtube] + +==== Spotify + +https://open.spotify.com/episode/1GSZFA8vWltPyxYkArdRKx + +link:https://open.spotify.com/episode/1GSZFA8vWltPyxYkArdRKx[Spotify example] + +==== Audio + +https://media.blubrry.com/takeituneasy/ins.blubrry.com/takeituneasy/lex_ai_rick_beato.mp3 + +audio::https://media.blubrry.com/takeituneasy/ins.blubrry.com/takeituneasy/lex_ai_rick_beato.mp3[Audio example] + +==== Video + +https://v.nostr.build/MTjaYib4upQuf8zn.mp4 + +video::https://v.nostr.build/MTjaYib4upQuf8zn.mp4[Video example] + +== Tables + +=== Orderly + +[cols="1,2"] +|=== +|Syntax|Description + +|Header +|Title + +|Paragraph +|Text +|=== + +=== Unorderly + +[cols="1,2"] +|=== +|Syntax|Description + +|Header +|Title + +|Paragraph +|Text +|=== + +=== With alignment + +[cols="<,^,>"] +|=== +|Syntax|Description|Test Text + +|Header +|Title +|Here's this + +|Paragraph +|Text +|And more +|=== + +[grid=rows] +|=== +|Column 1, header row |Column 2, header row |Column 3, header row + +|Cell in column 1, row 2 +|Cell in column 2, row 2 +|Cell in column 3, row 2 + +|Cell in column 1, row 3 +|Cell in column 2, row 3 +|Cell in column 3, row 3 +|=== + +[grid=cols] +|=== +|Column 1, header row |Column 2, header row |Column 3, header row + +|Cell in column 1, row 2 +|Cell in column 2, row 2 +|Cell in column 3, row 2 + +|Cell in column 1, row 3 +|Cell in column 2, row 3 +|Cell in column 3, row 3 +|=== + +[grid=none] +|=== +|Column 1, header row |Column 2, header row |Column 3, header row + +|Cell in column 1, row 2 +|Cell in column 2, row 2 +|Cell in column 3, row 2 + +|Cell in column 1, row 3 +|Cell in column 2, row 3 +|Cell in column 3, row 3 +|=== + +[width=75%] +|=== +|Column 1, header row |Column 2, header row |Column 3, header row + +|Cell in column 1, row 2 +|Cell in column 2, row 2 +|Cell in column 3, row 2 + +|Cell in column 1, row 3 +|Cell in column 2, row 3 +|Cell in column 3, row 3 +|=== + +[%autowidth.stretch] +|=== +|Column 1, header row |Column 2, header row |Column 3, header row + +|Cell in column 1, row 2 +|Cell in column 2, row 2 +|Cell in column 3, row 2 + +|Cell in column 1, row 3 +|Cell in column 2, row 3 +|Cell in column 3, row 3 +|=== + +[cols="5,3*"] +|=== +|Column 1 |Column 2 |Column 3 |Column 4 + +|Cell in column 1 +|Cell in column 2 +|Cell in column 3 +|Cell in column 4 +|=== + +== Code blocks + +This is inline code: `console.log("Hello, world!");` and these this is inline code, too: `console.log("Hello, world! This is a very long inline code that should not be split into multiple lines.");` + +=== json + +[source,json] +---- +{ + "id": "", + "pubkey": "", + "created_at": 1725087283, + "kind": 30040, + "tags": [ + ["d", "aesop's-fables-by-aesop"], + ["title", "Aesop's Fables"], + ["author", "Aesop"], + ], + "sig": "" +} +---- + +=== shell + +[source,shell] +---- +mkdir new_directory +cp source.txt destination.txt + +---- + +### Inline-code + +`this is code` and `this is also code` + +== Footnotes + +Here's a simple footnote,footnote:[This is the first footnote.] and here's a longer one.footnote:[Here's one with multiple paragraphs and code.] + +== Anchor links + +<<_bullet_list,Link to bullet list section>> + +== Formatting + +=== Strikethrough + +~~The world is flat.~~ We now know that the world is round. This should not be ~struck~ through. + +=== Bold + +This is *bold* text. So is this *bold* text. + +=== Italic + +This is _italic_ text. So is this _italic_ text. + +=== Bold and italic Mixed + +This is **bold and _italic_** text. +This is _italic and **bold**_ text. + +=== Task List + +* [x] Write the press release +* [ ] Update the website +* [ ] Contact the media + +=== Emoji shortcodes + +Gone camping! :tent: Be back soon. + +That is so funny! :joy: + +=== Marking and highlighting text + +I need to highlight these [highlight]#very important words#. + +=== Subscript and Superscript + +H~2~O + +X^2^ + +=== Delimiter + +based upon a single quote + +''' + +based upon a dashes + +--- + +=== Quotes + +[quote] +____ +This is a single line blockequote sdfjsdlfkjasldkfjsdölfkjsdlfkjsadlöfkjsdlöfkjsadölfkjsdlf kjsldfkjsdalkjslkdfjlöskdfjlösdkjfsldkfjsöldkfjlösdkfjalsd kfjlsdkfjlödkfjlaksdfjlkjdfslkjalsdkfjlasdkfj alsdkjflskdfj sdfklj +____ + +[quote,Monty Python and the Holy Grail] +____ +Dennis: Come and see the violence inherent in the system. Help! Help! I'm being repressed! + +King Arthur: Bloody peasant! + +Dennis: Oh, what a giveaway! Did you hear that? Did you hear that, eh? That's what I'm on about! Did you see him repressing me? You saw him, Didn't you? + +> We should also support normal quotes. + + +____ + +=== Green-text + +>This is green-text +>It differs from quotes because there is no space between the chevron and the first letter in the line. +>Should match greentext from 4chan. + +=== Keyboard shortcuts + +|=== +|Shortcut |Purpose + +|kbd:[F11] +|Toggle fullscreen + +|kbd:[Ctrl+T] +|Open a new tab + +|kbd:[Ctrl+Shift+N] +|New incognito window + +|kbd:[\ ] +|Used to escape characters + +|kbd:[Ctrl+\]] +|Jump to keyword + +|kbd:[Ctrl + +] +|Increase zoom +|=== + +== Admonitions + +[NOTE] +==== +This is a note. +==== + +[WARNING] +==== +This is a warning. +==== + +[TIP] +==== +This is a tip. +==== + +[IMPORTANT] +==== +This is an important message. +==== + +[CAUTION] +==== +This is a caution. +==== + +WARNING: Wolpertingers are known to nest in server racks. +Enter at your own risk. + +== Sidebars + +[sidebar] +==== +This is a sidebar. +==== + +[sidebar] +Sidebars are used to visually separate auxiliary bits of content +that supplement the main text. + +.Optional Title +**** +Sidebars are used to visually separate auxiliary bits of content +that supplement the main text. + +TIP: They can contain any type of content. + +.Source code block in a sidebar +[source,js] +---- +const { expect, expectCalledWith, heredoc } = require('../test/test-utils') +---- +**** + +== Examples + +.Optional title +[example] +This is an example of an example block. + +.Onomatopoeia +==== +The book hit the floor with a *thud*. + +He could hear doves *cooing* in the pine trees`' branches. +==== + +== Literal blocks + +[literal] +error: 1954 Forbidden search +absolutely fatal: operation lost in the dodecahedron of doom +Would you like to try again? y/n + +.... +Kismet: Where is the *defensive operations manual*? + +Computer: Calculating ... +Can not locate object. +You are not authorized to know it exists. + +Kismet: Did the werewolves tell you to say that? + +Computer: Calculating ... +.... \ No newline at end of file diff --git a/integration-fixtures/publication.json b/integration-fixtures/publication.json new file mode 100644 index 0000000..1cebe82 --- /dev/null +++ b/integration-fixtures/publication.json @@ -0,0 +1,184 @@ +{ + "indexEvent": { + "kind": 30040, + "content": "", + "tags": [ + [ + "d", + "document-test" + ], + [ + "title", + "Document Test" + ], + [ + "author", + "unknown" + ], + [ + "version", + "1" + ], + [ + "m", + "application/json" + ], + [ + "M", + "meta-data/index/replaceable" + ], + [ + "a", + "30041:fd208ee8c8f283780a9552896e4823cc9dc6bfd442063889577106940fd927c1:first-level-heading" + ], + [ + "a", + "30041:fd208ee8c8f283780a9552896e4823cc9dc6bfd442063889577106940fd927c1:another-first-level-heading" + ], + [ + "a", + "30041:fd208ee8c8f283780a9552896e4823cc9dc6bfd442063889577106940fd927c1:a-third-first-level-heading" + ], + [ + "a", + "30041:fd208ee8c8f283780a9552896e4823cc9dc6bfd442063889577106940fd927c1:asciimath-test-document" + ], + [ + "t", + "a-tags" + ], + [ + "t", + "testfile" + ], + [ + "t", + "asciimath" + ], + [ + "t", + "latexmath" + ], + [ + "image", + "https://i.nostr.build/5kWwbDR04joIASVx.png" + ] + ], + "pubkey": "fd208ee8c8f283780a9552896e4823cc9dc6bfd442063889577106940fd927c1", + "id": "4585ed74a0be37655aa887340d239f0bbb9df5476165d912f098c55a71196fef" + }, + "sectionEvents": [ + { + "kind": 30041, + "content": "This is a paragraph with a *bold* word and an _italicized_ word.\n\n.Image caption\nimage::https://upload.wikimedia.org/wikipedia/commons/1/11/Test-Logo.svg[I am the image alt text.]\n\nThis is another paragraph.footnote:[I am footnote text and will be displayed at the bottom of the article.]\n\n.Unordered list title\n* list item 1\n** nested list item\n*** nested nested list item 1\n*** nested nested list item 2\n* list item 2\n\nThis is a paragraph.\n\n.Example block title\n....\nContent in an example block is subject to normal substitutions.\n....\n\n.Sidebar title\n****\nSidebars contain aside text and are subject to normal substitutions.\n****\n\n[#id-for-listing-block]\n.Listing block title\n----\nContent in a listing block is subject to verbatim substitutions.\nListing block content is commonly used to preserve code input.\n----", + "tags": [ + [ + "d", + "first-level-heading" + ], + [ + "title", + "First level heading" + ], + [ + "image", + "https://i.nostr.build/5kWwbDR04joIASVx.png" + ], + [ + "m", + "text/asciidoc" + ], + [ + "M", + "article/publication-content/replaceable" + ] + ], + "pubkey": "fd208ee8c8f283780a9552896e4823cc9dc6bfd442063889577106940fd927c1", + "id": "6763b635ab8d7a308277892b570616b346765c9eef8d836541f21231bef26a04" + }, + { + "kind": 30041, + "content": "[quote, firstname lastname, movie title]\n____\nI am a block quote or a prose excerpt.\nI am subject to normal substitutions.\n____\n\n[verse, firstname lastname, poem title and more]\n____\nI am a verse block.\n Indents and endlines are preserved in verse blocks.\n____\n\n\nTIP: There are five admonition labels: Tip, Note, Important, Caution and Warning.\n\n// I am a comment and won't be rendered.\n\n. ordered list item\n.. nested ordered list item\n. ordered list item\n\nThe text at the end of this sentence is cross referenced to <<_third_level_heading,the third level heading>>", + "tags": [ + [ + "d", + "another-first-level-heading" + ], + [ + "title", + "Another first-level heading" + ], + [ + "image", + "https://i.nostr.build/5kWwbDR04joIASVx.png" + ], + [ + "m", + "text/asciidoc" + ], + [ + "M", + "article/publication-content/replaceable" + ] + ], + "pubkey": "fd208ee8c8f283780a9552896e4823cc9dc6bfd442063889577106940fd927c1", + "id": "cba23f9343a79963855d7649bf7164b9fe2c6b92f661ac12ebf9a224386e5d09" + }, + { + "kind": 30041, + "content": "This is a link to the https://asciidoctor.org/docs/user-manual/[Asciidoctor User Manual].\nThis is an attribute reference {quick-uri}[which links this text to the Asciidoctor Quick Reference Guide].", + "tags": [ + [ + "d", + "a-third-first-level-heading" + ], + [ + "title", + "A third first-level heading" + ], + [ + "image", + "https://i.nostr.build/5kWwbDR04joIASVx.png" + ], + [ + "m", + "text/asciidoc" + ], + [ + "M", + "article/publication-content/replaceable" + ] + ], + "pubkey": "fd208ee8c8f283780a9552896e4823cc9dc6bfd442063889577106940fd927c1", + "id": "078f52ef883444279d327980a21340d02891d6aed6c405e8b73a722a5cd7b93b" + }, + { + "kind": 30041, + "content": "=== Basic Math Expressions\n\nHere's a simple equation: asciimath:[sqrt(4) = 2]\n\nAnd an inline expression with asciimath notation: asciimath:[x^2 + y^2 = z^2]\n\n=== Block Math Expressions\n\nHere's a block equation:\n\n[asciimath]\n++++\nsum_(i=1)^n i^3=((n(n+1))/2)^2\n++++\n\nAnd another one with explicit asciimath notation:\n\n[asciimath]\n++++\nf(x) = int_{-infty}^x~e^{-t^2}dt\n++++\n\n=== Complex Expressions\n\nHere's a more complex expression:\n\n[asciimath]\n++++\nlim_(N->oo) sum_(i=1)^N i = (N(N+1))/2\n++++\n\nAnd a matrix:\n\n[asciimath]\n++++\n[[a,b],[c,d]]\n++++\n\n=== LaTeX Math\n\nWe can also use LaTeX math notation:\n\n[latexmath]\n++++\n\\frac{n!}{k!(n-k)!} = \\binom{n}{k}\n++++", + "tags": [ + [ + "d", + "asciimath-test-document" + ], + [ + "title", + "AsciiMath Test Document" + ], + [ + "image", + "https://i.nostr.build/5kWwbDR04joIASVx.png" + ], + [ + "m", + "text/asciidoc" + ], + [ + "M", + "article/publication-content/replaceable" + ] + ], + "pubkey": "fd208ee8c8f283780a9552896e4823cc9dc6bfd442063889577106940fd927c1", + "id": "f204287665877f55feb9bf1de52b644a257009228d93c5622e74c5f223a115c2" + } + ] +} \ No newline at end of file diff --git a/integration-fixtures/single-event.json b/integration-fixtures/single-event.json new file mode 100644 index 0000000..5f1e162 --- /dev/null +++ b/integration-fixtures/single-event.json @@ -0,0 +1,60 @@ +{ + "kind": 30041, + "content": "= AsciiDoc Test Document\nKismet Lee \n2.9, October 31, 2021: Fall incarnation\n:description: Test description\n:author: Kismet Lee\n:date: 2021-10-31\n:version: 2.9\n:status: Draft\n:keywords: AsciiDoc, Test, Document\n:category: Test\n:language: English\n\n== Bullet list\n\nThis is a test unordered list with mixed bullets:\n\n* First item with a number 2. in it\n* Second item\n* Third item\n** Indented item\n** Indented item\n* Fourth item\n\nAnother unordered list:\n\n* 1st item\n* 2nd item\n* third item containing _italic_ text\n** indented item\n** second indented item\n* fourth item\n\nThis is a test ordered list with indented items:\n\n. First item\n. Second item\n. Third item\n.. Indented item\n.. Indented item\n. Fourth item\n\nOrdered list where everything has no number:\n\n. First item\n. Second item\n. Third item\n. Fourth item\n\nThis is a mixed list with indented items:\n\n. First item\n. Second item\n. Third item\n* Indented item\n* Indented item\n. Fourth item\n\nThis is another mixed list with indented items:\n\n* First item\n* Second item\n* Third item\n. Indented item\n. Indented item\n* Fourth item\n\n== Headers\n\n=== Third-level header\n\n==== Fourth-level header\n\n===== Fifth-level header\n\n[discrete]\n====== Sixth-level header\n\nThis discrete header shouldn't become a section. It should just be rendered in the header style.\n\n== Media and Links\n\n=== Nostr address\n\nThis should be ignored and rendered as plaintext: naddr1qvzqqqr4gupzplfq3m5v3u5r0q9f255fdeyz8nyac6lagss...\n\nThis is also plaintext: npub1gv069uwhateverbunchofnumbers\n\nAnd this should stay plaintext, as it is just a prefix with no address: nostr:\n\nThis nostr address should be ignored, since it is in a URL https://notanostraddress/nevent1qvzqqqqqqypzp382htsmu08k277ps40wqhnfm60st89h5pvjyutghq9cjasuh38qqythwumn8ghj7un9d3shjtnswf5k6ctv9ehx2ap0qqsysletg3lqnl4uy59xsj4rp9rgw67wg23l827f4uvn5ckn20fuxcq45d8pj\n\nThese should be turned into links:\nnostr:naddr1qvzqqqr4gupzplfq3m5v3u5r0q9f255fdeyz8nyac6lagssx8zy4wugxjs8ajf7pqyghwumn8ghj7mn0wd68ytnvv9hxgtcqy4sj6ar9wd6xv6tvv5kkvmmj94kkzuntv3hhwm3dvfuj6enyxgcrset98p3nsve2v5l at the start of the line\n\neven this one nostr:npub1l5sga6xg72phsz5422ykujprejwud075ggrr3z2hwyrfgr7eylqstegx9z in the middle of the line\n\nThese should be turned into links, but should only have one \"nostr:\" prefix:\n\nnostr:naddr1qvzqqqr4gupzplfq3m5v3u5r0q9f255fdeyz8nyac6lagssx8zy4wugxjs8ajf7pqyghwumn8ghj7mn0wd68ytnvv9hxgtcqy4sj6ar9wd6xv6tvv5kkvmmj94kkzuntv3hhwm3dvfuj6enyxgcrset98p3nsve2v5l\n\nnostr:npub1l5sga6xg72phsz5422ykujprejwud075ggrr3z2hwyrfgr7eylqstegx9z\n\nnostr:nevent1qqs07p6r33p7yslp6lzk59k2fwlkudhnm4mg7chz5a0apeqfeluuyqsyay40e\n\nnostr:nprofile1qqsd6ejdteqpvse63ntf7qz6u9yqspp4z7ymt8094urzwm0x2ceaxxg5g7l0j\n\nnostr:note1uuw73zj2hmrtfqqzzff526msjvnl90n24q7z2lz6k4a4gknz94rqvl2846\n\n=== Hashtag\n\n#testhashtag at the start of the line and #inlinehashtag in the middle\n\n=== Wikilinks\n\n[[NKBIP-01|NKBIP-01 Specification]] and [[mirepoix]]\n\n=== URL\n\nhttps://www.welt.de/politik/ausland/article69a7ca00ad41f3cd65a1bc63/iran-drohte-jedes-schiff-zu-verbrennen-trump-will-oel-tanker-durch-strasse-von-hormus-eskortieren.html\n\nlink:https://www.welt.de/politik/ausland/article69a7ca00ad41f3cd65a1bc63/iran-drohte-jedes-schiff-zu-verbrennen-trump-will-oel-tanker-durch-strasse-von-hormus-eskortieren.html[Welt Online link]\n\nthis should render as plaintext: `http://www.example.com`/\n\nthis should be a hyperlink to the http URL with the same address link:https://theforest.nostr1.com[wss://theforest.nostr1.com]\n\n=== Images\n\nhttps://blog.ronin.cloud/content/images/size/w2000/2022/02/markdown.png\n\nimage::https://blog.ronin.cloud/content/images/size/w2000/2022/02/markdown.png[Markdown example,width=400]\n\nHere is an inline image:https://upload.wikimedia.org/wikipedia/commons/3/35/Tux.svg[Linux,25,35]. This only works in AsciiDoc, not Markdown.\n\n=== Media\n\n==== YouTube\n\nNormal\n\nhttps://www.youtube.com/watch?v=KGIAS0cslSU\n\nhttps://youtu.be/KGIAS0cslSU\n\nvideo::KGIAS0cslSU[youtube]\n\nShorts\n\nhttps://www.youtube.com/shorts/s-BQhXdCs8Y\n\nvideo::s-BQhXdCs8Y[youtube]\n\n==== Spotify\n\nhttps://open.spotify.com/episode/1GSZFA8vWltPyxYkArdRKx\n\nlink:https://open.spotify.com/episode/1GSZFA8vWltPyxYkArdRKx[Spotify example]\n\n==== Audio\n\nhttps://media.blubrry.com/takeituneasy/ins.blubrry.com/takeituneasy/lex_ai_rick_beato.mp3\n\naudio::https://media.blubrry.com/takeituneasy/ins.blubrry.com/takeituneasy/lex_ai_rick_beato.mp3[Audio example]\n\n==== Video\n\nhttps://v.nostr.build/MTjaYib4upQuf8zn.mp4\n\nvideo::https://v.nostr.build/MTjaYib4upQuf8zn.mp4[Video example]\n\n== Tables\n\n=== Orderly\n\n[cols=\"1,2\"]\n|===\n|Syntax|Description\n\n|Header\n|Title\n\n|Paragraph\n|Text\n|===\n\n=== Unorderly\n\n[cols=\"1,2\"]\n|===\n|Syntax|Description\n\n|Header\n|Title\n\n|Paragraph\n|Text\n|===\n\n=== With alignment\n\n[cols=\"<,^,>\"]\n|===\n|Syntax|Description|Test Text\n\n|Header\n|Title\n|Here's this\n\n|Paragraph\n|Text\n|And more\n|===\n\n[grid=rows]\n|===\n|Column 1, header row |Column 2, header row |Column 3, header row\n\n|Cell in column 1, row 2\n|Cell in column 2, row 2\n|Cell in column 3, row 2\n\n|Cell in column 1, row 3\n|Cell in column 2, row 3\n|Cell in column 3, row 3\n|===\n\n[grid=cols]\n|===\n|Column 1, header row |Column 2, header row |Column 3, header row\n\n|Cell in column 1, row 2\n|Cell in column 2, row 2\n|Cell in column 3, row 2\n\n|Cell in column 1, row 3\n|Cell in column 2, row 3\n|Cell in column 3, row 3\n|===\n\n[grid=none]\n|===\n|Column 1, header row |Column 2, header row |Column 3, header row\n\n|Cell in column 1, row 2\n|Cell in column 2, row 2\n|Cell in column 3, row 2\n\n|Cell in column 1, row 3\n|Cell in column 2, row 3\n|Cell in column 3, row 3\n|===\n\n[width=75%]\n|===\n|Column 1, header row |Column 2, header row |Column 3, header row\n\n|Cell in column 1, row 2\n|Cell in column 2, row 2\n|Cell in column 3, row 2\n\n|Cell in column 1, row 3\n|Cell in column 2, row 3\n|Cell in column 3, row 3\n|===\n\n[%autowidth.stretch]\n|===\n|Column 1, header row |Column 2, header row |Column 3, header row\n\n|Cell in column 1, row 2\n|Cell in column 2, row 2\n|Cell in column 3, row 2\n\n|Cell in column 1, row 3\n|Cell in column 2, row 3\n|Cell in column 3, row 3\n|===\n\n[cols=\"5,3*\"]\n|===\n|Column 1 |Column 2 |Column 3 |Column 4\n\n|Cell in column 1\n|Cell in column 2\n|Cell in column 3\n|Cell in column 4\n|===\n\n== Code blocks\n\nThis is inline code: `console.log(\"Hello, world!\");` and these this is inline code, too: `console.log(\"Hello, world! This is a very long inline code that should not be split into multiple lines.\");`\n\n=== json\n\n[source,json]\n----\n{\n \"id\": \"\",\n \"pubkey\": \"\",\n \"created_at\": 1725087283,\n \"kind\": 30040,\n \"tags\": [\n [\"d\", \"aesop's-fables-by-aesop\"],\n [\"title\", \"Aesop's Fables\"],\n [\"author\", \"Aesop\"],\n ],\n \"sig\": \"\"\n}\n----\n\n=== shell\n\n[source,shell]\n----\nmkdir new_directory\ncp source.txt destination.txt\n\n----\n\n### Inline-code\n\n`this is code` and `this is also code`\n\n== Footnotes\n\nHere's a simple footnote,footnote:[This is the first footnote.] and here's a longer one.footnote:[Here's one with multiple paragraphs and code.]\n\n== Anchor links\n\n<<_bullet_list,Link to bullet list section>>\n\n== Formatting\n\n=== Strikethrough\n\n~~The world is flat.~~ We now know that the world is round. This should not be ~struck~ through.\n\n=== Bold\n\nThis is *bold* text. So is this *bold* text.\n\n=== Italic\n\nThis is _italic_ text. So is this _italic_ text.\n\n=== Bold and italic Mixed\n\nThis is **bold and _italic_** text.\nThis is _italic and **bold**_ text.\n\n=== Task List\n\n* [x] Write the press release\n* [ ] Update the website\n* [ ] Contact the media\n\n=== Emoji shortcodes\n\nGone camping! :tent: Be back soon.\n\nThat is so funny! :joy:\n\n=== Marking and highlighting text\n\nI need to highlight these [highlight]#very important words#.\n\n=== Subscript and Superscript\n\nH~2~O\n\nX^2^\n\n=== Delimiter\n\nbased upon a single quote\n\n'''\n\nbased upon a dashes\n\n---\n\n=== Quotes\n\n[quote]\n____\nThis is a single line blockequote sdfjsdlfkjasldkfjsdölfkjsdlfkjsadlöfkjsdlöfkjsadölfkjsdlf kjsldfkjsdalkjslkdfjlöskdfjlösdkjfsldkfjsöldkfjlösdkfjalsd kfjlsdkfjlödkfjlaksdfjlkjdfslkjalsdkfjlasdkfj alsdkjflskdfj sdfklj\n____\n\n[quote,Monty Python and the Holy Grail]\n____\nDennis: Come and see the violence inherent in the system. Help! Help! I'm being repressed!\n\nKing Arthur: Bloody peasant!\n\nDennis: Oh, what a giveaway! Did you hear that? Did you hear that, eh? That's what I'm on about! Did you see him repressing me? You saw him, Didn't you?\n\n> We should also support normal quotes.\n\n\n____\n\n=== Green-text\n\n>This is green-text\n>It differs from quotes because there is no space between the chevron and the first letter in the line.\n>Should match greentext from 4chan.\n\n=== Keyboard shortcuts\n\n|===\n|Shortcut |Purpose\n\n|kbd:[F11]\n|Toggle fullscreen\n\n|kbd:[Ctrl+T]\n|Open a new tab\n\n|kbd:[Ctrl+Shift+N]\n|New incognito window\n\n|kbd:[\\ ]\n|Used to escape characters\n\n|kbd:[Ctrl+\\]]\n|Jump to keyword\n\n|kbd:[Ctrl + +]\n|Increase zoom\n|===\n\n== Admonitions\n\n[NOTE]\n====\nThis is a note.\n====\n\n[WARNING]\n====\nThis is a warning.\n====\n\n[TIP]\n====\nThis is a tip.\n====\n\n[IMPORTANT]\n====\nThis is an important message.\n====\n\n[CAUTION]\n====\nThis is a caution.\n====\n\nWARNING: Wolpertingers are known to nest in server racks.\nEnter at your own risk.\n\n== Sidebars\n\n[sidebar]\n====\nThis is a sidebar.\n====\n\n[sidebar]\nSidebars are used to visually separate auxiliary bits of content\nthat supplement the main text.\n\n.Optional Title\n****\nSidebars are used to visually separate auxiliary bits of content\nthat supplement the main text.\n\nTIP: They can contain any type of content.\n\n.Source code block in a sidebar\n[source,js]\n----\nconst { expect, expectCalledWith, heredoc } = require('../test/test-utils')\n----\n****\n\n== Examples\n\n.Optional title\n[example]\nThis is an example of an example block.\n\n.Onomatopoeia\n====\nThe book hit the floor with a *thud*.\n\nHe could hear doves *cooing* in the pine trees`' branches.\n====\n\n== Literal blocks\n\n[literal]\nerror: 1954 Forbidden search\nabsolutely fatal: operation lost in the dodecahedron of doom\nWould you like to try again? y/n\n\n....\nKismet: Where is the *defensive operations manual*?\n\nComputer: Calculating ...\nCan not locate object.\nYou are not authorized to know it exists.\n\nKismet: Did the werewolves tell you to say that?\n\nComputer: Calculating ...\n....", + "tags": [ + [ + "d", + "asciidoc-test-note" + ], + [ + "title", + "Asciidoc Test Note" + ], + [ + "t", + "testhashtag" + ], + [ + "t", + "inlinehashtag" + ], + [ + "t", + "very" + ], + [ + "t", + "asciidoc" + ], + [ + "t", + "test" + ], + [ + "p", + "fd208ee8c8f283780a9552896e4823cc9dc6bfd442063889577106940fd927c1" + ], + [ + "p", + "0d97beae567fcec9c6574f1c6ef6126ea969d4992c3198e51c0fac52c5274a14" + ], + [ + "p", + "dd664d5e4016433a8cd69f005ae1480804351789b59de5af06276de65633d319" + ], + [ + "p", + "dc4cd086cd7ce5b1832adf4fdd1211289880d2c7e295bcb0e684c01acee77c06" + ], + [ + "client", + "jumble" + ], + [ + "alt", + "This event was published by https://jumble.imwald.eu." + ] + ], + "pubkey": "645eb808ac7689f08b5143fbe7aa7289baad2e3bf069c81d2a22a0d3b3589c18", + "id": "83930d105def65c84c766180cf934f040706a81c1bd864307ea1be07c7dd2e9d" +} \ No newline at end of file diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..e2a5608 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,1568 @@ +{ + "name": "gc-parser-asciidoc", + "version": "0.1.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "gc-parser-asciidoc", + "version": "0.1.0", + "license": "MIT", + "devDependencies": { + "@types/node": "^20.10.0", + "nostr-tools": "^2.23.3", + "typescript": "^5.3.0", + "vitest": "^2.0.0" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz", + "integrity": "sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.21.5.tgz", + "integrity": "sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.21.5.tgz", + "integrity": "sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.21.5.tgz", + "integrity": "sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.21.5.tgz", + "integrity": "sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.21.5.tgz", + "integrity": "sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.5.tgz", + "integrity": "sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.21.5.tgz", + "integrity": "sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.21.5.tgz", + "integrity": "sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.21.5.tgz", + "integrity": "sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.21.5.tgz", + "integrity": "sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.21.5.tgz", + "integrity": "sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.21.5.tgz", + "integrity": "sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.21.5.tgz", + "integrity": "sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.21.5.tgz", + "integrity": "sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.21.5.tgz", + "integrity": "sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz", + "integrity": "sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz", + "integrity": "sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz", + "integrity": "sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz", + "integrity": "sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.21.5.tgz", + "integrity": "sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.21.5.tgz", + "integrity": "sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz", + "integrity": "sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "dev": true, + "license": "MIT" + }, + "node_modules/@noble/ciphers": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/@noble/ciphers/-/ciphers-2.1.1.tgz", + "integrity": "sha512-bysYuiVfhxNJuldNXlFEitTVdNnYUc+XNJZd7Qm2a5j1vZHgY+fazadNFWFaMK/2vye0JVlxV3gHmC0WDfAOQw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 20.19.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@noble/curves": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@noble/curves/-/curves-2.0.1.tgz", + "integrity": "sha512-vs1Az2OOTBiP4q0pwjW5aF0xp9n4MxVrmkFBxc6EKZc6ddYx5gaZiAsZoq0uRRXWbi3AT/sBqn05eRPtn1JCPw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@noble/hashes": "2.0.1" + }, + "engines": { + "node": ">= 20.19.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@noble/hashes": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-2.0.1.tgz", + "integrity": "sha512-XlOlEbQcE9fmuXxrVTXCTlG2nlRXa9Rj3rr5Ue/+tX+nmkgbX720YHh0VR3hBF9xDvwnb8D2shVGOwNx+ulArw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 20.19.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.59.0.tgz", + "integrity": "sha512-upnNBkA6ZH2VKGcBj9Fyl9IGNPULcjXRlg0LLeaioQWueH30p6IXtJEbKAgvyv+mJaMxSm1l6xwDXYjpEMiLMg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.59.0.tgz", + "integrity": "sha512-hZ+Zxj3SySm4A/DylsDKZAeVg0mvi++0PYVceVyX7hemkw7OreKdCvW2oQ3T1FMZvCaQXqOTHb8qmBShoqk69Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.59.0.tgz", + "integrity": "sha512-W2Psnbh1J8ZJw0xKAd8zdNgF9HRLkdWwwdWqubSVk0pUuQkoHnv7rx4GiF9rT4t5DIZGAsConRE3AxCdJ4m8rg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.59.0.tgz", + "integrity": "sha512-ZW2KkwlS4lwTv7ZVsYDiARfFCnSGhzYPdiOU4IM2fDbL+QGlyAbjgSFuqNRbSthybLbIJ915UtZBtmuLrQAT/w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.59.0.tgz", + "integrity": "sha512-EsKaJ5ytAu9jI3lonzn3BgG8iRBjV4LxZexygcQbpiU0wU0ATxhNVEpXKfUa0pS05gTcSDMKpn3Sx+QB9RlTTA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.59.0.tgz", + "integrity": "sha512-d3DuZi2KzTMjImrxoHIAODUZYoUUMsuUiY4SRRcJy6NJoZ6iIqWnJu9IScV9jXysyGMVuW+KNzZvBLOcpdl3Vg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.59.0.tgz", + "integrity": "sha512-t4ONHboXi/3E0rT6OZl1pKbl2Vgxf9vJfWgmUoCEVQVxhW6Cw/c8I6hbbu7DAvgp82RKiH7TpLwxnJeKv2pbsw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.59.0.tgz", + "integrity": "sha512-CikFT7aYPA2ufMD086cVORBYGHffBo4K8MQ4uPS/ZnY54GKj36i196u8U+aDVT2LX4eSMbyHtyOh7D7Zvk2VvA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.59.0.tgz", + "integrity": "sha512-jYgUGk5aLd1nUb1CtQ8E+t5JhLc9x5WdBKew9ZgAXg7DBk0ZHErLHdXM24rfX+bKrFe+Xp5YuJo54I5HFjGDAA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.59.0.tgz", + "integrity": "sha512-peZRVEdnFWZ5Bh2KeumKG9ty7aCXzzEsHShOZEFiCQlDEepP1dpUl/SrUNXNg13UmZl+gzVDPsiCwnV1uI0RUA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-gnu": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.59.0.tgz", + "integrity": "sha512-gbUSW/97f7+r4gHy3Jlup8zDG190AuodsWnNiXErp9mT90iCy9NKKU0Xwx5k8VlRAIV2uU9CsMnEFg/xXaOfXg==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-musl": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.59.0.tgz", + "integrity": "sha512-yTRONe79E+o0FWFijasoTjtzG9EBedFXJMl888NBEDCDV9I2wGbFFfJQQe63OijbFCUZqxpHz1GzpbtSFikJ4Q==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-gnu": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.59.0.tgz", + "integrity": "sha512-sw1o3tfyk12k3OEpRddF68a1unZ5VCN7zoTNtSn2KndUE+ea3m3ROOKRCZxEpmT9nsGnogpFP9x6mnLTCaoLkA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-musl": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.59.0.tgz", + "integrity": "sha512-+2kLtQ4xT3AiIxkzFVFXfsmlZiG5FXYW7ZyIIvGA7Bdeuh9Z0aN4hVyXS/G1E9bTP/vqszNIN/pUKCk/BTHsKA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.59.0.tgz", + "integrity": "sha512-NDYMpsXYJJaj+I7UdwIuHHNxXZ/b/N2hR15NyH3m2qAtb/hHPA4g4SuuvrdxetTdndfj9b1WOmy73kcPRoERUg==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.59.0.tgz", + "integrity": "sha512-nLckB8WOqHIf1bhymk+oHxvM9D3tyPndZH8i8+35p/1YiVoVswPid2yLzgX7ZJP0KQvnkhM4H6QZ5m0LzbyIAg==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.59.0.tgz", + "integrity": "sha512-oF87Ie3uAIvORFBpwnCvUzdeYUqi2wY6jRFWJAy1qus/udHFYIkplYRW+wo+GRUP4sKzYdmE1Y3+rY5Gc4ZO+w==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.59.0.tgz", + "integrity": "sha512-3AHmtQq/ppNuUspKAlvA8HtLybkDflkMuLK4DPo77DfthRb71V84/c4MlWJXixZz4uruIH4uaa07IqoAkG64fg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.59.0.tgz", + "integrity": "sha512-2UdiwS/9cTAx7qIUZB/fWtToJwvt0Vbo0zmnYt7ED35KPg13Q0ym1g442THLC7VyI6JfYTP4PiSOWyoMdV2/xg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-openbsd-x64": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.59.0.tgz", + "integrity": "sha512-M3bLRAVk6GOwFlPTIxVBSYKUaqfLrn8l0psKinkCFxl4lQvOSz8ZrKDz2gxcBwHFpci0B6rttydI4IpS4IS/jQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ] + }, + "node_modules/@rollup/rollup-openharmony-arm64": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.59.0.tgz", + "integrity": "sha512-tt9KBJqaqp5i5HUZzoafHZX8b5Q2Fe7UjYERADll83O4fGqJ49O1FsL6LpdzVFQcpwvnyd0i+K/VSwu/o/nWlA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.59.0.tgz", + "integrity": "sha512-V5B6mG7OrGTwnxaNUzZTDTjDS7F75PO1ae6MJYdiMu60sq0CqN5CVeVsbhPxalupvTX8gXVSU9gq+Rx1/hvu6A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.59.0.tgz", + "integrity": "sha512-UKFMHPuM9R0iBegwzKF4y0C4J9u8C6MEJgFuXTBerMk7EJ92GFVFYBfOZaSGLu6COf7FxpQNqhNS4c4icUPqxA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-gnu": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.59.0.tgz", + "integrity": "sha512-laBkYlSS1n2L8fSo1thDNGrCTQMmxjYY5G0WFWjFFYZkKPjsMBsgJfGf4TLxXrF6RyhI60L8TMOjBMvXiTcxeA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.59.0.tgz", + "integrity": "sha512-2HRCml6OztYXyJXAvdDXPKcawukWY2GpR5/nxKp4iBgiO3wcoEGkAaqctIbZcNB6KlUQBIqt8VYkNSj2397EfA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@scure/base": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@scure/base/-/base-2.0.0.tgz", + "integrity": "sha512-3E1kpuZginKkek01ovG8krQ0Z44E3DHPjc5S2rjJw9lZn3KSQOs8S7wqikF/AH7iRanHypj85uGyxk0XAyC37w==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@scure/bip32": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@scure/bip32/-/bip32-2.0.1.tgz", + "integrity": "sha512-4Md1NI5BzoVP+bhyJaY3K6yMesEFzNS1sE/cP+9nuvE7p/b0kx9XbpDHHFl8dHtufcbdHRUUQdRqLIPHN/s7yA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@noble/curves": "2.0.1", + "@noble/hashes": "2.0.1", + "@scure/base": "2.0.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@scure/bip39": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@scure/bip39/-/bip39-2.0.1.tgz", + "integrity": "sha512-PsxdFj/d2AcJcZDX1FXN3dDgitDDTmwf78rKZq1a6c1P1Nan1X/Sxc7667zU3U+AN60g7SxxP0YCVw2H/hBycg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@noble/hashes": "2.0.1", + "@scure/base": "2.0.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@types/estree": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", + "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/node": { + "version": "20.19.37", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.37.tgz", + "integrity": "sha512-8kzdPJ3FsNsVIurqBs7oodNnCEVbni9yUEkaHbgptDACOPW04jimGagZ51E6+lXUwJjgnBw+hyko/lkFWCldqw==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~6.21.0" + } + }, + "node_modules/@vitest/expect": { + "version": "2.1.9", + "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-2.1.9.tgz", + "integrity": "sha512-UJCIkTBenHeKT1TTlKMJWy1laZewsRIzYighyYiJKZreqtdxSos/S1t+ktRMQWu2CKqaarrkeszJx1cgC5tGZw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/spy": "2.1.9", + "@vitest/utils": "2.1.9", + "chai": "^5.1.2", + "tinyrainbow": "^1.2.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/mocker": { + "version": "2.1.9", + "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-2.1.9.tgz", + "integrity": "sha512-tVL6uJgoUdi6icpxmdrn5YNo3g3Dxv+IHJBr0GXHaEdTcw3F+cPKnsXFhli6nO+f/6SDKPHEK1UN+k+TQv0Ehg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/spy": "2.1.9", + "estree-walker": "^3.0.3", + "magic-string": "^0.30.12" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "msw": "^2.4.9", + "vite": "^5.0.0" + }, + "peerDependenciesMeta": { + "msw": { + "optional": true + }, + "vite": { + "optional": true + } + } + }, + "node_modules/@vitest/pretty-format": { + "version": "2.1.9", + "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-2.1.9.tgz", + "integrity": "sha512-KhRIdGV2U9HOUzxfiHmY8IFHTdqtOhIzCpd8WRdJiE7D/HUcZVD0EgQCVjm+Q9gkUXWgBvMmTtZgIG48wq7sOQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "tinyrainbow": "^1.2.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/runner": { + "version": "2.1.9", + "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-2.1.9.tgz", + "integrity": "sha512-ZXSSqTFIrzduD63btIfEyOmNcBmQvgOVsPNPe0jYtESiXkhd8u2erDLnMxmGrDCwHCCHE7hxwRDCT3pt0esT4g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/utils": "2.1.9", + "pathe": "^1.1.2" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/snapshot": { + "version": "2.1.9", + "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-2.1.9.tgz", + "integrity": "sha512-oBO82rEjsxLNJincVhLhaxxZdEtV0EFHMK5Kmx5sJ6H9L183dHECjiefOAdnqpIgT5eZwT04PoggUnW88vOBNQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/pretty-format": "2.1.9", + "magic-string": "^0.30.12", + "pathe": "^1.1.2" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/spy": { + "version": "2.1.9", + "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-2.1.9.tgz", + "integrity": "sha512-E1B35FwzXXTs9FHNK6bDszs7mtydNi5MIfUWpceJ8Xbfb1gBMscAnwLbEu+B44ed6W3XjL9/ehLPHR1fkf1KLQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "tinyspy": "^3.0.2" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/utils": { + "version": "2.1.9", + "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-2.1.9.tgz", + "integrity": "sha512-v0psaMSkNJ3A2NMrUEHFRzJtDPFn+/VWZ5WxImB21T9fjucJRmS7xCS3ppEnARb9y11OAzaD+P2Ps+b+BGX5iQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/pretty-format": "2.1.9", + "loupe": "^3.1.2", + "tinyrainbow": "^1.2.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/assertion-error": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz", + "integrity": "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + } + }, + "node_modules/cac": { + "version": "6.7.14", + "resolved": "https://registry.npmjs.org/cac/-/cac-6.7.14.tgz", + "integrity": "sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/chai": { + "version": "5.3.3", + "resolved": "https://registry.npmjs.org/chai/-/chai-5.3.3.tgz", + "integrity": "sha512-4zNhdJD/iOjSH0A05ea+Ke6MU5mmpQcbQsSOkgdaUMJ9zTlDTD/GYlwohmIE2u0gaxHYiVHEn1Fw9mZ/ktJWgw==", + "dev": true, + "license": "MIT", + "dependencies": { + "assertion-error": "^2.0.1", + "check-error": "^2.1.1", + "deep-eql": "^5.0.1", + "loupe": "^3.1.0", + "pathval": "^2.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/check-error": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/check-error/-/check-error-2.1.3.tgz", + "integrity": "sha512-PAJdDJusoxnwm1VwW07VWwUN1sl7smmC3OKggvndJFadxxDRyFJBX/ggnu/KE4kQAB7a3Dp8f/YXC1FlUprWmA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 16" + } + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/deep-eql": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-5.0.2.tgz", + "integrity": "sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/es-module-lexer": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.7.0.tgz", + "integrity": "sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==", + "dev": true, + "license": "MIT" + }, + "node_modules/esbuild": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.21.5.tgz", + "integrity": "sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=12" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.21.5", + "@esbuild/android-arm": "0.21.5", + "@esbuild/android-arm64": "0.21.5", + "@esbuild/android-x64": "0.21.5", + "@esbuild/darwin-arm64": "0.21.5", + "@esbuild/darwin-x64": "0.21.5", + "@esbuild/freebsd-arm64": "0.21.5", + "@esbuild/freebsd-x64": "0.21.5", + "@esbuild/linux-arm": "0.21.5", + "@esbuild/linux-arm64": "0.21.5", + "@esbuild/linux-ia32": "0.21.5", + "@esbuild/linux-loong64": "0.21.5", + "@esbuild/linux-mips64el": "0.21.5", + "@esbuild/linux-ppc64": "0.21.5", + "@esbuild/linux-riscv64": "0.21.5", + "@esbuild/linux-s390x": "0.21.5", + "@esbuild/linux-x64": "0.21.5", + "@esbuild/netbsd-x64": "0.21.5", + "@esbuild/openbsd-x64": "0.21.5", + "@esbuild/sunos-x64": "0.21.5", + "@esbuild/win32-arm64": "0.21.5", + "@esbuild/win32-ia32": "0.21.5", + "@esbuild/win32-x64": "0.21.5" + } + }, + "node_modules/estree-walker": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", + "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0" + } + }, + "node_modules/expect-type": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/expect-type/-/expect-type-1.3.0.tgz", + "integrity": "sha512-knvyeauYhqjOYvQ66MznSMs83wmHrCycNEN6Ao+2AeYEfxUIkuiVxdEa1qlGEPK+We3n0THiDciYSsCcgW/DoA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/loupe": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/loupe/-/loupe-3.2.1.tgz", + "integrity": "sha512-CdzqowRJCeLU72bHvWqwRBBlLcMEtIvGrlvef74kMnV2AolS9Y8xUv1I0U/MNAWMhBlKIoyuEgoJ0t/bbwHbLQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/magic-string": { + "version": "0.30.21", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", + "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.5" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/nanoid": { + "version": "3.3.11", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", + "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/nostr-tools": { + "version": "2.23.3", + "resolved": "https://registry.npmjs.org/nostr-tools/-/nostr-tools-2.23.3.tgz", + "integrity": "sha512-AALyt9k8xPdF4UV2mlLJ2mgCn4kpTB0DZ8t2r6wjdUh6anfx2cTVBsHUlo9U0EY/cKC5wcNyiMAmRJV5OVEalA==", + "dev": true, + "license": "Unlicense", + "dependencies": { + "@noble/ciphers": "2.1.1", + "@noble/curves": "2.0.1", + "@noble/hashes": "2.0.1", + "@scure/base": "2.0.0", + "@scure/bip32": "2.0.1", + "@scure/bip39": "2.0.1", + "nostr-wasm": "0.1.0" + }, + "peerDependencies": { + "typescript": ">=5.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/nostr-wasm": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/nostr-wasm/-/nostr-wasm-0.1.0.tgz", + "integrity": "sha512-78BTryCLcLYv96ONU8Ws3Q1JzjlAt+43pWQhIl86xZmWeegYCNLPml7yQ+gG3vR6V5h4XGj+TxO+SS5dsThQIA==", + "dev": true, + "license": "MIT" + }, + "node_modules/pathe": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/pathe/-/pathe-1.1.2.tgz", + "integrity": "sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/pathval": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/pathval/-/pathval-2.0.1.tgz", + "integrity": "sha512-//nshmD55c46FuFw26xV/xFAaB5HF9Xdap7HJBBnrKdAd6/GxDBaNA1870O79+9ueg61cZLSVc+OaFlfmObYVQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 14.16" + } + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true, + "license": "ISC" + }, + "node_modules/postcss": { + "version": "8.5.8", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.8.tgz", + "integrity": "sha512-OW/rX8O/jXnm82Ey1k44pObPtdblfiuWnrd8X7GJ7emImCOstunGbXUpp7HdBrFQX6rJzn3sPT397Wp5aCwCHg==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.11", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/rollup": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.59.0.tgz", + "integrity": "sha512-2oMpl67a3zCH9H79LeMcbDhXW/UmWG/y2zuqnF2jQq5uq9TbM9TVyXvA4+t+ne2IIkBdrLpAaRQAvo7YI/Yyeg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "1.0.8" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.59.0", + "@rollup/rollup-android-arm64": "4.59.0", + "@rollup/rollup-darwin-arm64": "4.59.0", + "@rollup/rollup-darwin-x64": "4.59.0", + "@rollup/rollup-freebsd-arm64": "4.59.0", + "@rollup/rollup-freebsd-x64": "4.59.0", + "@rollup/rollup-linux-arm-gnueabihf": "4.59.0", + "@rollup/rollup-linux-arm-musleabihf": "4.59.0", + "@rollup/rollup-linux-arm64-gnu": "4.59.0", + "@rollup/rollup-linux-arm64-musl": "4.59.0", + "@rollup/rollup-linux-loong64-gnu": "4.59.0", + "@rollup/rollup-linux-loong64-musl": "4.59.0", + "@rollup/rollup-linux-ppc64-gnu": "4.59.0", + "@rollup/rollup-linux-ppc64-musl": "4.59.0", + "@rollup/rollup-linux-riscv64-gnu": "4.59.0", + "@rollup/rollup-linux-riscv64-musl": "4.59.0", + "@rollup/rollup-linux-s390x-gnu": "4.59.0", + "@rollup/rollup-linux-x64-gnu": "4.59.0", + "@rollup/rollup-linux-x64-musl": "4.59.0", + "@rollup/rollup-openbsd-x64": "4.59.0", + "@rollup/rollup-openharmony-arm64": "4.59.0", + "@rollup/rollup-win32-arm64-msvc": "4.59.0", + "@rollup/rollup-win32-ia32-msvc": "4.59.0", + "@rollup/rollup-win32-x64-gnu": "4.59.0", + "@rollup/rollup-win32-x64-msvc": "4.59.0", + "fsevents": "~2.3.2" + } + }, + "node_modules/siginfo": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/siginfo/-/siginfo-2.0.0.tgz", + "integrity": "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==", + "dev": true, + "license": "ISC" + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/stackback": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz", + "integrity": "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==", + "dev": true, + "license": "MIT" + }, + "node_modules/std-env": { + "version": "3.10.0", + "resolved": "https://registry.npmjs.org/std-env/-/std-env-3.10.0.tgz", + "integrity": "sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg==", + "dev": true, + "license": "MIT" + }, + "node_modules/tinybench": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.9.0.tgz", + "integrity": "sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==", + "dev": true, + "license": "MIT" + }, + "node_modules/tinyexec": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-0.3.2.tgz", + "integrity": "sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==", + "dev": true, + "license": "MIT" + }, + "node_modules/tinypool": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/tinypool/-/tinypool-1.1.1.tgz", + "integrity": "sha512-Zba82s87IFq9A9XmjiX5uZA/ARWDrB03OHlq+Vw1fSdt0I+4/Kutwy8BP4Y/y/aORMo61FQ0vIb5j44vSo5Pkg==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.0.0 || >=20.0.0" + } + }, + "node_modules/tinyrainbow": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-1.2.0.tgz", + "integrity": "sha512-weEDEq7Z5eTHPDh4xjX789+fHfF+P8boiFB+0vbWzpbnbsEr/GRaohi/uMKxg8RZMXnl1ItAi/IUHWMsjDV7kQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/tinyspy": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/tinyspy/-/tinyspy-3.0.2.tgz", + "integrity": "sha512-n1cw8k1k0x4pgA2+9XrOkFydTerNcJ1zWCO5Nn9scWHTD+5tp8dghT2x1uduQePZTZgd3Tupf+x9BxJjeJi77Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/typescript": { + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/undici-types": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", + "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/vite": { + "version": "5.4.21", + "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.21.tgz", + "integrity": "sha512-o5a9xKjbtuhY6Bi5S3+HvbRERmouabWbyUcpXXUA1u+GNUKoROi9byOJ8M0nHbHYHkYICiMlqxkg1KkYmm25Sw==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "^0.21.3", + "postcss": "^8.4.43", + "rollup": "^4.20.0" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^18.0.0 || >=20.0.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^18.0.0 || >=20.0.0", + "less": "*", + "lightningcss": "^1.21.0", + "sass": "*", + "sass-embedded": "*", + "stylus": "*", + "sugarss": "*", + "terser": "^5.4.0" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + } + } + }, + "node_modules/vite-node": { + "version": "2.1.9", + "resolved": "https://registry.npmjs.org/vite-node/-/vite-node-2.1.9.tgz", + "integrity": "sha512-AM9aQ/IPrW/6ENLQg3AGY4K1N2TGZdR5e4gu/MmmR2xR3Ll1+dib+nook92g4TV3PXVyeyxdWwtaCAiUL0hMxA==", + "dev": true, + "license": "MIT", + "dependencies": { + "cac": "^6.7.14", + "debug": "^4.3.7", + "es-module-lexer": "^1.5.4", + "pathe": "^1.1.2", + "vite": "^5.0.0" + }, + "bin": { + "vite-node": "vite-node.mjs" + }, + "engines": { + "node": "^18.0.0 || >=20.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/vitest": { + "version": "2.1.9", + "resolved": "https://registry.npmjs.org/vitest/-/vitest-2.1.9.tgz", + "integrity": "sha512-MSmPM9REYqDGBI8439mA4mWhV5sKmDlBKWIYbA3lRb2PTHACE0mgKwA8yQ2xq9vxDTuk4iPrECBAEW2aoFXY0Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/expect": "2.1.9", + "@vitest/mocker": "2.1.9", + "@vitest/pretty-format": "^2.1.9", + "@vitest/runner": "2.1.9", + "@vitest/snapshot": "2.1.9", + "@vitest/spy": "2.1.9", + "@vitest/utils": "2.1.9", + "chai": "^5.1.2", + "debug": "^4.3.7", + "expect-type": "^1.1.0", + "magic-string": "^0.30.12", + "pathe": "^1.1.2", + "std-env": "^3.8.0", + "tinybench": "^2.9.0", + "tinyexec": "^0.3.1", + "tinypool": "^1.0.1", + "tinyrainbow": "^1.2.0", + "vite": "^5.0.0", + "vite-node": "2.1.9", + "why-is-node-running": "^2.3.0" + }, + "bin": { + "vitest": "vitest.mjs" + }, + "engines": { + "node": "^18.0.0 || >=20.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "@edge-runtime/vm": "*", + "@types/node": "^18.0.0 || >=20.0.0", + "@vitest/browser": "2.1.9", + "@vitest/ui": "2.1.9", + "happy-dom": "*", + "jsdom": "*" + }, + "peerDependenciesMeta": { + "@edge-runtime/vm": { + "optional": true + }, + "@types/node": { + "optional": true + }, + "@vitest/browser": { + "optional": true + }, + "@vitest/ui": { + "optional": true + }, + "happy-dom": { + "optional": true + }, + "jsdom": { + "optional": true + } + } + }, + "node_modules/why-is-node-running": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-2.3.0.tgz", + "integrity": "sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==", + "dev": true, + "license": "MIT", + "dependencies": { + "siginfo": "^2.0.0", + "stackback": "0.0.2" + }, + "bin": { + "why-is-node-running": "cli.js" + }, + "engines": { + "node": ">=8" + } + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..60fb069 --- /dev/null +++ b/package.json @@ -0,0 +1,35 @@ +{ + "name": "gc-parser-asciidoc", + "author": "Silberengel", + "company": "GitCitadel", + "version": "0.1.0", + "description": "Basic AsciiDoc parser and kind 30040 publication helper for Nostr", + "main": "dist/index.js", + "types": "dist/index.d.ts", + "files": [ + "dist", + "README.md" + ], + "scripts": { + "build": "tsc", + "test": "vitest run", + "test:watch": "vitest", + "test:integration": "RUN_INTEGRATION=1 vitest run src/integration.test.ts", + "test:integration:record": "RECORD_FIXTURES=1 RUN_INTEGRATION=1 vitest run src/integration.test.ts" + }, + "keywords": [ + "nostr", + "asciidoc", + "parser", + "30040", + "publication", + "typescript" + ], + "license": "MIT", + "devDependencies": { + "@types/node": "^20.10.0", + "nostr-tools": "^2.23.3", + "typescript": "^5.3.0", + "vitest": "^2.0.0" + } +} diff --git a/src/index.ts b/src/index.ts new file mode 100644 index 0000000..bf90ad0 --- /dev/null +++ b/src/index.ts @@ -0,0 +1,17 @@ +export { parseAsciiDoc, renderEventContent } from './parser.js'; +export { + buildPublicationAsciiDoc, + renderPublication, + renderPublicationFromEvents, + renderEvent, + toPublicationIndexLike, +} from './publication-helper.js'; +export type { + AsciiDocParserOptions, + AsciiDocParseResult, + NostrEventLike, + PublicationIndexLike, + PublicationSection, + PublicationInput, + PublicationRenderResult, +} from './types.js'; diff --git a/src/inline.ts b/src/inline.ts new file mode 100644 index 0000000..e3508ce --- /dev/null +++ b/src/inline.ts @@ -0,0 +1,134 @@ +/** + * Inline formatting: hashtags, nostr links, wikilinks, URLs, + * bold, italic, code, strikethrough, subscript, superscript. + */ + +import type { AsciiDocParserOptions } from './types.js'; + +const CODE_PLACEHOLDER = '\u0000CODE\u0000'; + +/** Bech32 nostr prefixes; min length after prefix to avoid false positives */ +const NOSTR_PREFIX = /(?:nostr:)?(naddr1|npub1|nevent1|nprofile1|note1)([a-zA-HJ-NP-Z0-9]{20,})/g; + +/** Hashtag: #word (word = alphanumeric + underscore, not inside URL or word) */ +const HASHTAG = /(?:^|(?<=[\s>)]))#([a-zA-Z][a-zA-Z0-9_]*)/g; + +/** Wikilink: [[id]] or [[id|label]] */ +const WIKILINK = /\[\[([^\]|]+)(?:\|([^\]]*))?\]\]/g; + +/** Bare URL (not in backticks); greedy so we consume trailing path */ +const BARE_URL = /(https?:\/\/[^\s<>"']+)/g; + +/** Strikethrough ~~text~~ */ +const STRIKETHROUGH = /~~([^~]+)~~/g; + +/** Subscript ~x~ (non-greedy, avoid matching ~~) */ +const SUBSCRIPT = /(?/g, '>') + .replace(/"/g, '"'); +} + +function buildNostrHref(bech32: string, options: AsciiDocParserOptions): string { + const base = options.nostrBaseUrl ?? ''; + if (base === '') return 'nostr:' + bech32; + const sep = base.endsWith('/') ? '' : '/'; + return base + sep + bech32; +} + +/** Linkify nostr addresses in text. */ +function linkifyNostr(segment: string, options: AsciiDocParserOptions): string { + return segment.replace(NOSTR_PREFIX, (fullMatch: string, prefix: string, rest: string) => { + const bech32 = prefix + rest; + const href = buildNostrHref(bech32, options); + const label = fullMatch.startsWith('nostr:') ? 'nostr:' + bech32 : bech32; + return `${escapeHtml(label)}`; + }); +} + +/** Apply hashtag spans; run on already-escaped text so we only wrap #word */ +function applyHashtags(text: string, options: AsciiDocParserOptions): string { + const cls = options.hashtagClass ?? 'hashtag'; + return text.replace(HASHTAG, (_, tag) => { + return `#${escapeHtml(tag)}`; + }); +} + +function applyWikilinks(text: string, options: AsciiDocParserOptions): string { + const urlFn = options.wikilinkUrl; + return text.replace(WIKILINK, (_, dtag: string, label?: string) => { + const href = urlFn + ? typeof urlFn === 'function' + ? urlFn(dtag) + : urlFn.replace('{dtag}', dtag) + : '#' + dtag.replace(/\s+/g, '_').toLowerCase(); + const linkText = (label ?? dtag).trim(); + return `${escapeHtml(linkText)}`; + }); +} + +/** Replace bare URLs with ; skip if the "URL" is actually part of nostr (e.g. nostr: in middle). */ +function applyBareUrls(text: string): string { + return text.replace(BARE_URL, (url) => { + if (/^https?:\/\/[^/]*nostr:/i.test(url)) return url; + return `${escapeHtml(url)}`; + }); +} + +function applyLinkMacro(text: string): string { + return text.replace(LINK_MACRO, (_, url: string, label: string) => { + const t = label.trim(); + return `${escapeHtml(t || url)}`; + }); +} + +/** + * Process inline content: escape, code, bold, italic, strikethrough, + * sub, sup, hashtags, wikilinks, link macro, bare URLs, nostr links. + */ +export function formatInline(text: string, options: AsciiDocParserOptions = {}): string { + let out = escapeHtml(text); + + const codeBlocks: string[] = []; + out = out.replace(INLINE_CODE, (_, code) => { + codeBlocks.push(code); + return CODE_PLACEHOLDER + (codeBlocks.length - 1) + CODE_PLACEHOLDER; + }); + + out = out.replace(BOLD, (_, s) => '' + s + ''); + out = out.replace(ITALIC, (_, s) => '' + s + ''); + out = out.replace(STRIKETHROUGH, (_, s) => '' + s + ''); + out = out.replace(SUBSCRIPT, (_, s) => '' + escapeHtml(s) + ''); + out = out.replace(SUPERSCRIPT, (_, s) => '' + escapeHtml(s) + ''); + out = applyHashtags(out, options); + out = applyWikilinks(out, options); + out = applyLinkMacro(out); + out = applyBareUrls(out); + out = linkifyNostr(out, options); + + out = out.replace(new RegExp(CODE_PLACEHOLDER + '(\\d+)' + CODE_PLACEHOLDER, 'g'), (_, n) => { + const code = codeBlocks[parseInt(n, 10)] ?? ''; + return '' + escapeHtml(code) + ''; + }); + + return out; +} diff --git a/src/integration.test.ts b/src/integration.test.ts new file mode 100644 index 0000000..0b2ac54 --- /dev/null +++ b/src/integration.test.ts @@ -0,0 +1,167 @@ +/** + * Integration tests: render from fixture events (no relay required). + * Fixtures live in integration-fixtures/; record them once with: + * RECORD_FIXTURES=1 RUN_INTEGRATION=1 npm run test:integration + * Then run with: RUN_INTEGRATION=1 npm run test:integration + * Output HTML is written to integration-output/ for inspection. + */ + +import { describe, it, expect, afterAll } from 'vitest'; +import { mkdirSync, writeFileSync, readFileSync, existsSync } from 'fs'; +import { join } from 'path'; +import { SimplePool, nip19 } from 'nostr-tools'; +import type { Filter } from 'nostr-tools'; +import { renderEvent, renderPublicationFromEvents } from './index.js'; +import type { NostrEventLike } from './types.js'; + +const RELAY = 'wss://thecitadel.nostr1.com'; +const OUTPUT_DIR = join(process.cwd(), 'integration-output'); +const FIXTURES_DIR = join(process.cwd(), 'integration-fixtures'); +const SINGLE_EVENT_FIXTURE = join(FIXTURES_DIR, 'single-event.json'); +const PUBLICATION_FIXTURE = join(FIXTURES_DIR, 'publication.json'); + +// Single event (30818 or 30041) +const NADDR_SINGLE = + 'naddr1qvzqqqr4typzqez7hqy2ca5f7z94zslmu7489zd645hrhurfeqwj5g4q6we438qcqydhwumn8ghj7mmjd3uj6un9d3shjtnfd4mkzmry9ejh2tcpzfmhxue69uhkummnw3eryvfwvdhk6tcqzfshxcmfd9jx7cedw3jhxapddehhgegxwscd3'; + +// Publication (30040 + 30041s) +const NADDR_PUBLICATION = + 'naddr1qvzqqqr4tqpzplfq3m5v3u5r0q9f255fdeyz8nyac6lagssx8zy4wugxjs8ajf7pqyd8wumn8ghj7argv4nx7un9wd6zumn0wd68yvfwvdhk6qg6waehxw309ahhymre94ex2mrp0yhxjmthv9kxgtn9w5q3qamnwvaz7tmwdaehgu3wd3skueqpr9mhxue69uhkvun9v4kxz7fwwdhhvcnfwshxsmmnwsq3kamnwvaz7tm5dpjkx6t5v9jx2mpwdehhxarjxyhxxmmdqyg8wumn8ghj7mn0wd68ytnhd9hx2qghwaehxw309ahx7um5wgh8xmmkvf5hgtngdaehgqg3waehxw309ahx7um5wgerztnrdaksqrtyda3h2mt9de6z6ar9wd6qr2h2sv'; + +const runIntegration = !!process.env.RUN_INTEGRATION; +const recordFixtures = !!process.env.RECORD_FIXTURES; + +function toNostrEventLike(evt: { kind: number; content: string; tags: string[][]; pubkey: string; id?: string }): NostrEventLike { + return { + kind: evt.kind, + content: evt.content, + tags: evt.tags, + pubkey: evt.pubkey, + id: evt.id, + }; +} + +function writeHtml(filename: string, body: string, toc = '') { + mkdirSync(OUTPUT_DIR, { recursive: true }); + const fullPath = join(OUTPUT_DIR, filename); + const html = ` + + + + + ${filename.replace('.html', '')} + + + +${toc ? `

Contents

${toc}
` : ''} +${body} + +`; + writeFileSync(fullPath, html, 'utf-8'); + console.log(`Wrote ${fullPath}`); +} + +function saveFixture(path: string, data: unknown) { + mkdirSync(FIXTURES_DIR, { recursive: true }); + writeFileSync(path, JSON.stringify(data, null, 2), 'utf-8'); + console.log(`Wrote fixture ${path}`); +} + +describe.skipIf(!runIntegration)('integration: render from fixtures (or record from relay)', () => { + const pool = new SimplePool(); + + afterAll(() => { + pool.destroy(); + }); + + it('single event (30818/30041): load from fixture or fetch and record, then render HTML', async () => { + let event: NostrEventLike; + + if (existsSync(SINGLE_EVENT_FIXTURE) && !recordFixtures) { + const raw = JSON.parse(readFileSync(SINGLE_EVENT_FIXTURE, 'utf-8')); + event = raw as NostrEventLike; + } else { + const decoded = nip19.decode(NADDR_SINGLE); + if (decoded.type !== 'naddr') throw new Error('expected naddr'); + const { kind, pubkey, identifier } = decoded.data; + const filter: Filter = { + kinds: [kind], + authors: [pubkey], + limit: 1, + }; + if (identifier) filter['#d'] = [identifier]; + const ev = await pool.get([RELAY], filter, { maxWait: 10000 }); + expect(ev).not.toBeNull(); + if (!ev) return; + event = toNostrEventLike(ev); + if (recordFixtures) saveFixture(SINGLE_EVENT_FIXTURE, event); + } + + const html = renderEvent(event); + expect(html).toBeDefined(); + expect(typeof html).toBe('string'); + expect(html.length).toBeGreaterThan(0); + expect(html).toMatch(/<[a-z]+/); + writeHtml('single-event.html', html); + }, recordFixtures ? 15000 : 5000); + + it('publication (30040): load from fixture or fetch and record, then render one HTML page', async () => { + let indexEvent: NostrEventLike; + let sectionEvents: NostrEventLike[]; + + if (existsSync(PUBLICATION_FIXTURE) && !recordFixtures) { + const raw = JSON.parse(readFileSync(PUBLICATION_FIXTURE, 'utf-8')) as { + indexEvent: NostrEventLike; + sectionEvents: NostrEventLike[]; + }; + indexEvent = raw.indexEvent; + sectionEvents = raw.sectionEvents; + } else { + const decoded = nip19.decode(NADDR_PUBLICATION); + if (decoded.type !== 'naddr') throw new Error('expected naddr'); + const { kind, pubkey, identifier } = decoded.data; + expect(kind).toBe(30040); + const indexFilter: Filter = { + kinds: [30040], + authors: [pubkey], + limit: 1, + }; + if (identifier) indexFilter['#d'] = [identifier]; + const indexEv = await pool.get([RELAY], indexFilter, { maxWait: 15000 }); + expect(indexEv).not.toBeNull(); + if (!indexEv) return; + indexEvent = toNostrEventLike(indexEv); + const aTags = indexEv.tags.filter((t) => t[0] === 'a').map((t) => t[1]); + sectionEvents = []; + for (const a of aTags) { + const parts = a.split(':'); + if (parts.length < 3) continue; + const [kindStr, author, d] = parts; + const sectionKind = parseInt(kindStr!, 10); + const sectionFilter: Filter = { + kinds: [sectionKind], + authors: [author], + '#d': [d], + limit: 1, + }; + const ev = await pool.get([RELAY], sectionFilter, { maxWait: 8000 }); + if (ev) sectionEvents.push(toNostrEventLike(ev)); + } + if (recordFixtures) saveFixture(PUBLICATION_FIXTURE, { indexEvent, sectionEvents }); + } + + const result = renderPublicationFromEvents(indexEvent, sectionEvents, { toc: true }); + expect(result.html).toBeDefined(); + expect(result.html.length).toBeGreaterThan(0); + expect(result.html).toMatch(/ { + it('renders a simple paragraph', () => { + const result = parseAsciiDoc('Hello, world.'); + expect(result.html).toContain('Hello, world.'); + expect(result.html).toMatch(/]*>[\s\S]*Hello, world\./); + expect(result.tableOfContents).toBe(''); + }); + + it('renders level-1 heading as h1', () => { + const result = parseAsciiDoc('= The Title'); + expect(result.html).toContain('The Title'); + expect(result.html).toMatch(/]*>[\s\S]*The Title/); + }); + + it('renders level-2 heading as h2', () => { + const result = parseAsciiDoc('== Section'); + expect(result.html).toMatch(/]*>[\s\S]*Section/); + }); + + it('renders bold and italic', () => { + const result = parseAsciiDoc('*bold* and _italic_'); + expect(result.html).toContain('bold'); + expect(result.html).toContain('italic'); + expect(result.html).toMatch(/]*>[\s\S]*bold/); + expect(result.html).toMatch(/]*>[\s\S]*italic/); + }); + + it('returns empty tableOfContents when toc is false', () => { + const doc = `= Doc +== A +== B`; + const result = parseAsciiDoc(doc, { toc: false }); + expect(result.tableOfContents).toBe(''); + }); + + it('extracts table of contents when toc is true', () => { + const doc = `= Doc +== Section A +== Section B`; + const result = parseAsciiDoc(doc, { toc: true }); + expect(result.tableOfContents).toContain('Section A'); + expect(result.tableOfContents).toContain('Section B'); + expect(result.html).toContain('Section A'); + }); + + it('renders a list', () => { + const result = parseAsciiDoc(`* one +* two`); + expect(result.html).toMatch(/