@mintlify/common 1.0.647 → 1.0.649

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.
@@ -1,5 +1,5 @@
1
1
  export * from './createPathArr.js';
2
- export * from './optionallyLeadingSlash.js';
2
+ export * from './optionallySlash.js';
3
3
  export * from './removeLeadingSlash.js';
4
4
  export * from './normalizeRelativePath.js';
5
5
  export * from './addBasePath.js';
package/dist/fs/index.js CHANGED
@@ -1,5 +1,5 @@
1
1
  export * from './createPathArr.js';
2
- export * from './optionallyLeadingSlash.js';
2
+ export * from './optionallySlash.js';
3
3
  export * from './removeLeadingSlash.js';
4
4
  export * from './normalizeRelativePath.js';
5
5
  export * from './addBasePath.js';
@@ -1,2 +1,3 @@
1
1
  export declare function optionallyAddLeadingSlash(filePath: string): string;
2
2
  export declare function optionallyRemoveLeadingSlash(filePath: string): string;
3
+ export declare function optionallyRemoveTrailingSlash(path: string): string;
@@ -11,3 +11,9 @@ export function optionallyRemoveLeadingSlash(filePath) {
11
11
  }
12
12
  return filePath;
13
13
  }
14
+ export function optionallyRemoveTrailingSlash(path) {
15
+ if (path.endsWith('/')) {
16
+ return path.slice(0, -1);
17
+ }
18
+ return path;
19
+ }
@@ -0,0 +1 @@
1
+ export declare const UPDATE_MAX = 15;
@@ -0,0 +1 @@
1
+ export const UPDATE_MAX = 15;
@@ -0,0 +1,4 @@
1
+ import type { RootContent } from 'mdast';
2
+ import type { UpdateMDXComponent } from './index.js';
3
+ export declare const getDateForNode: (node: RootContent, lineBlame: Record<number, string>) => string;
4
+ export declare const getMostRepresentativeDate: (updateComponent: UpdateMDXComponent, lineBlame: Record<number, string>, defaultDate: string) => string;
@@ -0,0 +1,49 @@
1
+ export const getDateForNode = (node, lineBlame) => {
2
+ if (!node.position) {
3
+ return new Date().toISOString();
4
+ }
5
+ const startLine = node.position.start.line;
6
+ const blameDate = lineBlame[startLine];
7
+ if (blameDate) {
8
+ const parsedDate = new Date(blameDate);
9
+ if (!isNaN(parsedDate.getTime())) {
10
+ return parsedDate.toISOString();
11
+ }
12
+ }
13
+ return new Date().toISOString();
14
+ };
15
+ export const getMostRepresentativeDate = (updateComponent, lineBlame, defaultDate) => {
16
+ const dates = [];
17
+ const collectDates = (nodes) => {
18
+ for (const node of nodes) {
19
+ if (node.type !== 'heading' && node.position) {
20
+ const nodeDate = getDateForNode(node, lineBlame);
21
+ dates.push(nodeDate);
22
+ }
23
+ if ('children' in node && Array.isArray(node.children) && node.children.length > 0) {
24
+ collectDates(node.children);
25
+ }
26
+ }
27
+ };
28
+ collectDates(updateComponent.children);
29
+ if (dates.length === 0) {
30
+ return defaultDate;
31
+ }
32
+ const dateCounts = new Map();
33
+ for (const date of dates) {
34
+ dateCounts.set(date, (dateCounts.get(date) || 0) + 1);
35
+ }
36
+ let mostCommonDate = defaultDate;
37
+ let maxCount = 0;
38
+ for (const [date, count] of dateCounts.entries()) {
39
+ if (count > maxCount) {
40
+ maxCount = count;
41
+ mostCommonDate = date;
42
+ }
43
+ }
44
+ const threshold = dates.length * 0.9;
45
+ if (maxCount >= threshold) {
46
+ return mostCommonDate;
47
+ }
48
+ return defaultDate;
49
+ };
@@ -0,0 +1 @@
1
+ export declare const encodeContentAsHtml: (content: string) => string;
@@ -0,0 +1,40 @@
1
+ import { fromHtml } from 'hast-util-from-html';
2
+ import rehypeStringify from 'rehype-stringify';
3
+ import remarkGfm from 'remark-gfm';
4
+ import remarkParse from 'remark-parse';
5
+ import remarkRehype from 'remark-rehype';
6
+ import { unified } from 'unified';
7
+ import { visit } from 'unist-util-visit';
8
+ const DANGEROUS_TAGS = ['script', 'iframe', 'object', 'embed', 'style'];
9
+ const rehypeRaw = () => {
10
+ return (tree) => {
11
+ visit(tree, 'raw', (raw, index, parent) => {
12
+ if ('value' in raw && parent && index !== undefined) {
13
+ const rawAst = fromHtml(raw.value, { fragment: true });
14
+ parent.children.splice(index, 1, ...rawAst.children);
15
+ }
16
+ });
17
+ };
18
+ };
19
+ const rehypeSanitizeDangerous = () => {
20
+ return (tree) => {
21
+ visit(tree, 'element', (node, index, parent) => {
22
+ if (parent && typeof index === 'number' && DANGEROUS_TAGS.includes(node.tagName)) {
23
+ parent.children.splice(index, 1);
24
+ return ['skip', index];
25
+ }
26
+ });
27
+ };
28
+ };
29
+ export const encodeContentAsHtml = (content) => {
30
+ const html = unified()
31
+ .use(remarkParse)
32
+ .use(remarkGfm)
33
+ .use(remarkRehype, { allowDangerousHtml: true })
34
+ .use(rehypeRaw)
35
+ .use(rehypeSanitizeDangerous)
36
+ .use(rehypeStringify, { allowDangerousHtml: true })
37
+ .processSync(content)
38
+ .toString();
39
+ return html;
40
+ };
@@ -0,0 +1,3 @@
1
+ import type { Root } from 'mdast';
2
+ import type { RssSnippetFile } from '../types/rss.js';
3
+ export declare const enhanceSnippetsWithImportNames: (ast: Root, snippets: RssSnippetFile[]) => Promise<RssSnippetFile[]>;
@@ -0,0 +1,28 @@
1
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
2
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
3
+ return new (P || (P = Promise))(function (resolve, reject) {
4
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
5
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
6
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
7
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
8
+ });
9
+ };
10
+ import { findAndRemoveImports } from '../mdx/snippets/findAndRemoveImports.js';
11
+ export const enhanceSnippetsWithImportNames = (ast, snippets) => __awaiter(void 0, void 0, void 0, function* () {
12
+ const { importMap } = yield findAndRemoveImports(structuredClone(ast));
13
+ return snippets.map((snippet) => {
14
+ if (snippet.importNames && snippet.importNames.length > 0) {
15
+ return snippet;
16
+ }
17
+ let importSpecifiers = importMap[snippet.path];
18
+ if (!importSpecifiers) {
19
+ const pathWithSlash = snippet.path.startsWith('/') ? snippet.path : `/${snippet.path}`;
20
+ const pathWithoutSlash = snippet.path.startsWith('/') ? snippet.path.slice(1) : snippet.path;
21
+ importSpecifiers = importMap[pathWithSlash] || importMap[pathWithoutSlash];
22
+ }
23
+ if (importSpecifiers && importSpecifiers.length > 0) {
24
+ return Object.assign(Object.assign({}, snippet), { importNames: importSpecifiers.map((spec) => spec.name) });
25
+ }
26
+ return snippet;
27
+ });
28
+ });
@@ -0,0 +1,2 @@
1
+ import type { SavedRssFileV4, RSSItemV2 } from '../types/rss.js';
2
+ export declare const getV4FeedUpdates: (rssFile: SavedRssFileV4) => Promise<RSSItemV2[]>;
@@ -0,0 +1,53 @@
1
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
2
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
3
+ return new (P || (P = Promise))(function (resolve, reject) {
4
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
5
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
6
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
7
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
8
+ });
9
+ };
10
+ import { getAST } from '../mdx/remark.js';
11
+ import { UPDATE_MAX } from './constants.js';
12
+ import { getDateForNode, getMostRepresentativeDate } from './dates.js';
13
+ import { enhanceSnippetsWithImportNames } from './enhanceSnippetsWithImportNames.js';
14
+ import { isUpdate } from './index.js';
15
+ import { processUpdateNode } from './processUpdateNode.js';
16
+ import { resolveSnippets } from './resolveSnippets.js';
17
+ export const getV4FeedUpdates = (rssFile) => __awaiter(void 0, void 0, void 0, function* () {
18
+ const { content, lineBlame, snippets } = rssFile;
19
+ if (!content) {
20
+ return [];
21
+ }
22
+ const effectiveLineBlame = Object.keys(lineBlame).length > 0 ? lineBlame : { 1: new Date().toISOString() };
23
+ let ast;
24
+ try {
25
+ ast = getAST(content);
26
+ }
27
+ catch (error) {
28
+ return [];
29
+ }
30
+ if (snippets && snippets.length > 0) {
31
+ try {
32
+ const enhancedSnippets = yield enhanceSnippetsWithImportNames(ast, snippets);
33
+ ast = resolveSnippets(ast, enhancedSnippets);
34
+ }
35
+ catch (_a) { }
36
+ }
37
+ const updateComponents = ast.children.filter((child) => isUpdate(child));
38
+ if (updateComponents.length === 0) {
39
+ return [];
40
+ }
41
+ const allUpdates = [];
42
+ for (const updateComponent of updateComponents) {
43
+ const componentDate = getDateForNode(updateComponent, effectiveLineBlame);
44
+ const representativeDate = getMostRepresentativeDate(updateComponent, effectiveLineBlame, componentDate);
45
+ const updates = processUpdateNode({
46
+ updateNode: updateComponent,
47
+ date: representativeDate,
48
+ });
49
+ allUpdates.push(...updates);
50
+ }
51
+ const limitedUpdates = allUpdates.slice(0, UPDATE_MAX);
52
+ return limitedUpdates;
53
+ });
@@ -1,7 +1,6 @@
1
1
  import type { FrontmatterContent, Heading, Parent, Root, RootContent } from 'mdast';
