diff --git a/.pnp.cjs b/.pnp.cjs index a56ec1e6afe..9a6a2f74833 100644 --- a/.pnp.cjs +++ b/.pnp.cjs @@ -6627,6 +6627,7 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) { "packageLocation": "./packages/cli/docs-markdown-utils/",\ "packageDependencies": [\ ["@fern-api/docs-markdown-utils", "workspace:packages/cli/docs-markdown-utils"],\ + ["@fern-api/fdr-sdk", "npm:0.98.16-3955e989a"],\ ["@fern-api/fs-utils", "workspace:packages/commons/fs-utils"],\ ["@fern-api/task-context", "workspace:packages/cli/task-context"],\ ["@types/diff", "npm:5.2.1"],\ diff --git a/packages/cli/docs-markdown-utils/package.json b/packages/cli/docs-markdown-utils/package.json index 737319528e1..d2aeb4e481f 100644 --- a/packages/cli/docs-markdown-utils/package.json +++ b/packages/cli/docs-markdown-utils/package.json @@ -27,6 +27,7 @@ "depcheck": "depcheck" }, "dependencies": { + "@fern-api/fdr-sdk": "0.98.16-3955e989a", "@fern-api/fs-utils": "workspace:*", "@fern-api/task-context": "workspace:*", "gray-matter": "^4.0.3", diff --git a/packages/cli/docs-markdown-utils/src/__test__/parseImagePaths.test.ts b/packages/cli/docs-markdown-utils/src/__test__/parseImagePaths.test.ts index f79b3ad83ad..92e69f88611 100644 --- a/packages/cli/docs-markdown-utils/src/__test__/parseImagePaths.test.ts +++ b/packages/cli/docs-markdown-utils/src/__test__/parseImagePaths.test.ts @@ -101,6 +101,52 @@ describe("parseImagePaths", () => { `); }); + it("should parse url from frontmatter json", () => { + const page = '---\nimage: { type: "url", value: "https://someurl.com" }\n---'; + const result = parseImagePaths(page, PATHS); + expect(result.filepaths).toEqual([]); + expect(result.markdown.trim()).toEqual("---\nimage:\n type: url\n value: 'https://someurl.com'\n---"); + }); + + it("should parse url from frontmatter yaml", () => { + const page = '---\nimage:\n type: url\n value: "https://someurl.com"\n---'; + const result = parseImagePaths(page, PATHS); + expect(result.filepaths).toEqual([]); + expect(result.markdown.trim()).toEqual("---\nimage:\n type: url\n value: 'https://someurl.com'\n---"); + }); + + it("should parse url from frontmatter text", () => { + const page = '---\nimage: "https://someurl.com"\n---'; + const result = parseImagePaths(page, PATHS); + expect(result.filepaths).toEqual([]); + expect(result.markdown.trim()).toEqual("---\nimage:\n type: url\n value: 'https://someurl.com'\n---"); + }); + + it("should parse images from frontmatter text", () => { + const page = '---\nimage: "path/to/image.png"\n---'; + const result = parseImagePaths(page, PATHS); + expect(result.filepaths).toEqual(["/Volume/git/fern/my/docs/folder/path/to/image.png"]); + expect(result.markdown.trim()).toEqual( + "---\nimage:\n type: fileId\n value: /Volume/git/fern/my/docs/folder/path/to/image.png\n---" + ); + }); + + it("should parse og:images from frontmatter text", () => { + const page = '---\nog:image: "path/to/image.png"\n---'; + const result = parseImagePaths(page, PATHS); + expect(result.filepaths).toEqual(["/Volume/git/fern/my/docs/folder/path/to/image.png"]); + expect(result.markdown.trim()).toEqual( + "---\n'og:image':\n type: fileId\n value: /Volume/git/fern/my/docs/folder/path/to/image.png\n---" + ); + }); + + it("should parse the same result when run twice for frontmatter text", () => { + const page = '---\nimage: "path/to/image.png"\n---'; + const result = parseImagePaths(page, PATHS); + const result2 = parseImagePaths(page, PATHS); + expect(result.markdown).toEqual(result.markdown); + }); + it("should parse image with alt on multiple lines", () => { const page = "This is a test page with an image ![image with \n new line in alt](path/to/image.png)"; const result = parseImagePaths(page, PATHS); diff --git a/packages/cli/docs-markdown-utils/src/parseImagePaths.ts b/packages/cli/docs-markdown-utils/src/parseImagePaths.ts index 56707ec5a20..fb778b7289f 100644 --- a/packages/cli/docs-markdown-utils/src/parseImagePaths.ts +++ b/packages/cli/docs-markdown-utils/src/parseImagePaths.ts @@ -1,3 +1,4 @@ +import { DocsV1Write } from "@fern-api/fdr-sdk"; import { AbsoluteFilePath, dirname, relative, RelativeFilePath, resolve } from "@fern-api/fs-utils"; import { TaskContext } from "@fern-api/task-context"; import grayMatter from "gray-matter"; @@ -30,11 +31,23 @@ export function parseImagePaths( filepaths: AbsoluteFilePath[]; markdown: string; } { - const { content, data } = grayMatter(markdown); + // Don't remove {}! https://github.com/jonschlinkert/gray-matter/issues/43#issuecomment-318258919 + const { content, data } = grayMatter(markdown, {}); let replacedContent = content; const filepaths = new Set(); + function mapImage(image: string | undefined) { + const resolvedPath = resolvePath(image, metadata); + if (resolvedPath != null) { + filepaths.add(resolvedPath); + return resolvedPath; + } + return; + } + + visitFrontmatterImages(data, ["image", "og:image", "og:logo", "twitter:image"], mapImage); + const tree = fromMarkdown(content, { extensions: [mdx()], mdastExtensions: [mdxFromMarkdown()] @@ -118,7 +131,7 @@ export function parseImagePaths( } } - if (replaced === original) { + if (replaced === original && filepaths.size === 0) { return; } @@ -169,7 +182,7 @@ export function replaceImagePathsAndUrls( metadata: AbsolutePathMetadata, context: TaskContext ): string { - const { content, data } = grayMatter(markdown); + const { content, data } = grayMatter(markdown, {}); let replacedContent = content; const tree = fromMarkdown(content, { @@ -179,6 +192,23 @@ export function replaceImagePathsAndUrls( let offset = 0; + function mapImage(image: string | undefined) { + if (image != null && !isExternalUrl(image) && !isDataUrl(image)) { + try { + const fileId = fileIdsMap.get(AbsoluteFilePath.of(image)); + if (fileId != null) { + return `file:${fileId}`; + } + } catch (e) { + // do nothing + return; + } + } + return; + } + + visitFrontmatterImages(data, ["image", "og:image", "og:logo", "twitter:image"], mapImage); + visit(tree, (node) => { if (node.position == null) { return; @@ -188,15 +218,9 @@ export function replaceImagePathsAndUrls( let replaced = original; function replaceSrc(src: string | undefined) { - if (src != null && !isExternalUrl(src) && !isDataUrl(src)) { - try { - const fileId = fileIdsMap.get(AbsoluteFilePath.of(src)); - if (fileId != null) { - replaced = replaced.replace(src, `file:${fileId}`); - } - } catch (e) { - // do nothing - } + const imageSrc = mapImage(src); + if (src && imageSrc) { + replaced = replaced.replace(src, imageSrc); } } @@ -347,3 +371,36 @@ function trimAnchor(text: unknown): string | undefined { } return text.replace(/#.*$/, ""); } + +function visitFrontmatterImages( + data: Record, + keys: string[], + mapImage: (image: string | undefined) => string | undefined +) { + for (const key of keys) { + const value = data[key]; + if (value != null) { + // realtime validation, this also assumes there can be other stuff in the object, but we only care about the valid keys + if (typeof value === "object") { + if (value.type === "fileId") { + data[key] = { + ...value, + value: mapImage(value.value) ?? value.value + }; + } + } else if (typeof value === "string") { + const mappedImage = mapImage(value); + data[key] = mappedImage + ? { + type: "fileId", + value: mappedImage + } + : { + type: "url", + value + }; + } + // else do nothing + } + } +} diff --git a/yarn.lock b/yarn.lock index 5872b3edf1b..5c4fbc31b48 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3563,6 +3563,7 @@ __metadata: version: 0.0.0-use.local resolution: "@fern-api/docs-markdown-utils@workspace:packages/cli/docs-markdown-utils" dependencies: + "@fern-api/fdr-sdk": 0.98.16-3955e989a "@fern-api/fs-utils": "workspace:*" "@fern-api/task-context": "workspace:*" "@types/diff": ^5.2.1