@dogsbay/format-starlight 0.2.0-beta.13 → 0.2.0-beta.130
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/dist/adapter.d.ts +69 -1
- package/dist/adapters/cloudflare.d.ts +183 -1
- package/dist/adapters/cloudflare.js +1995 -60
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +671 -6
- package/dist/fences.d.ts +16 -0
- package/dist/fences.js +49 -0
- package/dist/github-slug.d.ts +19 -0
- package/dist/github-slug.js +112 -0
- package/dist/js-literal.d.ts +12 -0
- package/dist/js-literal.js +249 -0
- package/dist/jsx-to-jinja.d.ts +62 -0
- package/dist/jsx-to-jinja.js +869 -0
- package/dist/parse-mdx.d.ts +61 -1
- package/dist/parse-mdx.js +465 -31
- package/dist/parse.js +11 -4
- package/dist/template-tokens.d.ts +63 -0
- package/dist/template-tokens.js +206 -0
- package/package.json +9 -7
package/dist/fences.d.ts
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/** Fence-aware text helpers shared by the parser and adapters. */
|
|
2
|
+
/**
|
|
3
|
+
* Apply `fn` to the text outside fenced code blocks; fences pass through
|
|
4
|
+
* untouched. A fence closes on a line holding only the same character, at
|
|
5
|
+
* least as long, as CommonMark reads it.
|
|
6
|
+
*/
|
|
7
|
+
export declare function outsideFences(source: string, fn: (text: string) => string): string;
|
|
8
|
+
/**
|
|
9
|
+
* Apply `fn` with inline code spans (`` `x` ``, ``` ``x`` ```) masked, so a
|
|
10
|
+
* rewrite cannot reach into them. A style guide writes "the
|
|
11
|
+
* `<GlossaryTooltip>` component", and a tag rewrite reaching into that span
|
|
12
|
+
* ate the prose up to the next real closing tag. MASKED, not split around:
|
|
13
|
+
* a tag's body may itself be code (`<GlossaryTooltip …>`robots.txt`</…>`),
|
|
14
|
+
* and splitting separated the opener from its closer.
|
|
15
|
+
*/
|
|
16
|
+
export declare function outsideInlineCode(text: string, fn: (text: string) => string): string;
|
package/dist/fences.js
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/** Fence-aware text helpers shared by the parser and adapters. */
|
|
2
|
+
/**
|
|
3
|
+
* Apply `fn` to the text outside fenced code blocks; fences pass through
|
|
4
|
+
* untouched. A fence closes on a line holding only the same character, at
|
|
5
|
+
* least as long, as CommonMark reads it.
|
|
6
|
+
*/
|
|
7
|
+
export function outsideFences(source, fn) {
|
|
8
|
+
const out = [];
|
|
9
|
+
let prose = [];
|
|
10
|
+
let fence = null;
|
|
11
|
+
const flush = () => {
|
|
12
|
+
if (prose.length)
|
|
13
|
+
out.push(fn(prose.join("")));
|
|
14
|
+
prose = [];
|
|
15
|
+
};
|
|
16
|
+
for (const line of source.split(/(?<=\n)/)) {
|
|
17
|
+
const m = /^[ \t]*(`{3,}|~{3,})/.exec(line); // any indent: MDX opens a fence at any indent
|
|
18
|
+
if (fence) {
|
|
19
|
+
out.push(line);
|
|
20
|
+
if (m && m[1][0] === fence[0] && m[1].length >= fence.length && line.trim() === m[1])
|
|
21
|
+
fence = null;
|
|
22
|
+
}
|
|
23
|
+
else if (m && !(m[1][0] === "`" && line.slice(line.indexOf(m[1]) + m[1].length).includes("`"))) {
|
|
24
|
+
flush();
|
|
25
|
+
out.push(line);
|
|
26
|
+
fence = m[1];
|
|
27
|
+
}
|
|
28
|
+
else {
|
|
29
|
+
prose.push(line);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
flush();
|
|
33
|
+
return out.join("");
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Apply `fn` with inline code spans (`` `x` ``, ``` ``x`` ```) masked, so a
|
|
37
|
+
* rewrite cannot reach into them. A style guide writes "the
|
|
38
|
+
* `<GlossaryTooltip>` component", and a tag rewrite reaching into that span
|
|
39
|
+
* ate the prose up to the next real closing tag. MASKED, not split around:
|
|
40
|
+
* a tag's body may itself be code (`<GlossaryTooltip …>`robots.txt`</…>`),
|
|
41
|
+
* and splitting separated the opener from its closer.
|
|
42
|
+
*/
|
|
43
|
+
export function outsideInlineCode(text, fn) {
|
|
44
|
+
const spans = [];
|
|
45
|
+
const masked = text.replace(/(`+)(?!`)[\s\S]*?(?<!`)\1(?!`)/g, (m) => `\u0000${spans.push(m) - 1}\u0000`);
|
|
46
|
+
if (!spans.length)
|
|
47
|
+
return fn(text);
|
|
48
|
+
return fn(masked).replace(/\u0000(\d+)\u0000/g, (_m, i) => spans[Number(i)]);
|
|
49
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
export declare function githubSlug(value: string): string;
|
|
2
|
+
/** One per page: dedupes the way `new GithubSlugger().slug()` does. */
|
|
3
|
+
export declare class GithubSlugger {
|
|
4
|
+
private occurrences;
|
|
5
|
+
slug(value: string): string;
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* Marks a heading whose id is its own (`<AnchorHeading>`, and the headings
|
|
9
|
+
* resolvers write for component data, which are AnchorHeadings upstream):
|
|
10
|
+
* not re-slugged and outside the page's dedupe. A STRING key (a Symbol does
|
|
11
|
+
* not survive structuredClone), removed by the final pass so it is never
|
|
12
|
+
* serialized as an attribute.
|
|
13
|
+
*/
|
|
14
|
+
export declare const FIXED_SLUG = "__fixedSlug";
|
|
15
|
+
/**
|
|
16
|
+
* Give every heading of a PAGE its Starlight id, in document order, deduped
|
|
17
|
+
* on the page. Headings from `<AnchorHeading>` keep the id they were given.
|
|
18
|
+
*/
|
|
19
|
+
export declare function assignHeadingSlugs(tree: unknown[]): void;
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* GitHub-style heading ids, as Cloudflare's docs assign them (their Sätteri
|
|
3
|
+
* `heading-slugs` plugin → github-slugger 2): lowercase, drop the characters
|
|
4
|
+
* in github-slugger's table, each space → `-` (runs NOT collapsed), and
|
|
5
|
+
* `-1`, `-2` for repeats within one compile. The table below is copied
|
|
6
|
+
* verbatim from github-slugger 2.0.0 `regex.js` (ISC, © 2015 Dan Flettre):
|
|
7
|
+
* `\p{…}` classes are close but not exact (`²` and new-Unicode letters
|
|
8
|
+
* differ), and adding the package re-resolved unrelated lockfile peers.
|
|
9
|
+
*
|
|
10
|
+
* github-slugger: Copyright (c) 2015, Dan Flettre <fletd01@yahoo.com>.
|
|
11
|
+
* Permission to use, copy, modify, and/or distribute this software for any
|
|
12
|
+
* purpose with or without fee is hereby granted, provided that the above
|
|
13
|
+
* copyright notice and this permission notice appear in all copies.
|
|
14
|
+
*
|
|
15
|
+
* Imported links were written against THESE ids; the old rule
|
|
16
|
+
* (`[^a-z0-9]+ → -`, from the heading's raw Markdown) broke 16% of
|
|
17
|
+
* cross-page fragment links on the Cloudflare corpus ("1.1.1.1 for
|
|
18
|
+
* Families" is `1111-for-families`, not `1-1-1-1-for-families`).
|
|
19
|
+
*/
|
|
20
|
+
// eslint-disable-next-line no-control-regex, no-misleading-character-class, no-useless-escape
|
|
21
|
+
const REMOVE = /[\0-\x1F!-,\.\/:-@\[-\^`\{-\xA9\xAB-\xB4\xB6-\xB9\xBB-\xBF\xD7\xF7\u02C2-\u02C5\u02D2-\u02DF\u02E5-\u02EB\u02ED\u02EF-\u02FF\u0375\u0378\u0379\u037E\u0380-\u0385\u0387\u038B\u038D\u03A2\u03F6\u0482\u0530\u0557\u0558\u055A-\u055F\u0589-\u0590\u05BE\u05C0\u05C3\u05C6\u05C8-\u05CF\u05EB-\u05EE\u05F3-\u060F\u061B-\u061F\u066A-\u066D\u06D4\u06DD\u06DE\u06E9\u06FD\u06FE\u0700-\u070F\u074B\u074C\u07B2-\u07BF\u07F6-\u07F9\u07FB\u07FC\u07FE\u07FF\u082E-\u083F\u085C-\u085F\u086B-\u089F\u08B5\u08C8-\u08D2\u08E2\u0964\u0965\u0970\u0984\u098D\u098E\u0991\u0992\u09A9\u09B1\u09B3-\u09B5\u09BA\u09BB\u09C5\u09C6\u09C9\u09CA\u09CF-\u09D6\u09D8-\u09DB\u09DE\u09E4\u09E5\u09F2-\u09FB\u09FD\u09FF\u0A00\u0A04\u0A0B-\u0A0E\u0A11\u0A12\u0A29\u0A31\u0A34\u0A37\u0A3A\u0A3B\u0A3D\u0A43-\u0A46\u0A49\u0A4A\u0A4E-\u0A50\u0A52-\u0A58\u0A5D\u0A5F-\u0A65\u0A76-\u0A80\u0A84\u0A8E\u0A92\u0AA9\u0AB1\u0AB4\u0ABA\u0ABB\u0AC6\u0ACA\u0ACE\u0ACF\u0AD1-\u0ADF\u0AE4\u0AE5\u0AF0-\u0AF8\u0B00\u0B04\u0B0D\u0B0E\u0B11\u0B12\u0B29\u0B31\u0B34\u0B3A\u0B3B\u0B45\u0B46\u0B49\u0B4A\u0B4E-\u0B54\u0B58-\u0B5B\u0B5E\u0B64\u0B65\u0B70\u0B72-\u0B81\u0B84\u0B8B-\u0B8D\u0B91\u0B96-\u0B98\u0B9B\u0B9D\u0BA0-\u0BA2\u0BA5-\u0BA7\u0BAB-\u0BAD\u0BBA-\u0BBD\u0BC3-\u0BC5\u0BC9\u0BCE\u0BCF\u0BD1-\u0BD6\u0BD8-\u0BE5\u0BF0-\u0BFF\u0C0D\u0C11\u0C29\u0C3A-\u0C3C\u0C45\u0C49\u0C4E-\u0C54\u0C57\u0C5B-\u0C5F\u0C64\u0C65\u0C70-\u0C7F\u0C84\u0C8D\u0C91\u0CA9\u0CB4\u0CBA\u0CBB\u0CC5\u0CC9\u0CCE-\u0CD4\u0CD7-\u0CDD\u0CDF\u0CE4\u0CE5\u0CF0\u0CF3-\u0CFF\u0D0D\u0D11\u0D45\u0D49\u0D4F-\u0D53\u0D58-\u0D5E\u0D64\u0D65\u0D70-\u0D79\u0D80\u0D84\u0D97-\u0D99\u0DB2\u0DBC\u0DBE\u0DBF\u0DC7-\u0DC9\u0DCB-\u0DCE\u0DD5\u0DD7\u0DE0-\u0DE5\u0DF0\u0DF1\u0DF4-\u0E00\u0E3B-\u0E3F\u0E4F\u0E5A-\u0E80\u0E83\u0E85\u0E8B\u0EA4\u0EA6\u0EBE\u0EBF\u0EC5\u0EC7\u0ECE\u0ECF\u0EDA\u0EDB\u0EE0-\u0EFF\u0F01-\u0F17\u0F1A-\u0F1F\u0F2A-\u0F34\u0F36\u0F38\u0F3A-\u0F3D\u0F48\u0F6D-\u0F70\u0F85\u0F98\u0FBD-\u0FC5\u0FC7-\u0FFF\u104A-\u104F\u109E\u109F\u10C6\u10C8-\u10CC\u10CE\u10CF\u10FB\u1249\u124E\u124F\u1257\u1259\u125E\u125F\u1289\u128E\u128F\u12B1\u12B6\u12B7\u12BF\u12C1\u12C6\u12C7\u12D7\u1311\u1316\u1317\u135B\u135C\u1360-\u137F\u1390-\u139F\u13F6\u13F7\u13FE-\u1400\u166D\u166E\u1680\u169B-\u169F\u16EB-\u16ED\u16F9-\u16FF\u170D\u1715-\u171F\u1735-\u173F\u1754-\u175F\u176D\u1771\u1774-\u177F\u17D4-\u17D6\u17D8-\u17DB\u17DE\u17DF\u17EA-\u180A\u180E\u180F\u181A-\u181F\u1879-\u187F\u18AB-\u18AF\u18F6-\u18FF\u191F\u192C-\u192F\u193C-\u1945\u196E\u196F\u1975-\u197F\u19AC-\u19AF\u19CA-\u19CF\u19DA-\u19FF\u1A1C-\u1A1F\u1A5F\u1A7D\u1A7E\u1A8A-\u1A8F\u1A9A-\u1AA6\u1AA8-\u1AAF\u1AC1-\u1AFF\u1B4C-\u1B4F\u1B5A-\u1B6A\u1B74-\u1B7F\u1BF4-\u1BFF\u1C38-\u1C3F\u1C4A-\u1C4C\u1C7E\u1C7F\u1C89-\u1C8F\u1CBB\u1CBC\u1CC0-\u1CCF\u1CD3\u1CFB-\u1CFF\u1DFA\u1F16\u1F17\u1F1E\u1F1F\u1F46\u1F47\u1F4E\u1F4F\u1F58\u1F5A\u1F5C\u1F5E\u1F7E\u1F7F\u1FB5\u1FBD\u1FBF-\u1FC1\u1FC5\u1FCD-\u1FCF\u1FD4\u1FD5\u1FDC-\u1FDF\u1FED-\u1FF1\u1FF5\u1FFD-\u203E\u2041-\u2053\u2055-\u2070\u2072-\u207E\u2080-\u208F\u209D-\u20CF\u20F1-\u2101\u2103-\u2106\u2108\u2109\u2114\u2116-\u2118\u211E-\u2123\u2125\u2127\u2129\u212E\u213A\u213B\u2140-\u2144\u214A-\u214D\u214F-\u215F\u2189-\u24B5\u24EA-\u2BFF\u2C2F\u2C5F\u2CE5-\u2CEA\u2CF4-\u2CFF\u2D26\u2D28-\u2D2C\u2D2E\u2D2F\u2D68-\u2D6E\u2D70-\u2D7E\u2D97-\u2D9F\u2DA7\u2DAF\u2DB7\u2DBF\u2DC7\u2DCF\u2DD7\u2DDF\u2E00-\u2E2E\u2E30-\u3004\u3008-\u3020\u3030\u3036\u3037\u303D-\u3040\u3097\u3098\u309B\u309C\u30A0\u30FB\u3100-\u3104\u3130\u318F-\u319F\u31C0-\u31EF\u3200-\u33FF\u4DC0-\u4DFF\u9FFD-\u9FFF\uA48D-\uA4CF\uA4FE\uA4FF\uA60D-\uA60F\uA62C-\uA63F\uA673\uA67E\uA6F2-\uA716\uA720\uA721\uA789\uA78A\uA7C0\uA7C1\uA7CB-\uA7F4\uA828-\uA82B\uA82D-\uA83F\uA874-\uA87F\uA8C6-\uA8CF\uA8DA-\uA8DF\uA8F8-\uA8FA\uA8FC\uA92E\uA92F\uA954-\uA95F\uA97D-\uA97F\uA9C1-\uA9CE\uA9DA-\uA9DF\uA9FF\uAA37-\uAA3F\uAA4E\uAA4F\uAA5A-\uAA5F\uAA77-\uAA79\uAAC3-\uAADA\uAADE\uAADF\uAAF0\uAAF1\uAAF7-\uAB00\uAB07\uAB08\uAB0F\uAB10\uAB17-\uAB1F\uAB27\uAB2F\uAB5B\uAB6A-\uAB6F\uABEB\uABEE\uABEF\uABFA-\uABFF\uD7A4-\uD7AF\uD7C7-\uD7CA\uD7FC-\uD7FF\uE000-\uF8FF\uFA6E\uFA6F\uFADA-\uFAFF\uFB07-\uFB12\uFB18-\uFB1C\uFB29\uFB37\uFB3D\uFB3F\uFB42\uFB45\uFBB2-\uFBD2\uFD3E-\uFD4F\uFD90\uFD91\uFDC8-\uFDEF\uFDFC-\uFDFF\uFE10-\uFE1F\uFE30-\uFE32\uFE35-\uFE4C\uFE50-\uFE6F\uFE75\uFEFD-\uFF0F\uFF1A-\uFF20\uFF3B-\uFF3E\uFF40\uFF5B-\uFF65\uFFBF-\uFFC1\uFFC8\uFFC9\uFFD0\uFFD1\uFFD8\uFFD9\uFFDD-\uFFFF]|\uD800[\uDC0C\uDC27\uDC3B\uDC3E\uDC4E\uDC4F\uDC5E-\uDC7F\uDCFB-\uDD3F\uDD75-\uDDFC\uDDFE-\uDE7F\uDE9D-\uDE9F\uDED1-\uDEDF\uDEE1-\uDEFF\uDF20-\uDF2C\uDF4B-\uDF4F\uDF7B-\uDF7F\uDF9E\uDF9F\uDFC4-\uDFC7\uDFD0\uDFD6-\uDFFF]|\uD801[\uDC9E\uDC9F\uDCAA-\uDCAF\uDCD4-\uDCD7\uDCFC-\uDCFF\uDD28-\uDD2F\uDD64-\uDDFF\uDF37-\uDF3F\uDF56-\uDF5F\uDF68-\uDFFF]|\uD802[\uDC06\uDC07\uDC09\uDC36\uDC39-\uDC3B\uDC3D\uDC3E\uDC56-\uDC5F\uDC77-\uDC7F\uDC9F-\uDCDF\uDCF3\uDCF6-\uDCFF\uDD16-\uDD1F\uDD3A-\uDD7F\uDDB8-\uDDBD\uDDC0-\uDDFF\uDE04\uDE07-\uDE0B\uDE14\uDE18\uDE36\uDE37\uDE3B-\uDE3E\uDE40-\uDE5F\uDE7D-\uDE7F\uDE9D-\uDEBF\uDEC8\uDEE7-\uDEFF\uDF36-\uDF3F\uDF56-\uDF5F\uDF73-\uDF7F\uDF92-\uDFFF]|\uD803[\uDC49-\uDC7F\uDCB3-\uDCBF\uDCF3-\uDCFF\uDD28-\uDD2F\uDD3A-\uDE7F\uDEAA\uDEAD-\uDEAF\uDEB2-\uDEFF\uDF1D-\uDF26\uDF28-\uDF2F\uDF51-\uDFAF\uDFC5-\uDFDF\uDFF7-\uDFFF]|\uD804[\uDC47-\uDC65\uDC70-\uDC7E\uDCBB-\uDCCF\uDCE9-\uDCEF\uDCFA-\uDCFF\uDD35\uDD40-\uDD43\uDD48-\uDD4F\uDD74\uDD75\uDD77-\uDD7F\uDDC5-\uDDC8\uDDCD\uDDDB\uDDDD-\uDDFF\uDE12\uDE38-\uDE3D\uDE3F-\uDE7F\uDE87\uDE89\uDE8E\uDE9E\uDEA9-\uDEAF\uDEEB-\uDEEF\uDEFA-\uDEFF\uDF04\uDF0D\uDF0E\uDF11\uDF12\uDF29\uDF31\uDF34\uDF3A\uDF45\uDF46\uDF49\uDF4A\uDF4E\uDF4F\uDF51-\uDF56\uDF58-\uDF5C\uDF64\uDF65\uDF6D-\uDF6F\uDF75-\uDFFF]|\uD805[\uDC4B-\uDC4F\uDC5A-\uDC5D\uDC62-\uDC7F\uDCC6\uDCC8-\uDCCF\uDCDA-\uDD7F\uDDB6\uDDB7\uDDC1-\uDDD7\uDDDE-\uDDFF\uDE41-\uDE43\uDE45-\uDE4F\uDE5A-\uDE7F\uDEB9-\uDEBF\uDECA-\uDEFF\uDF1B\uDF1C\uDF2C-\uDF2F\uDF3A-\uDFFF]|\uD806[\uDC3B-\uDC9F\uDCEA-\uDCFE\uDD07\uDD08\uDD0A\uDD0B\uDD14\uDD17\uDD36\uDD39\uDD3A\uDD44-\uDD4F\uDD5A-\uDD9F\uDDA8\uDDA9\uDDD8\uDDD9\uDDE2\uDDE5-\uDDFF\uDE3F-\uDE46\uDE48-\uDE4F\uDE9A-\uDE9C\uDE9E-\uDEBF\uDEF9-\uDFFF]|\uD807[\uDC09\uDC37\uDC41-\uDC4F\uDC5A-\uDC71\uDC90\uDC91\uDCA8\uDCB7-\uDCFF\uDD07\uDD0A\uDD37-\uDD39\uDD3B\uDD3E\uDD48-\uDD4F\uDD5A-\uDD5F\uDD66\uDD69\uDD8F\uDD92\uDD99-\uDD9F\uDDAA-\uDEDF\uDEF7-\uDFAF\uDFB1-\uDFFF]|\uD808[\uDF9A-\uDFFF]|\uD809[\uDC6F-\uDC7F\uDD44-\uDFFF]|[\uD80A\uD80B\uD80E-\uD810\uD812-\uD819\uD824-\uD82B\uD82D\uD82E\uD830-\uD833\uD837\uD839\uD83D\uD83F\uD87B-\uD87D\uD87F\uD885-\uDB3F\uDB41-\uDBFF][\uDC00-\uDFFF]|\uD80D[\uDC2F-\uDFFF]|\uD811[\uDE47-\uDFFF]|\uD81A[\uDE39-\uDE3F\uDE5F\uDE6A-\uDECF\uDEEE\uDEEF\uDEF5-\uDEFF\uDF37-\uDF3F\uDF44-\uDF4F\uDF5A-\uDF62\uDF78-\uDF7C\uDF90-\uDFFF]|\uD81B[\uDC00-\uDE3F\uDE80-\uDEFF\uDF4B-\uDF4E\uDF88-\uDF8E\uDFA0-\uDFDF\uDFE2\uDFE5-\uDFEF\uDFF2-\uDFFF]|\uD821[\uDFF8-\uDFFF]|\uD823[\uDCD6-\uDCFF\uDD09-\uDFFF]|\uD82C[\uDD1F-\uDD4F\uDD53-\uDD63\uDD68-\uDD6F\uDEFC-\uDFFF]|\uD82F[\uDC6B-\uDC6F\uDC7D-\uDC7F\uDC89-\uDC8F\uDC9A-\uDC9C\uDC9F-\uDFFF]|\uD834[\uDC00-\uDD64\uDD6A-\uDD6C\uDD73-\uDD7A\uDD83\uDD84\uDD8C-\uDDA9\uDDAE-\uDE41\uDE45-\uDFFF]|\uD835[\uDC55\uDC9D\uDCA0\uDCA1\uDCA3\uDCA4\uDCA7\uDCA8\uDCAD\uDCBA\uDCBC\uDCC4\uDD06\uDD0B\uDD0C\uDD15\uDD1D\uDD3A\uDD3F\uDD45\uDD47-\uDD49\uDD51\uDEA6\uDEA7\uDEC1\uDEDB\uDEFB\uDF15\uDF35\uDF4F\uDF6F\uDF89\uDFA9\uDFC3\uDFCC\uDFCD]|\uD836[\uDC00-\uDDFF\uDE37-\uDE3A\uDE6D-\uDE74\uDE76-\uDE83\uDE85-\uDE9A\uDEA0\uDEB0-\uDFFF]|\uD838[\uDC07\uDC19\uDC1A\uDC22\uDC25\uDC2B-\uDCFF\uDD2D-\uDD2F\uDD3E\uDD3F\uDD4A-\uDD4D\uDD4F-\uDEBF\uDEFA-\uDFFF]|\uD83A[\uDCC5-\uDCCF\uDCD7-\uDCFF\uDD4C-\uDD4F\uDD5A-\uDFFF]|\uD83B[\uDC00-\uDDFF\uDE04\uDE20\uDE23\uDE25\uDE26\uDE28\uDE33\uDE38\uDE3A\uDE3C-\uDE41\uDE43-\uDE46\uDE48\uDE4A\uDE4C\uDE50\uDE53\uDE55\uDE56\uDE58\uDE5A\uDE5C\uDE5E\uDE60\uDE63\uDE65\uDE66\uDE6B\uDE73\uDE78\uDE7D\uDE7F\uDE8A\uDE9C-\uDEA0\uDEA4\uDEAA\uDEBC-\uDFFF]|\uD83C[\uDC00-\uDD2F\uDD4A-\uDD4F\uDD6A-\uDD6F\uDD8A-\uDFFF]|\uD83E[\uDC00-\uDFEF\uDFFA-\uDFFF]|\uD869[\uDEDE-\uDEFF]|\uD86D[\uDF35-\uDF3F]|\uD86E[\uDC1E\uDC1F]|\uD873[\uDEA2-\uDEAF]|\uD87A[\uDFE1-\uDFFF]|\uD87E[\uDE1E-\uDFFF]|\uD884[\uDF4B-\uDFFF]|\uDB40[\uDC00-\uDCFF\uDDF0-\uDFFF]/g;
|
|
22
|
+
export function githubSlug(value) {
|
|
23
|
+
return value.toLowerCase().replace(REMOVE, "").replace(/ /g, "-");
|
|
24
|
+
}
|
|
25
|
+
/** One per page: dedupes the way `new GithubSlugger().slug()` does. */
|
|
26
|
+
export class GithubSlugger {
|
|
27
|
+
occurrences = new Map();
|
|
28
|
+
slug(value) {
|
|
29
|
+
const original = githubSlug(value);
|
|
30
|
+
let result = original;
|
|
31
|
+
while (this.occurrences.has(result)) {
|
|
32
|
+
const n = (this.occurrences.get(original) ?? 0) + 1;
|
|
33
|
+
this.occurrences.set(original, n);
|
|
34
|
+
result = `${original}-${n}`;
|
|
35
|
+
}
|
|
36
|
+
this.occurrences.set(result, 0);
|
|
37
|
+
return result;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Marks a heading whose id is its own (`<AnchorHeading>`, and the headings
|
|
42
|
+
* resolvers write for component data, which are AnchorHeadings upstream):
|
|
43
|
+
* not re-slugged and outside the page's dedupe. A STRING key (a Symbol does
|
|
44
|
+
* not survive structuredClone), removed by the final pass so it is never
|
|
45
|
+
* serialized as an attribute.
|
|
46
|
+
*/
|
|
47
|
+
export const FIXED_SLUG = "__fixedSlug";
|
|
48
|
+
/** The heading's rendered text: text, inline code and link TEXT (never the URL); raw HTML tags add nothing. */
|
|
49
|
+
function plainText(nodes) {
|
|
50
|
+
if (!Array.isArray(nodes))
|
|
51
|
+
return "";
|
|
52
|
+
let out = "";
|
|
53
|
+
for (const n of nodes) {
|
|
54
|
+
if (!n || typeof n !== "object")
|
|
55
|
+
continue;
|
|
56
|
+
const x = n;
|
|
57
|
+
// Raw HTML tags, and inline COMPONENTS (a `<Badge>` in a heading), add
|
|
58
|
+
// no text upstream: `## Durable Objects <Badge text="Beta" />` is
|
|
59
|
+
// `durable-objects`.
|
|
60
|
+
if (x.type === "html-inline" || x.type === "badge" || x.type === "link-button")
|
|
61
|
+
continue;
|
|
62
|
+
if (typeof x.text === "string")
|
|
63
|
+
out += x.text;
|
|
64
|
+
out += plainText(x.children);
|
|
65
|
+
}
|
|
66
|
+
return out;
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Give every heading of a PAGE its Starlight id, in document order, deduped
|
|
70
|
+
* on the page. Headings from `<AnchorHeading>` keep the id they were given.
|
|
71
|
+
*/
|
|
72
|
+
export function assignHeadingSlugs(tree) {
|
|
73
|
+
const slugger = new GithubSlugger();
|
|
74
|
+
const walk = (nodes) => {
|
|
75
|
+
if (!Array.isArray(nodes))
|
|
76
|
+
return;
|
|
77
|
+
for (const n of nodes) {
|
|
78
|
+
if (!n || typeof n !== "object")
|
|
79
|
+
continue;
|
|
80
|
+
const node = n;
|
|
81
|
+
if (node.type === "heading" && node.props) {
|
|
82
|
+
if (node.props[FIXED_SLUG])
|
|
83
|
+
delete node.props[FIXED_SLUG];
|
|
84
|
+
else {
|
|
85
|
+
const custom = takeCustomId(node);
|
|
86
|
+
const text = (plainText(node.inline) || plainText(node.children) || String(node.props.text ?? "")).trimEnd();
|
|
87
|
+
const slug = slugger.slug(custom ?? text.trim());
|
|
88
|
+
if (slug)
|
|
89
|
+
node.props.slug = slug;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
walk(node.children);
|
|
93
|
+
}
|
|
94
|
+
};
|
|
95
|
+
walk(tree);
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* `### Title {/* custom-id *\/}` — Cloudflare's custom heading id (16 uses,
|
|
99
|
+
* there so links can target them). The comment leaves the visible text;
|
|
100
|
+
* the id still goes through the slugger (deduped), as upstream.
|
|
101
|
+
*/
|
|
102
|
+
function takeCustomId(node) {
|
|
103
|
+
const inline = Array.isArray(node.inline) ? node.inline : [];
|
|
104
|
+
const last = [...inline].reverse().find((i) => i.type === "text" && typeof i.text === "string" && i.text.trim());
|
|
105
|
+
const m = last?.text ? /\s*\{\s*\/\*\s*([^*]+?)\s*\*\/\s*\}\s*$/.exec(last.text) : null;
|
|
106
|
+
if (!last || !m)
|
|
107
|
+
return undefined;
|
|
108
|
+
last.text = last.text.slice(0, m.index);
|
|
109
|
+
if (typeof node.props?.text === "string")
|
|
110
|
+
node.props.text = node.props.text.replace(/\s*\{\s*\/\*[\s\S]*?\*\/\s*\}\s*$/, "");
|
|
111
|
+
return m[1];
|
|
112
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export declare function parseJsLiteral(src: string): unknown;
|
|
2
|
+
export interface JsxTag {
|
|
3
|
+
start: number;
|
|
4
|
+
end: number;
|
|
5
|
+
/** Attribute name → raw value: a string's contents, or `{…}` with its braces. `true` when bare. */
|
|
6
|
+
attrs: Record<string, string | true>;
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* Every self-closing `<Name … />` in `source`, attributes read with quotes
|
|
10
|
+
* and nested braces respected — `json={{ a: "x/>" }}` does not end the tag.
|
|
11
|
+
*/
|
|
12
|
+
export declare function findSelfClosingTags(source: string, name: string): JsxTag[];
|
|
@@ -0,0 +1,249 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* JS LITERALS from MDX props (`json={{ feedback: 1 }}`), read — never run.
|
|
3
|
+
*
|
|
4
|
+
* Objects (identifier, string or number keys), arrays, strings (`'`, `"`, and
|
|
5
|
+
* backtick templates with no `${}`), numbers, `true`/`false`/`null`, trailing
|
|
6
|
+
* commas and comments. Anything else — a variable, a call, a spread, an
|
|
7
|
+
* interpolation — REFUSES (returns `undefined`); the caller leaves the source
|
|
8
|
+
* as written and reports it. A plausible wrong value is worse than none.
|
|
9
|
+
*/
|
|
10
|
+
class Refuse extends Error {
|
|
11
|
+
}
|
|
12
|
+
export function parseJsLiteral(src) {
|
|
13
|
+
let i = 0;
|
|
14
|
+
const s = src;
|
|
15
|
+
const ws = () => {
|
|
16
|
+
for (;;) {
|
|
17
|
+
while (i < s.length && /\s/.test(s[i]))
|
|
18
|
+
i++;
|
|
19
|
+
if (s.startsWith("//", i)) {
|
|
20
|
+
while (i < s.length && s[i] !== "\n")
|
|
21
|
+
i++;
|
|
22
|
+
}
|
|
23
|
+
else if (s.startsWith("/*", i)) {
|
|
24
|
+
const end = s.indexOf("*/", i + 2);
|
|
25
|
+
if (end < 0)
|
|
26
|
+
throw new Refuse();
|
|
27
|
+
i = end + 2;
|
|
28
|
+
}
|
|
29
|
+
else
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
};
|
|
33
|
+
const str = () => {
|
|
34
|
+
const q = s[i++];
|
|
35
|
+
let out = "";
|
|
36
|
+
while (i < s.length && s[i] !== q) {
|
|
37
|
+
if (q === "`" && s.startsWith("${", i))
|
|
38
|
+
throw new Refuse();
|
|
39
|
+
if (s[i] === "\\") {
|
|
40
|
+
const c = s[++i];
|
|
41
|
+
const map = { n: "\n", t: "\t", r: "\r", b: "\b", f: "\f", v: "\v" };
|
|
42
|
+
// `\x41`, octal `\101`: read as plain letters they are a plausible
|
|
43
|
+
// WRONG value; refuse. `\0` alone (not before a digit) is NUL.
|
|
44
|
+
if (c === "x" || /[1-7]/.test(c) || (c === "0" && /[0-9]/.test(s[i + 1] ?? "")))
|
|
45
|
+
throw new Refuse();
|
|
46
|
+
if (c === "0") {
|
|
47
|
+
out += "\0";
|
|
48
|
+
i++;
|
|
49
|
+
continue;
|
|
50
|
+
}
|
|
51
|
+
if (c === "u") {
|
|
52
|
+
const hex = s.slice(i + 1, i + 5);
|
|
53
|
+
if (!/^[0-9a-fA-F]{4}$/.test(hex))
|
|
54
|
+
throw new Refuse();
|
|
55
|
+
out += String.fromCharCode(parseInt(hex, 16));
|
|
56
|
+
i += 5;
|
|
57
|
+
continue;
|
|
58
|
+
}
|
|
59
|
+
if (c === "\n") {
|
|
60
|
+
i++;
|
|
61
|
+
continue;
|
|
62
|
+
}
|
|
63
|
+
out += map[c] ?? c;
|
|
64
|
+
i++;
|
|
65
|
+
continue;
|
|
66
|
+
}
|
|
67
|
+
out += s[i++];
|
|
68
|
+
}
|
|
69
|
+
if (s[i] !== q)
|
|
70
|
+
throw new Refuse();
|
|
71
|
+
i++;
|
|
72
|
+
return out;
|
|
73
|
+
};
|
|
74
|
+
let depth = 0;
|
|
75
|
+
const value = () => {
|
|
76
|
+
ws();
|
|
77
|
+
// A cap, not the call stack: 20,000 nested `[` threw a RangeError that
|
|
78
|
+
// escaped the Refuse catch and aborted the import.
|
|
79
|
+
if (++depth > 200)
|
|
80
|
+
throw new Refuse();
|
|
81
|
+
try {
|
|
82
|
+
return inner();
|
|
83
|
+
}
|
|
84
|
+
finally {
|
|
85
|
+
depth--;
|
|
86
|
+
}
|
|
87
|
+
};
|
|
88
|
+
const inner = () => {
|
|
89
|
+
const c = s[i];
|
|
90
|
+
if (c === "{") {
|
|
91
|
+
i++;
|
|
92
|
+
const obj = {};
|
|
93
|
+
for (;;) {
|
|
94
|
+
ws();
|
|
95
|
+
if (s[i] === "}") {
|
|
96
|
+
i++;
|
|
97
|
+
return obj;
|
|
98
|
+
}
|
|
99
|
+
let key;
|
|
100
|
+
if (s[i] === '"' || s[i] === "'")
|
|
101
|
+
key = str();
|
|
102
|
+
else {
|
|
103
|
+
const m = /^[A-Za-z_$][\w$]*|^\d+/.exec(s.slice(i));
|
|
104
|
+
if (!m)
|
|
105
|
+
throw new Refuse();
|
|
106
|
+
key = m[0];
|
|
107
|
+
i += key.length;
|
|
108
|
+
}
|
|
109
|
+
ws();
|
|
110
|
+
if (s[i] !== ":")
|
|
111
|
+
throw new Refuse(); // shorthand `{ a }` is a variable
|
|
112
|
+
i++;
|
|
113
|
+
obj[key] = value();
|
|
114
|
+
ws();
|
|
115
|
+
if (s[i] === ",")
|
|
116
|
+
i++;
|
|
117
|
+
else if (s[i] !== "}")
|
|
118
|
+
throw new Refuse();
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
if (c === "[") {
|
|
122
|
+
i++;
|
|
123
|
+
const arr = [];
|
|
124
|
+
for (;;) {
|
|
125
|
+
ws();
|
|
126
|
+
if (s[i] === "]") {
|
|
127
|
+
i++;
|
|
128
|
+
return arr;
|
|
129
|
+
}
|
|
130
|
+
arr.push(value());
|
|
131
|
+
ws();
|
|
132
|
+
if (s[i] === ",")
|
|
133
|
+
i++;
|
|
134
|
+
else if (s[i] !== "]")
|
|
135
|
+
throw new Refuse();
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
if (c === '"' || c === "'" || c === "`")
|
|
139
|
+
return str();
|
|
140
|
+
const m = /^-?(?:\d+\.?\d*|\.\d+)(?:[eE][+-]?\d+)?/.exec(s.slice(i));
|
|
141
|
+
if (m) {
|
|
142
|
+
i += m[0].length;
|
|
143
|
+
return Number(m[0]);
|
|
144
|
+
}
|
|
145
|
+
for (const [word, v] of [["true", true], ["false", false], ["null", null]]) {
|
|
146
|
+
if (s.startsWith(word, i) && !/[\w$]/.test(s[i + word.length] ?? "")) {
|
|
147
|
+
i += word.length;
|
|
148
|
+
return v;
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
throw new Refuse();
|
|
152
|
+
};
|
|
153
|
+
try {
|
|
154
|
+
const v = value();
|
|
155
|
+
ws();
|
|
156
|
+
return i === s.length ? v : undefined;
|
|
157
|
+
}
|
|
158
|
+
catch (err) {
|
|
159
|
+
if (err instanceof Refuse)
|
|
160
|
+
return undefined;
|
|
161
|
+
throw err;
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
/**
|
|
165
|
+
* Every self-closing `<Name … />` in `source`, attributes read with quotes
|
|
166
|
+
* and nested braces respected — `json={{ a: "x/>" }}` does not end the tag.
|
|
167
|
+
*/
|
|
168
|
+
export function findSelfClosingTags(source, name) {
|
|
169
|
+
const out = [];
|
|
170
|
+
const re = new RegExp(`<${name}(?![\\w-])`, "g");
|
|
171
|
+
for (let m = re.exec(source); m; m = re.exec(source)) {
|
|
172
|
+
let i = m.index + m[0].length;
|
|
173
|
+
const attrs = {};
|
|
174
|
+
let ok = false;
|
|
175
|
+
for (;;) {
|
|
176
|
+
while (i < source.length && /\s/.test(source[i]))
|
|
177
|
+
i++;
|
|
178
|
+
if (source.startsWith("/>", i)) {
|
|
179
|
+
ok = true;
|
|
180
|
+
i += 2;
|
|
181
|
+
break;
|
|
182
|
+
}
|
|
183
|
+
const an = /^[A-Za-z_][\w:-]*/.exec(source.slice(i));
|
|
184
|
+
if (!an)
|
|
185
|
+
break;
|
|
186
|
+
i += an[0].length;
|
|
187
|
+
while (/[ \t]/.test(source[i] ?? ""))
|
|
188
|
+
i++;
|
|
189
|
+
if (source[i] !== "=") {
|
|
190
|
+
attrs[an[0]] = true;
|
|
191
|
+
continue;
|
|
192
|
+
}
|
|
193
|
+
i++;
|
|
194
|
+
while (/[ \t]/.test(source[i] ?? ""))
|
|
195
|
+
i++;
|
|
196
|
+
const q = source[i];
|
|
197
|
+
if (q === '"' || q === "'") {
|
|
198
|
+
const end = source.indexOf(q, i + 1);
|
|
199
|
+
if (end < 0)
|
|
200
|
+
break;
|
|
201
|
+
attrs[an[0]] = source.slice(i + 1, end);
|
|
202
|
+
i = end + 1;
|
|
203
|
+
}
|
|
204
|
+
else if (q === "{") {
|
|
205
|
+
let depth = 0;
|
|
206
|
+
let j = i;
|
|
207
|
+
let quote = null;
|
|
208
|
+
for (; j < source.length; j++) {
|
|
209
|
+
const ch = source[j];
|
|
210
|
+
if (quote) {
|
|
211
|
+
if (ch === "\\")
|
|
212
|
+
j++;
|
|
213
|
+
else if (ch === quote)
|
|
214
|
+
quote = null;
|
|
215
|
+
}
|
|
216
|
+
else if (source.startsWith("//", j)) {
|
|
217
|
+
// A comment's apostrophe ("don't") is not a string opener.
|
|
218
|
+
const nl = source.indexOf("\n", j);
|
|
219
|
+
j = nl < 0 ? source.length : nl;
|
|
220
|
+
}
|
|
221
|
+
else if (source.startsWith("/*", j)) {
|
|
222
|
+
const end = source.indexOf("*/", j + 2);
|
|
223
|
+
j = end < 0 ? source.length : end + 1;
|
|
224
|
+
}
|
|
225
|
+
else if (ch === '"' || ch === "'" || ch === "`")
|
|
226
|
+
quote = ch;
|
|
227
|
+
else if (ch === "{")
|
|
228
|
+
depth++;
|
|
229
|
+
else if (ch === "}" && --depth === 0)
|
|
230
|
+
break;
|
|
231
|
+
}
|
|
232
|
+
if (j >= source.length) {
|
|
233
|
+
// Unbalanced to the end of the text: every later tag would scan to
|
|
234
|
+
// the end too (quadratic). Stop; what is left is reported as leaked.
|
|
235
|
+
return out;
|
|
236
|
+
}
|
|
237
|
+
attrs[an[0]] = source.slice(i, j + 1);
|
|
238
|
+
i = j + 1;
|
|
239
|
+
}
|
|
240
|
+
else
|
|
241
|
+
break;
|
|
242
|
+
}
|
|
243
|
+
if (ok) {
|
|
244
|
+
out.push({ start: m.index, end: i, attrs });
|
|
245
|
+
re.lastIndex = i;
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
return out;
|
|
249
|
+
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* JSX expressions over partial props → Jinja (template pages,
|
|
3
|
+
* plans/page-templates.md Phase 1).
|
|
4
|
+
*
|
|
5
|
+
* Cloudflare partials read their parameters as `props.x` inside MDX
|
|
6
|
+
* expressions: `{props.x}`, `{props.a === "b" ? "c" : props.d}`,
|
|
7
|
+
* `{props.a === "b" && (<p>…</p>)}`, `{props.x ?? "default"}`, and
|
|
8
|
+
* `href={`/x/${props.y}/`}` in attributes. This module translates the subset
|
|
9
|
+
* that corpus actually uses into Jinja:
|
|
10
|
+
*
|
|
11
|
+
* {props.x} → {{ x }}
|
|
12
|
+
* {props.x ?? "d"} → {{ x | default("d") }}
|
|
13
|
+
* {A ? B : C} (text position) → {% if A %}B{% else %}C{% endif %}
|
|
14
|
+
* {A && B} (text position) → {% if A %}B{% endif %}
|
|
15
|
+
* attr={EXPR} → attr="{{ EXPR }}" (or the literal text)
|
|
16
|
+
*
|
|
17
|
+
* The expression grammar is deliberately small: `props.name`, string /
|
|
18
|
+
* number / boolean literals, template literals, `=== == !== != && || ! ??`,
|
|
19
|
+
* `?:`, parentheses, and `.includes(…)`. Anything else — a function call,
|
|
20
|
+
* an arrow function, an object literal, another identifier — is REFUSED:
|
|
21
|
+
* the original `{…}` stays byte-for-byte and is counted, so a conversion
|
|
22
|
+
* never produces a plausible wrong template (the format-vitepress rule).
|
|
23
|
+
*/
|
|
24
|
+
import type { DirectiveTable } from "./template-tokens.js";
|
|
25
|
+
export interface JsxToJinjaStats {
|
|
26
|
+
/** `{…}` expressions over props that became Jinja. */
|
|
27
|
+
converted: number;
|
|
28
|
+
/** `{…}` expressions over props left as written (outside the subset). */
|
|
29
|
+
refused: number;
|
|
30
|
+
/** A few refused sources, for the report. */
|
|
31
|
+
refusedSamples: string[];
|
|
32
|
+
/** Every `props.name` read, for partials that declare no `params:`. */
|
|
33
|
+
names: Set<string>;
|
|
34
|
+
/** `<Markdown text={…} />` wrappers replaced by their text. */
|
|
35
|
+
markdownInlined: number;
|
|
36
|
+
/** Where emitted directives are registered (see template-tokens.ts). */
|
|
37
|
+
table?: DirectiveTable;
|
|
38
|
+
}
|
|
39
|
+
export declare function newStats(): JsxToJinjaStats;
|
|
40
|
+
/**
|
|
41
|
+
* Convert a partial's MDX body. Fenced code blocks are copied verbatim;
|
|
42
|
+
* everything else goes through `convertText`.
|
|
43
|
+
*/
|
|
44
|
+
export declare function jsxPropsToJinja(body: string, stats: JsxToJinjaStats): string;
|
|
45
|
+
/**
|
|
46
|
+
* A JS literal from `params={{ … }}` as a Jinja expression, or undefined when
|
|
47
|
+
* it is outside the subset (the caller then inlines the partial as before).
|
|
48
|
+
*/
|
|
49
|
+
export declare function jsValueToJinja(src: string, names?: Set<string>): string | undefined;
|
|
50
|
+
/** Split `a, b, "c, d"` at top-level commas (strings, brackets and braces respected). */
|
|
51
|
+
export declare function splitTopLevel(src: string): string[];
|
|
52
|
+
/**
|
|
53
|
+
* Render an Astro component's TEMPLATE for known props — read, never run:
|
|
54
|
+
* `{cond && (<p>…</p>)}`, `{a === "x" ? (A) : (B)}`, `attr={cond ? "a" : "b"}`,
|
|
55
|
+
* `{"text"}`, fragments, comments — the JS subset `parseExpr` reads, with
|
|
56
|
+
* the component's own prop names (`cli`, not `props.cli`). `inline` may
|
|
57
|
+
* replace a self-closing sub-component with its own rendering. Undefined
|
|
58
|
+
* when anything is outside the subset (the caller leaves the tag reported).
|
|
59
|
+
*/
|
|
60
|
+
export declare function renderComponentTemplate(template: string, props: Record<string, unknown>, inline?: (tag: string, attrs: Record<string, string | true>) => string | undefined): string | undefined;
|
|
61
|
+
/** `const { a, b = false } = Astro.props;` → the prop names and their defaults (literals only). */
|
|
62
|
+
export declare function componentPropDefaults(frontmatter: string): Record<string, unknown> | undefined;
|