Skip to content

Commit

Permalink
feat(cli): add Readme importer changelog, fix missing imported slugs …
Browse files Browse the repository at this point in the history
…& callout components. (#5702)

* Fix missing slugs and malformed callouts.

* chore: update changelog

* Improve versions changelog.

* chore: update changelog

---------

Co-authored-by: fern-support <[email protected]>
  • Loading branch information
eyw520 and fern-support authored Jan 22, 2025
1 parent 89b06f2 commit 1dc859f
Show file tree
Hide file tree
Showing 9 changed files with 62 additions and 73 deletions.
1 change: 1 addition & 0 deletions .github/CODEOWNERS
Original file line number Diff line number Diff line change
Expand Up @@ -23,5 +23,6 @@ generators/openapi/* @dsinghvi
# Specific rules for packages
packages/cli/* @amckinney
packages/cli/api-importers/* @eyw520
packages/cli/docs-importers/* @eyw520
packages/generators/cli/* @dsinghvi
packages/generators/docker/* @dsinghvi
14 changes: 14 additions & 0 deletions packages/cli/cli/versions.yml
Original file line number Diff line number Diff line change
@@ -1,3 +1,16 @@
- changelogEntry:
- summary: |
The CLI now supports a --readme flag pointing to the URL of a Readme generated docs site and
migrates existing documentation to a fern-compatible repository.
To use this feature:
```bash
fern init --readme https://url-to-readme-docs.com
```
type: feat
irVersion: 55
version: 0.51.0

- changelogEntry:
- summary: |
Improve performance of `fern docs dev` by only reloading the markdown content when
Expand All @@ -7,6 +20,7 @@
irVersion: 55
version: 0.50.17


- changelogEntry:
- summary: |
Improve performance of `fern docs dev` by debouncing across edits to multiple files,
Expand Down
56 changes: 30 additions & 26 deletions packages/cli/docs-importers/readme/src/ReadmeImporter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -196,20 +196,18 @@ export class ReadmeImporter extends DocsImporter<ReadmeImporter.Args> {

const urlObj = new URL(url);
const origin = urlObj.origin;

if (origin === "") {
return { success: false, message: `Invalid URL: ${url}` };
}
const sidebar = retrieveRootNavElement(siteHast);
if (!sidebar) {
return { success: false, message: `${url.toString()}: Failed to find sidebar element` };
}
const navItems = parseSidebar(sidebar);
if (origin === "") {
return { success: false, message: `invalid URL provided to scrape site: ${url}` };
}

const flatNavItems = navItems.flatMap((section) => section.pages);
const listOfLinks = iterateOverNavItems(flatNavItems, origin);
if (listOfLinks.length === 0) {
return { success: false, message: `no navigation links were able to be found: ${url}` };
return { success: false, message: `No navigation items found for URL: ${url}` };
}

const externalLinks = listOfLinks.filter((url: URL) => url.origin !== origin);
Expand Down Expand Up @@ -253,27 +251,33 @@ export class ReadmeImporter extends DocsImporter<ReadmeImporter.Args> {
return value;
};

traverse(navItems).forEach(function (value) {
if (
externalLinkReplaceMap.has(value) ||
(Array.isArray(value) && value.some((item) => externalLinkReplaceMap.has(item)))
) {
this.update(replaceLinks(value, externalLinkReplaceMap));
} else if (
rootPathReplaceMap.has(value) ||
(Array.isArray(value) && value.some((item) => rootPathReplaceMap.has(item)))
) {
this.update(replaceLinks(value, rootPathReplaceMap));
}
});
for (const section of navItems) {
traverse(section.pages).forEach(function (value) {
if (
externalLinkReplaceMap.has(value) ||
(Array.isArray(value) && value.some((item) => externalLinkReplaceMap.has(item)))
) {
this.update(replaceLinks(value, externalLinkReplaceMap));
} else if (
rootPathReplaceMap.has(value) ||
(Array.isArray(value) && value.some((item) => rootPathReplaceMap.has(item)))
) {
this.update(replaceLinks(value, rootPathReplaceMap));
}
});
}

traverse(navItems).forEach(function (value) {
if (typeof value === "string") {
this.update(value.replace("/overview", ""));
} else if (Array.isArray(value)) {
this.update(value.map((item) => (typeof item === "string" ? item.replace("/overview", "") : item)));
}
});
for (const section of navItems) {
traverse(section.pages).forEach(function (value) {
if (typeof value === "string") {
this.update(value.replace("/overview", ""));
} else if (Array.isArray(value)) {
this.update(
value.map((item) => (typeof item === "string" ? item.replace("/overview", "") : item))
);
}
});
}

const failedPaths = [...extResults, ...intResults, ...rootResults]
.filter((r) => !r.success)
Expand Down
37 changes: 4 additions & 33 deletions packages/cli/docs-importers/readme/src/components/Callout.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import type { Element, ElementContent } from "hast";

import { assertIsStringArray } from "../assert";
import { convertHastChildrenToMdast } from "../customComponents/children";
import type { HastNode, HastNodeIndex, HastNodeParent } from "../types/hastTypes";

export function scrapeCallout(node: HastNode, _: HastNodeIndex, __: HastNodeParent): Element | undefined {
Expand Down Expand Up @@ -35,43 +36,13 @@ export function scrapeCallout(node: HastNode, _: HastNodeIndex, __: HastNodePare
break;
}

let icon: string | undefined;
for (const child of node.children) {
if (
child.type === "element" &&
child.tagName === "h2" &&
Array.isArray(child.properties?.className) &&
child.properties?.className.includes("callout-heading")
) {
const iconSpan = child.children?.find(
(c) =>
c.type === "element" &&
c.tagName === "span" &&
Array.isArray(c.properties?.className) &&
c.properties?.className.includes("callout-icon")
);
if (iconSpan && iconSpan.type === "element" && iconSpan.children?.[0] && "value" in iconSpan.children[0]) {
icon = String(iconSpan.children[0].value);
}
break;
}
}
const textChildren = node.children.filter((child) => child.type === "element" && child.tagName === "p");

const newNode: Element = {
type: "element",
tagName,
properties: {
...(icon && { icon })
},
children: node.children.filter(
(child) =>
!(
child.type === "element" &&
child.tagName === "h2" &&
Array.isArray(child.properties?.className) &&
child.properties?.className.includes("callout-heading")
)
) as Array<ElementContent>
properties: {},
children: convertHastChildrenToMdast(textChildren) as Array<ElementContent>
};

return newNode;
Expand Down
3 changes: 2 additions & 1 deletion packages/cli/docs-importers/readme/src/components/Card.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import type { Element, ElementContent } from "hast";
import { CONTINUE, EXIT, visit } from "unist-util-visit";

import { assertIsDefined } from "../assert";
import { convertHastChildrenToMdast } from "../customComponents/children";
import { findTitle } from "../extract/title";
import type { HastNode, HastNodeIndex, HastNodeParent } from "../types/hastTypes";

Expand Down Expand Up @@ -52,7 +53,7 @@ export function scrapeCard(node: HastNode, _: HastNodeIndex, parent: HastNodePar
title,
href
},
children: node.children as Array<ElementContent>
children: convertHastChildrenToMdast(node.children as Array<Element>) as Array<ElementContent>
};

return newNode;
Expand Down
10 changes: 6 additions & 4 deletions packages/cli/docs-importers/readme/src/parse/parsePage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ import remarkMdx from "remark-mdx";
import remarkStringify from "remark-stringify";
import { unified } from "unified";

import { TaskContext } from "@fern-api/task-context";

import { unifiedRemoveBreaks } from "../cleaners/breaks";
import { unifiedRemoveClassNames } from "../cleaners/className";
import { remarkRemoveEmptyEmphases } from "../cleaners/emptyEmphasis";
Expand Down Expand Up @@ -41,6 +43,7 @@ import { htmlToHast } from "../utils/hast";
import { normalizePath, removeTrailingSlash } from "../utils/strings";

export async function parsePage(
context: TaskContext,
html: string,
url: string | URL,
opts: {
Expand Down Expand Up @@ -115,14 +118,13 @@ export async function parsePage(
}

writePage(url, title, description, resultStr);
context.logger.debug(`Successfully parsed page ${urlStr}`);
return {
success: true,
data: opts.rootPath ? [normalizePath(new URL(urlStr).pathname), opts.rootPath] : undefined
};
} catch (error) {
return {
success: false,
data: [urlStr, ""]
};
context.logger.debug(`Error parsing page ${urlStr}: ${error}`);
return { success: false, data: [urlStr, ""] };
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -28,11 +28,11 @@ export async function parsePageGroup(
try {
if (opts.externalLinks) {
taskContext.logger.debug(`Scraping external link with URL: ${url}...`);
return parsePage(`external-link-${index}`, url, { externalLink: true });
return parsePage(taskContext, `external-link-${index}`, url, { externalLink: true });
} else {
const html = await fetchPageHtml({ url });
taskContext.logger.debug(`Scraping internal link with URL: ${url}...`);
return parsePage(html, url, {
return parsePage(taskContext, html, url, {
externalLink: false,
rootPath: opts.rootPaths ? opts.rootPaths[index] : undefined
});
Expand Down
6 changes: 1 addition & 5 deletions packages/cli/docs-importers/readme/src/parse/parseSidebar.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import { getFirstChild } from "../extract/firstChild";
import { getText } from "../extract/text";
import { findTitle } from "../extract/title";
import { scrapedNavigationEntry, scrapedNavigationSection } from "../types/scrapedNavigation";
import { removeTrailingSlash } from "../utils/strings";

export function parseSidebar(rootNode: Element): Array<scrapedNavigationSection> {
const result: Array<scrapedNavigationSection> = [];
Expand Down Expand Up @@ -154,10 +153,7 @@ export function parseListItem({
}

const childEntries = parseNavItems(childList);
const newLink =
linkHref !== "#" && childEntries.find((child) => typeof child === "string" && child.startsWith(linkHref))
? removeTrailingSlash(linkHref) + "/overview"
: linkHref;
const newLink = linkHref;

if (linkHref !== "#") {
if (childEntries.includes(linkHref)) {
Expand Down
4 changes: 2 additions & 2 deletions packages/cli/docs-importers/readme/src/utils/network.ts
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@ export async function fetchPageHtml({ url, browser }: { url: string | URL; brows
}
throw new Error("An unknown error occured.");
} catch (error) {
throw new Error("Error retrieving HTML for ${url.toString()}");
throw new Error(`Error retrieving HTML for ${url.toString()}`);
}
}

Expand All @@ -108,6 +108,6 @@ export async function fetchImage(url: string): Promise<NodeJS.TypedArray> {

return imageData;
} catch (error) {
throw new Error(`${url} - failed to retrieve image from source`);
throw new Error(`Failed to retrieve image from source url ${url}`);
}
}

0 comments on commit 1dc859f

Please sign in to comment.