forked from CopilotKit/CopilotKit
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathrewrite-demo-code.ts
More file actions
130 lines (121 loc) · 4.22 KB
/
Copy pathrewrite-demo-code.ts
File metadata and controls
130 lines (121 loc) · 4.22 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
// Pure source-rewrite pass for `<DemoCode>` references in a concept
// file. Lives in its own file (no JSX) so test code can import it
// without pulling the React component shim through the test graph.
// See setup-concept.tsx for the orchestrator that drives this pass.
import fs from "fs";
import path from "path";
import { extractRegion, inferLanguage } from "./demo-code";
import { resolveWithinDir } from "./safe-fs";
/**
* Match the value of a string-literal JSX attribute. Returns undefined
* for expression-valued attrs (e.g. `file={x}`) so the rewrite pass can
* leave those references intact for the runtime component shim.
*/
function matchAttr(attrs: string, name: string): string | undefined {
const dq = new RegExp(`\\b${name}="([^"]*)"`).exec(attrs);
if (dq) return dq[1];
const sq = new RegExp(`\\b${name}='([^']*)'`).exec(attrs);
if (sq) return sq[1];
return undefined;
}
/**
* Pre-expand `<DemoCode file="..." region="..." [language="..."] [title="..."] />`
* JSX references in a concept-file source into fenced markdown blocks
* sourced from `packageRoot`. The rewritten fences flow through the
* regular MDXRemote → rehypeCode pipeline so they pick up Shiki
* highlighting + the MdxCodeBlock chrome (copy button + figcaption).
*
* Only string-literal props are handled here. References with
* expression-valued props (e.g. `file={something}`) are left intact
* for the runtime component shim to resolve. Same posture as
* `inlineSnippets` in docs-render.tsx.
*
* A reference whose file or region can't be found is replaced with an
* empty string (logged to the server console). The body shouldn't
* silently surface a broken `<DemoCode>` JSX tag — that would crash
* the MDX compile.
*/
export interface RewriteDemoCodeOptions {
onError?: "warn" | "throw";
}
function handleRewriteError(
message: string,
options: RewriteDemoCodeOptions,
): string {
if (options.onError === "throw") {
throw new Error(message);
}
console.warn(message);
return "";
}
function formatFenceTitle(title: string): string {
return JSON.stringify(title);
}
const DEMO_CODE_TAG_RX = /<DemoCode\b((?:"[^"]*"|'[^']*'|[^'"<>])*)\/>/g;
export function rewriteDemoCode(
source: string,
packageRoot: string,
options: RewriteDemoCodeOptions = {},
): string {
// Match `<DemoCode ... />` with quoted attributes, including values
// such as `title="A > B"`, without spanning into the next JSX tag.
return source.replace(DEMO_CODE_TAG_RX, (match, attrs: string) => {
const file = matchAttr(attrs, "file");
const region = matchAttr(attrs, "region");
if (!file || !region) {
if (options.onError === "throw") {
throw new Error(
`[demo-code] DemoCode references must use static file and region props: ${match}`,
);
}
return match;
}
const language = matchAttr(attrs, "language");
const title = matchAttr(attrs, "title");
const resolved = resolveWithinDir(packageRoot, file);
if (!resolved || !fs.existsSync(resolved)) {
return handleRewriteError(
`[demo-code] file not found ${file} in package root ${packageRoot}`,
options,
);
}
let raw: string;
try {
raw = fs.readFileSync(resolved, "utf-8");
} catch (err) {
return handleRewriteError(
`[demo-code] failed to read ${resolved}: ${(err as Error).message}`,
options,
);
}
const ext = file.includes(".")
? file.slice(file.lastIndexOf(".") + 1).toLowerCase()
: "";
let body: string | null;
try {
body = extractRegion(raw, region, ext);
} catch (err) {
return handleRewriteError(
`[demo-code] extraction failed ${file} ${region}: ${(err as Error).message}`,
options,
);
}
if (body === null) {
return handleRewriteError(
`[demo-code] region not found ${region} in ${file}`,
options,
);
}
const lang = language ?? inferLanguage(file);
const fenceTitle = title ?? path.basename(file);
// 4-tilde fence so the embedded body can safely contain triple
// backticks without prematurely closing the fence.
return [
"",
`~~~~${lang} title=${formatFenceTitle(fenceTitle)}`,
body,
"~~~~",
"",
].join("\n");
});
}