@gentleduck/md 0.2.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 ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 gentleduck
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 ADDED
@@ -0,0 +1,95 @@
1
+ <p align="center">
2
+ <img src="./logo-dark.svg" alt="@gentleduck/md" width="120"/>
3
+ </p>
4
+
5
+ <h1 align="center">@gentleduck/md</h1>
6
+
7
+ <p align="center">
8
+ Native Rust MDX compiler with a velite-shaped TypeScript API.
9
+ </p>
10
+
11
+ <p align="center">
12
+ <a href="https://github.com/gentleeduck/duck-mc/blob/master/LICENSE">MIT</a> -
13
+ <a href="https://github.com/gentleeduck/duck-mc/blob/master/CHANGELOG.md">Changelog</a> -
14
+ <a href="https://github.com/gentleeduck/duck-mc/blob/master/CONTRIBUTING.md">Contributing</a> -
15
+ <a href="https://github.com/gentleeduck/duck-mc/tree/master/dmc-docs">Docs</a> -
16
+ <a href="https://github.com/gentleeduck/duck-mc/tree/master/duck-benchmarks">Benchmarks</a>
17
+ </p>
18
+
19
+ <p align="center">
20
+ <a href="https://www.npmjs.com/package/@gentleduck/md"><img src="https://img.shields.io/npm/v/@gentleduck/md.svg" alt="npm"/></a>
21
+ <a href="https://github.com/gentleeduck/duck-mc/blob/master/LICENSE"><img src="https://img.shields.io/npm/l/@gentleduck/md.svg" alt="MIT"/></a>
22
+ </p>
23
+
24
+ ---
25
+
26
+ ## Install
27
+
28
+ ```sh
29
+ pnpm add @gentleduck/md
30
+ ```
31
+
32
+ Optional: `@gentleduck/md-sidecar` for foreign remark / rehype plugins.
33
+
34
+ ## Quick start
35
+
36
+ ```ts
37
+ // dmc.config.ts
38
+ import { defineConfig, s } from "@gentleduck/md";
39
+
40
+ export default defineConfig({
41
+ output: { data: ".gentleduck", html: true },
42
+ collections: {
43
+ posts: {
44
+ name: "Post",
45
+ pattern: "content/posts/**/*.mdx",
46
+ schema: (s) => s.object({
47
+ title: s.string(),
48
+ date: s.isodate(),
49
+ slug: s.path(),
50
+ }),
51
+ },
52
+ },
53
+ });
54
+ ```
55
+
56
+ ```sh
57
+ dmc build
58
+ ```
59
+
60
+ Outputs `.gentleduck/Post.json` + typed `index.d.ts`. Import from any framework.
61
+
62
+ ## Native features
63
+
64
+ Pretty code (syntect), KaTeX/MathML math, emoji, code imports,
65
+ npm-command tabs, mermaid, bare URL autolinks, heading autolinks,
66
+ asset copy.
67
+
68
+ JS plugins listed in config that have native equivalents
69
+ (`remark-gfm`, `rehype-pretty-code`, `rehype-katex`, `rehype-slug`,
70
+ etc) are stripped from the sidecar payload automatically.
71
+
72
+ ## Docs
73
+
74
+ Repo: [github.com/gentleeduck/duck-mc](https://github.com/gentleeduck/duck-mc)
75
+
76
+ - Per-crate references, architecture, integration guides:
77
+ [`dmc-docs/`](https://github.com/gentleeduck/duck-mc/tree/master/dmc-docs)
78
+ - duck-ui website cross-link:
79
+ [github.com/gentleeduck/duck-ui](https://github.com/gentleeduck/duck-ui)
80
+ - Migration from velite:
81
+ [`docs/migrating-from-velite.md`](https://github.com/gentleeduck/duck-mc/blob/master/docs/migrating-from-velite.md)
82
+
83
+ ## Benchmarks
84
+
85
+ Five recorded phases; **9.5x velite** at the kitchen-sink workload.
86
+ Full numbers: [`duck-benchmarks/`](https://github.com/gentleeduck/duck-mc/tree/master/duck-benchmarks).
87
+
88
+ ## Contributing
89
+
90
+ PR checklist + style notes:
91
+ [`CONTRIBUTING.md`](https://github.com/gentleeduck/duck-mc/blob/master/CONTRIBUTING.md).
92
+
93
+ ## License
94
+
95
+ MIT
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
package/index.d.ts ADDED
@@ -0,0 +1,60 @@
1
+ /* tslint:disable */
2
+ /* eslint-disable */
3
+
4
+ /* auto-generated by NAPI-RS */
5
+
6
+ export declare function compile(source: string): any
7
+ /**
8
+ * Render a LaTeX fragment to KaTeX HTML via the embedded KaTeX engine.
9
+ * Output matches the JS chain `rehype-katex` byte-for-byte. Pair with
10
+ * the standard `katex.min.css` for glyph rendering.
11
+ */
12
+ export declare function latexToHtml(latex: string, display: boolean): string
13
+ export declare function compileMany(sources: Array<string>): Array<any>
14
+ export interface CollectionInput {
15
+ name: string
16
+ pattern: string
17
+ baseDir: string
18
+ schema?: any
19
+ single?: boolean
20
+ }
21
+ export interface BuildInput {
22
+ outputDir: string
23
+ collections: Array<CollectionInput>
24
+ root?: string
25
+ strict?: boolean
26
+ clean?: boolean
27
+ outputAssets?: string
28
+ outputBase?: string
29
+ outputName?: string
30
+ outputFormat?: string
31
+ markdownRemarkPlugins?: any
32
+ markdownRehypePlugins?: any
33
+ mdxRemarkPlugins?: any
34
+ mdxRehypePlugins?: any
35
+ copyLinkedFiles?: boolean
36
+ mdxOutputFormat?: string
37
+ mdxMinify?: boolean
38
+ markdownGfm?: boolean
39
+ includeHtml?: boolean
40
+ cacheEnabled?: boolean
41
+ /**
42
+ * Bypass the plugin gate for every plugin: every JS plugin runs
43
+ * in the sidecar, every native transformer is dropped.
44
+ */
45
+ forceSidecar?: boolean
46
+ /**
47
+ * Per-plugin sidecar preference. Names listed here run in the
48
+ * sidecar; the matching native transformer is dropped from the
49
+ * pipeline. Names dmc recognises:
50
+ * "remark-gfm", "remark-math", "remark-emoji",
51
+ * "rehype-pretty-code", "shiki",
52
+ * "rehype-katex", "rehype-mathjax",
53
+ * "rehype-slug", "rehype-autolink-headings"
54
+ */
55
+ preferSidecar?: Array<string>
56
+ }
57
+ export interface BuildReport {
58
+ diagnostics: Array<string>
59
+ }
60
+ export declare function build(input: BuildInput): BuildReport
package/index.js ADDED
@@ -0,0 +1,318 @@
1
+ /* tslint:disable */
2
+ /* eslint-disable */
3
+ /* prettier-ignore */
4
+
5
+ /* auto-generated by NAPI-RS */
6
+
7
+ const { existsSync, readFileSync } = require('fs')
8
+ const { join } = require('path')
9
+
10
+ const { platform, arch } = process
11
+
12
+ let nativeBinding = null
13
+ let localFileExisted = false
14
+ let loadError = null
15
+
16
+ function isMusl() {
17
+ // For Node 10
18
+ if (!process.report || typeof process.report.getReport !== 'function') {
19
+ try {
20
+ const lddPath = require('child_process').execSync('which ldd').toString().trim()
21
+ return readFileSync(lddPath, 'utf8').includes('musl')
22
+ } catch (e) {
23
+ return true
24
+ }
25
+ } else {
26
+ const { glibcVersionRuntime } = process.report.getReport().header
27
+ return !glibcVersionRuntime
28
+ }
29
+ }
30
+
31
+ switch (platform) {
32
+ case 'android':
33
+ switch (arch) {
34
+ case 'arm64':
35
+ localFileExisted = existsSync(join(__dirname, 'dmc.android-arm64.node'))
36
+ try {
37
+ if (localFileExisted) {
38
+ nativeBinding = require('./dmc.android-arm64.node')
39
+ } else {
40
+ nativeBinding = require('@gentleduck/md-android-arm64')
41
+ }
42
+ } catch (e) {
43
+ loadError = e
44
+ }
45
+ break
46
+ case 'arm':
47
+ localFileExisted = existsSync(join(__dirname, 'dmc.android-arm-eabi.node'))
48
+ try {
49
+ if (localFileExisted) {
50
+ nativeBinding = require('./dmc.android-arm-eabi.node')
51
+ } else {
52
+ nativeBinding = require('@gentleduck/md-android-arm-eabi')
53
+ }
54
+ } catch (e) {
55
+ loadError = e
56
+ }
57
+ break
58
+ default:
59
+ throw new Error(`Unsupported architecture on Android ${arch}`)
60
+ }
61
+ break
62
+ case 'win32':
63
+ switch (arch) {
64
+ case 'x64':
65
+ localFileExisted = existsSync(
66
+ join(__dirname, 'dmc.win32-x64-msvc.node')
67
+ )
68
+ try {
69
+ if (localFileExisted) {
70
+ nativeBinding = require('./dmc.win32-x64-msvc.node')
71
+ } else {
72
+ nativeBinding = require('@gentleduck/md-win32-x64-msvc')
73
+ }
74
+ } catch (e) {
75
+ loadError = e
76
+ }
77
+ break
78
+ case 'ia32':
79
+ localFileExisted = existsSync(
80
+ join(__dirname, 'dmc.win32-ia32-msvc.node')
81
+ )
82
+ try {
83
+ if (localFileExisted) {
84
+ nativeBinding = require('./dmc.win32-ia32-msvc.node')
85
+ } else {
86
+ nativeBinding = require('@gentleduck/md-win32-ia32-msvc')
87
+ }
88
+ } catch (e) {
89
+ loadError = e
90
+ }
91
+ break
92
+ case 'arm64':
93
+ localFileExisted = existsSync(
94
+ join(__dirname, 'dmc.win32-arm64-msvc.node')
95
+ )
96
+ try {
97
+ if (localFileExisted) {
98
+ nativeBinding = require('./dmc.win32-arm64-msvc.node')
99
+ } else {
100
+ nativeBinding = require('@gentleduck/md-win32-arm64-msvc')
101
+ }
102
+ } catch (e) {
103
+ loadError = e
104
+ }
105
+ break
106
+ default:
107
+ throw new Error(`Unsupported architecture on Windows: ${arch}`)
108
+ }
109
+ break
110
+ case 'darwin':
111
+ localFileExisted = existsSync(join(__dirname, 'dmc.darwin-universal.node'))
112
+ try {
113
+ if (localFileExisted) {
114
+ nativeBinding = require('./dmc.darwin-universal.node')
115
+ } else {
116
+ nativeBinding = require('@gentleduck/md-darwin-universal')
117
+ }
118
+ break
119
+ } catch {}
120
+ switch (arch) {
121
+ case 'x64':
122
+ localFileExisted = existsSync(join(__dirname, 'dmc.darwin-x64.node'))
123
+ try {
124
+ if (localFileExisted) {
125
+ nativeBinding = require('./dmc.darwin-x64.node')
126
+ } else {
127
+ nativeBinding = require('@gentleduck/md-darwin-x64')
128
+ }
129
+ } catch (e) {
130
+ loadError = e
131
+ }
132
+ break
133
+ case 'arm64':
134
+ localFileExisted = existsSync(
135
+ join(__dirname, 'dmc.darwin-arm64.node')
136
+ )
137
+ try {
138
+ if (localFileExisted) {
139
+ nativeBinding = require('./dmc.darwin-arm64.node')
140
+ } else {
141
+ nativeBinding = require('@gentleduck/md-darwin-arm64')
142
+ }
143
+ } catch (e) {
144
+ loadError = e
145
+ }
146
+ break
147
+ default:
148
+ throw new Error(`Unsupported architecture on macOS: ${arch}`)
149
+ }
150
+ break
151
+ case 'freebsd':
152
+ if (arch !== 'x64') {
153
+ throw new Error(`Unsupported architecture on FreeBSD: ${arch}`)
154
+ }
155
+ localFileExisted = existsSync(join(__dirname, 'dmc.freebsd-x64.node'))
156
+ try {
157
+ if (localFileExisted) {
158
+ nativeBinding = require('./dmc.freebsd-x64.node')
159
+ } else {
160
+ nativeBinding = require('@gentleduck/md-freebsd-x64')
161
+ }
162
+ } catch (e) {
163
+ loadError = e
164
+ }
165
+ break
166
+ case 'linux':
167
+ switch (arch) {
168
+ case 'x64':
169
+ if (isMusl()) {
170
+ localFileExisted = existsSync(
171
+ join(__dirname, 'dmc.linux-x64-musl.node')
172
+ )
173
+ try {
174
+ if (localFileExisted) {
175
+ nativeBinding = require('./dmc.linux-x64-musl.node')
176
+ } else {
177
+ nativeBinding = require('@gentleduck/md-linux-x64-musl')
178
+ }
179
+ } catch (e) {
180
+ loadError = e
181
+ }
182
+ } else {
183
+ localFileExisted = existsSync(
184
+ join(__dirname, 'dmc.linux-x64-gnu.node')
185
+ )
186
+ try {
187
+ if (localFileExisted) {
188
+ nativeBinding = require('./dmc.linux-x64-gnu.node')
189
+ } else {
190
+ nativeBinding = require('@gentleduck/md-linux-x64-gnu')
191
+ }
192
+ } catch (e) {
193
+ loadError = e
194
+ }
195
+ }
196
+ break
197
+ case 'arm64':
198
+ if (isMusl()) {
199
+ localFileExisted = existsSync(
200
+ join(__dirname, 'dmc.linux-arm64-musl.node')
201
+ )
202
+ try {
203
+ if (localFileExisted) {
204
+ nativeBinding = require('./dmc.linux-arm64-musl.node')
205
+ } else {
206
+ nativeBinding = require('@gentleduck/md-linux-arm64-musl')
207
+ }
208
+ } catch (e) {
209
+ loadError = e
210
+ }
211
+ } else {
212
+ localFileExisted = existsSync(
213
+ join(__dirname, 'dmc.linux-arm64-gnu.node')
214
+ )
215
+ try {
216
+ if (localFileExisted) {
217
+ nativeBinding = require('./dmc.linux-arm64-gnu.node')
218
+ } else {
219
+ nativeBinding = require('@gentleduck/md-linux-arm64-gnu')
220
+ }
221
+ } catch (e) {
222
+ loadError = e
223
+ }
224
+ }
225
+ break
226
+ case 'arm':
227
+ if (isMusl()) {
228
+ localFileExisted = existsSync(
229
+ join(__dirname, 'dmc.linux-arm-musleabihf.node')
230
+ )
231
+ try {
232
+ if (localFileExisted) {
233
+ nativeBinding = require('./dmc.linux-arm-musleabihf.node')
234
+ } else {
235
+ nativeBinding = require('@gentleduck/md-linux-arm-musleabihf')
236
+ }
237
+ } catch (e) {
238
+ loadError = e
239
+ }
240
+ } else {
241
+ localFileExisted = existsSync(
242
+ join(__dirname, 'dmc.linux-arm-gnueabihf.node')
243
+ )
244
+ try {
245
+ if (localFileExisted) {
246
+ nativeBinding = require('./dmc.linux-arm-gnueabihf.node')
247
+ } else {
248
+ nativeBinding = require('@gentleduck/md-linux-arm-gnueabihf')
249
+ }
250
+ } catch (e) {
251
+ loadError = e
252
+ }
253
+ }
254
+ break
255
+ case 'riscv64':
256
+ if (isMusl()) {
257
+ localFileExisted = existsSync(
258
+ join(__dirname, 'dmc.linux-riscv64-musl.node')
259
+ )
260
+ try {
261
+ if (localFileExisted) {
262
+ nativeBinding = require('./dmc.linux-riscv64-musl.node')
263
+ } else {
264
+ nativeBinding = require('@gentleduck/md-linux-riscv64-musl')
265
+ }
266
+ } catch (e) {
267
+ loadError = e
268
+ }
269
+ } else {
270
+ localFileExisted = existsSync(
271
+ join(__dirname, 'dmc.linux-riscv64-gnu.node')
272
+ )
273
+ try {
274
+ if (localFileExisted) {
275
+ nativeBinding = require('./dmc.linux-riscv64-gnu.node')
276
+ } else {
277
+ nativeBinding = require('@gentleduck/md-linux-riscv64-gnu')
278
+ }
279
+ } catch (e) {
280
+ loadError = e
281
+ }
282
+ }
283
+ break
284
+ case 's390x':
285
+ localFileExisted = existsSync(
286
+ join(__dirname, 'dmc.linux-s390x-gnu.node')
287
+ )
288
+ try {
289
+ if (localFileExisted) {
290
+ nativeBinding = require('./dmc.linux-s390x-gnu.node')
291
+ } else {
292
+ nativeBinding = require('@gentleduck/md-linux-s390x-gnu')
293
+ }
294
+ } catch (e) {
295
+ loadError = e
296
+ }
297
+ break
298
+ default:
299
+ throw new Error(`Unsupported architecture on Linux: ${arch}`)
300
+ }
301
+ break
302
+ default:
303
+ throw new Error(`Unsupported OS: ${platform}, architecture: ${arch}`)
304
+ }
305
+
306
+ if (!nativeBinding) {
307
+ if (loadError) {
308
+ throw loadError
309
+ }
310
+ throw new Error(`Failed to load native binding`)
311
+ }
312
+
313
+ const { compile, latexToHtml, compileMany, build } = nativeBinding
314
+
315
+ module.exports.compile = compile
316
+ module.exports.latexToHtml = latexToHtml
317
+ module.exports.compileMany = compileMany
318
+ module.exports.build = build
package/logo-dark.svg ADDED
@@ -0,0 +1,9 @@
1
+ <svg width="1080" height="1080" viewBox="0 0 1080 1080" fill="none" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
2
+ <rect width="1080" height="1080" fill="url(#pattern0_3323_8212)"/>
3
+ <defs>
4
+ <pattern id="pattern0_3323_8212" patternContentUnits="objectBoundingBox" width="1" height="1">
5
+ <use xlink:href="#image0_3323_8212" transform="scale(0.000925926)"/>
6
+ </pattern>
7
+ <image id="image0_3323_8212" width="1080" height="1080" preserveAspectRatio="none" xlink:href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAABDgAAAQ4CAYAAADsEGyPAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAADUJSURBVHgB7d2/b1/V+cDxw7dIMCB7axbMP2BvlAGzBYZEnZqhpp3IEIsJd4jUASWqoF0gHZJOdYZELOAOkTrFA4ip6UCVyd46ESY62VMz8dXzoQ6B/PKPc+/nPue8XpLlUFoqwc1H3Lef85znTp069V0BAAAASOz/CgAAAEByAgcAAACQnsABAAAApCdwAAAAAOkJHAAAAEB6AgcAAACQnsABAAAApCdwAAAAAOkJHAAAAEB6AgcAAACQnsABAAAApCdwAAAAAOkJHAAAAEB6AgcAAACQnsABAAAApCdwAAAAAOkJHAAAAEB6AgcAAACQnsABAAAApCdwAAAAAOkJHAAAAEB6AgcAAACQnsABAAAApCdwAAAAAOkJHAAAAEB6AgcAAACQnsABAAAApCdwAAAAAOkJHAAAAEB6AgcAAACQnsABAAAApCdwAAAAAOkJHAAAAEB6AgcAAACQnsABAAAApCdwAAAAAOkJHAAAAEB6AgcAAACQnsABAAAApCdwAAAAAOkJHAAAAEB6AgcAAACQnsABAAAApCdwAAAAAOkJHAAAAEB6AgcAAACQnsABAAAApCdwAAAAAOkJHAAAAEB6AgcAAACQnsABAAAApCdwAAAAAOkJHAAAAEB6AgcAAACQnsABAAAApCdwAAAAAOkJHAAAAEB6AgcAAACQnsABAAAApCdwAAAAAOkJHAAAAEB6AgcAAACQnsABAAAApCdwAAAAAOkJHAAAAEB6AgcAAACQnsABAAAApCdwAAAAAOkJHAAAAEB6AgcAAACQnsABAAAApCdwAAAAAOkJHAAAAEB6AgcAAACQnsABAAAApCdwAAAAAOkJHAAAAEB6AgcAAACQnsABAAAApCdwAAAAAOkJHAAAAEB6AgcAAACQnsABAAUAgOwEDgAAACA9gQMAAABIT+AAAAAA0hM4AAAAgPQEDgAAACA9gQMAAABIT+AAAAAA0hM4AAAAgPQEDgAAACA9gQMAAABIT+AAAAAA0hM4AAAAgPQEDgAAACA9gQMAAABIT+AAAAAA0hM4AAAAgPQEDgAAACA9gQMAAABIT+AAAAAA0hM4AAAAgPQEDgAAACA9gQMAAABIT+AAAAAA0hM4AAAAgPQEDgAAACA9gQMAAABIT+AAAAAA0hM4AAAAgPQEDgAAACA9gQMAAABIT+AAAAAA0hM4AAAAgPQEDgAAACA9gQMAAABIT+AAAAAA0hM4AAAAgPQEDgAAACA9gQMAAABIT+AAAAAA0hM4AAAAgPQEDgAAACA9gQMAAABIT+AAAAAA0hM4AAAAgPQEDgAAACA9gQMAAABIT+AAAAAA0hM4AAAAgPQEDgAAACA9gQMAAABIT+AAAAAA0hM4AAAAgPQEDgAAACA9gQMAAABIT+AAAAAA0hM4AAAAgPQEDgAAACA9gQMAAABIT+AAAAAA0hM4AAAAgPQEDgAAACA9gQMAAABIT+AAAAAA0hM4AAAAgPQEDgAAACA9gQMAAABIT+AAAAAA0hM4AAAAgPQEDgAAACA9gQMAAABIT+AAAAAA0hM4AAAAgPQEDgAAACA9gQMAAABIT+AAAAAA0hM4AAAAgPQEDgAAACA9gQMAAABIT+AAAAAA0hM4AAAAgPQEDgAAACA9gQMAAABIT+AAAAAA0hM4AAAAgPQEDgAAACA9gQMAAABIT+AAAAAA0hM4AAAAgPQEDgAAACA9gQMAAABIT+AAAAAA0hM4AAAAgPQEDgAAACA9gQMAAABIT+AAAAAA0hM4AAAAgPQEDgAAACA9gQMAAABIT+AAAAAA0hM4AAAAgPQEDgAAACA9gQMAAABIT+AAAAAA0hM4AAAAgPQEDgAAACA9gQMAAABIT+AAAAAA0hM4AAAAgPQEDgAAACA9gQMAAABIT+AAAAAA0hM4AAAAgPQEDgAAACA9gQMAAABIT+AAAAAA0hM4AAAAgPQEDgAAACA9gQMAAABIT+AAAAAA0hM4AAAAgPQEDgAAACA9gQMAAABIT+AAAAAA0hM4AAAAgPQEDgAAACA9gQMAAABIT+AAAAAA0hM4AAAAgPQEDgAAACA9gQMAAABIT+AAAAAA0hM4AAAAgPQEDgAAACA9gQMAAABIT+AAAAAA0hM4AAAAgPQEDgAAACA9gQMAAABIT+AAAAAA0hM4AAAAgPQEDgAAACA9gQMAAABIT+AAAAAA0hM4AAAAgPQEDgAAACA9gQMAAABIT+AAAAAA0hM4AAAAgPQEDgAAACA9gQMAAABIT+AAAAAA0hM4AAAAgPQEDgAAACA9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AACA9AQOAAAAID2BAwAAAEhP4AAAAADSEzgAAACA9AQOAAAAID2BAwAAAEhP4AAAAADSEzgAAACA9AQOAAAAID2BAwAAAEhP4AAAAADSEzgAAACA9AQOAAAAID2BAwAAAEhP4AAAAADSEzgAAACA9AQOAAAAID2BAwAAAEhP4AAAAADSEzgAAACA9AQOAAAAID2BAwAAAEhP4AAAAADSEzgAAACA9AQOAAAAID2BAwAAAEhP4AAAAADSEzgAAACA9AQOAAAAID2BAwAAAEhP4AAAAADSEzgAAACA9AQOAAAAID2BAwAAAEhP4AAAAADSEzgAAACA9AQOAAAAID2BAwAAAEhP4AAAAADSEzgAAACA9AQOAAAAID2BAwAAAEhP4AAAAADSEzgAAACA9AQOAAAAID2BAwAAAEhP4AAAAADSEzgAAACA9AQOAAAAID2BAwAAAEhP4AAAAADSEzgAAACA9AQOAAAAID2BAwAAAEhP4AAAAADSEzgAAACA9AQOAAAAID2BAwAAAEhP4AAAAADSEzgAAACA9AQOAAAAID2BAwAAAEhP4AAAAADSEzgAAACA9AQOAAAAID2BAwAAAEhP4AAAAADSEzgAAACA9AQOAAAAID2BAwAAAEhP4AAAAADSEzgAAACA9AQOAAAAID2BAwAAAEhP4AAAAADSEzgAAACA9AQOAAAAID2BAwAAAEhP4AAAAADSEzgAAACA9AQOAAAAID2BAwAAAEhP4AAAAADSEzgAAACA9AQOAAAAID2BAwAAAEhP4AAAAADSEzgAAACA9AQOAAAAID2BAwAAAEhP4AAAAADSEzgAAACA9AQOAAAAID2BAwAAAEhP4AAAAADSEzgAAACA9AQOAAAAID2BAwAAAEhP4AAAAADSEzgAAACA9AQOAAAAID2BAwAAAEhP4AAAAADSEzgAAACA9AQOAAAAID2BAwAAAEhP4AAAAADSEzgAAACA9AQOAAAAID2BAwAAAEhP4AAAAADSEzgAAACA9AQOAAAAID2BAwAAAEhP4AAAAADSEzgAAACA9AQOAAAAID2BAwAAAEhP4AAAAADSEzgAAACA9AQOAAAAID2BAwAAAEhP4AAAAADSEzgAAACA9AQOAAAAID2BAwAAAEhP4AAAAADSEzgAAACA9AQOAAAAID2BAwAAAEhP4AAAAADSEzgAAACA9AQOAAAAID2BAwAAAEhP4AAAAADSEzgAAACA9AQOAAAAID2BAwAAAEhP4AAAAADSEzgAAACA9AQOAAAAID2BAwAAAEhP4AAAAADSEzgAAACA9AQOAAAAID2BAwAAAEhP4AAAAADSEzgAAACA9AQOAAAAID2BAwAAAEhP4AAAAADSEzgAAACA9AQOAAAAID2BAwAAAEhP4AAAAADSEzgAAACA9AQOAAAAID2BAwAAAEhP4AAAAADSEzgAAACA9AQOAAAAID2BAwAAAEhP4AAAAADSEzgAAACA9AQOAAAAID2BAwAAAEhP4AAAAADSEzgAAACA9AQOAAAAID2BAwAAAEhP4AAAAADSEzgAAACA9AQOAAAAID2BAwAAAEhP4AAAAADSEzgAAACA9AQOAAAAID2BAwAAAEhP4AAAAADSEzgAAACA9AQOAAAAID2BAwAAAEhP4AAAAADSEzgAAACA9AQOAAAAID2BAwAAAEhP4AAAAADSEzgAAACA9AQOAAAAID2BAwAAAEhP4AAAAADSEzgAAACA9AQOAAAAID2BAwAAAEhP4AAAAADSEzgAAACA9AQOAAAAID2BAwAAAEhP4AAAAADSEzgAAACA9AQOAAAAID2BAwAAAEhP4AAAAADSEzgAAACA9AQOAAAAID2BAwAAAEhP4AAAAADSEzgAAACA9AQOAAAAID2BAwAAAEhP4AAAAADSEzgAAACA9AQOAAAAID2BAwAAAEhP4AAAAADSEzgAAACA9AQOAAAAID2BAwAAAEhP4AAAAADSEzgAAACA9AQOAAAAID2BAwAAAEhP4AAAAADSEzgAAACA9AQOAAAAID2BAwAAAEhP4AAAAADSEzgAAACA9AQOAAAAID2BAwAAAEhP4AAAAADSEzgAAACA9AQOAAAAID2BAwAAAEhP4AAAAADSEzgAAACA9AQOAAAAID2BAwAAAEhP4AAAAADSEzgAAACA9AQOAAAAID2BAwAAAEhP4AAAAADSEzgAAACA9AQOAAAAID2BAwAAAEhP4AAAAADSEzgAAACA9AQOAAAAID2BAwAAAEhP4AAAAADSEzgAAACA9AQOAAAAID2BAwAAAEhP4AAAAADSEzgAAACA9AQOAAAAID2BAwAAAEhP4AAAAADSEzgAAACA9AQOAAAAID2BAwAAAEhP4AAAAADSEzgAAACA9AQOAAAAID2BAwAAAEhP4AAAAADSEzgAAACA9AQOAAAAID2BAwAAAEhP4AAAAADSEzgAAACA9AQOAAAAID2BAwAAAEhP4AAAAADSEzgAAACA9AQOAAAAID2BAwAAAEhP4AAAAADSEzgAAACA9AQOAAAAID2BAwAAAEhP4AAAAADSEzgAAACA9AQOAAAAID2BAwAAAEhP4AAAAADSEzgAAACA9AQOAAAAID2BAwAAAEjv/wHL2txvR6WxjQAAAABJRU5ErkJggg=="/>
8
+ </defs>
9
+ </svg>
package/mod.d.ts ADDED
@@ -0,0 +1,206 @@
1
+ import type { Plugin, Pluggable } from "unified";
2
+ export type { Plugin, Pluggable } from "unified";
3
+ /**
4
+ * Type-safe `[plugin, options]` tuple. The `options` argument is inferred
5
+ * from the plugin function's first parameter type, so misspelled or wrong-shape
6
+ * options surface as TS errors at config time.
7
+ *
8
+ * Usage:
9
+ * rehypePlugins: [
10
+ * rehypeSlug,
11
+ * definePlugin(rehypePrettyCode, { theme: { light, dark } }),
12
+ * definePlugin(rehypeAutolinkHeadings, { properties: {...} }),
13
+ * ]
14
+ */
15
+ export declare function definePlugin<P extends Plugin>(plugin: P): P;
16
+ export declare function definePlugin<P extends Plugin<Params, any, any>, Params extends [any?, ...any[]] = P extends Plugin<infer X, any, any> ? X : never>(plugin: P, options: Params[0]): [P, Params[0]];
17
+ export interface TocItem {
18
+ title: string;
19
+ url: string;
20
+ items: TocItem[];
21
+ }
22
+ export interface Metadata {
23
+ readingTime: number;
24
+ wordCount: number;
25
+ }
26
+ export interface CompileOutput {
27
+ body: string;
28
+ content: string;
29
+ html: string;
30
+ excerpt: string;
31
+ metadata: Metadata;
32
+ toc: TocItem[];
33
+ frontmatter: unknown;
34
+ frontmatterRaw: string;
35
+ imports: string[];
36
+ exports: string[];
37
+ }
38
+ export type SchemaKind = "string" | "number" | "boolean" | "array" | "object" | "record" | "tuple" | "intersection" | "enum" | "literal" | "union" | "discriminatedUnion" | "optional" | "nullable" | "default" | "transform" | "refine" | "superRefine" | "coerce.string" | "coerce.number" | "coerce.boolean" | "coerce.date" | "raw" | "markdown" | "mdx" | "toc" | "metadata" | "excerpt" | "path" | "slug" | "unique" | "isodate" | "file" | "image";
39
+ export interface SchemaDescriptor {
40
+ kind: SchemaKind;
41
+ [field: string]: unknown;
42
+ }
43
+ export interface CollectionConfig<S = unknown> {
44
+ name?: string;
45
+ pattern: string | string[];
46
+ baseDir?: string;
47
+ single?: boolean;
48
+ schema?: SchemaBuilder<S> | SchemaDescriptor;
49
+ }
50
+ export interface OutputOptions {
51
+ data?: string;
52
+ assets?: string;
53
+ base?: string;
54
+ name?: string;
55
+ clean?: boolean;
56
+ format?: "esm" | "cjs";
57
+ /** Emit a per-record `html` field rendered by the native pipeline (no
58
+ * sidecar/JS plugins). Set to `false` (default) when you only want the
59
+ * MDX body / JSX tree and will render via React. */
60
+ html?: boolean;
61
+ }
62
+ export interface MarkdownOptions {
63
+ gfm?: boolean;
64
+ removeComments?: boolean;
65
+ copyLinkedFiles?: boolean;
66
+ remarkPlugins?: Pluggable[];
67
+ rehypePlugins?: Pluggable[];
68
+ /**
69
+ * Bypass the plugin gate for every plugin: every JS plugin runs in
70
+ * the sidecar, every native transformer is dropped.
71
+ */
72
+ forceSidecar?: boolean;
73
+ /**
74
+ * Per-plugin sidecar preference. Names listed here run in the
75
+ * sidecar; the matching native transformer is dropped from the
76
+ * pipeline. Recognised names:
77
+ * "remark-gfm", "remark-math", "remark-emoji",
78
+ * "rehype-pretty-code", "shiki",
79
+ * "rehype-katex", "rehype-mathjax",
80
+ * "rehype-slug", "rehype-autolink-headings"
81
+ */
82
+ preferSidecar?: string[];
83
+ }
84
+ export interface MdxOptions extends MarkdownOptions {
85
+ outputFormat?: "function-body" | "module";
86
+ minify?: boolean;
87
+ }
88
+ export interface UserConfig {
89
+ root?: string;
90
+ strict?: boolean;
91
+ output?: OutputOptions;
92
+ collections: Record<string, CollectionConfig>;
93
+ loaders?: unknown[];
94
+ markdown?: MarkdownOptions;
95
+ mdx?: MdxOptions;
96
+ prepare?: (data: Record<string, unknown[]>, ctx: {
97
+ config: UserConfig;
98
+ }) => unknown;
99
+ complete?: (data: Record<string, unknown[]>, ctx: {
100
+ config: UserConfig;
101
+ }) => unknown;
102
+ }
103
+ export interface BuildCollectionReport {
104
+ name: string;
105
+ records: number;
106
+ outputPath: string;
107
+ }
108
+ export interface BuildErrorReport {
109
+ file: string;
110
+ message: string;
111
+ }
112
+ export interface BuildReport {
113
+ collections: BuildCollectionReport[];
114
+ errors: BuildErrorReport[];
115
+ }
116
+ export declare class SchemaBuilder<_T = unknown> {
117
+ [k: string]: unknown;
118
+ constructor(descriptor: SchemaDescriptor);
119
+ toJSON(): SchemaDescriptor;
120
+ optional(): SchemaBuilder;
121
+ nullable(): SchemaBuilder;
122
+ default(value: unknown): SchemaBuilder;
123
+ min(n: number): SchemaBuilder;
124
+ max(n: number): SchemaBuilder;
125
+ length(n: number): SchemaBuilder;
126
+ regex(p: string): SchemaBuilder;
127
+ int(): SchemaBuilder;
128
+ by(bucket: string): SchemaBuilder;
129
+ reserved(list: string[]): SchemaBuilder;
130
+ passthrough(): SchemaBuilder;
131
+ transform(fn: (v: unknown) => unknown): SchemaBuilder;
132
+ refine(fn: (v: unknown) => boolean, message?: string): SchemaBuilder;
133
+ }
134
+ export interface SBuilders {
135
+ string(): SchemaBuilder<string>;
136
+ number(): SchemaBuilder<number>;
137
+ boolean(): SchemaBuilder<boolean>;
138
+ array<I>(item: SchemaBuilder<I>): SchemaBuilder<I[]>;
139
+ object<S extends Record<string, SchemaBuilder>>(fields: S): SchemaBuilder;
140
+ record<V>(value: SchemaBuilder<V>): SchemaBuilder<Record<string, V>>;
141
+ tuple(items: SchemaBuilder[]): SchemaBuilder<unknown[]>;
142
+ intersection<A, B>(a: SchemaBuilder<A>, b: SchemaBuilder<B>): SchemaBuilder<A & B>;
143
+ enum<T>(variants: T[]): SchemaBuilder<T>;
144
+ literal<T>(value: T): SchemaBuilder<T>;
145
+ union<T>(variants: SchemaBuilder<T>[]): SchemaBuilder<T>;
146
+ discriminatedUnion<T>(discriminator: string, variants: SchemaBuilder<T>[]): SchemaBuilder<T>;
147
+ coerce: {
148
+ string(): SchemaBuilder<string>;
149
+ number(): SchemaBuilder<number>;
150
+ boolean(): SchemaBuilder<boolean>;
151
+ date(): SchemaBuilder<string>;
152
+ };
153
+ raw(): SchemaBuilder<string>;
154
+ markdown(): SchemaBuilder<string>;
155
+ mdx(): SchemaBuilder<string>;
156
+ toc(): SchemaBuilder<TocItem[]>;
157
+ metadata(): SchemaBuilder<Metadata>;
158
+ excerpt(opts?: {
159
+ length?: number;
160
+ }): SchemaBuilder<string>;
161
+ path(opts?: {
162
+ removeIndex?: boolean;
163
+ }): SchemaBuilder<string>;
164
+ slug(bucket?: string, reserved?: string[]): SchemaBuilder<string>;
165
+ unique(bucket?: string): SchemaBuilder<string>;
166
+ isodate(): SchemaBuilder<string>;
167
+ file(opts?: {
168
+ allowNonRelativePath?: boolean;
169
+ }): SchemaBuilder<string>;
170
+ image(opts?: {
171
+ absoluteRoot?: string;
172
+ }): SchemaBuilder<{
173
+ src: string;
174
+ width: number;
175
+ height: number;
176
+ }>;
177
+ }
178
+ export declare const s: SBuilders;
179
+ export declare const defineConfig: (config: UserConfig) => UserConfig;
180
+ export declare const defineCollection: <S>(c: CollectionConfig<S>) => CollectionConfig<S>;
181
+ export declare const defineLoader: <L>(l: L) => L;
182
+ export declare const defineSchema: <S>(sch: S) => S;
183
+ export interface CustomLoader<T = unknown> {
184
+ test: RegExp | string;
185
+ load: (file: {
186
+ path: string;
187
+ value: string;
188
+ }) => T | Promise<T>;
189
+ }
190
+ export declare function applyLoaders<T>(loaders: CustomLoader<T>[] | undefined, filePath: string, content: string): Promise<T | null>;
191
+ export declare function compile(source: string): CompileOutput;
192
+ export declare function compileMany(sources: string[]): CompileOutput[];
193
+ export declare function build(input: UserConfig): Promise<BuildReport>;
194
+ declare const _default: {
195
+ compile: typeof compile;
196
+ compileMany: typeof compileMany;
197
+ build: typeof build;
198
+ defineConfig: (config: UserConfig) => UserConfig;
199
+ defineCollection: <S>(c: CollectionConfig<S>) => CollectionConfig<S>;
200
+ defineLoader: <L>(l: L) => L;
201
+ defineSchema: <S>(sch: S) => S;
202
+ applyLoaders: typeof applyLoaders;
203
+ s: SBuilders;
204
+ SchemaBuilder: typeof SchemaBuilder;
205
+ };
206
+ export default _default;
package/mod.js ADDED
@@ -0,0 +1,483 @@
1
+ import { createRequire } from "node:module";
2
+ import { readFileSync, writeFileSync, unlinkSync, readdirSync, statSync, existsSync, } from "node:fs";
3
+ import { join, relative, dirname } from "node:path";
4
+ import { fileURLToPath } from "node:url";
5
+ export function definePlugin(plugin, options) {
6
+ return options === undefined ? plugin : [plugin, options];
7
+ }
8
+ const require = createRequire(import.meta.url);
9
+ const native = require("./index.js");
10
+ // Resolve sidecar entry relative to @gentleduck/md package + propagate via env
11
+ function resolveSidecar() {
12
+ const here = dirname(fileURLToPath(import.meta.url));
13
+ const candidates = [
14
+ join(here, "..", "dmc-sidecar", "index.mjs"),
15
+ join(here, "..", "..", "dmc-sidecar", "index.mjs"),
16
+ join(here, "..", "node_modules", "@duck", "md-sidecar", "index.mjs"),
17
+ ];
18
+ for (const p of candidates)
19
+ if (existsSync(p))
20
+ return p;
21
+ return null;
22
+ }
23
+ const SIDECAR_PATH = resolveSidecar();
24
+ if (SIDECAR_PATH && !process.env.dmc_SIDECAR) {
25
+ process.env.dmc_SIDECAR = SIDECAR_PATH;
26
+ }
27
+ const cbRegistry = new Map();
28
+ let cbId = 0;
29
+ const registerCallback = (fn) => {
30
+ const id = ++cbId;
31
+ cbRegistry.set(id, fn);
32
+ return id;
33
+ };
34
+ export class SchemaBuilder {
35
+ constructor(descriptor) {
36
+ Object.assign(this, descriptor);
37
+ }
38
+ toJSON() {
39
+ const out = { kind: this.kind };
40
+ for (const k of Object.keys(this))
41
+ out[k] = this[k];
42
+ return out;
43
+ }
44
+ optional() {
45
+ return new SchemaBuilder({ kind: "optional", inner: this.toJSON() });
46
+ }
47
+ nullable() {
48
+ return new SchemaBuilder({ kind: "nullable", inner: this.toJSON() });
49
+ }
50
+ default(value) {
51
+ return new SchemaBuilder({
52
+ kind: "default",
53
+ inner: this.toJSON(),
54
+ fallback: value,
55
+ });
56
+ }
57
+ min(n) {
58
+ return new SchemaBuilder({ ...this.toJSON(), min: n });
59
+ }
60
+ max(n) {
61
+ return new SchemaBuilder({ ...this.toJSON(), max: n });
62
+ }
63
+ length(n) {
64
+ return new SchemaBuilder({ ...this.toJSON(), length: n });
65
+ }
66
+ regex(p) {
67
+ return new SchemaBuilder({ ...this.toJSON(), regex: p });
68
+ }
69
+ int() {
70
+ return new SchemaBuilder({ ...this.toJSON(), int: true });
71
+ }
72
+ by(bucket) {
73
+ return new SchemaBuilder({ ...this.toJSON(), bucket });
74
+ }
75
+ reserved(list) {
76
+ return new SchemaBuilder({ ...this.toJSON(), reserved: list });
77
+ }
78
+ passthrough() {
79
+ return new SchemaBuilder({ ...this.toJSON(), passthrough: true });
80
+ }
81
+ transform(fn) {
82
+ return new SchemaBuilder({
83
+ kind: "transform",
84
+ inner: this.toJSON(),
85
+ __callbackId: registerCallback(fn),
86
+ });
87
+ }
88
+ refine(fn, message) {
89
+ return new SchemaBuilder({
90
+ kind: "refine",
91
+ inner: this.toJSON(),
92
+ __callbackId: registerCallback(fn),
93
+ __message: message,
94
+ });
95
+ }
96
+ }
97
+ const sb = (d) => new SchemaBuilder(d);
98
+ export const s = {
99
+ string: () => sb({ kind: "string" }),
100
+ number: () => sb({ kind: "number" }),
101
+ boolean: () => sb({ kind: "boolean" }),
102
+ array: (item) => sb({ kind: "array", item: item.toJSON() }),
103
+ object: (fields) => sb({
104
+ kind: "object",
105
+ fields: Object.fromEntries(Object.entries(fields).map(([k, v]) => [
106
+ k,
107
+ v.toJSON(),
108
+ ])),
109
+ }),
110
+ record: (value) => sb({ kind: "record", value: value.toJSON() }),
111
+ tuple: (items) => sb({
112
+ kind: "tuple",
113
+ items: items.map((v) => v.toJSON()),
114
+ }),
115
+ intersection: (a, b) => sb({
116
+ kind: "intersection",
117
+ left: a.toJSON(),
118
+ right: b.toJSON(),
119
+ }),
120
+ enum: (variants) => sb({ kind: "enum", variants }),
121
+ literal: (expected) => sb({ kind: "literal", expected }),
122
+ union: (variants) => sb({
123
+ kind: "union",
124
+ variants: variants.map((v) => v.toJSON()),
125
+ }),
126
+ discriminatedUnion: (discriminator, variants) => sb({
127
+ kind: "discriminatedUnion",
128
+ discriminator,
129
+ variants: variants.map((v) => v.toJSON()),
130
+ }),
131
+ coerce: {
132
+ string: () => sb({ kind: "coerce.string" }),
133
+ number: () => sb({ kind: "coerce.number" }),
134
+ boolean: () => sb({ kind: "coerce.boolean" }),
135
+ date: () => sb({ kind: "coerce.date" }),
136
+ },
137
+ raw: () => sb({ kind: "raw" }),
138
+ markdown: () => sb({ kind: "markdown" }),
139
+ mdx: () => sb({ kind: "mdx" }),
140
+ toc: () => sb({ kind: "toc" }),
141
+ metadata: () => sb({ kind: "metadata" }),
142
+ excerpt: (opts = {}) => sb({ kind: "excerpt", ...opts }),
143
+ path: (opts = {}) => sb({ kind: "path", ...opts }),
144
+ slug: (bucket, reserved) => sb({ kind: "slug", bucket, reserved }),
145
+ unique: (bucket) => sb({ kind: "unique", bucket }),
146
+ isodate: () => sb({ kind: "isodate" }),
147
+ file: (opts = {}) => sb({ kind: "file", ...opts }),
148
+ image: (opts = {}) => sb({ kind: "image", ...opts }),
149
+ };
150
+ export const defineConfig = (config) => config;
151
+ export const defineCollection = (c) => c;
152
+ export const defineLoader = (l) => l;
153
+ export const defineSchema = (sch) => sch;
154
+ export async function applyLoaders(loaders, filePath, content) {
155
+ if (!loaders || loaders.length === 0)
156
+ return null;
157
+ for (const loader of loaders) {
158
+ const re = loader.test instanceof RegExp ? loader.test : new RegExp(loader.test);
159
+ if (re.test(filePath)) {
160
+ return await loader.load({ path: filePath, value: content });
161
+ }
162
+ }
163
+ return null;
164
+ }
165
+ function collectCallbacks(descriptor, base = []) {
166
+ if (!descriptor || typeof descriptor !== "object")
167
+ return [];
168
+ const found = [];
169
+ if (descriptor.kind === "transform" &&
170
+ typeof descriptor.__callbackId === "number") {
171
+ const fn = cbRegistry.get(descriptor.__callbackId);
172
+ if (fn)
173
+ found.push({ path: [...base], kind: "transform", fn });
174
+ }
175
+ if (descriptor.kind === "refine" &&
176
+ typeof descriptor.__callbackId === "number") {
177
+ const fn = cbRegistry.get(descriptor.__callbackId);
178
+ if (fn)
179
+ found.push({
180
+ path: [...base],
181
+ kind: "refine",
182
+ fn,
183
+ message: descriptor.__message,
184
+ });
185
+ }
186
+ if (descriptor.inner)
187
+ found.push(...collectCallbacks(descriptor.inner, base));
188
+ if (descriptor.kind === "object" && descriptor.fields) {
189
+ for (const [k, v] of Object.entries(descriptor.fields)) {
190
+ found.push(...collectCallbacks(v, [...base, k]));
191
+ }
192
+ }
193
+ if (descriptor.kind === "array" && descriptor.item) {
194
+ found.push(...collectCallbacks(descriptor.item, [...base, "*"]));
195
+ }
196
+ return found;
197
+ }
198
+ function walkPath(obj, path) {
199
+ if (path.length === 0)
200
+ return [];
201
+ if (path[0] === "*") {
202
+ if (!Array.isArray(obj))
203
+ return [];
204
+ return obj.flatMap((_, i) => walkPath(obj[i], path.slice(1)));
205
+ }
206
+ const [key, ...rest] = path;
207
+ if (obj == null ||
208
+ typeof obj !== "object" ||
209
+ !(key in obj))
210
+ return [];
211
+ if (rest.length === 0)
212
+ return [{ parent: obj, key }];
213
+ return walkPath(obj[key], rest);
214
+ }
215
+ function applyCallbacks(record, cbs, errors, file) {
216
+ for (const cb of cbs) {
217
+ for (const { parent, key } of walkPath(record, cb.path)) {
218
+ const v = parent[key];
219
+ if (cb.kind === "transform") {
220
+ try {
221
+ parent[key] = cb.fn(v);
222
+ }
223
+ catch (e) {
224
+ errors.push({
225
+ file,
226
+ message: `${cb.path.join(".")}: transform threw: ${e.message ?? e}`,
227
+ });
228
+ }
229
+ }
230
+ else {
231
+ let ok = false;
232
+ try {
233
+ ok = !!cb.fn(v);
234
+ }
235
+ catch (e) {
236
+ errors.push({
237
+ file,
238
+ message: `${cb.path.join(".")}: refine threw: ${e.message ?? e}`,
239
+ });
240
+ continue;
241
+ }
242
+ if (!ok)
243
+ errors.push({
244
+ file,
245
+ message: `${cb.path.join(".")}: ${cb.message ?? "failed refinement"}`,
246
+ });
247
+ }
248
+ }
249
+ }
250
+ }
251
+ function adaptToBuildInput(input) {
252
+ if ("outputDir" in input && Array.isArray(input.collections))
253
+ return input;
254
+ const cfg = input;
255
+ const root = cfg.root ?? ".";
256
+ const outputDir = cfg.output?.data ?? ".gentleduck";
257
+ const collections = Object.entries(cfg.collections ?? {}).map(([key, c]) => ({
258
+ name: c.name ?? key,
259
+ pattern: Array.isArray(c.pattern) ? c.pattern[0] : c.pattern,
260
+ baseDir: c.baseDir ?? root,
261
+ schema: c.schema instanceof SchemaBuilder
262
+ ? c.schema.toJSON()
263
+ : c.schema,
264
+ single: c.single,
265
+ }));
266
+ return {
267
+ outputDir,
268
+ collections,
269
+ root,
270
+ strict: cfg.strict,
271
+ clean: cfg.output?.clean,
272
+ outputAssets: cfg.output?.assets,
273
+ outputBase: cfg.output?.base,
274
+ outputName: cfg.output?.name,
275
+ outputFormat: cfg.output?.format,
276
+ markdownRemarkPlugins: cfg.markdown?.remarkPlugins,
277
+ markdownRehypePlugins: cfg.markdown?.rehypePlugins,
278
+ mdxRemarkPlugins: cfg.mdx?.remarkPlugins,
279
+ mdxRehypePlugins: cfg.mdx?.rehypePlugins,
280
+ copyLinkedFiles: cfg.markdown?.copyLinkedFiles ?? cfg.mdx?.copyLinkedFiles,
281
+ mdxOutputFormat: cfg.mdx?.outputFormat,
282
+ mdxMinify: cfg.mdx?.minify,
283
+ markdownGfm: cfg.markdown?.gfm,
284
+ includeHtml: cfg.output?.html,
285
+ forceSidecar: cfg.markdown?.forceSidecar ?? cfg.mdx?.forceSidecar,
286
+ preferSidecar: cfg.markdown?.preferSidecar ?? cfg.mdx?.preferSidecar,
287
+ };
288
+ }
289
+ export function compile(source) {
290
+ return native.compile(source);
291
+ }
292
+ export function compileMany(sources) {
293
+ return native.compileMany(sources);
294
+ }
295
+ async function processWithUnified(markdown, remarkPlugins, rehypePlugins) {
296
+ const { unified } = await import("unified");
297
+ const { default: remarkParse } = await import("remark-parse");
298
+ const { default: remarkRehype } = await import("remark-rehype");
299
+ const { default: rehypeRaw } = await import("rehype-raw");
300
+ const { default: rehypeStringify } = await import("rehype-stringify");
301
+ // unified's `.use([fn, opts])` interprets the array as a list of plugins
302
+ // (not a [plugin, opts] tuple). To pass options correctly we must call
303
+ // `.use(fn, opts)` w/ two args. Unwrap each Pluggable shape here.
304
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
305
+ const apply = (proc, p) => {
306
+ if (typeof p === "function")
307
+ return proc.use(p);
308
+ if (Array.isArray(p))
309
+ return proc.use(p[0], p[1]);
310
+ return proc.use(p);
311
+ };
312
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
313
+ let proc = unified().use(remarkParse);
314
+ for (const p of remarkPlugins)
315
+ proc = apply(proc, p);
316
+ proc = proc.use(remarkRehype, { allowDangerousHtml: true }).use(rehypeRaw);
317
+ for (const p of rehypePlugins)
318
+ proc = apply(proc, p);
319
+ proc = proc.use(rehypeStringify, { allowDangerousHtml: true });
320
+ const file = await proc.process(markdown);
321
+ return String(file);
322
+ }
323
+ function walkDir(dir) {
324
+ const out = [];
325
+ try {
326
+ for (const name of readdirSync(dir)) {
327
+ const full = join(dir, name);
328
+ const st = statSync(full);
329
+ if (st.isDirectory())
330
+ out.push(...walkDir(full));
331
+ else
332
+ out.push(full);
333
+ }
334
+ }
335
+ catch { }
336
+ return out;
337
+ }
338
+ async function applyCustomLoaders(input, report) {
339
+ const extras = new Map();
340
+ const loaders = input.loaders ?? [];
341
+ if (loaders.length === 0)
342
+ return { extras };
343
+ const root = input.root ?? ".";
344
+ for (const [key, c] of Object.entries(input.collections)) {
345
+ const baseDir = c.baseDir ?? root;
346
+ const files = walkDir(baseDir);
347
+ const matched = [];
348
+ const matchedPaths = new Set();
349
+ for (const file of files) {
350
+ const rel = relative(baseDir, file);
351
+ for (const loader of loaders) {
352
+ const re = loader.test instanceof RegExp ? loader.test : new RegExp(loader.test);
353
+ if (re.test(rel) || re.test(file)) {
354
+ const content = readFileSync(file, "utf8");
355
+ const data = await loader.load({ path: file, value: content });
356
+ if (data && typeof data === "object") {
357
+ const record = { ...data, sourceFilePath: file };
358
+ matched.push(record);
359
+ matchedPaths.add(file);
360
+ }
361
+ break;
362
+ }
363
+ }
364
+ }
365
+ if (matched.length > 0) {
366
+ const name = c.name ?? key;
367
+ extras.set(name, matched);
368
+ const target = report.collections.find((rc) => rc.name === name);
369
+ if (target) {
370
+ const existing = JSON.parse(readFileSync(target.outputPath, "utf8"));
371
+ const filtered = existing.filter((r) => !matchedPaths.has(r?.sourceFilePath ?? ""));
372
+ const merged = [...filtered, ...matched];
373
+ writeFileSync(target.outputPath, JSON.stringify(merged, null, 2));
374
+ target.records = merged.length;
375
+ }
376
+ }
377
+ }
378
+ return { extras };
379
+ }
380
+ export async function build(input) {
381
+ const collectionCallbacks = new Map();
382
+ if (input?.collections && !Array.isArray(input.collections)) {
383
+ for (const [key, c] of Object.entries(input.collections)) {
384
+ if (c.schema) {
385
+ const desc = c.schema instanceof SchemaBuilder
386
+ ? c.schema.toJSON()
387
+ : c.schema;
388
+ const cbs = collectCallbacks(desc);
389
+ if (cbs.length)
390
+ collectionCallbacks.set(c.name ?? key, cbs);
391
+ }
392
+ }
393
+ }
394
+ // Strip JS plugin function refs from the napi input - they can't cross
395
+ // the FFI boundary. The in-process post-pass below applies them.
396
+ const stripped = {
397
+ ...input,
398
+ markdown: input.markdown
399
+ ? {
400
+ ...input.markdown,
401
+ remarkPlugins: undefined,
402
+ rehypePlugins: undefined,
403
+ }
404
+ : undefined,
405
+ mdx: input.mdx
406
+ ? { ...input.mdx, remarkPlugins: undefined, rehypePlugins: undefined }
407
+ : undefined,
408
+ };
409
+ const report = native.build(adaptToBuildInput(stripped));
410
+ await applyCustomLoaders(input, report);
411
+ // In-process unified pipeline - type-safe plugin refs run here.
412
+ const remark = [
413
+ ...(input.markdown?.remarkPlugins ?? []),
414
+ ...(input.mdx?.remarkPlugins ?? []),
415
+ ];
416
+ const rehype = [
417
+ ...(input.markdown?.rehypePlugins ?? []),
418
+ ...(input.mdx?.rehypePlugins ?? []),
419
+ ];
420
+ if (remark.length || rehype.length) {
421
+ for (const c of report.collections) {
422
+ const records = JSON.parse(readFileSync(c.outputPath, "utf8"));
423
+ for (const r of records) {
424
+ const md = r.content ?? "";
425
+ try {
426
+ r.html = await processWithUnified(md, remark, rehype);
427
+ }
428
+ catch (e) {
429
+ report.errors.push({
430
+ file: r.sourceFilePath ?? c.outputPath,
431
+ message: `unified pipeline: ${e.message ?? e}`,
432
+ });
433
+ }
434
+ }
435
+ writeFileSync(c.outputPath, JSON.stringify(records, null, 2));
436
+ }
437
+ }
438
+ const needPostprocess = collectionCallbacks.size > 0 || input.prepare || input.complete;
439
+ if (!needPostprocess)
440
+ return report;
441
+ const data = {};
442
+ for (const c of report.collections) {
443
+ data[c.name] = JSON.parse(readFileSync(c.outputPath, "utf8"));
444
+ }
445
+ for (const c of report.collections) {
446
+ const cbs = collectionCallbacks.get(c.name);
447
+ if (!cbs)
448
+ continue;
449
+ const records = Array.isArray(data[c.name]) ? data[c.name] : [data[c.name]];
450
+ for (const record of records) {
451
+ applyCallbacks(record, cbs, report.errors, c.outputPath);
452
+ }
453
+ }
454
+ if (input.prepare) {
455
+ const ret = await input.prepare(data, { config: input });
456
+ if (ret === false) {
457
+ for (const c of report.collections)
458
+ try {
459
+ unlinkSync(c.outputPath);
460
+ }
461
+ catch { }
462
+ return report;
463
+ }
464
+ }
465
+ for (const c of report.collections) {
466
+ writeFileSync(c.outputPath, JSON.stringify(data[c.name], null, 2));
467
+ }
468
+ if (input.complete)
469
+ await input.complete(data, { config: input });
470
+ return report;
471
+ }
472
+ export default {
473
+ compile,
474
+ compileMany,
475
+ build,
476
+ defineConfig,
477
+ defineCollection,
478
+ defineLoader,
479
+ defineSchema,
480
+ applyLoaders,
481
+ s,
482
+ SchemaBuilder,
483
+ };
package/package.json ADDED
@@ -0,0 +1,69 @@
1
+ {
2
+ "name": "@gentleduck/md",
3
+ "version": "0.2.0",
4
+ "description": "Native Rust MDX compiler with a velite-shaped TypeScript API",
5
+ "license": "MIT",
6
+ "author": "gentleduck",
7
+ "homepage": "https://github.com/gentleeduck/duck-mc#readme",
8
+ "repository": {
9
+ "type": "git",
10
+ "url": "git+https://github.com/gentleeduck/duck-mc.git",
11
+ "directory": "dmc-napi"
12
+ },
13
+ "bugs": {
14
+ "url": "https://github.com/gentleeduck/duck-mc/issues"
15
+ },
16
+ "keywords": [
17
+ "mdx",
18
+ "markdown",
19
+ "compiler",
20
+ "velite",
21
+ "rust",
22
+ "napi",
23
+ "shiki",
24
+ "syntect",
25
+ "katex"
26
+ ],
27
+ "type": "module",
28
+ "main": "mod.js",
29
+ "types": "mod.d.ts",
30
+ "exports": {
31
+ ".": {
32
+ "types": "./mod.d.ts",
33
+ "default": "./mod.js"
34
+ }
35
+ },
36
+ "files": [
37
+ "mod.js",
38
+ "mod.d.ts",
39
+ "index.js",
40
+ "index.d.ts",
41
+ "*.node",
42
+ "README.md",
43
+ "logo-dark.svg"
44
+ ],
45
+ "napi": {
46
+ "name": "dmc",
47
+ "triples": {
48
+ "defaults": true,
49
+ "additional": []
50
+ }
51
+ },
52
+ "dependencies": {
53
+ "unified": "^11",
54
+ "remark-parse": "^11",
55
+ "remark-rehype": "^11",
56
+ "rehype-raw": "^7",
57
+ "rehype-stringify": "^10"
58
+ },
59
+ "devDependencies": {
60
+ "@napi-rs/cli": "^2.18.4",
61
+ "@types/node": "^25",
62
+ "@types/unist": "^3",
63
+ "typescript": "^6"
64
+ },
65
+ "scripts": {
66
+ "build": "napi build --platform --release",
67
+ "build:debug": "napi build --platform"
68
+ }
69
+ }