2
2
  import { MdxJsxFlowElement } from 'mdast-util-mdx-jsx';
3
- import { RSSItemV2 } from '../types/rss.js';
4
- export declare const UPDATE_MAX = 15;
3
+ import { RSSItemV2, SavedRssFileV4, SavedRssFileWithContent } from '../types/rss.js';
5
4
  export type UpdateMDXComponent = MdxJsxFlowElement;
6
5
  export declare const isFrontmatter: (node: RootContent | undefined) => node is FrontmatterContent;
7
6
  export declare const isUpdate: (node: RootContent | undefined) => node is UpdateMDXComponent;
@@ -53,3 +52,9 @@ export declare const processUpdatePerMarkdownGroup: ({ updateNode, date, updates
53
52
  date: string;
54
53
  updatesByMarkdown: RootContent[][];
55
54
  }) => RSSItemV2[];
55
+ export declare const isRSSFileV4: (rssFile: SavedRssFileWithContent | SavedRssFileV4) => rssFile is SavedRssFileV4;
56
+ export { enhanceSnippetsWithImportNames } from './enhanceSnippetsWithImportNames.js';
57
+ export { getV4FeedUpdates } from './getV4FeedUpdates.js';
58
+ export { resolveSnippets } from './resolveSnippets.js';
59
+ export { encodeContentAsHtml } from './encodeContentAsHtml.js';
60
+ export { UPDATE_MAX } from './constants.js';
package/dist/rss/index.js CHANGED
@@ -1,7 +1,6 @@
1
1
  import slugify from '@sindresorhus/slugify';
