diff --git a/AGENTS.md b/AGENTS.md index 7be82c617..8e6291880 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -86,7 +86,7 @@ packages/comark/ │ │ ├── task-list.ts # GFM task lists │ │ └── toc.ts # Table of contents │ ├── utils/ # Shared utilities (comark/utils entry point) -│ │ ├── index.ts # textContent(), visit(), visitAsync(), escapeHtml(), indent(), string/object utils +│ │ ├── index.ts # textContent(), visit(), visitAsync(), escapeHtml(), isHtmlVoidElement(), indent(), string/object utils │ │ ├── helpers.ts # defineComarkPlugin(), dedupePlugins() │ │ ├── trace.ts # ComarkTracer helpers: noopTracer, withSpan() (comark/utils/trace) │ │ └── caret.ts # Caret utilities for streaming @@ -481,7 +481,15 @@ import { renderMarkdown } from 'comark/render' // Document model types and utilities import type { MarkdownDocument, Node, ElementNode, TextNode, CommentNode } from 'comark' -import { textContent, visit, visitAsync, escapeHtml, isMarkdownDocument } from 'comark/utils' +import { + textContent, + visit, + visitAsync, + escapeHtml, + isHtmlVoidElement, + HTML_VOID_ELEMENTS, + isMarkdownDocument, +} from 'comark/utils' import { noopTracer, withSpan } from 'comark/utils/trace' // Core plugins — use when calling parseMarkdown() directly (framework-agnostic) @@ -747,6 +755,16 @@ SPEC coverage: `SPEC/COMARK/component-nested-*-outdented.md`, `SPEC/COMARK/component-nested-codeblock-indented.md`, `SPEC/COMARK/codeblock-indented-content.md`. +## Embedded HTML + +Contract: `packages/comark/SPEC/HTML/README.md`. HTML nodes carry +`$: { html: 1, block: 0 | 1 }`. `block` means the tag outlived the paragraph +that opened it, not that the tag is a block element. `html({ markdown: false })` +keeps closed-block text literal; a blank line still nests real markdown under +the open tag. ``) + + expect(pre.nodes).toEqual([['pre', { $: { html: 1, block: 1 }, class: 'x' }, '\n const x = 1\n']]) + expect(style.nodes).toEqual([['style', { $: { html: 1, block: 1 }, class: 'x' }, '\n .a { color: red; }\n']]) + }) + + it('styles', async () => { + const result = await parseMarkdown(` + +

This is a warning message.

+

Your changes have been saved.

+

More information is available here.

