From 9a2f76ff3d52884addeb7300fa1a5189745744f3 Mon Sep 17 00:00:00 2001 From: Paul Gottschling Date: Mon, 28 Nov 2022 17:41:13 -0500 Subject: [PATCH] Allow more paths in partial link resolution (#180) Fixes #167 `handlePartialLink` currently hardcodes `docs/pages` as the location of all docs pages, and uses this file path segment to construct the absolute path of the MDX file that includes a partial. This change adds the root directory of all partials as a parameter of `handlePartialLink` and uses that instead. Since this root directory is configurable outside `remark-includes`, this change lets us add unit tests for `handlePartialLink`. This change also handles relative path resolution for image and link definition URLs. The current version of `handlePartialLink` only handles links paths. As a result, this renames `handlePartialLink` to `handleURLPath`. --- .../database-access/attach-iam-policies.mdx | 11 ++ .../includes/include-relative-link.mdx | 6 + .../includes/includes-relative-link-def.mdx | 3 + server/remark-includes.ts | 45 ++++++-- uvu-tests/remark-includes.test.ts | 107 ++++++++++++++++++ 5 files changed, 160 insertions(+), 12 deletions(-) create mode 100644 server/fixtures/includes/database-access/attach-iam-policies.mdx create mode 100644 server/fixtures/includes/include-relative-link.mdx create mode 100644 server/fixtures/includes/includes-relative-link-def.mdx diff --git a/server/fixtures/includes/database-access/attach-iam-policies.mdx b/server/fixtures/includes/database-access/attach-iam-policies.mdx new file mode 100644 index 0000000000000..df5fc4bb2af3a --- /dev/null +++ b/server/fixtures/includes/database-access/attach-iam-policies.mdx @@ -0,0 +1,11 @@ +Attach the policy and permission boundary you created earlier to the IAM +identity your Teleport Database Service will be using. + +For example, if the Database Service runs as an IAM user, go to the page of the IAM user +in the AWS Management Console, attach the created policy in the "Permissions +policies" section, and set the created boundary policy in the "Permissions +boundary" section. + +
+![IAM user](../../../img/database-access/iam@2x.png) +
diff --git a/server/fixtures/includes/include-relative-link.mdx b/server/fixtures/includes/include-relative-link.mdx new file mode 100644 index 0000000000000..490a0e090f83f --- /dev/null +++ b/server/fixtures/includes/include-relative-link.mdx @@ -0,0 +1,6 @@ + +Check out our [instructions](../installation.mdx). + +Here is an image showing a successful installation: + +[Successful installation](../installation.png) diff --git a/server/fixtures/includes/includes-relative-link-def.mdx b/server/fixtures/includes/includes-relative-link-def.mdx new file mode 100644 index 0000000000000..5bd90a993ea5e --- /dev/null +++ b/server/fixtures/includes/includes-relative-link-def.mdx @@ -0,0 +1,3 @@ +This partial has a relative link [definition]. + +[definition]: ../../installation.mdx diff --git a/server/remark-includes.ts b/server/remark-includes.ts index 5deaf517d5e9b..97d6c5e615cb9 100644 --- a/server/remark-includes.ts +++ b/server/remark-includes.ts @@ -12,7 +12,7 @@ import type { Parent } from "unist"; import type { Content, Code, Text } from "mdast"; import type { VFile } from "vfile"; import type { Node } from "mdast-util-from-markdown/lib"; -import { dirname, join, relative } from "path"; +import { basename, dirname, join, relative, resolve } from "path"; import { existsSync, readFileSync } from "fs"; import { visitParents } from "unist-util-visit-parents"; @@ -273,25 +273,46 @@ const isInclude = (node: Code | Text): node is Code | Text => * and without: * docs/image.jpg */ -const handlePartialLink = (node: Node, path: string, mdxPath: string) => { - if (node.type === "link") { +const handleURLPath = ( + node: Node, + rootDir: string, + path: string, + mdxPath: string +) => { + if ( + node.type === "image" || + node.type === "link" || + node.type === "definition" + ) { const href = node.url; + // Ignore non-strings, absolute paths, or web URLs if (typeof href !== "string" || href[0] === "/" || /^http/.test(href)) { return href; } - // root where all documentation pages store - const absStart = "docs/pages"; - // find an "abs" (starting with root) directory path of the file in which the partial doc was inserted - const absMdxPath = dirname(absStart + mdxPath.split(absStart).pop()); - const absTargetPath = join(dirname(path), href); - // make the reference path relative to the place where the partial doc was inserted - node.url = relative(absMdxPath, absTargetPath); + + // Find the absolute path of the file that includes the partial + const absMdxPath = resolve(mdxPath); + // Construct an absolute path out of the root directory for all partials, + // the directory containing the partial (within the root directory for all + // partials) and the relative path to the target asset, e.g., + // "docs/pages/includes", "kubernetes", and + // "../../target.png". + const absTargetPath = resolve(rootDir, dirname(path), href); + // Make the reference path relative to the place where the partial doc was + // inserted. + node.url = relative( + // relative() counts all path segments, even the file itself, when + // comparing path segments between the "from" and "to" paths, so we + // start from the directory containing the file that includes the partial. + dirname(absMdxPath), + absTargetPath + ); } if ("children" in node) { node.children?.forEach?.((child) => - handlePartialLink(child, path, mdxPath) + handleURLPath(child, rootDir, path, mdxPath) ); } }; @@ -361,7 +382,7 @@ export default function remarkIncludes({ ], }); - handlePartialLink(tree, path, vfile.path); + handleURLPath(tree, resolvedRootDir, path, vfile.path); const grandParent = ancestors[ancestors.length - 2] as Parent; const parentIndex = grandParent.children.indexOf(parent); diff --git a/uvu-tests/remark-includes.test.ts b/uvu-tests/remark-includes.test.ts index c8d48e1742190..8ef9145201186 100644 --- a/uvu-tests/remark-includes.test.ts +++ b/uvu-tests/remark-includes.test.ts @@ -483,4 +483,111 @@ Suite("Resolves template variables in includes", () => { assert.equal(result, expected); }); +Suite( + "Resolves relative links in partials based on the path of the partial", + () => { + const includingRelativeLink = `Here are instructions on installing the software: + +(!include-relative-link.mdx!) +`; + interface testCase { + includingPage: string; + description: string; + path: string; + expected: string; + } + const testCases: testCase[] = [ + { + includingPage: includingRelativeLink, + description: "including file is on the same dir level as the partial", + path: "server/fixtures/dir/samelevel.mdx", + expected: `Here are instructions on installing the software: + +Check out our [instructions](../installation.mdx). + +Here is an image showing a successful installation: + +[Successful installation](../installation.png) +`, + }, + { + includingPage: includingRelativeLink, + description: "including file is below the dir level of the partial", + path: "server/fixtures/dir/dir2/below.mdx", + expected: `Here are instructions on installing the software: + +Check out our [instructions](../../installation.mdx). + +Here is an image showing a successful installation: + +[Successful installation](../../installation.png) +`, + }, + { + includingPage: includingRelativeLink, + description: "including file is above the dir level of the partial", + path: "server/fixtures/above.mdx", + expected: `Here are instructions on installing the software: + +Check out our [instructions](installation.mdx). + +Here is an image showing a successful installation: + +[Successful installation](installation.png) +`, + }, + { + includingPage: `Here's how to attach an IAM policy for DB Access: + +(!database-access/attach-iam-policies.mdx!) +`, + description: "relative image path", + path: "server/fixtures/includes/db-policy.mdx", + expected: `Here's how to attach an IAM policy for DB Access: + +Attach the policy and permission boundary you created earlier to the IAM +identity your Teleport Database Service will be using. + +For example, if the Database Service runs as an IAM user, go to the page of the IAM user +in the AWS Management Console, attach the created policy in the "Permissions +policies" section, and set the created boundary policy in the "Permissions +boundary" section. + +
+ ![IAM user](../../img/database-access/iam@2x.png) +
+`, + }, + { + includingPage: "(!includes-relative-link-def.mdx!)", + description: "relative definition path", + path: "server/fixtures/definition.mdx", + expected: `This partial has a relative link [definition]. + +[definition]: ../installation.mdx +`, + }, + ]; + + for (const testCase of testCases) { + const actual = transformer({ + value: testCase.includingPage, + path: testCase.path, + }).toString(); + + assert.equal( + actual, + testCase.expected, + new Error( + `${testCase.description}: expected the output:\n` + + testCase.expected + + "\n\n" + + "but got:\n" + + actual + ) + ); + } + } +); + Suite.run();