From 2ae647f0786e524e8179f30b291d6b336fae5840 Mon Sep 17 00:00:00 2001 From: Igor Klepacki Date: Wed, 25 Jun 2025 20:54:50 +0200 Subject: [PATCH 1/4] feat: initialize the library --- README.md | 301 ++++++++++++---- package.json | 32 +- pnpm-lock.yaml | 869 ++++++++++++++++++++++++++++++++++++++++++++- src/index.ts | 372 ++++++++++++++++++- test/index.test.ts | 472 ++++++++++++++++++++++-- tsconfig.json | 1 - 6 files changed, 1937 insertions(+), 110 deletions(-) diff --git a/README.md b/README.md index 0f11766..30dc382 100644 --- a/README.md +++ b/README.md @@ -1,94 +1,259 @@ -# TypeScript Library Template +# remark-github-markdown-alerts -An opinionated production-ready TypeScript library template with automated builds, testing, and releases. +Transform GitHub-style markdown alerts into HTML using the [unified][unified] ecosystem -## Tech Stack - -- **TypeScript** - Strict configuration for type safety -- **Rollup** - Builds both CommonJS and ESM formats -- **Biome** - Fast linting and formatting -- **Vitest** - Testing with coverage reports -- **Husky** - Pre-commit hooks for code quality -- **Semantic Release** - Automated versioning and releases -- **pnpm** - Fast package management with Corepack -- **GitHub Actions** - CI/CD pipeline +> **Why this package over [jaywcjlove/remark-github-blockquote-alert][alternative]?** +> First of all, the mentioned project is a great one and has been there for a while, however it's design indicates slightly different usage - it's rather for out of the box implementation of 1:1 GitHub Alerts visuals than for custom implementations. +> +> On the other hand, the [neg4n/remark-github-markdown-alerts](https://github.com/neg4n/remark-github-markdown-alerts) offers maximum extensibility with granular configuration for class names, HTML elements, and custom icons. It's both ESM and CJS compatible, completely unstyled by default (no opinionated GitHub CSS), adaptable to any design system ## Features -- ๐Ÿ“ฆ **Dual Package Support** - Outputs CommonJS and ESM builds -- ๐Ÿ›ก๏ธ **Type Safety** - Extremely strict TypeScript configuration -- โœ… **Build Validation** - Uses `@arethetypeswrong/cli` to check package exports -- ๐Ÿงช **Automated Testing** - Vitest with coverage reporting -- ๐ŸŽจ **Code Quality** - Biome linting and formatting with pre-commit hooks -- ๐Ÿš€ **Automated Releases** - Semantic versioning with changelog generation -- โš™๏ธ **CI/CD Pipeline** - GitHub Actions for testing and publishing -- ๐Ÿ”ง **One-Click Setup** - Automated repository configuration with `init.sh` +- ๐ŸŽฏ **GitHub compatibility** - Renders `[!NOTE]`, `[!TIP]`, `[!IMPORTANT]`, `[!WARNING]`, and `[!CAUTION]` alerts +- ๐Ÿ›ก๏ธ **100% test coverage** - Comprehensive test suite +- ๐Ÿ”ง **Maximum extensibility** - Configure HTML elements, class names, and custom icons per alert type +- ๐ŸŽจ **Unstyled by default** - No opinionated CSS, works with any design system +- ๐Ÿ“ฆ **TypeScript support** - Batteries included with typed HTML tags and more +- ๐Ÿ”ง **[Unified][unified] ecosystem** - Works with remark, rehype and can be easily used with [react-markdown][react-markdown] + +## Installation + +```sh +npm i remark-github-markdown-alerts +# or +yarn add remark-github-markdown-alerts +# or +pnpm add remark-github-markdown-alerts +# or +bun add remark-github-markdown-alerts +``` + +## Usage + +### With remark + +```ts +import { remark } from 'remark' +import remarkHtml from 'remark-html' +import { remarkGitHubAlerts } from 'remark-github-markdown-alerts' + +const processor = remark() + .use(remarkGitHubAlerts) + .use(remarkHtml) + +const markdown = ` +> [!NOTE] +> This is a note alert with some important information. + +> [!WARNING] Custom title +> This is a warning with a custom title. +` + +const result = await processor.process(markdown) +console.log(result.toString()) +``` + + +### With React Server Components and custom icons + +Using [`react-markdown`][react-markdown] and [`common-tags`][common-tags]'s `html` helper, example code in Next.js application: + +```tsx +import { MarkdownAsync } from 'react-markdown' +import { html } from 'common-tags' +import { remarkGitHubAlerts } from 'remark-github-markdown-alerts' + +// โš ๏ธ Use only in server environment (RSC) +const customIcon = html` + +` + +async function ServerMarkdown() { + const markdown = ` + > [!NOTE] + > Server-rendered alert with custom SVG icon + ` + + return ( + + {markdown} + + ) +} +``` + +### With [unified][unified] pipeline -## Setup +```ts +import { unified } from 'unified' +import remarkParse from 'remark-parse' +import remarkRehype from 'remark-rehype' +import rehypeStringify from 'rehype-stringify' +import { remarkGitHubAlerts } from 'remark-github-markdown-alerts' -### 1. Quick Start +const processor = unified() + .use(remarkParse) + .use(remarkGitHubAlerts) + .use(remarkRehype, { allowDangerousHtml: true }) + .use(rehypeStringify, { allowDangerousHtml: true }) -Run the initialization script to automatically configure your repository: +const result = await processor.process('> [!IMPORTANT]\\n> Critical information here!') +``` + +## Alert Types + +```markdown +> [!NOTE] +> Information that users should know. + +> [!TIP] +> Helpful advice for better results. + +> [!IMPORTANT] +> Key information for success. + +> [!WARNING] +> Urgent information to avoid problems. + +> [!CAUTION] +> Risks or negative outcomes. +``` + +### Custom Titles + +```markdown +> [!NOTE] Custom title +> Content with custom title. -```bash -# One-command setup -./init.sh +> [!WARNING] Breaking Changes +> This version has breaking changes. ``` -This script will: -- ๐Ÿ”’ **Create repository rulesets** for branch protection (linear history, PR reviews) -- ๐Ÿšซ **Disable unnecessary features** (wikis, projects, squash/merge commits) -- โš™๏ธ **Configure merge settings** (rebase-only workflow) -- โœ… **Verify GitHub Actions** are enabled -- ๐Ÿ”‘ **Check required secrets** and provide setup instructions +## Configuration -### 2. Required Secrets +The plugin accepts an options object with two main sections: -The script will guide you to set up these secrets if missing: +### ๐ŸŽ›๏ธ Global Configuration (`defaultConfig`) -**NPM_TOKEN** (for publishing): -```bash -# Generate NPM token with OTP for enhanced security -pnpm token create --otp= --registry=https://registry.npmjs.org/ +Applied to all alert types unless overridden: -# Set the token as repository secret -gh secret set NPM_TOKEN --body "your-npm-token-here" +```ts +import { remarkGitHubAlerts } from 'remark-github-markdown-alerts' + +const options = { + defaultConfig: { + // ๐ŸŽจ CSS Class Names + classNames: { + container: 'alert', // Main wrapper class + icon: 'alert-icon', // Icon container class + title: 'alert-title', // Title/header class + content: 'alert-content' // Content body class + }, + + // ๐Ÿ—๏ธ HTML Elements + tags: { + container: 'section', // Wrapper element + icon: 'i', // Icon element + title: 'h3', // Title element + content: 'div' // Content element + }, + + // ๐Ÿ”ง Custom Icon + iconElementHtml: '๐Ÿ””' // Default icon HTML + } +} ``` -**ACTIONS_BRANCH_PROTECTION_BYPASS** (for automated releases): -```bash -# Create Personal Access Token with 'repo' permissions -# Visit: https://github.com/settings/personal-access-tokens/new +### ๐ŸŽฏ Alert-Specific Configuration (`alerts`) + +Override settings for individual alert types: + +```ts +const options = { + // ... defaultConfig above + alerts: { + note: { + iconElementHtml: '...', + classNames: { + container: 'note-container', + icon: 'note-icon' + } + }, + warning: { + tags: { + container: 'aside', + title: 'h4' + } + } + } +} -# Set the PAT as repository secret -gh secret set ACTIONS_BRANCH_PROTECTION_BYPASS --body "your-pat-token-here" +const processor = remark().use(remarkGitHubAlerts, options) ``` -#### Why Linear History? +### ๐Ÿ“‹ Configuration Reference + +#### Default Configuration Options + +| Property | Type | Default | Description | +|----------|------|---------|-------------| +| **CSS Classes** | | | | +| `classNames.container` | `string` | `'markdown-alert'` | Main alert container class | +| `classNames.icon` | `string` | `'markdown-alert-icon'` | Icon element class | +| `classNames.title` | `string` | `'markdown-alert-title'` | Title/header class | +| `classNames.content` | `string` | `'markdown-alert-content'` | Content body class | +| **HTML Elements** | | | | +| `tags.container` | `HtmlElement` | `'div'` | Alert wrapper element | +| `tags.icon` | `HtmlElement` | `'span'` | Icon container element | +| `tags.title` | `HtmlElement` | `'div'` | Title/header element | +| `tags.content` | `HtmlElement` | `'div'` | Content body element | +| **Customization** | | | | +| `iconElementHtml` | `string` | `''` | Custom icon HTML/emoji | -Linear history provides several benefits for library releases: +#### Alert-Specific Overrides -- **Clean commit history** - Easy to track changes and debug issues -- **Simplified releases** - Semantic release works better with linear commits -- **Clear changelog** - Each commit represents a complete change -- **Better debugging** - `git bisect` works more effectively -- **Consistent workflow** - Forces proper PR review process +| Property | Type | Description | +|----------|------|-------------| +| `alerts.note` | `PartialDeep` | Override config for `[!NOTE]` alerts | +| `alerts.tip` | `PartialDeep` | Override config for `[!TIP]` alerts | +| `alerts.important` | `PartialDeep` | Override config for `[!IMPORTANT]` alerts | +| `alerts.warning` | `PartialDeep` | Override config for `[!WARNING]` alerts | +| `alerts.caution` | `PartialDeep` | Override config for `[!CAUTION]` alerts | -## Scripts -| Command | Description | -|---------|-------------| -| `pnpm dev` | Watch mode build | -| `pnpm build` | Production build | -| `pnpm build:check` | Build + package validation | -| `pnpm test` | Run tests | -| `pnpm test:watch` | Watch mode testing | -| `pnpm test:coverage` | Generate coverage report | -| `pnpm lint` | Check linting and formatting | -| `pnpm lint:fix` | Fix linting and formatting issues | -| `pnpm typecheck` | TypeScript type checking | -| `pnpm release` | Create release (CI only) | -## Contributing +> [!TIP] +> Alert-specific configurations merge with the default config, so you only need to specify the properties you want to override. + +## Example output + +```html +
+
+ + Note +
+
+

Your content here

