repl-sdk 1.5.1 → 1.5.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/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "repl-sdk",
|
|
3
|
-
"version": "1.5.
|
|
3
|
+
"version": "1.5.2",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"exports": {
|
|
6
6
|
".": {
|
|
@@ -45,6 +45,7 @@
|
|
|
45
45
|
"@types/hast": "^3.0.4",
|
|
46
46
|
"@types/mdast": "^4.0.4",
|
|
47
47
|
"common-tags": "^1.8.2",
|
|
48
|
+
"decorator-transforms": "2.3.1",
|
|
48
49
|
"eslint": "^9.39.1",
|
|
49
50
|
"prettier": "^3.7.4",
|
|
50
51
|
"publint": "^0.3.16",
|
|
@@ -103,13 +104,14 @@
|
|
|
103
104
|
"extends": "../../package.json"
|
|
104
105
|
},
|
|
105
106
|
"scripts": {
|
|
106
|
-
"lint
|
|
107
|
+
"lint": "concurrently \"pnpm:lint:*(!fix)\" --names \"lint:\" --prefixColors auto",
|
|
108
|
+
"lint:fix": "concurrently \"pnpm:lint:*:fix\" --names \"fix:\" --prefixColors auto && pnpm run format",
|
|
107
109
|
"example": "cd example && vite",
|
|
108
110
|
"lint:package": "pnpm publint",
|
|
109
111
|
"lint:js": "pnpm -w exec lint js",
|
|
110
112
|
"lint:types": "tsc --noEmit",
|
|
111
113
|
"lint:js:fix": "pnpm -w exec lint js:fix",
|
|
112
|
-
"
|
|
114
|
+
"format": "pnpm -w exec lint prettier:fix",
|
|
113
115
|
"lint:prettier": "pnpm -w exec lint prettier",
|
|
114
116
|
"test:node": "vitest"
|
|
115
117
|
}
|
|
@@ -12,6 +12,7 @@
|
|
|
12
12
|
* @property {CodeBlock[]} codeBlocks
|
|
13
13
|
*/
|
|
14
14
|
|
|
15
|
+
import remarkEscapeComponents, { REPL_LT } from '../../remark-escape-components.js';
|
|
15
16
|
import { buildCompiler } from './build-compiler.js';
|
|
16
17
|
|
|
17
18
|
export { buildCompiler } from './build-compiler.js';
|
|
@@ -22,36 +23,67 @@ export { buildCompiler } from './build-compiler.js';
|
|
|
22
23
|
* @returns {Promise<ParseResult>}
|
|
23
24
|
*/
|
|
24
25
|
export async function parseMarkdown(input, options) {
|
|
25
|
-
const markdownCompiler =
|
|
26
|
+
const markdownCompiler =
|
|
27
|
+
options?.compiler ??
|
|
28
|
+
buildCompiler({
|
|
29
|
+
...options,
|
|
30
|
+
remarkPlugins: [...(options?.remarkPlugins || []), remarkEscapeComponents],
|
|
31
|
+
});
|
|
26
32
|
const processed = await markdownCompiler.process(input);
|
|
27
33
|
const liveCode = /** @type {CodeBlock[]} */ (processed.data.liveCode || []);
|
|
28
34
|
// @ts-ignore - processed is typed as unknown due to unified processor complexity
|
|
29
35
|
let templateOnly = processed.toString();
|
|
30
36
|
|
|
31
|
-
//
|
|
32
|
-
//
|
|
33
|
-
//
|
|
34
|
-
|
|
35
|
-
|
|
37
|
+
// 1. Convert the placeholder written by the remark plugin to <
|
|
38
|
+
// This placeholder survives the entire unified pipeline without being
|
|
39
|
+
// entity-encoded, so no double-escaping can occur.
|
|
40
|
+
if (REPL_LT) {
|
|
41
|
+
templateOnly = templateOnly.replaceAll(REPL_LT, '<');
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// 2. The pipeline may HTML-escape `<` for PascalCase component invocations
|
|
45
|
+
// that appear in regular markdown (outside code/backticks). Undo that so
|
|
46
|
+
// Glimmer can still invoke them. We only unescape outside <code> elements
|
|
47
|
+
// (and outside <pre> blocks) to preserve escaping in code.
|
|
48
|
+
templateOnly = unescapeComponentsOutsideCode(templateOnly);
|
|
49
|
+
|
|
50
|
+
return { text: templateOnly, codeBlocks: liveCode };
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Undo HTML-escaping of PascalCase component tags that appear outside
|
|
55
|
+
* `<code>` and `<pre>` blocks so Glimmer can invoke them.
|
|
56
|
+
*
|
|
57
|
+
* @param {string} html
|
|
58
|
+
* @returns {string}
|
|
59
|
+
*/
|
|
60
|
+
function unescapeComponentsOutsideCode(html) {
|
|
61
|
+
// Split by <pre>…</pre> blocks first – never touch code fences.
|
|
62
|
+
const parts = html.split(/(<pre[\s\S]*?<\/pre>)/gi);
|
|
36
63
|
|
|
37
64
|
for (let i = 0; i < parts.length; i++) {
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
65
|
+
// Only touch content outside <pre>
|
|
66
|
+
if (i % 2 === 0) {
|
|
67
|
+
// Split by <code>…</code> so we skip inline code too
|
|
68
|
+
const part = parts[i] ?? '';
|
|
69
|
+
const codeParts = part.split(/(<code[^>]*>[\s\S]*?<\/code>)/gi);
|
|
70
|
+
|
|
71
|
+
for (let j = 0; j < codeParts.length; j++) {
|
|
72
|
+
const segment = codeParts[j];
|
|
73
|
+
|
|
74
|
+
// Even indices are outside <code> – unescape PascalCase there
|
|
75
|
+
if (j % 2 === 0 && segment) {
|
|
76
|
+
codeParts[j] = segment
|
|
77
|
+
.replace(/<([A-Z][a-zA-Z0-9]*\s[^<]*?)>/g, (_m, content) =>
|
|
78
|
+
content.includes('@') ? `<${content}>` : _m
|
|
79
|
+
)
|
|
80
|
+
.replace(/<\/([A-Z][a-zA-Z0-9]*)>/g, '</$1>');
|
|
47
81
|
}
|
|
82
|
+
}
|
|
48
83
|
|
|
49
|
-
|
|
50
|
-
});
|
|
84
|
+
parts[i] = codeParts.join('');
|
|
51
85
|
}
|
|
52
86
|
}
|
|
53
87
|
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
return { text: templateOnly, codeBlocks: liveCode };
|
|
88
|
+
return parts.join('');
|
|
57
89
|
}
|
|
@@ -230,7 +230,7 @@ describe('default features', () => {
|
|
|
230
230
|
expect(result).toMatchInlineSnapshot(`
|
|
231
231
|
{
|
|
232
232
|
"codeBlocks": [],
|
|
233
|
-
"text": "<h2 id="hello-foo-two"><code
|
|
233
|
+
"text": "<h2 id="hello-foo-two"><code><Hello @foo="two" /></code></h2>",
|
|
234
234
|
}
|
|
235
235
|
`);
|
|
236
236
|
});
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { visit } from 'unist-util-visit';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* A unique placeholder that replaces `<` in PascalCase component tags
|
|
5
|
+
* inside inline code and non-live code fences. It survives the unified
|
|
6
|
+
* pipeline without being entity-encoded, and is converted to `<`
|
|
7
|
+
* in the final post-processing step inside `parseMarkdown()`.
|
|
8
|
+
*/
|
|
9
|
+
export const REPL_LT = '__REPL_LT__';
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Remark plugin: escape PascalCase component tags in `inlineCode` and
|
|
13
|
+
* non-live `code` (code fence) nodes by replacing `<` with a placeholder.
|
|
14
|
+
*/
|
|
15
|
+
function remarkEscapeComponents() {
|
|
16
|
+
/** @param {import('mdast').Root} tree */
|
|
17
|
+
return (tree) => {
|
|
18
|
+
visit(tree, (node) => {
|
|
19
|
+
// Inline code (backticks)
|
|
20
|
+
if (node.type === 'inlineCode') {
|
|
21
|
+
node.value = node.value
|
|
22
|
+
.replace(/<([A-Z][a-zA-Z0-9]*(?:\s[^<]*)?)>/g, REPL_LT + '$1>')
|
|
23
|
+
.replace(/<\/([A-Z][a-zA-Z0-9]*)>/g, REPL_LT + '/$1>');
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// Code fences (``` blocks)
|
|
27
|
+
if (node.type === 'code') {
|
|
28
|
+
// Only escape if not live
|
|
29
|
+
if (!/\blive\b/.test(node.meta || '')) {
|
|
30
|
+
node.value = node.value
|
|
31
|
+
.replace(/<([A-Z][a-zA-Z0-9]*(?:\s[^<]*)?)>/g, REPL_LT + '$1>')
|
|
32
|
+
.replace(/<\/([A-Z][a-zA-Z0-9]*)>/g, REPL_LT + '/$1>');
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
});
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export default remarkEscapeComponents;
|