{"id":50183286,"url":"https://github.com/karuifeather/featherdown","last_synced_at":"2026-05-25T08:01:08.746Z","repository":{"id":351467123,"uuid":"1211079128","full_name":"karuifeather/featherdown","owner":"karuifeather","description":"ESM-first Markdown to HTML pipeline for Node, browsers, and Deno with math, syntax highlighting, chart placeholders, image rewriting, and optional Node-only Mermaid rendering.","archived":false,"fork":false,"pushed_at":"2026-05-05T18:02:01.000Z","size":370,"stargazers_count":2,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-05-05T19:33:53.342Z","etag":null,"topics":["deno","html","katex","markdown","mermaid","rehype","remark","unified"],"latest_commit_sha":null,"homepage":"http://featherdown.karuifeather.com/","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/karuifeather.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-04-15T03:37:39.000Z","updated_at":"2026-05-05T18:05:48.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/karuifeather/featherdown","commit_stats":null,"previous_names":["karuifeather/featherdown"],"tags_count":2,"template":false,"template_full_name":null,"purl":"pkg:github/karuifeather/featherdown","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/karuifeather%2Ffeatherdown","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/karuifeather%2Ffeatherdown/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/karuifeather%2Ffeatherdown/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/karuifeather%2Ffeatherdown/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/karuifeather","download_url":"https://codeload.github.com/karuifeather/featherdown/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/karuifeather%2Ffeatherdown/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":33457339,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-24T19:21:36.376Z","status":"ssl_error","status_checked_at":"2026-05-24T19:21:10.562Z","response_time":57,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.5:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["deno","html","katex","markdown","mermaid","rehype","remark","unified"],"created_at":"2026-05-25T08:00:54.040Z","updated_at":"2026-05-25T08:01:08.731Z","avatar_url":"https://github.com/karuifeather.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# featherdown\n\n**featherdown** is a publishing-focused Markdown engine for apps, blogs, docs, and product surfaces that want **sanitized HTML**, **publishing metadata**, **diagnostics**, **code and math support**, and **asset hints**—without hand-wiring a full [unified](https://github.com/unifiedjs/unified) pipeline.\n\nIt is **not** a static site generator and does not ship client runtimes for charts, copy-to-clipboard behavior, or Mermaid in the browser. It produces HTML contracts and metadata your app can integrate.\n\nThe default npm entry is **browser-safe**. Optional **Node-only** Mermaid (inline SVG) lives behind `featherdown/node`.\n\n## Install\n\n```bash\nnpm install featherdown\n```\n\nNode.js **18+** is required.\n\n### JSR / Deno\n\nYou can import the library from JSR (see [@karuifeather/featherdown](https://jsr.io/@karuifeather/featherdown)). **npm** publishes **CSS subpath exports** (`featherdown/styles.css`, `featherdown/styles/base.css`, and the other `featherdown/styles/*` entries in `package.json`). **Deno/JSR** (`deno.json` exports) does **not** mirror those CSS paths today—**CSS parity for JSR is deferred**; treat stylesheet wiring as environment-specific. **CSS packaging and `@import` resolution for JSR/Deno consumers are not treated as equivalent to npm bundler workflows.** Prefer npm (or a bundler) when you want the documented CSS entry points without extra setup.\n\n## Quick start\n\n```ts\nimport { Featherdown } from \"featherdown\";\nimport \"featherdown/styles.css\";\n\nconst featherdown = new Featherdown();\n\nconst result = await featherdown.parse(\"# Hello\");\n\nconsole.log(result.html);\n```\n\nThe default entry is browser-safe and does **not** include Mermaid rendering.\n\n## Rendering Markdown\n\nUse the **`Featherdown`** class as the primary API. Constructor options set defaults; each call to **`parse(markdown, parseOptions?)`** can override them (with a shallow merge and one-level merge for nested groups like `code` and `publishing`).\n\n```ts\nconst featherdown = new Featherdown({\n  frontmatter: \"auto\",\n  math: true,\n  code: true,\n  diagnostics: \"warn\",\n});\n\nconst result = await featherdown.parse(markdown);\n```\n\n### Image manifest example\n\n```ts\nconst featherdown = new Featherdown({\n  kind: \"post\",\n  slug: \"hello-world\",\n  manifest: {\n    map: {\n      \"post/hello-world/images/logo.png\": { url: \"https://cdn.example.com/logo.png\" },\n    },\n  },\n});\n\nconst result = await featherdown.parse(markdown);\n```\n\n## Result object\n\n`Featherdown.parse()` resolves to a **`FeatherdownResult`**. Commonly used fields:\n\n| Field | Meaning |\n| --- | --- |\n| **`html`** | Sanitized HTML string. |\n| **`body`** | Markdown body after front matter is stripped when front matter parsing is enabled and a valid block exists; otherwise the full input string. |\n| **`frontmatter`** | Parsed YAML object, or `{}` when absent or when front matter parsing is disabled. |\n| **`metadata`** | Normalized subset of common publishing fields (`title`, `description`, `tags`, etc.) derived from front matter. |\n| **`toc`** | Compact heading list for navigation (`depth`, `text`, `id`). |\n| **`headings`** | Richer heading metadata in document order (includes `hasCustomId`). |\n| **`excerpt`** | First meaningful plain-text excerpt from rendered flow content, or `null` when disabled or unavailable. |\n| **`wordCount`** / **`estimatedReadingMinutes`** | Derived from the rendered document text (subject to `publishing` options). |\n| **`stats`** | Mirrors `wordCount` and reading time for convenience. |\n| **`diagnostics`** | Non-fatal warnings (for example invalid chart JSON or manifest misses). |\n| **`assets`** | Hints such as **`assets.styles`** (selective package CSS paths) and **`assets.features`** (`math`, `code`, `charts`, `mermaid`). |\n\n`assets.styles` lists **selective** style paths (for example `featherdown/styles/base.css`), not the all-in `featherdown/styles.css` aggregate.\n\n## Styles\n\nOfficial styles target the **`featherdown-content`** wrapper class and related HTML contracts.\n\n### All-in (recommended starting point)\n\nThese entry points are intended for **npm / bundler** workflows where the toolchain resolves package paths and nested `@import`s.\n\n```ts\nimport \"featherdown/styles.css\";\n```\n\nThis aggregates base, KaTeX, code, and highlight layers. It is the simplest path when you want the full default look.\n\n### Selective imports\n\nFor smaller CSS payloads, import only what you need:\n\n```ts\nimport \"featherdown/styles/base.css\";\nimport \"featherdown/styles/katex.css\"; // when math is enabled\nimport \"featherdown/styles/code.css\"; // when code blocks / shell chrome are used\nimport \"featherdown/styles/highlight.css\"; // only when syntax highlighting is enabled\n```\n\n- **`base.css`** — Prose/layout and structural contracts for rendered content.\n- **`katex.css`** — Bundler-oriented entry that pulls in KaTeX’s distribution CSS for math output.\n- **`code.css`** — Featherdown code-block shell / chrome (titles, line layout hooks, etc.).\n- **`highlight.css`** — Default highlight.js theme CSS; **include this only when syntax highlighting is enabled** (`code` with default highlighting, or equivalent).\n\nDo **not** assume raw `\u003clink href=\"node_modules/.../featherdown/styles.css\"\u003e` without a bundler will resolve nested `@import`s the same way a bundler does.\n\n### Custom themes\n\nYou can start from Featherdown's baseline styles and override them:\n\n```ts\nimport \"featherdown/styles/base.css\";\nimport \"./markdown-theme.css\";\nimport \"./code-theme.css\";\n```\n\nOr skip `base.css` and fully own the rendered Markdown styles in your app.\n\nWhen code highlighting is enabled, rendered code uses highlight.js-style `hljs` token classes, so custom code themes can target classes such as `.hljs-keyword`, `.hljs-string`, and `.hljs-comment`.\n\n### Asset hints\n\n```ts\nconst result = await featherdown.parse(markdown);\nconsole.log(result.assets.styles);\n```\n\nUse this in SSR or static generators to decide which stylesheets to emit alongside HTML.\n\n## Options\n\nThese options are wired end-to-end today:\n\n```ts\nconst featherdown = new Featherdown({\n  frontmatter: \"auto\",\n  math: true,\n  code: true,\n  sanitize: true,\n  diagnostics: true,\n  charts: true,\n  headings: true,\n  publishing: true,\n});\n```\n\nObject forms for nested groups:\n\n```ts\nconst featherdown = new Featherdown({\n  code: {\n    enabled: true,\n    highlighting: true,\n  },\n  publishing: {\n    excerpt: true,\n    wordCount: true,\n    readingTime: true,\n  },\n});\n```\n\n- **`math`** — Toggle KaTeX HTML output for `$…$` / `$$…$$`.\n- **`code`** — Toggle code pipeline features; **`highlighting`** controls whether highlight.js classes and **`highlight.css`** are relevant.\n- **`frontmatter`** — `false`, `true`, or `\"auto\"`; controls whether leading YAML front matter is parsed and stripped from `body`.\n- **`sanitize`** — When enabled, output passes through `rehype-sanitize` with the package schema.\n- **`diagnostics`** — Controls whether warnings appear on the result and whether strict mode fails the parse.\n  - **`false`** — `result.diagnostics` is always `[]` (issues may still be detected internally).\n  - **`true`** or **`\"warn\"`** — Non-fatal diagnostics are returned on `result.diagnostics`; the parse does not throw.\n  - **`\"strict\"`** — Same collection as warn; if any diagnostic exists after rendering, the parse throws **`FeatherdownDiagnosticsError`** with **`error.diagnostics`** and optional **`error.result`** (full `FeatherdownResult`, including `html`). Use `instanceof FeatherdownDiagnosticsError` in callers that need to branch.\n\n```ts\nimport { Featherdown, FeatherdownDiagnosticsError } from \"featherdown\";\n\nconst featherdown = new Featherdown({\n  diagnostics: \"warn\",\n});\n\nconst strict = new Featherdown({\n  diagnostics: \"strict\",\n});\n\ntry {\n  await strict.parse(markdown);\n} catch (e) {\n  if (e instanceof FeatherdownDiagnosticsError) {\n    console.error(e.diagnostics);\n  }\n}\n```\n- **`charts`** — When enabled, valid `chart-*` fences become mount placeholders; invalid JSON surfaces diagnostics when diagnostics are enabled.\n- **`headings`** — Controls **`result.headings`** and **`result.toc`** (metadata extracted after render). Heading markup in **`html`** is unchanged; this option gates the arrays on the result.\n  - **`false`** — **`result.headings`** and **`result.toc`** are both empty arrays.\n  - **`true`** — Default heading metadata: both arrays are populated when rendered headings have ids.\n  - **Object** — Shape matches **`FeatherdownHeadingOptions`**: optional **`toc`**. When **`toc`** is **`false`**, **`result.toc`** is empty while **`result.headings`** can still be filled. Optional **`slugs`** and **`anchors`** are part of the public type and merge like other nested options; they do not toggle the Markdown processor in this release.\n- **`publishing`** — Gates excerpt, word count, and reading-time fields on the result.\n\n**`kind`**, **`slug`**, and **`manifest`** (local `map` and optional `remote`) drive manifest-based image URL rewriting—the same shape supported historically on render helpers.\n\nAvoid treating every nested field on `FeatherdownOptions` as a global behavioral switch unless documented below (for example line numbers and copy UI remain **per-fence** in Markdown).\n\n## Front matter\n\nPublishing-style front matter is **opt in**. The default constructor does **not** strip YAML; pass **`frontmatter: \"auto\"`** (or `true`) when you want leading `---` YAML parsed and removed from the body string.\n\n```ts\nconst featherdown = new Featherdown({\n  frontmatter: \"auto\",\n});\n\nconst result = await featherdown.parse(`---\ntitle: Hello World\ntags:\n  - docs\n---\n\n# Hello`);\n```\n\nThen:\n\n- `result.frontmatter.title`, `result.metadata.title`\n- `result.body` — Markdown after the front matter block\n- `result.html` — Rendered from `body` only\n\n**Lenient behavior:** invalid or non-mapping YAML does not throw; the raw document is treated as body content with empty `frontmatter` / `metadata` where applicable.\n\n**Strict legacy utility:** `parseMarkdownFile()` still throws on invalid YAML and returns **`frontMatter`** / **`content`**. Use it only when you need that contract; otherwise prefer `Featherdown` with `frontmatter: \"auto\"`.\n\n## Code blocks\n\n- **Fenced code** renders as sanitized `\u003cpre\u003e\u003ccode\u003e` with highlight.js classes when highlighting is on.\n- **Titles:** ` ```ts title=\"example.ts\" ` — emits `code-block` / `code-block-title` markup.\n- **Line highlights:** ` ```ts {2,4-5} ` — `code-line` / `code-line-highlighted` wrappers.\n- **Line numbers:** ` ```ts showLineNumbers ` — `code-line-numbered` and per-line number spans (per fence).\n- **Copy UI hooks:** `showCopyButton` / `copyButton` on the fence emit button markup and `data-*` attributes for your app to wire up. **The package does not ship clipboard JavaScript.**\n\nStyling for these contracts comes from **`code.css`** and **`highlight.css`** (when highlighting is enabled).\n\n## Math\n\nEnable math on the instance or per parse:\n\n```ts\nconst featherdown = new Featherdown({\n  math: true,\n});\n```\n\nEnsure KaTeX styles are loaded (all-in `featherdown/styles.css`, or `featherdown/styles/katex.css`). The public option name is **`math`**; KaTeX is used internally for HTML output.\n\n## Charts, callouts, and publishing features\n\n### Chart placeholders\n\nValid `chart-*` fenced JSON becomes a **`chart-mount`** placeholder with `data-chart` and `data-chart-data`. The package does **not** bundle a chart runtime—your app hydrates or renders charts.\n\n### Callouts (admonitions)\n\n`:::note`, `:::warning`, `:::tip`, etc. (with optional `:::type[Title]`) render to **`callout`** / **`callout-\u003ctype\u003e`** structures. Body markdown is processed normally.\n\n### Images\n\nOptional **`manifest`** + **`kind`** + **`slug`** rewrite relative `![alt](./path)` sources using deterministic keys. No CDN fetching is built in.\n\n### TOC, excerpt, counts\n\n`toc`, `headings`, `excerpt`, `wordCount`, and `estimatedReadingMinutes` come from the **same** render pass as `html`, gated by **`headings`** and **`publishing`** options.\n\n## Node usage\n\nImport **`Featherdown` from `featherdown/node`** for filesystem helpers and optional Mermaid.\n\n```ts\nimport { Featherdown } from \"featherdown/node\";\n\nconst featherdown = new Featherdown({\n  frontmatter: \"auto\",\n});\n\nconst result = await featherdown.parseFile(\"./posts/hello.md\");\n```\n\n- **`parseFile(path)`** reads UTF-8 and returns the same **`FeatherdownResult`** as **`parse(markdown)`** for the same file contents and options.\n\n## Mermaid (Node only)\n\nInline SVG Mermaid is available **only** through the Node entry, with **`mermaid: { render: \"svg\" }`**. There is **no** placeholder-only mode documented today.\n\n```ts\nimport { Featherdown } from \"featherdown/node\";\n\nconst featherdown = new Featherdown({\n  mermaid: {\n    render: \"svg\",\n  },\n});\n\nconst result = await featherdown.parse(markdown);\n```\n\n- Uses **`rehype-mermaid`** and may require the **optional Playwright / Chromium** peer setup (`playwright` + `npx playwright install chromium`). Failures can be **environment-sensitive** in CI.\n- Valid diagrams become inline **SVG** in `html`; **`result.assets.features.mermaid`** is **`true`** when this path is active.\n- The Node Mermaid pipeline uses a **final SVG-aware sanitize** step and does **not** match every rehype plugin order detail of the default browser pipeline—expect rare edge differences for exotic inputs.\n\nThe default **`featherdown`** entry remains Mermaid-free for bundle safety.\n\n## Advanced and compatibility APIs\n\n### `featherdown/advanced`\n\nThe advanced subpath is for **existing low-level unified integrations**. Most applications should use **`Featherdown`**. The subpath exists for **existing unified pipelines** and low-level compatibility:\n\n```ts\nimport {\n  createMarkdownProcessor,\n  rehypeChartBlocks,\n  rehypeCdnImages,\n} from \"featherdown/advanced\";\n```\n\nThe same symbols remain re-exported from the **root** package for backward compatibility; prefer **`Featherdown`** for normal Markdown rendering.\n\n### Legacy function exports\n\nOlder helpers (`renderMarkdownToHtml`, `renderMarkdown`, `renderMarkdownDocument`, `parseMarkdownFile`, `createMarkdownProcessor`, …) remain exported and marked **`@deprecated`**. They take **`RenderMarkdownOptions`**, not the full **`FeatherdownOptions`** surface (for example **`math`**, **`code`**, **`charts`**, **`frontmatter`**, and **`diagnostics: \"strict\"`** are **`Featherdown`** features). Prefer **`new Featherdown().parse()`** for new code.\n\n## Migration from legacy helpers\n\nLegacy helpers remain available for compatibility, but new code should use **`Featherdown`**.\n\nFor default rendering behavior (what the legacy helpers covered under **`RenderMarkdownOptions`**), the closest migration is:\n\n```ts\n// Before\nconst html = await renderMarkdownToHtml(markdown);\n\n// After\nconst { html } = await new Featherdown().parse(markdown);\n```\n\n\u003e **Migration note:** The legacy helpers are kept for compatibility and use the older **`RenderMarkdownOptions`** surface. They are not a full alias for **`FeatherdownOptions`**. For feature gates such as **`math`**, **`code`**, **`charts`**, **`frontmatter`**, and **`diagnostics: \"strict\"`**, use the **`Featherdown`** class.\n\n| Legacy | Prefer |\n| --- | --- |\n| `renderMarkdownToHtml(md, opts)` | `(await new Featherdown().parse(md)).html` — legacy **`opts`** only apply the older options shape; pass **`FeatherdownOptions`** on **`Featherdown`** when you need feature gates |\n| `renderMarkdown(md, opts)` | `await new Featherdown().parse(md)` — use **`r.html`** and **`r.diagnostics`**; same **`RenderMarkdownOptions`** limitation as above |\n| `renderMarkdownDocument(md, opts)` | `await new Featherdown().parse(md)` for the default document pipeline when **`opts`** are legacy **`RenderMarkdownOptions`**. The **`Featherdown`** result adds **`body`**, **`frontmatter`**, **`assets`**, and related fields—configure **`Featherdown`** directly if you need **`FeatherdownOptions`** (not understood by the helper) |\n| `parseMarkdownFile(md)` | `new Featherdown({ frontmatter: \"auto\" }).parse(md)` returns **`frontmatter`**, **`metadata`**, **`body`**, and **`html`** in one result. Keep **`parseMarkdownFile`** only when you need its strict **`{ frontMatter, content }`** contract |\n| `renderMarkdownToHtmlWithMermaid(md, opts)` (Node) | `new Featherdown({ mermaid: { render: \"svg\" } }).parse(md)` then read **`html`** from **`featherdown/node`** — add **`kind`**, **`manifest`**, and other options in the same object as needed; **do not spread** an options object that might already define **`mermaid`** (merge carefully or use a dedicated instance). Legacy **`opts`** remain **`RenderMarkdownOptions`**-shaped |\n| `createMarkdownProcessor(opts)` | **`Featherdown`** for normal use; **`featherdown/advanced`** only for custom unified graphs |\n\n**Strict diagnostics:** **`diagnostics: \"strict\"`** exists only on **`Featherdown`**; legacy render helpers do not use it.\n\n## Styling reference (HTML contracts)\n\nRendered output uses stable classes including:\n\n- **Article surface:** wrap content in **`featherdown-content`** (see shipped CSS).\n- **Callouts:** `callout`, `callout-\u003ctype\u003e`, `callout-title`\n- **Code:** `code-block`, `code-block-title`, `code-line`, `code-line-highlighted`, `code-line-numbered`, `code-line-number`, `code-block-copyable`, `code-block-copy-button`\n- **Charts:** `chart-mount`\n- **Images:** `markdown-inline-img`\n\n## Security and trust\n\n- Input is sanitized (`rehype-sanitize`); scripts are stripped in the default pipeline.\n- Prefer the **default** entry for untrusted user content.\n- The **Node Mermaid** path is better suited to **trusted** publishing inputs.\n\n## Runtime boundaries\n\n| Entry | Role |\n| --- | --- |\n| **`featherdown`** | Browser-safe; no Mermaid / Playwright in the default bundle. |\n| **`featherdown/node`** | Node **`Featherdown`**, optional Mermaid SVG, **`parseFile`**. |\n| **`featherdown/advanced`** | Processor + rehype plugins for unified integrations. |\n\nMermaid is **npm subpath–oriented**; JSR usage focuses on the default library surface.\n\n### Client-side Mermaid previews\n\nThe default `featherdown` entry does not render Mermaid in the browser. If your app needs live Mermaid previews, parse Markdown first, then hydrate `code.language-mermaid` blocks with the `mermaid` package in your own app code. This keeps Mermaid out of Featherdown's browser bundle. Use `featherdown/node` when you want build-time SVG output.\n\n```ts\nimport mermaid from \"mermaid\";\n\nconst result = await featherdown.parse(markdown);\nmermaid.initialize({ startOnLoad: false, securityLevel: \"strict\" });\n```\n\n## Public API (summary)\n\n**Primary**\n\n- **`Featherdown`**, **`FeatherdownOptions`**, **`FeatherdownParseOptions`**, **`FeatherdownResult`**, and related types from **`featherdown`**.\n\n**Node**\n\n- **`Featherdown`** from **`featherdown/node`** with optional **`NodeFeatherdownOptions`** / Mermaid config.\n\n**Styles (package exports)**\n\n- `featherdown/styles.css`, `featherdown/styles/base.css`, `featherdown/styles/katex.css`, `featherdown/styles/code.css`, `featherdown/styles/highlight.css`\n\n**Compatibility**\n\n- Legacy render helpers and **`parseMarkdownFile`** on the default entry; **`renderMarkdownToHtmlWithMermaid`** on **`featherdown/node`**; **`featherdown/advanced`** for processor/plugins.\n\n## Scripts\n\n| Script | Description |\n| --- | --- |\n| `npm run build` | Build ESM artifacts in `dist/` |\n| `npm test` | Run Vitest once |\n| `npm run test:watch` | Vitest watch mode |\n| `npm run lint` | ESLint |\n| `npm run typecheck` | TypeScript `--noEmit` |\n| `npm run test:exports` | Build + export smoke checks |\n\n## Site\n\nA demo site lives in **`site/`**.\n\n```bash\nnpm run site:install\nnpm run site:dev\n```\n\nBuild:\n\n```bash\nnpm run site:build\n```\n\nGitHub Pages base path:\n\n```bash\nSITE_BASE_PATH=/featherdown/ npm run site:build\n```\n\n## Maintainers\n\nCI and publishing: [RELEASING.md](./RELEASING.md).\n\n## License\n\nMIT. See [LICENSE](./LICENSE).\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fkaruifeather%2Ffeatherdown","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fkaruifeather%2Ffeatherdown","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fkaruifeather%2Ffeatherdown/lists"}