+
+
+``` + +# License + +The MIT License -See [CONTRIBUTING.md](CONTRIBUTING.md) for development workflow, commit conventions, and contribution guidelines. \ No newline at end of file +[unified]: https://github.com/unifiedjs/unified +[react-markdown]: https://github.com/remarkjs/react-markdown +[common-tags]: https://www.npmjs.com/package/common-tags +[alternative]: https://github.com/jaywcjlove/remark-github-blockquote-alert diff --git a/package.json b/package.json index 6e5a813..0c09306 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { - "name": "typescript-library-template", + "name": "remark-github-markdown-alerts", "version": "0.0.1", - "description": "A neg4n's template for creating TypeScript libraries", + "description": "An unifiedjs (remark) plugin to convert GitHub Markdown alerts syntax into actual UI", "type": "module", "main": "./dist/index.cjs", "module": "./dist/index.esm.js", @@ -40,9 +40,13 @@ "*.{js,ts,json}": "biome check --write --no-errors-on-unmatched" }, "keywords": [ - "typescript", - "library", - "template" + "remark", + "rehype", + "markdown", + "unified", + "unifiedjs", + "remarkrehype", + "gfm" ], "author": { "email": "neg4n@icloud.com", @@ -54,6 +58,7 @@ "devDependencies": { "@arethetypeswrong/cli": "^0.18.2", "@biomejs/biome": "2.0.4", + "@hono/node-server": "^1.14.4", "@rollup/plugin-commonjs": "^28.0.6", "@rollup/plugin-node-resolve": "^16.0.1", "@rollup/plugin-terser": "^0.4.4", @@ -64,14 +69,31 @@ "@semantic-release/github": "11.0.3", "@semantic-release/npm": "12.0.1", "@semantic-release/release-notes-generator": "14.0.3", + "@types/hast": "^3.0.4", + "@types/mdast": "^4.0.4", + "@types/unist": "^3.0.3", + "@vitest/coverage-v8": "^3.2.4", + "hono": "^4.8.2", "husky": "9.1.7", "lint-staged": "16.1.2", + "rehype-stringify": "^10.0.1", + "remark": "^15.0.1", + "remark-rehype": "^11.1.2", "rollup": "^4.44.0", "rollup-plugin-dts": "^6.2.1", "semantic-release": "24.2.5", + "trough": "^2.2.0", "tslib": "^2.8.1", "type-fest": "^4.41.0", "typescript": "^5.8.3", + "unified": "^11.0.5", + "unist-util-is": "^6.0.0", + "unist-util-visit-parents": "^6.0.1", + "vfile": "^6.0.3", + "vfile-message": "^4.0.2", "vitest": "3.2.4" + }, + "dependencies": { + "unist-util-visit": "^5.0.0" } } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index fd46660..4dda84c 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -7,6 +7,25 @@ settings: importers: .: + dependencies: + hast-util-to-html: + specifier: ^9.0.5 + version: 9.0.5 + mdast-util-from-markdown: + specifier: ^2.0.2 + version: 2.0.2 + mdast-util-to-hast: + specifier: ^13.2.0 + version: 13.2.0 + remark: + specifier: ^15.0.1 + version: 15.0.1 + unified: + specifier: ^11.0.5 + version: 11.0.5 + unist-util-visit: + specifier: ^5.0.0 + version: 5.0.0 devDependencies: '@arethetypeswrong/cli': specifier: ^0.18.2 @@ -44,12 +63,30 @@ importers: '@semantic-release/release-notes-generator': specifier: 14.0.3 version: 14.0.3(semantic-release@24.2.5(typescript@5.8.3)) + '@types/hast': + specifier: ^3.0.4 + version: 3.0.4 + '@types/mdast': + specifier: ^4.0.4 + version: 4.0.4 + '@types/unist': + specifier: ^3.0.3 + version: 3.0.3 + '@vitest/coverage-v8': + specifier: ^3.2.4 + version: 3.2.4(vitest@3.2.4(@types/debug@4.1.12)(terser@5.43.1)(yaml@2.8.0)) husky: specifier: 9.1.7 version: 9.1.7 lint-staged: specifier: 16.1.2 version: 16.1.2 + rehype-stringify: + specifier: ^10.0.1 + version: 10.0.1 + remark-rehype: + specifier: ^11.1.2 + version: 11.1.2 rollup: specifier: ^4.44.0 version: 4.44.0 @@ -59,6 +96,9 @@ importers: semantic-release: specifier: 24.2.5 version: 24.2.5(typescript@5.8.3) + trough: + specifier: ^2.2.0 + version: 2.2.0 tslib: specifier: ^2.8.1 version: 2.8.1 @@ -68,12 +108,28 @@ importers: typescript: specifier: ^5.8.3 version: 5.8.3 + unist-util-is: + specifier: ^6.0.0 + version: 6.0.0 + unist-util-visit-parents: + specifier: ^6.0.1 + version: 6.0.1 + vfile: + specifier: ^6.0.3 + version: 6.0.3 + vfile-message: + specifier: ^4.0.2 + version: 4.0.2 vitest: specifier: 3.2.4 - version: 3.2.4(terser@5.43.1)(yaml@2.8.0) + version: 3.2.4(@types/debug@4.1.12)(terser@5.43.1)(yaml@2.8.0) packages: + '@ampproject/remapping@2.3.0': + resolution: {integrity: sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==} + engines: {node: '>=6.0.0'} + '@andrewbranch/untar.js@1.0.3': resolution: {integrity: sha512-Jh15/qVmrLGhkKJBdXlK1+9tY4lZruYjsgkDFj08ZmDiWVBLJcqkok7Z0/R0In+i1rScBpJlSvrTS2Lm41Pbnw==} @@ -90,10 +146,27 @@ packages: resolution: {integrity: sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==} engines: {node: '>=6.9.0'} + '@babel/helper-string-parser@7.27.1': + resolution: {integrity: sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==} + engines: {node: '>=6.9.0'} + '@babel/helper-validator-identifier@7.27.1': resolution: {integrity: sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow==} engines: {node: '>=6.9.0'} + '@babel/parser@7.27.5': + resolution: {integrity: sha512-OsQd175SxWkGlzbny8J3K8TnnDD0N3lrIUtB92xwyRpzaenGZhxDvxN/JgU00U3CDZNj9tPuDJ5H0WS4Nt3vKg==} + engines: {node: '>=6.0.0'} + hasBin: true + + '@babel/types@7.27.6': + resolution: {integrity: sha512-ETyHEk2VHHvl9b9jZP5IHPavHYk57EhanlRRuae9XCpb/j5bDCbPPMOBfCWhnl/7EDJz0jEMCi/RhccCE8r1+Q==} + engines: {node: '>=6.9.0'} + + '@bcoe/v8-coverage@1.0.2': + resolution: {integrity: sha512-6zABk/ECA/QYSCQ1NGiVwwbQerUCZ+TQbp64Q3AgmfNvurHH0j8TtXa1qbShXA6qqkpAj4V5W8pP6mLe1mcMqA==} + engines: {node: '>=18'} + '@biomejs/biome@2.0.4': resolution: {integrity: sha512-DNA++xe+E7UugTvI/HhzSFl6OwrVgU8SIV0Mb2fPtWPk2/oTr4eOSA5xy1JECrvgJeYxurmUBOS49qxv/OUkrQ==} engines: {node: '>=14.21.3'} @@ -304,6 +377,14 @@ packages: cpu: [x64] os: [win32] + '@isaacs/cliui@8.0.2': + resolution: {integrity: sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==} + engines: {node: '>=12'} + + '@istanbuljs/schema@0.1.3': + resolution: {integrity: sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==} + engines: {node: '>=8'} + '@jridgewell/gen-mapping@0.3.8': resolution: {integrity: sha512-imAbBGkb+ebQyxKgzv5Hu2nmROxoDOXHh80evxdoXNOrvAnVx7zimzc1Oo5h9RlfV4vPXaE2iM5pOFbvOCClWA==} engines: {node: '>=6.0.0'} @@ -388,6 +469,10 @@ packages: '@octokit/types@14.1.0': resolution: {integrity: sha512-1y6DgTy8Jomcpu33N+p5w58l6xyt55Ar2I91RPiIA0xCJBXyUAhXCcmZaDWSANiha7R9a6qJJ2CRomGPZ6f46g==} + '@pkgjs/parseargs@0.11.0': + resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==} + engines: {node: '>=14'} + '@pnpm/config.env-replace@1.1.0': resolution: {integrity: sha512-htyl8TWnKL7K/ESFa1oW2UB5lVDxuF5DpM7tBi6Hu2LNL3mWkIzNLG6N4zoCUP1lCKNxWy/3iu8mS8MvToGd6w==} engines: {node: '>=12.22.0'} @@ -611,18 +696,45 @@ packages: '@types/chai@5.2.2': resolution: {integrity: sha512-8kB30R7Hwqf40JPiKhVzodJs2Qc1ZJ5zuT3uzw5Hq/dhNCl3G3l83jfpdI1e20BP348+fV7VIL/+FxaXkqBmWg==} + '@types/debug@4.1.12': + resolution: {integrity: sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ==} + '@types/deep-eql@4.0.2': resolution: {integrity: sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==} '@types/estree@1.0.8': resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==} + '@types/hast@3.0.4': + resolution: {integrity: sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==} + + '@types/mdast@4.0.4': + resolution: {integrity: sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA==} + + '@types/ms@2.1.0': + resolution: {integrity: sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==} + '@types/normalize-package-data@2.4.4': resolution: {integrity: sha512-37i+OaWTh9qeK4LSHPsyRC7NahnGotNuZvjLSgcPzblpHB3rrCJxAOgI5gCdKm7coonsaX1Of0ILiTcnZjbfxA==} '@types/resolve@1.20.2': resolution: {integrity: sha512-60BCwRFOZCQhDncwQdxxeOEEkbc5dIMccYLwbxsS4TUNeVECQ/pBJ0j09mrHOl/JJvpRPGwO9SvE4nR2Nb/a4Q==} + '@types/unist@3.0.3': + resolution: {integrity: sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==} + + '@ungap/structured-clone@1.3.0': + resolution: {integrity: sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==} + + '@vitest/coverage-v8@3.2.4': + resolution: {integrity: sha512-EyF9SXU6kS5Ku/U82E259WSnvg6c8KTjppUncuNdm5QHpe17mwREHnjDzozC8x9MZ0xfBUFSaLkRv4TMA75ALQ==} + peerDependencies: + '@vitest/browser': 3.2.4 + vitest: 3.2.4 + peerDependenciesMeta: + '@vitest/browser': + optional: true + '@vitest/expect@3.2.4': resolution: {integrity: sha512-Io0yyORnB6sikFlt8QW5K7slY4OjqNX9jmJQ02QDda8lyM6B5oNgVWoSoKPac8/kgnCUzuHQKrSLtu/uOqqrig==} @@ -709,12 +821,24 @@ packages: resolution: {integrity: sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==} engines: {node: '>=12'} + ast-v8-to-istanbul@0.3.3: + resolution: {integrity: sha512-MuXMrSLVVoA6sYN/6Hke18vMzrT4TZNbZIj/hvh0fnYFpO+/kFXcLIaiPwXXWaQUPg4yJD8fj+lfJ7/1EBconw==} + + bail@2.0.2: + resolution: {integrity: sha512-0xO6mYd7JB2YesxDKplafRpsiOzPt9V02ddPCLbY1xYGPOX24NTyN50qnUxgCPcSoYMhKpAuBTjQoRZCAkUDRw==} + + balanced-match@1.0.2: + resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} + before-after-hook@4.0.0: resolution: {integrity: sha512-q6tR3RPqIB1pMiTRMFcZwuG5T8vwp+vUvEG0vuI6B+Rikh5BfPp2fQ82c925FOs+b0lcFQ8CFrL+KbilfZFhOQ==} bottleneck@2.19.5: resolution: {integrity: sha512-VHiNCbI1lKdl44tGrhNfU3lup0Tj/ZBMJB5/2ZbNXRCPuRCO7ed2mgcK4r17y+KB2EfuYuRaVlwNbAeaWGSpbw==} + brace-expansion@2.0.2: + resolution: {integrity: sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==} + braces@3.0.3: resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==} engines: {node: '>=8'} @@ -730,6 +854,9 @@ packages: resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==} engines: {node: '>=6'} + ccount@2.0.1: + resolution: {integrity: sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==} + chai@5.2.0: resolution: {integrity: sha512-mCuXncKXk5iCLhfhwTc0izo0gtEmpz5CtG2y8GiOINBlMVS6v8TMRc5TaLWKS6692m9+dVVfzgeVxR5UxWHTYw==} engines: {node: '>=12'} @@ -750,6 +877,15 @@ packages: resolution: {integrity: sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw==} engines: {node: '>=10'} + character-entities-html4@2.1.0: + resolution: {integrity: sha512-1v7fgQRj6hnSwFpq1Eu0ynr/CDEw0rXo2B61qXrLNdHZmPKgb7fqS1a2JwF0rISo9q77jDI8VMEHoApn8qDoZA==} + + character-entities-legacy@3.0.0: + resolution: {integrity: sha512-RpPp0asT/6ufRm//AJVwpViZbGM/MkjQFxJccQRHmISF/22NBtsHqAWmL+/pmkPWoIUJdWyeVleTl1wydHATVQ==} + + character-entities@2.0.2: + resolution: {integrity: sha512-shx7oQ0Awen/BRIdkjkvz54PnEEI/EjwXDSIZp86/KKdbafHh1Df/RYGBhn4hbe2+uKC9FnT5UCEdyPz3ai9hQ==} + check-error@2.1.1: resolution: {integrity: sha512-OAlb+T7V4Op9OwdkjmguYRqncdlx5JiofwOAUkmTF+jNdHwzTaTs4sRAGpzLF3oOz5xAyDGrPgeIDFQmDOTiJw==} engines: {node: '>= 16'} @@ -805,6 +941,9 @@ packages: colorette@2.0.20: resolution: {integrity: sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==} + comma-separated-tokens@2.0.3: + resolution: {integrity: sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg==} + commander@10.0.1: resolution: {integrity: sha512-y4Mg2tXshplEbSGzx7amzPwKKOCGuoSRP/CjEdwwk0FOGlUbq6lKuoyDZTNZkmxHdJtp54hdfY/JUrdL7Xfdug==} engines: {node: '>=14'} @@ -876,6 +1015,9 @@ packages: supports-color: optional: true + decode-named-character-reference@1.2.0: + resolution: {integrity: sha512-c6fcElNV6ShtZXmsgNgFFV5tVX2PaV4g+MOAkb8eXHvn6sryJBrZa9r0zV6+dtTyoCKxtDy5tyQ5ZwQuidtd+Q==} + deep-eql@5.0.2: resolution: {integrity: sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==} engines: {node: '>=6'} @@ -888,6 +1030,13 @@ packages: resolution: {integrity: sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==} engines: {node: '>=0.10.0'} + dequal@2.0.3: + resolution: {integrity: sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==} + engines: {node: '>=6'} + + devlop@1.1.0: + resolution: {integrity: sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA==} + dir-glob@3.0.1: resolution: {integrity: sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==} engines: {node: '>=8'} @@ -899,12 +1048,18 @@ packages: duplexer2@0.1.4: resolution: {integrity: sha512-asLFVfWWtJ90ZyOUHMqk7/S2w2guQKxUI2itj3d92ADHhxUSbCMGi1f1cBcJ7xM1To+pE/Khbwo1yuNbMEPKeA==} + eastasianwidth@0.2.0: + resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==} + emoji-regex@10.4.0: resolution: {integrity: sha512-EC+0oUMY1Rqm4O6LLrgjtYDvcVYTy7chDnM4Q7030tP4Kwj3u/pR6gP9ygnp2CJMK5Gq+9Q2oqmrFJAz01DXjw==} emoji-regex@8.0.0: resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} + emoji-regex@9.2.2: + resolution: {integrity: sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==} + emojilib@2.4.0: resolution: {integrity: sha512-5U0rVMU5Y2n2+ykNLQqMoqklN9ICBT/KsvC1Gz6vqHbz2AXXGkG+Pm5rMWk/8Vjrr/mY9985Hi8DYzn1F09Nyw==} @@ -968,6 +1123,9 @@ packages: resolution: {integrity: sha512-/kP8CAwxzLVEeFrMm4kMmy4CCDlpipyA7MYLVrdJIkV0fYF0UaigQHRsxHiuY/GEea+bh4KSv3TIlgr+2UL6bw==} engines: {node: '>=12.0.0'} + extend@3.0.2: + resolution: {integrity: sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==} + fast-content-type-parse@3.0.0: resolution: {integrity: sha512-ZvLdcY8P+N8mGQJahJV5G4U88CSvT1rP8ApL6uETe88MBXrBHAkZlSEySdUlyztF7ccb+Znos3TFqaepHxdhBg==} @@ -1013,6 +1171,10 @@ packages: resolution: {integrity: sha512-2kCCtc+JvcZ86IGAz3Z2Y0A1baIz9fL31pH/0S1IqZr9Iwnjq8izfPtrCyQKO6TLMPELLsQMre7VDqeIKCsHkA==} engines: {node: '>=18'} + foreground-child@3.3.1: + resolution: {integrity: sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==} + engines: {node: '>=14'} + from2@2.3.0: resolution: {integrity: sha512-OMcX/4IC/uqEPVgGeyfN22LJk6AZrMkRZHxcHBMBvHScDGgwTm2GT2Wkgtocyd3JfZffjj2kYUDXXII0Fk9W0g==} @@ -1063,6 +1225,10 @@ packages: resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==} engines: {node: '>= 6'} + glob@10.4.5: + resolution: {integrity: sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==} + hasBin: true + globby@14.1.0: resolution: {integrity: sha512-0Ia46fDOaT7k4og1PDW4YbodWWr3scS2vAr2lTbsplOt2WkKp0vQbkI9wKis/T5LV/dqPjO3bpS/z6GTJB82LA==} engines: {node: '>=18'} @@ -1090,6 +1256,12 @@ packages: resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==} engines: {node: '>= 0.4'} + hast-util-to-html@9.0.5: + resolution: {integrity: sha512-OguPdidb+fbHQSU4Q4ZiLKnzWo8Wwsf5bZfbvu7//a9oTYoqD/fWpe96NuHkoS9h0ccGOTe0C4NGXdtS0iObOw==} + + hast-util-whitespace@3.0.0: + resolution: {integrity: sha512-88JUN06ipLwsnv+dVn+OIYOvAuvBMy/Qoi6O7mQHxdPXpjy+Cd6xRkWwux7DKO+4sYILtLBRIKgsdpS2gQc7qw==} + highlight.js@10.7.3: resolution: {integrity: sha512-tzcUFauisWKNHaRkN4Wjl/ZA07gENAjFl3J/c480dprkGTg5EQstgaNFqBfUqCq54kZRIEcreTsAgF/m2quD7A==} @@ -1105,6 +1277,12 @@ packages: resolution: {integrity: sha512-Rw/B2DNQaPBICNXEm8balFz9a6WpZrkCGpcWFpy7nCj+NyhSdqXipmfvtmWt9xGfp0wZnBxB+iVpLmQMYt47Tw==} engines: {node: ^18.17.0 || >=20.5.0} + html-escaper@2.0.2: + resolution: {integrity: sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==} + + html-void-elements@3.0.0: + resolution: {integrity: sha512-bEqo66MRXsUGxWHV5IP0PUiAWwoEjba4VCzg0LjFJBpchPaTfyfCKTG6bc5F8ucKec3q5y6qOdGyYTSBEvhCrg==} + http-proxy-agent@7.0.2: resolution: {integrity: sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==} engines: {node: '>= 14'} @@ -1238,6 +1416,25 @@ packages: resolution: {integrity: sha512-3YZcUUR2Wt1WsapF+S/WiA2WmlW0cWAoPccMqne7AxEBhCdFeTPjfv/Axb8V2gyCgY3nRw+ksZ3xSUX+R47iAg==} engines: {node: ^18.17 || >=20.6.1} + istanbul-lib-coverage@3.2.2: + resolution: {integrity: sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==} + engines: {node: '>=8'} + + istanbul-lib-report@3.0.1: + resolution: {integrity: sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==} + engines: {node: '>=10'} + + istanbul-lib-source-maps@5.0.6: + resolution: {integrity: sha512-yg2d+Em4KizZC5niWhQaIomgf5WlL4vOOjZ5xGCmF8SnPE/mDWWXgvRExdcpCgh9lLRRa1/fSYp2ymmbJ1pI+A==} + engines: {node: '>=10'} + + istanbul-reports@3.1.7: + resolution: {integrity: sha512-BewmUXImeuRk2YY0PVbxgKAysvhRPUQE0h5QRM++nVWyubKGV0l8qQ5op8+B2DOmwSe63Jivj0BjkPQVf8fP5g==} + engines: {node: '>=8'} + + jackspeak@3.4.3: + resolution: {integrity: sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==} + java-properties@1.0.2: resolution: {integrity: sha512-qjdpeo2yKlYTH7nFdK0vbZWuTCesk4o63v5iVOlhMQPfuIZQfW/HI35SjfhA+4qpg36rnFSvUK5b1m+ckIblQQ==} engines: {node: '>= 0.6.0'} @@ -1310,6 +1507,9 @@ packages: resolution: {integrity: sha512-9ie8ItPR6tjY5uYJh8K/Zrv/RMZ5VOlOWvtZdEHYSTFKZfIBPQa9tOAEeAWhd+AnIneLJ22w5fjOYtoutpWq5w==} engines: {node: '>=18'} + longest-streak@3.1.0: + resolution: {integrity: sha512-9Ri+o0JYgehTaVBBDoMqIl8GXtbWg711O3srftcHhZ0dqnETqLaoIK0x17fUw9rFSlK/0NlsKe0Ahhyl5pXE2g==} + loupe@3.1.4: resolution: {integrity: sha512-wJzkKwJrheKtknCOKNEtDK4iqg/MxmZheEMtSTYvnzRdEYaZzmgH976nenp8WdJRdx5Vc1X/9MO0Oszl6ezeXg==} @@ -1323,6 +1523,13 @@ packages: magic-string@0.30.17: resolution: {integrity: sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA==} + magicast@0.3.5: + resolution: {integrity: sha512-L0WhttDl+2BOsybvEOLK7fW3UA0OQ0IQ2d6Zl2x/a6vVRs3bAY0ECOSHHeL5jD+SbOpOCUEi0y1DgHEn9Qn1AQ==} + + make-dir@4.0.0: + resolution: {integrity: sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==} + engines: {node: '>=10'} + marked-terminal@7.3.0: resolution: {integrity: sha512-t4rBvPsHc57uE/2nJOLmMbZCQ4tgAccAED3ngXQqW6g+TxA488JzJ+FK3lQkzBQOI1mRV/r/Kq+1ZlJ4D0owQw==} engines: {node: '>=16.0.0'} @@ -1339,6 +1546,21 @@ packages: engines: {node: '>= 16'} hasBin: true + mdast-util-from-markdown@2.0.2: + resolution: {integrity: sha512-uZhTV/8NBuw0WHkPTrCqDOl0zVe1BIng5ZtHoDk49ME1qqcjYmmLmOf0gELgcRMxN4w2iuIeVso5/6QymSrgmA==} + + mdast-util-phrasing@4.1.0: + resolution: {integrity: sha512-TqICwyvJJpBwvGAMZjj4J2n0X8QWp21b9l0o7eXyVJ25YNWYbJDVIyD1bZXE6WtV6RmKJVYmQAKWa0zWOABz2w==} + + mdast-util-to-hast@13.2.0: + resolution: {integrity: sha512-QGYKEuUsYT9ykKBCMOEDLsU5JRObWQusAolFMeko/tYPufNkRffBAQjIE+99jbA87xv6FgmjLtwjh9wBWajwAA==} + + mdast-util-to-markdown@2.1.2: + resolution: {integrity: sha512-xj68wMTvGXVOKonmog6LwyJKrYXZPvlwabaryTjLh9LuvovB/KAH+kvi8Gjj+7rJjsFi23nkUxRQv1KqSroMqA==} + + mdast-util-to-string@4.0.0: + resolution: {integrity: sha512-0H44vDimn51F0YwvxSJSm0eCDOJTRlmN0R1yBh4HLj9wiV1Dn0QoXGbvFAWj2hSItVTlCmBF1hqKlIyUBVFLPg==} + meow@13.2.0: resolution: {integrity: sha512-pxQJQzB6djGPXh08dacEloMFopsOqGVRKFPYvPOt9XDZ1HasbgDZA74CJGreSU4G3Ak7EFJGoiH2auq+yXISgA==} engines: {node: '>=18'} @@ -1350,6 +1572,69 @@ packages: resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==} engines: {node: '>= 8'} + micromark-core-commonmark@2.0.3: + resolution: {integrity: sha512-RDBrHEMSxVFLg6xvnXmb1Ayr2WzLAWjeSATAoxwKYJV94TeNavgoIdA0a9ytzDSVzBy2YKFK+emCPOEibLeCrg==} + + micromark-factory-destination@2.0.1: + resolution: {integrity: sha512-Xe6rDdJlkmbFRExpTOmRj9N3MaWmbAgdpSrBQvCFqhezUn4AHqJHbaEnfbVYYiexVSs//tqOdY/DxhjdCiJnIA==} + + micromark-factory-label@2.0.1: + resolution: {integrity: sha512-VFMekyQExqIW7xIChcXn4ok29YE3rnuyveW3wZQWWqF4Nv9Wk5rgJ99KzPvHjkmPXF93FXIbBp6YdW3t71/7Vg==} + + micromark-factory-space@2.0.1: + resolution: {integrity: sha512-zRkxjtBxxLd2Sc0d+fbnEunsTj46SWXgXciZmHq0kDYGnck/ZSGj9/wULTV95uoeYiK5hRXP2mJ98Uo4cq/LQg==} + + micromark-factory-title@2.0.1: + resolution: {integrity: sha512-5bZ+3CjhAd9eChYTHsjy6TGxpOFSKgKKJPJxr293jTbfry2KDoWkhBb6TcPVB4NmzaPhMs1Frm9AZH7OD4Cjzw==} + + micromark-factory-whitespace@2.0.1: + resolution: {integrity: sha512-Ob0nuZ3PKt/n0hORHyvoD9uZhr+Za8sFoP+OnMcnWK5lngSzALgQYKMr9RJVOWLqQYuyn6ulqGWSXdwf6F80lQ==} + + micromark-util-character@2.1.1: + resolution: {integrity: sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==} + + micromark-util-chunked@2.0.1: + resolution: {integrity: sha512-QUNFEOPELfmvv+4xiNg2sRYeS/P84pTW0TCgP5zc9FpXetHY0ab7SxKyAQCNCc1eK0459uoLI1y5oO5Vc1dbhA==} + + micromark-util-classify-character@2.0.1: + resolution: {integrity: sha512-K0kHzM6afW/MbeWYWLjoHQv1sgg2Q9EccHEDzSkxiP/EaagNzCm7T/WMKZ3rjMbvIpvBiZgwR3dKMygtA4mG1Q==} + + micromark-util-combine-extensions@2.0.1: + resolution: {integrity: sha512-OnAnH8Ujmy59JcyZw8JSbK9cGpdVY44NKgSM7E9Eh7DiLS2E9RNQf0dONaGDzEG9yjEl5hcqeIsj4hfRkLH/Bg==} + + micromark-util-decode-numeric-character-reference@2.0.2: + resolution: {integrity: sha512-ccUbYk6CwVdkmCQMyr64dXz42EfHGkPQlBj5p7YVGzq8I7CtjXZJrubAYezf7Rp+bjPseiROqe7G6foFd+lEuw==} + + micromark-util-decode-string@2.0.1: + resolution: {integrity: sha512-nDV/77Fj6eH1ynwscYTOsbK7rR//Uj0bZXBwJZRfaLEJ1iGBR6kIfNmlNqaqJf649EP0F3NWNdeJi03elllNUQ==} + + micromark-util-encode@2.0.1: + resolution: {integrity: sha512-c3cVx2y4KqUnwopcO9b/SCdo2O67LwJJ/UyqGfbigahfegL9myoEFoDYZgkT7f36T0bLrM9hZTAaAyH+PCAXjw==} + + micromark-util-html-tag-name@2.0.1: + resolution: {integrity: sha512-2cNEiYDhCWKI+Gs9T0Tiysk136SnR13hhO8yW6BGNyhOC4qYFnwF1nKfD3HFAIXA5c45RrIG1ub11GiXeYd1xA==} + + micromark-util-normalize-identifier@2.0.1: + resolution: {integrity: sha512-sxPqmo70LyARJs0w2UclACPUUEqltCkJ6PhKdMIDuJ3gSf/Q+/GIe3WKl0Ijb/GyH9lOpUkRAO2wp0GVkLvS9Q==} + + micromark-util-resolve-all@2.0.1: + resolution: {integrity: sha512-VdQyxFWFT2/FGJgwQnJYbe1jjQoNTS4RjglmSjTUlpUMa95Htx9NHeYW4rGDJzbjvCsl9eLjMQwGeElsqmzcHg==} + + micromark-util-sanitize-uri@2.0.1: + resolution: {integrity: sha512-9N9IomZ/YuGGZZmQec1MbgxtlgougxTodVwDzzEouPKo3qFWvymFHWcnDi2vzV1ff6kas9ucW+o3yzJK9YB1AQ==} + + micromark-util-subtokenize@2.1.0: + resolution: {integrity: sha512-XQLu552iSctvnEcgXw6+Sx75GflAPNED1qx7eBJ+wydBb2KCbRZe+NwvIEEMM83uml1+2WSXpBAcp9IUCgCYWA==} + + micromark-util-symbol@2.0.1: + resolution: {integrity: sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==} + + micromark-util-types@2.0.2: + resolution: {integrity: sha512-Yw0ECSpJoViF1qTU4DC6NwtC4aWGt1EkzaQB8KPPyCRR8z9TWeV0HbEFGTO+ZY1wB22zmxnJqhPyTpOVCpeHTA==} + + micromark@4.0.2: + resolution: {integrity: sha512-zpe98Q6kvavpCr1NPVSCMebCKfD7CA2NqZ+rykeNhONIJBpc1tFKt9hucLGwha3jNTNI8lHpctWJWoimVF4PfA==} + micromatch@4.0.8: resolution: {integrity: sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==} engines: {node: '>=8.6'} @@ -1371,9 +1656,17 @@ packages: resolution: {integrity: sha512-VP79XUPxV2CigYP3jWwAUFSku2aKqBH7uTAapFWCBqutsbmDo96KY5o8uh6U+/YSIn5OxJnXp73beVkpqMIGhA==} engines: {node: '>=18'} + minimatch@9.0.5: + resolution: {integrity: sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==} + engines: {node: '>=16 || 14 >=14.17'} + minimist@1.2.8: resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==} + minipass@7.1.2: + resolution: {integrity: sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==} + engines: {node: '>=16 || 14 >=14.17'} + ms@2.1.3: resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} @@ -1545,6 +1838,9 @@ packages: resolution: {integrity: sha512-U1etNYuMJoIz3ZXSrrySFjsXQTWOx2/jdi86L+2pRvph/qMKL6sbcCYdH23fqsbm8TH2Gn0OybpT4eSFlCVHww==} engines: {node: '>=4'} + package-json-from-dist@1.0.1: + resolution: {integrity: sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==} + parent-module@1.0.1: resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==} engines: {node: '>=6'} @@ -1589,6 +1885,10 @@ packages: path-parse@1.0.7: resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==} + path-scurry@1.11.1: + resolution: {integrity: sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==} + engines: {node: '>=16 || 14 >=14.18'} + path-type@4.0.0: resolution: {integrity: sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==} engines: {node: '>=8'} @@ -1639,6 +1939,9 @@ packages: process-nextick-args@2.0.1: resolution: {integrity: sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==} + property-information@7.1.0: + resolution: {integrity: sha512-TwEZ+X+yCJmYfL7TPUOcvBZ4QfoT5YenQiJuX//0th53DE6w0xxLEtfK3iyryQFddXuvkIk51EEgrJQ0WJkOmQ==} + proto-list@1.2.4: resolution: {integrity: sha512-vtK/94akxsTMhe0/cbfpR+syPuszcuwhqVjJq26CuNDgFGj682oRBXOP5MJpv2r7JtE8MsiepGIqvvOTBwn2vA==} @@ -1667,6 +1970,21 @@ packages: resolution: {integrity: sha512-GdekYuwLXLxMuFTwAPg5UKGLW/UXzQrZvH/Zj791BQif5T05T0RsaLfHc9q3ZOKi7n+BoprPD9mJ0O0k4xzUlw==} engines: {node: '>=14'} + rehype-stringify@10.0.1: + resolution: {integrity: sha512-k9ecfXHmIPuFVI61B9DeLPN0qFHfawM6RsuX48hoqlaKSF61RskNjSm1lI8PhBEM0MRdLxVVm4WmTqJQccH9mA==} + + remark-parse@11.0.0: + resolution: {integrity: sha512-FCxlKLNGknS5ba/1lmpYijMUzX2esxW5xQqjWxw2eHFfS2MSdaHVINFmhjo+qN1WhZhNimq0dZATN9pH0IDrpA==} + + remark-rehype@11.1.2: + resolution: {integrity: sha512-Dh7l57ianaEoIpzbp0PC9UKAdCSVklD8E5Rpw7ETfbTl3FqcOOgq5q2LVDhgGCkaBv7p24JXikPdvhhmHvKMsw==} + + remark-stringify@11.0.0: + resolution: {integrity: sha512-1OSmLd3awB/t8qdoEOMazZkNsfVTeY4fTsgzcQFdXNq8ToTN4ZGwrMnlda4K6smTFKD+GRV6O48i6Z4iKgPPpw==} + + remark@15.0.1: + resolution: {integrity: sha512-Eht5w30ruCXgFmxVUSlNWQ9iiimq07URKeFS3hNc8cUWy1llX4KDWfyEDZRycMc+znsN9Ux5/tJ/BFdgdOwA3A==} + require-directory@2.1.1: resolution: {integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==} engines: {node: '>=0.10.0'} @@ -1789,6 +2107,9 @@ packages: resolution: {integrity: sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==} engines: {node: '>=0.10.0'} + space-separated-tokens@2.0.2: + resolution: {integrity: sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q==} + spawn-error-forwarder@1.0.0: resolution: {integrity: sha512-gRjMgK5uFjbCvdibeGJuy3I5OYz6VLoVdsOJdA6wV0WlfQVLFueoqMxwwYD9RODdgb6oUIvlRlsyFSiQkMKu0g==} @@ -1824,6 +2145,10 @@ packages: resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==} engines: {node: '>=8'} + string-width@5.1.2: + resolution: {integrity: sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==} + engines: {node: '>=12'} + string-width@7.2.0: resolution: {integrity: sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==} engines: {node: '>=18'} @@ -1831,6 +2156,9 @@ packages: string_decoder@1.1.1: resolution: {integrity: sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==} + stringify-entities@4.0.4: + resolution: {integrity: sha512-IwfBptatlO+QCJUo19AqvrPNqlVMpW9YEL2LIVY+Rpv2qsjCGxaDLNRgeGsQWJhfItebuJhsGSLjaBbNSQ+ieg==} + strip-ansi@6.0.1: resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==} engines: {node: '>=8'} @@ -1895,6 +2223,10 @@ packages: engines: {node: '>=10'} hasBin: true + test-exclude@7.0.1: + resolution: {integrity: sha512-pFYqmTw68LXVjeWJMST4+borgQP2AyMNbg1BpZh9LbyhUeNkeaPF9gzfPGUAnSMV3qPYdWUwDIjjCLiSDOl7vg==} + engines: {node: '>=18'} + thenify-all@1.6.0: resolution: {integrity: sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==} engines: {node: '>=0.8'} @@ -1939,6 +2271,12 @@ packages: resolution: {integrity: sha512-aXJDbk6SnumuaZSANd21XAo15ucCDE38H4fkqiGsc3MhCK+wOlZvLP9cB/TvpHT0mOyWgC4Z8EwRlzqYSUzdsA==} engines: {node: '>= 0.4'} + trim-lines@3.0.1: + resolution: {integrity: sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg==} + + trough@2.2.0: + resolution: {integrity: sha512-tmMpK00BjZiUyVyvrBK7knerNgmgvcV/KLVyuma/SC+TQN167GrMRciANTz09+k3zW8L8t60jWO1GpfkZdjTaw==} + tslib@2.8.1: resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} @@ -1981,10 +2319,28 @@ packages: resolution: {integrity: sha512-+QBBXBCvifc56fsbuxZQ6Sic3wqqc3WWaqxs58gvJrcOuN83HGTCwz3oS5phzU9LthRNE9VrJCFCLUgHeeFnfA==} engines: {node: '>=18'} + unified@11.0.5: + resolution: {integrity: sha512-xKvGhPWw3k84Qjh8bI3ZeJjqnyadK+GEFtazSfZv/rKeTkTjOJho6mFqh2SM96iIcZokxiOpg78GazTSg8+KHA==} + unique-string@3.0.0: resolution: {integrity: sha512-VGXBUVwxKMBUznyffQweQABPRRW1vHZAbadFZud4pLFAqRGvv/96vafgjWFqzourzr8YonlQiPgH0YCJfawoGQ==} engines: {node: '>=12'} + unist-util-is@6.0.0: + resolution: {integrity: sha512-2qCTHimwdxLfz+YzdGfkqNlH0tLi9xjTnHddPmJwtIG9MGsdbutfTc4P+haPD7l7Cjxf/WZj+we5qfVPvvxfYw==} + + unist-util-position@5.0.0: + resolution: {integrity: sha512-fucsC7HjXvkB5R3kTCO7kUjRdrS0BJt3M/FPxmHMBOm8JQi2BsHAHFsy27E0EolP8rp0NzXsJ+jNPyDWvOJZPA==} + + unist-util-stringify-position@4.0.0: + resolution: {integrity: sha512-0ASV06AAoKCDkS2+xw5RXJywruurpbC4JZSm7nr7MOt1ojAzvyyaO+UxZf18j8FCF6kmzCZKcAgN/yu2gm2XgQ==} + + unist-util-visit-parents@6.0.1: + resolution: {integrity: sha512-L/PqWzfTP9lzzEa6CKs0k2nARxTdZduw3zyh8d2NVBnsyvHjSX4TWse388YrrQKbvI8w20fGjGlhgT96WwKykw==} + + unist-util-visit@5.0.0: + resolution: {integrity: sha512-MR04uvD+07cwl/yhVuVWAtw+3GOR/knlL55Nd/wAdblk27GCVt3lqpTivy/tkJcZoNPzTwS1Y+KMojlLDhoTzg==} + universal-user-agent@7.0.3: resolution: {integrity: sha512-TmnEAEAsBJVZM/AADELsK76llnwcf9vMKuPz8JflO1frO8Lchitr0fNaN9d+Ap0BjKtqWqd/J17qeDnXh8CL2A==} @@ -2006,6 +2362,12 @@ packages: resolution: {integrity: sha512-OljLrQ9SQdOUqTaQxqL5dEfZWrXExyyWsozYlAWFawPVNuD83igl7uJD2RTkNMbniIYgt8l81eCJGIdQF7avLQ==} engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} + vfile-message@4.0.2: + resolution: {integrity: sha512-jRDZ1IMLttGj41KcZvlrYAaI3CfqpLpfpf+Mfig13viT6NKvRzWZ+lXz0Y5D60w6uJIBAOGq9mSHf0gktF0duw==} + + vfile@6.0.3: + resolution: {integrity: sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q==} + vite-node@3.2.4: resolution: {integrity: sha512-EbKSKh+bh1E1IFxeO0pg1n4dvoOTt0UDiXMd/qn++r98+jPO1xtJilvXldeuQ8giIB5IkpjCgMleHMNEsGH6pg==} engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0} @@ -2096,6 +2458,10 @@ packages: resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==} engines: {node: '>=10'} + wrap-ansi@8.1.0: + resolution: {integrity: sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==} + engines: {node: '>=12'} + wrap-ansi@9.0.0: resolution: {integrity: sha512-G8ura3S+3Z2G+mkgNRq8dqaFZAuxfsxpBB8OCTGRTCtp+l/v9nbFNmCUP1BZMts3G1142MsZfn6eeUKrr4PD1Q==} engines: {node: '>=18'} @@ -2133,8 +2499,16 @@ packages: resolution: {integrity: sha512-GQHQqAopRhwU8Kt1DDM8NjibDXHC8eoh1erhGAJPEyveY9qqVeXvVikNKrDz69sHowPMorbPUrH/mx8c50eiBQ==} engines: {node: '>=18'} + zwitch@2.0.4: + resolution: {integrity: sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==} + snapshots: + '@ampproject/remapping@2.3.0': + dependencies: + '@jridgewell/gen-mapping': 0.3.8 + '@jridgewell/trace-mapping': 0.3.25 + '@andrewbranch/untar.js@1.0.3': {} '@arethetypeswrong/cli@0.18.2': @@ -2164,8 +2538,21 @@ snapshots: js-tokens: 4.0.0 picocolors: 1.1.1 + '@babel/helper-string-parser@7.27.1': {} + '@babel/helper-validator-identifier@7.27.1': {} + '@babel/parser@7.27.5': + dependencies: + '@babel/types': 7.27.6 + + '@babel/types@7.27.6': + dependencies: + '@babel/helper-string-parser': 7.27.1 + '@babel/helper-validator-identifier': 7.27.1 + + '@bcoe/v8-coverage@1.0.2': {} + '@biomejs/biome@2.0.4': optionalDependencies: '@biomejs/cli-darwin-arm64': 2.0.4 @@ -2281,6 +2668,17 @@ snapshots: '@esbuild/win32-x64@0.25.5': optional: true + '@isaacs/cliui@8.0.2': + dependencies: + string-width: 5.1.2 + string-width-cjs: string-width@4.2.3 + strip-ansi: 7.1.0 + strip-ansi-cjs: strip-ansi@6.0.1 + wrap-ansi: 8.1.0 + wrap-ansi-cjs: wrap-ansi@7.0.0 + + '@istanbuljs/schema@0.1.3': {} + '@jridgewell/gen-mapping@0.3.8': dependencies: '@jridgewell/set-array': 1.2.1 @@ -2378,6 +2776,9 @@ snapshots: dependencies: '@octokit/openapi-types': 25.1.0 + '@pkgjs/parseargs@0.11.0': + optional: true + '@pnpm/config.env-replace@1.1.0': {} '@pnpm/network.ca-file@1.0.2': @@ -2604,14 +3005,51 @@ snapshots: dependencies: '@types/deep-eql': 4.0.2 + '@types/debug@4.1.12': + dependencies: + '@types/ms': 2.1.0 + '@types/deep-eql@4.0.2': {} '@types/estree@1.0.8': {} + '@types/hast@3.0.4': + dependencies: + '@types/unist': 3.0.3 + + '@types/mdast@4.0.4': + dependencies: + '@types/unist': 3.0.3 + + '@types/ms@2.1.0': {} + '@types/normalize-package-data@2.4.4': {} '@types/resolve@1.20.2': {} + '@types/unist@3.0.3': {} + + '@ungap/structured-clone@1.3.0': {} + + '@vitest/coverage-v8@3.2.4(vitest@3.2.4(@types/debug@4.1.12)(terser@5.43.1)(yaml@2.8.0))': + dependencies: + '@ampproject/remapping': 2.3.0 + '@bcoe/v8-coverage': 1.0.2 + ast-v8-to-istanbul: 0.3.3 + debug: 4.4.1 + istanbul-lib-coverage: 3.2.2 + istanbul-lib-report: 3.0.1 + istanbul-lib-source-maps: 5.0.6 + istanbul-reports: 3.1.7 + magic-string: 0.30.17 + magicast: 0.3.5 + std-env: 3.9.0 + test-exclude: 7.0.1 + tinyrainbow: 2.0.0 + vitest: 3.2.4(@types/debug@4.1.12)(terser@5.43.1)(yaml@2.8.0) + transitivePeerDependencies: + - supports-color + '@vitest/expect@3.2.4': dependencies: '@types/chai': 5.2.2 @@ -2696,10 +3134,24 @@ snapshots: assertion-error@2.0.1: {} + ast-v8-to-istanbul@0.3.3: + dependencies: + '@jridgewell/trace-mapping': 0.3.25 + estree-walker: 3.0.3 + js-tokens: 9.0.1 + + bail@2.0.2: {} + + balanced-match@1.0.2: {} + before-after-hook@4.0.0: {} bottleneck@2.19.5: {} + brace-expansion@2.0.2: + dependencies: + balanced-match: 1.0.2 + braces@3.0.3: dependencies: fill-range: 7.1.1 @@ -2710,6 +3162,8 @@ snapshots: callsites@3.1.0: {} + ccount@2.0.1: {} + chai@5.2.0: dependencies: assertion-error: 2.0.1 @@ -2733,6 +3187,12 @@ snapshots: char-regex@1.0.2: {} + character-entities-html4@2.1.0: {} + + character-entities-legacy@3.0.0: {} + + character-entities@2.0.2: {} + check-error@2.1.1: {} cjs-module-lexer@1.4.3: {} @@ -2793,6 +3253,8 @@ snapshots: colorette@2.0.20: {} + comma-separated-tokens@2.0.3: {} + commander@10.0.1: {} commander@14.0.0: {} @@ -2855,12 +3317,22 @@ snapshots: dependencies: ms: 2.1.3 + decode-named-character-reference@1.2.0: + dependencies: + character-entities: 2.0.2 + deep-eql@5.0.2: {} deep-extend@0.6.0: {} deepmerge@4.3.1: {} + dequal@2.0.3: {} + + devlop@1.1.0: + dependencies: + dequal: 2.0.3 + dir-glob@3.0.1: dependencies: path-type: 4.0.0 @@ -2873,10 +3345,14 @@ snapshots: dependencies: readable-stream: 2.3.8 + eastasianwidth@0.2.0: {} + emoji-regex@10.4.0: {} emoji-regex@8.0.0: {} + emoji-regex@9.2.2: {} + emojilib@2.4.0: {} env-ci@11.1.1: @@ -2977,6 +3453,8 @@ snapshots: expect-type@1.2.1: {} + extend@3.0.2: {} + fast-content-type-parse@3.0.0: {} fast-glob@3.3.3: @@ -3020,6 +3498,11 @@ snapshots: semver-regex: 4.0.5 super-regex: 1.0.0 + foreground-child@3.3.1: + dependencies: + cross-spawn: 7.0.6 + signal-exit: 4.1.0 + from2@2.3.0: dependencies: inherits: 2.0.4 @@ -3066,6 +3549,15 @@ snapshots: dependencies: is-glob: 4.0.3 + glob@10.4.5: + dependencies: + foreground-child: 3.3.1 + jackspeak: 3.4.3 + minimatch: 9.0.5 + minipass: 7.1.2 + package-json-from-dist: 1.0.1 + path-scurry: 1.11.1 + globby@14.1.0: dependencies: '@sindresorhus/merge-streams': 2.3.0 @@ -3096,6 +3588,24 @@ snapshots: dependencies: function-bind: 1.1.2 + hast-util-to-html@9.0.5: + dependencies: + '@types/hast': 3.0.4 + '@types/unist': 3.0.3 + ccount: 2.0.1 + comma-separated-tokens: 2.0.3 + hast-util-whitespace: 3.0.0 + html-void-elements: 3.0.0 + mdast-util-to-hast: 13.2.0 + property-information: 7.1.0 + space-separated-tokens: 2.0.2 + stringify-entities: 4.0.4 + zwitch: 2.0.4 + + hast-util-whitespace@3.0.0: + dependencies: + '@types/hast': 3.0.4 + highlight.js@10.7.3: {} hook-std@3.0.0: {} @@ -3108,6 +3618,10 @@ snapshots: dependencies: lru-cache: 10.4.3 + html-escaper@2.0.2: {} + + html-void-elements@3.0.0: {} + http-proxy-agent@7.0.2: dependencies: agent-base: 7.1.3 @@ -3213,6 +3727,33 @@ snapshots: lodash.isstring: 4.0.1 lodash.uniqby: 4.7.0 + istanbul-lib-coverage@3.2.2: {} + + istanbul-lib-report@3.0.1: + dependencies: + istanbul-lib-coverage: 3.2.2 + make-dir: 4.0.0 + supports-color: 7.2.0 + + istanbul-lib-source-maps@5.0.6: + dependencies: + '@jridgewell/trace-mapping': 0.3.25 + debug: 4.4.1 + istanbul-lib-coverage: 3.2.2 + transitivePeerDependencies: + - supports-color + + istanbul-reports@3.1.7: + dependencies: + html-escaper: 2.0.2 + istanbul-lib-report: 3.0.1 + + jackspeak@3.4.3: + dependencies: + '@isaacs/cliui': 8.0.2 + optionalDependencies: + '@pkgjs/parseargs': 0.11.0 + java-properties@1.0.2: {} js-tokens@4.0.0: {} @@ -3295,6 +3836,8 @@ snapshots: strip-ansi: 7.1.0 wrap-ansi: 9.0.0 + longest-streak@3.1.0: {} + loupe@3.1.4: {} lru-cache@10.4.3: {} @@ -3305,6 +3848,16 @@ snapshots: dependencies: '@jridgewell/sourcemap-codec': 1.5.0 + magicast@0.3.5: + dependencies: + '@babel/parser': 7.27.5 + '@babel/types': 7.27.6 + source-map-js: 1.2.1 + + make-dir@4.0.0: + dependencies: + semver: 7.7.2 + marked-terminal@7.3.0(marked@15.0.12): dependencies: ansi-escapes: 7.0.0 @@ -3331,12 +3884,195 @@ snapshots: marked@9.1.6: {} + mdast-util-from-markdown@2.0.2: + dependencies: + '@types/mdast': 4.0.4 + '@types/unist': 3.0.3 + decode-named-character-reference: 1.2.0 + devlop: 1.1.0 + mdast-util-to-string: 4.0.0 + micromark: 4.0.2 + micromark-util-decode-numeric-character-reference: 2.0.2 + micromark-util-decode-string: 2.0.1 + micromark-util-normalize-identifier: 2.0.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + unist-util-stringify-position: 4.0.0 + transitivePeerDependencies: + - supports-color + + mdast-util-phrasing@4.1.0: + dependencies: + '@types/mdast': 4.0.4 + unist-util-is: 6.0.0 + + mdast-util-to-hast@13.2.0: + dependencies: + '@types/hast': 3.0.4 + '@types/mdast': 4.0.4 + '@ungap/structured-clone': 1.3.0 + devlop: 1.1.0 + micromark-util-sanitize-uri: 2.0.1 + trim-lines: 3.0.1 + unist-util-position: 5.0.0 + unist-util-visit: 5.0.0 + vfile: 6.0.3 + + mdast-util-to-markdown@2.1.2: + dependencies: + '@types/mdast': 4.0.4 + '@types/unist': 3.0.3 + longest-streak: 3.1.0 + mdast-util-phrasing: 4.1.0 + mdast-util-to-string: 4.0.0 + micromark-util-classify-character: 2.0.1 + micromark-util-decode-string: 2.0.1 + unist-util-visit: 5.0.0 + zwitch: 2.0.4 + + mdast-util-to-string@4.0.0: + dependencies: + '@types/mdast': 4.0.4 + meow@13.2.0: {} merge-stream@2.0.0: {} merge2@1.4.1: {} + micromark-core-commonmark@2.0.3: + dependencies: + decode-named-character-reference: 1.2.0 + devlop: 1.1.0 + micromark-factory-destination: 2.0.1 + micromark-factory-label: 2.0.1 + micromark-factory-space: 2.0.1 + micromark-factory-title: 2.0.1 + micromark-factory-whitespace: 2.0.1 + micromark-util-character: 2.1.1 + micromark-util-chunked: 2.0.1 + micromark-util-classify-character: 2.0.1 + micromark-util-html-tag-name: 2.0.1 + micromark-util-normalize-identifier: 2.0.1 + micromark-util-resolve-all: 2.0.1 + micromark-util-subtokenize: 2.1.0 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-factory-destination@2.0.1: + dependencies: + micromark-util-character: 2.1.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-factory-label@2.0.1: + dependencies: + devlop: 1.1.0 + micromark-util-character: 2.1.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-factory-space@2.0.1: + dependencies: + micromark-util-character: 2.1.1 + micromark-util-types: 2.0.2 + + micromark-factory-title@2.0.1: + dependencies: + micromark-factory-space: 2.0.1 + micromark-util-character: 2.1.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-factory-whitespace@2.0.1: + dependencies: + micromark-factory-space: 2.0.1 + micromark-util-character: 2.1.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-util-character@2.1.1: + dependencies: + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-util-chunked@2.0.1: + dependencies: + micromark-util-symbol: 2.0.1 + + micromark-util-classify-character@2.0.1: + dependencies: + micromark-util-character: 2.1.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-util-combine-extensions@2.0.1: + dependencies: + micromark-util-chunked: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-util-decode-numeric-character-reference@2.0.2: + dependencies: + micromark-util-symbol: 2.0.1 + + micromark-util-decode-string@2.0.1: + dependencies: + decode-named-character-reference: 1.2.0 + micromark-util-character: 2.1.1 + micromark-util-decode-numeric-character-reference: 2.0.2 + micromark-util-symbol: 2.0.1 + + micromark-util-encode@2.0.1: {} + + micromark-util-html-tag-name@2.0.1: {} + + micromark-util-normalize-identifier@2.0.1: + dependencies: + micromark-util-symbol: 2.0.1 + + micromark-util-resolve-all@2.0.1: + dependencies: + micromark-util-types: 2.0.2 + + micromark-util-sanitize-uri@2.0.1: + dependencies: + micromark-util-character: 2.1.1 + micromark-util-encode: 2.0.1 + micromark-util-symbol: 2.0.1 + + micromark-util-subtokenize@2.1.0: + dependencies: + devlop: 1.1.0 + micromark-util-chunked: 2.0.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-util-symbol@2.0.1: {} + + micromark-util-types@2.0.2: {} + + micromark@4.0.2: + dependencies: + '@types/debug': 4.1.12 + debug: 4.4.1 + decode-named-character-reference: 1.2.0 + devlop: 1.1.0 + micromark-core-commonmark: 2.0.3 + micromark-factory-space: 2.0.1 + micromark-util-character: 2.1.1 + micromark-util-chunked: 2.0.1 + micromark-util-combine-extensions: 2.0.1 + micromark-util-decode-numeric-character-reference: 2.0.2 + micromark-util-encode: 2.0.1 + micromark-util-normalize-identifier: 2.0.1 + micromark-util-resolve-all: 2.0.1 + micromark-util-sanitize-uri: 2.0.1 + micromark-util-subtokenize: 2.1.0 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + transitivePeerDependencies: + - supports-color + micromatch@4.0.8: dependencies: braces: 3.0.3 @@ -3350,8 +4086,14 @@ snapshots: mimic-function@5.0.1: {} + minimatch@9.0.5: + dependencies: + brace-expansion: 2.0.2 + minimist@1.2.8: {} + minipass@7.1.2: {} + ms@2.1.3: {} mz@2.7.0: @@ -3436,6 +4178,8 @@ snapshots: p-try@1.0.0: {} + package-json-from-dist@1.0.1: {} + parent-module@1.0.1: dependencies: callsites: 3.1.0 @@ -3476,6 +4220,11 @@ snapshots: path-parse@1.0.7: {} + path-scurry@1.11.1: + dependencies: + lru-cache: 10.4.3 + minipass: 7.1.2 + path-type@4.0.0: {} path-type@6.0.0: {} @@ -3511,6 +4260,8 @@ snapshots: process-nextick-args@2.0.1: {} + property-information@7.1.0: {} + proto-list@1.2.4: {} queue-microtask@1.2.3: {} @@ -3554,6 +4305,44 @@ snapshots: dependencies: '@pnpm/npm-conf': 2.3.1 + rehype-stringify@10.0.1: + dependencies: + '@types/hast': 3.0.4 + hast-util-to-html: 9.0.5 + unified: 11.0.5 + + remark-parse@11.0.0: + dependencies: + '@types/mdast': 4.0.4 + mdast-util-from-markdown: 2.0.2 + micromark-util-types: 2.0.2 + unified: 11.0.5 + transitivePeerDependencies: + - supports-color + + remark-rehype@11.1.2: + dependencies: + '@types/hast': 3.0.4 + '@types/mdast': 4.0.4 + mdast-util-to-hast: 13.2.0 + unified: 11.0.5 + vfile: 6.0.3 + + remark-stringify@11.0.0: + dependencies: + '@types/mdast': 4.0.4 + mdast-util-to-markdown: 2.1.2 + unified: 11.0.5 + + remark@15.0.1: + dependencies: + '@types/mdast': 4.0.4 + remark-parse: 11.0.0 + remark-stringify: 11.0.0 + unified: 11.0.5 + transitivePeerDependencies: + - supports-color + require-directory@2.1.1: {} resolve-from@4.0.0: {} @@ -3709,6 +4498,8 @@ snapshots: source-map@0.6.1: {} + space-separated-tokens@2.0.2: {} + spawn-error-forwarder@1.0.0: {} spdx-correct@3.2.0: @@ -3746,6 +4537,12 @@ snapshots: is-fullwidth-code-point: 3.0.0 strip-ansi: 6.0.1 + string-width@5.1.2: + dependencies: + eastasianwidth: 0.2.0 + emoji-regex: 9.2.2 + strip-ansi: 7.1.0 + string-width@7.2.0: dependencies: emoji-regex: 10.4.0 @@ -3756,6 +4553,11 @@ snapshots: dependencies: safe-buffer: 5.1.2 + stringify-entities@4.0.4: + dependencies: + character-entities-html4: 2.1.0 + character-entities-legacy: 3.0.0 + strip-ansi@6.0.1: dependencies: ansi-regex: 5.0.1 @@ -3814,6 +4616,12 @@ snapshots: commander: 2.20.3 source-map-support: 0.5.21 + test-exclude@7.0.1: + dependencies: + '@istanbuljs/schema': 0.1.3 + glob: 10.4.5 + minimatch: 9.0.5 + thenify-all@1.6.0: dependencies: thenify: 3.3.1 @@ -3852,6 +4660,10 @@ snapshots: traverse@0.6.8: {} + trim-lines@3.0.1: {} + + trough@2.2.0: {} + tslib@2.8.1: {} type-fest@1.4.0: {} @@ -3873,10 +4685,43 @@ snapshots: unicorn-magic@0.3.0: {} + unified@11.0.5: + dependencies: + '@types/unist': 3.0.3 + bail: 2.0.2 + devlop: 1.1.0 + extend: 3.0.2 + is-plain-obj: 4.1.0 + trough: 2.2.0 + vfile: 6.0.3 + unique-string@3.0.0: dependencies: crypto-random-string: 4.0.0 + unist-util-is@6.0.0: + dependencies: + '@types/unist': 3.0.3 + + unist-util-position@5.0.0: + dependencies: + '@types/unist': 3.0.3 + + unist-util-stringify-position@4.0.0: + dependencies: + '@types/unist': 3.0.3 + + unist-util-visit-parents@6.0.1: + dependencies: + '@types/unist': 3.0.3 + unist-util-is: 6.0.0 + + unist-util-visit@5.0.0: + dependencies: + '@types/unist': 3.0.3 + unist-util-is: 6.0.0 + unist-util-visit-parents: 6.0.1 + universal-user-agent@7.0.3: {} universalify@2.0.1: {} @@ -3892,6 +4737,16 @@ snapshots: validate-npm-package-name@5.0.1: {} + vfile-message@4.0.2: + dependencies: + '@types/unist': 3.0.3 + unist-util-stringify-position: 4.0.0 + + vfile@6.0.3: + dependencies: + '@types/unist': 3.0.3 + vfile-message: 4.0.2 + vite-node@3.2.4(terser@5.43.1)(yaml@2.8.0): dependencies: cac: 6.7.14 @@ -3926,7 +4781,7 @@ snapshots: terser: 5.43.1 yaml: 2.8.0 - vitest@3.2.4(terser@5.43.1)(yaml@2.8.0): + vitest@3.2.4(@types/debug@4.1.12)(terser@5.43.1)(yaml@2.8.0): dependencies: '@types/chai': 5.2.2 '@vitest/expect': 3.2.4 @@ -3951,6 +4806,8 @@ snapshots: vite: 6.3.5(terser@5.43.1)(yaml@2.8.0) vite-node: 3.2.4(terser@5.43.1)(yaml@2.8.0) why-is-node-running: 2.3.0 + optionalDependencies: + '@types/debug': 4.1.12 transitivePeerDependencies: - jiti - less @@ -3982,6 +4839,12 @@ snapshots: string-width: 4.2.3 strip-ansi: 6.0.1 + wrap-ansi@8.1.0: + dependencies: + ansi-styles: 6.2.1 + string-width: 5.1.2 + strip-ansi: 7.1.0 + wrap-ansi@9.0.0: dependencies: ansi-styles: 6.2.1 @@ -4019,3 +4882,5 @@ snapshots: yargs-parser: 21.1.1 yoctocolors@2.1.1: {} + + zwitch@2.0.4: {} diff --git a/src/index.ts b/src/index.ts index e94213b..d4023c7 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,17 +1,369 @@ -export function greet(name: string): string { - return `Hello, ${name}!` +import type { Blockquote, Html, Paragraph, PhrasingContent, Root, Text } from 'mdast' +import type { PartialDeep } from 'type-fest' +import type { Plugin } from 'unified' +import { visit } from 'unist-util-visit' + +type HtmlElement = + | 'html' + | 'head' + | 'body' + | 'base' + | 'link' + | 'meta' + | 'style' + | 'title' + | 'address' + | 'article' + | 'aside' + | 'footer' + | 'header' + | 'h1' + | 'h2' + | 'h3' + | 'h4' + | 'h5' + | 'h6' + | 'hgroup' + | 'main' + | 'nav' + | 'section' + | 'search' + | 'blockquote' + | 'dd' + | 'div' + | 'dl' + | 'dt' + | 'figcaption' + | 'figure' + | 'hr' + | 'li' + | 'menu' + | 'ol' + | 'p' + | 'pre' + | 'ul' + | 'a' + | 'abbr' + | 'b' + | 'bdi' + | 'bdo' + | 'br' + | 'cite' + | 'code' + | 'data' + | 'dfn' + | 'em' + | 'i' + | 'kbd' + | 'mark' + | 'q' + | 'rp' + | 'rt' + | 'ruby' + | 's' + | 'samp' + | 'small' + | 'span' + | 'strong' + | 'sub' + | 'sup' + | 'time' + | 'u' + | 'var' + | 'wbr' + | 'area' + | 'audio' + | 'img' + | 'map' + | 'track' + | 'video' + | 'embed' + | 'iframe' + | 'object' + | 'picture' + | 'source' + | 'svg' + | 'math' + | 'canvas' + | 'noscript' + | 'script' + | 'del' + | 'ins' + | 'caption' + | 'col' + | 'colgroup' + | 'table' + | 'tbody' + | 'td' + | 'tfoot' + | 'th' + | 'thead' + | 'tr' + | 'button' + | 'datalist' + | 'fieldset' + | 'form' + | 'input' + | 'label' + | 'legend' + | 'meter' + | 'optgroup' + | 'option' + | 'output' + | 'progress' + | 'select' + | 'textarea' + | 'details' + | 'dialog' + | 'summary' + | 'slot' + | 'template' + +export type AlertConfig = { + iconElementHtml: string + tags: { + container: HtmlElement + icon: HtmlElement + title: HtmlElement + content: HtmlElement + } + classNames: { + container: string + icon: string + title: string + content: string + } +} + +export type AlertsConfig = { + note?: PartialDeep + tip?: PartialDeep + important?: PartialDeep + warning?: PartialDeep + caution?: PartialDeep +} + +export type RemarkGitHubAlertsOptions = { + alerts?: AlertsConfig + defaultConfig?: PartialDeep +} + +const ALERT_TYPES = ['NOTE', 'TIP', 'IMPORTANT', 'WARNING', 'CAUTION'] as const +type AlertType = (typeof ALERT_TYPES)[number] + +const DEFAULT_CONFIG: AlertConfig = { + iconElementHtml: '', + tags: { + container: 'div', + icon: 'span', + title: 'div', + content: 'div', + }, + classNames: { + container: 'markdown-alert', + icon: 'markdown-alert-icon', + title: 'markdown-alert-title', + content: 'markdown-alert-content', + }, +} + +const DEFAULT_ICONS: Record, string> = { + note: '', + tip: '', + important: '', + warning: '', + caution: '', +} + +function isAlertBlockquote(node: Blockquote): { + isAlert: boolean + type?: AlertType + title?: string +} { + if (!node.children || node.children.length === 0) { + return { isAlert: false } + } + + const firstChild = node.children[0] + if (!firstChild || firstChild.type !== 'paragraph') { + return { isAlert: false } + } + + const firstTextNode = firstChild.children[0] + if (!firstTextNode || firstTextNode.type !== 'text') { + return { isAlert: false } + } + + const text = firstTextNode.value + const firstLine = text.split('\n')[0] || '' + const alertMatch = firstLine.match(/^\[!(NOTE|TIP|IMPORTANT|WARNING|CAUTION)\](?:\s+(.*))?$/) + + if (!alertMatch) { + return { isAlert: false } + } + + const alertType = alertMatch[1] as AlertType + const customTitle = alertMatch[2]?.trim() + + return { + isAlert: true, + type: alertType, + title: customTitle || alertType.charAt(0) + alertType.slice(1).toLowerCase(), + } +} + +function escapeHtml(text: string): string { + const replacements = [ + ['&', '&'], + ['<', '<'], + ['>', '>'], + ['"', '"'], + ["'", '''], + ] as const + + let result = text + for (const [char, entity] of replacements) { + result = result.replaceAll(char, entity) + } + return result } -export function add(a: number, b: number): number { - return a + b +function extractTextContent(node: PhrasingContent): string { + if (node.type === 'text') { + return node.value + } + if ('children' in node && node.children && Array.isArray(node.children)) { + return node.children.map(extractTextContent).join('') + } + return '' } -export function multiply(a: number, b: number): number { - return a * b +function createAlertHtml( + type: AlertType, + title: string, + content: string, + config: AlertConfig +): string { + const alertTypeKey = type.toLowerCase() as Lowercase + const containerClasses = [ + config.classNames.container, + `${config.classNames.container}-${alertTypeKey}`, + ] + .filter(Boolean) + .join(' ') + + const iconHtml = config.iconElementHtml || DEFAULT_ICONS[alertTypeKey] + + return `<${config.tags.container} class="${escapeHtml(containerClasses)}"> + <${config.tags.title} class="${escapeHtml(config.classNames.title)}"> + <${config.tags.icon} class="${escapeHtml(config.classNames.icon)}">${iconHtml} + ${escapeHtml(title)} + + <${config.tags.content} class="${escapeHtml(config.classNames.content)}"> + ${content} + +` } -export default { - greet, - add, - multiply, +function mergeConfig( + defaultConfig: AlertConfig, + userConfig?: PartialDeep +): AlertConfig { + if (!userConfig) return defaultConfig + + return { + iconElementHtml: userConfig.iconElementHtml ?? defaultConfig.iconElementHtml, + tags: { + container: userConfig.tags?.container ?? defaultConfig.tags.container, + icon: userConfig.tags?.icon ?? defaultConfig.tags.icon, + title: userConfig.tags?.title ?? defaultConfig.tags.title, + content: userConfig.tags?.content ?? defaultConfig.tags.content, + }, + classNames: { + container: userConfig.classNames?.container ?? defaultConfig.classNames.container, + icon: userConfig.classNames?.icon ?? defaultConfig.classNames.icon, + title: userConfig.classNames?.title ?? defaultConfig.classNames.title, + content: userConfig.classNames?.content ?? defaultConfig.classNames.content, + }, + } } + +export function processBlockquote( + node: Blockquote, + index: number | undefined, + parent: unknown, + baseConfig: AlertConfig, + alerts: AlertsConfig +): boolean { + if (!parent || typeof index !== 'number') return false + + const alertInfo = isAlertBlockquote(node) + if (!alertInfo.isAlert || !alertInfo.type || !alertInfo.title) return false + + const alertTypeKey = alertInfo.type.toLowerCase() as keyof AlertsConfig + const alertConfig = mergeConfig(baseConfig, alerts[alertTypeKey]) + + const firstParagraph = node.children[0] as Paragraph + const firstTextNode = firstParagraph.children[0] as Text + const alertDeclarationMatch = firstTextNode.value.match( + /^\[!(?:NOTE|TIP|IMPORTANT|WARNING|CAUTION)\](?:\s+.*)?$/ + ) + + if (alertDeclarationMatch) { + const remainingText = firstTextNode.value + .replace(/^\[!(?:NOTE|TIP|IMPORTANT|WARNING|CAUTION)\]\s*/, '') + .trim() + + if (remainingText) { + firstTextNode.value = remainingText + } else { + firstParagraph.children.shift() + if (firstParagraph.children.length === 0) { + node.children.shift() + } + } + } + + const contentHtml = node.children + .map(child => { + if (child.type === 'paragraph') { + const textContent = child.children.map(extractTextContent).join('') + return `

