@readme/markdown 13.1.2 → 13.1.4

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,25 @@
1
+ export interface BlockHit {
2
+ key: string;
3
+ raw: string;
4
+ token: string;
5
+ }
6
+ /**
7
+ * The content matching in this regex captures everything between `[block:TYPE]`
8
+ * and `[/block]`, including new lines. Negative lookahead for the closing
9
+ * `[/block]` tag is required to prevent greedy matching to ensure it stops at
10
+ * the first closing tag it encounters preventing vulnerability to polynomial
11
+ * backtracking issues.
12
+ */
13
+ export declare const MAGIC_BLOCK_REGEX: RegExp;
14
+ /**
15
+ * Extract legacy magic block syntax from a markdown string.
16
+ * Returns the modified markdown and an array of extracted blocks.
17
+ */
18
+ export declare function extractMagicBlocks(markdown: string): {
19
+ replaced: string;
20
+ blocks: BlockHit[];
21
+ };
22
+ /**
23
+ * Restore extracted magic blocks back into a markdown string.
24
+ */
25
+ export declare function restoreMagicBlocks(replaced: string, blocks: BlockHit[]): string;
package/dist/main.js CHANGED
@@ -70954,13 +70954,15 @@ const scanForClosingTag = (parent, startIndex, tag) => {
70954
70954
  if (isClosingTag(siblingValue, tag)) {
70955
70955
  return { closingIndex: i, extraClosingChildren: [] };
70956
70956
  }
70957
- // Embedded closing tag (closing tag at end of HTML block content)
70957
+ // Embedded closing tag (closing tag within HTML block content)
70958
70958
  if (siblingValue.includes(closingTagStr)) {
70959
- const contentBeforeClose = siblingValue.substring(0, siblingValue.lastIndexOf(closingTagStr)).trim();
70959
+ const closeTagPos = siblingValue.indexOf(closingTagStr);
70960
+ const contentBeforeClose = siblingValue.substring(0, closeTagPos).trim();
70961
+ const contentAfterClose = siblingValue.substring(closeTagPos + closingTagStr.length).trim();
70960
70962
  const extraChildren = contentBeforeClose
70961
70963
  ? parseMdChildren(contentBeforeClose)
70962
70964
  : [];
70963
- return { closingIndex: i, extraClosingChildren: extraChildren };
70965
+ return { closingIndex: i, extraClosingChildren: extraChildren, contentAfterClose: contentAfterClose || undefined };
70964
70966
  }
70965
70967
  }
70966
70968
  // Check paragraph siblings
