@vortexlabs/kirari-rehype 0.0.0-stage → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +173 -2
- package/dist/index.d.ts +13 -0
- package/dist/index.js +149 -0
- package/package.json +60 -4
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Ugi (https://ugi.dev)
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,3 +1,174 @@
|
|
|
1
|
-
#
|
|
1
|
+
# @vortexlabs/kirari-rehype
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Highlight code blocks in rendered Markdown and MDX with Kirari. This optional
|
|
4
|
+
package converts Kirari tokens directly into structured HTML syntax tree nodes.
|
|
5
|
+
It does not generate raw HTML or require a DOM.
|
|
6
|
+
|
|
7
|
+
Run it while building your content to send highlighted HTML and CSS to the
|
|
8
|
+
browser, with no client-side highlighter. The integration is separate from the
|
|
9
|
+
core package and does not change its bundle.
|
|
10
|
+
|
|
11
|
+
## Install
|
|
12
|
+
|
|
13
|
+
```sh
|
|
14
|
+
npm install @vortexlabs/kirari @vortexlabs/kirari-rehype
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
The plugin uses your application's Kirari through a peer dependency.
|
|
18
|
+
Inside this workspace, use `workspace:*` for both packages instead.
|
|
19
|
+
|
|
20
|
+
## Markdown to HTML
|
|
21
|
+
|
|
22
|
+
Install `unified`, `remark-parse`, `remark-rehype`, and `rehype-stringify` in your
|
|
23
|
+
application if it does not already have a Markdown pipeline.
|
|
24
|
+
|
|
25
|
+
```ts
|
|
26
|
+
import { unified } from 'unified'
|
|
27
|
+
import remarkParse from 'remark-parse'
|
|
28
|
+
import remarkRehype from 'remark-rehype'
|
|
29
|
+
import rehypeStringify from 'rehype-stringify'
|
|
30
|
+
import rehypeKirari from '@vortexlabs/kirari-rehype'
|
|
31
|
+
import { javascript } from '@vortexlabs/kirari/languages/javascript'
|
|
32
|
+
import { typescript } from '@vortexlabs/kirari/languages/typescript'
|
|
33
|
+
|
|
34
|
+
const processor = unified()
|
|
35
|
+
.use(remarkParse)
|
|
36
|
+
.use(remarkRehype)
|
|
37
|
+
.use(rehypeKirari, { languages: [javascript, typescript] })
|
|
38
|
+
.use(rehypeStringify)
|
|
39
|
+
|
|
40
|
+
const source = '```js\nconst answer = 42\n```'
|
|
41
|
+
const html = String(await processor.process(source))
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
Import the optional theme once from your application's stylesheet entry point:
|
|
45
|
+
|
|
46
|
+
```ts
|
|
47
|
+
import '@vortexlabs/kirari/theme.css'
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
The plugin adds `.kirari` to the enclosing `<pre>`, preserves existing properties
|
|
51
|
+
and the code element's `language-*` class, and adds `.kirari-*` syntax spans.
|
|
52
|
+
Customize colors with variables such as `--kirari-keyword` and `--kirari-variable`.
|
|
53
|
+
|
|
54
|
+
## MDX
|
|
55
|
+
|
|
56
|
+
Pass the same plugin to the MDX compiler's `rehypePlugins` option:
|
|
57
|
+
|
|
58
|
+
```ts
|
|
59
|
+
import { compile } from '@mdx-js/mdx'
|
|
60
|
+
import rehypeKirari from '@vortexlabs/kirari-rehype'
|
|
61
|
+
import { javascript } from '@vortexlabs/kirari/languages/javascript'
|
|
62
|
+
|
|
63
|
+
const markdown = '```js\nconst answer = 42\n```'
|
|
64
|
+
const compiled = await compile(markdown, {
|
|
65
|
+
rehypePlugins: [[rehypeKirari, { languages: [javascript] }]],
|
|
66
|
+
})
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
Frameworks that configure MDX expose this option in different places. Keep the
|
|
70
|
+
theme in the application entry point; it does not belong in compiler options.
|
|
71
|
+
Custom MDX `pre` and `code` components should forward the generated properties
|
|
72
|
+
and children.
|
|
73
|
+
|
|
74
|
+
## Register only what you need
|
|
75
|
+
|
|
76
|
+
No languages are registered by default. Import each definition and pass it in
|
|
77
|
+
`languages`. Labels resolve through [Kirari's language registry](../../docs/language-integration.md),
|
|
78
|
+
so `js` selects registered JavaScript and `ts` selects registered TypeScript.
|
|
79
|
+
There is no language detection or automatic grammar loading. JSX and TSX need
|
|
80
|
+
their own definitions.
|
|
81
|
+
|
|
82
|
+
The plugin only highlights labelled, registered `<code>` elements directly
|
|
83
|
+
inside `<pre>` whose children are all text nodes. It leaves unlabelled code,
|
|
84
|
+
unknown languages, inline code, and code with existing element children
|
|
85
|
+
untouched. A `no-highlight` or `nohighlight` class on either `pre` or `code` also
|
|
86
|
+
skips the block. Ordinary Markdown serialization still escapes their text.
|
|
87
|
+
|
|
88
|
+
`@vortexlabs/kirari/markdown` serves a different purpose: it colors Markdown source itself,
|
|
89
|
+
including the fence markers. Use `@vortexlabs/kirari-rehype` when rendering a Markdown
|
|
90
|
+
document into HTML or MDX.
|
|
91
|
+
|
|
92
|
+
## Lines and fence metadata
|
|
93
|
+
|
|
94
|
+
Add a standalone brace group to a fence's metadata to highlight lines:
|
|
95
|
+
|
|
96
|
+
````md
|
|
97
|
+
```js {2,4-6}
|
|
98
|
+
const count = 1
|
|
99
|
+
const doubled = count * 2
|
|
100
|
+
|
|
101
|
+
console.log(count)
|
|
102
|
+
console.log(doubled)
|
|
103
|
+
console.log('done')
|
|
104
|
+
```
|
|
105
|
+
````
|
|
106
|
+
|
|
107
|
+
Line numbers are one-based. Valid metadata enables `.kirari-line` wrappers with
|
|
108
|
+
`data-line` attributes and adds `.kirari-line-highlighted` to selected lines.
|
|
109
|
+
Malformed groups and groups inside quoted metadata values are ignored.
|
|
110
|
+
Metadata is read from `code.data.meta`, as supplied by `remark-rehype` and MDX.
|
|
111
|
+
|
|
112
|
+
Provide your own line highlight styling:
|
|
113
|
+
|
|
114
|
+
```css
|
|
115
|
+
.kirari-line-highlighted {
|
|
116
|
+
background: var(--kirari-line-background, #7452bd18);
|
|
117
|
+
}
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
Use `lines: true` to wrap every highlighted block's lines even without metadata.
|
|
121
|
+
The `lineClassName` callback can append your own classes:
|
|
122
|
+
|
|
123
|
+
```ts
|
|
124
|
+
import type { Options } from '@vortexlabs/kirari-rehype'
|
|
125
|
+
|
|
126
|
+
const options = {
|
|
127
|
+
languages: [javascript],
|
|
128
|
+
lines: true,
|
|
129
|
+
lineClassName: ({ number }) => number === 1 ? 'opening-line' : undefined,
|
|
130
|
+
} satisfies Options
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
Line endings remain outside the wrappers. Empty and trailing lines are
|
|
134
|
+
preserved. Callback offsets are UTF-16 code units into the code block's source;
|
|
135
|
+
`start` is inclusive and `end` is exclusive and excludes the line ending.
|
|
136
|
+
Token class callbacks receive each original token once, even when a token spans
|
|
137
|
+
multiple lines.
|
|
138
|
+
|
|
139
|
+
## Options
|
|
140
|
+
|
|
141
|
+
The default export is the plugin. `Options` is its exported TypeScript type.
|
|
142
|
+
|
|
143
|
+
| Option | Default | Behavior |
|
|
144
|
+
| --- | --- | --- |
|
|
145
|
+
| `languages` | `[]` | Explicit readonly list of Kirari language definitions. |
|
|
146
|
+
| `classes` | None | Token category to extra CSS class map, as in Kirari's `RenderOptions`. |
|
|
147
|
+
| `className(token)` | None | Extra CSS classes for each original token. |
|
|
148
|
+
| `lines` | `false` | Wrap every line of a highlighted block. Valid line metadata also enables wrappers. |
|
|
149
|
+
| `lineClassName({ number, start, end })` | None | Extra classes for a line; called only when line wrappers are enabled. |
|
|
150
|
+
|
|
151
|
+
Custom classes append to the standard Kirari classes. Plain `text` tokens remain
|
|
152
|
+
text nodes unless they receive a custom class. Source strings become text node
|
|
153
|
+
values, and classes become element properties; the downstream serializer or
|
|
154
|
+
MDX compiler handles escaping.
|
|
155
|
+
|
|
156
|
+
## Pipeline order
|
|
157
|
+
|
|
158
|
+
Run the plugin after `remark-rehype`, when the document has become an HTML tree,
|
|
159
|
+
and before `rehype-stringify`. In MDX, use `rehypePlugins`, not `remarkPlugins`.
|
|
160
|
+
Configure one highlighter for a block; this plugin skips existing markup rather
|
|
161
|
+
than removing another plugin's spans.
|
|
162
|
+
|
|
163
|
+
If your pipeline sanitizes HTML, run `rehype-sanitize` before `rehypeKirari` and
|
|
164
|
+
preserve the `language-*` classes needed for lookup. If sanitization runs after
|
|
165
|
+
highlighting instead, its schema must allow Kirari's span classes, `.kirari` on
|
|
166
|
+
`pre`, and `data-line` when lines are enabled.
|
|
167
|
+
|
|
168
|
+
This plugin does not sanitize the surrounding document or change how your
|
|
169
|
+
pipeline handles raw HTML or MDX expressions. Language definitions and callbacks
|
|
170
|
+
remain trusted application code.
|
|
171
|
+
|
|
172
|
+
## License
|
|
173
|
+
|
|
174
|
+
[MIT](LICENSE) © 2026 [Ugi](https://ugi.dev)
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { Root } from 'hast';
|
|
2
|
+
import type { Language, RenderOptions } from '@vortexlabs/kirari/core';
|
|
3
|
+
export interface Options extends RenderOptions {
|
|
4
|
+
readonly languages?: readonly Language[];
|
|
5
|
+
readonly lines?: boolean;
|
|
6
|
+
readonly lineClassName?: (line: {
|
|
7
|
+
readonly number: number;
|
|
8
|
+
readonly start: number;
|
|
9
|
+
readonly end: number;
|
|
10
|
+
}) => string | undefined;
|
|
11
|
+
}
|
|
12
|
+
declare const rehypeKirari: (options?: Options) => (tree: Root) => undefined;
|
|
13
|
+
export default rehypeKirari;
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
import { tokenize } from '@vortexlabs/kirari/core';
|
|
2
|
+
import { createRegistry } from '@vortexlabs/kirari/registry';
|
|
3
|
+
const classes = (value) => {
|
|
4
|
+
return (Array.isArray(value) ? value : [value])
|
|
5
|
+
.filter((item) => typeof item === 'string' || typeof item === 'number')
|
|
6
|
+
.flatMap(item => String(item).split(/[\t\n\f\r ]+/).filter(Boolean));
|
|
7
|
+
};
|
|
8
|
+
const lineRanges = (meta) => {
|
|
9
|
+
if (typeof meta !== 'string')
|
|
10
|
+
return [];
|
|
11
|
+
const ranges = [];
|
|
12
|
+
let quote = '';
|
|
13
|
+
let start = -1;
|
|
14
|
+
let depth = 0;
|
|
15
|
+
for (let index = 0; index < meta.length; index++) {
|
|
16
|
+
const char = meta[index];
|
|
17
|
+
if (char === '\\') {
|
|
18
|
+
index++;
|
|
19
|
+
start = -1;
|
|
20
|
+
}
|
|
21
|
+
else if (quote) {
|
|
22
|
+
if (char === quote)
|
|
23
|
+
quote = '';
|
|
24
|
+
}
|
|
25
|
+
else if (char === '"' || char === "'") {
|
|
26
|
+
quote = char;
|
|
27
|
+
start = -1;
|
|
28
|
+
}
|
|
29
|
+
else if (char === '{') {
|
|
30
|
+
depth++;
|
|
31
|
+
start = depth === 1 && (index === 0 || /\s/.test(meta[index - 1])) ? index + 1 : -1;
|
|
32
|
+
}
|
|
33
|
+
else if (char === '}') {
|
|
34
|
+
depth = Math.max(0, depth - 1);
|
|
35
|
+
if (depth !== 0 || start < 0)
|
|
36
|
+
continue;
|
|
37
|
+
const body = meta.slice(start, index);
|
|
38
|
+
if ((index + 1 === meta.length || /\s/.test(meta[index + 1]))
|
|
39
|
+
&& /^\s*[1-9]\d*(?:\s*-\s*[1-9]\d*)?(?:\s*,\s*[1-9]\d*(?:\s*-\s*[1-9]\d*)?)*\s*$/.test(body)) {
|
|
40
|
+
const parsed = body.split(',').map(part => {
|
|
41
|
+
const [first, last = first] = part.split('-').map(Number);
|
|
42
|
+
return [first, last];
|
|
43
|
+
});
|
|
44
|
+
if (parsed.every(([first, last]) => Number.isSafeInteger(first) && Number.isSafeInteger(last) && first <= last)) {
|
|
45
|
+
for (const range of parsed)
|
|
46
|
+
ranges.push(range);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
start = -1;
|
|
50
|
+
}
|
|
51
|
+
else if (start >= 0 && !/[\d\s,-]/.test(char)) {
|
|
52
|
+
start = -1;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
return ranges.sort((left, right) => left[0] - right[0]);
|
|
56
|
+
};
|
|
57
|
+
const tokenNodes = (source, tokens, options, ranges) => {
|
|
58
|
+
let offset = 0;
|
|
59
|
+
for (const token of tokens) {
|
|
60
|
+
if (token.start !== offset || token.end !== offset + token.value.length
|
|
61
|
+
|| token.value !== source.slice(token.start, token.end)) {
|
|
62
|
+
throw new RangeError('Kirari: tokens must cover the original code contiguously');
|
|
63
|
+
}
|
|
64
|
+
offset = token.end;
|
|
65
|
+
}
|
|
66
|
+
if (offset !== source.length)
|
|
67
|
+
throw new RangeError('Kirari: tokens must cover the original code contiguously');
|
|
68
|
+
// Resolve callbacks once before any multiline token is split into line spans.
|
|
69
|
+
const tokenClasses = tokens.map(token => {
|
|
70
|
+
const mapped = options.classes && Object.hasOwn(options.classes, token.type) ? options.classes[token.type] : undefined;
|
|
71
|
+
const extra = [mapped, options.className?.(token)].filter(Boolean).join(' ');
|
|
72
|
+
return token.type === 'text' && !extra ? [] : classes(`kirari-${token.type}${extra ? ` ${extra}` : ''}`);
|
|
73
|
+
});
|
|
74
|
+
let tokenIndex = 0;
|
|
75
|
+
const rangeNodes = (start, end) => {
|
|
76
|
+
const nodes = [];
|
|
77
|
+
while (start < end) {
|
|
78
|
+
while (tokens[tokenIndex].end <= start)
|
|
79
|
+
tokenIndex++;
|
|
80
|
+
const token = tokens[tokenIndex];
|
|
81
|
+
const stop = Math.min(end, token.end);
|
|
82
|
+
const text = { type: 'text', value: source.slice(start, stop) };
|
|
83
|
+
const className = tokenClasses[tokenIndex];
|
|
84
|
+
nodes.push(className.length ? { type: 'element', tagName: 'span', properties: { className }, children: [text] } : text);
|
|
85
|
+
start = stop;
|
|
86
|
+
}
|
|
87
|
+
return nodes;
|
|
88
|
+
};
|
|
89
|
+
if (!options.lines && ranges.length === 0)
|
|
90
|
+
return rangeNodes(0, source.length);
|
|
91
|
+
const nodes = [];
|
|
92
|
+
const newline = /\r\n|\r|\n/g;
|
|
93
|
+
let start = 0;
|
|
94
|
+
let number = 1;
|
|
95
|
+
let rangeIndex = 0;
|
|
96
|
+
do {
|
|
97
|
+
const match = newline.exec(source);
|
|
98
|
+
const end = match?.index ?? source.length;
|
|
99
|
+
const className = ['kirari-line', ...classes(options.lineClassName?.({ number, start, end }))];
|
|
100
|
+
while (ranges[rangeIndex] && ranges[rangeIndex][1] < number)
|
|
101
|
+
rangeIndex++;
|
|
102
|
+
if (ranges[rangeIndex] && ranges[rangeIndex][0] <= number)
|
|
103
|
+
className.push('kirari-line-highlighted');
|
|
104
|
+
nodes.push({ type: 'element', tagName: 'span', properties: { className, dataLine: number }, children: rangeNodes(start, end) });
|
|
105
|
+
if (!match)
|
|
106
|
+
break;
|
|
107
|
+
nodes.push({ type: 'text', value: match[0] });
|
|
108
|
+
start = end + match[0].length;
|
|
109
|
+
number++;
|
|
110
|
+
} while (true);
|
|
111
|
+
return nodes;
|
|
112
|
+
};
|
|
113
|
+
const rehypeKirari = (options = {}) => {
|
|
114
|
+
const registry = createRegistry(options.languages ?? []);
|
|
115
|
+
return tree => {
|
|
116
|
+
const parents = [tree];
|
|
117
|
+
while (parents.length) {
|
|
118
|
+
const parent = parents.pop();
|
|
119
|
+
for (const child of parent.children) {
|
|
120
|
+
if (child.type === 'element' && child.tagName === 'pre') {
|
|
121
|
+
const preClasses = classes(child.properties.className);
|
|
122
|
+
if (!preClasses.some(name => name === 'no-highlight' || name === 'nohighlight')) {
|
|
123
|
+
for (const code of child.children) {
|
|
124
|
+
if (code.type !== 'element' || code.tagName !== 'code' || code.children.some(node => node.type !== 'text'))
|
|
125
|
+
continue;
|
|
126
|
+
const codeClasses = classes(code.properties.className);
|
|
127
|
+
if (codeClasses.some(name => name === 'no-highlight' || name === 'nohighlight'))
|
|
128
|
+
continue;
|
|
129
|
+
const language = codeClasses.filter(name => name.startsWith('language-')).map(name => registry.resolve(name.slice(9))).find(Boolean);
|
|
130
|
+
if (!language)
|
|
131
|
+
continue;
|
|
132
|
+
const source = code.children.map(node => node.type === 'text' ? node.value : '').join('');
|
|
133
|
+
const meta = code.data && 'meta' in code.data ? code.data.meta : undefined;
|
|
134
|
+
const children = tokenNodes(source, tokenize(source, language), options, lineRanges(meta));
|
|
135
|
+
code.children = children;
|
|
136
|
+
child.properties.className = [...new Set([...preClasses, 'kirari'])];
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
continue;
|
|
140
|
+
}
|
|
141
|
+
// MDX JSX nodes can contain normal HAST children without being elements themselves.
|
|
142
|
+
if ('children' in child && Array.isArray(child.children))
|
|
143
|
+
parents.push(child);
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
return undefined;
|
|
147
|
+
};
|
|
148
|
+
};
|
|
149
|
+
export default rehypeKirari;
|
package/package.json
CHANGED
|
@@ -1,6 +1,62 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vortexlabs/kirari-rehype",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"
|
|
5
|
-
"
|
|
6
|
-
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Kirari syntax highlighting for Rehype and MDX.",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"rehype",
|
|
7
|
+
"rehype-plugin",
|
|
8
|
+
"mdx",
|
|
9
|
+
"syntax-highlighting"
|
|
10
|
+
],
|
|
11
|
+
"author": "Ugi (https://ugi.dev)",
|
|
12
|
+
"license": "MIT",
|
|
13
|
+
"homepage": "https://kirari.vortex.design",
|
|
14
|
+
"repository": {
|
|
15
|
+
"type": "git",
|
|
16
|
+
"url": "git+https://github.com/vortexkit/kirari.git",
|
|
17
|
+
"directory": "packages/rehype"
|
|
18
|
+
},
|
|
19
|
+
"type": "module",
|
|
20
|
+
"sideEffects": false,
|
|
21
|
+
"files": [
|
|
22
|
+
"dist",
|
|
23
|
+
"README.md",
|
|
24
|
+
"LICENSE",
|
|
25
|
+
"CHANGELOG.md"
|
|
26
|
+
],
|
|
27
|
+
"exports": {
|
|
28
|
+
".": {
|
|
29
|
+
"types": "./dist/index.d.ts",
|
|
30
|
+
"import": "./dist/index.js"
|
|
31
|
+
}
|
|
32
|
+
},
|
|
33
|
+
"scripts": {
|
|
34
|
+
"build": "tsc -p tsconfig.build.json",
|
|
35
|
+
"test": "bun test ./test",
|
|
36
|
+
"typecheck": "tsc --noEmit",
|
|
37
|
+
"check": "bun run typecheck && bun run test && bun run build && bun test ./checks",
|
|
38
|
+
"prepublishOnly": "bun run build"
|
|
39
|
+
},
|
|
40
|
+
"dependencies": {
|
|
41
|
+
"@types/hast": "^3.0.5"
|
|
42
|
+
},
|
|
43
|
+
"peerDependencies": {
|
|
44
|
+
"@vortexlabs/kirari": "^0.1.0"
|
|
45
|
+
},
|
|
46
|
+
"devDependencies": {
|
|
47
|
+
"@mdx-js/mdx": "^3.1.1",
|
|
48
|
+
"@types/react": "^19.3.0",
|
|
49
|
+
"@types/react-dom": "^19.3.0",
|
|
50
|
+
"@vortexlabs/kirari": "0.1.0",
|
|
51
|
+
"react": "^19.3.1",
|
|
52
|
+
"react-dom": "^19.3.1",
|
|
53
|
+
"rehype-sanitize": "^6.0.0",
|
|
54
|
+
"rehype-stringify": "^10.0.1",
|
|
55
|
+
"remark-parse": "^11.0.0",
|
|
56
|
+
"remark-rehype": "^11.1.2",
|
|
57
|
+
"unified": "^11.0.5"
|
|
58
|
+
},
|
|
59
|
+
"publishConfig": {
|
|
60
|
+
"access": "public"
|
|
61
|
+
}
|
|
62
|
+
}
|