@mzebley/mark-down-cli 1.2.2 → 1.2.3
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 +4 -2
- package/dist/index.cjs +77 -23
- package/dist/index.cjs.map +1 -1
- package/dist/index.mjs +82 -24
- package/dist/index.mjs.map +1 -1
- package/package.json +4 -2
package/README.md
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
# mark↓ CLI
|
|
2
|
-
|
|
2
|
+
|
|
3
|
+
_(published as `@mzebley/mark-down-cli`)_
|
|
3
4
|
|
|
4
5
|
`mark-down` is the command-line companion to the mark↓ runtime. It scans Markdown snippets, parses YAML front matter, and emits a sorted `snippets-index.json` manifest consumed by [the core client](../core/README.md). For a full project overview, see the [monorepo README](../../README.md).
|
|
5
6
|
|
|
@@ -59,7 +60,7 @@ The CLI walks the directory tree, gathers front matter, and writes `snippets-ind
|
|
|
59
60
|
- Resolves snippet metadata from `snippets-index.json` (auto-detected next to the HTML file or provided via `--manifest`).
|
|
60
61
|
- Loads Markdown from disk, strips front matter, and renders HTML with the same `marked` pipeline as the runtime.
|
|
61
62
|
- Injects the rendered HTML as the element `innerHTML` and writes the result to `dist/<file>.html` by default.
|
|
62
|
-
- Use `--outDir` to change the output directory
|
|
63
|
+
- Use `--outDir` to change the output directory, `--inPlace` to overwrite the source file, or `--sanitize` to apply HTML sanitization.
|
|
63
64
|
- Unknown slugs are left untouched and logged as warnings. Table-of-contents generation remains a runtime concern.
|
|
64
65
|
|
|
65
66
|
## Configuration options
|
|
@@ -73,6 +74,7 @@ The CLI stays intentionally small so it can be composed inside any toolchain. Cu
|
|
|
73
74
|
- `--manifest <path>` – path to `snippets-index.json`. Defaults to the file next to `<inputHtml>`.
|
|
74
75
|
- `--outDir <dir>` – output directory for compiled HTML. Defaults to `dist`.
|
|
75
76
|
- `--inPlace` – overwrite the input HTML file instead of writing to `dist/`.
|
|
77
|
+
- `--sanitize [policy]` – sanitize rendered HTML (`default`, `strict`, or `permissive`). Omitting the policy defaults to `default`.
|
|
76
78
|
|
|
77
79
|
Add flags directly after the command (`mark-down build content/snippets -o public/snippets-index.json`). Package scripts can capture these options as well.
|
|
78
80
|
|
package/dist/index.cjs
CHANGED
|
@@ -62,13 +62,20 @@ async function buildManifest(sourceDir) {
|
|
|
62
62
|
for (const absolutePath of files) {
|
|
63
63
|
const relativePath = import_node_path.default.relative(cwd, absolutePath);
|
|
64
64
|
const normalizedPath = toPosix(relativePath);
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
65
|
+
try {
|
|
66
|
+
const content = await import_promises.default.readFile(absolutePath, "utf8");
|
|
67
|
+
const parsed = (0, import_gray_matter.default)(content, MATTER_OPTIONS);
|
|
68
|
+
const snippet = createSnippet(normalizedPath, parsed.data ?? {});
|
|
69
|
+
if (snippet.draft) {
|
|
70
|
+
continue;
|
|
71
|
+
}
|
|
72
|
+
manifest.push(snippet);
|
|
73
|
+
} catch (error) {
|
|
74
|
+
console.warn(
|
|
75
|
+
`mark\u2193: failed to load snippet at '${normalizedPath}'`,
|
|
76
|
+
error
|
|
77
|
+
);
|
|
70
78
|
}
|
|
71
|
-
manifest.push(snippet);
|
|
72
79
|
}
|
|
73
80
|
ensureUniqueSlugs(manifest);
|
|
74
81
|
manifest.sort((a, b) => {
|
|
@@ -239,7 +246,10 @@ var import_mark_down2 = require("@mzebley/mark-down");
|
|
|
239
246
|
var DEFAULT_OUT_DIR = "dist";
|
|
240
247
|
async function compilePage(inputHtml, options = {}) {
|
|
241
248
|
const sourcePath = import_node_path3.default.resolve(inputHtml);
|
|
242
|
-
await assertExists(
|
|
249
|
+
await assertExists(
|
|
250
|
+
sourcePath,
|
|
251
|
+
`Input HTML file not found at '${inputHtml}'.`
|
|
252
|
+
);
|
|
243
253
|
const manifestPath = await resolveManifestPath(sourcePath, options.manifest);
|
|
244
254
|
const manifestDir = import_node_path3.default.dirname(manifestPath);
|
|
245
255
|
const manifest = await loadManifest(manifestPath);
|
|
@@ -274,9 +284,15 @@ async function compilePage(inputHtml, options = {}) {
|
|
|
274
284
|
body = frontMatter.content;
|
|
275
285
|
frontMatterSlug = frontMatter.slug;
|
|
276
286
|
} catch (error) {
|
|
277
|
-
console.warn(
|
|
287
|
+
console.warn(
|
|
288
|
+
`mark\u2193: failed to parse front matter for '${entry.path}'`,
|
|
289
|
+
error
|
|
290
|
+
);
|
|
291
|
+
}
|
|
292
|
+
let html = (0, import_mark_down2.renderMarkdown)(body);
|
|
293
|
+
if (options.sanitize) {
|
|
294
|
+
html = (0, import_mark_down2.sanitizeMarkup)(html, options.sanitize);
|
|
278
295
|
}
|
|
279
|
-
const html = (0, import_mark_down2.renderMarkdown)(body);
|
|
280
296
|
element.html(html);
|
|
281
297
|
if (!element.attr("id")) {
|
|
282
298
|
element.attr("id", frontMatterSlug ?? `snippet-${slug}`);
|
|
@@ -294,7 +310,10 @@ async function compilePage(inputHtml, options = {}) {
|
|
|
294
310
|
}
|
|
295
311
|
async function resolveManifestPath(inputHtml, manifestFlag) {
|
|
296
312
|
const manifestPath = manifestFlag ? import_node_path3.default.resolve(manifestFlag) : import_node_path3.default.join(import_node_path3.default.dirname(import_node_path3.default.resolve(inputHtml)), "snippets-index.json");
|
|
297
|
-
await assertExists(
|
|
313
|
+
await assertExists(
|
|
314
|
+
manifestPath,
|
|
315
|
+
`Manifest file not found at '${manifestPath}'.`
|
|
316
|
+
);
|
|
298
317
|
return manifestPath;
|
|
299
318
|
}
|
|
300
319
|
async function loadManifest(manifestPath) {
|
|
@@ -302,7 +321,9 @@ async function loadManifest(manifestPath) {
|
|
|
302
321
|
try {
|
|
303
322
|
raw = await import_promises2.default.readFile(manifestPath, "utf8");
|
|
304
323
|
} catch (error) {
|
|
305
|
-
throw new Error(
|
|
324
|
+
throw new Error(
|
|
325
|
+
`Failed to read manifest at '${manifestPath}': ${String(error)}`
|
|
326
|
+
);
|
|
306
327
|
}
|
|
307
328
|
try {
|
|
308
329
|
const parsed = JSON.parse(raw);
|
|
@@ -311,7 +332,9 @@ async function loadManifest(manifestPath) {
|
|
|
311
332
|
}
|
|
312
333
|
return parsed;
|
|
313
334
|
} catch (error) {
|
|
314
|
-
throw new Error(
|
|
335
|
+
throw new Error(
|
|
336
|
+
`Failed to parse manifest at '${manifestPath}': ${String(error)}`
|
|
337
|
+
);
|
|
315
338
|
}
|
|
316
339
|
}
|
|
317
340
|
async function assertExists(target, message) {
|
|
@@ -327,7 +350,10 @@ var program = new import_commander.Command();
|
|
|
327
350
|
program.name("mark-down").description(`${brand} CLI for building snippet manifests`).version("1.2.1");
|
|
328
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) => {
|
|
329
352
|
try {
|
|
330
|
-
const result = await buildManifestFile({
|
|
353
|
+
const result = await buildManifestFile({
|
|
354
|
+
sourceDir,
|
|
355
|
+
outputPath: options.output
|
|
356
|
+
});
|
|
331
357
|
logEvent("info", "manifest.written", {
|
|
332
358
|
outputPath: result.outputPath,
|
|
333
359
|
snippetCount: result.manifest.length
|
|
@@ -343,17 +369,26 @@ program.command("watch").argument("[sourceDir]", "directory containing snippets"
|
|
|
343
369
|
handleError(error);
|
|
344
370
|
}
|
|
345
371
|
});
|
|
346
|
-
program.command("compile-page").argument("<inputHtml>", "HTML file containing data-snippet placeholders").option("--manifest <path>", "path to snippets-index.json").option("--outDir <path>", "output directory for compiled HTML", "dist").option(
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
372
|
+
program.command("compile-page").argument("<inputHtml>", "HTML file containing data-snippet placeholders").option("--manifest <path>", "path to snippets-index.json").option("--outDir <path>", "output directory for compiled HTML", "dist").option(
|
|
373
|
+
"--inPlace",
|
|
374
|
+
"overwrite the input HTML file instead of writing to outDir"
|
|
375
|
+
).option(
|
|
376
|
+
"--sanitize [policy]",
|
|
377
|
+
"sanitize rendered HTML (default|strict|permissive)"
|
|
378
|
+
).action(
|
|
379
|
+
async (inputHtml, options) => {
|
|
380
|
+
try {
|
|
381
|
+
await compilePage(inputHtml, {
|
|
382
|
+
manifest: options.manifest,
|
|
383
|
+
outDir: options.outDir,
|
|
384
|
+
inPlace: options.inPlace,
|
|
385
|
+
sanitize: resolveSanitizeOption(options.sanitize)
|
|
386
|
+
});
|
|
387
|
+
} catch (error) {
|
|
388
|
+
handleError(error);
|
|
389
|
+
}
|
|
355
390
|
}
|
|
356
|
-
|
|
391
|
+
);
|
|
357
392
|
program.parseAsync(process.argv).catch(handleError);
|
|
358
393
|
function handleError(error) {
|
|
359
394
|
const err = error;
|
|
@@ -370,4 +405,23 @@ function handleError(error) {
|
|
|
370
405
|
});
|
|
371
406
|
process.exit(1);
|
|
372
407
|
}
|
|
408
|
+
function resolveSanitizeOption(value) {
|
|
409
|
+
if (!value) {
|
|
410
|
+
return void 0;
|
|
411
|
+
}
|
|
412
|
+
if (value === true) {
|
|
413
|
+
return { policy: "default" };
|
|
414
|
+
}
|
|
415
|
+
const policy = value.trim();
|
|
416
|
+
if (!policy) {
|
|
417
|
+
return { policy: "default" };
|
|
418
|
+
}
|
|
419
|
+
if (!isSanitizePolicy(policy)) {
|
|
420
|
+
throw new Error(`Unknown sanitize policy '${policy}'.`);
|
|
421
|
+
}
|
|
422
|
+
return { policy };
|
|
423
|
+
}
|
|
424
|
+
function isSanitizePolicy(value) {
|
|
425
|
+
return value === "default" || value === "strict" || value === "permissive";
|
|
426
|
+
}
|
|
373
427
|
//# sourceMappingURL=index.cjs.map
|
package/dist/index.cjs.map
CHANGED
|
@@ -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\";\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({ sourceDir, outputPath: options.output });\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(\"--inPlace\", \"overwrite the input HTML file instead of writing to outDir\")\n .action(async (inputHtml: string, options: { manifest?: string; outDir?: string; inPlace?: boolean }) => {\n try {\n await compilePage(inputHtml, {\n manifest: options.manifest,\n outDir: options.outDir,\n inPlace: options.inPlace\n });\n } catch (error) {\n handleError(error);\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","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(options: BuildOptions): Promise<BuildResult> {\n const manifest = await buildManifest(options.sourceDir);\n const target = 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 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 }\n\n ensureUniqueSlugs(manifest);\n\n manifest.sort((a, b) => {\n const orderA = typeof a.order === \"number\" ? a.order : Number.POSITIVE_INFINITY;\n const orderB = 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(relativePath: string, frontMatter: Record<string, unknown>): SnippetMeta {\n const group = deriveGroup(relativePath);\n const slugSource = 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\"\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(data: Record<string, unknown>): 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(sourceDir: string, outputPath?: string): 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(level: LogLevel, event: string, fields: LogFields = {}) {\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 { parseFrontMatter, renderMarkdown, type SnippetMeta } from \"@mzebley/mark-down\";\nimport { logEvent } from \"./logger.js\";\n\nexport interface CompilePageOptions {\n manifest?: string;\n outDir?: string;\n inPlace?: boolean;\n}\n\nconst DEFAULT_OUT_DIR = \"dist\";\n\nexport async function compilePage(inputHtml: string, options: CompilePageOptions = {}): Promise<string> {\n const sourcePath = path.resolve(inputHtml);\n await assertExists(sourcePath, `Input HTML file not found at '${inputHtml}'.`);\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(`mark↓: failed to parse front matter for '${entry.path}'`, error);\n }\n\n const html = renderMarkdown(body);\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 ? path.dirname(sourcePath) : 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(inputHtml: string, manifestFlag?: string): Promise<string> {\n const manifestPath = manifestFlag\n ? path.resolve(manifestFlag)\n : path.join(path.dirname(path.resolve(inputHtml)), \"snippets-index.json\");\n await assertExists(manifestPath, `Manifest file not found at '${manifestPath}'.`);\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(`Failed to read manifest at '${manifestPath}': ${String(error)}`);\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(`Failed to parse manifest at '${manifestPath}': ${String(error)}`);\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.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"]}
|
package/dist/index.mjs
CHANGED
|
@@ -39,13 +39,20 @@ async function buildManifest(sourceDir) {
|
|
|
39
39
|
for (const absolutePath of files) {
|
|
40
40
|
const relativePath = path.relative(cwd, absolutePath);
|
|
41
41
|
const normalizedPath = toPosix(relativePath);
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
42
|
+
try {
|
|
43
|
+
const content = await fs.readFile(absolutePath, "utf8");
|
|
44
|
+
const parsed = matter(content, MATTER_OPTIONS);
|
|
45
|
+
const snippet = createSnippet(normalizedPath, parsed.data ?? {});
|
|
46
|
+
if (snippet.draft) {
|
|
47
|
+
continue;
|
|
48
|
+
}
|
|
49
|
+
manifest.push(snippet);
|
|
50
|
+
} catch (error) {
|
|
51
|
+
console.warn(
|
|
52
|
+
`mark\u2193: failed to load snippet at '${normalizedPath}'`,
|
|
53
|
+
error
|
|
54
|
+
);
|
|
47
55
|
}
|
|
48
|
-
manifest.push(snippet);
|
|
49
56
|
}
|
|
50
57
|
ensureUniqueSlugs(manifest);
|
|
51
58
|
manifest.sort((a, b) => {
|
|
@@ -212,11 +219,18 @@ function debounce(fn, delay) {
|
|
|
212
219
|
import fs2 from "fs/promises";
|
|
213
220
|
import path3 from "path";
|
|
214
221
|
import { load as loadHtml } from "cheerio";
|
|
215
|
-
import {
|
|
222
|
+
import {
|
|
223
|
+
parseFrontMatter,
|
|
224
|
+
renderMarkdown,
|
|
225
|
+
sanitizeMarkup
|
|
226
|
+
} from "@mzebley/mark-down";
|
|
216
227
|
var DEFAULT_OUT_DIR = "dist";
|
|
217
228
|
async function compilePage(inputHtml, options = {}) {
|
|
218
229
|
const sourcePath = path3.resolve(inputHtml);
|
|
219
|
-
await assertExists(
|
|
230
|
+
await assertExists(
|
|
231
|
+
sourcePath,
|
|
232
|
+
`Input HTML file not found at '${inputHtml}'.`
|
|
233
|
+
);
|
|
220
234
|
const manifestPath = await resolveManifestPath(sourcePath, options.manifest);
|
|
221
235
|
const manifestDir = path3.dirname(manifestPath);
|
|
222
236
|
const manifest = await loadManifest(manifestPath);
|
|
@@ -251,9 +265,15 @@ async function compilePage(inputHtml, options = {}) {
|
|
|
251
265
|
body = frontMatter.content;
|
|
252
266
|
frontMatterSlug = frontMatter.slug;
|
|
253
267
|
} catch (error) {
|
|
254
|
-
console.warn(
|
|
268
|
+
console.warn(
|
|
269
|
+
`mark\u2193: failed to parse front matter for '${entry.path}'`,
|
|
270
|
+
error
|
|
271
|
+
);
|
|
272
|
+
}
|
|
273
|
+
let html = renderMarkdown(body);
|
|
274
|
+
if (options.sanitize) {
|
|
275
|
+
html = sanitizeMarkup(html, options.sanitize);
|
|
255
276
|
}
|
|
256
|
-
const html = renderMarkdown(body);
|
|
257
277
|
element.html(html);
|
|
258
278
|
if (!element.attr("id")) {
|
|
259
279
|
element.attr("id", frontMatterSlug ?? `snippet-${slug}`);
|
|
@@ -271,7 +291,10 @@ async function compilePage(inputHtml, options = {}) {
|
|
|
271
291
|
}
|
|
272
292
|
async function resolveManifestPath(inputHtml, manifestFlag) {
|
|
273
293
|
const manifestPath = manifestFlag ? path3.resolve(manifestFlag) : path3.join(path3.dirname(path3.resolve(inputHtml)), "snippets-index.json");
|
|
274
|
-
await assertExists(
|
|
294
|
+
await assertExists(
|
|
295
|
+
manifestPath,
|
|
296
|
+
`Manifest file not found at '${manifestPath}'.`
|
|
297
|
+
);
|
|
275
298
|
return manifestPath;
|
|
276
299
|
}
|
|
277
300
|
async function loadManifest(manifestPath) {
|
|
@@ -279,7 +302,9 @@ async function loadManifest(manifestPath) {
|
|
|
279
302
|
try {
|
|
280
303
|
raw = await fs2.readFile(manifestPath, "utf8");
|
|
281
304
|
} catch (error) {
|
|
282
|
-
throw new Error(
|
|
305
|
+
throw new Error(
|
|
306
|
+
`Failed to read manifest at '${manifestPath}': ${String(error)}`
|
|
307
|
+
);
|
|
283
308
|
}
|
|
284
309
|
try {
|
|
285
310
|
const parsed = JSON.parse(raw);
|
|
@@ -288,7 +313,9 @@ async function loadManifest(manifestPath) {
|
|
|
288
313
|
}
|
|
289
314
|
return parsed;
|
|
290
315
|
} catch (error) {
|
|
291
|
-
throw new Error(
|
|
316
|
+
throw new Error(
|
|
317
|
+
`Failed to parse manifest at '${manifestPath}': ${String(error)}`
|
|
318
|
+
);
|
|
292
319
|
}
|
|
293
320
|
}
|
|
294
321
|
async function assertExists(target, message) {
|
|
@@ -304,7 +331,10 @@ var program = new Command();
|
|
|
304
331
|
program.name("mark-down").description(`${brand} CLI for building snippet manifests`).version("1.2.1");
|
|
305
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) => {
|
|
306
333
|
try {
|
|
307
|
-
const result = await buildManifestFile({
|
|
334
|
+
const result = await buildManifestFile({
|
|
335
|
+
sourceDir,
|
|
336
|
+
outputPath: options.output
|
|
337
|
+
});
|
|
308
338
|
logEvent("info", "manifest.written", {
|
|
309
339
|
outputPath: result.outputPath,
|
|
310
340
|
snippetCount: result.manifest.length
|
|
@@ -320,17 +350,26 @@ program.command("watch").argument("[sourceDir]", "directory containing snippets"
|
|
|
320
350
|
handleError(error);
|
|
321
351
|
}
|
|
322
352
|
});
|
|
323
|
-
program.command("compile-page").argument("<inputHtml>", "HTML file containing data-snippet placeholders").option("--manifest <path>", "path to snippets-index.json").option("--outDir <path>", "output directory for compiled HTML", "dist").option(
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
353
|
+
program.command("compile-page").argument("<inputHtml>", "HTML file containing data-snippet placeholders").option("--manifest <path>", "path to snippets-index.json").option("--outDir <path>", "output directory for compiled HTML", "dist").option(
|
|
354
|
+
"--inPlace",
|
|
355
|
+
"overwrite the input HTML file instead of writing to outDir"
|
|
356
|
+
).option(
|
|
357
|
+
"--sanitize [policy]",
|
|
358
|
+
"sanitize rendered HTML (default|strict|permissive)"
|
|
359
|
+
).action(
|
|
360
|
+
async (inputHtml, options) => {
|
|
361
|
+
try {
|
|
362
|
+
await compilePage(inputHtml, {
|
|
363
|
+
manifest: options.manifest,
|
|
364
|
+
outDir: options.outDir,
|
|
365
|
+
inPlace: options.inPlace,
|
|
366
|
+
sanitize: resolveSanitizeOption(options.sanitize)
|
|
367
|
+
});
|
|
368
|
+
} catch (error) {
|
|
369
|
+
handleError(error);
|
|
370
|
+
}
|
|
332
371
|
}
|
|
333
|
-
|
|
372
|
+
);
|
|
334
373
|
program.parseAsync(process.argv).catch(handleError);
|
|
335
374
|
function handleError(error) {
|
|
336
375
|
const err = error;
|
|
@@ -347,4 +386,23 @@ function handleError(error) {
|
|
|
347
386
|
});
|
|
348
387
|
process.exit(1);
|
|
349
388
|
}
|
|
389
|
+
function resolveSanitizeOption(value) {
|
|
390
|
+
if (!value) {
|
|
391
|
+
return void 0;
|
|
392
|
+
}
|
|
393
|
+
if (value === true) {
|
|
394
|
+
return { policy: "default" };
|
|
395
|
+
}
|
|
396
|
+
const policy = value.trim();
|
|
397
|
+
if (!policy) {
|
|
398
|
+
return { policy: "default" };
|
|
399
|
+
}
|
|
400
|
+
if (!isSanitizePolicy(policy)) {
|
|
401
|
+
throw new Error(`Unknown sanitize policy '${policy}'.`);
|
|
402
|
+
}
|
|
403
|
+
return { policy };
|
|
404
|
+
}
|
|
405
|
+
function isSanitizePolicy(value) {
|
|
406
|
+
return value === "default" || value === "strict" || value === "permissive";
|
|
407
|
+
}
|
|
350
408
|
//# sourceMappingURL=index.mjs.map
|
package/dist/index.mjs.map
CHANGED
|
@@ -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\";\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({ sourceDir, outputPath: options.output });\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(\"--inPlace\", \"overwrite the input HTML file instead of writing to outDir\")\n .action(async (inputHtml: string, options: { manifest?: string; outDir?: string; inPlace?: boolean }) => {\n try {\n await compilePage(inputHtml, {\n manifest: options.manifest,\n outDir: options.outDir,\n inPlace: options.inPlace\n });\n } catch (error) {\n handleError(error);\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","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(options: BuildOptions): Promise<BuildResult> {\n const manifest = await buildManifest(options.sourceDir);\n const target = 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 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 }\n\n ensureUniqueSlugs(manifest);\n\n manifest.sort((a, b) => {\n const orderA = typeof a.order === \"number\" ? a.order : Number.POSITIVE_INFINITY;\n const orderB = 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(relativePath: string, frontMatter: Record<string, unknown>): SnippetMeta {\n const group = deriveGroup(relativePath);\n const slugSource = 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\"\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(data: Record<string, unknown>): 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(sourceDir: string, outputPath?: string): 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(level: LogLevel, event: string, fields: LogFields = {}) {\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 { parseFrontMatter, renderMarkdown, type SnippetMeta } from \"@mzebley/mark-down\";\nimport { logEvent } from \"./logger.js\";\n\nexport interface CompilePageOptions {\n manifest?: string;\n outDir?: string;\n inPlace?: boolean;\n}\n\nconst DEFAULT_OUT_DIR = \"dist\";\n\nexport async function compilePage(inputHtml: string, options: CompilePageOptions = {}): Promise<string> {\n const sourcePath = path.resolve(inputHtml);\n await assertExists(sourcePath, `Input HTML file not found at '${inputHtml}'.`);\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(`mark↓: failed to parse front matter for '${entry.path}'`, error);\n }\n\n const html = renderMarkdown(body);\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 ? path.dirname(sourcePath) : 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(inputHtml: string, manifestFlag?: string): Promise<string> {\n const manifestPath = manifestFlag\n ? path.resolve(manifestFlag)\n : path.join(path.dirname(path.resolve(inputHtml)), \"snippets-index.json\");\n await assertExists(manifestPath, `Manifest file not found at '${manifestPath}'.`);\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(`Failed to read manifest at '${manifestPath}': ${String(error)}`);\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(`Failed to parse manifest at '${manifestPath}': ${String(error)}`);\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.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":";;;AACA,SAAS,eAAe;;;ACDxB,OAAO,QAAQ;AACf,OAAO,UAAU;AACjB,OAAO,QAAQ;AACf,OAAO,YAAY;AACnB,OAAO,UAAU;AACjB,SAAS,qBAAuC;;;ACLzC,IAAM,qBAAN,cAAiC,MAAM;AAAA,EAG5C,YAAY,YAAsB;AAChC,UAAM,6BAA6B,WAAW,KAAK,IAAI,CAAC,EAAE;AAC1D,SAAK,OAAO;AACZ,SAAK,aAAa;AAAA,EACpB;AACF;;;ADAA,IAAM,iBAAiB;AAAA,EACrB,SAAS;AAAA,IACP,MAAM,CAAC,WAAmB,KAAK,MAAM,MAAM,KAAK,CAAC;AAAA,EACnD;AACF;AAYA,eAAsB,kBACpB,SACsB;AACtB,QAAM,WAAW,MAAM,cAAc,QAAQ,SAAS;AACtD,QAAM,SACJ,QAAQ,cAAc,KAAK,KAAK,QAAQ,WAAW,qBAAqB;AAC1E,QAAM,GAAG,UAAU,QAAQ,KAAK,UAAU,UAAU,MAAM,CAAC,CAAC;AAC5D,SAAO,EAAE,UAAU,YAAY,OAAO;AACxC;AAEA,eAAsB,cAAc,WAA2C;AAC7E,QAAM,MAAM,KAAK,QAAQ,SAAS;AAClC,QAAM,QAAQ,MAAM,GAAG,CAAC,SAAS,GAAG,EAAE,KAAK,UAAU,KAAK,CAAC;AAC3D,QAAM,WAA0B,CAAC;AAEjC,aAAW,gBAAgB,OAAO;AAChC,UAAM,eAAe,KAAK,SAAS,KAAK,YAAY;AACpD,UAAM,iBAAiB,QAAQ,YAAY;AAC3C,QAAI;AACF,YAAM,UAAU,MAAM,GAAG,SAAS,cAAc,MAAM;AACtD,YAAM,SAAS,OAAO,SAAS,cAAc;AAC7C,YAAM,UAAU,cAAc,gBAAgB,OAAO,QAAQ,CAAC,CAAC;AAC/D,UAAI,QAAQ,OAAO;AACjB;AAAA,MACF;AACA,eAAS,KAAK,OAAO;AAAA,IACvB,SAAS,OAAO;AACd,cAAQ;AAAA,QACN,0CAAqC,cAAc;AAAA,QACnD;AAAA,MACF;AAAA,IACF;AAAA,EACF;AAEA,oBAAkB,QAAQ;AAE1B,WAAS,KAAK,CAAC,GAAG,MAAM;AACtB,UAAM,SACJ,OAAO,EAAE,UAAU,WAAW,EAAE,QAAQ,OAAO;AACjD,UAAM,SACJ,OAAO,EAAE,UAAU,WAAW,EAAE,QAAQ,OAAO;AACjD,QAAI,WAAW,QAAQ;AACrB,aAAO,SAAS;AAAA,IAClB;AACA,UAAM,SAAS,EAAE,OAAO,YAAY,KAAK;AACzC,UAAM,SAAS,EAAE,OAAO,YAAY,KAAK;AACzC,WAAO,OAAO,cAAc,MAAM;AAAA,EACpC,CAAC;AAED,SAAO;AACT;AAEO,SAAS,cACd,cACA,aACa;AACb,QAAM,QAAQ,YAAY,YAAY;AACtC,QAAM,aACJ,OAAO,YAAY,SAAS,YAAY,YAAY,KAAK,KAAK,EAAE,SAC5D,YAAY,OACZ,aAAa,QAAQ,UAAU,EAAE;AACvC,QAAM,OAAO,cAAc,UAAU;AAErC,QAAM,EAAE,OAAO,OAAO,MAAM,MAAM,MAAM,IAAI,qBAAqB,WAAW;AAC5E,QAAM,QAAQ,aAAa,WAAW;AAEtC,SAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA,MAAM;AAAA,IACN;AAAA,IACA;AAAA,EACF;AACF;AAEA,SAAS,qBAAqB,MAA+B;AAC3D,SAAO;AAAA,IACL,OAAO,OAAO,KAAK,UAAU,WAAW,KAAK,QAAQ;AAAA,IACrD,OACE,OAAO,KAAK,UAAU,WAClB,KAAK,QACL,KAAK,UAAU,OACb,OACA;AAAA,IACR,MAAM,OAAO,KAAK,SAAS,WAAW,KAAK,OAAO;AAAA,IAClD,MAAM,cAAc,KAAK,IAAI;AAAA,IAC7B,OAAO,KAAK,UAAU,OAAO,OAAO;AAAA,EACtC;AACF;AAEA,SAAS,aACP,MACqC;AACrC,QAAM,QAAiC,CAAC;AACxC,QAAM,WAAW,oBAAI,IAAI,CAAC,QAAQ,SAAS,SAAS,QAAQ,QAAQ,OAAO,CAAC;AAC5E,aAAW,CAAC,KAAK,KAAK,KAAK,OAAO,QAAQ,IAAI,GAAG;AAC/C,QAAI,SAAS,IAAI,GAAG,GAAG;AACrB;AAAA,IACF;AACA,UAAM,GAAG,IAAI;AAAA,EACf;AACA,SAAO,OAAO,KAAK,KAAK,EAAE,SAAS,QAAQ;AAC7C;AAEA,SAAS,cAAc,OAAsC;AAC3D,MAAI,CAAC,OAAO;AACV,WAAO;AAAA,EACT;AACA,MAAI,MAAM,QAAQ,KAAK,GAAG;AACxB,WAAO,MAAM,IAAI,CAAC,UAAU,OAAO,KAAK,CAAC;AAAA,EAC3C;AACA,MAAI,OAAO,UAAU,UAAU;AAC7B,WAAO,MACJ,MAAM,GAAG,EACT,IAAI,CAAC,UAAU,MAAM,KAAK,CAAC,EAC3B,OAAO,OAAO;AAAA,EACnB;AACA,SAAO;AACT;AAEA,SAAS,YAAY,cAA8B;AACjD,QAAM,UAAU,QAAQ,KAAK,QAAQ,YAAY,CAAC;AAClD,MAAI,YAAY,OAAO,CAAC,QAAQ,QAAQ;AACtC,WAAO;AAAA,EACT;AACA,SAAO;AACT;AAEA,SAAS,QAAQ,OAAuB;AACtC,SAAO,MAAM,MAAM,KAAK,GAAG,EAAE,KAAK,GAAG;AACvC;AAEA,SAAS,kBAAkB,UAAyB;AAClD,QAAM,OAAO,oBAAI,IAAoB;AACrC,QAAM,aAAa,oBAAI,IAAY;AACnC,aAAW,WAAW,UAAU;AAC9B,QAAI,KAAK,IAAI,QAAQ,IAAI,GAAG;AAC1B,iBAAW,IAAI,QAAQ,IAAI;AAAA,IAC7B,OAAO;AACL,WAAK,IAAI,QAAQ,MAAM,QAAQ,IAAI;AAAA,IACrC;AAAA,EACF;AACA,MAAI,WAAW,MAAM;AACnB,UAAM,IAAI,mBAAmB,CAAC,GAAG,WAAW,OAAO,CAAC,CAAC;AAAA,EACvD;AACF;;;AE7KA,OAAOA,WAAU;AACjB,OAAO,cAAc;;;ACDd,IAAM,QAAQ;AASd,SAAS,SACd,OACA,OACA,SAAoB,CAAC,GACrB;AACA,QAAM,QAAQ;AAAA,IACZ;AAAA,IACA;AAAA,IACA;AAAA,IACA,YAAW,oBAAI,KAAK,GAAE,YAAY;AAAA,IAClC,GAAG;AAAA,EACL;AACA,QAAM,SAAS,GAAG,KAAK,UAAU,KAAK,CAAC;AAAA;AACvC,QAAM,SAAS,UAAU,UAAU,QAAQ,SAAS,QAAQ;AAC5D,SAAO,MAAM,MAAM;AACrB;;;ADnBA,eAAsB,MAAM,WAAmB,YAAqB;AAClE,QAAM,MAAMC,MAAK,QAAQ,SAAS;AAClC,WAAS,QAAQ,eAAe;AAAA,IAC9B,WAAW;AAAA,IACX,YAAY,cAAcA,MAAK,KAAK,KAAK,qBAAqB;AAAA,EAChE,CAAC;AACD,QAAM,QAAQ,KAAK,UAAU;AAE7B,QAAM,UAAU,SAAS,MAAM,CAAC,SAAS,GAAG;AAAA,IAC1C;AAAA,IACA,eAAe;AAAA,IACf,kBAAkB;AAAA,MAChB,oBAAoB;AAAA,MACpB,cAAc;AAAA,IAChB;AAAA,EACF,CAAC;AAED,QAAM,WAAW,SAAS,YAAY;AACpC,UAAM,QAAQ,KAAK,UAAU;AAAA,EAC/B,GAAG,GAAG;AAEN,UAAQ,GAAG,OAAO,CAAC,OAAO,aAAa;AACrC,aAAS,QAAQ,gBAAgB,EAAE,OAAO,MAAM,SAAS,CAAC;AAC1D,aAAS;AAAA,EACX,CAAC;AACH;AAEA,eAAe,QACb,WACA,YAC6B;AAC7B,MAAI;AACF,UAAM,SAAS,MAAM,kBAAkB,EAAE,WAAW,WAAW,CAAC;AAChE,aAAS,QAAQ,oBAAoB;AAAA,MACnC,YAAY,OAAO;AAAA,MACnB,cAAc,OAAO,SAAS;AAAA,IAChC,CAAC;AACD,WAAO;AAAA,EACT,SAAS,OAAO;AACd,UAAM,MAAM;AACZ,aAAS,SAAS,0BAA0B;AAAA,MAC1C,SAAS,IAAI;AAAA,MACb,OAAO,IAAI;AAAA,IACb,CAAC;AAAA,EACH;AACF;AAEA,SAAS,SACP,IACA,OACA;AACA,MAAI,QAA+B;AACnC,SAAO,IAAI,SAAwB;AACjC,QAAI,OAAO;AACT,mBAAa,KAAK;AAAA,IACpB;AACA,YAAQ,WAAW,MAAM;AACvB,cAAQ;AACR,WAAK,GAAG,GAAG,IAAI;AAAA,IACjB,GAAG,KAAK;AAAA,EACV;AACF;;;AElEA,OAAOC,SAAQ;AACf,OAAOC,WAAU;AACjB,SAAS,QAAQ,gBAAgB;AACjC;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,OAGK;AAUP,IAAM,kBAAkB;AAExB,eAAsB,YACpB,WACA,UAA8B,CAAC,GACd;AACjB,QAAM,aAAaC,MAAK,QAAQ,SAAS;AACzC,QAAM;AAAA,IACJ;AAAA,IACA,iCAAiC,SAAS;AAAA,EAC5C;AAEA,QAAM,eAAe,MAAM,oBAAoB,YAAY,QAAQ,QAAQ;AAC3E,QAAM,cAAcA,MAAK,QAAQ,YAAY;AAC7C,QAAM,WAAW,MAAM,aAAa,YAAY;AAEhD,QAAM,UAAU,MAAMC,IAAG,SAAS,YAAY,MAAM;AACpD,QAAM,eAAe,QAAQ,MAAM,wBAAwB;AAC3D,QAAM,UAAU,eAAe,CAAC,KAAK;AACrC,QAAM,MAAM,SAAS,SAAS,EAAE,gBAAgB,MAAM,CAAC;AAEvD,QAAM,UAAU,IAAI,gBAAgB,EAAE,QAAQ;AAC9C,aAAW,QAAQ,SAAS;AAC1B,UAAM,UAAU,IAAI,IAAI;AACxB,UAAM,OAAO,QAAQ,KAAK,cAAc;AACxC,QAAI,CAAC,MAAM;AACT;AAAA,IACF;AACA,UAAM,QAAQ,SAAS,KAAK,CAAC,SAAS,KAAK,SAAS,IAAI;AACxD,QAAI,CAAC,OAAO;AACV,cAAQ,KAAK,qCAAgC,IAAI,GAAG;AACpD;AAAA,IACF;AAEA,UAAM,cAAcD,MAAK,QAAQ,aAAa,MAAM,IAAI;AACxD,QAAI;AACJ,QAAI;AACF,YAAM,MAAMC,IAAG,SAAS,aAAa,MAAM;AAAA,IAC7C,SAAS,OAAO;AACd,cAAQ,KAAK,0CAAqC,MAAM,IAAI,KAAK,KAAK;AACtE;AAAA,IACF;AAEA,QAAI,OAAO;AACX,QAAI;AACJ,QAAI;AACF,YAAM,cAAc,iBAAiB,GAAG;AACxC,aAAO,YAAY;AACnB,wBAAkB,YAAY;AAAA,IAChC,SAAS,OAAO;AACd,cAAQ;AAAA,QACN,iDAA4C,MAAM,IAAI;AAAA,QACtD;AAAA,MACF;AAAA,IACF;AAEA,QAAI,OAAO,eAAe,IAAI;AAC9B,QAAI,QAAQ,UAAU;AACpB,aAAO,eAAe,MAAM,QAAQ,QAAQ;AAAA,IAC9C;AACA,YAAQ,KAAK,IAAI;AAEjB,QAAI,CAAC,QAAQ,KAAK,IAAI,GAAG;AACvB,cAAQ,KAAK,MAAM,mBAAmB,WAAW,IAAI,EAAE;AAAA,IACzD;AAAA,EACF;AAEA,QAAM,YAAY,QAAQ,UACtBD,MAAK,QAAQ,UAAU,IACvBA,MAAK,QAAQ,QAAQ,UAAU,eAAe;AAClD,MAAI,CAAC,QAAQ,SAAS;AACpB,UAAMC,IAAG,MAAM,WAAW,EAAE,WAAW,KAAK,CAAC;AAAA,EAC/C;AACA,QAAM,aAAa,QAAQ,UACvB,aACAD,MAAK,KAAK,WAAWA,MAAK,SAAS,UAAU,CAAC;AAElD,QAAM,aAAa,GAAG,OAAO,GAAG,IAAI,KAAK,KAAK,EAAE;AAChD,QAAMC,IAAG,UAAU,YAAY,UAAU;AAEzC,WAAS,QAAQ,wBAAwB,EAAE,WAAW,CAAC;AACvD,SAAO;AACT;AAEA,eAAe,oBACb,WACA,cACiB;AACjB,QAAM,eAAe,eACjBD,MAAK,QAAQ,YAAY,IACzBA,MAAK,KAAKA,MAAK,QAAQA,MAAK,QAAQ,SAAS,CAAC,GAAG,qBAAqB;AAC1E,QAAM;AAAA,IACJ;AAAA,IACA,+BAA+B,YAAY;AAAA,EAC7C;AACA,SAAO;AACT;AAEA,eAAe,aAAa,cAA8C;AACxE,MAAI;AACJ,MAAI;AACF,UAAM,MAAMC,IAAG,SAAS,cAAc,MAAM;AAAA,EAC9C,SAAS,OAAO;AACd,UAAM,IAAI;AAAA,MACR,+BAA+B,YAAY,MAAM,OAAO,KAAK,CAAC;AAAA,IAChE;AAAA,EACF;AAEA,MAAI;AACF,UAAM,SAAS,KAAK,MAAM,GAAG;AAC7B,QAAI,CAAC,MAAM,QAAQ,MAAM,GAAG;AAC1B,YAAM,IAAI,MAAM,gCAAgC;AAAA,IAClD;AACA,WAAO;AAAA,EACT,SAAS,OAAO;AACd,UAAM,IAAI;AAAA,MACR,gCAAgC,YAAY,MAAM,OAAO,KAAK,CAAC;AAAA,IACjE;AAAA,EACF;AACF;AAEA,eAAe,aAAa,QAAgB,SAAiB;AAC3D,MAAI;AACF,UAAMA,IAAG,OAAO,MAAM;AAAA,EACxB,QAAQ;AACN,UAAM,IAAI,MAAM,OAAO;AAAA,EACzB;AACF;;;ALzIA,IAAM,UAAU,IAAI,QAAQ;AAC5B,QACG,KAAK,WAAW,EAChB,YAAY,GAAG,KAAK,qCAAqC,EACzD,QAAQ,OAAO;AAElB,QACG,QAAQ,OAAO,EACf,SAAS,eAAe,iCAAiC,kBAAkB,EAC3E,OAAO,uBAAuB,oCAAoC,EAClE,OAAO,OAAO,WAAmB,YAAiC;AACjE,MAAI;AACF,UAAM,SAAS,MAAM,kBAAkB;AAAA,MACrC;AAAA,MACA,YAAY,QAAQ;AAAA,IACtB,CAAC;AACD,aAAS,QAAQ,oBAAoB;AAAA,MACnC,YAAY,OAAO;AAAA,MACnB,cAAc,OAAO,SAAS;AAAA,IAChC,CAAC;AAAA,EACH,SAAS,OAAO;AACd,gBAAY,KAAK;AAAA,EACnB;AACF,CAAC;AAEH,QACG,QAAQ,OAAO,EACf,SAAS,eAAe,iCAAiC,kBAAkB,EAC3E,OAAO,uBAAuB,oCAAoC,EAClE,OAAO,OAAO,WAAmB,YAAiC;AACjE,MAAI;AACF,UAAM,MAAc,WAAW,QAAQ,MAAM;AAAA,EAC/C,SAAS,OAAO;AACd,gBAAY,KAAK;AAAA,EACnB;AACF,CAAC;AAEH,QACG,QAAQ,cAAc,EACtB,SAAS,eAAe,gDAAgD,EACxE,OAAO,qBAAqB,6BAA6B,EACzD,OAAO,mBAAmB,sCAAsC,MAAM,EACtE;AAAA,EACC;AAAA,EACA;AACF,EACC;AAAA,EACC;AAAA,EACA;AACF,EACC;AAAA,EACC,OACE,WACA,YAMG;AACH,QAAI;AACF,YAAM,YAAY,WAAW;AAAA,QAC3B,UAAU,QAAQ;AAAA,QAClB,QAAQ,QAAQ;AAAA,QAChB,SAAS,QAAQ;AAAA,QACjB,UAAU,sBAAsB,QAAQ,QAAQ;AAAA,MAClD,CAAC;AAAA,IACH,SAAS,OAAO;AACd,kBAAY,KAAK;AAAA,IACnB;AAAA,EACF;AACF;AAEF,QAAQ,WAAW,QAAQ,IAAI,EAAE,MAAM,WAAW;AAElD,SAAS,YAAY,OAAgB;AACnC,QAAM,MAAM;AACZ,MAAI,eAAe,oBAAoB;AACrC,aAAS,SAAS,2BAA2B;AAAA,MAC3C,SAAS,IAAI;AAAA,MACb,OAAO,IAAI;AAAA,IACb,CAAC;AACD,YAAQ,KAAK,CAAC;AAAA,EAChB;AACA,WAAS,SAAS,aAAa;AAAA,IAC7B,SAAS,IAAI;AAAA,IACb,OAAO,IAAI;AAAA,EACb,CAAC;AACD,UAAQ,KAAK,CAAC;AAChB;AAEA,SAAS,sBACP,OAC6B;AAC7B,MAAI,CAAC,OAAO;AACV,WAAO;AAAA,EACT;AACA,MAAI,UAAU,MAAM;AAClB,WAAO,EAAE,QAAQ,UAAU;AAAA,EAC7B;AACA,QAAM,SAAS,MAAM,KAAK;AAC1B,MAAI,CAAC,QAAQ;AACX,WAAO,EAAE,QAAQ,UAAU;AAAA,EAC7B;AACA,MAAI,CAAC,iBAAiB,MAAM,GAAG;AAC7B,UAAM,IAAI,MAAM,4BAA4B,MAAM,IAAI;AAAA,EACxD;AACA,SAAO,EAAE,OAAO;AAClB;AAEA,SAAS,iBAAiB,OAAwC;AAChE,SAAO,UAAU,aAAa,UAAU,YAAY,UAAU;AAChE;","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
|
+
"version": "1.2.3",
|
|
4
4
|
"description": "mark↓ CLI for building snippet manifests",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -9,7 +9,9 @@
|
|
|
9
9
|
"main": "dist/index.mjs",
|
|
10
10
|
"module": "dist/index.mjs",
|
|
11
11
|
"types": "dist/index.d.ts",
|
|
12
|
-
"files": [
|
|
12
|
+
"files": [
|
|
13
|
+
"dist"
|
|
14
|
+
],
|
|
13
15
|
"publishConfig": {
|
|
14
16
|
"access": "public"
|
|
15
17
|
},
|