@mzebley/mark-down-cli 1.2.3 → 1.2.4

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 CHANGED
@@ -44,7 +44,7 @@ The CLI walks the directory tree, gathers front matter, and writes `snippets-ind
44
44
 
45
45
  - Discovers `*.md` files under `sourceDir` (defaults to `content/snippets`).
46
46
  - Parses YAML with the `yaml` package, normalizes slugs, flags duplicates, and removes drafts (`draft: true`).
47
- - Writes `snippets-index.json` to the source directory by default (use `--outDir` to override).
47
+ - Writes `snippets-index.json` to the source directory by default (use `-o` / `--output` to override).
48
48
  - Supports relative or absolute paths.
49
49
 
50
50
  ### `mark-down watch <sourceDir>`
@@ -83,7 +83,7 @@ Add flags directly after the command (`mark-down build content/snippets -o publi
83
83
  Use watch mode when authoring content:
84
84
 
85
85
  ```bash
86
- mark-down watch content/snippets --outDir public/snippets
86
+ mark-down watch content/snippets --output public/snippets-index.json
87
87
  ```
88
88
 
89
89
  The CLI will rebuild whenever files are created, changed, or removed.
package/dist/index.cjs CHANGED
@@ -111,7 +111,7 @@ function createSnippet(relativePath, frontMatter) {
111
111
  function normalizeKnownFields(data) {
112
112
  return {
113
113
  title: typeof data.title === "string" ? data.title : void 0,
114
- order: typeof data.order === "number" ? data.order : data.order === null ? null : void 0,
114
+ order: typeof data.order === "number" ? data.order : void 0,
115
115
  type: typeof data.type === "string" ? data.type : void 0,
116
116
  tags: normalizeTags(data.tags),
117
117
  draft: data.draft === true ? true : void 0
@@ -256,7 +256,7 @@ async function compilePage(inputHtml, options = {}) {
256
256
  const rawHtml = await import_promises2.default.readFile(sourcePath, "utf8");
257
257
  const doctypeMatch = rawHtml.match(/^(<!doctype[^>]*>\s*)/i);
258
258
  const doctype = doctypeMatch?.[1] ?? "";
259
- const dom = (0, import_cheerio.load)(rawHtml, { decodeEntities: false });
259
+ const dom = (0, import_cheerio.load)(rawHtml);
260
260
  const targets = dom("[data-snippet]").toArray();
261
261
  for (const node of targets) {
262
262
  const element = dom(node);
@@ -347,7 +347,7 @@ async function assertExists(target, message) {
347
347
 
348
348
  // src/index.ts
349
349
  var program = new import_commander.Command();
350
- program.name("mark-down").description(`${brand} CLI for building snippet manifests`).version("1.2.1");
350
+ program.name("mark-down").description(`${brand} CLI for building snippet manifests`).version("1.2.3");
351
351
  program.command("build").argument("[sourceDir]", "directory containing snippets", "content/snippets").option("-o, --output <path>", "where to write snippets-index.json").action(async (sourceDir, options) => {
352
352
  try {
353
353
  const result = await buildManifestFile({
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts","../src/manifest.ts","../src/errors.ts","../src/watch.ts","../src/logger.ts","../src/compile-page.ts"],"sourcesContent":["#!/usr/bin/env node\nimport { Command } from \"commander\";\nimport { buildManifestFile } from \"./manifest.js\";\nimport { watch as watchSnippets } from \"./watch.js\";\nimport { brand, logEvent } from \"./logger.js\";\nimport { DuplicateSlugError } from \"./errors.js\";\nimport { compilePage } from \"./compile-page.js\";\nimport type { SanitizeOptions, SanitizePolicy } from \"@mzebley/mark-down\";\n\nconst program = new Command();\nprogram\n .name(\"mark-down\")\n .description(`${brand} CLI for building snippet manifests`)\n .version(\"1.2.1\");\n\nprogram\n .command(\"build\")\n .argument(\"[sourceDir]\", \"directory containing snippets\", \"content/snippets\")\n .option(\"-o, --output <path>\", \"where to write snippets-index.json\")\n .action(async (sourceDir: string, options: { output?: string }) => {\n try {\n const result = await buildManifestFile({\n sourceDir,\n outputPath: options.output,\n });\n logEvent(\"info\", \"manifest.written\", {\n outputPath: result.outputPath,\n snippetCount: result.manifest.length,\n });\n } catch (error) {\n handleError(error);\n }\n });\n\nprogram\n .command(\"watch\")\n .argument(\"[sourceDir]\", \"directory containing snippets\", \"content/snippets\")\n .option(\"-o, --output <path>\", \"where to write snippets-index.json\")\n .action(async (sourceDir: string, options: { output?: string }) => {\n try {\n await watchSnippets(sourceDir, options.output);\n } catch (error) {\n handleError(error);\n }\n });\n\nprogram\n .command(\"compile-page\")\n .argument(\"<inputHtml>\", \"HTML file containing data-snippet placeholders\")\n .option(\"--manifest <path>\", \"path to snippets-index.json\")\n .option(\"--outDir <path>\", \"output directory for compiled HTML\", \"dist\")\n .option(\n \"--inPlace\",\n \"overwrite the input HTML file instead of writing to outDir\",\n )\n .option(\n \"--sanitize [policy]\",\n \"sanitize rendered HTML (default|strict|permissive)\",\n )\n .action(\n async (\n inputHtml: string,\n options: {\n manifest?: string;\n outDir?: string;\n inPlace?: boolean;\n sanitize?: string | boolean;\n },\n ) => {\n try {\n await compilePage(inputHtml, {\n manifest: options.manifest,\n outDir: options.outDir,\n inPlace: options.inPlace,\n sanitize: resolveSanitizeOption(options.sanitize),\n });\n } catch (error) {\n handleError(error);\n }\n },\n );\n\nprogram.parseAsync(process.argv).catch(handleError);\n\nfunction handleError(error: unknown) {\n const err = error as Error;\n if (err instanceof DuplicateSlugError) {\n logEvent(\"error\", \"manifest.duplicate_slug\", {\n message: err.message,\n slugs: err.duplicates,\n });\n process.exit(2);\n }\n logEvent(\"error\", \"cli.error\", {\n message: err.message,\n stack: err.stack,\n });\n process.exit(1);\n}\n\nfunction resolveSanitizeOption(\n value?: string | boolean,\n): SanitizeOptions | undefined {\n if (!value) {\n return undefined;\n }\n if (value === true) {\n return { policy: \"default\" };\n }\n const policy = value.trim();\n if (!policy) {\n return { policy: \"default\" };\n }\n if (!isSanitizePolicy(policy)) {\n throw new Error(`Unknown sanitize policy '${policy}'.`);\n }\n return { policy };\n}\n\nfunction isSanitizePolicy(value: string): value is SanitizePolicy {\n return value === \"default\" || value === \"strict\" || value === \"permissive\";\n}\n","import fs from \"node:fs/promises\";\nimport path from \"node:path\";\nimport fg from \"fast-glob\";\nimport matter from \"gray-matter\";\nimport YAML from \"yaml\";\nimport { normalizeSlug, type SnippetMeta } from \"@mzebley/mark-down\";\nimport { DuplicateSlugError } from \"./errors.js\";\n\nconst MATTER_OPTIONS = {\n engines: {\n yaml: (source: string) => YAML.parse(source) ?? {},\n },\n};\n\nexport interface BuildOptions {\n sourceDir: string;\n outputPath?: string;\n}\n\nexport interface BuildResult {\n manifest: SnippetMeta[];\n outputPath: string;\n}\n\nexport async function buildManifestFile(\n options: BuildOptions,\n): Promise<BuildResult> {\n const manifest = await buildManifest(options.sourceDir);\n const target =\n options.outputPath ?? path.join(options.sourceDir, \"snippets-index.json\");\n await fs.writeFile(target, JSON.stringify(manifest, null, 2));\n return { manifest, outputPath: target };\n}\n\nexport async function buildManifest(sourceDir: string): Promise<SnippetMeta[]> {\n const cwd = path.resolve(sourceDir);\n const files = await fg([\"**/*.md\"], { cwd, absolute: true });\n const manifest: SnippetMeta[] = [];\n\n for (const absolutePath of files) {\n const relativePath = path.relative(cwd, absolutePath);\n const normalizedPath = toPosix(relativePath);\n try {\n const content = await fs.readFile(absolutePath, \"utf8\");\n const parsed = matter(content, MATTER_OPTIONS);\n const snippet = createSnippet(normalizedPath, parsed.data ?? {});\n if (snippet.draft) {\n continue;\n }\n manifest.push(snippet);\n } catch (error) {\n console.warn(\n `mark↓: failed to load snippet at '${normalizedPath}'`,\n error,\n );\n }\n }\n\n ensureUniqueSlugs(manifest);\n\n manifest.sort((a, b) => {\n const orderA =\n typeof a.order === \"number\" ? a.order : Number.POSITIVE_INFINITY;\n const orderB =\n typeof b.order === \"number\" ? b.order : Number.POSITIVE_INFINITY;\n if (orderA !== orderB) {\n return orderA - orderB;\n }\n const titleA = a.title?.toLowerCase() ?? \"\";\n const titleB = b.title?.toLowerCase() ?? \"\";\n return titleA.localeCompare(titleB);\n });\n\n return manifest;\n}\n\nexport function createSnippet(\n relativePath: string,\n frontMatter: Record<string, unknown>,\n): SnippetMeta {\n const group = deriveGroup(relativePath);\n const slugSource =\n typeof frontMatter.slug === \"string\" && frontMatter.slug.trim().length\n ? frontMatter.slug\n : relativePath.replace(/\\.md$/i, \"\");\n const slug = normalizeSlug(slugSource);\n\n const { title, order, type, tags, draft } = normalizeKnownFields(frontMatter);\n const extra = collectExtra(frontMatter);\n\n return {\n slug,\n title,\n order,\n type,\n tags,\n draft,\n path: relativePath,\n group,\n extra,\n };\n}\n\nfunction normalizeKnownFields(data: Record<string, unknown>) {\n return {\n title: typeof data.title === \"string\" ? data.title : undefined,\n order:\n typeof data.order === \"number\"\n ? data.order\n : data.order === null\n ? null\n : undefined,\n type: typeof data.type === \"string\" ? data.type : undefined,\n tags: normalizeTags(data.tags),\n draft: data.draft === true ? true : undefined,\n };\n}\n\nfunction collectExtra(\n data: Record<string, unknown>,\n): Record<string, unknown> | undefined {\n const extra: Record<string, unknown> = {};\n const reserved = new Set([\"slug\", \"title\", \"order\", \"type\", \"tags\", \"draft\"]);\n for (const [key, value] of Object.entries(data)) {\n if (reserved.has(key)) {\n continue;\n }\n extra[key] = value;\n }\n return Object.keys(extra).length ? extra : undefined;\n}\n\nfunction normalizeTags(value: unknown): string[] | undefined {\n if (!value) {\n return undefined;\n }\n if (Array.isArray(value)) {\n return value.map((entry) => String(entry));\n }\n if (typeof value === \"string\") {\n return value\n .split(\",\")\n .map((entry) => entry.trim())\n .filter(Boolean);\n }\n return undefined;\n}\n\nfunction deriveGroup(relativePath: string): string {\n const dirname = toPosix(path.dirname(relativePath));\n if (dirname === \".\" || !dirname.length) {\n return \"root\";\n }\n return dirname;\n}\n\nfunction toPosix(value: string): string {\n return value.split(path.sep).join(\"/\");\n}\n\nfunction ensureUniqueSlugs(manifest: SnippetMeta[]) {\n const seen = new Map<string, string>();\n const duplicates = new Set<string>();\n for (const snippet of manifest) {\n if (seen.has(snippet.slug)) {\n duplicates.add(snippet.slug);\n } else {\n seen.set(snippet.slug, snippet.path);\n }\n }\n if (duplicates.size) {\n throw new DuplicateSlugError([...duplicates.values()]);\n }\n}\n","export class DuplicateSlugError extends Error {\n readonly duplicates: string[];\n\n constructor(duplicates: string[]) {\n super(`Duplicate slugs detected: ${duplicates.join(\", \")}`);\n this.name = \"DuplicateSlugError\";\n this.duplicates = duplicates;\n }\n}\n","import path from \"node:path\";\nimport chokidar from \"chokidar\";\nimport { buildManifestFile, type BuildResult } from \"./manifest.js\";\nimport { logEvent } from \"./logger.js\";\n\nexport async function watch(sourceDir: string, outputPath?: string) {\n const cwd = path.resolve(sourceDir);\n logEvent(\"info\", \"watch.start\", {\n directory: cwd,\n outputPath: outputPath ?? path.join(cwd, \"snippets-index.json\"),\n });\n await rebuild(cwd, outputPath);\n\n const watcher = chokidar.watch([\"**/*.md\"], {\n cwd,\n ignoreInitial: true,\n awaitWriteFinish: {\n stabilityThreshold: 200,\n pollInterval: 50,\n },\n });\n\n const schedule = debounce(async () => {\n await rebuild(cwd, outputPath);\n }, 150);\n\n watcher.on(\"all\", (event, filePath) => {\n logEvent(\"info\", \"watch.change\", { event, file: filePath });\n schedule();\n });\n}\n\nasync function rebuild(\n sourceDir: string,\n outputPath?: string,\n): Promise<BuildResult | void> {\n try {\n const result = await buildManifestFile({ sourceDir, outputPath });\n logEvent(\"info\", \"manifest.updated\", {\n outputPath: result.outputPath,\n snippetCount: result.manifest.length,\n });\n return result;\n } catch (error) {\n const err = error as Error;\n logEvent(\"error\", \"manifest.update_failed\", {\n message: err.message,\n stack: err.stack,\n });\n }\n}\n\nfunction debounce<T extends (...args: unknown[]) => Promise<unknown> | void>(\n fn: T,\n delay: number,\n) {\n let timer: NodeJS.Timeout | null = null;\n return (...args: Parameters<T>) => {\n if (timer) {\n clearTimeout(timer);\n }\n timer = setTimeout(() => {\n timer = null;\n void fn(...args);\n }, delay);\n };\n}\n","export const brand = \"mark↓\";\n\nexport type LogLevel = \"info\" | \"warn\" | \"error\";\n\nexport interface LogFields {\n message?: string;\n [key: string]: unknown;\n}\n\nexport function logEvent(\n level: LogLevel,\n event: string,\n fields: LogFields = {},\n) {\n const entry = {\n brand,\n level,\n event,\n timestamp: new Date().toISOString(),\n ...fields,\n };\n const output = `${JSON.stringify(entry)}\\n`;\n const stream = level === \"error\" ? process.stderr : process.stdout;\n stream.write(output);\n}\n\nexport function log(message: string, fields?: LogFields) {\n if (fields) {\n logEvent(\"info\", message, fields);\n return;\n }\n logEvent(\"info\", \"message\", { message });\n}\n\nexport function logError(message: string, fields?: LogFields) {\n if (fields) {\n logEvent(\"error\", message, fields);\n return;\n }\n logEvent(\"error\", \"message\", { message });\n}\n","import fs from \"node:fs/promises\";\nimport path from \"node:path\";\nimport { load as loadHtml } from \"cheerio\";\nimport {\n parseFrontMatter,\n renderMarkdown,\n sanitizeMarkup,\n type SanitizeOptions,\n type SnippetMeta,\n} from \"@mzebley/mark-down\";\nimport { logEvent } from \"./logger.js\";\n\nexport interface CompilePageOptions {\n manifest?: string;\n outDir?: string;\n inPlace?: boolean;\n sanitize?: SanitizeOptions;\n}\n\nconst DEFAULT_OUT_DIR = \"dist\";\n\nexport async function compilePage(\n inputHtml: string,\n options: CompilePageOptions = {},\n): Promise<string> {\n const sourcePath = path.resolve(inputHtml);\n await assertExists(\n sourcePath,\n `Input HTML file not found at '${inputHtml}'.`,\n );\n\n const manifestPath = await resolveManifestPath(sourcePath, options.manifest);\n const manifestDir = path.dirname(manifestPath);\n const manifest = await loadManifest(manifestPath);\n\n const rawHtml = await fs.readFile(sourcePath, \"utf8\");\n const doctypeMatch = rawHtml.match(/^(<!doctype[^>]*>\\s*)/i);\n const doctype = doctypeMatch?.[1] ?? \"\";\n const dom = loadHtml(rawHtml, { decodeEntities: false });\n\n const targets = dom(\"[data-snippet]\").toArray();\n for (const node of targets) {\n const element = dom(node);\n const slug = element.attr(\"data-snippet\");\n if (!slug) {\n continue;\n }\n const entry = manifest.find((item) => item.slug === slug);\n if (!entry) {\n console.warn(`mark↓: no snippet found for \"${slug}\"`);\n continue;\n }\n\n const snippetPath = path.resolve(manifestDir, entry.path);\n let raw: string;\n try {\n raw = await fs.readFile(snippetPath, \"utf8\");\n } catch (error) {\n console.warn(`mark↓: failed to read snippet at '${entry.path}'`, error);\n continue;\n }\n\n let body = raw;\n let frontMatterSlug: string | undefined;\n try {\n const frontMatter = parseFrontMatter(raw);\n body = frontMatter.content;\n frontMatterSlug = frontMatter.slug;\n } catch (error) {\n console.warn(\n `mark↓: failed to parse front matter for '${entry.path}'`,\n error,\n );\n }\n\n let html = renderMarkdown(body);\n if (options.sanitize) {\n html = sanitizeMarkup(html, options.sanitize);\n }\n element.html(html);\n\n if (!element.attr(\"id\")) {\n element.attr(\"id\", frontMatterSlug ?? `snippet-${slug}`);\n }\n }\n\n const outputDir = options.inPlace\n ? path.dirname(sourcePath)\n : path.resolve(options.outDir ?? DEFAULT_OUT_DIR);\n if (!options.inPlace) {\n await fs.mkdir(outputDir, { recursive: true });\n }\n const outputPath = options.inPlace\n ? sourcePath\n : path.join(outputDir, path.basename(sourcePath));\n\n const outputHtml = `${doctype}${dom.html() ?? \"\"}`;\n await fs.writeFile(outputPath, outputHtml);\n\n logEvent(\"info\", \"compile_page.written\", { outputPath });\n return outputPath;\n}\n\nasync function resolveManifestPath(\n inputHtml: string,\n manifestFlag?: string,\n): Promise<string> {\n const manifestPath = manifestFlag\n ? path.resolve(manifestFlag)\n : path.join(path.dirname(path.resolve(inputHtml)), \"snippets-index.json\");\n await assertExists(\n manifestPath,\n `Manifest file not found at '${manifestPath}'.`,\n );\n return manifestPath;\n}\n\nasync function loadManifest(manifestPath: string): Promise<SnippetMeta[]> {\n let raw: string;\n try {\n raw = await fs.readFile(manifestPath, \"utf8\");\n } catch (error) {\n throw new Error(\n `Failed to read manifest at '${manifestPath}': ${String(error)}`,\n );\n }\n\n try {\n const parsed = JSON.parse(raw);\n if (!Array.isArray(parsed)) {\n throw new Error(\"Manifest must be a JSON array.\");\n }\n return parsed as SnippetMeta[];\n } catch (error) {\n throw new Error(\n `Failed to parse manifest at '${manifestPath}': ${String(error)}`,\n );\n }\n}\n\nasync function assertExists(target: string, message: string) {\n try {\n await fs.access(target);\n } catch {\n throw new Error(message);\n }\n}\n"],"mappings":"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","names":["YAML","path","fs","fg","matter","import_node_path","path","chokidar","import_promises","import_node_path","import_mark_down","path","fs","loadHtml"]}
1
+ {"version":3,"sources":["../src/index.ts","../src/manifest.ts","../src/errors.ts","../src/watch.ts","../src/logger.ts","../src/compile-page.ts"],"sourcesContent":["#!/usr/bin/env node\nimport { Command } from \"commander\";\nimport { buildManifestFile } from \"./manifest.js\";\nimport { watch as watchSnippets } from \"./watch.js\";\nimport { brand, logEvent } from \"./logger.js\";\nimport { DuplicateSlugError } from \"./errors.js\";\nimport { compilePage } from \"./compile-page.js\";\nimport type { SanitizeOptions, SanitizePolicy } from \"@mzebley/mark-down\";\n\nconst program = new Command();\nprogram\n .name(\"mark-down\")\n .description(`${brand} CLI for building snippet manifests`)\n .version(\"1.2.3\");\n\nprogram\n .command(\"build\")\n .argument(\"[sourceDir]\", \"directory containing snippets\", \"content/snippets\")\n .option(\"-o, --output <path>\", \"where to write snippets-index.json\")\n .action(async (sourceDir: string, options: { output?: string }) => {\n try {\n const result = await buildManifestFile({\n sourceDir,\n outputPath: options.output,\n });\n logEvent(\"info\", \"manifest.written\", {\n outputPath: result.outputPath,\n snippetCount: result.manifest.length,\n });\n } catch (error) {\n handleError(error);\n }\n });\n\nprogram\n .command(\"watch\")\n .argument(\"[sourceDir]\", \"directory containing snippets\", \"content/snippets\")\n .option(\"-o, --output <path>\", \"where to write snippets-index.json\")\n .action(async (sourceDir: string, options: { output?: string }) => {\n try {\n await watchSnippets(sourceDir, options.output);\n } catch (error) {\n handleError(error);\n }\n });\n\nprogram\n .command(\"compile-page\")\n .argument(\"<inputHtml>\", \"HTML file containing data-snippet placeholders\")\n .option(\"--manifest <path>\", \"path to snippets-index.json\")\n .option(\"--outDir <path>\", \"output directory for compiled HTML\", \"dist\")\n .option(\n \"--inPlace\",\n \"overwrite the input HTML file instead of writing to outDir\",\n )\n .option(\n \"--sanitize [policy]\",\n \"sanitize rendered HTML (default|strict|permissive)\",\n )\n .action(\n async (\n inputHtml: string,\n options: {\n manifest?: string;\n outDir?: string;\n inPlace?: boolean;\n sanitize?: string | boolean;\n },\n ) => {\n try {\n await compilePage(inputHtml, {\n manifest: options.manifest,\n outDir: options.outDir,\n inPlace: options.inPlace,\n sanitize: resolveSanitizeOption(options.sanitize),\n });\n } catch (error) {\n handleError(error);\n }\n },\n );\n\nprogram.parseAsync(process.argv).catch(handleError);\n\nfunction handleError(error: unknown) {\n const err = error as Error;\n if (err instanceof DuplicateSlugError) {\n logEvent(\"error\", \"manifest.duplicate_slug\", {\n message: err.message,\n slugs: err.duplicates,\n });\n process.exit(2);\n }\n logEvent(\"error\", \"cli.error\", {\n message: err.message,\n stack: err.stack,\n });\n process.exit(1);\n}\n\nfunction resolveSanitizeOption(\n value?: string | boolean,\n): SanitizeOptions | undefined {\n if (!value) {\n return undefined;\n }\n if (value === true) {\n return { policy: \"default\" };\n }\n const policy = value.trim();\n if (!policy) {\n return { policy: \"default\" };\n }\n if (!isSanitizePolicy(policy)) {\n throw new Error(`Unknown sanitize policy '${policy}'.`);\n }\n return { policy };\n}\n\nfunction isSanitizePolicy(value: string): value is SanitizePolicy {\n return value === \"default\" || value === \"strict\" || value === \"permissive\";\n}\n","import fs from \"node:fs/promises\";\nimport path from \"node:path\";\nimport fg from \"fast-glob\";\nimport matter from \"gray-matter\";\nimport YAML from \"yaml\";\nimport { normalizeSlug, type SnippetMeta } from \"@mzebley/mark-down\";\nimport { DuplicateSlugError } from \"./errors.js\";\n\nconst MATTER_OPTIONS = {\n engines: {\n yaml: (source: string) => YAML.parse(source) ?? {},\n },\n};\n\nexport interface BuildOptions {\n sourceDir: string;\n outputPath?: string;\n}\n\nexport interface BuildResult {\n manifest: SnippetMeta[];\n outputPath: string;\n}\n\nexport async function buildManifestFile(\n options: BuildOptions,\n): Promise<BuildResult> {\n const manifest = await buildManifest(options.sourceDir);\n const target =\n options.outputPath ?? path.join(options.sourceDir, \"snippets-index.json\");\n await fs.writeFile(target, JSON.stringify(manifest, null, 2));\n return { manifest, outputPath: target };\n}\n\nexport async function buildManifest(sourceDir: string): Promise<SnippetMeta[]> {\n const cwd = path.resolve(sourceDir);\n const files = await fg([\"**/*.md\"], { cwd, absolute: true });\n const manifest: SnippetMeta[] = [];\n\n for (const absolutePath of files) {\n const relativePath = path.relative(cwd, absolutePath);\n const normalizedPath = toPosix(relativePath);\n try {\n const content = await fs.readFile(absolutePath, \"utf8\");\n const parsed = matter(content, MATTER_OPTIONS);\n const snippet = createSnippet(normalizedPath, parsed.data ?? {});\n if (snippet.draft) {\n continue;\n }\n manifest.push(snippet);\n } catch (error) {\n console.warn(\n `mark↓: failed to load snippet at '${normalizedPath}'`,\n error,\n );\n }\n }\n\n ensureUniqueSlugs(manifest);\n\n manifest.sort((a, b) => {\n const orderA =\n typeof a.order === \"number\" ? a.order : Number.POSITIVE_INFINITY;\n const orderB =\n typeof b.order === \"number\" ? b.order : Number.POSITIVE_INFINITY;\n if (orderA !== orderB) {\n return orderA - orderB;\n }\n const titleA = a.title?.toLowerCase() ?? \"\";\n const titleB = b.title?.toLowerCase() ?? \"\";\n return titleA.localeCompare(titleB);\n });\n\n return manifest;\n}\n\nexport function createSnippet(\n relativePath: string,\n frontMatter: Record<string, unknown>,\n): SnippetMeta {\n const group = deriveGroup(relativePath);\n const slugSource =\n typeof frontMatter.slug === \"string\" && frontMatter.slug.trim().length\n ? frontMatter.slug\n : relativePath.replace(/\\.md$/i, \"\");\n const slug = normalizeSlug(slugSource);\n\n const { title, order, type, tags, draft } = normalizeKnownFields(frontMatter);\n const extra = collectExtra(frontMatter);\n\n return {\n slug,\n title,\n order,\n type,\n tags,\n draft,\n path: relativePath,\n group,\n extra,\n };\n}\n\nfunction normalizeKnownFields(data: Record<string, unknown>) {\n return {\n title: typeof data.title === \"string\" ? data.title : undefined,\n order: typeof data.order === \"number\" ? data.order : undefined,\n type: typeof data.type === \"string\" ? data.type : undefined,\n tags: normalizeTags(data.tags),\n draft: data.draft === true ? true : undefined,\n };\n}\n\nfunction collectExtra(\n data: Record<string, unknown>,\n): Record<string, unknown> | undefined {\n const extra: Record<string, unknown> = {};\n const reserved = new Set([\"slug\", \"title\", \"order\", \"type\", \"tags\", \"draft\"]);\n for (const [key, value] of Object.entries(data)) {\n if (reserved.has(key)) {\n continue;\n }\n extra[key] = value;\n }\n return Object.keys(extra).length ? extra : undefined;\n}\n\nfunction normalizeTags(value: unknown): string[] | undefined {\n if (!value) {\n return undefined;\n }\n if (Array.isArray(value)) {\n return value.map((entry) => String(entry));\n }\n if (typeof value === \"string\") {\n return value\n .split(\",\")\n .map((entry) => entry.trim())\n .filter(Boolean);\n }\n return undefined;\n}\n\nfunction deriveGroup(relativePath: string): string {\n const dirname = toPosix(path.dirname(relativePath));\n if (dirname === \".\" || !dirname.length) {\n return \"root\";\n }\n return dirname;\n}\n\nfunction toPosix(value: string): string {\n return value.split(path.sep).join(\"/\");\n}\n\nfunction ensureUniqueSlugs(manifest: SnippetMeta[]) {\n const seen = new Map<string, string>();\n const duplicates = new Set<string>();\n for (const snippet of manifest) {\n if (seen.has(snippet.slug)) {\n duplicates.add(snippet.slug);\n } else {\n seen.set(snippet.slug, snippet.path);\n }\n }\n if (duplicates.size) {\n throw new DuplicateSlugError([...duplicates.values()]);\n }\n}\n","export class DuplicateSlugError extends Error {\n readonly duplicates: string[];\n\n constructor(duplicates: string[]) {\n super(`Duplicate slugs detected: ${duplicates.join(\", \")}`);\n this.name = \"DuplicateSlugError\";\n this.duplicates = duplicates;\n }\n}\n","import path from \"node:path\";\nimport chokidar from \"chokidar\";\nimport { buildManifestFile, type BuildResult } from \"./manifest.js\";\nimport { logEvent } from \"./logger.js\";\n\nexport async function watch(sourceDir: string, outputPath?: string) {\n const cwd = path.resolve(sourceDir);\n logEvent(\"info\", \"watch.start\", {\n directory: cwd,\n outputPath: outputPath ?? path.join(cwd, \"snippets-index.json\"),\n });\n await rebuild(cwd, outputPath);\n\n const watcher = chokidar.watch([\"**/*.md\"], {\n cwd,\n ignoreInitial: true,\n awaitWriteFinish: {\n stabilityThreshold: 200,\n pollInterval: 50,\n },\n });\n\n const schedule = debounce(async () => {\n await rebuild(cwd, outputPath);\n }, 150);\n\n watcher.on(\"all\", (event, filePath) => {\n logEvent(\"info\", \"watch.change\", { event, file: filePath });\n schedule();\n });\n}\n\nasync function rebuild(\n sourceDir: string,\n outputPath?: string,\n): Promise<BuildResult | void> {\n try {\n const result = await buildManifestFile({ sourceDir, outputPath });\n logEvent(\"info\", \"manifest.updated\", {\n outputPath: result.outputPath,\n snippetCount: result.manifest.length,\n });\n return result;\n } catch (error) {\n const err = error as Error;\n logEvent(\"error\", \"manifest.update_failed\", {\n message: err.message,\n stack: err.stack,\n });\n }\n}\n\nfunction debounce<T extends (...args: unknown[]) => Promise<unknown> | void>(\n fn: T,\n delay: number,\n) {\n let timer: NodeJS.Timeout | null = null;\n return (...args: Parameters<T>) => {\n if (timer) {\n clearTimeout(timer);\n }\n timer = setTimeout(() => {\n timer = null;\n void fn(...args);\n }, delay);\n };\n}\n","export const brand = \"mark↓\";\n\nexport type LogLevel = \"info\" | \"warn\" | \"error\";\n\nexport interface LogFields {\n message?: string;\n [key: string]: unknown;\n}\n\nexport function logEvent(\n level: LogLevel,\n event: string,\n fields: LogFields = {},\n) {\n const entry = {\n brand,\n level,\n event,\n timestamp: new Date().toISOString(),\n ...fields,\n };\n const output = `${JSON.stringify(entry)}\\n`;\n const stream = level === \"error\" ? process.stderr : process.stdout;\n stream.write(output);\n}\n\nexport function log(message: string, fields?: LogFields) {\n if (fields) {\n logEvent(\"info\", message, fields);\n return;\n }\n logEvent(\"info\", \"message\", { message });\n}\n\nexport function logError(message: string, fields?: LogFields) {\n if (fields) {\n logEvent(\"error\", message, fields);\n return;\n }\n logEvent(\"error\", \"message\", { message });\n}\n","import fs from \"node:fs/promises\";\nimport path from \"node:path\";\nimport { load as loadHtml } from \"cheerio\";\nimport {\n parseFrontMatter,\n renderMarkdown,\n sanitizeMarkup,\n type SanitizeOptions,\n type SnippetMeta,\n} from \"@mzebley/mark-down\";\nimport { logEvent } from \"./logger.js\";\n\nexport interface CompilePageOptions {\n manifest?: string;\n outDir?: string;\n inPlace?: boolean;\n sanitize?: SanitizeOptions;\n}\n\nconst DEFAULT_OUT_DIR = \"dist\";\n\nexport async function compilePage(\n inputHtml: string,\n options: CompilePageOptions = {},\n): Promise<string> {\n const sourcePath = path.resolve(inputHtml);\n await assertExists(\n sourcePath,\n `Input HTML file not found at '${inputHtml}'.`,\n );\n\n const manifestPath = await resolveManifestPath(sourcePath, options.manifest);\n const manifestDir = path.dirname(manifestPath);\n const manifest = await loadManifest(manifestPath);\n\n const rawHtml = await fs.readFile(sourcePath, \"utf8\");\n const doctypeMatch = rawHtml.match(/^(<!doctype[^>]*>\\s*)/i);\n const doctype = doctypeMatch?.[1] ?? \"\";\n const dom = loadHtml(rawHtml);\n\n const targets = dom(\"[data-snippet]\").toArray();\n for (const node of targets) {\n const element = dom(node);\n const slug = element.attr(\"data-snippet\");\n if (!slug) {\n continue;\n }\n const entry = manifest.find((item) => item.slug === slug);\n if (!entry) {\n console.warn(`mark↓: no snippet found for \"${slug}\"`);\n continue;\n }\n\n const snippetPath = path.resolve(manifestDir, entry.path);\n let raw: string;\n try {\n raw = await fs.readFile(snippetPath, \"utf8\");\n } catch (error) {\n console.warn(`mark↓: failed to read snippet at '${entry.path}'`, error);\n continue;\n }\n\n let body = raw;\n let frontMatterSlug: string | undefined;\n try {\n const frontMatter = parseFrontMatter(raw);\n body = frontMatter.content;\n frontMatterSlug = frontMatter.slug;\n } catch (error) {\n console.warn(\n `mark↓: failed to parse front matter for '${entry.path}'`,\n error,\n );\n }\n\n let html = renderMarkdown(body);\n if (options.sanitize) {\n html = sanitizeMarkup(html, options.sanitize);\n }\n element.html(html);\n\n if (!element.attr(\"id\")) {\n element.attr(\"id\", frontMatterSlug ?? `snippet-${slug}`);\n }\n }\n\n const outputDir = options.inPlace\n ? path.dirname(sourcePath)\n : path.resolve(options.outDir ?? DEFAULT_OUT_DIR);\n if (!options.inPlace) {\n await fs.mkdir(outputDir, { recursive: true });\n }\n const outputPath = options.inPlace\n ? sourcePath\n : path.join(outputDir, path.basename(sourcePath));\n\n const outputHtml = `${doctype}${dom.html() ?? \"\"}`;\n await fs.writeFile(outputPath, outputHtml);\n\n logEvent(\"info\", \"compile_page.written\", { outputPath });\n return outputPath;\n}\n\nasync function resolveManifestPath(\n inputHtml: string,\n manifestFlag?: string,\n): Promise<string> {\n const manifestPath = manifestFlag\n ? path.resolve(manifestFlag)\n : path.join(path.dirname(path.resolve(inputHtml)), \"snippets-index.json\");\n await assertExists(\n manifestPath,\n `Manifest file not found at '${manifestPath}'.`,\n );\n return manifestPath;\n}\n\nasync function loadManifest(manifestPath: string): Promise<SnippetMeta[]> {\n let raw: string;\n try {\n raw = await fs.readFile(manifestPath, \"utf8\");\n } catch (error) {\n throw new Error(\n `Failed to read manifest at '${manifestPath}': ${String(error)}`,\n );\n }\n\n try {\n const parsed = JSON.parse(raw);\n if (!Array.isArray(parsed)) {\n throw new Error(\"Manifest must be a JSON array.\");\n }\n return parsed as SnippetMeta[];\n } catch (error) {\n throw new Error(\n `Failed to parse manifest at '${manifestPath}': ${String(error)}`,\n );\n }\n}\n\nasync function assertExists(target: string, message: string) {\n try {\n await fs.access(target);\n } catch {\n throw new Error(message);\n }\n}\n"],"mappings":"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","names":["YAML","path","fs","fg","matter","import_node_path","path","chokidar","import_promises","import_node_path","import_mark_down","path","fs","loadHtml"]}
package/dist/index.mjs CHANGED
@@ -88,7 +88,7 @@ function createSnippet(relativePath, frontMatter) {
88
88
  function normalizeKnownFields(data) {
89
89
  return {
90
90
  title: typeof data.title === "string" ? data.title : void 0,
91
- order: typeof data.order === "number" ? data.order : data.order === null ? null : void 0,
91
+ order: typeof data.order === "number" ? data.order : void 0,
92
92
  type: typeof data.type === "string" ? data.type : void 0,
93
93
  tags: normalizeTags(data.tags),
94
94
  draft: data.draft === true ? true : void 0
@@ -237,7 +237,7 @@ async function compilePage(inputHtml, options = {}) {
237
237
  const rawHtml = await fs2.readFile(sourcePath, "utf8");
238
238
  const doctypeMatch = rawHtml.match(/^(<!doctype[^>]*>\s*)/i);
239
239
  const doctype = doctypeMatch?.[1] ?? "";
240
- const dom = loadHtml(rawHtml, { decodeEntities: false });
240
+ const dom = loadHtml(rawHtml);
241
241
  const targets = dom("[data-snippet]").toArray();
242
242
  for (const node of targets) {
243
243
  const element = dom(node);
@@ -328,7 +328,7 @@ async function assertExists(target, message) {
328
328
 
329
329
  // src/index.ts
330
330
  var program = new Command();
331
- program.name("mark-down").description(`${brand} CLI for building snippet manifests`).version("1.2.1");
331
+ program.name("mark-down").description(`${brand} CLI for building snippet manifests`).version("1.2.3");
332
332
  program.command("build").argument("[sourceDir]", "directory containing snippets", "content/snippets").option("-o, --output <path>", "where to write snippets-index.json").action(async (sourceDir, options) => {
333
333
  try {
334
334
  const result = await buildManifestFile({
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts","../src/manifest.ts","../src/errors.ts","../src/watch.ts","../src/logger.ts","../src/compile-page.ts"],"sourcesContent":["#!/usr/bin/env node\nimport { Command } from \"commander\";\nimport { buildManifestFile } from \"./manifest.js\";\nimport { watch as watchSnippets } from \"./watch.js\";\nimport { brand, logEvent } from \"./logger.js\";\nimport { DuplicateSlugError } from \"./errors.js\";\nimport { compilePage } from \"./compile-page.js\";\nimport type { SanitizeOptions, SanitizePolicy } from \"@mzebley/mark-down\";\n\nconst program = new Command();\nprogram\n .name(\"mark-down\")\n .description(`${brand} CLI for building snippet manifests`)\n .version(\"1.2.1\");\n\nprogram\n .command(\"build\")\n .argument(\"[sourceDir]\", \"directory containing snippets\", \"content/snippets\")\n .option(\"-o, --output <path>\", \"where to write snippets-index.json\")\n .action(async (sourceDir: string, options: { output?: string }) => {\n try {\n const result = await buildManifestFile({\n sourceDir,\n outputPath: options.output,\n });\n logEvent(\"info\", \"manifest.written\", {\n outputPath: result.outputPath,\n snippetCount: result.manifest.length,\n });\n } catch (error) {\n handleError(error);\n }\n });\n\nprogram\n .command(\"watch\")\n .argument(\"[sourceDir]\", \"directory containing snippets\", \"content/snippets\")\n .option(\"-o, --output <path>\", \"where to write snippets-index.json\")\n .action(async (sourceDir: string, options: { output?: string }) => {\n try {\n await watchSnippets(sourceDir, options.output);\n } catch (error) {\n handleError(error);\n }\n });\n\nprogram\n .command(\"compile-page\")\n .argument(\"<inputHtml>\", \"HTML file containing data-snippet placeholders\")\n .option(\"--manifest <path>\", \"path to snippets-index.json\")\n .option(\"--outDir <path>\", \"output directory for compiled HTML\", \"dist\")\n .option(\n \"--inPlace\",\n \"overwrite the input HTML file instead of writing to outDir\",\n )\n .option(\n \"--sanitize [policy]\",\n \"sanitize rendered HTML (default|strict|permissive)\",\n )\n .action(\n async (\n inputHtml: string,\n options: {\n manifest?: string;\n outDir?: string;\n inPlace?: boolean;\n sanitize?: string | boolean;\n },\n ) => {\n try {\n await compilePage(inputHtml, {\n manifest: options.manifest,\n outDir: options.outDir,\n inPlace: options.inPlace,\n sanitize: resolveSanitizeOption(options.sanitize),\n });\n } catch (error) {\n handleError(error);\n }\n },\n );\n\nprogram.parseAsync(process.argv).catch(handleError);\n\nfunction handleError(error: unknown) {\n const err = error as Error;\n if (err instanceof DuplicateSlugError) {\n logEvent(\"error\", \"manifest.duplicate_slug\", {\n message: err.message,\n slugs: err.duplicates,\n });\n process.exit(2);\n }\n logEvent(\"error\", \"cli.error\", {\n message: err.message,\n stack: err.stack,\n });\n process.exit(1);\n}\n\nfunction resolveSanitizeOption(\n value?: string | boolean,\n): SanitizeOptions | undefined {\n if (!value) {\n return undefined;\n }\n if (value === true) {\n return { policy: \"default\" };\n }\n const policy = value.trim();\n if (!policy) {\n return { policy: \"default\" };\n }\n if (!isSanitizePolicy(policy)) {\n throw new Error(`Unknown sanitize policy '${policy}'.`);\n }\n return { policy };\n}\n\nfunction isSanitizePolicy(value: string): value is SanitizePolicy {\n return value === \"default\" || value === \"strict\" || value === \"permissive\";\n}\n","import fs from \"node:fs/promises\";\nimport path from \"node:path\";\nimport fg from \"fast-glob\";\nimport matter from \"gray-matter\";\nimport YAML from \"yaml\";\nimport { normalizeSlug, type SnippetMeta } from \"@mzebley/mark-down\";\nimport { DuplicateSlugError } from \"./errors.js\";\n\nconst MATTER_OPTIONS = {\n engines: {\n yaml: (source: string) => YAML.parse(source) ?? {},\n },\n};\n\nexport interface BuildOptions {\n sourceDir: string;\n outputPath?: string;\n}\n\nexport interface BuildResult {\n manifest: SnippetMeta[];\n outputPath: string;\n}\n\nexport async function buildManifestFile(\n options: BuildOptions,\n): Promise<BuildResult> {\n const manifest = await buildManifest(options.sourceDir);\n const target =\n options.outputPath ?? path.join(options.sourceDir, \"snippets-index.json\");\n await fs.writeFile(target, JSON.stringify(manifest, null, 2));\n return { manifest, outputPath: target };\n}\n\nexport async function buildManifest(sourceDir: string): Promise<SnippetMeta[]> {\n const cwd = path.resolve(sourceDir);\n const files = await fg([\"**/*.md\"], { cwd, absolute: true });\n const manifest: SnippetMeta[] = [];\n\n for (const absolutePath of files) {\n const relativePath = path.relative(cwd, absolutePath);\n const normalizedPath = toPosix(relativePath);\n try {\n const content = await fs.readFile(absolutePath, \"utf8\");\n const parsed = matter(content, MATTER_OPTIONS);\n const snippet = createSnippet(normalizedPath, parsed.data ?? {});\n if (snippet.draft) {\n continue;\n }\n manifest.push(snippet);\n } catch (error) {\n console.warn(\n `mark↓: failed to load snippet at '${normalizedPath}'`,\n error,\n );\n }\n }\n\n ensureUniqueSlugs(manifest);\n\n manifest.sort((a, b) => {\n const orderA =\n typeof a.order === \"number\" ? a.order : Number.POSITIVE_INFINITY;\n const orderB =\n typeof b.order === \"number\" ? b.order : Number.POSITIVE_INFINITY;\n if (orderA !== orderB) {\n return orderA - orderB;\n }\n const titleA = a.title?.toLowerCase() ?? \"\";\n const titleB = b.title?.toLowerCase() ?? \"\";\n return titleA.localeCompare(titleB);\n });\n\n return manifest;\n}\n\nexport function createSnippet(\n relativePath: string,\n frontMatter: Record<string, unknown>,\n): SnippetMeta {\n const group = deriveGroup(relativePath);\n const slugSource =\n typeof frontMatter.slug === \"string\" && frontMatter.slug.trim().length\n ? frontMatter.slug\n : relativePath.replace(/\\.md$/i, \"\");\n const slug = normalizeSlug(slugSource);\n\n const { title, order, type, tags, draft } = normalizeKnownFields(frontMatter);\n const extra = collectExtra(frontMatter);\n\n return {\n slug,\n title,\n order,\n type,\n tags,\n draft,\n path: relativePath,\n group,\n extra,\n };\n}\n\nfunction normalizeKnownFields(data: Record<string, unknown>) {\n return {\n title: typeof data.title === \"string\" ? data.title : undefined,\n order:\n typeof data.order === \"number\"\n ? data.order\n : data.order === null\n ? null\n : undefined,\n type: typeof data.type === \"string\" ? data.type : undefined,\n tags: normalizeTags(data.tags),\n draft: data.draft === true ? true : undefined,\n };\n}\n\nfunction collectExtra(\n data: Record<string, unknown>,\n): Record<string, unknown> | undefined {\n const extra: Record<string, unknown> = {};\n const reserved = new Set([\"slug\", \"title\", \"order\", \"type\", \"tags\", \"draft\"]);\n for (const [key, value] of Object.entries(data)) {\n if (reserved.has(key)) {\n continue;\n }\n extra[key] = value;\n }\n return Object.keys(extra).length ? extra : undefined;\n}\n\nfunction normalizeTags(value: unknown): string[] | undefined {\n if (!value) {\n return undefined;\n }\n if (Array.isArray(value)) {\n return value.map((entry) => String(entry));\n }\n if (typeof value === \"string\") {\n return value\n .split(\",\")\n .map((entry) => entry.trim())\n .filter(Boolean);\n }\n return undefined;\n}\n\nfunction deriveGroup(relativePath: string): string {\n const dirname = toPosix(path.dirname(relativePath));\n if (dirname === \".\" || !dirname.length) {\n return \"root\";\n }\n return dirname;\n}\n\nfunction toPosix(value: string): string {\n return value.split(path.sep).join(\"/\");\n}\n\nfunction ensureUniqueSlugs(manifest: SnippetMeta[]) {\n const seen = new Map<string, string>();\n const duplicates = new Set<string>();\n for (const snippet of manifest) {\n if (seen.has(snippet.slug)) {\n duplicates.add(snippet.slug);\n } else {\n seen.set(snippet.slug, snippet.path);\n }\n }\n if (duplicates.size) {\n throw new DuplicateSlugError([...duplicates.values()]);\n }\n}\n","export class DuplicateSlugError extends Error {\n readonly duplicates: string[];\n\n constructor(duplicates: string[]) {\n super(`Duplicate slugs detected: ${duplicates.join(\", \")}`);\n this.name = \"DuplicateSlugError\";\n this.duplicates = duplicates;\n }\n}\n","import path from \"node:path\";\nimport chokidar from \"chokidar\";\nimport { buildManifestFile, type BuildResult } from \"./manifest.js\";\nimport { logEvent } from \"./logger.js\";\n\nexport async function watch(sourceDir: string, outputPath?: string) {\n const cwd = path.resolve(sourceDir);\n logEvent(\"info\", \"watch.start\", {\n directory: cwd,\n outputPath: outputPath ?? path.join(cwd, \"snippets-index.json\"),\n });\n await rebuild(cwd, outputPath);\n\n const watcher = chokidar.watch([\"**/*.md\"], {\n cwd,\n ignoreInitial: true,\n awaitWriteFinish: {\n stabilityThreshold: 200,\n pollInterval: 50,\n },\n });\n\n const schedule = debounce(async () => {\n await rebuild(cwd, outputPath);\n }, 150);\n\n watcher.on(\"all\", (event, filePath) => {\n logEvent(\"info\", \"watch.change\", { event, file: filePath });\n schedule();\n });\n}\n\nasync function rebuild(\n sourceDir: string,\n outputPath?: string,\n): Promise<BuildResult | void> {\n try {\n const result = await buildManifestFile({ sourceDir, outputPath });\n logEvent(\"info\", \"manifest.updated\", {\n outputPath: result.outputPath,\n snippetCount: result.manifest.length,\n });\n return result;\n } catch (error) {\n const err = error as Error;\n logEvent(\"error\", \"manifest.update_failed\", {\n message: err.message,\n stack: err.stack,\n });\n }\n}\n\nfunction debounce<T extends (...args: unknown[]) => Promise<unknown> | void>(\n fn: T,\n delay: number,\n) {\n let timer: NodeJS.Timeout | null = null;\n return (...args: Parameters<T>) => {\n if (timer) {\n clearTimeout(timer);\n }\n timer = setTimeout(() => {\n timer = null;\n void fn(...args);\n }, delay);\n };\n}\n","export const brand = \"mark↓\";\n\nexport type LogLevel = \"info\" | \"warn\" | \"error\";\n\nexport interface LogFields {\n message?: string;\n [key: string]: unknown;\n}\n\nexport function logEvent(\n level: LogLevel,\n event: string,\n fields: LogFields = {},\n) {\n const entry = {\n brand,\n level,\n event,\n timestamp: new Date().toISOString(),\n ...fields,\n };\n const output = `${JSON.stringify(entry)}\\n`;\n const stream = level === \"error\" ? process.stderr : process.stdout;\n stream.write(output);\n}\n\nexport function log(message: string, fields?: LogFields) {\n if (fields) {\n logEvent(\"info\", message, fields);\n return;\n }\n logEvent(\"info\", \"message\", { message });\n}\n\nexport function logError(message: string, fields?: LogFields) {\n if (fields) {\n logEvent(\"error\", message, fields);\n return;\n }\n logEvent(\"error\", \"message\", { message });\n}\n","import fs from \"node:fs/promises\";\nimport path from \"node:path\";\nimport { load as loadHtml } from \"cheerio\";\nimport {\n parseFrontMatter,\n renderMarkdown,\n sanitizeMarkup,\n type SanitizeOptions,\n type SnippetMeta,\n} from \"@mzebley/mark-down\";\nimport { logEvent } from \"./logger.js\";\n\nexport interface CompilePageOptions {\n manifest?: string;\n outDir?: string;\n inPlace?: boolean;\n sanitize?: SanitizeOptions;\n}\n\nconst DEFAULT_OUT_DIR = \"dist\";\n\nexport async function compilePage(\n inputHtml: string,\n options: CompilePageOptions = {},\n): Promise<string> {\n const sourcePath = path.resolve(inputHtml);\n await assertExists(\n sourcePath,\n `Input HTML file not found at '${inputHtml}'.`,\n );\n\n const manifestPath = await resolveManifestPath(sourcePath, options.manifest);\n const manifestDir = path.dirname(manifestPath);\n const manifest = await loadManifest(manifestPath);\n\n const rawHtml = await fs.readFile(sourcePath, \"utf8\");\n const doctypeMatch = rawHtml.match(/^(<!doctype[^>]*>\\s*)/i);\n const doctype = doctypeMatch?.[1] ?? \"\";\n const dom = loadHtml(rawHtml, { decodeEntities: false });\n\n const targets = dom(\"[data-snippet]\").toArray();\n for (const node of targets) {\n const element = dom(node);\n const slug = element.attr(\"data-snippet\");\n if (!slug) {\n continue;\n }\n const entry = manifest.find((item) => item.slug === slug);\n if (!entry) {\n console.warn(`mark↓: no snippet found for \"${slug}\"`);\n continue;\n }\n\n const snippetPath = path.resolve(manifestDir, entry.path);\n let raw: string;\n try {\n raw = await fs.readFile(snippetPath, \"utf8\");\n } catch (error) {\n console.warn(`mark↓: failed to read snippet at '${entry.path}'`, error);\n continue;\n }\n\n let body = raw;\n let frontMatterSlug: string | undefined;\n try {\n const frontMatter = parseFrontMatter(raw);\n body = frontMatter.content;\n frontMatterSlug = frontMatter.slug;\n } catch (error) {\n console.warn(\n `mark↓: failed to parse front matter for '${entry.path}'`,\n error,\n );\n }\n\n let html = renderMarkdown(body);\n if (options.sanitize) {\n html = sanitizeMarkup(html, options.sanitize);\n }\n element.html(html);\n\n if (!element.attr(\"id\")) {\n element.attr(\"id\", frontMatterSlug ?? `snippet-${slug}`);\n }\n }\n\n const outputDir = options.inPlace\n ? path.dirname(sourcePath)\n : path.resolve(options.outDir ?? DEFAULT_OUT_DIR);\n if (!options.inPlace) {\n await fs.mkdir(outputDir, { recursive: true });\n }\n const outputPath = options.inPlace\n ? sourcePath\n : path.join(outputDir, path.basename(sourcePath));\n\n const outputHtml = `${doctype}${dom.html() ?? \"\"}`;\n await fs.writeFile(outputPath, outputHtml);\n\n logEvent(\"info\", \"compile_page.written\", { outputPath });\n return outputPath;\n}\n\nasync function resolveManifestPath(\n inputHtml: string,\n manifestFlag?: string,\n): Promise<string> {\n const manifestPath = manifestFlag\n ? path.resolve(manifestFlag)\n : path.join(path.dirname(path.resolve(inputHtml)), \"snippets-index.json\");\n await assertExists(\n manifestPath,\n `Manifest file not found at '${manifestPath}'.`,\n );\n return manifestPath;\n}\n\nasync function loadManifest(manifestPath: string): Promise<SnippetMeta[]> {\n let raw: string;\n try {\n raw = await fs.readFile(manifestPath, \"utf8\");\n } catch (error) {\n throw new Error(\n `Failed to read manifest at '${manifestPath}': ${String(error)}`,\n );\n }\n\n try {\n const parsed = JSON.parse(raw);\n if (!Array.isArray(parsed)) {\n throw new Error(\"Manifest must be a JSON array.\");\n }\n return parsed as SnippetMeta[];\n } catch (error) {\n throw new Error(\n `Failed to parse manifest at '${manifestPath}': ${String(error)}`,\n );\n }\n}\n\nasync function assertExists(target: string, message: string) {\n try {\n await fs.access(target);\n } catch {\n throw new Error(message);\n }\n}\n"],"mappings":"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","names":["path","path","fs","path","path","fs"]}
1
+ {"version":3,"sources":["../src/index.ts","../src/manifest.ts","../src/errors.ts","../src/watch.ts","../src/logger.ts","../src/compile-page.ts"],"sourcesContent":["#!/usr/bin/env node\nimport { Command } from \"commander\";\nimport { buildManifestFile } from \"./manifest.js\";\nimport { watch as watchSnippets } from \"./watch.js\";\nimport { brand, logEvent } from \"./logger.js\";\nimport { DuplicateSlugError } from \"./errors.js\";\nimport { compilePage } from \"./compile-page.js\";\nimport type { SanitizeOptions, SanitizePolicy } from \"@mzebley/mark-down\";\n\nconst program = new Command();\nprogram\n .name(\"mark-down\")\n .description(`${brand} CLI for building snippet manifests`)\n .version(\"1.2.3\");\n\nprogram\n .command(\"build\")\n .argument(\"[sourceDir]\", \"directory containing snippets\", \"content/snippets\")\n .option(\"-o, --output <path>\", \"where to write snippets-index.json\")\n .action(async (sourceDir: string, options: { output?: string }) => {\n try {\n const result = await buildManifestFile({\n sourceDir,\n outputPath: options.output,\n });\n logEvent(\"info\", \"manifest.written\", {\n outputPath: result.outputPath,\n snippetCount: result.manifest.length,\n });\n } catch (error) {\n handleError(error);\n }\n });\n\nprogram\n .command(\"watch\")\n .argument(\"[sourceDir]\", \"directory containing snippets\", \"content/snippets\")\n .option(\"-o, --output <path>\", \"where to write snippets-index.json\")\n .action(async (sourceDir: string, options: { output?: string }) => {\n try {\n await watchSnippets(sourceDir, options.output);\n } catch (error) {\n handleError(error);\n }\n });\n\nprogram\n .command(\"compile-page\")\n .argument(\"<inputHtml>\", \"HTML file containing data-snippet placeholders\")\n .option(\"--manifest <path>\", \"path to snippets-index.json\")\n .option(\"--outDir <path>\", \"output directory for compiled HTML\", \"dist\")\n .option(\n \"--inPlace\",\n \"overwrite the input HTML file instead of writing to outDir\",\n )\n .option(\n \"--sanitize [policy]\",\n \"sanitize rendered HTML (default|strict|permissive)\",\n )\n .action(\n async (\n inputHtml: string,\n options: {\n manifest?: string;\n outDir?: string;\n inPlace?: boolean;\n sanitize?: string | boolean;\n },\n ) => {\n try {\n await compilePage(inputHtml, {\n manifest: options.manifest,\n outDir: options.outDir,\n inPlace: options.inPlace,\n sanitize: resolveSanitizeOption(options.sanitize),\n });\n } catch (error) {\n handleError(error);\n }\n },\n );\n\nprogram.parseAsync(process.argv).catch(handleError);\n\nfunction handleError(error: unknown) {\n const err = error as Error;\n if (err instanceof DuplicateSlugError) {\n logEvent(\"error\", \"manifest.duplicate_slug\", {\n message: err.message,\n slugs: err.duplicates,\n });\n process.exit(2);\n }\n logEvent(\"error\", \"cli.error\", {\n message: err.message,\n stack: err.stack,\n });\n process.exit(1);\n}\n\nfunction resolveSanitizeOption(\n value?: string | boolean,\n): SanitizeOptions | undefined {\n if (!value) {\n return undefined;\n }\n if (value === true) {\n return { policy: \"default\" };\n }\n const policy = value.trim();\n if (!policy) {\n return { policy: \"default\" };\n }\n if (!isSanitizePolicy(policy)) {\n throw new Error(`Unknown sanitize policy '${policy}'.`);\n }\n return { policy };\n}\n\nfunction isSanitizePolicy(value: string): value is SanitizePolicy {\n return value === \"default\" || value === \"strict\" || value === \"permissive\";\n}\n","import fs from \"node:fs/promises\";\nimport path from \"node:path\";\nimport fg from \"fast-glob\";\nimport matter from \"gray-matter\";\nimport YAML from \"yaml\";\nimport { normalizeSlug, type SnippetMeta } from \"@mzebley/mark-down\";\nimport { DuplicateSlugError } from \"./errors.js\";\n\nconst MATTER_OPTIONS = {\n engines: {\n yaml: (source: string) => YAML.parse(source) ?? {},\n },\n};\n\nexport interface BuildOptions {\n sourceDir: string;\n outputPath?: string;\n}\n\nexport interface BuildResult {\n manifest: SnippetMeta[];\n outputPath: string;\n}\n\nexport async function buildManifestFile(\n options: BuildOptions,\n): Promise<BuildResult> {\n const manifest = await buildManifest(options.sourceDir);\n const target =\n options.outputPath ?? path.join(options.sourceDir, \"snippets-index.json\");\n await fs.writeFile(target, JSON.stringify(manifest, null, 2));\n return { manifest, outputPath: target };\n}\n\nexport async function buildManifest(sourceDir: string): Promise<SnippetMeta[]> {\n const cwd = path.resolve(sourceDir);\n const files = await fg([\"**/*.md\"], { cwd, absolute: true });\n const manifest: SnippetMeta[] = [];\n\n for (const absolutePath of files) {\n const relativePath = path.relative(cwd, absolutePath);\n const normalizedPath = toPosix(relativePath);\n try {\n const content = await fs.readFile(absolutePath, \"utf8\");\n const parsed = matter(content, MATTER_OPTIONS);\n const snippet = createSnippet(normalizedPath, parsed.data ?? {});\n if (snippet.draft) {\n continue;\n }\n manifest.push(snippet);\n } catch (error) {\n console.warn(\n `mark↓: failed to load snippet at '${normalizedPath}'`,\n error,\n );\n }\n }\n\n ensureUniqueSlugs(manifest);\n\n manifest.sort((a, b) => {\n const orderA =\n typeof a.order === \"number\" ? a.order : Number.POSITIVE_INFINITY;\n const orderB =\n typeof b.order === \"number\" ? b.order : Number.POSITIVE_INFINITY;\n if (orderA !== orderB) {\n return orderA - orderB;\n }\n const titleA = a.title?.toLowerCase() ?? \"\";\n const titleB = b.title?.toLowerCase() ?? \"\";\n return titleA.localeCompare(titleB);\n });\n\n return manifest;\n}\n\nexport function createSnippet(\n relativePath: string,\n frontMatter: Record<string, unknown>,\n): SnippetMeta {\n const group = deriveGroup(relativePath);\n const slugSource =\n typeof frontMatter.slug === \"string\" && frontMatter.slug.trim().length\n ? frontMatter.slug\n : relativePath.replace(/\\.md$/i, \"\");\n const slug = normalizeSlug(slugSource);\n\n const { title, order, type, tags, draft } = normalizeKnownFields(frontMatter);\n const extra = collectExtra(frontMatter);\n\n return {\n slug,\n title,\n order,\n type,\n tags,\n draft,\n path: relativePath,\n group,\n extra,\n };\n}\n\nfunction normalizeKnownFields(data: Record<string, unknown>) {\n return {\n title: typeof data.title === \"string\" ? data.title : undefined,\n order: typeof data.order === \"number\" ? data.order : undefined,\n type: typeof data.type === \"string\" ? data.type : undefined,\n tags: normalizeTags(data.tags),\n draft: data.draft === true ? true : undefined,\n };\n}\n\nfunction collectExtra(\n data: Record<string, unknown>,\n): Record<string, unknown> | undefined {\n const extra: Record<string, unknown> = {};\n const reserved = new Set([\"slug\", \"title\", \"order\", \"type\", \"tags\", \"draft\"]);\n for (const [key, value] of Object.entries(data)) {\n if (reserved.has(key)) {\n continue;\n }\n extra[key] = value;\n }\n return Object.keys(extra).length ? extra : undefined;\n}\n\nfunction normalizeTags(value: unknown): string[] | undefined {\n if (!value) {\n return undefined;\n }\n if (Array.isArray(value)) {\n return value.map((entry) => String(entry));\n }\n if (typeof value === \"string\") {\n return value\n .split(\",\")\n .map((entry) => entry.trim())\n .filter(Boolean);\n }\n return undefined;\n}\n\nfunction deriveGroup(relativePath: string): string {\n const dirname = toPosix(path.dirname(relativePath));\n if (dirname === \".\" || !dirname.length) {\n return \"root\";\n }\n return dirname;\n}\n\nfunction toPosix(value: string): string {\n return value.split(path.sep).join(\"/\");\n}\n\nfunction ensureUniqueSlugs(manifest: SnippetMeta[]) {\n const seen = new Map<string, string>();\n const duplicates = new Set<string>();\n for (const snippet of manifest) {\n if (seen.has(snippet.slug)) {\n duplicates.add(snippet.slug);\n } else {\n seen.set(snippet.slug, snippet.path);\n }\n }\n if (duplicates.size) {\n throw new DuplicateSlugError([...duplicates.values()]);\n }\n}\n","export class DuplicateSlugError extends Error {\n readonly duplicates: string[];\n\n constructor(duplicates: string[]) {\n super(`Duplicate slugs detected: ${duplicates.join(\", \")}`);\n this.name = \"DuplicateSlugError\";\n this.duplicates = duplicates;\n }\n}\n","import path from \"node:path\";\nimport chokidar from \"chokidar\";\nimport { buildManifestFile, type BuildResult } from \"./manifest.js\";\nimport { logEvent } from \"./logger.js\";\n\nexport async function watch(sourceDir: string, outputPath?: string) {\n const cwd = path.resolve(sourceDir);\n logEvent(\"info\", \"watch.start\", {\n directory: cwd,\n outputPath: outputPath ?? path.join(cwd, \"snippets-index.json\"),\n });\n await rebuild(cwd, outputPath);\n\n const watcher = chokidar.watch([\"**/*.md\"], {\n cwd,\n ignoreInitial: true,\n awaitWriteFinish: {\n stabilityThreshold: 200,\n pollInterval: 50,\n },\n });\n\n const schedule = debounce(async () => {\n await rebuild(cwd, outputPath);\n }, 150);\n\n watcher.on(\"all\", (event, filePath) => {\n logEvent(\"info\", \"watch.change\", { event, file: filePath });\n schedule();\n });\n}\n\nasync function rebuild(\n sourceDir: string,\n outputPath?: string,\n): Promise<BuildResult | void> {\n try {\n const result = await buildManifestFile({ sourceDir, outputPath });\n logEvent(\"info\", \"manifest.updated\", {\n outputPath: result.outputPath,\n snippetCount: result.manifest.length,\n });\n return result;\n } catch (error) {\n const err = error as Error;\n logEvent(\"error\", \"manifest.update_failed\", {\n message: err.message,\n stack: err.stack,\n });\n }\n}\n\nfunction debounce<T extends (...args: unknown[]) => Promise<unknown> | void>(\n fn: T,\n delay: number,\n) {\n let timer: NodeJS.Timeout | null = null;\n return (...args: Parameters<T>) => {\n if (timer) {\n clearTimeout(timer);\n }\n timer = setTimeout(() => {\n timer = null;\n void fn(...args);\n }, delay);\n };\n}\n","export const brand = \"mark↓\";\n\nexport type LogLevel = \"info\" | \"warn\" | \"error\";\n\nexport interface LogFields {\n message?: string;\n [key: string]: unknown;\n}\n\nexport function logEvent(\n level: LogLevel,\n event: string,\n fields: LogFields = {},\n) {\n const entry = {\n brand,\n level,\n event,\n timestamp: new Date().toISOString(),\n ...fields,\n };\n const output = `${JSON.stringify(entry)}\\n`;\n const stream = level === \"error\" ? process.stderr : process.stdout;\n stream.write(output);\n}\n\nexport function log(message: string, fields?: LogFields) {\n if (fields) {\n logEvent(\"info\", message, fields);\n return;\n }\n logEvent(\"info\", \"message\", { message });\n}\n\nexport function logError(message: string, fields?: LogFields) {\n if (fields) {\n logEvent(\"error\", message, fields);\n return;\n }\n logEvent(\"error\", \"message\", { message });\n}\n","import fs from \"node:fs/promises\";\nimport path from \"node:path\";\nimport { load as loadHtml } from \"cheerio\";\nimport {\n parseFrontMatter,\n renderMarkdown,\n sanitizeMarkup,\n type SanitizeOptions,\n type SnippetMeta,\n} from \"@mzebley/mark-down\";\nimport { logEvent } from \"./logger.js\";\n\nexport interface CompilePageOptions {\n manifest?: string;\n outDir?: string;\n inPlace?: boolean;\n sanitize?: SanitizeOptions;\n}\n\nconst DEFAULT_OUT_DIR = \"dist\";\n\nexport async function compilePage(\n inputHtml: string,\n options: CompilePageOptions = {},\n): Promise<string> {\n const sourcePath = path.resolve(inputHtml);\n await assertExists(\n sourcePath,\n `Input HTML file not found at '${inputHtml}'.`,\n );\n\n const manifestPath = await resolveManifestPath(sourcePath, options.manifest);\n const manifestDir = path.dirname(manifestPath);\n const manifest = await loadManifest(manifestPath);\n\n const rawHtml = await fs.readFile(sourcePath, \"utf8\");\n const doctypeMatch = rawHtml.match(/^(<!doctype[^>]*>\\s*)/i);\n const doctype = doctypeMatch?.[1] ?? \"\";\n const dom = loadHtml(rawHtml);\n\n const targets = dom(\"[data-snippet]\").toArray();\n for (const node of targets) {\n const element = dom(node);\n const slug = element.attr(\"data-snippet\");\n if (!slug) {\n continue;\n }\n const entry = manifest.find((item) => item.slug === slug);\n if (!entry) {\n console.warn(`mark↓: no snippet found for \"${slug}\"`);\n continue;\n }\n\n const snippetPath = path.resolve(manifestDir, entry.path);\n let raw: string;\n try {\n raw = await fs.readFile(snippetPath, \"utf8\");\n } catch (error) {\n console.warn(`mark↓: failed to read snippet at '${entry.path}'`, error);\n continue;\n }\n\n let body = raw;\n let frontMatterSlug: string | undefined;\n try {\n const frontMatter = parseFrontMatter(raw);\n body = frontMatter.content;\n frontMatterSlug = frontMatter.slug;\n } catch (error) {\n console.warn(\n `mark↓: failed to parse front matter for '${entry.path}'`,\n error,\n );\n }\n\n let html = renderMarkdown(body);\n if (options.sanitize) {\n html = sanitizeMarkup(html, options.sanitize);\n }\n element.html(html);\n\n if (!element.attr(\"id\")) {\n element.attr(\"id\", frontMatterSlug ?? `snippet-${slug}`);\n }\n }\n\n const outputDir = options.inPlace\n ? path.dirname(sourcePath)\n : path.resolve(options.outDir ?? DEFAULT_OUT_DIR);\n if (!options.inPlace) {\n await fs.mkdir(outputDir, { recursive: true });\n }\n const outputPath = options.inPlace\n ? sourcePath\n : path.join(outputDir, path.basename(sourcePath));\n\n const outputHtml = `${doctype}${dom.html() ?? \"\"}`;\n await fs.writeFile(outputPath, outputHtml);\n\n logEvent(\"info\", \"compile_page.written\", { outputPath });\n return outputPath;\n}\n\nasync function resolveManifestPath(\n inputHtml: string,\n manifestFlag?: string,\n): Promise<string> {\n const manifestPath = manifestFlag\n ? path.resolve(manifestFlag)\n : path.join(path.dirname(path.resolve(inputHtml)), \"snippets-index.json\");\n await assertExists(\n manifestPath,\n `Manifest file not found at '${manifestPath}'.`,\n );\n return manifestPath;\n}\n\nasync function loadManifest(manifestPath: string): Promise<SnippetMeta[]> {\n let raw: string;\n try {\n raw = await fs.readFile(manifestPath, \"utf8\");\n } catch (error) {\n throw new Error(\n `Failed to read manifest at '${manifestPath}': ${String(error)}`,\n );\n }\n\n try {\n const parsed = JSON.parse(raw);\n if (!Array.isArray(parsed)) {\n throw new Error(\"Manifest must be a JSON array.\");\n }\n return parsed as SnippetMeta[];\n } catch (error) {\n throw new Error(\n `Failed to parse manifest at '${manifestPath}': ${String(error)}`,\n );\n }\n}\n\nasync function assertExists(target: string, message: string) {\n try {\n await fs.access(target);\n } catch {\n throw new Error(message);\n }\n}\n"],"mappings":"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","names":["path","path","fs","path","path","fs"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mzebley/mark-down-cli",
3
- "version": "1.2.3",
3
+ "version": "1.2.4",
4
4
  "description": "mark↓ CLI for building snippet manifests",
5
5
  "type": "module",
6
6
  "bin": {