@plttn/mkd 0.2.1 → 0.2.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +5 -0
- package/dist/index.cjs +352 -370
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +1 -1
- package/dist/index.d.mts +1 -0
- package/dist/index.mjs +384 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +4 -4
- package/dist/index.d.ts +0 -1
- package/dist/index.js +0 -404
- package/dist/index.js.map +0 -1
package/dist/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts","../src/lib/deps.ts","../src/commands/new.ts","../src/commands/publish.ts","../src/lib/commands.ts","../src/commands/update.ts","../src/commands/unpublish.ts","../src/commands/init.ts"],"sourcesContent":["#!/usr/bin/env node\nimport { run, subcommands } from \"cmd-ts\";\nimport { PoweredFileSystem } from \"pwd-fs\";\nimport { loadConfig } from \"./lib/deps\";\nimport { makeNewCommand } from \"./commands/new\";\nimport { makePublishCommand } from \"./commands/publish\";\nimport { makeUpdateCommand } from \"./commands/update\";\nimport { makeUnPublishCommand } from \"./commands/unpublish\";\nimport { makeInitCommand } from \"./commands/init\";\n\nasync function main() {\n const pfs = new PoweredFileSystem();\n const config = await loadConfig(pfs);\n const deps = { config, pfs };\n\n const app = subcommands({\n name: \"mkd\",\n cmds: {\n new: makeNewCommand(deps),\n publish: makePublishCommand(deps),\n update: makeUpdateCommand(deps),\n unpublish: makeUnPublishCommand(deps),\n init: makeInitCommand(deps),\n },\n });\n\n await run(app, process.argv.slice(2));\n}\n\nmain().catch((error) => {\n console.error(error);\n process.exit(1);\n});\n","import type { PoweredFileSystem } from \"pwd-fs\";\n\nexport type Config = {\n blogDir: string;\n titleKey: string;\n author: string;\n publishedAtKey: string;\n modifiedAtKey: string;\n authorKey: string;\n draftKey: string;\n descriptionKey: string;\n tagsKey: string;\n};\n\nexport type Deps = {\n config: Config;\n pfs: PoweredFileSystem;\n};\n\nconst defaultConfig: Config = {\n blogDir: \"./src/blog\",\n titleKey: \"title\",\n author: \"\",\n publishedAtKey: \"publishedAt\",\n modifiedAtKey: \"updatedAt\",\n authorKey: \"author\",\n draftKey: \"draft\",\n descriptionKey: \"description\",\n tagsKey: \"tags\",\n};\n\nexport async function loadConfig(pfs: PoweredFileSystem): Promise<Config> {\n try {\n const raw = await pfs.read(\"./mkd.json\");\n\n if (!raw.trim()) {\n return defaultConfig;\n }\n\n const parsed = JSON.parse(raw);\n\n if (!isRecord(parsed)) {\n return defaultConfig;\n }\n\n const { $schema: _schema, ...config } = parsed;\n\n return {\n ...defaultConfig,\n ...config,\n };\n } catch {\n return defaultConfig;\n }\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === \"object\" && value !== null && !Array.isArray(value);\n}\n","import { command, restPositionals, string } from \"cmd-ts\";\nimport path from \"node:path\";\nimport slugify from \"@sindresorhus/slugify\";\nimport filenamify from \"filenamify\";\nimport matter from \"gray-matter\";\nimport { isCancel, text, intro, outro } from \"@clack/prompts\";\nimport type { Config, Deps } from \"../lib/deps\";\n\nexport function makeNewCommand({ config, pfs }: Deps) {\n return command({\n name: \"new\",\n description: \"Create a new post\",\n args: {\n new: restPositionals({\n type: string,\n displayName: \"file\",\n description: \"name of the new post\",\n }),\n },\n handler: async ({ new: titleArray }) => {\n intro(\"Create a new post\");\n let title: string;\n if (titleArray.length === 0) {\n title = await generateTitle();\n if (title === \"\") {\n return;\n }\n } else {\n title = titleArray.join(\" \");\n }\n const slug = slugify(title);\n const fileName = filenamify(slug);\n const frontmatter = await generateFrontmatter(title, config);\n const filePath = path.join(config.blogDir, `${fileName}.md`);\n\n await pfs.write(filePath, frontmatter);\n },\n });\n}\n\nasync function generateFrontmatter(\n title: string,\n config: Config,\n): Promise<string> {\n const description = await makeDescription(title);\n // const now = new Date();\n\n const data = {\n [config.titleKey]: title,\n [config.publishedAtKey]: new Date(\"3000-01-01T00:00:00Z\"),\n [config.authorKey]: config.author,\n [config.draftKey]: true,\n [config.descriptionKey]: description,\n [config.tagsKey]: [],\n };\n\n return matter.stringify(\"\", data);\n}\n\nasync function makeDescription(title: string): Promise<string> {\n const description = await text({\n message: \"Enter a description for the post:\",\n defaultValue: title,\n });\n\n if (isCancel(description)) {\n return \"\";\n }\n\n return description;\n}\n\nasync function generateTitle(): Promise<string> {\n const title = await text({\n message: \"Enter a title for the post:\",\n validate: (value) => {\n if (!value) {\n return \"Title required\";\n }\n return undefined;\n },\n });\n\n if (isCancel(title)) {\n return \"\";\n }\n\n return title;\n}\n","import { command } from \"cmd-ts\";\nimport { isCancel, intro, outro, autocomplete } from \"@clack/prompts\";\nimport matter from \"gray-matter\";\nimport type { Config, Deps } from \"../lib/deps\";\nimport {\n readPosts,\n parseFrontmatter,\n postsToOptions,\n selectedValuesToPosts,\n Post,\n} from \"../lib/commands\";\n\nexport function makePublishCommand({ config, pfs }: Deps) {\n return command({\n name: \"publish\",\n description: \"Undraft a post\",\n args: {},\n handler: async () => {\n const posts = await readPosts(pfs, config);\n const drafts = posts.filter(\n (post) => parseFrontmatter(post)[config.draftKey] === true,\n );\n const selected = await getPostsToPublish(drafts, config);\n\n for (const draft of selected) {\n await updateDraftFrontMatter(draft, { config, pfs });\n }\n },\n });\n}\n\nasync function getPostsToPublish(\n drafts: Post[],\n config: Config,\n): Promise<Post[]> {\n const options = postsToOptions(drafts, config);\n intro(\"Publishing posts\");\n\n const selected = await autocomplete({\n message: \"Select post to publish\",\n options,\n });\n\n if (isCancel(selected)) {\n outro(\"Publishing cancelled.\");\n return [];\n }\n\n outro(\"Posts undrafted...\");\n\n return selectedValuesToPosts(\n Array.isArray(selected) ? selected : [selected],\n drafts,\n );\n}\n\nasync function updateDraftFrontMatter(draft: Post, deps: Deps) {\n const now = new Date();\n const parsed = matter(draft.content);\n const fm = parsed.data as Record<string, unknown>;\n fm[deps.config.draftKey] = false;\n fm[deps.config.publishedAtKey] = now;\n const updatedContent = matter.stringify(parsed.content, fm);\n await deps.pfs.write(`${deps.config.blogDir}/${draft.file}`, updatedContent);\n return { ...draft, content: updatedContent };\n}\n","import matter from \"gray-matter\";\nimport type { Config, Deps } from \"./deps\";\n\nexport type Post = {\n file: string;\n content: string;\n};\n\nexport type Frontmatter = Record<string, unknown>;\n\n/** Read all files from the blog directory and return them as Post objects */\nexport async function readPosts(pfs: Deps[\"pfs\"], config: Config): Promise<Post[]> {\n const files = (await pfs.readdir(config.blogDir)) as string[];\n const posts: Post[] = [];\n\n for (const file of files) {\n const content = await pfs.read(`${config.blogDir}/${file}`);\n posts.push({ file, content });\n }\n\n return posts;\n}\n\n/** Parse frontmatter from a Post */\nexport function parseFrontmatter(post: Post): Frontmatter {\n const { data } = matter(post.content);\n return data as Frontmatter;\n}\n\n/** Convert Posts to prompt option objects (value/label/hint) */\nexport function postsToOptions(posts: Post[], config: Config) {\n return posts.map((post) => {\n const fm = parseFrontmatter(post);\n const title = String(fm[config.titleKey] ?? post.file);\n\n return {\n value: post.file,\n label: title,\n hint: post.file,\n };\n });\n}\n\n/** Take the raw selected values returned by the prompt and return matching Post[] */\nexport function selectedValuesToPosts(selected: string[] | symbol, posts: Post[]): Post[] {\n if (typeof selected === \"symbol\") return [];\n return posts.filter((p) => selected.includes(p.file));\n}\n\nexport function findPostByFile(posts: Post[], fileName: string): Post | undefined {\n return posts.find((p) => p.file === fileName);\n}\n","import { command } from \"cmd-ts\";\nimport { autocomplete, isCancel, intro, outro } from \"@clack/prompts\";\nimport matter from \"gray-matter\";\nimport type { Config, Deps } from \"../lib/deps\";\nimport {\n readPosts,\n postsToOptions,\n findPostByFile,\n Post,\n} from \"../lib/commands\";\n\ntype Frontmatter = Record<string, unknown>;\n\nexport function makeUpdateCommand({ config, pfs }: Deps) {\n return command({\n name: \"update\",\n description: \"Update a post's modified date\",\n args: {},\n handler: async () => {\n const posts = await readPosts(pfs, config);\n const post = await getPostToUpdate(posts, config);\n if (!post) return;\n\n await updatePostDate(post, { config, pfs });\n },\n });\n}\n\nasync function getPostToUpdate(\n posts: Post[],\n config: Config,\n): Promise<Post | null> {\n const options = postsToOptions(posts, config);\n\n intro(\"Update a post\");\n\n const selected = await autocomplete({\n message: \"Select post to update\",\n options,\n });\n\n if (isCancel(selected)) {\n outro(\"Update cancelled.\");\n return null;\n }\n outro(\"Post updated.\");\n\n const found = findPostByFile(posts, selected as string);\n return found ?? null;\n}\n\nasync function updatePostDate(post: Post, deps: Deps) {\n const parsed = matter(post.content);\n const fm = parsed.data as Record<string, unknown>;\n fm[deps.config.modifiedAtKey] = new Date();\n const updatedContent = matter.stringify(parsed.content, fm);\n await deps.pfs.write(`${deps.config.blogDir}/${post.file}`, updatedContent);\n return { ...post, content: updatedContent };\n}\n","import { command } from \"cmd-ts\";\nimport { autocomplete, isCancel, intro, outro } from \"@clack/prompts\";\nimport matter from \"gray-matter\";\nimport type { Config, Deps } from \"../lib/deps\";\nimport {\n readPosts,\n postsToOptions,\n findPostByFile,\n Post,\n} from \"../lib/commands\";\n\ntype Frontmatter = Record<string, unknown>;\n\nexport function makeUnPublishCommand({ config, pfs }: Deps) {\n return command({\n name: \"unpublish\",\n description: \"Unpublish a post\",\n args: {},\n handler: async () => {\n const posts = await readPosts(pfs, config);\n const post = await getPostToUnpub(posts, config);\n if (!post) return;\n\n await unpubPost(post, { config, pfs });\n },\n });\n}\n\nasync function getPostToUnpub(\n posts: Post[],\n config: Config,\n): Promise<Post | null> {\n const options = postsToOptions(posts, config);\n\n intro(\"Unpublish a post\");\n\n const selected = await autocomplete({\n message: \"Select post to unpublish\",\n options,\n });\n\n if (isCancel(selected)) {\n outro(\"Unpublish cancelled.\");\n return null;\n }\n outro(\"Post unpublished.\");\n\n const found = findPostByFile(posts, selected as string);\n return found ?? null;\n}\n\nasync function unpubPost(post: Post, deps: Deps) {\n const parsed = matter(post.content);\n const fm = parsed.data as Record<string, unknown>;\n fm[deps.config.draftKey] = true;\n const updatedContent = matter.stringify(parsed.content, fm);\n await deps.pfs.write(`${deps.config.blogDir}/${post.file}`, updatedContent);\n return { ...post, content: updatedContent };\n}\n","import { command } from \"cmd-ts\";\nimport { text, isCancel, intro, outro } from \"@clack/prompts\";\nimport type { Deps } from \"../lib/deps\";\n\nexport function makeInitCommand({ config, pfs }: Deps) {\n return command({\n name: \"init\",\n description: \"Create or update mkd.json configuration\",\n args: {},\n handler: async () => {\n intro(\"Initialize mkd configuration\");\n\n const blogDir = await text({\n message: \"Directory where generated posts are written\",\n defaultValue: String(config.blogDir ?? \"./src/blog\"),\n });\n if (isCancel(blogDir)) {\n outro(\"Init cancelled\");\n return;\n }\n\n const author = await text({\n message: \"Default author\",\n defaultValue: String(config.author ?? \"\"),\n });\n if (isCancel(author)) {\n outro(\"Init cancelled\");\n return;\n }\n\n const titleKey = await text({\n message: \"Title frontmatter key\",\n defaultValue: String(config.titleKey ?? \"title\"),\n });\n if (isCancel(titleKey)) {\n outro(\"Init cancelled\");\n return;\n }\n\n const publishedAtKey = await text({\n message: \"Published date frontmatter key\",\n defaultValue: String(config.publishedAtKey ?? \"publishedAt\"),\n });\n if (isCancel(publishedAtKey)) {\n outro(\"Init cancelled\");\n return;\n }\n\n const modifiedAtKey = await text({\n message: \"Modified date frontmatter key\",\n defaultValue: String(config.modifiedAtKey ?? \"updatedAt\"),\n });\n if (isCancel(modifiedAtKey)) {\n outro(\"Init cancelled\");\n return;\n }\n\n const authorKey = await text({\n message: \"Author frontmatter key\",\n defaultValue: String(config.authorKey ?? \"author\"),\n });\n if (isCancel(authorKey)) {\n outro(\"Init cancelled\");\n return;\n }\n\n const draftKey = await text({\n message: \"Draft frontmatter key\",\n defaultValue: String(config.draftKey ?? \"draft\"),\n });\n if (isCancel(draftKey)) {\n outro(\"Init cancelled\");\n return;\n }\n\n const descriptionKey = await text({\n message: \"Description frontmatter key\",\n defaultValue: String(config.descriptionKey ?? \"description\"),\n });\n if (isCancel(descriptionKey)) {\n outro(\"Init cancelled\");\n return;\n }\n\n const tagsKey = await text({\n message: \"Tags frontmatter key\",\n defaultValue: String(config.tagsKey ?? \"tags\"),\n });\n if (isCancel(tagsKey)) {\n outro(\"Init cancelled\");\n return;\n }\n\n const newConfig = {\n blogDir: String(blogDir),\n author: String(author),\n publishedAtKey: String(publishedAtKey),\n modifiedAtKey: String(modifiedAtKey),\n authorKey: String(authorKey),\n draftKey: String(draftKey),\n descriptionKey: String(descriptionKey),\n tagsKey: String(tagsKey),\n titleKey: String(titleKey),\n };\n\n await pfs.write(\"./mkd.json\", JSON.stringify(newConfig, null, 2) + \"\\n\");\n\n outro(\"Configuration saved to mkd.json\");\n },\n });\n}\n"],"mappings":"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","names":["import_cmd_ts","slugify","filenamify","path","matter","import_cmd_ts","import_prompts","import_gray_matter","import_gray_matter","matter","matter","import_cmd_ts","import_prompts","import_gray_matter","matter","import_cmd_ts","import_prompts","import_gray_matter","matter","import_cmd_ts","import_prompts"]}
|
|
1
|
+
{"version":3,"file":"index.cjs","names":["string","path","matter","matter","matter","matter","PoweredFileSystem"],"sources":["../src/lib/deps.ts","../src/commands/new.ts","../src/lib/commands.ts","../src/commands/publish.ts","../src/commands/update.ts","../src/commands/unpublish.ts","../src/commands/init.ts","../src/index.ts"],"sourcesContent":["import type { PoweredFileSystem } from \"pwd-fs\";\n\nexport type Config = {\n blogDir: string;\n titleKey: string;\n author: string;\n publishedAtKey: string;\n modifiedAtKey: string;\n authorKey: string;\n draftKey: string;\n descriptionKey: string;\n tagsKey: string;\n};\n\nexport type Deps = {\n config: Config;\n pfs: PoweredFileSystem;\n};\n\nconst defaultConfig: Config = {\n blogDir: \"./src/blog\",\n titleKey: \"title\",\n author: \"\",\n publishedAtKey: \"publishedAt\",\n modifiedAtKey: \"updatedAt\",\n authorKey: \"author\",\n draftKey: \"draft\",\n descriptionKey: \"description\",\n tagsKey: \"tags\",\n};\n\nexport async function loadConfig(pfs: PoweredFileSystem): Promise<Config> {\n try {\n const raw = await pfs.read(\"./mkd.json\");\n\n if (!raw.trim()) {\n return defaultConfig;\n }\n\n const parsed = JSON.parse(raw);\n\n if (!isRecord(parsed)) {\n return defaultConfig;\n }\n\n const { $schema: _schema, ...config } = parsed;\n\n return {\n ...defaultConfig,\n ...config,\n };\n } catch {\n return defaultConfig;\n }\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === \"object\" && value !== null && !Array.isArray(value);\n}\n","import { command, restPositionals, string } from \"cmd-ts\";\nimport path from \"node:path\";\nimport slugify from \"@sindresorhus/slugify\";\nimport filenamify from \"filenamify\";\nimport matter from \"gray-matter\";\nimport { isCancel, text, intro, outro } from \"@clack/prompts\";\nimport type { Config, Deps } from \"../lib/deps\";\n\nexport function makeNewCommand({ config, pfs }: Deps) {\n return command({\n name: \"new\",\n description: \"Create a new post\",\n args: {\n new: restPositionals({\n type: string,\n displayName: \"file\",\n description: \"name of the new post\",\n }),\n },\n handler: async ({ new: titleArray }) => {\n intro(\"Create a new post\");\n let title: string;\n if (titleArray.length === 0) {\n title = await generateTitle();\n if (title === \"\") {\n return;\n }\n } else {\n title = titleArray.join(\" \");\n }\n const slug = slugify(title);\n const fileName = filenamify(slug);\n const frontmatter = await generateFrontmatter(title, config);\n const filePath = path.join(config.blogDir, `${fileName}.md`);\n\n await pfs.write(filePath, frontmatter);\n },\n });\n}\n\nasync function generateFrontmatter(\n title: string,\n config: Config,\n): Promise<string> {\n const description = await makeDescription(title);\n // const now = new Date();\n\n const data = {\n [config.titleKey]: title,\n [config.publishedAtKey]: new Date(\"3000-01-01T00:00:00Z\"),\n [config.authorKey]: config.author,\n [config.draftKey]: true,\n [config.descriptionKey]: description,\n [config.tagsKey]: [],\n };\n\n return matter.stringify(\"\", data);\n}\n\nasync function makeDescription(title: string): Promise<string> {\n const description = await text({\n message: \"Enter a description for the post:\",\n defaultValue: title,\n });\n\n if (isCancel(description)) {\n return \"\";\n }\n\n return description;\n}\n\nasync function generateTitle(): Promise<string> {\n const title = await text({\n message: \"Enter a title for the post:\",\n validate: (value) => {\n if (!value) {\n return \"Title required\";\n }\n return undefined;\n },\n });\n\n if (isCancel(title)) {\n return \"\";\n }\n\n return title;\n}\n","import matter from \"gray-matter\";\nimport type { Config, Deps } from \"./deps\";\n\nexport type Post = {\n file: string;\n content: string;\n};\n\nexport type Frontmatter = Record<string, unknown>;\n\n/** Read all files from the blog directory and return them as Post objects */\nexport async function readPosts(pfs: Deps[\"pfs\"], config: Config): Promise<Post[]> {\n const files = (await pfs.readdir(config.blogDir)) as string[];\n const posts: Post[] = [];\n\n for (const file of files) {\n const content = await pfs.read(`${config.blogDir}/${file}`);\n posts.push({ file, content });\n }\n\n return posts;\n}\n\n/** Parse frontmatter from a Post */\nexport function parseFrontmatter(post: Post): Frontmatter {\n const { data } = matter(post.content);\n return data as Frontmatter;\n}\n\n/** Convert Posts to prompt option objects (value/label/hint) */\nexport function postsToOptions(posts: Post[], config: Config) {\n return posts.map((post) => {\n const fm = parseFrontmatter(post);\n const title = String(fm[config.titleKey] ?? post.file);\n\n return {\n value: post.file,\n label: title,\n hint: post.file,\n };\n });\n}\n\n/** Take the raw selected values returned by the prompt and return matching Post[] */\nexport function selectedValuesToPosts(selected: string[] | symbol, posts: Post[]): Post[] {\n if (typeof selected === \"symbol\") return [];\n return posts.filter((p) => selected.includes(p.file));\n}\n\nexport function findPostByFile(posts: Post[], fileName: string): Post | undefined {\n return posts.find((p) => p.file === fileName);\n}\n","import { command } from \"cmd-ts\";\nimport { isCancel, intro, outro, autocomplete } from \"@clack/prompts\";\nimport matter from \"gray-matter\";\nimport type { Config, Deps } from \"../lib/deps\";\nimport {\n readPosts,\n parseFrontmatter,\n postsToOptions,\n selectedValuesToPosts,\n Post,\n} from \"../lib/commands\";\n\nexport function makePublishCommand({ config, pfs }: Deps) {\n return command({\n name: \"publish\",\n description: \"Undraft a post\",\n args: {},\n handler: async () => {\n const posts = await readPosts(pfs, config);\n const drafts = posts.filter(\n (post) => parseFrontmatter(post)[config.draftKey] === true,\n );\n const selected = await getPostsToPublish(drafts, config);\n\n for (const draft of selected) {\n await updateDraftFrontMatter(draft, { config, pfs });\n }\n },\n });\n}\n\nasync function getPostsToPublish(\n drafts: Post[],\n config: Config,\n): Promise<Post[]> {\n const options = postsToOptions(drafts, config);\n intro(\"Publishing posts\");\n\n const selected = await autocomplete({\n message: \"Select post to publish\",\n options,\n });\n\n if (isCancel(selected)) {\n outro(\"Publishing cancelled.\");\n return [];\n }\n\n outro(\"Posts undrafted...\");\n\n return selectedValuesToPosts(\n Array.isArray(selected) ? selected : [selected],\n drafts,\n );\n}\n\nasync function updateDraftFrontMatter(draft: Post, deps: Deps) {\n const now = new Date();\n const parsed = matter(draft.content);\n const fm = parsed.data as Record<string, unknown>;\n fm[deps.config.draftKey] = false;\n fm[deps.config.publishedAtKey] = now;\n const updatedContent = matter.stringify(parsed.content, fm);\n await deps.pfs.write(`${deps.config.blogDir}/${draft.file}`, updatedContent);\n return { ...draft, content: updatedContent };\n}\n","import { command } from \"cmd-ts\";\nimport { autocomplete, isCancel, intro, outro } from \"@clack/prompts\";\nimport matter from \"gray-matter\";\nimport type { Config, Deps } from \"../lib/deps\";\nimport {\n readPosts,\n postsToOptions,\n findPostByFile,\n Post,\n} from \"../lib/commands\";\n\ntype Frontmatter = Record<string, unknown>;\n\nexport function makeUpdateCommand({ config, pfs }: Deps) {\n return command({\n name: \"update\",\n description: \"Update a post's modified date\",\n args: {},\n handler: async () => {\n const posts = await readPosts(pfs, config);\n const post = await getPostToUpdate(posts, config);\n if (!post) return;\n\n await updatePostDate(post, { config, pfs });\n },\n });\n}\n\nasync function getPostToUpdate(\n posts: Post[],\n config: Config,\n): Promise<Post | null> {\n const options = postsToOptions(posts, config);\n\n intro(\"Update a post\");\n\n const selected = await autocomplete({\n message: \"Select post to update\",\n options,\n });\n\n if (isCancel(selected)) {\n outro(\"Update cancelled.\");\n return null;\n }\n outro(\"Post updated.\");\n\n const found = findPostByFile(posts, selected as string);\n return found ?? null;\n}\n\nasync function updatePostDate(post: Post, deps: Deps) {\n const parsed = matter(post.content);\n const fm = parsed.data as Record<string, unknown>;\n fm[deps.config.modifiedAtKey] = new Date();\n const updatedContent = matter.stringify(parsed.content, fm);\n await deps.pfs.write(`${deps.config.blogDir}/${post.file}`, updatedContent);\n return { ...post, content: updatedContent };\n}\n","import { command } from \"cmd-ts\";\nimport { autocomplete, isCancel, intro, outro } from \"@clack/prompts\";\nimport matter from \"gray-matter\";\nimport type { Config, Deps } from \"../lib/deps\";\nimport {\n readPosts,\n postsToOptions,\n findPostByFile,\n Post,\n} from \"../lib/commands\";\n\ntype Frontmatter = Record<string, unknown>;\n\nexport function makeUnPublishCommand({ config, pfs }: Deps) {\n return command({\n name: \"unpublish\",\n description: \"Unpublish a post\",\n args: {},\n handler: async () => {\n const posts = await readPosts(pfs, config);\n const post = await getPostToUnpub(posts, config);\n if (!post) return;\n\n await unpubPost(post, { config, pfs });\n },\n });\n}\n\nasync function getPostToUnpub(\n posts: Post[],\n config: Config,\n): Promise<Post | null> {\n const options = postsToOptions(posts, config);\n\n intro(\"Unpublish a post\");\n\n const selected = await autocomplete({\n message: \"Select post to unpublish\",\n options,\n });\n\n if (isCancel(selected)) {\n outro(\"Unpublish cancelled.\");\n return null;\n }\n outro(\"Post unpublished.\");\n\n const found = findPostByFile(posts, selected as string);\n return found ?? null;\n}\n\nasync function unpubPost(post: Post, deps: Deps) {\n const parsed = matter(post.content);\n const fm = parsed.data as Record<string, unknown>;\n fm[deps.config.draftKey] = true;\n const updatedContent = matter.stringify(parsed.content, fm);\n await deps.pfs.write(`${deps.config.blogDir}/${post.file}`, updatedContent);\n return { ...post, content: updatedContent };\n}\n","import { command } from \"cmd-ts\";\nimport { text, isCancel, intro, outro } from \"@clack/prompts\";\nimport type { Deps } from \"../lib/deps\";\n\nexport function makeInitCommand({ config, pfs }: Deps) {\n return command({\n name: \"init\",\n description: \"Create or update mkd.json configuration\",\n args: {},\n handler: async () => {\n intro(\"Initialize mkd configuration\");\n\n const blogDir = await text({\n message: \"Directory where generated posts are written\",\n defaultValue: String(config.blogDir ?? \"./src/blog\"),\n });\n if (isCancel(blogDir)) {\n outro(\"Init cancelled\");\n return;\n }\n\n const author = await text({\n message: \"Default author\",\n defaultValue: String(config.author ?? \"\"),\n });\n if (isCancel(author)) {\n outro(\"Init cancelled\");\n return;\n }\n\n const titleKey = await text({\n message: \"Title frontmatter key\",\n defaultValue: String(config.titleKey ?? \"title\"),\n });\n if (isCancel(titleKey)) {\n outro(\"Init cancelled\");\n return;\n }\n\n const publishedAtKey = await text({\n message: \"Published date frontmatter key\",\n defaultValue: String(config.publishedAtKey ?? \"publishedAt\"),\n });\n if (isCancel(publishedAtKey)) {\n outro(\"Init cancelled\");\n return;\n }\n\n const modifiedAtKey = await text({\n message: \"Modified date frontmatter key\",\n defaultValue: String(config.modifiedAtKey ?? \"updatedAt\"),\n });\n if (isCancel(modifiedAtKey)) {\n outro(\"Init cancelled\");\n return;\n }\n\n const authorKey = await text({\n message: \"Author frontmatter key\",\n defaultValue: String(config.authorKey ?? \"author\"),\n });\n if (isCancel(authorKey)) {\n outro(\"Init cancelled\");\n return;\n }\n\n const draftKey = await text({\n message: \"Draft frontmatter key\",\n defaultValue: String(config.draftKey ?? \"draft\"),\n });\n if (isCancel(draftKey)) {\n outro(\"Init cancelled\");\n return;\n }\n\n const descriptionKey = await text({\n message: \"Description frontmatter key\",\n defaultValue: String(config.descriptionKey ?? \"description\"),\n });\n if (isCancel(descriptionKey)) {\n outro(\"Init cancelled\");\n return;\n }\n\n const tagsKey = await text({\n message: \"Tags frontmatter key\",\n defaultValue: String(config.tagsKey ?? \"tags\"),\n });\n if (isCancel(tagsKey)) {\n outro(\"Init cancelled\");\n return;\n }\n\n const newConfig = {\n blogDir: String(blogDir),\n author: String(author),\n publishedAtKey: String(publishedAtKey),\n modifiedAtKey: String(modifiedAtKey),\n authorKey: String(authorKey),\n draftKey: String(draftKey),\n descriptionKey: String(descriptionKey),\n tagsKey: String(tagsKey),\n titleKey: String(titleKey),\n };\n\n await pfs.write(\"./mkd.json\", JSON.stringify(newConfig, null, 2) + \"\\n\");\n\n outro(\"Configuration saved to mkd.json\");\n },\n });\n}\n","#!/usr/bin/env node\nimport { run, subcommands } from \"cmd-ts\";\nimport { PoweredFileSystem } from \"pwd-fs\";\nimport { loadConfig } from \"./lib/deps\";\nimport { makeNewCommand } from \"./commands/new\";\nimport { makePublishCommand } from \"./commands/publish\";\nimport { makeUpdateCommand } from \"./commands/update\";\nimport { makeUnPublishCommand } from \"./commands/unpublish\";\nimport { makeInitCommand } from \"./commands/init\";\n\nasync function main() {\n const pfs = new PoweredFileSystem();\n const config = await loadConfig(pfs);\n const deps = { config, pfs };\n\n const app = subcommands({\n name: \"mkd\",\n cmds: {\n new: makeNewCommand(deps),\n publish: makePublishCommand(deps),\n update: makeUpdateCommand(deps),\n unpublish: makeUnPublishCommand(deps),\n init: makeInitCommand(deps),\n },\n });\n\n await run(app, process.argv.slice(2));\n}\n\nmain().catch((error) => {\n console.error(error);\n process.exit(1);\n});\n"],"mappings":"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"}
|
package/dist/index.d.cts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
|
|
1
|
+
export { };
|
package/dist/index.d.mts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { };
|
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,384 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { command, restPositionals, run, string, subcommands } from "cmd-ts";
|
|
3
|
+
import { PoweredFileSystem } from "pwd-fs";
|
|
4
|
+
import path from "node:path";
|
|
5
|
+
import slugify from "@sindresorhus/slugify";
|
|
6
|
+
import filenamify from "filenamify";
|
|
7
|
+
import matter from "gray-matter";
|
|
8
|
+
import { autocomplete, intro, isCancel, outro, text } from "@clack/prompts";
|
|
9
|
+
//#region src/lib/deps.ts
|
|
10
|
+
const defaultConfig = {
|
|
11
|
+
blogDir: "./src/blog",
|
|
12
|
+
titleKey: "title",
|
|
13
|
+
author: "",
|
|
14
|
+
publishedAtKey: "publishedAt",
|
|
15
|
+
modifiedAtKey: "updatedAt",
|
|
16
|
+
authorKey: "author",
|
|
17
|
+
draftKey: "draft",
|
|
18
|
+
descriptionKey: "description",
|
|
19
|
+
tagsKey: "tags"
|
|
20
|
+
};
|
|
21
|
+
async function loadConfig(pfs) {
|
|
22
|
+
try {
|
|
23
|
+
const raw = await pfs.read("./mkd.json");
|
|
24
|
+
if (!raw.trim()) return defaultConfig;
|
|
25
|
+
const parsed = JSON.parse(raw);
|
|
26
|
+
if (!isRecord(parsed)) return defaultConfig;
|
|
27
|
+
const { $schema: _schema, ...config } = parsed;
|
|
28
|
+
return {
|
|
29
|
+
...defaultConfig,
|
|
30
|
+
...config
|
|
31
|
+
};
|
|
32
|
+
} catch {
|
|
33
|
+
return defaultConfig;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
function isRecord(value) {
|
|
37
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
38
|
+
}
|
|
39
|
+
//#endregion
|
|
40
|
+
//#region src/commands/new.ts
|
|
41
|
+
function makeNewCommand({ config, pfs }) {
|
|
42
|
+
return command({
|
|
43
|
+
name: "new",
|
|
44
|
+
description: "Create a new post",
|
|
45
|
+
args: { new: restPositionals({
|
|
46
|
+
type: string,
|
|
47
|
+
displayName: "file",
|
|
48
|
+
description: "name of the new post"
|
|
49
|
+
}) },
|
|
50
|
+
handler: async ({ new: titleArray }) => {
|
|
51
|
+
intro("Create a new post");
|
|
52
|
+
let title;
|
|
53
|
+
if (titleArray.length === 0) {
|
|
54
|
+
title = await generateTitle();
|
|
55
|
+
if (title === "") return;
|
|
56
|
+
} else title = titleArray.join(" ");
|
|
57
|
+
const fileName = filenamify(slugify(title));
|
|
58
|
+
const frontmatter = await generateFrontmatter(title, config);
|
|
59
|
+
const filePath = path.join(config.blogDir, `${fileName}.md`);
|
|
60
|
+
await pfs.write(filePath, frontmatter);
|
|
61
|
+
}
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
async function generateFrontmatter(title, config) {
|
|
65
|
+
const description = await makeDescription(title);
|
|
66
|
+
const data = {
|
|
67
|
+
[config.titleKey]: title,
|
|
68
|
+
[config.publishedAtKey]: /* @__PURE__ */ new Date("3000-01-01T00:00:00Z"),
|
|
69
|
+
[config.authorKey]: config.author,
|
|
70
|
+
[config.draftKey]: true,
|
|
71
|
+
[config.descriptionKey]: description,
|
|
72
|
+
[config.tagsKey]: []
|
|
73
|
+
};
|
|
74
|
+
return matter.stringify("", data);
|
|
75
|
+
}
|
|
76
|
+
async function makeDescription(title) {
|
|
77
|
+
const description = await text({
|
|
78
|
+
message: "Enter a description for the post:",
|
|
79
|
+
defaultValue: title
|
|
80
|
+
});
|
|
81
|
+
if (isCancel(description)) return "";
|
|
82
|
+
return description;
|
|
83
|
+
}
|
|
84
|
+
async function generateTitle() {
|
|
85
|
+
const title = await text({
|
|
86
|
+
message: "Enter a title for the post:",
|
|
87
|
+
validate: (value) => {
|
|
88
|
+
if (!value) return "Title required";
|
|
89
|
+
}
|
|
90
|
+
});
|
|
91
|
+
if (isCancel(title)) return "";
|
|
92
|
+
return title;
|
|
93
|
+
}
|
|
94
|
+
//#endregion
|
|
95
|
+
//#region src/lib/commands.ts
|
|
96
|
+
/** Read all files from the blog directory and return them as Post objects */
|
|
97
|
+
async function readPosts(pfs, config) {
|
|
98
|
+
const files = await pfs.readdir(config.blogDir);
|
|
99
|
+
const posts = [];
|
|
100
|
+
for (const file of files) {
|
|
101
|
+
const content = await pfs.read(`${config.blogDir}/${file}`);
|
|
102
|
+
posts.push({
|
|
103
|
+
file,
|
|
104
|
+
content
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
return posts;
|
|
108
|
+
}
|
|
109
|
+
/** Parse frontmatter from a Post */
|
|
110
|
+
function parseFrontmatter(post) {
|
|
111
|
+
const { data } = matter(post.content);
|
|
112
|
+
return data;
|
|
113
|
+
}
|
|
114
|
+
/** Convert Posts to prompt option objects (value/label/hint) */
|
|
115
|
+
function postsToOptions(posts, config) {
|
|
116
|
+
return posts.map((post) => {
|
|
117
|
+
const fm = parseFrontmatter(post);
|
|
118
|
+
const title = String(fm[config.titleKey] ?? post.file);
|
|
119
|
+
return {
|
|
120
|
+
value: post.file,
|
|
121
|
+
label: title,
|
|
122
|
+
hint: post.file
|
|
123
|
+
};
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
/** Take the raw selected values returned by the prompt and return matching Post[] */
|
|
127
|
+
function selectedValuesToPosts(selected, posts) {
|
|
128
|
+
if (typeof selected === "symbol") return [];
|
|
129
|
+
return posts.filter((p) => selected.includes(p.file));
|
|
130
|
+
}
|
|
131
|
+
function findPostByFile(posts, fileName) {
|
|
132
|
+
return posts.find((p) => p.file === fileName);
|
|
133
|
+
}
|
|
134
|
+
//#endregion
|
|
135
|
+
//#region src/commands/publish.ts
|
|
136
|
+
function makePublishCommand({ config, pfs }) {
|
|
137
|
+
return command({
|
|
138
|
+
name: "publish",
|
|
139
|
+
description: "Undraft a post",
|
|
140
|
+
args: {},
|
|
141
|
+
handler: async () => {
|
|
142
|
+
const selected = await getPostsToPublish((await readPosts(pfs, config)).filter((post) => parseFrontmatter(post)[config.draftKey] === true), config);
|
|
143
|
+
for (const draft of selected) await updateDraftFrontMatter(draft, {
|
|
144
|
+
config,
|
|
145
|
+
pfs
|
|
146
|
+
});
|
|
147
|
+
}
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
async function getPostsToPublish(drafts, config) {
|
|
151
|
+
const options = postsToOptions(drafts, config);
|
|
152
|
+
intro("Publishing posts");
|
|
153
|
+
const selected = await autocomplete({
|
|
154
|
+
message: "Select post to publish",
|
|
155
|
+
options
|
|
156
|
+
});
|
|
157
|
+
if (isCancel(selected)) {
|
|
158
|
+
outro("Publishing cancelled.");
|
|
159
|
+
return [];
|
|
160
|
+
}
|
|
161
|
+
outro("Posts undrafted...");
|
|
162
|
+
return selectedValuesToPosts(Array.isArray(selected) ? selected : [selected], drafts);
|
|
163
|
+
}
|
|
164
|
+
async function updateDraftFrontMatter(draft, deps) {
|
|
165
|
+
const now = /* @__PURE__ */ new Date();
|
|
166
|
+
const parsed = matter(draft.content);
|
|
167
|
+
const fm = parsed.data;
|
|
168
|
+
fm[deps.config.draftKey] = false;
|
|
169
|
+
fm[deps.config.publishedAtKey] = now;
|
|
170
|
+
const updatedContent = matter.stringify(parsed.content, fm);
|
|
171
|
+
await deps.pfs.write(`${deps.config.blogDir}/${draft.file}`, updatedContent);
|
|
172
|
+
return {
|
|
173
|
+
...draft,
|
|
174
|
+
content: updatedContent
|
|
175
|
+
};
|
|
176
|
+
}
|
|
177
|
+
//#endregion
|
|
178
|
+
//#region src/commands/update.ts
|
|
179
|
+
function makeUpdateCommand({ config, pfs }) {
|
|
180
|
+
return command({
|
|
181
|
+
name: "update",
|
|
182
|
+
description: "Update a post's modified date",
|
|
183
|
+
args: {},
|
|
184
|
+
handler: async () => {
|
|
185
|
+
const post = await getPostToUpdate(await readPosts(pfs, config), config);
|
|
186
|
+
if (!post) return;
|
|
187
|
+
await updatePostDate(post, {
|
|
188
|
+
config,
|
|
189
|
+
pfs
|
|
190
|
+
});
|
|
191
|
+
}
|
|
192
|
+
});
|
|
193
|
+
}
|
|
194
|
+
async function getPostToUpdate(posts, config) {
|
|
195
|
+
const options = postsToOptions(posts, config);
|
|
196
|
+
intro("Update a post");
|
|
197
|
+
const selected = await autocomplete({
|
|
198
|
+
message: "Select post to update",
|
|
199
|
+
options
|
|
200
|
+
});
|
|
201
|
+
if (isCancel(selected)) {
|
|
202
|
+
outro("Update cancelled.");
|
|
203
|
+
return null;
|
|
204
|
+
}
|
|
205
|
+
outro("Post updated.");
|
|
206
|
+
return findPostByFile(posts, selected) ?? null;
|
|
207
|
+
}
|
|
208
|
+
async function updatePostDate(post, deps) {
|
|
209
|
+
const parsed = matter(post.content);
|
|
210
|
+
const fm = parsed.data;
|
|
211
|
+
fm[deps.config.modifiedAtKey] = /* @__PURE__ */ new Date();
|
|
212
|
+
const updatedContent = matter.stringify(parsed.content, fm);
|
|
213
|
+
await deps.pfs.write(`${deps.config.blogDir}/${post.file}`, updatedContent);
|
|
214
|
+
return {
|
|
215
|
+
...post,
|
|
216
|
+
content: updatedContent
|
|
217
|
+
};
|
|
218
|
+
}
|
|
219
|
+
//#endregion
|
|
220
|
+
//#region src/commands/unpublish.ts
|
|
221
|
+
function makeUnPublishCommand({ config, pfs }) {
|
|
222
|
+
return command({
|
|
223
|
+
name: "unpublish",
|
|
224
|
+
description: "Unpublish a post",
|
|
225
|
+
args: {},
|
|
226
|
+
handler: async () => {
|
|
227
|
+
const post = await getPostToUnpub(await readPosts(pfs, config), config);
|
|
228
|
+
if (!post) return;
|
|
229
|
+
await unpubPost(post, {
|
|
230
|
+
config,
|
|
231
|
+
pfs
|
|
232
|
+
});
|
|
233
|
+
}
|
|
234
|
+
});
|
|
235
|
+
}
|
|
236
|
+
async function getPostToUnpub(posts, config) {
|
|
237
|
+
const options = postsToOptions(posts, config);
|
|
238
|
+
intro("Unpublish a post");
|
|
239
|
+
const selected = await autocomplete({
|
|
240
|
+
message: "Select post to unpublish",
|
|
241
|
+
options
|
|
242
|
+
});
|
|
243
|
+
if (isCancel(selected)) {
|
|
244
|
+
outro("Unpublish cancelled.");
|
|
245
|
+
return null;
|
|
246
|
+
}
|
|
247
|
+
outro("Post unpublished.");
|
|
248
|
+
return findPostByFile(posts, selected) ?? null;
|
|
249
|
+
}
|
|
250
|
+
async function unpubPost(post, deps) {
|
|
251
|
+
const parsed = matter(post.content);
|
|
252
|
+
const fm = parsed.data;
|
|
253
|
+
fm[deps.config.draftKey] = true;
|
|
254
|
+
const updatedContent = matter.stringify(parsed.content, fm);
|
|
255
|
+
await deps.pfs.write(`${deps.config.blogDir}/${post.file}`, updatedContent);
|
|
256
|
+
return {
|
|
257
|
+
...post,
|
|
258
|
+
content: updatedContent
|
|
259
|
+
};
|
|
260
|
+
}
|
|
261
|
+
//#endregion
|
|
262
|
+
//#region src/commands/init.ts
|
|
263
|
+
function makeInitCommand({ config, pfs }) {
|
|
264
|
+
return command({
|
|
265
|
+
name: "init",
|
|
266
|
+
description: "Create or update mkd.json configuration",
|
|
267
|
+
args: {},
|
|
268
|
+
handler: async () => {
|
|
269
|
+
intro("Initialize mkd configuration");
|
|
270
|
+
const blogDir = await text({
|
|
271
|
+
message: "Directory where generated posts are written",
|
|
272
|
+
defaultValue: String(config.blogDir ?? "./src/blog")
|
|
273
|
+
});
|
|
274
|
+
if (isCancel(blogDir)) {
|
|
275
|
+
outro("Init cancelled");
|
|
276
|
+
return;
|
|
277
|
+
}
|
|
278
|
+
const author = await text({
|
|
279
|
+
message: "Default author",
|
|
280
|
+
defaultValue: String(config.author ?? "")
|
|
281
|
+
});
|
|
282
|
+
if (isCancel(author)) {
|
|
283
|
+
outro("Init cancelled");
|
|
284
|
+
return;
|
|
285
|
+
}
|
|
286
|
+
const titleKey = await text({
|
|
287
|
+
message: "Title frontmatter key",
|
|
288
|
+
defaultValue: String(config.titleKey ?? "title")
|
|
289
|
+
});
|
|
290
|
+
if (isCancel(titleKey)) {
|
|
291
|
+
outro("Init cancelled");
|
|
292
|
+
return;
|
|
293
|
+
}
|
|
294
|
+
const publishedAtKey = await text({
|
|
295
|
+
message: "Published date frontmatter key",
|
|
296
|
+
defaultValue: String(config.publishedAtKey ?? "publishedAt")
|
|
297
|
+
});
|
|
298
|
+
if (isCancel(publishedAtKey)) {
|
|
299
|
+
outro("Init cancelled");
|
|
300
|
+
return;
|
|
301
|
+
}
|
|
302
|
+
const modifiedAtKey = await text({
|
|
303
|
+
message: "Modified date frontmatter key",
|
|
304
|
+
defaultValue: String(config.modifiedAtKey ?? "updatedAt")
|
|
305
|
+
});
|
|
306
|
+
if (isCancel(modifiedAtKey)) {
|
|
307
|
+
outro("Init cancelled");
|
|
308
|
+
return;
|
|
309
|
+
}
|
|
310
|
+
const authorKey = await text({
|
|
311
|
+
message: "Author frontmatter key",
|
|
312
|
+
defaultValue: String(config.authorKey ?? "author")
|
|
313
|
+
});
|
|
314
|
+
if (isCancel(authorKey)) {
|
|
315
|
+
outro("Init cancelled");
|
|
316
|
+
return;
|
|
317
|
+
}
|
|
318
|
+
const draftKey = await text({
|
|
319
|
+
message: "Draft frontmatter key",
|
|
320
|
+
defaultValue: String(config.draftKey ?? "draft")
|
|
321
|
+
});
|
|
322
|
+
if (isCancel(draftKey)) {
|
|
323
|
+
outro("Init cancelled");
|
|
324
|
+
return;
|
|
325
|
+
}
|
|
326
|
+
const descriptionKey = await text({
|
|
327
|
+
message: "Description frontmatter key",
|
|
328
|
+
defaultValue: String(config.descriptionKey ?? "description")
|
|
329
|
+
});
|
|
330
|
+
if (isCancel(descriptionKey)) {
|
|
331
|
+
outro("Init cancelled");
|
|
332
|
+
return;
|
|
333
|
+
}
|
|
334
|
+
const tagsKey = await text({
|
|
335
|
+
message: "Tags frontmatter key",
|
|
336
|
+
defaultValue: String(config.tagsKey ?? "tags")
|
|
337
|
+
});
|
|
338
|
+
if (isCancel(tagsKey)) {
|
|
339
|
+
outro("Init cancelled");
|
|
340
|
+
return;
|
|
341
|
+
}
|
|
342
|
+
const newConfig = {
|
|
343
|
+
blogDir: String(blogDir),
|
|
344
|
+
author: String(author),
|
|
345
|
+
publishedAtKey: String(publishedAtKey),
|
|
346
|
+
modifiedAtKey: String(modifiedAtKey),
|
|
347
|
+
authorKey: String(authorKey),
|
|
348
|
+
draftKey: String(draftKey),
|
|
349
|
+
descriptionKey: String(descriptionKey),
|
|
350
|
+
tagsKey: String(tagsKey),
|
|
351
|
+
titleKey: String(titleKey)
|
|
352
|
+
};
|
|
353
|
+
await pfs.write("./mkd.json", JSON.stringify(newConfig, null, 2) + "\n");
|
|
354
|
+
outro("Configuration saved to mkd.json");
|
|
355
|
+
}
|
|
356
|
+
});
|
|
357
|
+
}
|
|
358
|
+
//#endregion
|
|
359
|
+
//#region src/index.ts
|
|
360
|
+
async function main() {
|
|
361
|
+
const pfs = new PoweredFileSystem();
|
|
362
|
+
const deps = {
|
|
363
|
+
config: await loadConfig(pfs),
|
|
364
|
+
pfs
|
|
365
|
+
};
|
|
366
|
+
await run(subcommands({
|
|
367
|
+
name: "mkd",
|
|
368
|
+
cmds: {
|
|
369
|
+
new: makeNewCommand(deps),
|
|
370
|
+
publish: makePublishCommand(deps),
|
|
371
|
+
update: makeUpdateCommand(deps),
|
|
372
|
+
unpublish: makeUnPublishCommand(deps),
|
|
373
|
+
init: makeInitCommand(deps)
|
|
374
|
+
}
|
|
375
|
+
}), process.argv.slice(2));
|
|
376
|
+
}
|
|
377
|
+
main().catch((error) => {
|
|
378
|
+
console.error(error);
|
|
379
|
+
process.exit(1);
|
|
380
|
+
});
|
|
381
|
+
//#endregion
|
|
382
|
+
export {};
|
|
383
|
+
|
|
384
|
+
//# sourceMappingURL=index.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.mjs","names":[],"sources":["../src/lib/deps.ts","../src/commands/new.ts","../src/lib/commands.ts","../src/commands/publish.ts","../src/commands/update.ts","../src/commands/unpublish.ts","../src/commands/init.ts","../src/index.ts"],"sourcesContent":["import type { PoweredFileSystem } from \"pwd-fs\";\n\nexport type Config = {\n blogDir: string;\n titleKey: string;\n author: string;\n publishedAtKey: string;\n modifiedAtKey: string;\n authorKey: string;\n draftKey: string;\n descriptionKey: string;\n tagsKey: string;\n};\n\nexport type Deps = {\n config: Config;\n pfs: PoweredFileSystem;\n};\n\nconst defaultConfig: Config = {\n blogDir: \"./src/blog\",\n titleKey: \"title\",\n author: \"\",\n publishedAtKey: \"publishedAt\",\n modifiedAtKey: \"updatedAt\",\n authorKey: \"author\",\n draftKey: \"draft\",\n descriptionKey: \"description\",\n tagsKey: \"tags\",\n};\n\nexport async function loadConfig(pfs: PoweredFileSystem): Promise<Config> {\n try {\n const raw = await pfs.read(\"./mkd.json\");\n\n if (!raw.trim()) {\n return defaultConfig;\n }\n\n const parsed = JSON.parse(raw);\n\n if (!isRecord(parsed)) {\n return defaultConfig;\n }\n\n const { $schema: _schema, ...config } = parsed;\n\n return {\n ...defaultConfig,\n ...config,\n };\n } catch {\n return defaultConfig;\n }\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === \"object\" && value !== null && !Array.isArray(value);\n}\n","import { command, restPositionals, string } from \"cmd-ts\";\nimport path from \"node:path\";\nimport slugify from \"@sindresorhus/slugify\";\nimport filenamify from \"filenamify\";\nimport matter from \"gray-matter\";\nimport { isCancel, text, intro, outro } from \"@clack/prompts\";\nimport type { Config, Deps } from \"../lib/deps\";\n\nexport function makeNewCommand({ config, pfs }: Deps) {\n return command({\n name: \"new\",\n description: \"Create a new post\",\n args: {\n new: restPositionals({\n type: string,\n displayName: \"file\",\n description: \"name of the new post\",\n }),\n },\n handler: async ({ new: titleArray }) => {\n intro(\"Create a new post\");\n let title: string;\n if (titleArray.length === 0) {\n title = await generateTitle();\n if (title === \"\") {\n return;\n }\n } else {\n title = titleArray.join(\" \");\n }\n const slug = slugify(title);\n const fileName = filenamify(slug);\n const frontmatter = await generateFrontmatter(title, config);\n const filePath = path.join(config.blogDir, `${fileName}.md`);\n\n await pfs.write(filePath, frontmatter);\n },\n });\n}\n\nasync function generateFrontmatter(\n title: string,\n config: Config,\n): Promise<string> {\n const description = await makeDescription(title);\n // const now = new Date();\n\n const data = {\n [config.titleKey]: title,\n [config.publishedAtKey]: new Date(\"3000-01-01T00:00:00Z\"),\n [config.authorKey]: config.author,\n [config.draftKey]: true,\n [config.descriptionKey]: description,\n [config.tagsKey]: [],\n };\n\n return matter.stringify(\"\", data);\n}\n\nasync function makeDescription(title: string): Promise<string> {\n const description = await text({\n message: \"Enter a description for the post:\",\n defaultValue: title,\n });\n\n if (isCancel(description)) {\n return \"\";\n }\n\n return description;\n}\n\nasync function generateTitle(): Promise<string> {\n const title = await text({\n message: \"Enter a title for the post:\",\n validate: (value) => {\n if (!value) {\n return \"Title required\";\n }\n return undefined;\n },\n });\n\n if (isCancel(title)) {\n return \"\";\n }\n\n return title;\n}\n","import matter from \"gray-matter\";\nimport type { Config, Deps } from \"./deps\";\n\nexport type Post = {\n file: string;\n content: string;\n};\n\nexport type Frontmatter = Record<string, unknown>;\n\n/** Read all files from the blog directory and return them as Post objects */\nexport async function readPosts(pfs: Deps[\"pfs\"], config: Config): Promise<Post[]> {\n const files = (await pfs.readdir(config.blogDir)) as string[];\n const posts: Post[] = [];\n\n for (const file of files) {\n const content = await pfs.read(`${config.blogDir}/${file}`);\n posts.push({ file, content });\n }\n\n return posts;\n}\n\n/** Parse frontmatter from a Post */\nexport function parseFrontmatter(post: Post): Frontmatter {\n const { data } = matter(post.content);\n return data as Frontmatter;\n}\n\n/** Convert Posts to prompt option objects (value/label/hint) */\nexport function postsToOptions(posts: Post[], config: Config) {\n return posts.map((post) => {\n const fm = parseFrontmatter(post);\n const title = String(fm[config.titleKey] ?? post.file);\n\n return {\n value: post.file,\n label: title,\n hint: post.file,\n };\n });\n}\n\n/** Take the raw selected values returned by the prompt and return matching Post[] */\nexport function selectedValuesToPosts(selected: string[] | symbol, posts: Post[]): Post[] {\n if (typeof selected === \"symbol\") return [];\n return posts.filter((p) => selected.includes(p.file));\n}\n\nexport function findPostByFile(posts: Post[], fileName: string): Post | undefined {\n return posts.find((p) => p.file === fileName);\n}\n","import { command } from \"cmd-ts\";\nimport { isCancel, intro, outro, autocomplete } from \"@clack/prompts\";\nimport matter from \"gray-matter\";\nimport type { Config, Deps } from \"../lib/deps\";\nimport {\n readPosts,\n parseFrontmatter,\n postsToOptions,\n selectedValuesToPosts,\n Post,\n} from \"../lib/commands\";\n\nexport function makePublishCommand({ config, pfs }: Deps) {\n return command({\n name: \"publish\",\n description: \"Undraft a post\",\n args: {},\n handler: async () => {\n const posts = await readPosts(pfs, config);\n const drafts = posts.filter(\n (post) => parseFrontmatter(post)[config.draftKey] === true,\n );\n const selected = await getPostsToPublish(drafts, config);\n\n for (const draft of selected) {\n await updateDraftFrontMatter(draft, { config, pfs });\n }\n },\n });\n}\n\nasync function getPostsToPublish(\n drafts: Post[],\n config: Config,\n): Promise<Post[]> {\n const options = postsToOptions(drafts, config);\n intro(\"Publishing posts\");\n\n const selected = await autocomplete({\n message: \"Select post to publish\",\n options,\n });\n\n if (isCancel(selected)) {\n outro(\"Publishing cancelled.\");\n return [];\n }\n\n outro(\"Posts undrafted...\");\n\n return selectedValuesToPosts(\n Array.isArray(selected) ? selected : [selected],\n drafts,\n );\n}\n\nasync function updateDraftFrontMatter(draft: Post, deps: Deps) {\n const now = new Date();\n const parsed = matter(draft.content);\n const fm = parsed.data as Record<string, unknown>;\n fm[deps.config.draftKey] = false;\n fm[deps.config.publishedAtKey] = now;\n const updatedContent = matter.stringify(parsed.content, fm);\n await deps.pfs.write(`${deps.config.blogDir}/${draft.file}`, updatedContent);\n return { ...draft, content: updatedContent };\n}\n","import { command } from \"cmd-ts\";\nimport { autocomplete, isCancel, intro, outro } from \"@clack/prompts\";\nimport matter from \"gray-matter\";\nimport type { Config, Deps } from \"../lib/deps\";\nimport {\n readPosts,\n postsToOptions,\n findPostByFile,\n Post,\n} from \"../lib/commands\";\n\ntype Frontmatter = Record<string, unknown>;\n\nexport function makeUpdateCommand({ config, pfs }: Deps) {\n return command({\n name: \"update\",\n description: \"Update a post's modified date\",\n args: {},\n handler: async () => {\n const posts = await readPosts(pfs, config);\n const post = await getPostToUpdate(posts, config);\n if (!post) return;\n\n await updatePostDate(post, { config, pfs });\n },\n });\n}\n\nasync function getPostToUpdate(\n posts: Post[],\n config: Config,\n): Promise<Post | null> {\n const options = postsToOptions(posts, config);\n\n intro(\"Update a post\");\n\n const selected = await autocomplete({\n message: \"Select post to update\",\n options,\n });\n\n if (isCancel(selected)) {\n outro(\"Update cancelled.\");\n return null;\n }\n outro(\"Post updated.\");\n\n const found = findPostByFile(posts, selected as string);\n return found ?? null;\n}\n\nasync function updatePostDate(post: Post, deps: Deps) {\n const parsed = matter(post.content);\n const fm = parsed.data as Record<string, unknown>;\n fm[deps.config.modifiedAtKey] = new Date();\n const updatedContent = matter.stringify(parsed.content, fm);\n await deps.pfs.write(`${deps.config.blogDir}/${post.file}`, updatedContent);\n return { ...post, content: updatedContent };\n}\n","import { command } from \"cmd-ts\";\nimport { autocomplete, isCancel, intro, outro } from \"@clack/prompts\";\nimport matter from \"gray-matter\";\nimport type { Config, Deps } from \"../lib/deps\";\nimport {\n readPosts,\n postsToOptions,\n findPostByFile,\n Post,\n} from \"../lib/commands\";\n\ntype Frontmatter = Record<string, unknown>;\n\nexport function makeUnPublishCommand({ config, pfs }: Deps) {\n return command({\n name: \"unpublish\",\n description: \"Unpublish a post\",\n args: {},\n handler: async () => {\n const posts = await readPosts(pfs, config);\n const post = await getPostToUnpub(posts, config);\n if (!post) return;\n\n await unpubPost(post, { config, pfs });\n },\n });\n}\n\nasync function getPostToUnpub(\n posts: Post[],\n config: Config,\n): Promise<Post | null> {\n const options = postsToOptions(posts, config);\n\n intro(\"Unpublish a post\");\n\n const selected = await autocomplete({\n message: \"Select post to unpublish\",\n options,\n });\n\n if (isCancel(selected)) {\n outro(\"Unpublish cancelled.\");\n return null;\n }\n outro(\"Post unpublished.\");\n\n const found = findPostByFile(posts, selected as string);\n return found ?? null;\n}\n\nasync function unpubPost(post: Post, deps: Deps) {\n const parsed = matter(post.content);\n const fm = parsed.data as Record<string, unknown>;\n fm[deps.config.draftKey] = true;\n const updatedContent = matter.stringify(parsed.content, fm);\n await deps.pfs.write(`${deps.config.blogDir}/${post.file}`, updatedContent);\n return { ...post, content: updatedContent };\n}\n","import { command } from \"cmd-ts\";\nimport { text, isCancel, intro, outro } from \"@clack/prompts\";\nimport type { Deps } from \"../lib/deps\";\n\nexport function makeInitCommand({ config, pfs }: Deps) {\n return command({\n name: \"init\",\n description: \"Create or update mkd.json configuration\",\n args: {},\n handler: async () => {\n intro(\"Initialize mkd configuration\");\n\n const blogDir = await text({\n message: \"Directory where generated posts are written\",\n defaultValue: String(config.blogDir ?? \"./src/blog\"),\n });\n if (isCancel(blogDir)) {\n outro(\"Init cancelled\");\n return;\n }\n\n const author = await text({\n message: \"Default author\",\n defaultValue: String(config.author ?? \"\"),\n });\n if (isCancel(author)) {\n outro(\"Init cancelled\");\n return;\n }\n\n const titleKey = await text({\n message: \"Title frontmatter key\",\n defaultValue: String(config.titleKey ?? \"title\"),\n });\n if (isCancel(titleKey)) {\n outro(\"Init cancelled\");\n return;\n }\n\n const publishedAtKey = await text({\n message: \"Published date frontmatter key\",\n defaultValue: String(config.publishedAtKey ?? \"publishedAt\"),\n });\n if (isCancel(publishedAtKey)) {\n outro(\"Init cancelled\");\n return;\n }\n\n const modifiedAtKey = await text({\n message: \"Modified date frontmatter key\",\n defaultValue: String(config.modifiedAtKey ?? \"updatedAt\"),\n });\n if (isCancel(modifiedAtKey)) {\n outro(\"Init cancelled\");\n return;\n }\n\n const authorKey = await text({\n message: \"Author frontmatter key\",\n defaultValue: String(config.authorKey ?? \"author\"),\n });\n if (isCancel(authorKey)) {\n outro(\"Init cancelled\");\n return;\n }\n\n const draftKey = await text({\n message: \"Draft frontmatter key\",\n defaultValue: String(config.draftKey ?? \"draft\"),\n });\n if (isCancel(draftKey)) {\n outro(\"Init cancelled\");\n return;\n }\n\n const descriptionKey = await text({\n message: \"Description frontmatter key\",\n defaultValue: String(config.descriptionKey ?? \"description\"),\n });\n if (isCancel(descriptionKey)) {\n outro(\"Init cancelled\");\n return;\n }\n\n const tagsKey = await text({\n message: \"Tags frontmatter key\",\n defaultValue: String(config.tagsKey ?? \"tags\"),\n });\n if (isCancel(tagsKey)) {\n outro(\"Init cancelled\");\n return;\n }\n\n const newConfig = {\n blogDir: String(blogDir),\n author: String(author),\n publishedAtKey: String(publishedAtKey),\n modifiedAtKey: String(modifiedAtKey),\n authorKey: String(authorKey),\n draftKey: String(draftKey),\n descriptionKey: String(descriptionKey),\n tagsKey: String(tagsKey),\n titleKey: String(titleKey),\n };\n\n await pfs.write(\"./mkd.json\", JSON.stringify(newConfig, null, 2) + \"\\n\");\n\n outro(\"Configuration saved to mkd.json\");\n },\n });\n}\n","#!/usr/bin/env node\nimport { run, subcommands } from \"cmd-ts\";\nimport { PoweredFileSystem } from \"pwd-fs\";\nimport { loadConfig } from \"./lib/deps\";\nimport { makeNewCommand } from \"./commands/new\";\nimport { makePublishCommand } from \"./commands/publish\";\nimport { makeUpdateCommand } from \"./commands/update\";\nimport { makeUnPublishCommand } from \"./commands/unpublish\";\nimport { makeInitCommand } from \"./commands/init\";\n\nasync function main() {\n const pfs = new PoweredFileSystem();\n const config = await loadConfig(pfs);\n const deps = { config, pfs };\n\n const app = subcommands({\n name: \"mkd\",\n cmds: {\n new: makeNewCommand(deps),\n publish: makePublishCommand(deps),\n update: makeUpdateCommand(deps),\n unpublish: makeUnPublishCommand(deps),\n init: makeInitCommand(deps),\n },\n });\n\n await run(app, process.argv.slice(2));\n}\n\nmain().catch((error) => {\n console.error(error);\n process.exit(1);\n});\n"],"mappings":";;;;;;;;;AAmBA,MAAM,gBAAwB;CAC5B,SAAS;CACT,UAAU;CACV,QAAQ;CACR,gBAAgB;CAChB,eAAe;CACf,WAAW;CACX,UAAU;CACV,gBAAgB;CAChB,SAAS;AACX;AAEA,eAAsB,WAAW,KAAyC;CACxE,IAAI;EACF,MAAM,MAAM,MAAM,IAAI,KAAK,YAAY;EAEvC,IAAI,CAAC,IAAI,KAAK,GACZ,OAAO;EAGT,MAAM,SAAS,KAAK,MAAM,GAAG;EAE7B,IAAI,CAAC,SAAS,MAAM,GAClB,OAAO;EAGT,MAAM,EAAE,SAAS,SAAS,GAAG,WAAW;EAExC,OAAO;GACL,GAAG;GACH,GAAG;EACL;CACF,QAAQ;EACN,OAAO;CACT;AACF;AAEA,SAAS,SAAS,OAAkD;CAClE,OAAO,OAAO,UAAU,YAAY,UAAU,QAAQ,CAAC,MAAM,QAAQ,KAAK;AAC5E;;;AClDA,SAAgB,eAAe,EAAE,QAAQ,OAAa;CACpD,OAAO,QAAQ;EACb,MAAM;EACN,aAAa;EACb,MAAM,EACJ,KAAK,gBAAgB;GACnB,MAAM;GACN,aAAa;GACb,aAAa;EACf,CAAC,EACH;EACA,SAAS,OAAO,EAAE,KAAK,iBAAiB;GACtC,MAAM,mBAAmB;GACzB,IAAI;GACJ,IAAI,WAAW,WAAW,GAAG;IAC3B,QAAQ,MAAM,cAAc;IAC5B,IAAI,UAAU,IACZ;GAEJ,OACE,QAAQ,WAAW,KAAK,GAAG;GAG7B,MAAM,WAAW,WADJ,QAAQ,KACU,CAAC;GAChC,MAAM,cAAc,MAAM,oBAAoB,OAAO,MAAM;GAC3D,MAAM,WAAW,KAAK,KAAK,OAAO,SAAS,GAAG,SAAS,IAAI;GAE3D,MAAM,IAAI,MAAM,UAAU,WAAW;EACvC;CACF,CAAC;AACH;AAEA,eAAe,oBACb,OACA,QACiB;CACjB,MAAM,cAAc,MAAM,gBAAgB,KAAK;CAG/C,MAAM,OAAO;GACV,OAAO,WAAW;GAClB,OAAO,iCAAiB,IAAI,KAAK,sBAAsB;GACvD,OAAO,YAAY,OAAO;GAC1B,OAAO,WAAW;GAClB,OAAO,iBAAiB;GACxB,OAAO,UAAU,CAAC;CACrB;CAEA,OAAO,OAAO,UAAU,IAAI,IAAI;AAClC;AAEA,eAAe,gBAAgB,OAAgC;CAC7D,MAAM,cAAc,MAAM,KAAK;EAC7B,SAAS;EACT,cAAc;CAChB,CAAC;CAED,IAAI,SAAS,WAAW,GACtB,OAAO;CAGT,OAAO;AACT;AAEA,eAAe,gBAAiC;CAC9C,MAAM,QAAQ,MAAM,KAAK;EACvB,SAAS;EACT,WAAW,UAAU;GACnB,IAAI,CAAC,OACH,OAAO;EAGX;CACF,CAAC;CAED,IAAI,SAAS,KAAK,GAChB,OAAO;CAGT,OAAO;AACT;;;;AC7EA,eAAsB,UAAU,KAAkB,QAAiC;CACjF,MAAM,QAAS,MAAM,IAAI,QAAQ,OAAO,OAAO;CAC/C,MAAM,QAAgB,CAAC;CAEvB,KAAK,MAAM,QAAQ,OAAO;EACxB,MAAM,UAAU,MAAM,IAAI,KAAK,GAAG,OAAO,QAAQ,GAAG,MAAM;EAC1D,MAAM,KAAK;GAAE;GAAM;EAAQ,CAAC;CAC9B;CAEA,OAAO;AACT;;AAGA,SAAgB,iBAAiB,MAAyB;CACxD,MAAM,EAAE,SAAS,OAAO,KAAK,OAAO;CACpC,OAAO;AACT;;AAGA,SAAgB,eAAe,OAAe,QAAgB;CAC5D,OAAO,MAAM,KAAK,SAAS;EACzB,MAAM,KAAK,iBAAiB,IAAI;EAChC,MAAM,QAAQ,OAAO,GAAG,OAAO,aAAa,KAAK,IAAI;EAErD,OAAO;GACL,OAAO,KAAK;GACZ,OAAO;GACP,MAAM,KAAK;EACb;CACF,CAAC;AACH;;AAGA,SAAgB,sBAAsB,UAA6B,OAAuB;CACxF,IAAI,OAAO,aAAa,UAAU,OAAO,CAAC;CAC1C,OAAO,MAAM,QAAQ,MAAM,SAAS,SAAS,EAAE,IAAI,CAAC;AACtD;AAEA,SAAgB,eAAe,OAAe,UAAoC;CAChF,OAAO,MAAM,MAAM,MAAM,EAAE,SAAS,QAAQ;AAC9C;;;ACvCA,SAAgB,mBAAmB,EAAE,QAAQ,OAAa;CACxD,OAAO,QAAQ;EACb,MAAM;EACN,aAAa;EACb,MAAM,CAAC;EACP,SAAS,YAAY;GAKnB,MAAM,WAAW,MAAM,mBAHR,MADK,UAAU,KAAK,MAAM,GACpB,QAClB,SAAS,iBAAiB,IAAI,EAAE,OAAO,cAAc,IAEV,GAAG,MAAM;GAEvD,KAAK,MAAM,SAAS,UAClB,MAAM,uBAAuB,OAAO;IAAE;IAAQ;GAAI,CAAC;EAEvD;CACF,CAAC;AACH;AAEA,eAAe,kBACb,QACA,QACiB;CACjB,MAAM,UAAU,eAAe,QAAQ,MAAM;CAC7C,MAAM,kBAAkB;CAExB,MAAM,WAAW,MAAM,aAAa;EAClC,SAAS;EACT;CACF,CAAC;CAED,IAAI,SAAS,QAAQ,GAAG;EACtB,MAAM,uBAAuB;EAC7B,OAAO,CAAC;CACV;CAEA,MAAM,oBAAoB;CAE1B,OAAO,sBACL,MAAM,QAAQ,QAAQ,IAAI,WAAW,CAAC,QAAQ,GAC9C,MACF;AACF;AAEA,eAAe,uBAAuB,OAAa,MAAY;CAC7D,MAAM,sBAAM,IAAI,KAAK;CACrB,MAAM,SAAS,OAAO,MAAM,OAAO;CACnC,MAAM,KAAK,OAAO;CAClB,GAAG,KAAK,OAAO,YAAY;CAC3B,GAAG,KAAK,OAAO,kBAAkB;CACjC,MAAM,iBAAiB,OAAO,UAAU,OAAO,SAAS,EAAE;CAC1D,MAAM,KAAK,IAAI,MAAM,GAAG,KAAK,OAAO,QAAQ,GAAG,MAAM,QAAQ,cAAc;CAC3E,OAAO;EAAE,GAAG;EAAO,SAAS;CAAe;AAC7C;;;ACpDA,SAAgB,kBAAkB,EAAE,QAAQ,OAAa;CACvD,OAAO,QAAQ;EACb,MAAM;EACN,aAAa;EACb,MAAM,CAAC;EACP,SAAS,YAAY;GAEnB,MAAM,OAAO,MAAM,gBAAgB,MADf,UAAU,KAAK,MAAM,GACC,MAAM;GAChD,IAAI,CAAC,MAAM;GAEX,MAAM,eAAe,MAAM;IAAE;IAAQ;GAAI,CAAC;EAC5C;CACF,CAAC;AACH;AAEA,eAAe,gBACb,OACA,QACsB;CACtB,MAAM,UAAU,eAAe,OAAO,MAAM;CAE5C,MAAM,eAAe;CAErB,MAAM,WAAW,MAAM,aAAa;EAClC,SAAS;EACT;CACF,CAAC;CAED,IAAI,SAAS,QAAQ,GAAG;EACtB,MAAM,mBAAmB;EACzB,OAAO;CACT;CACA,MAAM,eAAe;CAGrB,OADc,eAAe,OAAO,QACzB,KAAK;AAClB;AAEA,eAAe,eAAe,MAAY,MAAY;CACpD,MAAM,SAAS,OAAO,KAAK,OAAO;CAClC,MAAM,KAAK,OAAO;CAClB,GAAG,KAAK,OAAO,iCAAiB,IAAI,KAAK;CACzC,MAAM,iBAAiB,OAAO,UAAU,OAAO,SAAS,EAAE;CAC1D,MAAM,KAAK,IAAI,MAAM,GAAG,KAAK,OAAO,QAAQ,GAAG,KAAK,QAAQ,cAAc;CAC1E,OAAO;EAAE,GAAG;EAAM,SAAS;CAAe;AAC5C;;;AC7CA,SAAgB,qBAAqB,EAAE,QAAQ,OAAa;CAC1D,OAAO,QAAQ;EACb,MAAM;EACN,aAAa;EACb,MAAM,CAAC;EACP,SAAS,YAAY;GAEnB,MAAM,OAAO,MAAM,eAAe,MADd,UAAU,KAAK,MAAM,GACA,MAAM;GAC/C,IAAI,CAAC,MAAM;GAEX,MAAM,UAAU,MAAM;IAAE;IAAQ;GAAI,CAAC;EACvC;CACF,CAAC;AACH;AAEA,eAAe,eACb,OACA,QACsB;CACtB,MAAM,UAAU,eAAe,OAAO,MAAM;CAE5C,MAAM,kBAAkB;CAExB,MAAM,WAAW,MAAM,aAAa;EAClC,SAAS;EACT;CACF,CAAC;CAED,IAAI,SAAS,QAAQ,GAAG;EACtB,MAAM,sBAAsB;EAC5B,OAAO;CACT;CACA,MAAM,mBAAmB;CAGzB,OADc,eAAe,OAAO,QACzB,KAAK;AAClB;AAEA,eAAe,UAAU,MAAY,MAAY;CAC/C,MAAM,SAAS,OAAO,KAAK,OAAO;CAClC,MAAM,KAAK,OAAO;CAClB,GAAG,KAAK,OAAO,YAAY;CAC3B,MAAM,iBAAiB,OAAO,UAAU,OAAO,SAAS,EAAE;CAC1D,MAAM,KAAK,IAAI,MAAM,GAAG,KAAK,OAAO,QAAQ,GAAG,KAAK,QAAQ,cAAc;CAC1E,OAAO;EAAE,GAAG;EAAM,SAAS;CAAe;AAC5C;;;ACtDA,SAAgB,gBAAgB,EAAE,QAAQ,OAAa;CACrD,OAAO,QAAQ;EACb,MAAM;EACN,aAAa;EACb,MAAM,CAAC;EACP,SAAS,YAAY;GACnB,MAAM,8BAA8B;GAEpC,MAAM,UAAU,MAAM,KAAK;IACzB,SAAS;IACT,cAAc,OAAO,OAAO,WAAW,YAAY;GACrD,CAAC;GACD,IAAI,SAAS,OAAO,GAAG;IACrB,MAAM,gBAAgB;IACtB;GACF;GAEA,MAAM,SAAS,MAAM,KAAK;IACxB,SAAS;IACT,cAAc,OAAO,OAAO,UAAU,EAAE;GAC1C,CAAC;GACD,IAAI,SAAS,MAAM,GAAG;IACpB,MAAM,gBAAgB;IACtB;GACF;GAEA,MAAM,WAAW,MAAM,KAAK;IAC1B,SAAS;IACT,cAAc,OAAO,OAAO,YAAY,OAAO;GACjD,CAAC;GACD,IAAI,SAAS,QAAQ,GAAG;IACtB,MAAM,gBAAgB;IACtB;GACF;GAEA,MAAM,iBAAiB,MAAM,KAAK;IAChC,SAAS;IACT,cAAc,OAAO,OAAO,kBAAkB,aAAa;GAC7D,CAAC;GACD,IAAI,SAAS,cAAc,GAAG;IAC5B,MAAM,gBAAgB;IACtB;GACF;GAEA,MAAM,gBAAgB,MAAM,KAAK;IAC/B,SAAS;IACT,cAAc,OAAO,OAAO,iBAAiB,WAAW;GAC1D,CAAC;GACD,IAAI,SAAS,aAAa,GAAG;IAC3B,MAAM,gBAAgB;IACtB;GACF;GAEA,MAAM,YAAY,MAAM,KAAK;IAC3B,SAAS;IACT,cAAc,OAAO,OAAO,aAAa,QAAQ;GACnD,CAAC;GACD,IAAI,SAAS,SAAS,GAAG;IACvB,MAAM,gBAAgB;IACtB;GACF;GAEA,MAAM,WAAW,MAAM,KAAK;IAC1B,SAAS;IACT,cAAc,OAAO,OAAO,YAAY,OAAO;GACjD,CAAC;GACD,IAAI,SAAS,QAAQ,GAAG;IACtB,MAAM,gBAAgB;IACtB;GACF;GAEA,MAAM,iBAAiB,MAAM,KAAK;IAChC,SAAS;IACT,cAAc,OAAO,OAAO,kBAAkB,aAAa;GAC7D,CAAC;GACD,IAAI,SAAS,cAAc,GAAG;IAC5B,MAAM,gBAAgB;IACtB;GACF;GAEA,MAAM,UAAU,MAAM,KAAK;IACzB,SAAS;IACT,cAAc,OAAO,OAAO,WAAW,MAAM;GAC/C,CAAC;GACD,IAAI,SAAS,OAAO,GAAG;IACrB,MAAM,gBAAgB;IACtB;GACF;GAEA,MAAM,YAAY;IAChB,SAAS,OAAO,OAAO;IACvB,QAAQ,OAAO,MAAM;IACrB,gBAAgB,OAAO,cAAc;IACrC,eAAe,OAAO,aAAa;IACnC,WAAW,OAAO,SAAS;IAC3B,UAAU,OAAO,QAAQ;IACzB,gBAAgB,OAAO,cAAc;IACrC,SAAS,OAAO,OAAO;IACvB,UAAU,OAAO,QAAQ;GAC3B;GAEA,MAAM,IAAI,MAAM,cAAc,KAAK,UAAU,WAAW,MAAM,CAAC,IAAI,IAAI;GAEvE,MAAM,iCAAiC;EACzC;CACF,CAAC;AACH;;;ACpGA,eAAe,OAAO;CACpB,MAAM,MAAM,IAAI,kBAAkB;CAElC,MAAM,OAAO;EAAE,QAAA,MADM,WAAW,GAAG;EACZ;CAAI;CAa3B,MAAM,IAXM,YAAY;EACtB,MAAM;EACN,MAAM;GACJ,KAAK,eAAe,IAAI;GACxB,SAAS,mBAAmB,IAAI;GAChC,QAAQ,kBAAkB,IAAI;GAC9B,WAAW,qBAAqB,IAAI;GACpC,MAAM,gBAAgB,IAAI;EAC5B;CACF,CAEY,GAAG,QAAQ,KAAK,MAAM,CAAC,CAAC;AACtC;AAEA,KAAK,EAAE,OAAO,UAAU;CACtB,QAAQ,MAAM,KAAK;CACnB,QAAQ,KAAK,CAAC;AAChB,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@plttn/mkd",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.2",
|
|
4
4
|
"description": "A tool for managing markdown blog posts",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
7
|
-
"mkd": "dist/index.
|
|
7
|
+
"mkd": "dist/index.cjs"
|
|
8
8
|
},
|
|
9
9
|
"files": [
|
|
10
10
|
"dist"
|
|
@@ -26,7 +26,7 @@
|
|
|
26
26
|
"@changesets/cli": "^2.31.0",
|
|
27
27
|
"@types/node": "^25.9.1",
|
|
28
28
|
"prettier": "3.8.3",
|
|
29
|
-
"
|
|
29
|
+
"tsdown": "^0.22.0",
|
|
30
30
|
"tsx": "^4.22.3",
|
|
31
31
|
"typescript": "^6.0.3"
|
|
32
32
|
},
|
|
@@ -48,7 +48,7 @@
|
|
|
48
48
|
},
|
|
49
49
|
"scripts": {
|
|
50
50
|
"dev": "tsx ./src/index.ts",
|
|
51
|
-
"build": "
|
|
51
|
+
"build": "tsdown",
|
|
52
52
|
"ci:publish": "pnpm build && changeset publish"
|
|
53
53
|
}
|
|
54
54
|
}
|
package/dist/index.d.ts
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|