2
2
  import { stringifyTree } from '../mdx/index.js';
3
3
  import { getArrayExpressionStringProperties, getObjectExpressionStringProperty, } from '../mdx/utils.js';
4
- export const UPDATE_MAX = 15;
5
4
  export const isFrontmatter = (node) => {
6
5
  return (node === null || node === void 0 ? void 0 : node.type) === 'yaml';
7
6
  };
@@ -14,15 +13,49 @@ export const isHeading = (node) => {
14
13
  }
15
14
  return node.type === 'heading';
16
15
  };
16
+ // Standard HTML tags that should be included in RSS feeds
17
+ const STANDARD_HTML_TAGS = new Set([
18
+ 'table',
19
+ 'tr',
20
+ 'td',
21
+ 'th',
22
+ 'thead',
23
+ 'tbody',
24
+ 'tfoot',
25
+ 'p',
26
+ 'div',
27
+ 'span',
28
+ 'a',
29
+ 'br',
30
+ 'hr',
31
+ 'h1',
32
+ 'h2',
33
+ 'h3',
34
+ 'h4',
35
+ 'h5',
36
+ 'h6',
37
+ 'ul',
38
+ 'ol',
39
+ 'li',
40
+ 'strong',
41
+ 'em',
42
+ 'b',
43
+ 'i',
44
+ 'u',
45
+ 'img',
46
+ 'figure',
47
+ 'figcaption',
48
+ 'blockquote',
49
+ 'pre',
50
+ ]);
17
51
  export const isNormalMarkdown = (node) => {
18
52
  if (!node) {
19
53
  return false;
20
54
  }
21
- return (node.type !== 'mdxJsxFlowElement' &&
22
- node.type !== 'mdxJsxTextElement' &&
23
- node.type !== 'html' &&
24
- node.type !== 'code' &&
25
- node.type !== 'inlineCode');
55
+ if (node.type === 'mdxJsxFlowElement' || node.type === 'mdxJsxTextElement') {
56
+ return STANDARD_HTML_TAGS.has(node.name || '');
57
+ }
58
+ return node.type !== 'code' && node.type !== 'inlineCode';
26
59
  };
