seemore 1.8.7 → 1.9.1

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
@@ -81,9 +81,9 @@ Typical use cases include:
81
81
  - **Full MDX** — when Markdown isn't enough, `.mdx` pages take real JSX: your own React components, inline SVG, custom classes and CSS; `<Callout>`, `<Card>`, `<CodeBlockTabs>` and friends come built in, with no imports to write
82
82
  - **Static export** — `seemore build` prerenders every page to its own HTML file, `404.html` included, and adds the conventions individual hosts look for (`_redirects`, `200.html`, `.nojekyll`)
83
83
  - **Search built in** — static, zero-setup full-text search out of the box, with shareable highlighted results; [Algolia](https://algolia.com) and [Orama Cloud](https://orama.com) for hosted indexes
84
+ - **Page actions** — an Actions button on every page: export the page as one self-contained HTML file or print it to PDF, with the CLI equivalent in `seemore export <file>`
84
85
  - **12 themes** — dark and light follow the system, with a toggle that remembers your choice; your own CSS always wins
85
86
  - **Rich Markdown** — GitHub Flavoured Markdown, admonitions, steps, `[[wikilinks]]`, [Mermaid](https://mermaid.js.org) and [D2](https://d2lang.com) diagrams, click-to-zoom images, embedded PDFs
86
- - **First-class code blocks** — build-time [Shiki](https://shiki.style) highlighting in the theme's own colours, with titles, line numbers, diff markers and focus
87
87
  - **Editor integration** — one extension covers VS Code, Cursor, Antigravity and other VS Code-compatible editors, remote workspaces included
88
88
 
89
89
  ## View in your browser
@@ -147,6 +147,16 @@ The result is a `dist/` folder of plain web files: drop it on [Netlify](https://
147
147
  > [!TIP]
148
148
  > Project sites on GitHub Pages live under `username.github.io/my-repo/`, not the root, so set `base` once: `base: '/my-repo/'` (or `--base /my-repo/` on the CLI). Building under GitHub Actions without it set prints the exact line to add.
149
149
 
150
+ ## Export page
151
+
152
+ An **Actions** button above every page exports just that page. **Export as HTML** writes one self-contained file — styles inlined, images embedded, diagrams kept — that opens offline from a double-click, ready to drop into Slack, email or an AI chat. The CLI produces the same HTML file without a browser:
153
+
154
+ ```bash
155
+ npx seemore export docs/spec.md # writes spec.html next to the Markdown
156
+ ```
157
+
158
+ Which actions appear — or whether the button exists at all — is one line of config, `pageActions`. See [Configuration](#configuration) and the [features page](https://arifszn.github.io/seemore/features) for the details.
159
+
150
160
  ## Configuration
151
161
 
152
162
  Optional — a folder with no config file builds correctly everywhere. To adjust things, create `seemore.config.ts` next to your content:
@@ -165,6 +175,7 @@ export default {
165
175
  footer: { text: '© 2026' },
166
176
  editLink: { base: 'https://github.com/you/repo/edit/main/docs' },
167
177
  search: 'static', // or { provider: 'orama-cloud', endpoint, apiKey } / { provider: 'algolia', appId, apiKey, indexName }
178
+ pageActions: ['export-html', 'export-pdf'],
168
179
  exclude: ['drafts/**'],
169
180
  };
170
181
  ```
@@ -319,6 +330,7 @@ Pages are ordered by:
319
330
  ```
320
331
  seemore [dir] start the dev server
321
332
  seemore build [dir] build a static site into dist/
333
+ seemore export <file> export a page as a standalone HTML file
322
334
 
323
335
  Options
324
336
  --port <number> dev server port (default 4040)
@@ -326,7 +338,7 @@ Options
326
338
  --open / --no-open open a browser on start (default: no)
327
339
  --json print one machine-readable JSON line instead of the summary (dev only)
328
340
  --config <path> path to seemore.config.ts
329
- --out <dir> build output directory (default: dist)
341
+ --out <dir> build output directory (default: dist); for export, where the HTML file is written
330
342
  --base <path> subpath the site is served from, e.g. /my-repo/
331
343
  -h, --help show this message
332
344
  -v, --version show the version
package/dist/cli/index.js CHANGED
@@ -72,7 +72,7 @@ var init_paths = __esm({
72
72
 
73
73
  // src/cli/index.ts
74
74
  import { parseArgs } from "util";
75
- import pc4 from "picocolors";
75
+ import pc5 from "picocolors";
76
76
 
77
77
  // src/cli/build.ts
78
78
  import { mkdirSync as mkdirSync3, mkdtempSync, readFileSync as readFileSync5, rmSync, writeFileSync as writeFileSync5 } from "fs";
@@ -130,6 +130,7 @@ var FEATURES = [
130
130
  "search.highlight",
131
131
  "social.cards"
132
132
  ];
133
+ var ACTION_IDS = ["export-html", "export-pdf"];
133
134
 
134
135
  // src/node/config/features.ts
135
136
  var FEATURE_DEFAULTS = {
@@ -246,6 +247,7 @@ var searchSchema = z.union([
246
247
  indexName: z.string()
247
248
  })
248
249
  ]);
250
+ var pageActionsSchema = z.array(z.enum(ACTION_IDS)).default(["export-html", "export-pdf"]);
249
251
  var configSchema = z.object({
250
252
  /**
251
253
  * Optional here, but required whenever a config file exists — load.ts enforces that,
@@ -271,6 +273,7 @@ var configSchema = z.object({
271
273
  text: z.string().default("Edit this page")
272
274
  }).optional(),
273
275
  search: searchSchema.default("static"),
276
+ pageActions: pageActionsSchema,
274
277
  exclude: z.array(z.string()).default([])
275
278
  });
276
279
 
@@ -296,6 +299,7 @@ function resolveConfig(input, options) {
296
299
  footer: parsed.footer,
297
300
  editLink: parsed.editLink,
298
301
  search,
302
+ pageActions: parsed.pageActions,
299
303
  exclude: parsed.exclude,
300
304
  root: options.root,
301
305
  configFile: options.configFile
@@ -393,8 +397,8 @@ function slugifySegment(segment) {
393
397
  function toRoute(file) {
394
398
  const posix = toPosix(file);
395
399
  const segments = posix.split("/");
396
- const basename2 = segments.pop() ?? "";
397
- const stem = basename2.replace(CONTENT_EXT, "");
400
+ const basename3 = segments.pop() ?? "";
401
+ const stem = basename3.replace(CONTENT_EXT, "");
398
402
  const isIndex = INDEX_NAMES.has(stem.toLowerCase());
399
403
  const slugs = segments.map(slugifySegment);
400
404
  if (!isIndex) slugs.push(slugifySegment(stem));
@@ -460,10 +464,10 @@ function createLinkResolver(pages, base) {
460
464
  const dir = withoutExt.split("/").slice(0, -1).join("/");
461
465
  if (dir !== "") byPath.set(dir.toLowerCase(), page);
462
466
  }
463
- const basename2 = withoutExt.split("/").pop() ?? "";
464
- add(byName, basename2.toLowerCase(), page);
465
- const slugged = slugifySegment(basename2);
466
- if (slugged !== basename2.toLowerCase()) add(byName, slugged, page);
467
+ const basename3 = withoutExt.split("/").pop() ?? "";
468
+ add(byName, basename3.toLowerCase(), page);
469
+ const slugged = slugifySegment(basename3);
470
+ if (slugged !== basename3.toLowerCase()) add(byName, slugged, page);
467
471
  }
468
472
  const pick = (candidates) => [...candidates].sort((a, b) => {
469
473
  const depth = a.file.split("/").length - b.file.split("/").length;
@@ -523,9 +527,9 @@ function splitHash(value) {
523
527
  if (index === -1) return [value, void 0];
524
528
  return [value.slice(0, index), value.slice(index + 1)];
525
529
  }
526
- function joinPosix(fromDir, relative3) {
530
+ function joinPosix(fromDir, relative4) {
527
531
  const segments = [...fromDir];
528
- for (const part of toPosix(relative3).split("/")) {
532
+ for (const part of toPosix(relative4).split("/")) {
529
533
  if (part === "" || part === ".") continue;
530
534
  if (part === "..") segments.pop();
531
535
  else segments.push(part);
@@ -1387,6 +1391,7 @@ function clientConfig(ctx) {
1387
1391
  nav: config.nav,
1388
1392
  footer: config.footer,
1389
1393
  editLink: config.editLink,
1394
+ pageActions: config.pageActions,
1390
1395
  favicon: config.favicon === void 0 ? void 0 : withBase(config.base, `/${toPosix(config.favicon)}`),
1391
1396
  search: config.search.provider === "static" ? { provider: "static", from: withBase(config.base, "/api/search.json") } : config.search,
1392
1397
  contentRoot: config.root
@@ -1625,10 +1630,10 @@ async function loadPrerenderModule(ctx, ssrOutDir) {
1625
1630
  await build(createViteConfig({ ctx, mode: "build", ssrOutDir }));
1626
1631
  const entry = join6(ssrOutDir, "entry.prerender.js");
1627
1632
  const loaded = await import(pathToFileURL(entry).href);
1628
- if (typeof loaded.render !== "function" || typeof loaded.listRoutes !== "function") {
1629
- throw new Error(`seemore: the prerender build at ${entry} did not export \`render\` and \`listRoutes\`.`);
1633
+ if (typeof loaded.render !== "function" || typeof loaded.renderArticle !== "function" || typeof loaded.listRoutes !== "function") {
1634
+ throw new Error(`seemore: the prerender build at ${entry} did not export \`render\`, \`renderArticle\` and \`listRoutes\`.`);
1630
1635
  }
1631
- return { render: loaded.render, listRoutes: loaded.listRoutes };
1636
+ return { render: loaded.render, renderArticle: loaded.renderArticle, listRoutes: loaded.listRoutes };
1632
1637
  }
1633
1638
 
1634
1639
  // src/node/social/cards.ts
@@ -1838,12 +1843,248 @@ async function runDev(options) {
1838
1843
  };
1839
1844
  }
1840
1845
 
1846
+ // src/cli/export.ts
1847
+ import { existsSync as existsSync4, mkdtempSync as mkdtempSync2, readFileSync as readFileSync6, readdirSync, rmSync as rmSync2, statSync, writeFileSync as writeFileSync6, mkdirSync as mkdirSync4 } from "fs";
1848
+ import { basename as basename2, dirname as dirname8, extname, join as join9, relative as relative3, resolve as resolve6 } from "path";
1849
+ import { tmpdir as tmpdir3 } from "os";
1850
+ import pc4 from "picocolors";
1851
+ import { build as viteBuild2 } from "vite";
1852
+ init_paths();
1853
+
1854
+ // src/app/export/themeToggle.ts
1855
+ var THEME_TOGGLE = `<button type="button" class="seemore-export-theme-toggle" aria-label="Toggle dark mode">
1856
+ <svg class="seemore-icon-light" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><circle cx="12" cy="12" r="4"/><path d="M12 2v2"/><path d="M12 20v2"/><path d="m4.93 4.93 1.41 1.41"/><path d="m17.66 17.66 1.41 1.41"/><path d="M2 12h2"/><path d="M20 12h2"/><path d="m6.34 17.66-1.41 1.41"/><path d="m19.07 4.93-1.41 1.41"/></svg>
1857
+ <svg class="seemore-icon-dark" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M12 3a6 6 0 0 0 9 9 9 9 0 1 1-9-9Z"/></svg>
1858
+ </button>`;
1859
+
1860
+ // src/cli/export.ts
1861
+ async function runExport(options) {
1862
+ const target = resolve6(options.cwd, options.file);
1863
+ if (!existsSync4(target) || !statSync(target).isFile()) {
1864
+ throw new Error(`No such file: ${options.file}`);
1865
+ }
1866
+ const contentRoot = resolveContentRoot(options.cwd, dirname8(target));
1867
+ const loaded = await loadConfig({ root: contentRoot, configPath: resolveConfigPath(options) });
1868
+ const config = {
1869
+ ...loaded.config,
1870
+ base: options.base === void 0 ? loaded.config.base : normaliseBase(options.base)
1871
+ };
1872
+ if (!config.pageActions.includes("export-html")) {
1873
+ throw new Error(`\`pageActions\` in ${loaded.file ?? "seemore.config.ts"} does not include 'export-html', so this site's pages cannot be exported.`);
1874
+ }
1875
+ const ctx = createContext({ config, contentRoot });
1876
+ const scan2 = ctx.source.current();
1877
+ for (const warning of scan2.warnings) ctx.warnings.add(warning);
1878
+ const errors = ctx.errors();
1879
+ if (errors.length > 0) {
1880
+ throw new Error(`seemore found ${errors.length} problem(s) in ${contentRoot}:
1881
+
1882
+ ${errors.join("\n\n")}`);
1883
+ }
1884
+ const page = ctx.pages().find((candidate) => candidate.absPath === canonicalise(target));
1885
+ if (page === void 0) {
1886
+ throw new Error(`${options.file} is not part of this site \u2014 excluded in the config, or outside ${relative3(options.cwd, contentRoot) || "."}.`);
1887
+ }
1888
+ const outDir = mkdtempSync2(join9(tmpdir3(), "seemore-export-"));
1889
+ const ssrOutDir = mkdtempSync2(join9(tmpdir3(), "seemore-export-ssr-"));
1890
+ try {
1891
+ await viteBuild2(createViteConfig({ ctx, mode: "build", outDir }));
1892
+ const css = readBuiltCss(outDir);
1893
+ const template = readFileSync6(join9(outDir, "index.html"), "utf8");
1894
+ const prerender = await loadPrerenderModule(ctx, ssrOutDir);
1895
+ const article = await prerender.renderArticle(page.url);
1896
+ const runtime = await bundleRuntime();
1897
+ const html = assemble({ article, css, runtime, config, outDir, contentRoot, template });
1898
+ const filename = `${basename2(target).replace(/\.(?:md|mdx)$/i, "")}.html`;
1899
+ const targetPath = options.out === void 0 ? join9(dirname8(target), filename) : join9(resolve6(options.cwd, options.out), filename);
1900
+ if (existsSync4(targetPath)) {
1901
+ console.log(pc4.yellow(`seemore replacing existing ${relative3(options.cwd, targetPath) || targetPath}`));
1902
+ }
1903
+ mkdirSync4(dirname8(targetPath), { recursive: true });
1904
+ writeFileSync6(targetPath, html, "utf8");
1905
+ ctx.warnings.flush();
1906
+ console.log(pc4.green(`seemore wrote ${relative3(options.cwd, targetPath) || targetPath} (${formatBytes2(html.length)})`));
1907
+ } finally {
1908
+ rmSync2(outDir, { recursive: true, force: true });
1909
+ rmSync2(ssrOutDir, { recursive: true, force: true });
1910
+ }
1911
+ }
1912
+ function readBuiltCss(outDir) {
1913
+ const assets = join9(outDir, "assets");
1914
+ const files = existsSync4(assets) ? readdirSync(assets).filter((file) => file.endsWith(".css")) : [];
1915
+ if (files.length === 0) throw new Error("The build produced no stylesheet \u2014 the export would be unstyled.");
1916
+ return files.map((file) => readFileSync6(join9(assets, file), "utf8")).join("\n");
1917
+ }
1918
+ async function bundleRuntime() {
1919
+ const dir = mkdtempSync2(join9(tmpdir3(), "seemore-export-runtime-"));
1920
+ try {
1921
+ await viteBuild2({
1922
+ configFile: false,
1923
+ root: appRoot(),
1924
+ build: {
1925
+ outDir: dir,
1926
+ emptyOutDir: true,
1927
+ minify: true,
1928
+ target: "es2018",
1929
+ lib: {
1930
+ entry: join9(appRoot(), "export", "standalone.ts"),
1931
+ name: "seemoreExport",
1932
+ formats: ["iife"],
1933
+ fileName: () => "runtime.js"
1934
+ }
1935
+ }
1936
+ });
1937
+ return readFileSync6(join9(dir, "runtime.js"), "utf8");
1938
+ } finally {
1939
+ rmSync2(dir, { recursive: true, force: true });
1940
+ }
1941
+ }
1942
+ function assemble(input) {
1943
+ const { article, css, runtime, config, outDir, contentRoot, template } = input;
1944
+ const content = withExportToc(inlineHtmlAssets(article.html, config.base, outDir, contentRoot));
1945
+ const title = article.title === "" ? config.title : `${article.title} \xB7 ${config.title}`;
1946
+ const description = article.description ?? config.description;
1947
+ const favicon = faviconLink(config, contentRoot, template);
1948
+ return [
1949
+ "<!doctype html>",
1950
+ '<html lang="en">',
1951
+ "<head>",
1952
+ '<meta charset="utf-8">',
1953
+ '<meta name="viewport" content="width=device-width, initial-scale=1">',
1954
+ `<title>${escapeHtml(title)}</title>`,
1955
+ description === void 0 ? "" : `<meta name="description" content="${escapeHtml(description)}">`,
1956
+ '<meta name="generator" content="seemore">',
1957
+ favicon,
1958
+ `<style>
1959
+ ${inlineCssUrls(css, config.base, outDir, contentRoot).replaceAll("</style", "<\\/style")}</style>`,
1960
+ "</head>",
1961
+ "<body>",
1962
+ `<main class="seemore-export-main">`,
1963
+ `<article class="seemore-article prose">${content}</article>`,
1964
+ "</main>",
1965
+ THEME_TOGGLE,
1966
+ `<script>${runtime.replaceAll("</script", "<\\/script")}</script>`,
1967
+ "</body>",
1968
+ "</html>"
1969
+ ].filter((line) => line !== "").join("\n");
1970
+ }
1971
+ function inlineHtmlAssets(html, base, outDir, contentRoot) {
1972
+ return html.replace(
1973
+ /(<(?:img|embed|iframe|video|audio|source)\b[^>]*\bsrc=")([^"]*)(")/g,
1974
+ (whole, open, src, close) => {
1975
+ const file = resolveAsset(src, base, outDir, contentRoot);
1976
+ if (file === void 0) return whole;
1977
+ try {
1978
+ return `${open}${dataUriFor(file)}${close}`;
1979
+ } catch {
1980
+ return whole;
1981
+ }
1982
+ }
1983
+ );
1984
+ }
1985
+ function inlineCssUrls(css, base, outDir, contentRoot) {
1986
+ return css.replace(/url\(\s*(['"]?)([^'")]+)\1\s*\)/g, (whole, _quote, raw) => {
1987
+ if (/^(?:data:|https?:)/i.test(raw)) return whole;
1988
+ const file = resolveAsset(raw, base, outDir, contentRoot);
1989
+ if (file === void 0) return whole;
1990
+ try {
1991
+ return `url("${dataUriFor(file)}")`;
1992
+ } catch {
1993
+ return whole;
1994
+ }
1995
+ });
1996
+ }
1997
+ function resolveAsset(src, base, outDir, contentRoot) {
1998
+ if (src === "" || /^(?:data:|blob:)/i.test(src) || src.startsWith("#")) return void 0;
1999
+ let path = src.split("#")[0]?.split("?")[0] ?? "";
2000
+ if (base !== "/" && path.startsWith(base)) path = path.slice(base.length - 1);
2001
+ const suffix = path.replace(/^\/+/, "");
2002
+ for (const root of [outDir, contentRoot]) {
2003
+ const candidate = join9(root, suffix);
2004
+ if (existsSync4(candidate) && statSync(candidate).isFile()) return candidate;
2005
+ }
2006
+ return void 0;
2007
+ }
2008
+ var MIME = {
2009
+ ".svg": "image/svg+xml",
2010
+ ".png": "image/png",
2011
+ ".jpg": "image/jpeg",
2012
+ ".jpeg": "image/jpeg",
2013
+ ".gif": "image/gif",
2014
+ ".webp": "image/webp",
2015
+ ".avif": "image/avif",
2016
+ ".ico": "image/x-icon",
2017
+ ".pdf": "application/pdf",
2018
+ ".woff": "font/woff",
2019
+ ".woff2": "font/woff2",
2020
+ ".ttf": "font/ttf",
2021
+ ".otf": "font/otf",
2022
+ ".mp4": "video/mp4",
2023
+ ".webm": "video/webm"
2024
+ };
2025
+ function dataUriFor(file) {
2026
+ const mime = MIME[extname(file).toLowerCase()] ?? "application/octet-stream";
2027
+ return `data:${mime};base64,${readFileSync6(file).toString("base64")}`;
2028
+ }
2029
+ function faviconLink(config, contentRoot, template) {
2030
+ if (config.favicon !== void 0) {
2031
+ const file = join9(contentRoot, config.favicon);
2032
+ if (existsSync4(file)) {
2033
+ try {
2034
+ return `<link rel="icon" href="${dataUriFor(file)}" />`;
2035
+ } catch {
2036
+ }
2037
+ }
2038
+ }
2039
+ const matches = Array.from(template.matchAll(/<link rel="icon"[^>]*>/gi));
2040
+ return matches.at(-1)?.[0] ?? "";
2041
+ }
2042
+ function withExportToc(html) {
2043
+ const headings = Array.from(html.matchAll(/<h([23])\b[^>]*\bid="([^"]*)"[^>]*>([\s\S]*?)<\/h\1>/g)).map(
2044
+ (match) => ({
2045
+ depth: Number(match[1]),
2046
+ id: match[2] ?? "",
2047
+ text: decodeEntities((match[3] ?? "").replace(/<[^>]+>/g, ""))
2048
+ })
2049
+ );
2050
+ if (headings.length === 0) return html;
2051
+ const sections = [];
2052
+ for (const heading of headings) {
2053
+ if (heading.depth === 2 || sections.length === 0) {
2054
+ sections.push({ id: heading.id, text: heading.text, children: [] });
2055
+ } else {
2056
+ sections.at(-1)?.children.push({ id: heading.id, text: heading.text });
2057
+ }
2058
+ }
2059
+ const items = sections.map((section) => {
2060
+ const self = `<a href="#${section.id}">${escapeHtml(section.text)}</a>`;
2061
+ if (section.children.length === 0) return `<li>${self}</li>`;
2062
+ const kids = section.children.map((child) => `<li><a href="#${child.id}">${escapeHtml(child.text)}</a></li>`).join("");
2063
+ return `<li>${self}<ul>${kids}</ul></li>`;
2064
+ }).join("");
2065
+ const toc = `<nav class="seemore-export-toc"><details><summary>On this page</summary><ul>${items}</ul></details></nav>`;
2066
+ const h1 = /<\/h1>/i.exec(html);
2067
+ return h1 === null ? toc + html : html.slice(0, h1.index + h1[0].length) + toc + html.slice(h1.index + h1[0].length);
2068
+ }
2069
+ function decodeEntities(text) {
2070
+ return text.replaceAll("&amp;", "&").replaceAll("&lt;", "<").replaceAll("&gt;", ">").replaceAll("&quot;", '"').replaceAll("&#39;", "'");
2071
+ }
2072
+ function escapeHtml(value) {
2073
+ return value.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll('"', "&quot;");
2074
+ }
2075
+ function formatBytes2(size) {
2076
+ if (size < 1024) return `${size} B`;
2077
+ if (size < 1024 * 1024) return `${(size / 1024).toFixed(1)} kB`;
2078
+ return `${(size / (1024 * 1024)).toFixed(1)} MB`;
2079
+ }
2080
+
1841
2081
  // src/cli/index.ts
1842
2082
  var USAGE = `
1843
- ${pc4.bold("seemore")} \u2014 turn a folder of Markdown into a docs site
2083
+ ${pc5.bold("seemore")} \u2014 turn a folder of Markdown into a docs site
1844
2084
 
1845
2085
  seemore [dir] start the dev server
1846
2086
  seemore build [dir] build a static site into dist/
2087
+ seemore export <file> export a page as a standalone HTML file
1847
2088
 
1848
2089
  Options
1849
2090
  --port <number> dev server port (default 4040)
@@ -1851,7 +2092,7 @@ Options
1851
2092
  --open / --no-open open a browser on start (default: no)
1852
2093
  --json print one machine-readable JSON line instead of the summary (dev only)
1853
2094
  --config <path> path to seemore.config.ts
1854
- --out <dir> build output directory (default: dist)
2095
+ --out <dir> build output directory (default: dist); for export, where the HTML file is written
1855
2096
  --base <path> subpath the site is served from, e.g. /my-repo/
1856
2097
  -h, --help show this message
1857
2098
  -v, --version show the version
@@ -1885,21 +2126,28 @@ async function main(argv = process.argv.slice(2)) {
1885
2126
  return;
1886
2127
  }
1887
2128
  if (values.version === true) {
1888
- const { readFileSync: readFileSync6 } = await import("fs");
1889
- const { join: join9 } = await import("path");
2129
+ const { readFileSync: readFileSync7 } = await import("fs");
2130
+ const { join: join10 } = await import("path");
1890
2131
  const { packageRoot: packageRoot2 } = await Promise.resolve().then(() => (init_paths(), paths_exports));
1891
- const pkg = JSON.parse(readFileSync6(join9(packageRoot2(), "package.json"), "utf8"));
2132
+ const pkg = JSON.parse(readFileSync7(join10(packageRoot2(), "package.json"), "utf8"));
1892
2133
  console.log(pkg.version);
1893
2134
  return;
1894
2135
  }
1895
2136
  const [command, ...rest] = positionals;
1896
2137
  const isBuild = command === "build";
2138
+ const isExport = command === "export";
1897
2139
  const dir = isBuild ? rest[0] : command;
1898
2140
  const shared = { cwd: process.cwd(), dir, configPath: values.config, base: values.base };
1899
2141
  if (isBuild) {
1900
2142
  await runBuild({ ...shared, outDir: values.out });
1901
2143
  return;
1902
2144
  }
2145
+ if (isExport) {
2146
+ const file = rest[0];
2147
+ if (file === void 0) throw new Error("Usage: seemore export <file> \u2014 name the Markdown file to export.");
2148
+ await runExport({ cwd: shared.cwd, file, out: values.out, configPath: values.config, base: values.base });
2149
+ return;
2150
+ }
1903
2151
  await runDev({
1904
2152
  ...shared,
1905
2153
  port: values.port === void 0 ? void 0 : Number(values.port),
@@ -1910,7 +2158,7 @@ async function main(argv = process.argv.slice(2)) {
1910
2158
  }
1911
2159
  main().catch((error) => {
1912
2160
  console.error(`
1913
- ${pc4.red("seemore")} ${error instanceof Error ? error.message : String(error)}
2161
+ ${pc5.red("seemore")} ${error instanceof Error ? error.message : String(error)}
1914
2162
  `);
1915
2163
  process.exitCode = 1;
1916
2164
  });