${escapeHtml(textContent)}

` + } + return '' + }) + .join('\n ') + + const alertHtml = createAlertHtml(alertInfo.type, alertInfo.title, contentHtml, alertConfig) + + const htmlNode: Html = { + type: 'html', + value: alertHtml, + } + + if ( + parent && + typeof parent === 'object' && + 'children' in parent && + Array.isArray(parent.children) + ) { + parent.children[index] = htmlNode + } + + return true +} + +export const remarkGitHubAlerts: Plugin<[RemarkGitHubAlertsOptions?], Root> = (options = {}) => { + const { alerts = {}, defaultConfig } = options + const baseConfig = mergeConfig(DEFAULT_CONFIG, defaultConfig) + + return tree => { + visit(tree, 'blockquote', (node: Blockquote, index, parent) => { + processBlockquote(node, index, parent, baseConfig, alerts) + }) + + return tree + } +} + +export default remarkGitHubAlerts diff --git a/test/index.test.ts b/test/index.test.ts index 61fc331..5ada6c8 100644 --- a/test/index.test.ts +++ b/test/index.test.ts @@ -1,40 +1,464 @@ -import { describe, expect, it } from 'vitest' -import { add, greet, multiply } from '../src/index' +import type { Blockquote, Root } from 'mdast' +import rehypeStringify from 'rehype-stringify' +import { remark } from 'remark' +import remarkRehype from 'remark-rehype' +import { describe, expect, it, vi } from 'vitest' +import { processBlockquote, type RemarkGitHubAlertsOptions, remarkGitHubAlerts } from '../src/index' -describe('greet', () => { - it('should return a greeting message', () => { - expect(greet('World')).toBe('Hello, World!') - }) +function processMarkdown(markdown: string, options?: RemarkGitHubAlertsOptions) { + return remark() + .use(remarkGitHubAlerts, options) + .use(remarkRehype, { allowDangerousHtml: true }) + .use(rehypeStringify, { allowDangerousHtml: true }) + .process(markdown) + .then(result => result.toString()) +} + +describe('remarkGitHubAlerts', () => { + describe('basic alert types', () => { + it('should convert NOTE alert', async () => { + const markdown = `> [!NOTE] +> This is a note alert.` + const result = await processMarkdown(markdown) + + expect(result).toContain('class="markdown-alert markdown-alert-note"') + expect(result).toContain('class="markdown-alert-title"') + expect(result).toContain('class="markdown-alert-icon"') + expect(result).toContain('class="markdown-alert-content"') + expect(result).toContain('Note') + expect(result).toContain('This is a note alert.') + }) + + it('should convert TIP alert', async () => { + const markdown = `> [!TIP] +> This is a tip alert.` + const result = await processMarkdown(markdown) + + expect(result).toContain('class="markdown-alert markdown-alert-tip"') + expect(result).toContain('Tip') + expect(result).toContain('This is a tip alert.') + }) + + it('should convert IMPORTANT alert', async () => { + const markdown = `> [!IMPORTANT] +> This is an important alert.` + const result = await processMarkdown(markdown) + + expect(result).toContain('class="markdown-alert markdown-alert-important"') + expect(result).toContain('Important') + expect(result).toContain('This is an important alert.') + }) + + it('should convert WARNING alert', async () => { + const markdown = `> [!WARNING] +> This is a warning alert.` + const result = await processMarkdown(markdown) + + expect(result).toContain('class="markdown-alert markdown-alert-warning"') + expect(result).toContain('Warning') + expect(result).toContain('This is a warning alert.') + }) - it('should handle empty string', () => { - expect(greet('')).toBe('Hello, !') + it('should convert CAUTION alert', async () => { + const markdown = `> [!CAUTION] +> This is a caution alert.` + const result = await processMarkdown(markdown) + + expect(result).toContain('class="markdown-alert markdown-alert-caution"') + expect(result).toContain('Caution') + expect(result).toContain('This is a caution alert.') + }) }) -}) -describe('add', () => { - it('should add two positive numbers', () => { - expect(add(2, 3)).toBe(5) + describe('custom titles', () => { + it('should use custom title when provided', async () => { + const markdown = `> [!NOTE] Custom Title +> This note has a custom title.` + const result = await processMarkdown(markdown) + + expect(result).toContain('Custom Title') + expect(result).not.toContain('Note') + expect(result).toContain('This note has a custom title.') + }) + + it('should handle custom title with multiple words', async () => { + const markdown = `> [!TIP] Very Important Tip Here +> This is the content.` + const result = await processMarkdown(markdown) + + expect(result).toContain('Very Important Tip Here') + expect(result).toContain('This is the content.') + }) }) - it('should add negative numbers', () => { - expect(add(-1, -2)).toBe(-3) + describe('multi-line content', () => { + it('should handle multi-line alert content', async () => { + const markdown = `> [!NOTE] +> First line of content. +> Second line of content. +> Third line of content.` + const result = await processMarkdown(markdown) + + expect(result).toContain('First line of content.') + expect(result).toContain('Second line of content.') + expect(result).toContain('Third line of content.') + }) + + it('should handle multi-paragraph content', async () => { + const markdown = `> [!WARNING] +> First paragraph. +> +> Second paragraph with more content.` + const result = await processMarkdown(markdown) + + expect(result).toContain('First paragraph.') + expect(result).toContain('Second paragraph with more content.') + }) + + it('should handle alerts with mixed content types', async () => { + const markdown = `> [!TIP] +> Regular paragraph content. +> +> \`\`\` +> code block content +> \`\`\` +> +> Another paragraph.` + const result = await processMarkdown(markdown) + + expect(result).toContain('markdown-alert') + expect(result).toContain('Regular paragraph content.') + expect(result).toContain('Another paragraph.') + // Code block should be filtered out (returns empty string) + expect(result).not.toContain('code block content') + }) }) - it('should add zero', () => { - expect(add(5, 0)).toBe(5) + describe('configuration options', () => { + it('should apply custom class names', async () => { + const markdown = `> [!NOTE] +> Test content.` + const options: RemarkGitHubAlertsOptions = { + defaultConfig: { + classNames: { + container: 'custom-class another-class', + }, + }, + } + const result = await processMarkdown(markdown, options) + + expect(result).toContain('custom-class') + expect(result).toContain('another-class') + }) + + it('should apply custom container class name', async () => { + const markdown = `> [!TIP] +> Test content.` + const options: RemarkGitHubAlertsOptions = { + defaultConfig: { + classNames: { + container: 'custom-alert', + }, + }, + } + const result = await processMarkdown(markdown, options) + + expect(result).toContain('class="custom-alert custom-alert-tip"') + // Should still use default title/content classes since only container class was customized + expect(result).toContain('class="markdown-alert-title"') + expect(result).toContain('class="markdown-alert-content"') + }) + + it('should apply alert-specific configuration', async () => { + const markdown = `> [!NOTE] +> Test content.` + const options: RemarkGitHubAlertsOptions = { + alerts: { + note: { + classNames: { + container: 'note-container', + title: 'note-title', + content: 'note-content', + icon: 'note-icon', + }, + }, + }, + } + const result = await processMarkdown(markdown, options) + + expect(result).toContain('class="note-container note-container-note"') + expect(result).toContain('class="note-title"') + expect(result).toContain('class="note-content"') + expect(result).toContain('class="note-icon"') + }) + + it('should merge default and alert-specific configurations', async () => { + const markdown = `> [!WARNING] +> Test content.` + const options: RemarkGitHubAlertsOptions = { + defaultConfig: { + classNames: { + container: 'base-class', + }, + }, + alerts: { + warning: { + classNames: { + container: 'warning-specific', + }, + }, + }, + } + const result = await processMarkdown(markdown, options) + + expect(result).toContain('warning-specific') + }) + + it('should apply custom HTML tags', async () => { + const markdown = `> [!NOTE] +> Test content.` + const options: RemarkGitHubAlertsOptions = { + defaultConfig: { + tags: { + container: 'section', + title: 'h3', + icon: 'i', + content: 'article', + }, + }, + } + const result = await processMarkdown(markdown, options) + + expect(result).toContain('
') + expect(result).toContain('') + expect(result).toContain('') + expect(result).toContain('') + }) + + it('should apply custom icon HTML', async () => { + const markdown = `> [!TIP] +> Test content.` + const options: RemarkGitHubAlertsOptions = { + alerts: { + tip: { + iconElementHtml: 'tip-icon', + }, + }, + } + const result = await processMarkdown(markdown, options) + + expect(result).toContain('tip-icon') + }) + + it('should apply global icon HTML', async () => { + const markdown = `> [!IMPORTANT] +> Test content.` + const options: RemarkGitHubAlertsOptions = { + defaultConfig: { + iconElementHtml: '๐Ÿ”ฅ', + }, + } + const result = await processMarkdown(markdown, options) + + expect(result).toContain('๐Ÿ”ฅ') + }) }) -}) -describe('multiply', () => { - it('should multiply two positive numbers', () => { - expect(multiply(3, 4)).toBe(12) + describe('edge cases and error handling', () => { + it('should not convert regular blockquotes', async () => { + const markdown = `> This is a regular blockquote +> without alert syntax.` + const result = await processMarkdown(markdown) + + expect(result).not.toContain('markdown-alert') + expect(result).toContain('blockquote') + }) + + it('should handle blockquotes with non-paragraph first child', async () => { + // This tests the case where firstChild is not a paragraph (lines 48-49) + const markdown = `> \`\`\` +> code block +> \`\`\`` + const result = await processMarkdown(markdown) + + expect(result).not.toContain('markdown-alert') + expect(result).toContain('blockquote') + }) + + it('should handle blockquotes with non-text first node in paragraph', async () => { + // This tests the case where firstTextNode is not text (lines 53-54) + const markdown = `> **Bold text only** +> No plain text at start` + const result = await processMarkdown(markdown) + + expect(result).not.toContain('markdown-alert') + expect(result).toContain('blockquote') + }) + + it('should not convert malformed alert syntax', async () => { + const markdown = `> [NOTE] +> Missing exclamation mark.` + const result = await processMarkdown(markdown) + + expect(result).not.toContain('markdown-alert') + expect(result).toContain('blockquote') + }) + + it('should not convert invalid alert types', async () => { + const markdown = `> [!INVALID] +> This is not a valid alert type.` + const result = await processMarkdown(markdown) + + expect(result).not.toContain('markdown-alert') + expect(result).toContain('blockquote') + }) + + it('should handle empty blockquotes', async () => { + const markdown = `>` + const result = await processMarkdown(markdown) + + expect(result).not.toContain('markdown-alert') + }) + + it('should handle blockquotes with no text content', async () => { + const markdown = `> [!NOTE]` + const result = await processMarkdown(markdown) + + expect(result).toContain('markdown-alert') + expect(result).toContain('Note') + }) + + it('should handle alert with only whitespace after declaration', async () => { + const markdown = `> [!TIP] \n> Actual content here.` + const result = await processMarkdown(markdown) + + expect(result).toContain('markdown-alert') + expect(result).toContain('Tip') + expect(result).toContain('Actual content here.') + }) + + it('should handle alert with empty text node', async () => { + // This tests line 58 - the || '' fallback when split returns undefined + // This is an edge case that's hard to trigger through normal markdown + const markdown = `> [!NOTE] Custom title +> Content here.` + const result = await processMarkdown(markdown) + + expect(result).toContain('markdown-alert') + expect(result).toContain('Custom title') + expect(result).toContain('Content here.') + }) }) - it('should multiply by zero', () => { - expect(multiply(5, 0)).toBe(0) + describe('mixed content', () => { + it('should only convert alert blockquotes in mixed content', async () => { + const markdown = `Some regular text. + +> Regular blockquote + +> [!NOTE] +> This is an alert. + +More regular text. + +> Another regular blockquote` + const result = await processMarkdown(markdown) + + expect(result).toContain('markdown-alert') + expect(result).toContain('Some regular text') + expect(result).toContain('More regular text') + expect(result.match(/
/g)).toHaveLength(2) // Two regular blockquotes + }) }) - it('should multiply negative numbers', () => { - expect(multiply(-2, 3)).toBe(-6) + describe('output structure', () => { + it('should generate minimal DOM structure', async () => { + const markdown = `> [!NOTE] +> Simple content.` + const result = await processMarkdown(markdown) + + // Should have exactly one alert container (not counting title/content/icon classes) + expect(result.match(/class="markdown-alert markdown-alert-note"/g)).toHaveLength(1) + + // Should have title and content sections + expect(result.match(/class="markdown-alert-title"/g)).toHaveLength(1) + expect(result.match(/class="markdown-alert-content"/g)).toHaveLength(1) + expect(result.match(/class="markdown-alert-icon"/g)).toHaveLength(1) + }) + + it('should preserve content formatting', async () => { + const markdown = `> [!TIP] +> **Bold text** and *italic text*.` + const result = await processMarkdown(markdown) + + expect(result).toContain('Bold text') + expect(result).toContain('italic text') + }) + + it('should handle edge case with invalid parent or index', () => { + // This tests the defensive code path directly + const blockquoteNode: Blockquote = { + type: 'blockquote', + children: [ + { + type: 'paragraph', + children: [ + { + type: 'text', + value: '[!NOTE] Test', + }, + ], + }, + ], + } + + const mockConfig = { + iconElementHtml: '', + tags: { + container: 'div' as const, + icon: 'span' as const, + title: 'div' as const, + content: 'div' as const, + }, + classNames: { + container: 'test', + icon: 'test-icon', + title: 'test-title', + content: 'test-content', + }, + } + + // Test with null parent - should return false + expect(processBlockquote(blockquoteNode, 0, null, mockConfig, {})).toBe(false) + + // Test with undefined index - should return false + expect(processBlockquote(blockquoteNode, undefined as any, {}, mockConfig, {})).toBe(false) + + // Test with string index instead of number - should return false + expect(processBlockquote(blockquoteNode, '0' as any, {}, mockConfig, {})).toBe(false) + + // Test the edge case where text.split('\n')[0] is falsy (empty string) + const emptyTextBlockquote: Blockquote = { + type: 'blockquote', + children: [ + { + type: 'paragraph', + children: [ + { + type: 'text', + value: '', // Empty text triggers the || '' fallback + }, + ], + }, + ], + } + + const mockParent = { children: [emptyTextBlockquote] } + expect(processBlockquote(emptyTextBlockquote, 0, mockParent, mockConfig, {})).toBe(false) + }) }) }) diff --git a/tsconfig.json b/tsconfig.json index fe11a0b..dc8d781 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -26,7 +26,6 @@ "noPropertyAccessFromIndexSignature": true, "useUnknownInCatchVariables": true, "exactOptionalPropertyTypes": true, - "noUncheckedSideEffectImports": true, "noUnusedLocals": true, "noUnusedParameters": true, From fb9e9f18b2d3df56c46da7313fde40c2e0b2586e Mon Sep 17 00:00:00 2001 From: Igor Klepacki Date: Wed, 25 Jun 2025 21:14:25 +0200 Subject: [PATCH 2/4] fix: coverage reporting --- .github/workflows/release.yml | 3 +++ .github/workflows/test-coverage.yml | 3 +++ 2 files changed, 6 insertions(+) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 682e0da..227eb40 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -23,6 +23,9 @@ jobs: token: ${{ secrets.ACTIONS_BRANCH_PROTECTION_BYPASS }} fetch-depth: 0 + - name: Install pnpm with version specified in 'packageManager' + uses: pnpm/action-setup@v4 + - name: Install Node.js 20 uses: actions/setup-node@v4 with: diff --git a/.github/workflows/test-coverage.yml b/.github/workflows/test-coverage.yml index 9b903f7..f1f6a9f 100644 --- a/.github/workflows/test-coverage.yml +++ b/.github/workflows/test-coverage.yml @@ -21,6 +21,9 @@ jobs: with: token: ${{ secrets.ACTIONS_BRANCH_PROTECTION_BYPASS }} + - name: Install pnpm with version specified in 'packageManager' + uses: pnpm/action-setup@v4 + - name: Install Node.js ${{ matrix.node-version }} uses: actions/setup-node@v4 with: From e1981c3ca318b513713f7cb606fe86ce3272e03d Mon Sep 17 00:00:00 2001 From: Igor Klepacki Date: Wed, 25 Jun 2025 21:16:08 +0200 Subject: [PATCH 3/4] fix: invalid pnpm-lock --- pnpm-lock.yaml | 43 ++++++++++++++++++++++++++++--------------- 1 file changed, 28 insertions(+), 15 deletions(-) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 4dda84c..43393bf 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -8,21 +8,6 @@ importers: .: dependencies: - hast-util-to-html: - specifier: ^9.0.5 - version: 9.0.5 - mdast-util-from-markdown: - specifier: ^2.0.2 - version: 2.0.2 - mdast-util-to-hast: - specifier: ^13.2.0 - version: 13.2.0 - remark: - specifier: ^15.0.1 - version: 15.0.1 - unified: - specifier: ^11.0.5 - version: 11.0.5 unist-util-visit: specifier: ^5.0.0 version: 5.0.0 @@ -33,6 +18,9 @@ importers: '@biomejs/biome': specifier: 2.0.4 version: 2.0.4 + '@hono/node-server': + specifier: ^1.14.4 + version: 1.14.4(hono@4.8.3) '@rollup/plugin-commonjs': specifier: ^28.0.6 version: 28.0.6(rollup@4.44.0) @@ -75,6 +63,9 @@ importers: '@vitest/coverage-v8': specifier: ^3.2.4 version: 3.2.4(vitest@3.2.4(@types/debug@4.1.12)(terser@5.43.1)(yaml@2.8.0)) + hono: + specifier: ^4.8.2 + version: 4.8.3 husky: specifier: 9.1.7 version: 9.1.7 @@ -84,6 +75,9 @@ importers: rehype-stringify: specifier: ^10.0.1 version: 10.0.1 + remark: + specifier: ^15.0.1 + version: 15.0.1 remark-rehype: specifier: ^11.1.2 version: 11.1.2 @@ -108,6 +102,9 @@ importers: typescript: specifier: ^5.8.3 version: 5.8.3 + unified: + specifier: ^11.0.5 + version: 11.0.5 unist-util-is: specifier: ^6.0.0 version: 6.0.0 @@ -377,6 +374,12 @@ packages: cpu: [x64] os: [win32] + '@hono/node-server@1.14.4': + resolution: {integrity: sha512-DnxpshhYewr2q9ZN8ez/M5mmc3sucr8CT1sIgIy1bkeUXut9XWDkqHoFHRhWIQgkYnKpVRxunyhK7WzpJeJ6qQ==} + engines: {node: '>=18.14.1'} + peerDependencies: + hono: ^4 + '@isaacs/cliui@8.0.2': resolution: {integrity: sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==} engines: {node: '>=12'} @@ -1265,6 +1268,10 @@ packages: highlight.js@10.7.3: resolution: {integrity: sha512-tzcUFauisWKNHaRkN4Wjl/ZA07gENAjFl3J/c480dprkGTg5EQstgaNFqBfUqCq54kZRIEcreTsAgF/m2quD7A==} + hono@4.8.3: + resolution: {integrity: sha512-jYZ6ZtfWjzBdh8H/0CIFfCBHaFL75k+KMzaM177hrWWm2TWL39YMYaJgB74uK/niRc866NMlH9B8uCvIo284WQ==} + engines: {node: '>=16.9.0'} + hook-std@3.0.0: resolution: {integrity: sha512-jHRQzjSDzMtFy34AGj1DN+vq54WVuhSvKgrHf0OMiFQTwDD4L/qqofVEWjLOBMTn5+lCD3fPg32W9yOfnEJTTw==} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} @@ -2668,6 +2675,10 @@ snapshots: '@esbuild/win32-x64@0.25.5': optional: true + '@hono/node-server@1.14.4(hono@4.8.3)': + dependencies: + hono: 4.8.3 + '@isaacs/cliui@8.0.2': dependencies: string-width: 5.1.2 @@ -3608,6 +3619,8 @@ snapshots: highlight.js@10.7.3: {} + hono@4.8.3: {} + hook-std@3.0.0: {} hosted-git-info@7.0.2: From 69941ea299a2c588ec8172081655ac400840b19e Mon Sep 17 00:00:00 2001 From: Igor Klepacki Date: Wed, 25 Jun 2025 21:18:19 +0200 Subject: [PATCH 4/4] fix: add missing typings in tsconfig --- tsconfig.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tsconfig.json b/tsconfig.json index dc8d781..ac405e6 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,7 +1,7 @@ { "compilerOptions": { "target": "ES2022", - "lib": ["ES2022"], + "lib": ["ES2022", "DOM"], "module": "ESNext", "moduleResolution": "bundler", "allowSyntheticDefaultImports": true,