astro-xmdx 0.0.2
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/index.ts +8 -0
- package/package.json +80 -0
- package/src/constants.ts +52 -0
- package/src/index.ts +150 -0
- package/src/pipeline/index.ts +38 -0
- package/src/pipeline/orchestrator.test.ts +324 -0
- package/src/pipeline/orchestrator.ts +121 -0
- package/src/pipeline/pipe.test.ts +251 -0
- package/src/pipeline/pipe.ts +70 -0
- package/src/pipeline/types.ts +59 -0
- package/src/plugins.test.ts +274 -0
- package/src/presets/index.ts +225 -0
- package/src/transforms/blocks-to-jsx.test.ts +590 -0
- package/src/transforms/blocks-to-jsx.ts +617 -0
- package/src/transforms/expressive-code.test.ts +274 -0
- package/src/transforms/expressive-code.ts +147 -0
- package/src/transforms/index.test.ts +143 -0
- package/src/transforms/index.ts +100 -0
- package/src/transforms/inject-components.test.ts +406 -0
- package/src/transforms/inject-components.ts +184 -0
- package/src/transforms/shiki.test.ts +289 -0
- package/src/transforms/shiki.ts +312 -0
- package/src/types.ts +92 -0
- package/src/utils/config.test.ts +252 -0
- package/src/utils/config.ts +146 -0
- package/src/utils/frontmatter.ts +33 -0
- package/src/utils/imports.test.ts +518 -0
- package/src/utils/imports.ts +201 -0
- package/src/utils/mdx-detection.test.ts +41 -0
- package/src/utils/mdx-detection.ts +209 -0
- package/src/utils/paths.test.ts +206 -0
- package/src/utils/paths.ts +92 -0
- package/src/utils/validation.test.ts +60 -0
- package/src/utils/validation.ts +15 -0
- package/src/vite-plugin/binding-loader.ts +81 -0
- package/src/vite-plugin/directive-rewriter.test.ts +331 -0
- package/src/vite-plugin/directive-rewriter.ts +272 -0
- package/src/vite-plugin/esbuild-pool.ts +173 -0
- package/src/vite-plugin/index.ts +37 -0
- package/src/vite-plugin/jsx-module.ts +106 -0
- package/src/vite-plugin/mdx-wrapper.ts +328 -0
- package/src/vite-plugin/normalize-config.test.ts +78 -0
- package/src/vite-plugin/normalize-config.ts +29 -0
- package/src/vite-plugin/shiki-highlighter.ts +46 -0
- package/src/vite-plugin/shiki-manager.test.ts +175 -0
- package/src/vite-plugin/shiki-manager.ts +53 -0
- package/src/vite-plugin/types.ts +189 -0
- package/src/vite-plugin.ts +1342 -0
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pipeline orchestrator for composing Xmdx transforms
|
|
3
|
+
* @module pipeline/orchestrator
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { pipe } from './pipe.js';
|
|
7
|
+
import type { TransformContext, TransformConfig, Transform, PipelineOptions } from './types.js';
|
|
8
|
+
import {
|
|
9
|
+
transformExpressiveCode,
|
|
10
|
+
transformInjectComponentsFromRegistry,
|
|
11
|
+
transformShikiHighlight,
|
|
12
|
+
} from '../transforms/index.js';
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Default context values for transforms.
|
|
16
|
+
*/
|
|
17
|
+
const DEFAULT_CONTEXT: TransformContext = {
|
|
18
|
+
code: '',
|
|
19
|
+
source: '',
|
|
20
|
+
filename: '',
|
|
21
|
+
frontmatter: {},
|
|
22
|
+
headings: [],
|
|
23
|
+
registry: undefined,
|
|
24
|
+
config: {
|
|
25
|
+
expressiveCode: null,
|
|
26
|
+
starlightComponents: false,
|
|
27
|
+
shiki: null,
|
|
28
|
+
},
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Creates a TransformContext with default values.
|
|
33
|
+
* Useful for standalone pipeline usage outside of Vite.
|
|
34
|
+
*
|
|
35
|
+
* @example
|
|
36
|
+
* const ctx = createContext({
|
|
37
|
+
* code: compiledJsx,
|
|
38
|
+
* source: markdownSource,
|
|
39
|
+
* filename: '/path/to/file.md',
|
|
40
|
+
* frontmatter: { title: 'Hello' },
|
|
41
|
+
* headings: [{ depth: 1, text: 'Hello' }],
|
|
42
|
+
* });
|
|
43
|
+
*/
|
|
44
|
+
export function createContext(
|
|
45
|
+
overrides: Partial<TransformContext> = {}
|
|
46
|
+
): TransformContext {
|
|
47
|
+
return {
|
|
48
|
+
...DEFAULT_CONTEXT,
|
|
49
|
+
...overrides,
|
|
50
|
+
config: {
|
|
51
|
+
...DEFAULT_CONTEXT.config,
|
|
52
|
+
...(overrides.config || {}),
|
|
53
|
+
} as TransformConfig,
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Creates the standard Xmdx transform pipeline with hook support.
|
|
59
|
+
* This is the same pipeline used by the Vite plugin internally.
|
|
60
|
+
*
|
|
61
|
+
* Hook execution order:
|
|
62
|
+
* 1. afterParse hooks (user transforms after parsing)
|
|
63
|
+
* 2. ExpressiveCode rewriting (built-in)
|
|
64
|
+
* 3. beforeInject hooks (user transforms before injection)
|
|
65
|
+
* 4. Component injection from registry (built-in)
|
|
66
|
+
* 5. Shiki highlighting (built-in)
|
|
67
|
+
* 6. beforeOutput hooks (user transforms before output)
|
|
68
|
+
*
|
|
69
|
+
* @example
|
|
70
|
+
* // Create pipeline with custom hooks
|
|
71
|
+
* const pipeline = createPipeline({
|
|
72
|
+
* afterParse: [myCustomTransform],
|
|
73
|
+
* beforeOutput: [addMetadataComments],
|
|
74
|
+
* });
|
|
75
|
+
*
|
|
76
|
+
* // Use the pipeline
|
|
77
|
+
* const ctx = createContext({ code, source, filename });
|
|
78
|
+
* const result = await pipeline(ctx);
|
|
79
|
+
*/
|
|
80
|
+
export function createPipeline(options: PipelineOptions = {}): Transform {
|
|
81
|
+
const { afterParse = [], beforeInject = [], beforeOutput = [] } = options;
|
|
82
|
+
|
|
83
|
+
return pipe<TransformContext>(
|
|
84
|
+
// User hooks: afterParse
|
|
85
|
+
...afterParse,
|
|
86
|
+
|
|
87
|
+
// Built-in: ExpressiveCode rewriting
|
|
88
|
+
transformExpressiveCode,
|
|
89
|
+
|
|
90
|
+
// User hooks: beforeInject
|
|
91
|
+
...beforeInject,
|
|
92
|
+
|
|
93
|
+
// Built-in: Component injection (unified, registry-driven)
|
|
94
|
+
transformInjectComponentsFromRegistry,
|
|
95
|
+
|
|
96
|
+
// Built-in: Shiki highlighting
|
|
97
|
+
transformShikiHighlight,
|
|
98
|
+
|
|
99
|
+
// User hooks: beforeOutput
|
|
100
|
+
...beforeOutput
|
|
101
|
+
);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Creates a custom pipeline with only the specified transforms.
|
|
106
|
+
* Unlike createPipeline, this doesn't include any built-in transforms.
|
|
107
|
+
* Useful for testing or when you want full control over the pipeline.
|
|
108
|
+
*
|
|
109
|
+
* @example
|
|
110
|
+
* // Create a minimal pipeline for testing
|
|
111
|
+
* const testPipeline = createCustomPipeline(
|
|
112
|
+
* transformExpressiveCode,
|
|
113
|
+
* myCustomTransform,
|
|
114
|
+
* );
|
|
115
|
+
*
|
|
116
|
+
* // Use it standalone
|
|
117
|
+
* const result = await testPipeline(ctx);
|
|
118
|
+
*/
|
|
119
|
+
export function createCustomPipeline(...transforms: Transform[]): Transform {
|
|
120
|
+
return pipe<TransformContext>(...transforms);
|
|
121
|
+
}
|
|
@@ -0,0 +1,251 @@
|
|
|
1
|
+
import { describe, it, expect } from 'bun:test';
|
|
2
|
+
import { pipe, when, tap } from './pipe.js';
|
|
3
|
+
|
|
4
|
+
describe('pipe', () => {
|
|
5
|
+
it('should compose sync functions', async () => {
|
|
6
|
+
const add1 = (x: number): number => x + 1;
|
|
7
|
+
const mult2 = (x: number): number => x * 2;
|
|
8
|
+
const sub3 = (x: number): number => x - 3;
|
|
9
|
+
|
|
10
|
+
const pipeline = pipe(add1, mult2, sub3);
|
|
11
|
+
const result = await pipeline(5);
|
|
12
|
+
|
|
13
|
+
// (5 + 1) * 2 - 3 = 9
|
|
14
|
+
expect(result).toBe(9);
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
it('should compose async functions', async () => {
|
|
18
|
+
const asyncAdd = async (x: number): Promise<number> => x + 1;
|
|
19
|
+
const asyncMult = async (x: number): Promise<number> => x * 2;
|
|
20
|
+
|
|
21
|
+
const pipeline = pipe(asyncAdd, asyncMult);
|
|
22
|
+
const result = await pipeline(5);
|
|
23
|
+
|
|
24
|
+
// (5 + 1) * 2 = 12
|
|
25
|
+
expect(result).toBe(12);
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
it('should handle mixed sync and async functions', async () => {
|
|
29
|
+
const syncFn = (x: number): number => x + 1;
|
|
30
|
+
const asyncFn = async (x: number): Promise<number> => x * 2;
|
|
31
|
+
|
|
32
|
+
const pipeline = pipe(syncFn, asyncFn, syncFn);
|
|
33
|
+
const result = await pipeline(5);
|
|
34
|
+
|
|
35
|
+
// ((5 + 1) * 2) + 1 = 13
|
|
36
|
+
expect(result).toBe(13);
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
it('should pass data through empty pipeline', async () => {
|
|
40
|
+
const pipeline = pipe<number>();
|
|
41
|
+
const result = await pipeline(42);
|
|
42
|
+
|
|
43
|
+
expect(result).toBe(42);
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
it('should work with object transformations', async () => {
|
|
47
|
+
interface Data {
|
|
48
|
+
initial?: string;
|
|
49
|
+
added?: boolean;
|
|
50
|
+
count?: number;
|
|
51
|
+
}
|
|
52
|
+
const addField = (obj: Data): Data => ({ ...obj, added: true });
|
|
53
|
+
const incrementCount = (obj: Data): Data => ({ ...obj, count: (obj.count || 0) + 1 });
|
|
54
|
+
|
|
55
|
+
const pipeline = pipe(addField, incrementCount);
|
|
56
|
+
const result = await pipeline({ initial: 'data' });
|
|
57
|
+
|
|
58
|
+
expect(result).toEqual({
|
|
59
|
+
initial: 'data',
|
|
60
|
+
added: true,
|
|
61
|
+
count: 1,
|
|
62
|
+
});
|
|
63
|
+
});
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
describe('when', () => {
|
|
67
|
+
it('should execute transform when condition is true', async () => {
|
|
68
|
+
const transform = when(true, (x: number) => x * 2);
|
|
69
|
+
const result = await transform(5);
|
|
70
|
+
|
|
71
|
+
expect(result).toBe(10);
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
it('should skip transform when condition is false', async () => {
|
|
75
|
+
const transform = when(false, (x: number) => x * 2);
|
|
76
|
+
const result = await transform(5);
|
|
77
|
+
|
|
78
|
+
expect(result).toBe(5);
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
it('should evaluate dynamic condition with function', async () => {
|
|
82
|
+
const transform = when((x: number) => x > 10, (x: number) => x * 2);
|
|
83
|
+
|
|
84
|
+
const result1 = await transform(5);
|
|
85
|
+
expect(result1).toBe(5); // Condition false, unchanged
|
|
86
|
+
|
|
87
|
+
const result2 = await transform(15);
|
|
88
|
+
expect(result2).toBe(30); // Condition true, transformed
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
it('should work with async transforms', async () => {
|
|
92
|
+
const asyncTransform = when(true, async (x: number) => {
|
|
93
|
+
return x + 10;
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
const result = await asyncTransform(5);
|
|
97
|
+
expect(result).toBe(15);
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
it('should work in pipeline composition', async () => {
|
|
101
|
+
const pipeline = pipe(
|
|
102
|
+
(x: number) => x + 1,
|
|
103
|
+
when(false, (x: number) => x * 100), // Skipped
|
|
104
|
+
when(true, (x: number) => x * 2), // Applied
|
|
105
|
+
(x: number) => x - 3
|
|
106
|
+
);
|
|
107
|
+
|
|
108
|
+
const result = await pipeline(5);
|
|
109
|
+
// (5 + 1) * 2 - 3 = 9
|
|
110
|
+
expect(result).toBe(9);
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
it('should handle condition function with object data', async () => {
|
|
114
|
+
interface Data {
|
|
115
|
+
enabled: boolean;
|
|
116
|
+
processed?: boolean;
|
|
117
|
+
}
|
|
118
|
+
const transform = when<Data>(
|
|
119
|
+
(data) => data.enabled === true,
|
|
120
|
+
(data) => ({ ...data, processed: true })
|
|
121
|
+
);
|
|
122
|
+
|
|
123
|
+
const result1 = await transform({ enabled: false });
|
|
124
|
+
expect(result1).toEqual({ enabled: false });
|
|
125
|
+
|
|
126
|
+
const result2 = await transform({ enabled: true });
|
|
127
|
+
expect(result2).toEqual({ enabled: true, processed: true });
|
|
128
|
+
});
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
describe('tap', () => {
|
|
132
|
+
it('should execute side effect without modifying data', async () => {
|
|
133
|
+
let sideEffectValue = 0;
|
|
134
|
+
const transform = tap((x: number) => {
|
|
135
|
+
sideEffectValue = x * 2;
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
const result = await transform(5);
|
|
139
|
+
|
|
140
|
+
expect(result).toBe(5); // Data unchanged
|
|
141
|
+
expect(sideEffectValue).toBe(10); // Side effect executed
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
it('should work with async side effects', async () => {
|
|
145
|
+
const collected: number[] = [];
|
|
146
|
+
const transform = tap(async (x: number) => {
|
|
147
|
+
collected.push(x);
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
await transform(10);
|
|
151
|
+
await transform(20);
|
|
152
|
+
|
|
153
|
+
expect(collected).toEqual([10, 20]);
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
it('should work in pipeline composition', async () => {
|
|
157
|
+
const log: string[] = [];
|
|
158
|
+
const pipeline = pipe(
|
|
159
|
+
(x: number) => x + 1,
|
|
160
|
+
tap((x: number) => log.push(`after add: ${x}`)),
|
|
161
|
+
(x: number) => x * 2,
|
|
162
|
+
tap((x: number) => log.push(`after mult: ${x}`)),
|
|
163
|
+
(x: number) => x - 3
|
|
164
|
+
);
|
|
165
|
+
|
|
166
|
+
const result = await pipeline(5);
|
|
167
|
+
|
|
168
|
+
expect(result).toBe(9); // (5 + 1) * 2 - 3 = 9
|
|
169
|
+
expect(log).toEqual([
|
|
170
|
+
'after add: 6',
|
|
171
|
+
'after mult: 12',
|
|
172
|
+
]);
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
it('should not affect data flow even if side effect does nothing', async () => {
|
|
176
|
+
const transform = tap(() => {
|
|
177
|
+
// Side effect that doesn't throw
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
const result = await transform({ data: 'test' });
|
|
181
|
+
expect(result).toEqual({ data: 'test' });
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
it('should handle object mutations in side effect', async () => {
|
|
185
|
+
const metadata: { callCount: number; lastValue?: number } = { callCount: 0 };
|
|
186
|
+
const transform = tap((data: number) => {
|
|
187
|
+
metadata.callCount++;
|
|
188
|
+
metadata.lastValue = data;
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
await transform(10);
|
|
192
|
+
await transform(20);
|
|
193
|
+
|
|
194
|
+
expect(metadata.callCount).toBe(2);
|
|
195
|
+
expect(metadata.lastValue).toBe(20);
|
|
196
|
+
});
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
describe('integration', () => {
|
|
200
|
+
it('should combine pipe, when, and tap', async () => {
|
|
201
|
+
const log: string[] = [];
|
|
202
|
+
const pipeline = pipe(
|
|
203
|
+
(x: number) => x + 1,
|
|
204
|
+
tap((x: number) => log.push(`step1: ${x}`)),
|
|
205
|
+
when((x: number) => x % 2 === 0, (x: number) => x * 10), // Only for even numbers
|
|
206
|
+
tap((x: number) => log.push(`step2: ${x}`)),
|
|
207
|
+
(x: number) => x + 5
|
|
208
|
+
);
|
|
209
|
+
|
|
210
|
+
const result1 = await pipeline(5);
|
|
211
|
+
// (5 + 1 = 6) * 10 + 5 = 65
|
|
212
|
+
expect(result1).toBe(65);
|
|
213
|
+
expect(log).toEqual(['step1: 6', 'step2: 60']);
|
|
214
|
+
|
|
215
|
+
log.length = 0; // Clear log
|
|
216
|
+
|
|
217
|
+
const result2 = await pipeline(4);
|
|
218
|
+
// (4 + 1 = 5) + 5 = 10 (when condition false, no mult)
|
|
219
|
+
expect(result2).toBe(10);
|
|
220
|
+
expect(log).toEqual(['step1: 5', 'step2: 5']);
|
|
221
|
+
});
|
|
222
|
+
|
|
223
|
+
it('should work with complex data transformations', async () => {
|
|
224
|
+
interface Data {
|
|
225
|
+
enabled: boolean;
|
|
226
|
+
value: number;
|
|
227
|
+
step?: number;
|
|
228
|
+
processed?: boolean;
|
|
229
|
+
logs?: string[];
|
|
230
|
+
}
|
|
231
|
+
const pipeline = pipe<Data>(
|
|
232
|
+
(data) => ({ ...data, step: 1 }),
|
|
233
|
+
tap((data) => { data.logs = ['start']; }),
|
|
234
|
+
when(
|
|
235
|
+
(data) => data.enabled,
|
|
236
|
+
(data) => ({ ...data, processed: true, step: 2 })
|
|
237
|
+
),
|
|
238
|
+
(data) => ({ ...data, step: (data.step ?? 0) + 1 })
|
|
239
|
+
);
|
|
240
|
+
|
|
241
|
+
const result = await pipeline({ enabled: true, value: 42 });
|
|
242
|
+
|
|
243
|
+
expect(result).toEqual({
|
|
244
|
+
enabled: true,
|
|
245
|
+
value: 42,
|
|
246
|
+
step: 3,
|
|
247
|
+
processed: true,
|
|
248
|
+
logs: ['start'],
|
|
249
|
+
});
|
|
250
|
+
});
|
|
251
|
+
});
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Functional pipeline utilities for composing transformations
|
|
3
|
+
* @module pipeline/pipe
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Compose functions into a pipeline that passes data through each step.
|
|
8
|
+
* Supports both sync and async functions.
|
|
9
|
+
*
|
|
10
|
+
* @example
|
|
11
|
+
* const transform = pipe(
|
|
12
|
+
* (data) => step1(data),
|
|
13
|
+
* async (data) => await step2(data),
|
|
14
|
+
* (data) => step3(data)
|
|
15
|
+
* );
|
|
16
|
+
* const result = await transform(input);
|
|
17
|
+
*/
|
|
18
|
+
export function pipe<T>(...fns: Array<(input: T) => T | Promise<T>>): (input: T) => Promise<T> {
|
|
19
|
+
return async (input: T): Promise<T> => {
|
|
20
|
+
let result = input;
|
|
21
|
+
for (const fn of fns) {
|
|
22
|
+
result = await fn(result);
|
|
23
|
+
}
|
|
24
|
+
return result;
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Create a conditional pipeline step.
|
|
30
|
+
* Only executes transform if condition is met.
|
|
31
|
+
*
|
|
32
|
+
* @example
|
|
33
|
+
* const pipeline = pipe(
|
|
34
|
+
* when(config.enableShiki, (data) => highlightCode(data)),
|
|
35
|
+
* when((data) => data.hasComponents, (data) => injectImports(data))
|
|
36
|
+
* );
|
|
37
|
+
*/
|
|
38
|
+
export function when<T>(
|
|
39
|
+
condition: boolean | ((data: T) => boolean),
|
|
40
|
+
transform: (data: T) => T | Promise<T>
|
|
41
|
+
): (data: T) => Promise<T> {
|
|
42
|
+
return async (data: T): Promise<T> => {
|
|
43
|
+
const shouldRun = typeof condition === 'function'
|
|
44
|
+
? condition(data)
|
|
45
|
+
: condition;
|
|
46
|
+
|
|
47
|
+
if (!shouldRun) {
|
|
48
|
+
return data;
|
|
49
|
+
}
|
|
50
|
+
return await transform(data);
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Tap into pipeline for side effects without modifying data.
|
|
56
|
+
* Useful for logging, validation, or triggering watchers.
|
|
57
|
+
*
|
|
58
|
+
* @example
|
|
59
|
+
* const pipeline = pipe(
|
|
60
|
+
* transform1,
|
|
61
|
+
* tap((data) => console.log('After transform1:', data)),
|
|
62
|
+
* transform2
|
|
63
|
+
* );
|
|
64
|
+
*/
|
|
65
|
+
export function tap<T>(sideEffect: (data: T) => void | Promise<void>): (data: T) => Promise<T> {
|
|
66
|
+
return async (data: T): Promise<T> => {
|
|
67
|
+
await sideEffect(data);
|
|
68
|
+
return data;
|
|
69
|
+
};
|
|
70
|
+
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Type definitions for Xmdx transform pipeline
|
|
3
|
+
* @module pipeline/types
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import type { Registry } from 'xmdx/registry';
|
|
7
|
+
import type { ExpressiveCodeConfig, StarlightUserConfig } from '../utils/config.js';
|
|
8
|
+
import type { ShikiHighlighter } from '../transforms/shiki.js';
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Configuration available to transforms.
|
|
12
|
+
*/
|
|
13
|
+
export interface TransformConfig {
|
|
14
|
+
/** ExpressiveCode configuration or null if disabled */
|
|
15
|
+
expressiveCode: ExpressiveCodeConfig | null;
|
|
16
|
+
/** Starlight components configuration */
|
|
17
|
+
starlightComponents: boolean | StarlightUserConfig;
|
|
18
|
+
/** Shiki highlighter function or null if disabled */
|
|
19
|
+
shiki: ShikiHighlighter | null;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Transform context passed through the pipeline.
|
|
24
|
+
* Contains the current code state and metadata needed by transforms.
|
|
25
|
+
*/
|
|
26
|
+
export interface TransformContext {
|
|
27
|
+
/** Current JSX code being transformed */
|
|
28
|
+
code: string;
|
|
29
|
+
/** Original markdown source */
|
|
30
|
+
source: string;
|
|
31
|
+
/** Source file path */
|
|
32
|
+
filename: string;
|
|
33
|
+
/** Parsed frontmatter object */
|
|
34
|
+
frontmatter: Record<string, unknown>;
|
|
35
|
+
/** Extracted headings from the document */
|
|
36
|
+
headings: Array<{ depth: number; slug: string; text: string }>;
|
|
37
|
+
/** Component registry for import resolution */
|
|
38
|
+
registry?: Registry;
|
|
39
|
+
/** Plugin configuration for transforms */
|
|
40
|
+
config: TransformConfig;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* A transform function that takes a context and returns a modified context.
|
|
45
|
+
* Can be synchronous or asynchronous.
|
|
46
|
+
*/
|
|
47
|
+
export type Transform = (ctx: TransformContext) => TransformContext | Promise<TransformContext>;
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Options for creating a standard Xmdx pipeline with hooks.
|
|
51
|
+
*/
|
|
52
|
+
export interface PipelineOptions {
|
|
53
|
+
/** Hooks to run after parsing, before built-in transforms */
|
|
54
|
+
afterParse?: Transform[];
|
|
55
|
+
/** Hooks to run before component injection */
|
|
56
|
+
beforeInject?: Transform[];
|
|
57
|
+
/** Hooks to run after all transforms, before output */
|
|
58
|
+
beforeOutput?: Transform[];
|
|
59
|
+
}
|