`) + + expect(result.nodes).toEqual([ + [ + 'style', + { + $: { + block: 1, + html: 1, + }, + }, + `\n .warning { + color: red; + } + .success { + color: green; + } + + .info { + color: blue; + } + `, + ], + ['p', { $: { html: 1, block: 0 }, class: 'warning' }, 'This is a warning message.'], + ['p', { $: { html: 1, block: 0 }, class: 'success' }, 'Your changes have been saved.'], + ['p', { $: { html: 1, block: 0 }, class: 'info' }, 'More information is available here.'], + ]) + }) + + it('two consecutive tags without a blank line between close and open', async () => { + const result = await parseMarkdown(` +
+This is a warning message. +
+ +Your changes have been saved. + +
+ `) + + // First div closes inside its opening paragraph → block: 0 (text-only body). + // Second spans a free markdown paragraph → block: 1. + expect(result.nodes).toEqual([ + ['div', { $: { html: 1, block: 0 } }, 'This is a warning message.'], + ['div', { $: { html: 1, block: 1 } }, 'Your changes have been saved.'], + ]) + }) +}) + +// HTML opened inside a markdown container must stay inside (and close with) that container. +describe('HTML inside markdown containers', () => { + it('keeps inline HTML content inside a table cell', async () => { + const result = await parseMarkdown('| a |\n|---|\n| x |') + + expect(result.nodes).toEqual([ + [ + 'table', + {}, + ['thead', {}, ['tr', {}, ['th', {}, 'a']]], + ['tbody', {}, ['tr', {}, ['td', {}, ['b', { $: { html: 1, block: 0 } }, 'x']]]], + ], + ]) + }) + + it('keeps block HTML content inside a blockquote', async () => { + const result = await parseMarkdown('>
\n> hi\n>
') + + expect(result.nodes).toEqual([['blockquote', {}, ['div', { $: { html: 1, block: 1 } }, 'hi']]]) + }) + + it('closes an unclosed inline tag at the end of its list item', async () => { + const result = await parseMarkdown('- a\n- b') + + expect(result.nodes).toEqual([['ul', {}, ['li', {}, ['span', { $: { html: 1, block: 1 } }, 'a']], ['li', {}, 'b']]]) + }) +}) + +describe('stray HTML close tags', () => { + it('keeps the surrounding paragraph when a close tag has no opener', async () => { + const result = await parseMarkdown(' orphan') + + expect(result.nodes).toEqual([['p', {}, ' orphan']]) + }) }) diff --git a/packages/comark/test/html-escape.test.ts b/packages/comark/test/html-escape.test.ts index 81bc7a351..81120661a 100644 --- a/packages/comark/test/html-escape.test.ts +++ b/packages/comark/test/html-escape.test.ts @@ -27,6 +27,18 @@ describe('HTML attribute escaping', () => { expect(html).not.toContain('title="a" onmouseover=alert(1)"') }) + it('decodes " inside quoted HTML attribute values', async () => { + const tree = await parseMarkdown('hi') + const p = tree.nodes[0] as [string, Record, ...Node[]] + const span = (Array.isArray(p[2]) ? p[2] : p) as [string, Record, ...Node[]] + expect(span[0]).toBe('span') + expect(span[1].title).toBe('A "quote"') + + const html = await renderHtml('hi') + expect(html).toContain('title="A "quote""') + expect(html).not.toContain('title="A "quote""') + }) + it('escapes quotes in component attribute props', async () => { const html = await renderHtml(`:span[hi]{title='a" onmouseover=alert(1) x="b'}`) expect(html).toContain('title="a" onmouseover=alert(1) x="b"') @@ -59,6 +71,16 @@ describe('HTML attribute escaping', () => { expect(html).toContain('title="a&b<c>d"') }) + it('escapes bare ampersands in URL attribute values', async () => { + // Query-string `&` must become `&` in HTML attrs (HTML5). + // Re-parse still yields bare `&` because attribute entities are decoded on parse. + const html = await renderHtml(`x`) + expect(html).toContain('src="https://x.com/?a=1&b=2"') + const tree = await parseMarkdown(html) + const img = tree.nodes[0] as [string, Record] + expect(img[1].src).toBe('https://x.com/?a=1&b=2') + }) + it('escapes object attribute values as JSON with entities', async () => { const html = await renderNodes([['div', { ':data': { x: '">' } }, 'hi']]) expect(html).not.toContain('') diff --git a/packages/comark/test/index.test.ts b/packages/comark/test/index.test.ts index e2d16478a..128c1fa37 100644 --- a/packages/comark/test/index.test.ts +++ b/packages/comark/test/index.test.ts @@ -178,7 +178,7 @@ function findMarkdownFiles(dir: string, baseDir: string = dir): string[] { if (entry.isDirectory()) { files.push(...findMarkdownFiles(fullPath, baseDir)) - } else if (entry.isFile() && entry.name.endsWith('.md')) { + } else if (entry.isFile() && entry.name.endsWith('.md') && entry.name !== 'README.md') { files.push(relativePath) } } diff --git a/packages/comark/test/is-html-void-element.test.ts b/packages/comark/test/is-html-void-element.test.ts new file mode 100644 index 000000000..57e6d5ee7 --- /dev/null +++ b/packages/comark/test/is-html-void-element.test.ts @@ -0,0 +1,18 @@ +import { describe, expect, it } from 'vitest' +import { HTML_VOID_ELEMENTS, isHtmlVoidElement } from '../src/utils/index.ts' + +describe('isHtmlVoidElement', () => { + it('matches the HTML void element set, case-insensitively', () => { + expect(isHtmlVoidElement('br')).toBe(true) + expect(isHtmlVoidElement('IMG')).toBe(true) + expect(isHtmlVoidElement('Wbr')).toBe(true) + expect(HTML_VOID_ELEMENTS.has('br')).toBe(true) + }) + + it('rejects elements that can have children', () => { + expect(isHtmlVoidElement('div')).toBe(false) + expect(isHtmlVoidElement('p')).toBe(false) + expect(isHtmlVoidElement('span')).toBe(false) + expect(isHtmlVoidElement('')).toBe(false) + }) +}) diff --git a/packages/comark/test/plugins/default-plugins.test.ts b/packages/comark/test/plugins/default-plugins.test.ts index d47605e58..bcd970051 100644 --- a/packages/comark/test/plugins/default-plugins.test.ts +++ b/packages/comark/test/plugins/default-plugins.test.ts @@ -25,7 +25,7 @@ describe('default plugin options', () => { it('parses HTML by default', async () => { const tree = await parseMarkdown('Hello') - expect(tree.nodes).toEqual([['p', {}, ['strong', { class: 'bold', $: { html: 1, block: 0 } }, 'Hello']]]) + expect(tree.nodes).toEqual([['strong', { class: 'bold', $: { html: 1, block: 0 } }, 'Hello']]) }) it('parses frontmatter by default', async () => { @@ -64,7 +64,7 @@ describe('default plugin options', () => { registerDefaultPlugins: false, plugins: [html()], }) - expect(tree.nodes).toEqual([['p', {}, ['em', { $: { html: 1, block: 0 } }, 'hi']]]) + expect(tree.nodes).toEqual([['em', { $: { html: 1, block: 0 } }, 'hi']]) }) it('treats attribute braces as plain text when registerDefaultPlugins is false', async () => { diff --git a/packages/comark/test/plugins/summary.test.ts b/packages/comark/test/plugins/summary.test.ts index c820e45f1..fca097c3d 100644 --- a/packages/comark/test/plugins/summary.test.ts +++ b/packages/comark/test/plugins/summary.test.ts @@ -13,7 +13,6 @@ After the fold. describe('summary plugin', () => { it('writes the nodes before the delimiter to tree.meta.summary', async () => { const tree = await parseMarkdown(CONTENT, { plugins: [summary()] }) - expect(tree.meta.summary).toEqual([['p', {}, 'Intro paragraph.']]) }) diff --git a/packages/comark/test/visit.test.ts b/packages/comark/test/visit.test.ts index a6b6df40e..ebe8e1e52 100644 --- a/packages/comark/test/visit.test.ts +++ b/packages/comark/test/visit.test.ts @@ -1,5 +1,5 @@ import { describe, expect, it } from 'vitest' -import { visit } from 'comark/utils' +import { TREE_WALK_MAX_DEPTH, visit, visitAsync } from 'comark/utils' import type { MarkdownDocument, Node } from 'comark' describe('visit', () => { @@ -409,4 +409,70 @@ describe('visit', () => { expect(tree.nodes[1]).toEqual(['section', {}, 'Replaced']) expect(tree.nodes[2]).toEqual(['span', {}, 'Keep']) }) + + it('should visit nodes within the depth cap', () => { + // TREE_WALK_MAX_DEPTH - 1 wrappers + text — text is at depth N-1 and still visited. + let node: Node = 'Deep' + for (let i = TREE_WALK_MAX_DEPTH - 2; i >= 0; i--) { + node = [`d${i}`, {}, node] + } + + const tree: MarkdownDocument = { nodes: [node], frontmatter: {}, meta: {} } + const visited: string[] = [] + visit( + tree, + () => true, + (n) => { + visited.push(typeof n === 'string' ? n : String(n[0])) + } + ) + + const expected = Array.from({ length: TREE_WALK_MAX_DEPTH - 1 }, (_, i) => `d${i}`) + expected.push('Deep') + expect(visited).toEqual(expected) + }) + + it('should drop nodes past TREE_WALK_MAX_DEPTH element levels', () => { + // TREE_WALK_MAX_DEPTH + 1 wrappers + text — last wrapper and its children are past the cap. + let node: Node = 'Too deep' + for (let i = TREE_WALK_MAX_DEPTH; i >= 0; i--) { + node = [`d${i}`, {}, node] + } + + const tree: MarkdownDocument = { nodes: [node], frontmatter: {}, meta: {} } + const visited: string[] = [] + visit( + tree, + () => true, + (n) => { + visited.push(typeof n === 'string' ? n : String(n[0])) + } + ) + + expect(visited).toEqual(Array.from({ length: TREE_WALK_MAX_DEPTH }, (_, i) => `d${i}`)) + + let cursor = tree.nodes[0] as Node[] + for (let i = 0; i < TREE_WALK_MAX_DEPTH - 1; i++) { + expect(cursor[0]).toBe(`d${i}`) + cursor = cursor[2] as Node[] + } + // The last visited wrapper keeps no children: everything deeper is removed. + expect(cursor).toEqual([`d${TREE_WALK_MAX_DEPTH - 1}`, {}]) + }) + + it('should drop deep subtrees in visitAsync so unvisited content cannot survive', async () => { + let node: Node = ['img', { onerror: 'alert(1)' }] + for (let i = TREE_WALK_MAX_DEPTH + 10; i >= 0; i--) { + node = ['blockquote', {}, node] + } + + const tree: MarkdownDocument = { nodes: [node], frontmatter: {}, meta: {} } + await visitAsync( + tree, + () => true, + () => undefined + ) + + expect(JSON.stringify(tree.nodes)).not.toContain('onerror') + }) }) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index c06d21dc3..1d238427e 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -323,9 +323,6 @@ catalogs: hast-util-to-string: specifier: ^3.0.1 version: 3.0.1 - htmlparser2: - specifier: ^12.0.0 - version: 12.0.0 js-yaml: specifier: ^5.4.2 version: 5.4.2 @@ -693,7 +690,7 @@ importers: version: link:../../../packages/comark-vue '@nuxt/ui': specifier: 'catalog:' - version: 4.11.2(c77efd868fa853b6efbc5c86610e740e) + version: 4.11.2(fa5844bfdc74c64dd01c986666e59821) nuxt: specifier: 'catalog:' version: 4.5.2(@babel/plugin-syntax-jsx@7.29.7(@babel/core@7.29.7(supports-color@10.2.2)))(@babel/plugin-syntax-typescript@7.29.7(@babel/core@7.29.7(supports-color@10.2.2)))(@oxc-project/types@0.150.0)(@parcel/watcher@2.6.0)(@types/node@26.6.2)(@vercel/functions@3.9.8(ws@8.21.3))(@vue/compiler-sfc@3.5.43)(commander@15.0.0)(db0@0.3.4)(esbuild@0.28.2)(ioredis@5.11.1(supports-color@10.2.2))(lightningcss@1.33.0)(magicast@0.5.5)(oxc-parser@0.149.0)(oxlint@1.85.0)(rolldown@1.2.9)(rollup-plugin-visualizer@7.1.1(rolldown@1.2.9)(rollup@4.63.4))(rollup@4.63.4)(sass@1.101.0)(srvx@0.11.22)(supports-color@10.2.2)(terser@5.51.2)(tsx@4.23.15)(typescript@6.0.3)(vite@8.3.0(@types/node@26.6.2)(esbuild@0.28.2)(jiti@2.7.0)(sass@1.101.0)(terser@5.51.2)(tsx@4.23.15)(yaml@2.9.1))(vue-tsc@3.3.11(typescript@6.0.3))(yaml@2.9.1) @@ -1410,9 +1407,6 @@ importers: entities: specifier: 'catalog:' version: 8.1.0 - htmlparser2: - specifier: 'catalog:' - version: 12.0.0 js-yaml: specifier: 'catalog:' version: 5.4.2 @@ -13104,7 +13098,7 @@ snapshots: rc9: 3.1.0 std-env: 4.2.0 - '@nuxt/ui@4.11.2(c77efd868fa853b6efbc5c86610e740e)': + '@nuxt/ui@4.11.2(48eec6f14dd5da94c5723622cb20c05a)': dependencies: '@floating-ui/dom': 1.8.0 '@iconify/vue': 5.0.2(vue@3.5.43(typescript@6.0.3)) @@ -13174,7 +13168,6 @@ snapshots: '@internationalized/date': 3.12.4 '@internationalized/number': 3.6.8 ai: 7.0.112(zod@4.6.5) - vue-router: 5.3.1(@vue/compiler-sfc@3.5.43)(esbuild@0.28.2)(rolldown@1.2.9)(rollup@4.63.4)(vite@8.3.0(@types/node@26.6.2)(esbuild@0.28.2)(jiti@2.7.0)(sass@1.101.0)(terser@5.51.2)(tsx@4.23.15)(yaml@2.9.1))(vue@3.5.43(typescript@6.0.3)) zod: 4.6.5 transitivePeerDependencies: - '@azure/app-configuration' @@ -13352,6 +13345,130 @@ snapshots: - vue - webpack + '@nuxt/ui@4.11.2(fa5844bfdc74c64dd01c986666e59821)': + dependencies: + '@floating-ui/dom': 1.8.0 + '@iconify/vue': 5.0.2(vue@3.5.43(typescript@6.0.3)) + '@nuxt/fonts': 0.14.0(@vercel/functions@3.9.8(ws@8.21.3))(db0@0.3.4)(esbuild@0.28.2)(ioredis@5.11.1(supports-color@10.2.2))(magic-string@1.4.1)(magicast@0.5.5)(oxc-parser@0.149.0)(rolldown@1.2.9)(rollup@4.63.4)(vite@8.3.0(@types/node@26.6.2)(esbuild@0.28.2)(jiti@2.7.0)(sass@1.101.0)(terser@5.51.2)(tsx@4.23.15)(yaml@2.9.1)) + '@nuxt/icon': 2.5.1(magic-string@1.4.1)(magicast@0.5.5)(oxc-parser@0.149.0)(rolldown@1.2.9)(unplugin@3.4.0(esbuild@0.28.2)(rolldown@1.2.9)(rollup@4.63.4)(vite@8.3.0(@types/node@26.6.2)(esbuild@0.28.2)(jiti@2.7.0)(sass@1.101.0)(terser@5.51.2)(tsx@4.23.15)(yaml@2.9.1)))(vite@8.3.0(@types/node@26.6.2)(esbuild@0.28.2)(jiti@2.7.0)(sass@1.101.0)(terser@5.51.2)(tsx@4.23.15)(yaml@2.9.1))(vue@3.5.43(typescript@6.0.3)) + '@nuxt/kit': 4.5.2(magic-string@1.4.1)(magicast@0.5.5)(oxc-parser@0.149.0)(rolldown@1.2.9)(unplugin@3.4.0(esbuild@0.28.2)(rolldown@1.2.9)(rollup@4.63.4)(vite@8.3.0(@types/node@26.6.2)(esbuild@0.28.2)(jiti@2.7.0)(sass@1.101.0)(terser@5.51.2)(tsx@4.23.15)(yaml@2.9.1))) + '@nuxt/schema': 4.5.2 + '@nuxtjs/color-mode': 4.0.1(magic-string@1.4.1)(magicast@0.5.5)(oxc-parser@0.149.0)(rolldown@1.2.9)(unplugin@3.4.0(esbuild@0.28.2)(rolldown@1.2.9)(rollup@4.63.4)(vite@8.3.0(@types/node@26.6.2)(esbuild@0.28.2)(jiti@2.7.0)(sass@1.101.0)(terser@5.51.2)(tsx@4.23.15)(yaml@2.9.1))) + '@standard-schema/spec': 1.1.0 + '@tailwindcss/postcss': 4.3.3 + '@tailwindcss/vite': 4.3.3(vite@8.3.0(@types/node@26.6.2)(esbuild@0.28.2)(jiti@2.7.0)(sass@1.101.0)(terser@5.51.2)(tsx@4.23.15)(yaml@2.9.1)) + '@tanstack/vue-table': 8.21.3(vue@3.5.43(typescript@6.0.3)) + '@tanstack/vue-virtual': 3.13.39(vue@3.5.43(typescript@6.0.3)) + '@tiptap/core': 3.31.3(@tiptap/pm@3.31.3) + '@tiptap/extension-bubble-menu': 3.31.3(@tiptap/core@3.31.3(@tiptap/pm@3.31.3))(@tiptap/pm@3.31.3) + '@tiptap/extension-code': 3.31.3(@tiptap/core@3.31.3(@tiptap/pm@3.31.3)) + '@tiptap/extension-collaboration': 3.31.3(@tiptap/core@3.31.3(@tiptap/pm@3.31.3))(@tiptap/pm@3.31.3)(@tiptap/y-tiptap@3.0.9(prosemirror-model@1.25.11)(prosemirror-state@1.4.4)(prosemirror-view@1.42.4)(y-protocols@1.0.7(yjs@13.6.32))(yjs@13.6.32))(yjs@13.6.32) + '@tiptap/extension-drag-handle': 3.31.3(@tiptap/core@3.31.3(@tiptap/pm@3.31.3))(@tiptap/extension-collaboration@3.31.3(@tiptap/core@3.31.3(@tiptap/pm@3.31.3))(@tiptap/pm@3.31.3)(@tiptap/y-tiptap@3.0.9))(@tiptap/extension-node-range@3.31.3(@tiptap/core@3.31.3(@tiptap/pm@3.31.3))(@tiptap/pm@3.31.3))(@tiptap/pm@3.31.3)(@tiptap/y-tiptap@3.0.9) + '@tiptap/extension-drag-handle-vue-3': 3.31.3(@tiptap/extension-drag-handle@3.31.3(@tiptap/core@3.31.3(@tiptap/pm@3.31.3))(@tiptap/extension-collaboration@3.31.3(@tiptap/core@3.31.3(@tiptap/pm@3.31.3))(@tiptap/pm@3.31.3)(@tiptap/y-tiptap@3.0.9))(@tiptap/extension-node-range@3.31.3(@tiptap/core@3.31.3(@tiptap/pm@3.31.3))(@tiptap/pm@3.31.3))(@tiptap/pm@3.31.3)(@tiptap/y-tiptap@3.0.9))(@tiptap/pm@3.31.3)(@tiptap/vue-3@3.31.3(@floating-ui/dom@1.8.0)(@tiptap/core@3.31.3(@tiptap/pm@3.31.3))(@tiptap/pm@3.31.3)(vue@3.5.43(typescript@6.0.3)))(vue@3.5.43(typescript@6.0.3)) + '@tiptap/extension-floating-menu': 3.31.3(@floating-ui/dom@1.8.0)(@tiptap/core@3.31.3(@tiptap/pm@3.31.3))(@tiptap/pm@3.31.3) + '@tiptap/extension-horizontal-rule': 3.31.3(@tiptap/core@3.31.3(@tiptap/pm@3.31.3))(@tiptap/pm@3.31.3) + '@tiptap/extension-image': 3.31.3(@tiptap/core@3.31.3(@tiptap/pm@3.31.3)) + '@tiptap/extension-mention': 3.31.3(@tiptap/core@3.31.3(@tiptap/pm@3.31.3))(@tiptap/pm@3.31.3)(@tiptap/suggestion@3.31.3(@floating-ui/dom@1.8.0)(@tiptap/core@3.31.3(@tiptap/pm@3.31.3))(@tiptap/pm@3.31.3)) + '@tiptap/extension-node-range': 3.31.3(@tiptap/core@3.31.3(@tiptap/pm@3.31.3))(@tiptap/pm@3.31.3) + '@tiptap/extension-placeholder': 3.31.3(@tiptap/extensions@3.31.3(@tiptap/core@3.31.3(@tiptap/pm@3.31.3))(@tiptap/pm@3.31.3)) + '@tiptap/markdown': 3.31.3(@tiptap/core@3.31.3(@tiptap/pm@3.31.3))(@tiptap/pm@3.31.3) + '@tiptap/pm': 3.31.3 + '@tiptap/starter-kit': 3.31.3 + '@tiptap/suggestion': 3.31.3(@floating-ui/dom@1.8.0)(@tiptap/core@3.31.3(@tiptap/pm@3.31.3))(@tiptap/pm@3.31.3) + '@tiptap/vue-3': 3.31.3(@floating-ui/dom@1.8.0)(@tiptap/core@3.31.3(@tiptap/pm@3.31.3))(@tiptap/pm@3.31.3)(vue@3.5.43(typescript@6.0.3)) + '@unhead/vue': 3.4.1(@oxc-project/types@0.150.0)(esbuild@0.28.2)(lightningcss@1.33.0)(oxc-parser@0.149.0)(rolldown@1.2.9)(rollup@4.63.4)(vite@8.3.0(@types/node@26.6.2)(esbuild@0.28.2)(jiti@2.7.0)(sass@1.101.0)(terser@5.51.2)(tsx@4.23.15)(yaml@2.9.1))(vue@3.5.43(typescript@6.0.3)) + '@vueuse/core': 14.4.0(vue@3.5.43(typescript@6.0.3)) + '@vueuse/integrations': 14.4.0(focus-trap@7.8.0)(fuse.js@7.5.0)(vue@3.5.43(typescript@6.0.3)) + '@vueuse/shared': 14.4.0(vue@3.5.43(typescript@6.0.3)) + colortranslator: 5.0.0 + consola: 3.4.2 + defu: 6.1.7 + embla-carousel-auto-height: 8.6.0(embla-carousel@8.6.0) + embla-carousel-auto-scroll: 8.6.0(embla-carousel@8.6.0) + embla-carousel-autoplay: 8.6.0(embla-carousel@8.6.0) + embla-carousel-class-names: 8.6.0(embla-carousel@8.6.0) + embla-carousel-fade: 8.6.0(embla-carousel@8.6.0) + embla-carousel-vue: 8.6.0(vue@3.5.43(typescript@6.0.3)) + embla-carousel-wheel-gestures: 8.1.0(embla-carousel@8.6.0) + fuse.js: 7.5.0 + hookable: 6.1.2 + knitwork: 1.3.0 + magic-string: 1.4.1 + mlly: 1.8.2 + motion-v: 2.4.4(@vueuse/core@14.4.0(vue@3.5.43(typescript@6.0.3)))(react-dom@19.3.0(react@19.3.0))(react@19.3.0)(vue@3.5.43(typescript@6.0.3)) + ohash: 2.0.12 + pathe: 2.0.3 + reka-ui: 2.10.4(vue@3.5.43(typescript@6.0.3)) + scule: 1.3.0 + tailwind-merge: 3.7.0 + tailwind-variants: 3.3.1(tailwind-merge@3.7.0)(tailwindcss@4.3.3) + tailwindcss: 4.3.3 + tinyglobby: 0.2.17 + typescript: 6.0.3 + ufo: 1.6.4 + unplugin: 3.4.0(esbuild@0.28.2)(rolldown@1.2.9)(rollup@4.63.4)(vite@8.3.0(@types/node@26.6.2)(esbuild@0.28.2)(jiti@2.7.0)(sass@1.101.0)(terser@5.51.2)(tsx@4.23.15)(yaml@2.9.1)) + unplugin-auto-import: 21.1.0(@nuxt/kit@4.5.2(magic-string@1.4.1)(magicast@0.5.5)(oxc-parser@0.149.0)(rolldown@1.2.9)(unplugin@3.4.0(esbuild@0.28.2)(rolldown@1.2.9)(rollup@4.63.4)(vite@8.3.0(@types/node@26.6.2)(esbuild@0.28.2)(jiti@2.7.0)(sass@1.101.0)(terser@5.51.2)(tsx@4.23.15)(yaml@2.9.1))))(@vueuse/core@14.4.0(vue@3.5.43(typescript@6.0.3)))(esbuild@0.28.2)(oxc-parser@0.149.0)(rolldown@1.2.9)(rollup@4.63.4)(vite@8.3.0(@types/node@26.6.2)(esbuild@0.28.2)(jiti@2.7.0)(sass@1.101.0)(terser@5.51.2)(tsx@4.23.15)(yaml@2.9.1)) + unplugin-vue-components: 32.1.0(@nuxt/kit@4.5.2(magic-string@1.4.1)(magicast@0.5.5)(oxc-parser@0.149.0)(rolldown@1.2.9)(unplugin@3.4.0(esbuild@0.28.2)(rolldown@1.2.9)(rollup@4.63.4)(vite@8.3.0(@types/node@26.6.2)(esbuild@0.28.2)(jiti@2.7.0)(sass@1.101.0)(terser@5.51.2)(tsx@4.23.15)(yaml@2.9.1))))(esbuild@0.28.2)(rolldown@1.2.9)(rollup@4.63.4)(vite@8.3.0(@types/node@26.6.2)(esbuild@0.28.2)(jiti@2.7.0)(sass@1.101.0)(terser@5.51.2)(tsx@4.23.15)(yaml@2.9.1))(vue@3.5.43(typescript@6.0.3)) + vaul-vue: 0.4.1(reka-ui@2.10.4(vue@3.5.43(typescript@6.0.3)))(vue@3.5.43(typescript@6.0.3)) + vue-component-type-helpers: 3.3.11 + optionalDependencies: + '@internationalized/date': 3.12.4 + '@internationalized/number': 3.6.8 + ai: 7.0.112(zod@4.6.5) + vue-router: 5.3.1(@vue/compiler-sfc@3.5.43)(esbuild@0.28.2)(rolldown@1.2.9)(rollup@4.63.4)(vite@8.3.0(@types/node@26.6.2)(esbuild@0.28.2)(jiti@2.7.0)(sass@1.101.0)(terser@5.51.2)(tsx@4.23.15)(yaml@2.9.1))(vue@3.5.43(typescript@6.0.3)) + zod: 4.6.5 + transitivePeerDependencies: + - '@azure/app-configuration' + - '@azure/cosmos' + - '@azure/data-tables' + - '@azure/identity' + - '@azure/keyvault-secrets' + - '@azure/storage-blob' + - '@capacitor/preferences' + - '@deno/kv' + - '@farmfe/core' + - '@netlify/blobs' + - '@oxc-project/types' + - '@planetscale/database' + - '@rsbuild/core' + - '@rspack/core' + - '@unhead/cli' + - '@upstash/redis' + - '@vercel/blob' + - '@vercel/functions' + - '@vercel/kv' + - '@vitejs/devtools-kit' + - '@vue/composition-api' + - async-validator + - aws4fetch + - axios + - bun-types-no-globals + - change-case + - db0 + - drauu + - embla-carousel + - esbuild + - focus-trap + - idb-keyval + - ioredis + - jwt-decode + - lightningcss + - magicast + - nprogress + - oxc-parser + - qrcode + - react + - react-dom + - rolldown + - rollup + - sortablejs + - universal-cookie + - unloader + - uploadthing + - vite + - vue + - webpack + '@nuxt/vite-builder@4.5.2(0ca8c220c7096baa3c427dc8d7242a87)': dependencies: '@nuxt/kit': 4.5.2(magic-string@1.4.1)(magicast@0.5.5)(oxc-parser@0.149.0)(rolldown@1.2.9)(unplugin@3.4.0(esbuild@0.28.2)(rolldown@1.2.9)(rollup@4.63.4)(vite@8.3.0(@types/node@26.6.2)(esbuild@0.28.2)(jiti@2.7.0)(sass@1.101.0)(terser@5.51.2)(tsx@4.23.15)(yaml@2.9.1))) @@ -16144,7 +16261,7 @@ snapshots: '@iconify-json/vscode-icons': 1.2.79 '@iconify/vue': 5.0.2(vue@3.5.43(typescript@6.0.3)) '@nuxt/kit': 4.5.2(magic-string@1.4.1)(magicast@0.5.5)(oxc-parser@0.149.0)(rolldown@1.2.9)(unplugin@3.4.0(esbuild@0.28.2)(rolldown@1.2.9)(rollup@4.63.4)(vite@8.3.0(@types/node@26.6.2)(esbuild@0.28.2)(jiti@2.7.0)(sass@1.101.0)(terser@5.51.2)(tsx@4.23.15)(yaml@2.9.1))) - '@nuxt/ui': 4.11.2(c77efd868fa853b6efbc5c86610e740e) + '@nuxt/ui': 4.11.2(48eec6f14dd5da94c5723622cb20c05a) '@nuxtjs/mcp-toolkit': 0.21.0(@vue/compiler-sfc@3.5.43)(h3@1.15.11)(magic-string@1.4.1)(magicast@0.5.5)(oxc-parser@0.149.0)(rolldown@1.2.9)(rollup@4.63.4)(supports-color@10.2.2)(unplugin@3.4.0(esbuild@0.28.2)(rolldown@1.2.9)(rollup@4.63.4)(vite@8.3.0(@types/node@26.6.2)(esbuild@0.28.2)(jiti@2.7.0)(sass@1.101.0)(terser@5.51.2)(tsx@4.23.15)(yaml@2.9.1)))(vite@8.3.0(@types/node@26.6.2)(esbuild@0.28.2)(jiti@2.7.0)(sass@1.101.0)(terser@5.51.2)(tsx@4.23.15)(yaml@2.9.1))(vue@3.5.43(typescript@6.0.3))(zod@4.6.5) '@nuxtjs/robots': 6.2.3(7a54b3daef8a97a6c1e9da9b246637d3) '@nuxtjs/sitemap': 8.5.1(7a54b3daef8a97a6c1e9da9b246637d3) diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index 51c697ee5..5bae928d7 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -93,7 +93,6 @@ catalog: entities: ^8.1.0 github-slugger: ^2.0.0 hast-util-to-string: ^3.0.1 - htmlparser2: ^12.0.0 js-yaml: ^5.4.2 katex: ^0.18.7 markdown-exit: 1.3.0 diff --git a/test/bundle.test.ts b/test/bundle.test.ts index bd7e87ff7..c0e22f1f8 100644 --- a/test/bundle.test.ts +++ b/test/bundle.test.ts @@ -60,14 +60,14 @@ describe('package bundle size', { timeout: 60_000 }, () => { expect(report).toMatchInlineSnapshot(` { - "@comark/angular": "56.4k (72 files)", + "@comark/angular": "56.2k (72 files)", "@comark/ansi": "37.3k (98 files)", "@comark/html": "16.5k (58 files)", "@comark/nuxt": "11.9k (58 files)", "@comark/react": "38.0k (76 files)", - "@comark/svelte": "45.9k (84 files)", + "@comark/svelte": "45.8k (84 files)", "@comark/vue": "56.2k (80 files)", - "comark": "377k (160 files)", + "comark": "381k (150 files)", } `) })