From af24806d323074e0c88ffcba1c2919c78003241c Mon Sep 17 00:00:00 2001 From: Paul Gottschling Date: Mon, 8 Jul 2024 14:10:54 -0400 Subject: [PATCH] Progress --- server/remark-toc.ts | 36 ++++++--- uvu-tests/remark-toc.test.ts | 138 +++++++++++++++++------------------ 2 files changed, 94 insertions(+), 80 deletions(-) diff --git a/server/remark-toc.ts b/server/remark-toc.ts index f8b988fb6e..6470d4dde3 100644 --- a/server/remark-toc.ts +++ b/server/remark-toc.ts @@ -1,5 +1,6 @@ import * as nodeFS from "fs"; import * as path from "path"; +import "process"; import matter from "gray-matter"; import { visitParents } from "unist-util-visit-parents"; import { fromMarkdown } from "mdast-util-from-markdown"; @@ -27,8 +28,9 @@ export const getTOC = (dirPath: string, fs = nodeFS) => { const parts = path.parse(dirPath); const tocIntro = path.join(parts.dir, parts.name, parts.name + ".mdx"); if (!fs.existsSync(tocIntro)) { - console.log("bad stuff!"); - throw `There must be a page called ${tocIntro} to introduce ${dirPath}".`; + return { + error: `There must be a page called ${tocIntro} to introduce ${dirPath}".`, + }; } const files = fs.readdirSync(dirPath, "utf8"); @@ -68,9 +70,9 @@ export const getTOC = (dirPath: string, fs = nodeFS) => { dirs.forEach((f, idx) => { const menuPath = path.join(f, path.parse(f).base + ".mdx"); if (!fs.existsSync(menuPath)) { - throw new Error( - `there must be a page called ${menuPath} that introduces ${f}` - ); + return { + error: `there must be a page called ${menuPath} that introduces ${f}`, + }; } const text = fs.readFileSync(menuPath, "utf8"); let relPath = relativePathToFile(dirPath, menuPath); @@ -81,13 +83,26 @@ export const getTOC = (dirPath: string, fs = nodeFS) => { ); }); entries.sort(); - return entries.join("\n"); + return { result: entries.join("\n") }; }; const tocRegexpPattern = "^\\(!toc ([^!]+)!\\)$"; -export default function remarkTOC(): Transformer { +export interface RemarkTOCOptions { + rootDir?: string | ((vfile: VFile) => string); +} + +export default function remarkTOC({ + rootDir = "", +}: RemarkTOCOptions): Transformer { return (root: Content, vfile: VFile) => { + let resolvedRootDir: string; + if (typeof rootDir === "function") { + resolvedRootDir = rootDir(vfile); + } else { + resolvedRootDir = rootDir; + } + const lastErrorIndex = vfile.messages.length; visitParents(root, (node, ancestors: Parent[]) => { @@ -108,8 +123,11 @@ export default function remarkTOC(): Transformer { return; } - // TODO: Don't throw errors. Add messages instead. - const result = getTOC(tocExpr[1]); + const { result, error } = getTOC(path.join(resolvedRootDir, tocExpr[1])); + if (!!error) { + vfile.message(error, node); + return; + } const tree = fromMarkdown(result, { extensions: [mdxjs(), gfm(), frontmatter()], mdastExtensions: [ diff --git a/uvu-tests/remark-toc.test.ts b/uvu-tests/remark-toc.test.ts index 27b5eb0ceb..a11fc54a4f 100644 --- a/uvu-tests/remark-toc.test.ts +++ b/uvu-tests/remark-toc.test.ts @@ -11,41 +11,6 @@ import { remark } from "remark"; const Suite = suite("server/remark-toc"); -const transformer = (vfileOptions: VFileOptions) => { - const file = new VFile(vfileOptions); - - return remark() - .use(remarkMdx) - .use(remarkGFM) - .use(remarkTOC) - .processSync(file); -}; - -Suite("replaces inclusion expressions", () => { - console.log("blah"); - const value = readFileSync( - resolve("server/fixtures/toc/source.mdx"), - "utf-8" - ); - - console.log("value:", value); - - const result = transformer({ - value, - path: "/content/4.0/docs/pages/filename.mdx", - }).toString(); - - console.log("result:", result); - - const expected = readFileSync( - resolve("server/fixtures/toc/expected.mdx"), - "utf-8" - ); - - console.log(result); - assert.equal(result, expected); -}); - const testFilesTwoSections = { "/docs/docs.mdx": `--- title: "Documentation Home" @@ -118,7 +83,7 @@ description: "Protecting App 2 with Teleport" }); const fs = createFsFromVolume(vol); const actual = getTOC("/docs/", fs); - assert.equal(actual, expected); + assert.equal(actual.result, expected); }); Suite("getTOC with multiple links to directories", () => { @@ -128,43 +93,42 @@ Suite("getTOC with multiple links to directories", () => { const vol = Volume.fromJSON(testFilesTwoSections); const fs = createFsFromVolume(vol); const actual = getTOC("/docs/", fs); - assert.equal(actual, expected); + assert.equal(actual.result, expected); }); -// Suite( -// `getTOC throws an error on a generated menu page that does not correspond to a subdirectory`, -// () => { -// const vol = Volume.fromJSON({ -// "/docs/docs.mdx": `--- -// title: "Documentation Home" -// description: "Guides to setting up the product." -// --- -// -// `, -// "/docs/application-access/application-access.mdx": `--- -// title: "Application Access" -// description: "Guides related to Application Access" -// --- -// -// `, -// "/docs/application-access/page1.mdx": `--- -// title: "Application Access Page 1" -// description: "Protecting App 1 with Teleport" -// ---`, -// "/docs/jwt.mdx": `--- -// title: "JWT guides" -// description: "Guides related to JWTs" -// --- -// -// `, -// }); -// -// const fs = createFsFromVolume(vol); -// assert.throws(() => { -// const actual = getTOC("/docs/", fs); -// }, "jwt.mdx"); -// } -// ); +Suite( + `getTOC returns an error on a generated menu page that does not correspond to a subdirectory`, + () => { + const vol = Volume.fromJSON({ + "/docs/docs.mdx": `--- +title: "Documentation Home" +description: "Guides to setting up the product." +--- + +`, + "/docs/application-access/application-access.mdx": `--- +title: "Application Access" +description: "Guides related to Application Access" +--- + +`, + "/docs/application-access/page1.mdx": `--- +title: "Application Access Page 1" +description: "Protecting App 1 with Teleport" +---`, + "/docs/jwt.mdx": `--- +title: "JWT guides" +description: "Guides related to JWTs" +--- + +`, + }); + + const fs = createFsFromVolume(vol); + const actual = getTOC("/docs/", fs); + assert.equal(actual.error, ""); + } +); Suite("getTOC orders sections correctly", () => { const expected = `- [API Usage](api.mdx): Using the API. @@ -219,6 +183,38 @@ description: "Using the API." }); const fs = createFsFromVolume(vol); const actual = getTOC("/docs/", fs); + assert.equal(actual.result, expected); +}); + +const transformer = (vfileOptions: VFileOptions) => { + const file = new VFile(vfileOptions); + + return remark() + .use(remarkMdx) + .use(remarkGFM) + .use(remarkTOC, { rootDir: "server/fixtures/toc" }) + .processSync(file); +}; + +Suite("replaces inclusion expressions", () => { + const value = readFileSync( + resolve("server/fixtures/toc/source.mdx"), + "utf-8" + ); + + const result = transformer({ + value, + path: "/content/4.0/docs/pages/filename.mdx", + }); + + const actual = result.toString(); + + const expected = readFileSync( + resolve("server/fixtures/toc/expected.mdx"), + "utf-8" + ); + + assert.equal(result.messages, []); assert.equal(actual, expected); });