27
60
  export const containsUpdates = (tree) => {
28
61
  return tree.children.some((child) => isUpdate(child));
@@ -120,6 +153,42 @@ export const getMarkdownHeadingProps = (heading) => {
120
153
  }
121
154
  return { title, anchor };
122
155
  };
156
+ const jsxToHtml = (node) => {
157
+ const tagName = node.name || 'div';
158
+ const attrs = node.attributes
159
+ .filter((attr) => attr.type === 'mdxJsxAttribute' && typeof attr.value === 'string')
160
+ .map((attr) => {
161
+ if (attr.type === 'mdxJsxAttribute' && attr.name !== 'className') {
162
+ return `${attr.name}="${attr.value}"`;
163
+ }
164
+ return '';
165
+ })
166
+ .filter(Boolean)
167
+ .join(' ');
168
+ const attrsStr = attrs ? ` ${attrs}` : '';
169
+ const childrenStr = node.children
170
+ .map((child) => {
171
+ if (child.type === 'mdxJsxFlowElement' || child.type === 'mdxJsxTextElement') {
172
+ return jsxToHtml(child);
173
+ }
174
+ if (child.type === 'text') {
175
+ return child.value;
176
+ }
177
+ return stringifyTree({ type: 'root', children: [child] }).trim();
178
+ })
179
+ .join('');
180
+ return `<${tagName}${attrsStr}>${childrenStr}</${tagName}>`;
181
+ };
182
+ const stringifyTreeForRSS = (tree) => {
183
+ return tree.children
184
+ .map((child) => {
185
+ if (child.type === 'mdxJsxFlowElement' || child.type === 'mdxJsxTextElement') {
186
+ return jsxToHtml(child);
187
+ }
188
+ return stringifyTree({ type: 'root', children: [child] }).trim();
189
+ })
190
+ .join('\n\n');
191
+ };
123
192
  export const updateGroupToRSSItemV2 = ({ group, date, }) => {
124
193
  const dateToUse = date || new Date().toISOString();
125
194
  const heading = group[0];
@@ -128,7 +197,7 @@ export const updateGroupToRSSItemV2 = ({ group, date, }) => {
128
197
  }
129
198
  const { title, anchor } = getMarkdownHeadingProps(heading);
130
199
  const content = group.slice(1);
131
- const contentString = stringifyTree({
200
+ const contentString = stringifyTreeForRSS({
132
201
  type: 'root',
133
202
  children: content,
134
203
  });
@@ -155,13 +224,11 @@ export const getNewMarkdownUpdates = ({ newTree, previousTree, previousUpdates,
155
224
  const firstUpdateInNewTree = newTree.children.find(isUpdate);
156
225
  const firstUpdateInPreviousTree = previousTree.children.find(isUpdate);
157
226
  if (!isUpdate(firstUpdateInNewTree) || !isUpdate(firstUpdateInPreviousTree)) {
158
- // no last updates found to compare
159
227
  return [];
160
228
  }
161
229
  const firstUpdateTitleInNewTree = getUpdateTitle(firstUpdateInNewTree);
162
230
  const firstUpdateTitleInPreviousTree = getUpdateTitle(firstUpdateInPreviousTree);
163
231
  if (firstUpdateTitleInNewTree !== firstUpdateTitleInPreviousTree) {
164
- // last update component has changed
165
232
  return [];
166
233
  }
167
234
  const newUpdates = splitChildrenAtHeadings(firstUpdateInNewTree.children);
@@ -191,6 +258,7 @@ export const containsMarkdownHeading = (updateNode, title) => {
191
258
  child.children.some((child) => child.type === 'text' && child.value.includes(title)));
192
259
  return containsHeading;
193
260
  };
261
+ // soon to be deprecated, use processUpdateNode.ts instead
194
262
  export const processUpdateNode = ({ updateNode, date, }) => {
195
263
  const updates = [];
196
264
  const nowOrOldDate = date || new Date().toISOString();
@@ -230,7 +298,7 @@ export const processUpdatePerNode = ({ updateNode, date, title, description, })
230
298
  const anchor = slugify(label);
231
299
  const categories = getTags(updateNode);
232
300
  const contentNodes = updateNode.children.filter((child) => isNormalMarkdown(child));
233
- const contentString = stringifyTree({
301
+ const contentString = stringifyTreeForRSS({
234
302
  type: 'root',
235
303
  children: contentNodes,
236
304
  });
@@ -256,3 +324,11 @@ export const processUpdatePerMarkdownGroup = ({ updateNode, date, updatesByMarkd
256
324
  }
257
325
  return updates;
258
326
  };
327
+ export const isRSSFileV4 = (rssFile) => {
328
+ return rssFile.version === 'v4';
329
+ };
330
+ export { enhanceSnippetsWithImportNames } from './enhanceSnippetsWithImportNames.js';
331
+ export { getV4FeedUpdates } from './getV4FeedUpdates.js';
332
+ export { resolveSnippets } from './resolveSnippets.js';
333
+ export { encodeContentAsHtml } from './encodeContentAsHtml.js';
334
+ export { UPDATE_MAX } from './constants.js';
@@ -0,0 +1,6 @@
1
+ import { RSSItemV2 } from '../types/rss.js';
2
+ import { UpdateMDXComponent } from './index.js';
3
+ export declare const processUpdateNode: ({ updateNode, date, }: {
4
+ updateNode: UpdateMDXComponent;
5
+ date: string;
6
+ }) => RSSItemV2[];
@@ -0,0 +1,10 @@
1
+ import { getRssPropsData, processUpdatePerNode } from './index.js';
2
+ export const processUpdateNode = ({ updateNode, date, }) => {
3
+ const { rssTitle, rssDescription } = getRssPropsData(updateNode);
4
+ return processUpdatePerNode({
5
+ updateNode,
6
+ date,
7
+ title: rssTitle,
8
+ description: rssDescription,
9
+ });
10
+ };
@@ -0,0 +1,3 @@
1
+ import type { Root } from 'mdast';
2
+ import { RssSnippetFile } from '../types/rss.js';
3
+ export declare const resolveSnippets: (ast: Root, snippets: RssSnippetFile[]) => Root;
@@ -0,0 +1,60 @@
1
+ import { getAST } from '../mdx/remark.js';
2
+ export const resolveSnippets = (ast, snippets) => {
3
+ const snippetTreeMap = buildSnippetTreeMap(snippets);
4
+ const newChildren = [];
5
+ for (const child of ast.children) {
6
+ if (child.type === 'mdxJsxFlowElement' && 'name' in child && child.name) {
7
+ const componentName = String(child.name);
8
+ if (componentName === 'Snippet' && 'attributes' in child) {
9
+ const fileAttr = child.attributes.find((attr) => attr.type === 'mdxJsxAttribute' && attr.name === 'file');
10
+ if (fileAttr && fileAttr.value) {
11
+ const fileName = typeof fileAttr.value === 'string' ? fileAttr.value : fileAttr.value.value;
12
+ if (snippetTreeMap[fileName]) {
13
+ const snippetAst = snippetTreeMap[fileName];
14
+ newChildren.push(...snippetAst.children);
15
+ continue;
16
+ }
17
+ const fileNameOnly = fileName.split('/').pop();
18
+ if (fileNameOnly && snippetTreeMap[fileNameOnly]) {
19
+ const snippetAst = snippetTreeMap[fileNameOnly];
20
+ newChildren.push(...snippetAst.children);
21
+ continue;
22
+ }
23
+ }
24
+ }
25
+ if (snippetTreeMap[componentName]) {
26
+ const snippetAst = snippetTreeMap[componentName];
27
+ newChildren.push(...snippetAst.children);
28
+ continue;
29
+ }
30
+ newChildren.push(child);
31
+ }
32
+ else {
33
+ newChildren.push(child);
34
+ }
35
+ }
36
+ ast.children = newChildren;
37
+ return ast;
38
+ };
39
+ const buildSnippetTreeMap = (snippets) => {
40
+ const map = {};
41
+ for (const snippet of snippets) {
42
+ try {
43
+ const snippetAst = getAST(snippet.content);
44
+ if ('importNames' in snippet && snippet.importNames && snippet.importNames.length > 0) {
45
+ for (const importName of snippet.importNames) {
46
+ map[importName] = snippetAst;
47
+ }
48
+ }
49
+ map[snippet.path] = snippetAst;
50
+ const filename = snippet.path.split('/').pop();
51
+ if (filename && filename !== snippet.path) {
52
+ map[filename] = snippetAst;
53
+ }
54
+ }
55
+ catch (error) {
56
+ console.error('Failed to parse snippet:', snippet.path, error);
57
+ }
58
+ }
59
+ return map;
60
+ };