@readme/markdown 15.7.2 → 15.8.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.
@@ -0,0 +1,31 @@
1
+ import type { Program } from 'estree';
2
+ import type { Root } from 'hast';
3
+ import type { Plugin, Transformer } from 'unified';
4
+ /**
5
+ * Tags removed from user-provided content because rendering them executes in the
6
+ * page. Add an entry to cover another vector; nothing else needs to change.
7
+ *
8
+ * Deliberately narrow for now — `<script>` is the only vector this plugin claims
9
+ * to close. Broader parity with the `md` sanitization schema (`javascript:` URLs,
10
+ * `on*` handlers, `<iframe>`, `<object>`, …) is tracked in readmeio/markdown#1526.
11
+ */
12
+ export declare const STRIPPED_TAG_NAMES: Set<string>;
13
+ /**
14
+ * Removes matching elements and their subtrees when `sanitize` is enabled.
15
+ *
16
+ * Explicit `HTMLBlock`s are an intentional exception, matching legacy: their
17
+ * scripts live in a string prop rather than as elements, so they never appear
18
+ * in SSR HTML, and they only execute client-side when the author opts in via
19
+ * `runScripts`.
20
+ */
21
+ export declare const rehypeStripTags: () => Transformer<Root, Root>;
22
+ /**
23
+ * JSX inside expressions and exports stays in ESTree, outside the HAST pass.
24
+ * Strip it before evaluation in mdxish or after JSX compilation in MDX.
25
+ *
26
+ * Elements are spliced out of JSX children and replaced with `null` anywhere
27
+ * else, which React renders as nothing.
28
+ */
29
+ export declare const stripJsxTags: (program: Program) => Program;
30
+ /** `stripJsxTags` as a recma plugin, for the `mdx` compile pipeline. */
31
+ export declare const recmaStripTags: Plugin<[], Program>;
@@ -11,6 +11,10 @@ declare module 'hast' {
11
11
  mdxishScope?: MdxishScope;
12
12
  }
13
13
  }
14
+ interface Options {
15
+ /** Drop `STRIPPED_TAG_NAMES` JSX from the exported code before it is evaluated. */
16
+ sanitize?: boolean;
17
+ }
14
18
  /**
15
19
  * Evaluate `export const/function` declarations introduced by mdxjsEsm nodes.
16
20
  *
@@ -23,5 +27,5 @@ declare module 'hast' {
23
27
  * Any evaluation error is consumed and logged. We don't throw because it's
24
28
  * against the spirit of this engine to be less permissive than MDX needs.
25
29
  */
26
- declare const evaluateExports: Plugin<[], Root>;
30
+ declare const evaluateExports: Plugin<[Options?], Root>;
27
31
  export default evaluateExports;
@@ -19045,7 +19045,7 @@ module.exports = function () {
19045
19045
 
19046
19046
  /***/ },
19047
19047
 
