Something went wrong. Try again.
A CLI for publishing standard.site documents to ATProto
Something went wrong. Try again.
TypeScript
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495import { describe, expect, it } from "bun:test";import { parseFrontmatter, resolvePostPath, stripMarkdownForText,} from "../src/lib/markdown";import type { BlogPost } from "../src/lib/types";
describe("parseFrontmatter", () => { describe("delimiters", () => { it("parses YAML frontmatter (--- delimiter)", () => { const content = `---title: Hello World---Body content here.`; const { frontmatter, body } = parseFrontmatter(content); expect(frontmatter.title).toBe("Hello World"); expect(body).toBe("Body content here."); });
it("parses TOML frontmatter (+++ delimiter)", () => { const content = `+++title = "Hugo Post"+++Body content here.`; const { frontmatter, body } = parseFrontmatter(content); expect(frontmatter.title).toBe("Hugo Post"); expect(body).toBe("Body content here."); });
it("parses alternative frontmatter (*** delimiter)", () => { const content = `***title: Alt Post***Body content here.`; const { frontmatter, body } = parseFrontmatter(content); expect(frontmatter.title).toBe("Alt Post"); expect(body).toBe("Body content here."); });
it("throws when no frontmatter is present", () => { const content = "Just plain content with no frontmatter."; expect(() => parseFrontmatter(content)).toThrow( "Could not parse frontmatter", ); }); });
describe("scalar values", () => { it("parses a string value", () => { const content = `---title: My Postdescription: A short description---`; const { frontmatter } = parseFrontmatter(content); expect(frontmatter.title).toBe("My Post"); expect(frontmatter.description).toBe("A short description"); });
it("strips double quotes from values", () => { const content = `---title: "Quoted Title"---`; const { frontmatter } = parseFrontmatter(content); expect(frontmatter.title).toBe("Quoted Title"); });
it("strips single quotes from values", () => { const content = `---title: 'Single Quoted'---`; const { frontmatter } = parseFrontmatter(content); expect(frontmatter.title).toBe("Single Quoted"); });
it("parses YAML folded multiline string", () => { const content = `---excerpt: > This is a folded multiline string---`; const { rawFrontmatter } = parseFrontmatter(content); expect(rawFrontmatter.excerpt).toBe( "This is a folded multiline string\n", ); });
it("parses YAML stripped folded multiline string", () => { const content = `---excerpt: >- This is a stripped folded multiline string---`; const { rawFrontmatter } = parseFrontmatter(content); expect(rawFrontmatter.excerpt).toBe( "This is a stripped folded multiline string", ); });
it("parses YAML literal multiline string", () => { const content = `---excerpt: | This is a literal multiline string---`; const { rawFrontmatter } = parseFrontmatter(content); expect(rawFrontmatter.excerpt).toBe( "This is a literal\nmultiline string\n", ); });
it("parses YAML kept literal multiline string", () => { const content = `---excerpt: |+ This is a kept literal multiline string
end: true---`; const { rawFrontmatter } = parseFrontmatter(content); expect(rawFrontmatter.excerpt).toBe( "This is a kept literal\nmultiline string\n\n", ); });
it("parses boolean true", () => { const content = `---draft: true---`; const { frontmatter } = parseFrontmatter(content); expect(frontmatter.draft).toBe(true); });
it("parses boolean false", () => { const content = `---draft: false---`; const { frontmatter } = parseFrontmatter(content); expect(frontmatter.draft).toBe(false); });
it('parses string "true" in draft field as boolean true', () => { const content = `---draft: true---`; const { rawFrontmatter } = parseFrontmatter(content); expect(rawFrontmatter.draft).toBe(true); }); });
describe("arrays", () => { it("parses inline YAML arrays", () => { const content = `---tags: [typescript, bun, testing]---`; const { frontmatter } = parseFrontmatter(content); expect(frontmatter.tags).toEqual(["typescript", "bun", "testing"]); });
it("parses inline YAML arrays with quoted items", () => { const content = `---tags: ["typescript", "bun", "testing"]---`; const { frontmatter } = parseFrontmatter(content); expect(frontmatter.tags).toEqual(["typescript", "bun", "testing"]); });
it("parses YAML block arrays", () => { const content = `---tags: - typescript - bun - testing---`; const { frontmatter } = parseFrontmatter(content); expect(frontmatter.tags).toEqual(["typescript", "bun", "testing"]); });
it("parses YAML block arrays with quoted items", () => { const content = `---tags: - "typescript" - 'bun'---`; const { frontmatter } = parseFrontmatter(content); expect(frontmatter.tags).toEqual(["typescript", "bun"]); });
it("parses inline TOML arrays", () => { const content = `+++tags = ["typescript", "bun"]+++`; const { frontmatter } = parseFrontmatter(content); expect(frontmatter.tags).toEqual(["typescript", "bun"]); }); });
describe("publish date fallbacks", () => { it("uses publishDate field directly", () => { const content = `---publishDate: 2024-01-15---`; const { frontmatter } = parseFrontmatter(content); expect(frontmatter.publishDate).toBe("2024-01-15"); });
it("falls back to pubDate", () => { const content = `---pubDate: 2024-02-01---`; const { frontmatter } = parseFrontmatter(content); expect(frontmatter.publishDate).toBe("2024-02-01"); });
it("falls back to date", () => { const content = `---date: 2024-03-10---`; const { frontmatter } = parseFrontmatter(content); expect(frontmatter.publishDate).toBe("2024-03-10"); });
it("falls back to createdAt", () => { const content = `---createdAt: 2024-04-20---`; const { frontmatter } = parseFrontmatter(content); expect(frontmatter.publishDate).toBe("2024-04-20"); });
it("falls back to created_at", () => { const content = `---created_at: 2024-05-30---`; const { frontmatter } = parseFrontmatter(content); expect(frontmatter.publishDate).toBe("2024-05-30"); });
it("prefers publishDate over other fallbacks", () => { const content = `---publishDate: 2024-01-01date: 2023-01-01---`; const { frontmatter } = parseFrontmatter(content); expect(frontmatter.publishDate).toBe("2024-01-01"); }); });
describe("rawFrontmatter", () => { it("returns all raw fields", () => { const content = `---title: Raw Testcustom: value---`; const { rawFrontmatter } = parseFrontmatter(content); expect(rawFrontmatter.title).toBe("Raw Test"); expect(rawFrontmatter.custom).toBe("value"); });
it("preserves atUri in both frontmatter and rawFrontmatter", () => { const content = `---title: PostatUri: at://did:plc:abc123/app.bsky.feed.post/xyz---`; const { frontmatter, rawFrontmatter } = parseFrontmatter(content); expect(frontmatter.atUri).toBe( "at://did:plc:abc123/app.bsky.feed.post/xyz", ); expect(rawFrontmatter.atUri).toBe( "at://did:plc:abc123/app.bsky.feed.post/xyz", ); }); });
describe("FrontmatterMapping", () => { it("maps a custom title field", () => { const content = `---name: My Mapped Title---`; const { frontmatter } = parseFrontmatter(content, { title: "name" }); expect(frontmatter.title).toBe("My Mapped Title"); });
it("maps a custom description field", () => { const content = `---summary: Custom description---`; const { frontmatter } = parseFrontmatter(content, { description: "summary", }); expect(frontmatter.description).toBe("Custom description"); });
it("maps a custom publishDate field", () => { const content = `---publishedOn: 2024-06-15---`; const { frontmatter } = parseFrontmatter(content, { publishDate: "publishedOn", }); expect(frontmatter.publishDate).toBe("2024-06-15"); });
it("maps a custom coverImage field", () => { const content = `---heroImage: /images/cover.jpg---`; const { frontmatter } = parseFrontmatter(content, { coverImage: "heroImage", }); expect(frontmatter.ogImage).toBe("/images/cover.jpg"); });
it("maps a custom tags field", () => { const content = `---categories: [news, updates]---`; const { frontmatter } = parseFrontmatter(content, { tags: "categories" }); expect(frontmatter.tags).toEqual(["news", "updates"]); });
it("maps a custom draft field", () => { const content = `---unpublished: true---`; const { frontmatter } = parseFrontmatter(content, { draft: "unpublished", }); expect(frontmatter.draft).toBe(true); });
it("falls back to standard field name when mapped field is absent", () => { const content = `---title: Standard Title---`; const { frontmatter } = parseFrontmatter(content, { title: "heading" }); expect(frontmatter.title).toBe("Standard Title"); }); });
describe("body", () => { it("returns the body content after the closing delimiter", () => { const content = `---title: Post---# Heading
Some paragraph text.`; const { body } = parseFrontmatter(content); expect(body).toBe("# Heading\n\nSome paragraph text."); });
it("returns an empty body when there is no content after frontmatter", () => { const content = `---title: Post---`; const { body } = parseFrontmatter(content); expect(body).toBe(""); }); });});
describe("resolvePostPath", () => { const post: BlogPost = { filePath: "/tmp/hello.md", slug: "hello-world", frontmatter: { title: "Hello" } as BlogPost["frontmatter"], content: "", rawContent: "", rawFrontmatter: {}, };
it("defaults to /posts when pathPrefix is undefined", () => { expect(resolvePostPath(post)).toBe("/posts/hello-world"); });
it("uses custom prefix when provided", () => { expect(resolvePostPath(post, "/blog")).toBe("/blog/hello-world"); });
it("omits prefix when pathPrefix is an empty string", () => { expect(resolvePostPath(post, "")).toBe("/hello-world"); });
it("pathTemplate overrides pathPrefix", () => { expect(resolvePostPath(post, "", "/custom/{slug}")).toBe( "/custom/hello-world", ); });});
describe("stripMarkdownForText", () => { it("removes h1 through h6 headers", () => { expect( stripMarkdownForText("# H1\n## H2\n### H3\n#### H4\n##### H5\n###### H6"), ).toBe("H1\nH2\nH3\nH4\nH5\nH6"); });
it("removes bold formatting", () => { expect(stripMarkdownForText("This is **bold** text.")).toBe( "This is bold text.", ); });
it("removes italic formatting", () => { expect(stripMarkdownForText("This is *italic* text.")).toBe( "This is italic text.", ); });
it("removes links but keeps link text", () => { expect(stripMarkdownForText("[click here](https://example.com)")).toBe( "click here", ); });
it("removes fenced code blocks", () => { const input = "before\n```\nconst x = 1;\n```\nafter"; expect(stripMarkdownForText(input)).toBe("before\n\nafter"); });
it("removes inline code formatting", () => { expect(stripMarkdownForText("Run `npm install` to install.")).toBe( "Run npm install to install.", ); });
it("removes images", () => { expect(stripMarkdownForText("before  after")).toBe( "before after", ); });
it("normalizes three or more consecutive newlines to two", () => { expect(stripMarkdownForText("line 1\n\n\n\nline 2")).toBe( "line 1\n\nline 2", ); });
it("removes single tilde strikethrough", () => { expect(stripMarkdownForText("~strikethrough~")).toBe("strikethrough"); });
it("removes double tilde strikethrough", () => { expect(stripMarkdownForText("~~strikethrough~~")).toBe("strikethrough"); });
it("removes HTML comments but preserves -- outside comments", () => { expect(stripMarkdownForText("a -- b <!-- c -- d --> e -- f")).toBe( "a -- b e -- f", ); });
it("removes footnote definitions and inline references", () => { const input = "Text with[^1] a reference.\n\n[^1]: The footnote definition."; expect(stripMarkdownForText(input)).toBe("Text with a reference."); });
it("trims leading and trailing whitespace", () => { expect(stripMarkdownForText("\n\nhello\n\n")).toBe("hello"); });});