@@ -71105,7 +71107,7 @@ const mdxishComponentBlocks = () => tree => {
71105
71107
  const scanResult = scanForClosingTag(parent, index, tag);
71106
71108
  if (!scanResult)
71107
71109
  return;
71108
- const { closingIndex, extraClosingChildren, strippedParagraph } = scanResult;
71110
+ const { closingIndex, extraClosingChildren, strippedParagraph, contentAfterClose: remainingAfterClose } = scanResult;
71109
71111
  const extraChildren = contentAfterTag ? parseMdChildren(contentAfterTag.trimStart()) : [];
71110
71112
  // Collect all intermediate siblings between opening tag and closing tag
71111
71113
  const intermediateChildren = parent.children.slice(index + 1, closingIndex);
@@ -71128,6 +71130,11 @@ const mdxishComponentBlocks = () => tree => {
71128
71130
  if (componentNode.children.length > 0) {
71129
71131
  stack.push(componentNode);
71130
71132
  }
71133
+ // If the closing tag sibling had content after it (e.g., another component opening tag),
71134
+ // re-insert it as a sibling so it can be processed in subsequent iterations
71135
+ if (remainingAfterClose) {
71136
+ parseSibling(stack, parent, index, remainingAfterClose);
71137
+ }
71131
71138
  };
71132
71139
  // Process the nodes with the components depth-first to maintain the correct order of the nodes
71133
71140
  while (stack.length) {
@@ -71360,6 +71367,10 @@ const types = {
71360
71367
  Recipe: NodeTypes.recipe,
71361
71368
  TutorialTile: NodeTypes.recipe, // coerce to recipe for backwards compatibility
71362
71369
  };
71370
+ // Node types that are phrasing (inline) content per the mdast spec. Phrasing
71371
+ // content at the document root violates the spec and causes mdx() to collapse
71372
+ // blank lines, so these must be wrapped in a paragraph when at root level.
71373
+ const phrasingTypes = new Set([NodeTypes.variable]);
71363
71374
  var TableNames;
71364
71375
  (function (TableNames) {
71365
71376
  TableNames["td"] = "td";
@@ -71515,7 +71526,16 @@ const coerceJsxToMd = ({ components = {}, html = false } = {}) => (node, index,
71515
71526
  type: types[node.name],
71516
71527
  position: node.position,
71517
71528
  };
71518
- parent.children[index] = mdNode;
71529
+ // Wrap in a paragraph if at root level. Links are phrasing content and
71530
+ // root children must all be the same category (per mdast spec). Mixing
71531
+ // phrasing with flow content (headings, paragraphs, etc.) causes mdx()
71532
+ // to collapse blank lines in the document.
71533
+ if (parent.type === 'root') {
71534
+ parent.children[index] = { type: 'paragraph', children: [mdNode], position: node.position };
71535
+ }
71536
+ else {
71537
+ parent.children[index] = mdNode;
71538
+ }
71519
71539
  }
71520
71540
  else if (node.name === 'Recipe' || node.name === 'TutorialTile') {
71521
71541
  const hProperties = getAttrs(node);
@@ -71541,7 +71561,13 @@ const coerceJsxToMd = ({ components = {}, html = false } = {}) => (node, index,
71541
71561
  },
71542
71562
  position: node.position,
71543
71563
  };
71544
- parent.children[index] = mdNode;
71564
+ if (parent.type === 'root' && phrasingTypes.has(types[node.name])) {
71565
+ // @ts-expect-error mdNode is typed as BlockContent but is actually phrasing content
71566
+ parent.children[index] = { type: 'paragraph', children: [mdNode], position: node.position };
71567
+ }
71568
+ else {
71569
+ parent.children[index] = mdNode;
71570
+ }
71545
71571
  }
71546
71572
  };
71547
71573
  const readmeComponents = (opts) => () => tree => {
@@ -93425,7 +93451,7 @@ function isActualHtmlTag(tagName, originalExcerpt) {
93425
93451
  return false;
93426
93452
  }
93427
93453
  /** Parse and replace text children with processed markdown */
93428
- function parseTextChildren(node, processMarkdown) {
93454
+ function parseTextChildren(node, processMarkdown, components) {
93429
93455
  if (!node.children?.length)
93430
93456
  return;
93431
93457
  // First pass: Recursively process text children as they may contain stringified markdown / mdx content
@@ -93440,6 +93466,27 @@ function parseTextChildren(node, processMarkdown) {
93440
93466
  }
93441
93467
  return children;
93442
93468
  });
93469
+ // Unwrap <p> elements whose meaningful children are ALL components.
93470
+ // The markdown parser wraps inline content in <p> tags, but when that content
93471
+ // is actually component children (e.g., <Tab> inside <Tabs>), the wrapper
93472
+ // should be removed so components appear as direct children.
93473
+ // Only unwrap when every non-whitespace, non-br child is a known component
93474
+ // to avoid breaking paragraphs with mixed content (text + inline HTML like <code>).
93475
+ node.children = node.children.flatMap(child => {
93476
+ if (child.type !== 'element' || child.tagName !== 'p')
93477
+ return [child];
93478
+ const meaningfulChildren = child.children.filter(gc => {
93479
+ if (gc.type === 'text' && !gc.value.trim())
93480
+ return false;
93481
+ if (gc.type === 'element' && gc.tagName === 'br')
93482
+ return false;
93483
+ return true;
93484
+ });
93485
+ const allComponents = meaningfulChildren.length > 0 && meaningfulChildren.every(gc => gc.type === 'element' && getComponentName(gc.tagName, components));
93486
+ if (!allComponents)
93487
+ return [child];
93488
+ return meaningfulChildren;
93489
+ });
93443
93490
  // Post-processing: remove whitespace-only text nodes if all siblings are components
93444
93491
  // This prevents whitespace between component children from being counted as extra children
93445
93492
  if (areAllChildrenComponents(node.children)) {
@@ -93494,7 +93541,7 @@ const rehypeMdxishComponents = ({ components, processMarkdown }) => {
93494
93541
  }
93495
93542
  node.tagName = componentName;
93496
93543
  normalizeProperties(node);
93497
- parseTextChildren(node, processMarkdown);
93544
+ parseTextChildren(node, processMarkdown, components);
93498
93545
  });
93499
93546
  // Remove unknown components in reverse order to preserve indices
93500
93547
  for (let i = nodesToRemove.length - 1; i >= 0; i -= 1) {
@@ -93795,8 +93842,10 @@ function escapeUnbalancedBraces(content) {
93795
93842
  const unbalanced = new Set();
93796
93843
  let strDelim = null;
93797
93844
  let strEscaped = false;
93798
- for (let i = 0; i < content.length; i += 1) {
93799
- const ch = content[i];
93845
+ // Convert to array of Unicode code points to handle emojis and multi-byte characters correctly
93846
+ const chars = Array.from(content);
93847
+ for (let i = 0; i < chars.length; i += 1) {
93848
+ const ch = chars[i];
93800
93849
  // Track strings inside expressions to ignore braces within them
93801
93850
  if (opens.length > 0) {
93802
93851
  if (strDelim) {
@@ -93818,7 +93867,7 @@ function escapeUnbalancedBraces(content) {
93818
93867
  // Skip already-escaped braces (count preceding backslashes)
93819
93868
  if (ch === '{' || ch === '}') {
93820
93869
  let bs = 0;
93821
- for (let j = i - 1; j >= 0 && content[j] === '\\'; j -= 1)
93870
+ for (let j = i - 1; j >= 0 && chars[j] === '\\'; j -= 1)
93822
93871
  bs += 1;
93823
93872
  if (bs % 2 === 1) {
93824
93873
  // eslint-disable-next-line no-continue
@@ -93837,7 +93886,7 @@ function escapeUnbalancedBraces(content) {
93837
93886
  opens.forEach(pos => unbalanced.add(pos));
93838
93887
  if (unbalanced.size === 0)
93839
93888
  return content;
93840
- return Array.from(content)
93889
+ return chars
93841
93890
  .map((ch, i) => (unbalanced.has(i) ? `\\${ch}` : ch))
93842
93891
  .join('');
93843
93892
  }
@@ -97742,8 +97791,59 @@ const mdxishTags_tags = (doc) => {
97742
97791
  };
97743
97792
  /* harmony default export */ const mdxishTags = (mdxishTags_tags);
97744
97793
 
97745
- ;// ./lib/stripComments.ts
97794
+ ;// ./lib/utils/extractMagicBlocks.ts
97795
+ /**
97796
+ * The content matching in this regex captures everything between `[block:TYPE]`
97797
+ * and `[/block]`, including new lines. Negative lookahead for the closing
97798
+ * `[/block]` tag is required to prevent greedy matching to ensure it stops at
97799
+ * the first closing tag it encounters preventing vulnerability to polynomial
97800
+ * backtracking issues.
97801
+ */
97802
+ const MAGIC_BLOCK_REGEX = /\[block:[^\]]{1,100}\](?:(?!\[block:)(?!\[\/block\])[\s\S])*\[\/block\]/g;
97803
+ /**
97804
+ * Extract legacy magic block syntax from a markdown string.
97805
+ * Returns the modified markdown and an array of extracted blocks.
97806
+ */
97807
+ function extractMagicBlocks(markdown) {
97808
+ const blocks = [];
97809
+ let index = 0;
97810
+ const replaced = markdown.replace(MAGIC_BLOCK_REGEX, match => {
97811
+ /**
97812
+ * Key is the unique identifier for the magic block
97813
+ */
97814
+ const key = `__MAGIC_BLOCK_${index}__`;
97815
+ /**
97816
+ * Token is a wrapper around the `key` to serialize & influence how the
97817
+ * magic block is parsed in the remark pipeline.
97818
+ * - Use backticks so it becomes a code span, preventing `remarkParse` from
97819
+ * parsing special characters in the token as markdown syntax
97820
+ * - Prepend a newline to ensure it is parsed as a block level node
97821
+ * - Append a newline to ensure it is separated from following content
97822
+ */
97823
+ const token = `\n\`${key}\`\n`;
97824
+ blocks.push({ key, raw: match, token });
97825
+ index += 1;
97826
+ return token;
97827
+ });
97828
+ return { replaced, blocks };
97829
+ }
97830
+ /**
97831
+ * Restore extracted magic blocks back into a markdown string.
97832
+ */
97833
+ function restoreMagicBlocks(replaced, blocks) {
97834
+ // If a magic block is at the start or end of the document, the extraction
97835
+ // token's newlines will have been trimmed during processing. We need to
97836
+ // account for that here to ensure the token is found and replaced correctly.
97837
+ // These extra newlines will be removed again when the final string is trimmed.
97838
+ const content = `\n${replaced}\n`;
97839
+ const restoredContent = blocks.reduce((acc, { token, raw }) => {
97840
+ // Ensure each magic block is separated by newlines when restored.
97841
+ return acc.split(token).join(`\n${raw}\n`);
97842
+ }, content);
97843
+ return restoredContent.trim();
97844
+ }
97746
97845
 
97846
+ ;// ./lib/stripComments.ts
97747
97847
 
97748
97848
 
97749
97849
 
@@ -97758,19 +97858,16 @@ const mdxishTags_tags = (doc) => {
97758
97858
  * Removes Markdown and MDX comments.
97759
97859
  */
97760
97860
  async function stripComments(doc, { mdx, mdxish } = {}) {
97761
- const micromarkExtensions = [magicBlock()];
97762
- const fromMarkdownExtensions = [magicBlockFromMarkdown()];
97861
+ const { replaced, blocks } = extractMagicBlocks(doc);
97862
+ const processor = unified();
97763
97863
  // we still require these two extensions because:
97764
- // 1. we cant rely on remarkMdx to parse MDXish
97864
+ // 1. we can rely on remarkMdx to parse MDXish
97765
97865
  // 2. we need to parse JSX comments into mdxTextExpression nodes so that the transformers can pick them up
97766
97866
  if (mdxish) {
97767
- micromarkExtensions.push(mdxExpression({ allowEmpty: true }));
97768
- fromMarkdownExtensions.push(mdxExpressionFromMarkdown());
97867
+ processor
97868
+ .data('micromarkExtensions', [mdxExpression({ allowEmpty: true })])
97869
+ .data('fromMarkdownExtensions', [mdxExpressionFromMarkdown()]);
97769
97870
  }
97770
- const processor = unified()
97771
- .data('micromarkExtensions', micromarkExtensions)
97772
- .data('fromMarkdownExtensions', fromMarkdownExtensions)
97773
- .data('toMarkdownExtensions', [magicBlockToMarkdown()]);
97774
97871
  processor
97775
97872
  .use(remarkParse)
97776
97873
  .use(normalize_malformed_md_syntax)
@@ -97804,8 +97901,10 @@ async function stripComments(doc, { mdx, mdxish } = {}) {
97804
97901
  },
97805
97902
  ],
97806
97903
  });
97807
- const file = await processor.process(doc);
97808
- return String(file).trim();
97904
+ const file = await processor.process(replaced);
97905
+ const stringified = String(file).trim();
97906
+ const restored = restoreMagicBlocks(stringified, blocks);
97907
+ return restored;
97809
97908
  }
97810
97909
  /* harmony default export */ const lib_stripComments = (stripComments);
97811
97910
 
package/dist/main.node.js CHANGED
@@ -91158,13 +91158,15 @@ const scanForClosingTag = (parent, startIndex, tag) => {
91158
91158
  if (isClosingTag(siblingValue, tag)) {
91159
91159
  return { closingIndex: i, extraClosingChildren: [] };
91160
91160
  }
91161
- // Embedded closing tag (closing tag at end of HTML block content)
91161
+ // Embedded closing tag (closing tag within HTML block content)
91162
91162
  if (siblingValue.includes(closingTagStr)) {
91163
- const contentBeforeClose = siblingValue.substring(0, siblingValue.lastIndexOf(closingTagStr)).trim();
91163
+ const closeTagPos = siblingValue.indexOf(closingTagStr);
91164
+ const contentBeforeClose = siblingValue.substring(0, closeTagPos).trim();
91165
+ const contentAfterClose = siblingValue.substring(closeTagPos + closingTagStr.length).trim();
91164
91166
  const extraChildren = contentBeforeClose
91165
91167
  ? parseMdChildren(contentBeforeClose)
91166
91168
  : [];
91167
- return { closingIndex: i, extraClosingChildren: extraChildren };
91169
+ return { closingIndex: i, extraClosingChildren: extraChildren, contentAfterClose: contentAfterClose || undefined };
91168
91170
  }
91169
91171
  }
91170
91172
  // Check paragraph siblings
@@ -91309,7 +91311,7 @@ const mdxishComponentBlocks = () => tree => {
91309
91311
  const scanResult = scanForClosingTag(parent, index, tag);
91310
91312
  if (!scanResult)
91311
91313
  return;
91312
- const { closingIndex, extraClosingChildren, strippedParagraph } = scanResult;
91314
+ const { closingIndex, extraClosingChildren, strippedParagraph, contentAfterClose: remainingAfterClose } = scanResult;
91313
91315
  const extraChildren = contentAfterTag ? parseMdChildren(contentAfterTag.trimStart()) : [];
91314
91316
  // Collect all intermediate siblings between opening tag and closing tag
91315
91317
  const intermediateChildren = parent.children.slice(index + 1, closingIndex);
@@ -91332,6 +91334,11 @@ const mdxishComponentBlocks = () => tree => {
91332
91334
  if (componentNode.children.length > 0) {
91333
91335
  stack.push(componentNode);
91334
91336
  }
91337
+ // If the closing tag sibling had content after it (e.g., another component opening tag),
91338
+ // re-insert it as a sibling so it can be processed in subsequent iterations
91339
+ if (remainingAfterClose) {
91340
+ parseSibling(stack, parent, index, remainingAfterClose);
91341
+ }
91335
91342
  };
91336
91343
  // Process the nodes with the components depth-first to maintain the correct order of the nodes
91337
91344
  while (stack.length) {
@@ -91564,6 +91571,10 @@ const readme_components_types = {
91564
91571
  Recipe: NodeTypes.recipe,
91565
91572
  TutorialTile: NodeTypes.recipe, // coerce to recipe for backwards compatibility
91566
91573
  };
91574
+ // Node types that are phrasing (inline) content per the mdast spec. Phrasing
91575
+ // content at the document root violates the spec and causes mdx() to collapse
91576
+ // blank lines, so these must be wrapped in a paragraph when at root level.
91577
+ const phrasingTypes = new Set([NodeTypes.variable]);
91567
91578
  var TableNames;
91568
91579
  (function (TableNames) {
91569
91580
  TableNames["td"] = "td";
@@ -91719,7 +91730,16 @@ const coerceJsxToMd = ({ components = {}, html = false } = {}) => (node, index,
91719
91730
  type: readme_components_types[node.name],
91720
91731
  position: node.position,
91721
91732
  };
91722
- parent.children[index] = mdNode;
91733
+ // Wrap in a paragraph if at root level. Links are phrasing content and
91734
+ // root children must all be the same category (per mdast spec). Mixing
91735
+ // phrasing with flow content (headings, paragraphs, etc.) causes mdx()
91736
+ // to collapse blank lines in the document.
91737
+ if (parent.type === 'root') {
91738
+ parent.children[index] = { type: 'paragraph', children: [mdNode], position: node.position };
91739
+ }
91740
+ else {
91741
+ parent.children[index] = mdNode;
91742
+ }
91723
91743
  }
91724
91744
  else if (node.name === 'Recipe' || node.name === 'TutorialTile') {
91725
91745
  const hProperties = getAttrs(node);
@@ -91745,7 +91765,13 @@ const coerceJsxToMd = ({ components = {}, html = false } = {}) => (node, index,
91745
91765
  },
91746
91766
  position: node.position,
91747
91767
  };
91748
- parent.children[index] = mdNode;
91768
+ if (parent.type === 'root' && phrasingTypes.has(readme_components_types[node.name])) {
91769
+ // @ts-expect-error mdNode is typed as BlockContent but is actually phrasing content
91770
+ parent.children[index] = { type: 'paragraph', children: [mdNode], position: node.position };
91771
+ }
91772
+ else {
91773
+ parent.children[index] = mdNode;
91774
+ }
91749
91775
  }
91750
91776
  };
91751
91777
  const readmeComponents = (opts) => () => tree => {
@@ -113629,7 +113655,7 @@ function isActualHtmlTag(tagName, originalExcerpt) {
113629
113655
  return false;
113630
113656
  }
113631
113657
  /** Parse and replace text children with processed markdown */
113632
- function parseTextChildren(node, processMarkdown) {
113658
+ function parseTextChildren(node, processMarkdown, components) {
113633
113659
  if (!node.children?.length)
113634
113660
  return;
113635
113661
  // First pass: Recursively process text children as they may contain stringified markdown / mdx content
@@ -113644,6 +113670,27 @@ function parseTextChildren(node, processMarkdown) {
113644
113670
  }
113645
113671
  return children;
113646
113672
  });
113673
+ // Unwrap <p> elements whose meaningful children are ALL components.
113674
+ // The markdown parser wraps inline content in <p> tags, but when that content
113675
+ // is actually component children (e.g., <Tab> inside <Tabs>), the wrapper
113676
+ // should be removed so components appear as direct children.
113677
+ // Only unwrap when every non-whitespace, non-br child is a known component
113678
+ // to avoid breaking paragraphs with mixed content (text + inline HTML like <code>).
113679
+ node.children = node.children.flatMap(child => {
113680
+ if (child.type !== 'element' || child.tagName !== 'p')
113681
+ return [child];
113682
+ const meaningfulChildren = child.children.filter(gc => {
113683
+ if (gc.type === 'text' && !gc.value.trim())
113684
+ return false;
113685
+ if (gc.type === 'element' && gc.tagName === 'br')
113686
+ return false;
113687
+ return true;
113688
+ });
113689
+ const allComponents = meaningfulChildren.length > 0 && meaningfulChildren.every(gc => gc.type === 'element' && getComponentName(gc.tagName, components));
113690
+ if (!allComponents)
113691
+ return [child];
113692
+ return meaningfulChildren;
113693
+ });
113647
113694
  // Post-processing: remove whitespace-only text nodes if all siblings are components
113648
113695
  // This prevents whitespace between component children from being counted as extra children
113649
113696
  if (areAllChildrenComponents(node.children)) {
@@ -113698,7 +113745,7 @@ const rehypeMdxishComponents = ({ components, processMarkdown }) => {
113698
113745
  }
113699
113746
  node.tagName = componentName;
113700
113747
  normalizeProperties(node);
113701
- parseTextChildren(node, processMarkdown);
113748
+ parseTextChildren(node, processMarkdown, components);
113702
113749
  });
113703
113750
  // Remove unknown components in reverse order to preserve indices
113704
113751
  for (let i = nodesToRemove.length - 1; i >= 0; i -= 1) {
@@ -113999,8 +114046,10 @@ function escapeUnbalancedBraces(content) {
113999
114046
  const unbalanced = new Set();
114000
114047
  let strDelim = null;
114001
114048
  let strEscaped = false;
114002
- for (let i = 0; i < content.length; i += 1) {
114003
- const ch = content[i];
114049
+ // Convert to array of Unicode code points to handle emojis and multi-byte characters correctly
114050
+ const chars = Array.from(content);
114051
+ for (let i = 0; i < chars.length; i += 1) {
114052
+ const ch = chars[i];
114004
114053
  // Track strings inside expressions to ignore braces within them
114005
114054
  if (opens.length > 0) {
114006
114055
  if (strDelim) {
@@ -114022,7 +114071,7 @@ function escapeUnbalancedBraces(content) {
114022
114071
  // Skip already-escaped braces (count preceding backslashes)
114023
114072
  if (ch === '{' || ch === '}') {
114024
114073
  let bs = 0;
114025
- for (let j = i - 1; j >= 0 && content[j] === '\\'; j -= 1)
114074
+ for (let j = i - 1; j >= 0 && chars[j] === '\\'; j -= 1)
114026
114075
  bs += 1;
114027
114076
  if (bs % 2 === 1) {
114028
114077
  // eslint-disable-next-line no-continue
@@ -114041,7 +114090,7 @@ function escapeUnbalancedBraces(content) {
114041
114090
  opens.forEach(pos => unbalanced.add(pos));
114042
114091
  if (unbalanced.size === 0)
114043
114092
  return content;
114044
- return Array.from(content)
114093
+ return chars
114045
114094
  .map((ch, i) => (unbalanced.has(i) ? `\\${ch}` : ch))
114046
114095
  .join('');
114047
114096
  }
@@ -117946,8 +117995,59 @@ const mdxishTags_tags = (doc) => {
117946
117995
  };
117947
117996
  /* harmony default export */ const mdxishTags = (mdxishTags_tags);
117948
117997
 
117949
- ;// ./lib/stripComments.ts
117998
+ ;// ./lib/utils/extractMagicBlocks.ts
117999
+ /**
118000
+ * The content matching in this regex captures everything between `[block:TYPE]`
118001
+ * and `[/block]`, including new lines. Negative lookahead for the closing
118002
+ * `[/block]` tag is required to prevent greedy matching to ensure it stops at
118003
+ * the first closing tag it encounters preventing vulnerability to polynomial
118004
+ * backtracking issues.
118005
+ */
118006
+ const MAGIC_BLOCK_REGEX = /\[block:[^\]]{1,100}\](?:(?!\[block:)(?!\[\/block\])[\s\S])*\[\/block\]/g;
118007
+ /**
118008
+ * Extract legacy magic block syntax from a markdown string.
118009
+ * Returns the modified markdown and an array of extracted blocks.
118010
+ */
118011
+ function extractMagicBlocks(markdown) {
118012
+ const blocks = [];
118013
+ let index = 0;
118014
+ const replaced = markdown.replace(MAGIC_BLOCK_REGEX, match => {
118015
+ /**
118016
+ * Key is the unique identifier for the magic block
118017
+ */
118018
+ const key = `__MAGIC_BLOCK_${index}__`;
118019
+ /**
118020
+ * Token is a wrapper around the `key` to serialize & influence how the
118021
+ * magic block is parsed in the remark pipeline.
118022
+ * - Use backticks so it becomes a code span, preventing `remarkParse` from
118023
+ * parsing special characters in the token as markdown syntax
118024
+ * - Prepend a newline to ensure it is parsed as a block level node
118025
+ * - Append a newline to ensure it is separated from following content
118026
+ */
118027
+ const token = `\n\`${key}\`\n`;
118028
+ blocks.push({ key, raw: match, token });
118029
+ index += 1;
118030
+ return token;
118031
+ });
118032
+ return { replaced, blocks };
118033
+ }
118034
+ /**
118035
+ * Restore extracted magic blocks back into a markdown string.
118036
+ */
118037
+ function restoreMagicBlocks(replaced, blocks) {
118038
+ // If a magic block is at the start or end of the document, the extraction
118039
+ // token's newlines will have been trimmed during processing. We need to
118040
+ // account for that here to ensure the token is found and replaced correctly.
118041
+ // These extra newlines will be removed again when the final string is trimmed.
118042
+ const content = `\n${replaced}\n`;
118043
+ const restoredContent = blocks.reduce((acc, { token, raw }) => {
118044
+ // Ensure each magic block is separated by newlines when restored.
118045
+ return acc.split(token).join(`\n${raw}\n`);
118046
+ }, content);
118047
+ return restoredContent.trim();
118048
+ }
117950
118049
 
118050
+ ;// ./lib/stripComments.ts
117951
118051
 
117952
118052
 
117953
118053
 
@@ -117962,19 +118062,16 @@ const mdxishTags_tags = (doc) => {
117962
118062
  * Removes Markdown and MDX comments.
117963
118063
  */
117964
118064
  async function stripComments(doc, { mdx, mdxish } = {}) {
117965
- const micromarkExtensions = [magicBlock()];
117966
- const fromMarkdownExtensions = [magicBlockFromMarkdown()];
118065
+ const { replaced, blocks } = extractMagicBlocks(doc);
118066
+ const processor = unified();
117967
118067
  // we still require these two extensions because:
117968
- // 1. we cant rely on remarkMdx to parse MDXish
118068
+ // 1. we can rely on remarkMdx to parse MDXish
117969
118069
  // 2. we need to parse JSX comments into mdxTextExpression nodes so that the transformers can pick them up
117970
118070
  if (mdxish) {
117971
- micromarkExtensions.push(mdxExpression({ allowEmpty: true }));
117972
- fromMarkdownExtensions.push(mdxExpressionFromMarkdown());
118071
+ processor
118072
+ .data('micromarkExtensions', [mdxExpression({ allowEmpty: true })])
118073
+ .data('fromMarkdownExtensions', [mdxExpressionFromMarkdown()]);
117973
118074
  }
117974
- const processor = unified()
117975
- .data('micromarkExtensions', micromarkExtensions)
117976
- .data('fromMarkdownExtensions', fromMarkdownExtensions)
117977
- .data('toMarkdownExtensions', [magicBlockToMarkdown()]);
117978
118075
  processor
117979
118076
  .use(remarkParse)
117980
118077
  .use(normalize_malformed_md_syntax)
@@ -118008,8 +118105,10 @@ async function stripComments(doc, { mdx, mdxish } = {}) {
118008
118105
  },
118009
118106
  ],
118010
118107
  });
118011
- const file = await processor.process(doc);
118012
- return String(file).trim();
118108
+ const file = await processor.process(replaced);
118109
+ const stringified = String(file).trim();
118110
+ const restored = restoreMagicBlocks(stringified, blocks);
118111
+ return restored;
118013
118112
  }
118014
118113
  /* harmony default export */ const lib_stripComments = (stripComments);
118015
118114