19048
- /***/ 5072
19048
+ /***/ 6829
19049
19049
  (module, __webpack_exports__, __webpack_require__) {
19050
19050
 
19051
19051
  "use strict";
@@ -114138,6 +114138,133 @@ const hardBreaks = () => tree => {
114138
114138
  };
114139
114139
  /* harmony default export */ const hard_breaks = (hardBreaks);
114140
114140
 
114141
+ ;// ./processor/plugin/strip-tags.ts
114142
+
114143
+ /**
114144
+ * Tags removed from user-provided content because rendering them executes in the
114145
+ * page. Add an entry to cover another vector; nothing else needs to change.
114146
+ *
114147
+ * Deliberately narrow for now — `<script>` is the only vector this plugin claims
114148
+ * to close. Broader parity with the `md` sanitization schema (`javascript:` URLs,
114149
+ * `on*` handlers, `<iframe>`, `<object>`, …) is tracked in readmeio/markdown#1526.
114150
+ */
114151
+ const STRIPPED_TAG_NAMES = new Set(['script']);
114152
+ /** JSX names starting with a capital are component references, not literal tags. */
114153
+ const LITERAL_TAG_NAME = /^[a-z]/;
114154
+ /**
114155
+ * Whether a node would render as one of the stripped literal DOM elements.
114156
+ * Browsers match tag names case-insensitively, so `<sCrIpT>` executes too.
114157
+ *
114158
+ * `<Script />` and friends are component references and are left alone.
114159
+ */
114160
+ const isStrippedTag = (node) => {
114161
+ if (node.type === 'element')
114162
+ return STRIPPED_TAG_NAMES.has(node.tagName.toLowerCase());
114163
+ if (node.type === 'mdxJsxFlowElement' || node.type === 'mdxJsxTextElement') {
114164
+ const name = node.name ?? '';
114165
+ return LITERAL_TAG_NAME.test(name) && STRIPPED_TAG_NAMES.has(name.toLowerCase());
114166
+ }
114167
+ return false;
114168
+ };
114169
+ /**
114170
+ * Removes matching elements and their subtrees when `sanitize` is enabled.
114171
+ *
114172
+ * Explicit `HTMLBlock`s are an intentional exception, matching legacy: their
114173
+ * scripts live in a string prop rather than as elements, so they never appear
114174
+ * in SSR HTML, and they only execute client-side when the author opts in via
114175
+ * `runScripts`.
114176
+ */
114177
+ const rehypeStripTags = () => {
114178
+ return (tree) => {
114179
+ visit(tree, isStrippedTag, (_node, index, parent) => {
114180
+ parent.children.splice(index, 1);
114181
+ return [(/* inlined export .SKIP */"skip"), index];
114182
+ });
114183
+ };
114184
+ };
114185
+ /** The automatic-runtime factories MDX compiles JSX into. */
114186
+ const JSX_FACTORIES = new Set(['_jsx', '_jsxs', '_jsxDEV']);
114187
+ /**
114188
+ * Matches raw JSX and compiled `_jsx('tag', …)` calls. MDX may rewrite literal
114189
+ * tags as `_components.<tag>` so a `components` prop can override them.
114190
+ */
114191
+ const isStrippedJsxElement = (node) => {
114192
+ if (node.type === 'JSXElement') {
114193
+ const { name } = node.openingElement;
114194
+ if (name.type === 'JSXIdentifier') {
114195
+ return LITERAL_TAG_NAME.test(name.name) && STRIPPED_TAG_NAMES.has(name.name.toLowerCase());
114196
+ }
114197
+ return (name.type === 'JSXMemberExpression' &&
114198
+ name.object.type === 'JSXIdentifier' &&
114199
+ name.object.name === '_components' &&
114200
+ STRIPPED_TAG_NAMES.has(name.property.name.toLowerCase()));
114201
+ }
114202
+ if (node.type === 'CallExpression' && node.callee.type === 'Identifier' && JSX_FACTORIES.has(node.callee.name)) {
114203
+ const [tag] = node.arguments;
114204
+ if (!tag)
114205
+ return false;
114206
+ if (tag.type === 'Literal')
114207
+ return typeof tag.value === 'string' && STRIPPED_TAG_NAMES.has(tag.value.toLowerCase());
114208
+ return (tag.type === 'MemberExpression' &&
114209
+ tag.object.type === 'Identifier' &&
114210
+ tag.object.name === '_components' &&
114211
+ tag.property.type === 'Identifier' &&
114212
+ STRIPPED_TAG_NAMES.has(tag.property.name.toLowerCase()));
114213
+ }
114214
+ return false;
114215
+ };
114216
+ const isEstreeNode = (value) => typeof value === 'object' && value !== null && typeof value.type === 'string';
114217
+ const nullLiteral = () => ({ type: 'Literal', value: null });
114218
+ /**
114219
+ * JSX inside expressions and exports stays in ESTree, outside the HAST pass.
114220
+ * Strip it before evaluation in mdxish or after JSX compilation in MDX.
114221
+ *
114222
+ * Elements are spliced out of JSX children and replaced with `null` anywhere
114223
+ * else, which React renders as nothing.
114224
+ */
114225
+ const stripJsxTags = (program) => {
114226
+ const walk = (node) => {
114227
+ const record = node;
114228
+ Object.keys(record).forEach(key => {
114229
+ if (key === 'loc' || key === 'range' || key === 'position')
114230
+ return;
114231
+ const value = record[key];
114232
+ if (Array.isArray(value)) {
114233
+ for (let i = value.length - 1; i >= 0; i -= 1) {
114234
+ const child = value[i];
114235
+ if (isEstreeNode(child)) {
114236
+ if (isStrippedJsxElement(child)) {
114237
+ if (key === 'children')
114238
+ value.splice(i, 1);
114239
+ else
114240
+ value[i] = nullLiteral();
114241
+ }
114242
+ else {
114243
+ walk(child);
114244
+ }
114245
+ }
114246
+ }
114247
+ return;
114248
+ }
114249
+ if (!isEstreeNode(value))
114250
+ return;
114251
+ if (isStrippedJsxElement(value)) {
114252
+ record[key] =
114253
+ node.type === 'JSXAttribute' ? { type: 'JSXExpressionContainer', expression: nullLiteral() } : nullLiteral();
114254
+ }
114255
+ else {
114256
+ walk(value);
114257
+ }
114258
+ });
114259
+ };
114260
+ walk(program);
114261
+ return program;
114262
+ };
114263
+ /** `stripJsxTags` as a recma plugin, for the `mdx` compile pipeline. */
114264
+ const recmaStripTags = () => tree => {
114265
+ stripJsxTags(tree);
114266
+ };
114267
+
114141
114268
  ;// ./node_modules/estree-util-value-to-estree/dist/estree-util-value-to-estree.js
114142
114269
  /**
114143
114270
  * Create an ESTree identifier node for a given name.
@@ -116875,6 +117002,7 @@ const tocHastToMdx = (toc, components, variables) => {
116875
117002
 
116876
117003
 
116877
117004
 
117005
+
116878
117006
  const sanitizeSchema = cjs_default()(defaultSchema, {
116879
117007
  attributes: {
116880
117008
  a: ['target'],
@@ -116883,7 +117011,10 @@ const sanitizeSchema = cjs_default()(defaultSchema, {
116883
117011
  href: ['doc', 'ref', 'blog', 'changelog', 'page'],
116884
117012
  },
116885
117013
  });
116886
- const compile_compile = (text, { components = {}, missingComponents, copyButtons, useTailwind, hardBreaks, ...opts } = {}) => {
117014
+ const compile_compile = (text, { components = {}, missingComponents, copyButtons, useTailwind, hardBreaks, sanitize = false,
117015
+ // Pulled out of `...opts` so a caller's plugins extend the pipeline instead of
117016
+ // replacing it — otherwise the spread below would drop `rehypeStripTags`.
117017
+ remarkPlugins: userRemarkPlugins, rehypePlugins: userRehypePlugins, recmaPlugins: userRecmaPlugins, ...opts } = {}) => {
116887
117018
  // Destructure at runtime to avoid circular dependency issues
116888
117019
  const { codeTabsTransformer, ...transforms } = defaultTransforms;
116889
117020
  const remarkPlugins = [
@@ -116901,7 +117032,12 @@ const compile_compile = (text, { components = {}, missingComponents, copyButtons
116901
117032
  if (useTailwind) {
116902
117033
  remarkPlugins.push([transform_tailwind, { components }]);
116903
117034
  }
116904
- const rehypePlugins = [...ast_processor_rehypePlugins, [rehypeToc, { components }]];
117035
+ remarkPlugins.push(...(userRemarkPlugins ?? []));
117036
+ const rehypePlugins = [
117037
+ ...ast_processor_rehypePlugins,
117038
+ [rehypeToc, { components }],
117039
+ ...(userRehypePlugins ?? []),
117040
+ ];
116905
117041
  if (opts.format === 'md') {
116906
117042
  /**
116907
117043
  * When running in `md` format we need to sanitize the content for security reasons.
@@ -116919,12 +117055,23 @@ const compile_compile = (text, { components = {}, missingComponents, copyButtons
116919
117055
  ]);
116920
117056
  rehypePlugins.push([rehypeSanitize, sanitizeSchema]);
116921
117057
  }
117058
+ const recmaPlugins = [...(userRecmaPlugins ?? [])];
117059
+ // In `mdx` format a literal <script> parses as JSX rather than raw HTML, so it
117060
+ // bypasses sanitization entirely — strip it in every format. Must stay last:
117061
+ // in `md` format raw HTML isn't an element until `rehypeRaw` has run. A tag
117062
+ // inside an expression or export is code, not an element, so the recma pass
117063
+ // catches it in the compiled program instead.
117064
+ if (sanitize) {
117065
+ rehypePlugins.push(rehypeStripTags);
117066
+ recmaPlugins.push(recmaStripTags);
117067
+ }
116922
117068
  try {
116923
117069
  const vfile = compileSync(text, {
116924
117070
  outputFormat: 'function-body',
116925
117071
  providerImportSource: '#',
116926
117072
  remarkPlugins,
116927
117073
  rehypePlugins,
117074
+ recmaPlugins,
116928
117075
  ...opts,
116929
117076
  });
116930
117077
  const string = String(vfile)
@@ -123598,6 +123745,7 @@ const collectImportValues = (importDeclarations, registry = defaultModuleRegistr
123598
123745
 
123599
123746
 
123600
123747
 
123748
+
123601
123749
  /**
123602
123750
  * Recursively extract all identifier names introduced by a binding pattern.
123603
123751
  * Handles destructuring (`{ a, b }`, `[x, y]`), rest elements (`...rest`),
@@ -123642,7 +123790,7 @@ const collectExportNames = (declaration) => {
123642
123790
  * Any evaluation error is consumed and logged. We don't throw because it's
123643
123791
  * against the spirit of this engine to be less permissive than MDX needs.
123644
123792
  */
123645
- const evaluateExports = () => (tree, file) => {
123793
+ const evaluateExports = (opts) => (tree, file) => {
123646
123794
  const programBody = [];
123647
123795
  const exportNames = [];
123648
123796
  const importDeclarations = [];
@@ -123688,6 +123836,8 @@ const evaluateExports = () => (tree, file) => {
123688
123836
  // Evaluate the declarations together at once in a single sandboxed Function
123689
123837
  try {
123690
123838
  const program = { type: 'Program', sourceType: 'module', body: programBody };
123839
+ if (opts?.sanitize)
123840
+ stripJsxTags(program);
123691
123841
  buildJsx(program, { runtime: 'classic', pragma: 'React.createElement', pragmaFrag: 'React.Fragment' });
123692
123842
  const { value: source } = toJs(program);
123693
123843
  // Make sure import values are available in the scope
@@ -127884,6 +128034,7 @@ function loadComponents() {
127884
128034
 
127885
128035
 
127886
128036
 
128037
+
127887
128038
 
127888
128039
 
127889
128040
  const defaultTransformers = [
@@ -128010,7 +128161,7 @@ function mdxishMdastToMd(mdast) {
128010
128161
  * @see .claude/context/MDXish/Processor Overview.md
128011
128162
  */
128012
128163
  function mdxish(mdContent, opts = {}) {
128013
- const { components: userComponents = {}, hardBreaks: enableHardBreaks = true, safeMode = false, variables } = opts;
128164
+ const { components: userComponents = {}, hardBreaks: enableHardBreaks = true, safeMode = false, sanitize = false, variables, } = opts;
128014
128165
  const components = {
128015
128166
  ...loadComponents(),
128016
128167
  ...userComponents,
@@ -128021,7 +128172,7 @@ function mdxish(mdContent, opts = {}) {
128021
128172
  const contentWithoutComments = restoreCodeBlocks(withoutComments, protectedCode);
128022
128173
  const { processor, parserReadyContent } = mdxishAstProcessor(contentWithoutComments, opts);
128023
128174
  processor
128024
- .use(safeMode ? undefined : evaluate_exports) // Evaluate `export const/function` and stash scope on file.data.mdxishScope
128175
+ .use(safeMode ? undefined : evaluate_exports, { sanitize }) // Evaluate `export const/function` and stash scope on file.data.mdxishScope
128025
128176
  .use(enableHardBreaks ? hard_breaks : undefined) // Must precede evaluateExpressions to avoid splitting the \n in an evaluated template literal into a <br> node
128026
128177
  .use(safeMode ? undefined : evaluate_expressions, { components, variables }) // Evaluate self-contained MDX expressions (e.g. `{1+1}`)
128027
128178
  .use(safeMode ? undefined : evaluate_style_block_expressions) // Evaluate `<style>{`...`}</style>` template literals into plain CSS
@@ -128032,6 +128183,7 @@ function mdxish(mdContent, opts = {}) {
128032
128183
  .use(restoreBooleanProperties)
128033
128184
  .use(safeMode ? undefined : resolve_deferred_attribute_expression_props) // Evaluate deferred attribute expressions on mdx-jsx nodes (now past rehypeRaw's clone)
128034
128185
  .use(normalize_mdx_jsx_nodes) // Rewrite `mdx-jsx` back to standard `element` nodes for downstream plugins
128186
+ .use(sanitize ? rehypeStripTags : undefined) // Strip STRIPPED_TAG_NAMES elements; this pipeline has no sanitization step
128035
128187
  .use(rehypeFlattenTableCellParagraphs) // Remove <p> wrappers inside table cells to prevent margin issues
128036
128188
  .use(mdxish_mermaid) // Add mermaid-render className to pre wrappers
128037
128189
  .use(heading_slugs)
@@ -129114,7 +129266,7 @@ module.exports = /*#__PURE__*/JSON.parse('{"*":["about","acceptCharset","accessK
129114
129266
  /******/ // startup
129115
129267
  /******/ // Load entry module and return exports
129116
129268
  /******/ // This entry module used 'module' so it can't be inlined
129117
- /******/ let __webpack_exports__ = __webpack_require__(5072);
129269
+ /******/ let __webpack_exports__ = __webpack_require__(6829);
129118
129270
  /******/ module.exports = __webpack_exports__;
129119
129271
  /******/
129120
129272
  /******/ })()