diff --git a/.github/workflows/tests-cli.yml b/.github/workflows/tests-cli.yml index d9f94eb..bab8eda 100644 --- a/.github/workflows/tests-cli.yml +++ b/.github/workflows/tests-cli.yml @@ -73,4 +73,4 @@ jobs: run: pnpm install --frozen-lockfile - name: Run cli tests - run: ./ci/scripts/run-tests.sh -c + run: ./ci/scripts/run-tests.sh -c 'npm,pnpm,bun' diff --git a/README.md b/README.md index 24e1a16..3752c02 100644 --- a/README.md +++ b/README.md @@ -16,12 +16,21 @@ Docpress automates the process of downloading documentation files from specified For optimal use of Docpress, please check the [rules section](#rules) to understand the conventions and guidelines that will ensure that everything works correctly. By following these rules, you can maximize the effectiveness of the documentation process. +## Documentation + +__Website:__ . + +__Table of Contents__ *- md sources*: +- [Rules](./docs/02-rules.md) +- [Advanced usage](./docs/03-advanced-usage.md) +- [Development](./docs/04-development.md) + ## Quickstart 1. Generate website using the Docpress docker image. ```sh docker run --name docpress --rm -v $(pwd)/docpress:/app/docpress:rw \ - ghcr.io/this-is-tobi/docpress -u + ghcr.io/this-is-tobi/docpress -U ``` > The dist folder is available at `./docpress/.vitepress/dist`, ready to be served by a web server like Nginx, Apache, etc... @@ -33,32 +42,9 @@ For optimal use of Docpress, please check the [rules section](#rules) to underst 3. Access the website at the following address : . -## Rules - -To ensure that the program functions correctly, please follow these conventions: - -1. __Documentation folder structure__: - - The script will only parse the `docs/` folder located at the root level of the repository. This folder is used to import advanced documentation features, such as multi-page documentation, embedded images, and files. - -2. __File naming and sorting__: - - If a `docs/` folder is present, all files within it will be sorted and renamed by removing any prefix numbers. This ensures that files appear cleanly in the generated website. For example, `docs/01-get-started.md` will be renamed to `get-started.md`. - -3. __Handling the root readme file__: - - The `README.md` file located at the root of the repository will only be imported if there is no `./docs/01-readme.md` file present. This allows you to differentiate between the general README and the advanced documentation introduction page. - - For instance, you might use the README file for a table of contents that is not relevant in the context of the documentation website. - -4. __Link management__: - - Any inline link in the `./README.md` file that does not point to `./docs/**` will be replaced with the corresponding GitHub link. - - Similarly, any inline link in the `./docs/*.md` files that does not reference `./docs/**` will also be replaced with the appropriate GitHub link. - -5. __Project descriptions__: - - The project description displayed on the home page of the generated website is extracted from the GitHub repository's description. - -By adhering to these rules, you can ensure that your documentation is processed correctly and that it aligns with the intended structure and functionality of the generated site. +## Options -## Advanced usage - -All CLI options are available with the helper flag `docker run ghcr.io/this-is-tobi/docpress -h` : +CLI description : ```txt Usage: docpress [options] [command] @@ -69,13 +55,14 @@ Options: -b, --branch Branch used to collect Git provider data. (default: "main") -c, --extra-public-content List of comma separated additional files or directories to process Vitepress public folder. -C, --config Path to the docpress configuration file. + -f, --forks Whether or not to create the dedicated fork page that aggregate external contributions. -g, --git-provider Git provider used to retrieve data. Values should be "github". (default: "github") -h, --help display help for command -p, --extra-header-pages List of comma separated additional files or directories to process Vitepress header pages. -r, --repos-filter List of comma separated repositories to retrieve from Git provider. Default to all user's public repositories. - -t, --extra-theme List of comma separated additional files or directories to process Vitepress public folder. + -t, --extra-theme List of comma separated additional files or directories to use as Vitepress theme. -T, --token Git provider token used to collect data. - -u, --username Git provider username used to collect data. + -U, --username Git provider username used to collect data. -v, --vitepress-config Path to the vitepress configuration file. -V, --version output the version number @@ -85,29 +72,29 @@ Commands: prepare [options] Transform doc to the target vitepress format. ``` -### Usage methods +## Usage methods Docpress is available through both [npm](https://www.npmjs.com/package/@tobi-or-not/docpress) and [Docker](https://github.com/this-is-tobi/docpress/pkgs/container/docpress), so you can choose the installation method that best suits your environment. -#### Using npm (or other package managers) +### Using npm (or other package managers) If you prefer Node.js package managers like npm, pnpm, or bun, you can easily install and run Docpress without additional setup. To run Docpress using npm: ```sh -npx @tobi-or-not/docpress -u +npx @tobi-or-not/docpress -U ``` > [!TIP] > If you’re using a package manager like pnpm or bun, simply replace npx with the corresponding command (pnpx or bunx) to execute Docpress. -#### Using Docker +### Using Docker Docpress also provides a Docker image, which is especially useful if you want to avoid installing dependencies directly on your system or if you’re working in a containerized environment. Using Docker ensures a consistent runtime environment. To run Docpress with Docker: ```sh -docker run --rm -v $(pwd)/docpress:/app/docpress:rw ghcr.io/this-is-tobi/docpress -u +docker run --rm -v $(pwd)/docpress:/app/docpress:rw ghcr.io/this-is-tobi/docpress -U ``` In this command: @@ -117,174 +104,3 @@ In this command: > Ensure Docker is installed and running on your system before using this method. Both methods provide the same functionality, so you can choose the one that fits your setup or use case. - -### Filter repositories - -The `-r` or `--repos-filter` option allows you to specify which repositories to include or exclude when generating documentation. This option accepts a comma-separated list of repository names and can include exclusions by prefixing a repository name with `!`. - -- __Including specific repositories__: To generate documentation for specific repositories, provide their names separated by commas. For example, `-r 'repo1,repo2'` will retrieve documentation only for `repo1` and `repo2`. -- __Excluding repositories__: To exclude certain repositories, prefix their names with `!`. For instance, `-r '!repo1,!repo2'` will retrieve all public repositories except `repo1` and `repo2`. - -> Only public, non-fork repositories are fetched. - -### Docpress configuration - -Docpress can be configured with an external configuration file specified by the `-C` or `--config` option. This file, which should be in JSON or YAML format, allows you to set Docpress parameters to automate and customize the documentation fetching and generation process. Key options that can be configured include: - -- `username`: Git provider username (usually GitHub) to fetch repositories (equivalent to the `-u` CLI option). -- `reposFilter`: List of repositories to include or exclude (equivalent to the `-r` CLI option). -- `branch`: Default branch from which documentation will be fetched. -- `extraHeaderPages`: Additional pages to include in the website header (equivalent to `-p`). -- `extraPublicContent`: Additional content for the Vitepress public folder (equivalent to `-c`). -- `extraTheme`: Files or folders to customize the Vitepress theme (equivalent to `-t`). - -Example JSON configuration: - -```json -{ - "username": "my-github-username", - "reposFilter": ["!repo1", "!repo2"], - "branch": "main", - "extraHeaderPages": ["header1.md", "header2.md"], - "extraPublicContent": ["favicon.ico", "logo.png"], - "extraTheme": ["custom-theme.css"] -} -``` - -### Vitepress configuration - -Vitepress configuration can be customized via a vitepress.config.js file, specified with the `-v` or `--vitepress-config` option. This file lets you adjust Vitepress options like the site title, navigation structure, footer, and plugins. - -Key configurable options include: - -- `title`: The title of the generated site. -- `description`: A description of the site (used for SEO). -- `themeConfig`: The theme configuration, including navigation and header links. -- `markdown`: Options to customize Markdown rendering. - -Basic Vitepress configuration example: - -```json -{ - "title": "My Project Documentation", - "description": "A site generated by Docpress", - "themeConfig": { - "socialLinks": [ - { "icon": "github", "link": "https://github.com/this-is-tobi" } - ], - "outline": [2, 4] - } -} -``` - -### Vitepress theme - -Docpress allows you to customize the Vitepress theme by adding files or directories specified with the `-t` or `--extra-theme` option. This flexibility enables you to adjust the look and feel of your documentation site by adding custom CSS, modifying components, or even creating an entirely custom theme. - -You can approach theme customization in two main ways: - -1. __Extending the Default Theme__: - Vitepress offers options to extend its built-in default theme, which includes configuration for the layout, sidebar, and header. Extending the default theme is a good choice if you want to make minor tweaks to colors, fonts, or layout without changing the overall structure. To do this, you can: - - Add custom CSS or SCSS files for style adjustments. - - Override default theme components to change the behavior of certain sections (like headers or footers). - - Introduce new Vue components if needed. - - For more details on extending the default theme, visit the [Vitepress guide on extending the default theme](https://vitepress.dev/guide/extending-default-theme). - -2. __Creating a Custom Theme__: - If your project requires a unique design or a complete overhaul of the site layout, you can create a fully custom theme. Vitepress allows you to replace its default theme entirely by creating a custom theme directory structure. With this approach, you gain full control over every part of the site, from page structure to individual components. - - To implement a custom theme: - - Create a `theme/index.js` file where you export custom components. - - Organize components like headers, footers, and layout in the theme directory. - - Add global styles to `theme/style.css` or other preferred CSS files. - - For a comprehensive guide on creating a custom theme, refer to the [Vitepress documentation on using a custom theme](https://vitepress.dev/guide/custom-theme#using-a-custom-theme). - -> For either method, keep in mind that customizations should be tested across different pages to ensure they integrate smoothly with your content. - -With these approaches, you can create a distinct look and feel for your documentation site while keeping it aligned with your project’s branding and user experience requirements. - -### Extra header pages - -With the `-p` or `--extra-header-pages` option, you can add additional pages to the Vitepress website header, such as privacy policies, advanced user guides, or links to external resources. - -This option accepts a comma-separated list of Markdown files or directories containing Markdown files. These pages will be included as-is in the final website and automatically added to the navigation bar at the top of each page on the generated site. - -> All specified extra-header-pages must be in Markdown format and can include any Vitepress-compatible content, such as links, images, and other documentation elements. - -### Extra public content - -The `-c` or `--extra-public-content` option allows you to include additional static assets in the Vitepress `public` folder. This is useful for adding content that needs to be accessible across all pages of the documentation, such as custom icons, logos, or other assets. - -These files or directories will be copied directly into the `public` folder and will be available as static resources on the final website. - -Common use cases for `extra-public-content` include: -- __Adding branding assets__: For example, including a custom favicon (`favicon.ico`) or logo (`logo.png`) that will display on every page. -- __Providing downloadable files__: Such as PDF guides, policy documents, or downloadable images that users may need quick access to. -- __Supplementing media files__: Including additional images, icons, or background files that you may want to reference across different parts of your documentation. - -This option accepts a comma-separated list of file or directory paths. - -> Ensure that any files you add are optimized for the web, as large or uncompressed files may impact site performance. - -> All files and directories specified with extra-public-content are directly accessible at the root of the final site, making it easy to link to them within your Markdown files (e.g., using /logo.png in an img tag). - -### GitHub Token Option - -When using the Docpress tool to fetch data from GitHub, you have the option to provide a personal access token using the `-T` flag. This token is important as it allows you to authenticate your requests, which can help increase your request limits on the GitHub API. - -Utilizing a personal access token not only helps you avoid rate limiting issues but also enhances the reliability of data retrieval for your documentation projects. - -For further details on how authentication affects rate limits and other considerations, please refer to the official GitHub documentation on [Rate limits for the REST API](https://docs.github.com/en/rest/overview/rate-limits-for-the-rest-api). - -## Development - -### Prerequisites - -To successfully run the Docpress application in development mode, you'll need to install the following prerequisites: - -- __[Node.js](https://nodejs.org/)__: A JavaScript runtime that allows you to execute JavaScript code server-side. Make sure you install a version that is compatible with the project requirements. -- __[pnpm](https://pnpm.io/)__: A fast, disk space-efficient package manager for Node.js that is used to manage the project's dependencies. - -### Setting Up the Development Environment - -Follow these steps to set up the development environment and start working with Docpress: - -1. __Clone the repository__ - Start by cloning the Docpress repository to your local machine using Git: - ```sh - git clone https://github.com/this-is-tobi/docpress.git - cd docpress - ``` -2. __Install project dependencies__ - Use pnpm to install all necessary Node.js dependencies: - ```sh - pnpm install - ``` -3. __Fetch and build the website__ - Run the following command to fetch documentation from the specified GitHub username and build the static website: - ```sh - pnpm run dev -u - ``` -4. __Preview the website__ - Launch a local development server to preview your website: - ```sh - pnpm run vp:dev - ``` - You can access the preview by navigating to [http://localhost:8080](http://localhost:8080) in your web browser. - -> [!TIP] -> Use command `pnpm run dev -h` to print options. - -## Contributions - -All contributions to my repositories are welcome and must be made via Github with a pull request following the rules below. - -### Conventions - -Commits must follow the specification of [Conventional Commits](https://www.conventionalcommits.org/en/v1.0.0/), it is possible to add the [VSCode extension](https://github.com/vivaxy/vscode-conventional-commits) to facilitate the creation of commits. - -A PR must be made with an updated branch with the `main` branch in rebase (and without merge) before requesting a merge, and the merge must be requested in `main`. - -Check whether the repository has linting rules or tests to keep them clean and ensure that CI workflows pass (new features in repositories with tests should be accompanied by new tests to ensure that the new feature works properly). diff --git a/ci/scripts/run-tests.sh b/ci/scripts/run-tests.sh index 57c1625..b304de8 100755 --- a/ci/scripts/run-tests.sh +++ b/ci/scripts/run-tests.sh @@ -22,14 +22,15 @@ RUN_LINT="false" RUN_UNIT_TESTS="false" RUN_CLI_TESTS="false" RUN_DOCKER_TESTS="false" +PACKAGE_MANAGER="npm" # Declare script helper TEXT_HELPER="\nThis script aims to run application tests. Following flags are available: - -c Run cli tests. + -c Run cli tests (optionally, specify the package manager to be tested as a comma-separated list. Available options are 'npm', 'pnpm' and 'bun', default is '${PACKAGE_MANAGER}') - -d Run docker tests (optionally pass the docker image to test as argument). + -d Run docker tests (optionally, specify the docker image to test as argument). -l Run lint. @@ -42,11 +43,12 @@ print_help() { } # Parse options -while getopts hcd:lu flag +while getopts hc:d:lu flag do case "${flag}" in c) - RUN_CLI_TESTS=true;; + RUN_CLI_TESTS=true + PACKAGE_MANAGER=${OPTARG};; d) RUN_DOCKER_TESTS=true DOCKER_IMAGE=${OPTARG};; @@ -111,7 +113,7 @@ printf "\nScript settings: -> docker buildx version: ${DOCKER_BUILDX_VERSION} -> run lint: ${RUN_LINT} -> run unit tests: ${RUN_UNIT_TESTS} - -> run cli tests: ${RUN_CLI_TESTS} + -> run cli tests: ${RUN_CLI_TESTS} (${PACKAGE_MANAGER}) -> run docker tests: ${RUN_DOCKER_TESTS}\n" @@ -144,29 +146,33 @@ if [ "$RUN_CLI_TESTS" == "true" ]; then && pnpm pack \ && TGZ_PKG_NAME=$(ls -d $PWD/* | grep '.tgz') - mkdir -p /tmp/docpress/cli/pnpm \ - && cd /tmp/docpress/cli/pnpm \ - && [ -f "./package.json" ] || pnpm init \ - && pnpm add $TGZ_PKG_NAME \ - && pnpm exec docpress -u this-is-tobi -r homelab \ - && cd - > /dev/null - checkDocsResult /tmp/docpress/cli/pnpm/docpress - - mkdir -p /tmp/docpress/cli/npm \ - && cd /tmp/docpress/cli/npm \ - && [ -f "./package.json" ] || npm init -y \ - && npm install $TGZ_PKG_NAME \ - && npm exec docpress -- -u this-is-tobi -r homelab \ - && cd - > /dev/null - checkDocsResult /tmp/docpress/cli/npm/docpress - - mkdir -p /tmp/docpress/cli/bun \ - && cd /tmp/docpress/cli/bun \ - && [ -f "./package.json" ] || bun init \ - && bun add $TGZ_PKG_NAME \ - && bunx docpress -u this-is-tobi -r homelab \ - && cd - > /dev/null - checkDocsResult /tmp/docpress/cli/bun/docpress + if [[ ",${PACKAGE_MANAGER}," == *",npm,"* ]]; then + mkdir -p /tmp/docpress/cli/npm \ + && cd /tmp/docpress/cli/npm \ + && [ -f "./package.json" ] || npm init -y \ + && npm install $TGZ_PKG_NAME \ + && npm exec docpress -- -U this-is-tobi -r homelab \ + && cd - > /dev/null + checkDocsResult /tmp/docpress/cli/npm/docpress + fi + if [[ ",${PACKAGE_MANAGER}," == *",pnpm,"* ]]; then + mkdir -p /tmp/docpress/cli/pnpm \ + && cd /tmp/docpress/cli/pnpm \ + && [ -f "./package.json" ] || pnpm init \ + && pnpm add $TGZ_PKG_NAME \ + && pnpm exec docpress -U this-is-tobi -r homelab \ + && cd - > /dev/null + checkDocsResult /tmp/docpress/cli/pnpm/docpress + fi + if [[ ",${PACKAGE_MANAGER}," == *",bun,"* ]]; then + mkdir -p /tmp/docpress/cli/bun \ + && cd /tmp/docpress/cli/bun \ + && [ -f "./package.json" ] || bun init -y \ + && bun add $TGZ_PKG_NAME \ + && bunx docpress -U this-is-tobi -r homelab \ + && cd - > /dev/null + checkDocsResult /tmp/docpress/cli/bun/docpress + fi fi @@ -179,9 +185,9 @@ if [ "$RUN_DOCKER_TESTS" == "true" ]; then if [ -n "$DOCKER_IMAGE" ]; then mkdir -p /tmp/docpress/docker/docpress \ - && docker run --name docpress --rm -v /tmp/docpress/docker/docpress:/app/docpress:rw --user root $DOCKER_IMAGE -u this-is-tobi -r homelab + && docker run --name docpress --rm -v /tmp/docpress/docker/docpress:/app/docpress:rw --user root $DOCKER_IMAGE -U this-is-tobi -r homelab else - pnpm run build:docker && pnpm run start:docker -u this-is-tobi -r homelab + pnpm run build:docker && pnpm run start:docker -U this-is-tobi -r homelab fi checkDocsResult /tmp/docpress/docker/docpress diff --git a/docs/01-readme.md b/docs/01-readme.md new file mode 100644 index 0000000..755af7c --- /dev/null +++ b/docs/01-readme.md @@ -0,0 +1,97 @@ +# Docpress :zap: + +This project aims to automate the construction of documentation website based on a Github username and optionally a list of repositories. + +## Explanation + +Docpress automates the process of downloading documentation files from specified GitHub repositories. Here's how it works: + +1. __Repository Documentation Retrieval__: + - The program checks if a `docs/` folder is present at the root level of each specified repository. + - If the `docs/` folder exists, the entire folder will be downloaded, including all its contents. + - If the `docs/` folder is not found, only the `README.md` file will be downloaded. + +2. __Static Website Generation__: + - After retrieving the documentation files, Docpress generates a static website using [VitePress](https://vitepress.dev/). This framework allows for the creation of fast and customizable documentation sites. + +For optimal use of Docpress, please check the [rules section](#rules) to understand the conventions and guidelines that will ensure that everything works correctly. By following these rules, you can maximize the effectiveness of the documentation process. + +## Quickstart + +1. Generate website using the Docpress docker image. + ```sh + docker run --name docpress --rm -v $(pwd)/docpress:/app/docpress:rw \ + ghcr.io/this-is-tobi/docpress -U + ``` + > The dist folder is available at `./docpress/.vitepress/dist`, ready to be served by a web server like Nginx, Apache, etc... + +2. Start an Nginx docker image using the generated static folder. + ```sh + docker run --name my-docs --rm -v ./docpress/.vitepress/dist:/usr/share/nginx/html:ro -p 8080:80 \ + docker.io/nginx + ``` + +3. Access the website at the following address : . + +## Options + +CLI description : + +```txt +Usage: docpress [options] [command] + +Build your doc website faster than light ⚡️⚡️⚡️ + +Options: + -b, --branch Branch used to collect Git provider data. (default: "main") + -c, --extra-public-content List of comma separated additional files or directories to process Vitepress public folder. + -C, --config Path to the docpress configuration file. + -f, --forks Whether or not to create the dedicated fork page that aggregate external contributions. + -g, --git-provider Git provider used to retrieve data. Values should be "github". (default: "github") + -h, --help display help for command + -p, --extra-header-pages List of comma separated additional files or directories to process Vitepress header pages. + -r, --repos-filter List of comma separated repositories to retrieve from Git provider. Default to all user's public repositories. + -t, --extra-theme List of comma separated additional files or directories to use as Vitepress theme. + -T, --token Git provider token used to collect data. + -U, --username Git provider username used to collect data. + -v, --vitepress-config Path to the vitepress configuration file. + -V, --version output the version number + +Commands: + build [options] Build vitepress website. + fetch [options] Fetch docs with the given username and git provider. + prepare [options] Transform doc to the target vitepress format. +``` + +## Usage methods + +Docpress is available through both [npm](https://www.npmjs.com/package/@tobi-or-not/docpress) and [Docker](https://github.com/this-is-tobi/docpress/pkgs/container/docpress), so you can choose the installation method that best suits your environment. + +### Using npm (or other package managers) + +If you prefer Node.js package managers like npm, pnpm, or bun, you can easily install and run Docpress without additional setup. + +To run Docpress using npm: +```sh +npx @tobi-or-not/docpress -U +``` + +> [!TIP] +> If you’re using a package manager like pnpm or bun, simply replace npx with the corresponding command (pnpx or bunx) to execute Docpress. + +### Using Docker + +Docpress also provides a Docker image, which is especially useful if you want to avoid installing dependencies directly on your system or if you’re working in a containerized environment. Using Docker ensures a consistent runtime environment. + +To run Docpress with Docker: +```sh +docker run --rm -v $(pwd)/docpress:/app/docpress:rw ghcr.io/this-is-tobi/docpress -U +``` + +In this command: +- `--rm` removes the container after it stops, keeping your environment clean. +- `-v $(pwd)/docpress:/app/docpress:rw` mounts the `docpress` directory in your current path to the container’s `/app/docpress` folder, allowing Docpress to store generated files locally. + +> Ensure Docker is installed and running on your system before using this method. + +Both methods provide the same functionality, so you can choose the one that fits your setup or use case. diff --git a/docs/02-rules.md b/docs/02-rules.md new file mode 100644 index 0000000..c5a4cc8 --- /dev/null +++ b/docs/02-rules.md @@ -0,0 +1,22 @@ +# Rules + +To ensure that the program functions correctly, please follow these conventions: + +1. __Documentation folder structure__: + - The script will only parse the `docs/` folder located at the root level of the repository. This folder is used to import advanced documentation features, such as multi-page documentation, embedded images, and files. + +2. __File naming and sorting__: + - If a `docs/` folder is present, all files within it will be sorted and renamed by removing any prefix numbers. This ensures that files appear cleanly in the generated website. For example, `docs/01-get-started.md` will be renamed to `get-started.md`. + +3. __Handling the root readme file__: + - The `README.md` file located at the root of the repository will only be imported if there is no `./docs/01-readme.md` file present. This allows you to differentiate between the general README and the advanced documentation introduction page. + - For instance, you might use the README file for a table of contents that is not relevant in the context of the documentation website. + +4. __Link management__: + - Any inline link in the `./README.md` file that does not point to `./docs/**` will be replaced with the corresponding GitHub link. + - Similarly, any inline link in the `./docs/*.md` files that does not reference `./docs/**` will also be replaced with the appropriate GitHub link. + +5. __Project descriptions__: + - The project description displayed on the home page of the generated website is extracted from the GitHub repository's description. + +By adhering to these rules, you can ensure that your documentation is processed correctly and that it aligns with the intended structure and functionality of the generated site. diff --git a/docs/03-advanced-usage.md b/docs/03-advanced-usage.md new file mode 100644 index 0000000..b2437aa --- /dev/null +++ b/docs/03-advanced-usage.md @@ -0,0 +1,241 @@ +# Advanced Usage + +All CLI options are available with the helper flag `docker run ghcr.io/this-is-tobi/docpress -h` : + +```txt +Usage: docpress [options] [command] + +Build your doc website faster than light ⚡️⚡️⚡️ + +Options: + -b, --branch Branch used to collect Git provider data. (default: "main") + -c, --extra-public-content List of comma separated additional files or directories to process Vitepress public folder. + -C, --config Path to the docpress configuration file. + -f, --forks Whether or not to create the dedicated fork page that aggregate external contributions. + -g, --git-provider Git provider used to retrieve data. Values should be "github". (default: "github") + -h, --help display help for command + -p, --extra-header-pages List of comma separated additional files or directories to process Vitepress header pages. + -r, --repos-filter List of comma separated repositories to retrieve from Git provider. Default to all user's public repositories. + -t, --extra-theme List of comma separated additional files or directories to use as Vitepress theme. + -T, --token Git provider token used to collect data. + -U, --username Git provider username used to collect data. + -v, --vitepress-config Path to the vitepress configuration file. + -V, --version output the version number + +Commands: + build [options] Build vitepress website. + fetch [options] Fetch docs with the given username and git provider. + prepare [options] Transform doc to the target vitepress format. +``` + +## Filter Repositories + +The `-r` or `--repos-filter` option allows you to specify which repositories to include or exclude when generating documentation. This option accepts a comma-separated list of repository names and can include exclusions by prefixing a repository name with `!`. + +- __Including specific repositories__: To generate documentation for specific repositories, provide their names separated by commas. For example, `-r 'repo1,repo2'` will retrieve documentation only for `repo1` and `repo2`. +- __Excluding repositories__: To exclude certain repositories, prefix their names with `!`. For instance, `-r '!repo1,!repo2'` will retrieve all public repositories except `repo1` and `repo2`. + +> Only public, non-fork repositories are fetched. + +## Vitepress Configuration + +Vitepress configuration can be customized via a vitepress.config.js file, specified with the `-v` or `--vitepress-config` option. This file lets you adjust Vitepress options like the site title, navigation structure, footer, and plugins. + +Key configurable options include: + +- `title`: The title of the generated site. +- `description`: A description of the site (used for SEO). +- `themeConfig`: The theme configuration, including navigation and header links. +- `markdown`: Options to customize Markdown rendering. + +Basic Vitepress configuration example: + +```json +{ + "title": "My Project Documentation", + "description": "A site generated by Docpress", + "themeConfig": { + "socialLinks": [ + { "icon": "github", "link": "https://github.com/this-is-tobi" } + ], + "outline": [2, 4] + } +} +``` + +## Vitepress Theme + +The `-t` or `--extra-theme` option allows users to provide additional files or directories that will be integrated into the VitePress theme dynamically. This enables users to enhance or completely customize the look and feel of their documentation site by adding their own components, styles, and other assets. + +When using this option, the provided files are processed and registered automatically by Docpress. This means you can include custom Vue components, CSS/SCSS styles, JavaScript files, and other assets to complement or replace the default theme functionality. + +### How It Works + +1. __Dynamic Asset Registration__: + - All files provided through `--extra-theme` are dynamically loaded and made available in the VitePress environment. + - Vue components are automatically registered globally, allowing you to use them in your Markdown files without additional imports. + - CSS/SCSS files are globally applied to ensure your styles are reflected across all pages of your documentation. + +2. __Supported File Types__: + - `.vue`: Vue components are registered dynamically. The name of the component is derived from the filename. + - `.css` or `.scss`: Stylesheets are applied globally to enhance or override existing styles. + - `.js` or `.ts`: JavaScript or TypeScript files can be used to add logic or utilities. + - Static assets like `.png`, `.jpg`, `.svg`, etc., are included as part of the final build and are accessible in your documentation. + +3. __Fallback to Default Theme__: + - Even if custom theme files are provided, the default Docpress/VitePress theme remains accessible. Your custom components and styles are layered on top of the existing theme, allowing for a hybrid or fully customized experience. + +### Usage + +You can pass one or more file paths (comma-separated) to the `--extra-theme` option. For example: + +```bash +npx @tobi-or-not/docpress -U -t './custom-theme/components,./custom-theme/styles' +``` + +In this example: + +- The `components` directory contains custom Vue components. +- The `styles` directory contains CSS/SCSS files to override or enhance the default styles. + +### Common Use Cases + +- Adding Custom Components: + - Create a `CustomHeader.vue` or `CustomFooter.vue` component and include it in the theme. These components can then be used in your Markdown files or layouts. +- Applying Custom Styles: + - Include a `styles.css` file to define your own branding styles (e.g., colors, fonts, spacing). +- Extending Functionality: + - Add JavaScript utilities or TypeScript logic for interactive elements or advanced functionality. +- Static Assets: + - Include logos, background images, or icons that your theme uses to create a personalized appearance. + +### Example Directory Structure + +Suppose you have the following directory structure for your custom theme: + +```sh +custom-theme/ +├── components/ +│ ├── CustomHeader.vue +│ └── CustomFooter.vue +├── styles/ +│ ├── theme.css +│ └── overrides.scss +└── assets/ + ├── logo.png + └── background.jpg +``` + +By running the following command: + +```bash +npx @tobi-or-not/docpress -U -t './custom-theme' +``` + +Docpress will: + +1. Register `CustomHeader.vue` and `CustomFooter.vue` as global components. +2. Apply the styles defined in `theme.css` and `overrides.scss`. +3. Include `logo.png` and `background.jpg` in the final build, making them accessible in your documentation. + +> Important Notes +> - Ensure that your custom files do not conflict with the existing theme structure unless your intent is to override default behavior. +> - Avoid including unnecessary or unused files to keep the build efficient. +> - Static assets like images and fonts will be copied as-is into the final build directory, so ensure they are optimized for web use. + +This flexibility allows you to create a fully customized, professional documentation experience tailored to your needs. + +## Extra Header Pages + +With the `-p` or `--extra-header-pages` option, you can add additional pages to the Vitepress website header, such as privacy policies, advanced user guides, or links to external resources. + +This option accepts a comma-separated list of Markdown files or directories containing Markdown files. These pages will be included as-is in the final website and automatically added to the navigation bar at the top of each page on the generated site. + +> All specified extra-header-pages must be in Markdown format and can include any Vitepress-compatible content, such as links, images, and other documentation elements. + +## Extra Public Content + +The `-c` or `--extra-public-content` option allows you to include additional static assets in the Vitepress `public` folder. This is useful for adding content that needs to be accessible across all pages of the documentation, such as custom icons, logos, or other assets. + +These files or directories will be copied directly into the `public` folder and will be available as static resources on the final website. + +Common use cases for `extra-public-content` include: +- __Adding branding assets__: For example, including a custom favicon (`favicon.ico`) or logo (`logo.png`) that will display on every page. +- __Providing downloadable files__: Such as PDF guides, policy documents, or downloadable images that users may need quick access to. +- __Supplementing media files__: Including additional images, icons, or background files that you may want to reference across different parts of your documentation. + +This option accepts a comma-separated list of file or directory paths. + +> Ensure that any files you add are optimized for the web, as large or uncompressed files may impact site performance. + +> All files and directories specified with extra-public-content are directly accessible at the root of the final site, making it easy to link to them within your Markdown files (e.g., using /logo.png in an img tag). + +## Fork Contributions Page + +The `-f` or `--forks` option generates a dedicated page showcasing all forked repositories where the user's contributions have been successfully merged into the original source repositories. This feature is ideal for creating a centralized portfolio or documentation to highlight the user's collaborative work and contributions across multiple projects. + +By aggregating these repositories, the page serves as both a recognition of the user’s contributions and a historical archive of projects influenced by their efforts. It emphasizes the impact of these contributions and celebrates the investment made in community and open-source projects. + +### Common Use Cases + +- Showcasing user contributions: Highlight the breadth of repositories where the user has made meaningful contributions. +Building a contribution portfolio: Create a centralized "museum" of merged pull requests to demonstrate the user's influence across projects. +- Encouraging collaboration: Inspire others by showing how individual contributions can make a significant difference in the broader ecosystem. +- Archiving collaborative efforts: Preserve a historical record of impactful contributions and projects influenced by the user. + +### Generated Forks Page + +When this flag is enabled, the tool creates a markdown page (`docs/forks.md` that will be available in header as `Forks`) listing all forked repositories where the user's pull requests have been merged into the original repositories. The page typically includes details such as: +- Repository name +- Repository description +- Repository stars counter + +This page acts as a dynamic portfolio of contributions, making it an excellent resource for professionals, developers, and community members who wish to document their work and demonstrate their impact across various projects. + +### Forks With Extra Theme + +If this option is used with extra theme, the + +## Docpress Configuration + +Docpress can be configured with an external configuration file specified by the `-C` or `--config` option. This file, which should be in JSON or YAML format, allows you to set Docpress parameters to automate and customize the documentation fetching and generation process. Key options that can be configured include: + +- `username`: Git provider username (usually GitHub) to fetch repositories (equivalent to the `-U` CLI option). +- `reposFilter`: List of repositories to include or exclude (equivalent to the `-r` CLI option). +- `branch`: Default branch from which documentation will be fetched. +- `extraHeaderPages`: Additional pages to include in the website header (equivalent to `-p`). +- `extraPublicContent`: Additional content for the Vitepress public folder (equivalent to `-c`). +- `extraTheme`: Files or folders to customize the Vitepress theme (equivalent to `-t`). +- `forks`: Whether or not to create the forks page documentation `-f`). + +Example JSON configuration: + +```json +{ + "username": "my-github-username", + "reposFilter": ["!repo1", "!repo2"], + "branch": "main", + "extraHeaderPages": ["header1.md", "header2.md"], + "extraPublicContent": ["favicon.ico", "logo.png"], + "extraTheme": ["theme/index.md", "theme/custom-theme.css"], + "forks": false, + "vitepress": { + "title": "My Project Documentation", + "description": "A site generated by Docpress", + "themeConfig": { + "socialLinks": [ + { "icon": "github", "link": "https://github.com/this-is-tobi" } + ], + "outline": [2, 4] + } + } +} +``` + +## GitHub Token Option + +When using the Docpress tool to fetch data from GitHub, you have the option to provide a personal access token using the `-T` flag. This token is important as it allows you to authenticate your requests, which can help increase your request limits on the GitHub API. + +Utilizing a personal access token not only helps you avoid rate limiting issues but also enhances the reliability of data retrieval for your documentation projects. + +For further details on how authentication affects rate limits and other considerations, please refer to the official GitHub documentation on [Rate limits for the REST API](https://docs.github.com/en/rest/overview/rate-limits-for-the-rest-api). diff --git a/docs/04-development.md b/docs/04-development.md new file mode 100644 index 0000000..d46ce04 --- /dev/null +++ b/docs/04-development.md @@ -0,0 +1,50 @@ +# Development + +## Prerequisites + +To successfully run the Docpress application in development mode, you'll need to install the following prerequisites: + +- __[Node.js](https://nodejs.org/)__: A JavaScript runtime that allows you to execute JavaScript code server-side. Make sure you install a version that is compatible with the project requirements. +- __[pnpm](https://pnpm.io/)__: A fast, disk space-efficient package manager for Node.js that is used to manage the project's dependencies. + +## Setting Up the Development Environment + +Follow these steps to set up the development environment and start working with Docpress: + +1. __Clone the repository__ + Start by cloning the Docpress repository to your local machine using Git: + ```sh + git clone https://github.com/this-is-tobi/docpress.git + cd docpress + ``` +2. __Install project dependencies__ + Use pnpm to install all necessary Node.js dependencies: + ```sh + pnpm install + ``` +3. __Fetch and build the website__ + Run the following command to fetch documentation from the specified GitHub username and build the static website: + ```sh + pnpm run dev -U + ``` +4. __Preview the website__ + Launch a local development server to preview your website: + ```sh + pnpm run vp:dev + ``` + You can access the preview by navigating to [http://localhost:8080](http://localhost:8080) in your web browser. + +> [!TIP] +> Use command `pnpm run dev -h` to print options. + +## Contributions + +All contributions to my repositories are welcome and must be made via Github with a pull request following the rules below. + +### Conventions + +Commits must follow the specification of [Conventional Commits](https://www.conventionalcommits.org/en/v1.0.0/), it is possible to add the [VSCode extension](https://github.com/vivaxy/vscode-conventional-commits) to facilitate the creation of commits. + +A PR must be made with an updated branch with the `main` branch in rebase (and without merge) before requesting a merge, and the merge must be requested in `main`. + +Check whether the repository has linting rules or tests to keep them clean and ensure that CI workflows pass (new features in repositories with tests should be accompanied by new tests to ensure that the new feature works properly). diff --git a/package.json b/package.json index 7f712c9..d13b720 100644 --- a/package.json +++ b/package.json @@ -22,7 +22,8 @@ "build:docker": "docker build --tag tobi-or-not/docpress --target prod .", "build:types": "tsc", "build:vite": "vite build", - "dev": "rimraf ./docpress && vite-node ./dev/docpress.ts", + "dev": "vite-node ./dev/docpress.ts", + "dev:clean": "rimraf ./docpress && pnpm run dev", "format": "eslint . --fix", "lint": "eslint .", "prepare": "husky", @@ -38,30 +39,32 @@ "vp:preview": "vitepress preview ./docpress --port 8080" }, "dependencies": { + "@octokit/rest": "^21.0.2", "axios": "^1.7.7", "chalk": "^5.3.0", "commander": "^12.1.0", "octokit": "^4.0.2", "rimraf": "^6.0.1", "simple-git": "^3.27.0", - "vitepress": "^1.4.1", - "yaml": "^2.6.0", + "vitepress": "^1.5.0", + "yaml": "^2.6.1", "zod": "^3.23.8", "zod-validation-error": "^3.4.0" }, "devDependencies": { - "@antfu/eslint-config": "^3.8.0", - "@commitlint/cli": "^19.5.0", - "@commitlint/config-conventional": "^19.5.0", - "@types/node": "^22.7.7", - "@vitest/coverage-v8": "^2.1.3", - "eslint": "^9.13.0", - "husky": "^9.1.6", + "@antfu/eslint-config": "^3.9.2", + "@commitlint/cli": "^19.6.0", + "@commitlint/config-conventional": "^19.6.0", + "@octokit/types": "^13.6.1", + "@types/node": "^22.9.1", + "@vitest/coverage-v8": "^2.1.5", + "eslint": "^9.15.0", + "husky": "^9.1.7", "lint-staged": "^15.2.10", "typescript": "^5.6.3", - "vite": "^5.4.9", - "vite-node": "^2.1.3", - "vitest": "^2.1.3" + "vite": "^5.4.11", + "vite-node": "^2.1.5", + "vitest": "^2.1.5" }, "publishConfig": { "registry": "https://registry.npmjs.org/", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index cd18c6e..54f98f3 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -8,6 +8,9 @@ importers: .: dependencies: + '@octokit/rest': + specifier: ^21.0.2 + version: 21.0.2 axios: specifier: ^1.7.7 version: 1.7.7 @@ -27,11 +30,11 @@ importers: specifier: ^3.27.0 version: 3.27.0 vitepress: - specifier: ^1.4.1 - version: 1.4.1(@algolia/client-search@4.24.0)(@types/node@22.8.1)(axios@1.7.7)(postcss@8.4.47)(search-insights@2.17.2)(typescript@5.6.3) + specifier: ^1.5.0 + version: 1.5.0(@algolia/client-search@4.24.0)(@types/node@22.9.1)(axios@1.7.7)(postcss@8.4.47)(search-insights@2.17.2)(typescript@5.6.3) yaml: - specifier: ^2.6.0 - version: 2.6.0 + specifier: ^2.6.1 + version: 2.6.1 zod: specifier: ^3.23.8 version: 3.23.8 @@ -40,26 +43,29 @@ importers: version: 3.4.0(zod@3.23.8) devDependencies: '@antfu/eslint-config': - specifier: ^3.8.0 - version: 3.8.0(@typescript-eslint/utils@8.11.0(eslint@9.13.0(jiti@1.21.6))(typescript@5.6.3))(@vue/compiler-sfc@3.5.12)(eslint@9.13.0(jiti@1.21.6))(typescript@5.6.3)(vitest@2.1.3(@types/node@22.8.1)) + specifier: ^3.9.2 + version: 3.9.2(@typescript-eslint/utils@8.15.0(eslint@9.15.0(jiti@1.21.6))(typescript@5.6.3))(@vue/compiler-sfc@3.5.12)(eslint@9.15.0(jiti@1.21.6))(typescript@5.6.3)(vitest@2.1.5(@types/node@22.9.1)) '@commitlint/cli': - specifier: ^19.5.0 - version: 19.5.0(@types/node@22.8.1)(typescript@5.6.3) + specifier: ^19.6.0 + version: 19.6.0(@types/node@22.9.1)(typescript@5.6.3) '@commitlint/config-conventional': - specifier: ^19.5.0 - version: 19.5.0 + specifier: ^19.6.0 + version: 19.6.0 + '@octokit/types': + specifier: ^13.6.1 + version: 13.6.1 '@types/node': - specifier: ^22.7.7 - version: 22.8.1 + specifier: ^22.9.1 + version: 22.9.1 '@vitest/coverage-v8': - specifier: ^2.1.3 - version: 2.1.3(vitest@2.1.3(@types/node@22.8.1)) + specifier: ^2.1.5 + version: 2.1.5(vitest@2.1.5(@types/node@22.9.1)) eslint: - specifier: ^9.13.0 - version: 9.13.0(jiti@1.21.6) + specifier: ^9.15.0 + version: 9.15.0(jiti@1.21.6) husky: - specifier: ^9.1.6 - version: 9.1.6 + specifier: ^9.1.7 + version: 9.1.7 lint-staged: specifier: ^15.2.10 version: 15.2.10 @@ -67,14 +73,14 @@ importers: specifier: ^5.6.3 version: 5.6.3 vite: - specifier: ^5.4.9 - version: 5.4.10(@types/node@22.8.1) + specifier: ^5.4.11 + version: 5.4.11(@types/node@22.9.1) vite-node: - specifier: ^2.1.3 - version: 2.1.3(@types/node@22.8.1) + specifier: ^2.1.5 + version: 2.1.5(@types/node@22.9.1) vitest: - specifier: ^2.1.3 - version: 2.1.3(@types/node@22.8.1) + specifier: ^2.1.5 + version: 2.1.5(@types/node@22.9.1) packages: @@ -147,8 +153,8 @@ packages: resolution: {integrity: sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==} engines: {node: '>=6.0.0'} - '@antfu/eslint-config@3.8.0': - resolution: {integrity: sha512-O5QSufPHpKTm0wk1OQ5c2mOZVzCqYV3hIDrt5zt+cOWqiG8YXLPkSOD4fFwjomATtOuUbcLUwkcgY5dErM7aIw==} + '@antfu/eslint-config@3.9.2': + resolution: {integrity: sha512-a1I1CXmtQdTL9jxcb2RzKjuYYAzjdKK3ktVpQGd/1S/aUdhKgcEEi3DRXYgnB8xdpYLqracETxEMDf9PQlmyBg==} hasBin: true peerDependencies: '@eslint-react/eslint-plugin': ^1.5.8 @@ -226,18 +232,18 @@ packages: '@clack/core@0.3.4': resolution: {integrity: sha512-H4hxZDXgHtWTwV3RAVenqcC4VbJZNegbBjlPvzOzCouXtS2y3sDvlO3IsbrPNWuLWPPlYVYPghQdSF64683Ldw==} - '@clack/prompts@0.7.0': - resolution: {integrity: sha512-0MhX9/B4iL6Re04jPrttDm+BsP8y6mS7byuv0BvXgdXhbV5PdlsHt55dvNsuBCPZ7xq1oTAOOuotR9NFbQyMSA==} + '@clack/prompts@0.8.1': + resolution: {integrity: sha512-I263nEUNbX4lPTX93trl1fkIvGrGlz6nUYkqOddF0ZmjqcxUgUlXmpUIUqfapirRKJrFddvwF+qdZgg8cSqF7g==} bundledDependencies: - is-unicode-supported - '@commitlint/cli@19.5.0': - resolution: {integrity: sha512-gaGqSliGwB86MDmAAKAtV9SV1SHdmN8pnGq4EJU4+hLisQ7IFfx4jvU4s+pk6tl0+9bv6yT+CaZkufOinkSJIQ==} + '@commitlint/cli@19.6.0': + resolution: {integrity: sha512-v17BgGD9w5KnthaKxXnEg6KLq6DYiAxyiN44TpiRtqyW8NSq+Kx99mkEG8Qo6uu6cI5eMzMojW2muJxjmPnF8w==} engines: {node: '>=v18'} hasBin: true - '@commitlint/config-conventional@19.5.0': - resolution: {integrity: sha512-OBhdtJyHNPryZKg0fFpZNOBM1ZDbntMvqMuSmpfyP86XSfwzGw4CaoYRG4RutUPg0BTK07VMRIkNJT6wi2zthg==} + '@commitlint/config-conventional@19.6.0': + resolution: {integrity: sha512-DJT40iMnTYtBtUfw9ApbsLZFke1zKh6llITVJ+x9mtpHD08gsNXaIRqHTmwTZL3dNX5+WoyK7pCN/5zswvkBCQ==} engines: {node: '>=v18'} '@commitlint/config-validator@19.5.0': @@ -256,12 +262,12 @@ packages: resolution: {integrity: sha512-yNy088miE52stCI3dhG/vvxFo9e4jFkU1Mj3xECfzp/bIS/JUay4491huAlVcffOoMK1cd296q0W92NlER6r3A==} engines: {node: '>=v18'} - '@commitlint/is-ignored@19.5.0': - resolution: {integrity: sha512-0XQ7Llsf9iL/ANtwyZ6G0NGp5Y3EQ8eDQSxv/SRcfJ0awlBY4tHFAvwWbw66FVUaWICH7iE5en+FD9TQsokZ5w==} + '@commitlint/is-ignored@19.6.0': + resolution: {integrity: sha512-Ov6iBgxJQFR9koOupDPHvcHU9keFupDgtB3lObdEZDroiG4jj1rzky60fbQozFKVYRTUdrBGICHG0YVmRuAJmw==} engines: {node: '>=v18'} - '@commitlint/lint@19.5.0': - resolution: {integrity: sha512-cAAQwJcRtiBxQWO0eprrAbOurtJz8U6MgYqLz+p9kLElirzSCc0vGMcyCaA1O7AqBuxo11l1XsY3FhOFowLAAg==} + '@commitlint/lint@19.6.0': + resolution: {integrity: sha512-LRo7zDkXtcIrpco9RnfhOKeg8PAnE3oDDoalnrVU/EVaKHYBWYL1DlRR7+3AWn0JiBqD8yKOfetVxJGdEtZ0tg==} engines: {node: '>=v18'} '@commitlint/load@19.5.0': @@ -284,8 +290,8 @@ packages: resolution: {integrity: sha512-CU/GscZhCUsJwcKTJS9Ndh3AKGZTNFIOoQB2n8CmFnizE0VnEuJoum+COW+C1lNABEeqk6ssfc1Kkalm4bDklA==} engines: {node: '>=v18'} - '@commitlint/rules@19.5.0': - resolution: {integrity: sha512-hDW5TPyf/h1/EufSHEKSp6Hs+YVsDMHazfJ2azIk9tHPXS6UqSz1dIRs1gpqS3eMXgtkT7JH6TW4IShdqOwhAw==} + '@commitlint/rules@19.6.0': + resolution: {integrity: sha512-1f2reW7lbrI0X0ozZMesS/WZxgPa4/wi56vFuJENBmed6mWq5KsheN/nxqnl/C23ioxpPO/PL6tXpiiFy5Bhjw==} engines: {node: '>=v18'} '@commitlint/to-lines@19.5.0': @@ -485,6 +491,10 @@ packages: resolution: {integrity: sha512-2WwyTYNVaMNUWPZTOJdkax9iqTdirrApgTbk+Qoq5EPX6myqZvG8QGFRgdKmkjKVG6/G/a565vpPauHk0+hpBA==} engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0} + '@eslint-community/regexpp@4.12.1': + resolution: {integrity: sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ==} + engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0} + '@eslint/compat@1.2.1': resolution: {integrity: sha512-JbHG2TWuCeNzh87fXo+/46Z1LEo9DBA9T188d0fZgGxAD+cNyS6sx9fdiyxjGPBMyQVRlCutTByZ6a5+YMkF7g==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} @@ -494,20 +504,20 @@ packages: eslint: optional: true - '@eslint/config-array@0.18.0': - resolution: {integrity: sha512-fTxvnS1sRMu3+JjXwJG0j/i4RT9u4qJ+lqS/yCGap4lH4zZGzQ7tu+xZqQmcMZq5OBZDL4QRxQzRjkWcGt8IVw==} + '@eslint/config-array@0.19.0': + resolution: {integrity: sha512-zdHg2FPIFNKPdcHWtiNT+jEFCHYVplAXRDlQDyqy0zGx/q2parwh7brGJSiTxRk/TSMkbM//zt/f5CHgyTyaSQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@eslint/core@0.7.0': - resolution: {integrity: sha512-xp5Jirz5DyPYlPiKat8jaq0EmYvDXKKpzTbxXMpT9eqlRJkRKIz9AGMdlvYjih+im+QlhWrpvVjl8IPC/lHlUw==} + '@eslint/core@0.9.0': + resolution: {integrity: sha512-7ATR9F0e4W85D/0w7cU0SNj7qkAexMG+bAHEZOjo9akvGuhHE2m7umzWzfnpa0XAg5Kxc1BWmtPMV67jJ+9VUg==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@eslint/eslintrc@3.1.0': - resolution: {integrity: sha512-4Bfj15dVJdoy3RfZmmo86RK1Fwzn6SstsvK9JS+BaVKqC6QQQQyXekNaC+g+LKNgkQ+2VhGAzm6hO40AhMR3zQ==} + '@eslint/eslintrc@3.2.0': + resolution: {integrity: sha512-grOjVNN8P3hjJn/eIETF1wwd12DdnwFDoyceUJLYYdkpbwq3nLi+4fqrTAONx7XDALqlL220wC/RHSC/QTI/0w==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@eslint/js@9.13.0': - resolution: {integrity: sha512-IFLyoY4d72Z5y/6o/BazFBezupzI/taV8sGumxTAVw3lXG9A6md1Dc34T9s1FoD/an9pJH8RHbAxsaEbBed9lA==} + '@eslint/js@9.15.0': + resolution: {integrity: sha512-tMTqrY+EzbXmKJR5ToI8lxu7jaN5EdmrBFJpQk5JmSlyLsx6o4t27r883K5xsLuCYCpfKBCGswMSWXsM+jB7lg==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} '@eslint/markdown@6.2.1': @@ -522,12 +532,16 @@ packages: resolution: {integrity: sha512-HFZ4Mp26nbWk9d/BpvP0YNL6W4UoZF0VFcTw/aPPA8RpOxeFQgK+ClABGgAUXs9Y/RGX/l1vOmrqz1MQt9MNuw==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@humanfs/core@0.19.0': - resolution: {integrity: sha512-2cbWIHbZVEweE853g8jymffCA+NCMiuqeECeBBLm8dg2oFdjuGJhgN4UAbI+6v0CKbbhvtXA4qV8YR5Ji86nmw==} + '@eslint/plugin-kit@0.2.3': + resolution: {integrity: sha512-2b/g5hRmpbb1o4GnTZax9N9m0FXzz9OV42ZzI4rDDMDuHUqigAiQCEWChBWCY4ztAGVRjoWT19v0yMmc5/L5kA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@humanfs/core@0.19.1': + resolution: {integrity: sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==} engines: {node: '>=18.18.0'} - '@humanfs/node@0.16.5': - resolution: {integrity: sha512-KSPA4umqSG4LHYRodq31VDwKAvaTF4xmVlzM8Aeh4PlU1JQ3IG0wiA8C25d3RQ9nJyM3mBHyI53K06VVL/oFFg==} + '@humanfs/node@0.16.6': + resolution: {integrity: sha512-YuI2ZHQL78Q5HbhDiBA1X4LmYdXCKCMQIfw0pw7piHJwyREFebJUvrQN4cMssyES6x+vfUbx1CIpaQUKYdQZOw==} engines: {node: '>=18.18.0'} '@humanwhocodes/module-importer@1.0.1': @@ -538,6 +552,16 @@ packages: resolution: {integrity: sha512-JBxkERygn7Bv/GbN5Rv8Ul6LVknS+5Bp6RgDC/O8gEBU/yeH5Ui5C/OlWrTb6qct7LjjfT6Re2NxB0ln0yYybA==} engines: {node: '>=18.18'} + '@humanwhocodes/retry@0.4.1': + resolution: {integrity: sha512-c7hNEllBlenFTHBky65mhq8WD2kbN9Q6gk0bTk8lSBvc554jpXSkST1iePudpt7+A/AQvuHs9EMqjHDXMY1lrA==} + engines: {node: '>=18.18'} + + '@iconify-json/simple-icons@1.2.12': + resolution: {integrity: sha512-lRNORrIdeLStShxAjN6FgXE1iMkaAgiAHZdP0P0GZecX91FVYW58uZnRSlXLlSx5cxMoELulkAAixybPA2g52g==} + + '@iconify/types@2.0.0': + resolution: {integrity: sha512-+wluvCrRhXrhyOmRDJ3q8mux9JkKy5SJ/v8ol2tu4FVjyYvtEzkc/3pK15ET6RKg4b4w4BmTk1+gsCUhf21Ykg==} + '@isaacs/cliui@8.0.2': resolution: {integrity: sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==} engines: {node: '>=12'} @@ -652,6 +676,12 @@ packages: peerDependencies: '@octokit/core': '>=6' + '@octokit/plugin-request-log@5.3.1': + resolution: {integrity: sha512-n/lNeCtq+9ofhC15xzmJCNKP2BWTv8Ih2TTy+jatNCCq/gQP/V7rK3fjIfuz0pDWDALO/o/4QY4hyOF6TQQFUw==} + engines: {node: '>= 18'} + peerDependencies: + '@octokit/core': '>=6' + '@octokit/plugin-rest-endpoint-methods@13.2.6': resolution: {integrity: sha512-wMsdyHMjSfKjGINkdGKki06VEkgdEldIGstIEyGX0wbYHGByOwN/KiM+hAAlUwAtPkP3gvXtVQA9L3ITdV2tVw==} engines: {node: '>= 18'} @@ -678,6 +708,10 @@ packages: resolution: {integrity: sha512-V+TFhu5fdF3K58rs1pGUJIDH5RZLbZm5BI+MNF+6o/ssFNT4vWlCh/tVpF3NxGtP15HUxTTMUbsG5llAuU2CZA==} engines: {node: '>= 18'} + '@octokit/rest@21.0.2': + resolution: {integrity: sha512-+CiLisCoyWmYicH25y1cDfCrv41kRSvTq6pPWtRroRJzhsCZWZyCqGyI8foJT5LmScADSwRAnr/xo+eewL04wQ==} + engines: {node: '>= 18'} + '@octokit/types@13.6.1': resolution: {integrity: sha512-PHZE9Z+kWXb23Ndik8MKPirBPziOc0D2/3KH1P+6jK5nGWe96kadZuE4jev2/Jq7FvIfTlT2Ltg8Fv2x1v0a5g==} @@ -777,26 +811,26 @@ packages: cpu: [x64] os: [win32] - '@shikijs/core@1.22.1': - resolution: {integrity: sha512-bqAhT/Ri5ixV4oYsvJNH8UJjpjbINWlWyXY6tBTsP4OmD6XnFv43nRJ+lTdxd2rmG5pgam/x+zGR6kLRXrpEKA==} + '@shikijs/core@1.23.1': + resolution: {integrity: sha512-NuOVgwcHgVC6jBVH5V7iblziw6iQbWWHrj5IlZI3Fqu2yx9awH7OIQkXIcsHsUmY19ckwSgUMgrqExEyP5A0TA==} - '@shikijs/engine-javascript@1.22.1': - resolution: {integrity: sha512-540pyoy0LWe4jj2BVbgELwOFu1uFvRI7lg4hdsExrSXA9x7gqfzZ/Nnh4RfX86aDAgJ647gx4TCmRwACbnQSvw==} + '@shikijs/engine-javascript@1.23.1': + resolution: {integrity: sha512-i/LdEwT5k3FVu07SiApRFwRcSJs5QM9+tod5vYCPig1Ywi8GR30zcujbxGQFJHwYD7A5BUqagi8o5KS+LEVgBg==} - '@shikijs/engine-oniguruma@1.22.1': - resolution: {integrity: sha512-L+1Vmd+a2kk8HtogUFymQS6BjUfJnzcWoUp1BUgxoDiklbKSMvrsMuLZGevTOP1m0rEjgnC5MsDmsr8lX1lC+Q==} + '@shikijs/engine-oniguruma@1.23.1': + resolution: {integrity: sha512-KQ+lgeJJ5m2ISbUZudLR1qHeH3MnSs2mjFg7bnencgs5jDVPeJ2NVDJ3N5ZHbcTsOIh0qIueyAJnwg7lg7kwXQ==} - '@shikijs/transformers@1.22.1': - resolution: {integrity: sha512-KvG49YFV6gV116sC4L3Sn1Rp6HXsioMKBBG373j088rw849440hm8s2r+/dgjsGLvT4p+QB7newev+5a3ARM6w==} + '@shikijs/transformers@1.23.1': + resolution: {integrity: sha512-yQ2Cn0M9i46p30KwbyIzLvKDk+dQNU+lj88RGO0XEj54Hn4Cof1bZoDb9xBRWxFE4R8nmK63w7oHnJwvOtt0NQ==} - '@shikijs/types@1.22.1': - resolution: {integrity: sha512-+45f8mu/Hxqs6Kyhfm98Nld5n7Q7lwhjU8UtdQwrOPs7BnM4VAb929O3IQ2ce+4D7SlNFlZGd8CnKRSnwbQreQ==} + '@shikijs/types@1.23.1': + resolution: {integrity: sha512-98A5hGyEhzzAgQh2dAeHKrWW4HfCMeoFER2z16p5eJ+vmPeF6lZ/elEne6/UCU551F/WqkopqRsr1l2Yu6+A0g==} '@shikijs/vscode-textmate@9.3.0': resolution: {integrity: sha512-jn7/7ky30idSkd/O5yDBfAnVt+JJpepofP/POZ1iMOxK59cOfqIgg/Dj0eFsjOTMw+4ycJN0uhZH/Eb0bs/EUA==} - '@stylistic/eslint-plugin@2.9.0': - resolution: {integrity: sha512-OrDyFAYjBT61122MIY1a3SfEgy3YCMgt2vL4eoPmvTwDBwyQhAXurxNQznlRD/jESNfYWfID8Ej+31LljvF7Xg==} + '@stylistic/eslint-plugin@2.11.0': + resolution: {integrity: sha512-PNRHbydNG5EH8NK4c+izdJlxajIR6GxcUhzsYNRsn6Myep4dsZt0qFCz3rCPnkvgO5FYibDcMqgNHUT+zvjYZw==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: eslint: '>=8.40.0' @@ -834,8 +868,8 @@ packages: '@types/ms@0.7.34': resolution: {integrity: sha512-nG96G3Wp6acyAgJqGasjODb+acrI7KltPiRxzHPXnP3NgI28bpQDRv53olbqGXbfcgF5aiiHmO3xpwEpS5Ld9g==} - '@types/node@22.8.1': - resolution: {integrity: sha512-k6Gi8Yyo8EtrNtkHXutUu2corfDf9su95VYVP10aGYMMROM6SAItZi0w1XszA6RtWTHSVp5OeFof37w0IEqCQg==} + '@types/node@22.9.1': + resolution: {integrity: sha512-p8Yy/8sw1caA8CdRIQBG5tiLHmxtQKObCijiAa9Ez+d4+PRffM4054xbju0msf+cvhJpnFEeNjxmVT/0ipktrg==} '@types/normalize-package-data@2.4.4': resolution: {integrity: sha512-37i+OaWTh9qeK4LSHPsyRC7NahnGotNuZvjLSgcPzblpHB3rrCJxAOgI5gCdKm7coonsaX1Of0ILiTcnZjbfxA==} @@ -846,8 +880,8 @@ packages: '@types/web-bluetooth@0.0.20': resolution: {integrity: sha512-g9gZnnXVq7gM7v3tJCWV/qw7w+KeOlSHAhgF9RytFyifW6AF61hdT2ucrYhPq9hLs5JIryeupHV3qGk95dH9ow==} - '@typescript-eslint/eslint-plugin@8.11.0': - resolution: {integrity: sha512-KhGn2LjW1PJT2A/GfDpiyOfS4a8xHQv2myUagTM5+zsormOmBlYsnQ6pobJ8XxJmh6hnHwa2Mbe3fPrDJoDhbA==} + '@typescript-eslint/eslint-plugin@8.15.0': + resolution: {integrity: sha512-+zkm9AR1Ds9uLWN3fkoeXgFppaQ+uEVtfOV62dDmsy9QCNqlRHWNEck4yarvRNrvRcHQLGfqBNui3cimoz8XAg==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: '@typescript-eslint/parser': ^8.0.0 || ^8.0.0-alpha.0 @@ -857,8 +891,8 @@ packages: typescript: optional: true - '@typescript-eslint/parser@8.11.0': - resolution: {integrity: sha512-lmt73NeHdy1Q/2ul295Qy3uninSqi6wQI18XwSpm8w0ZbQXUpjCAWP1Vlv/obudoBiIjJVjlztjQ+d/Md98Yxg==} + '@typescript-eslint/parser@8.15.0': + resolution: {integrity: sha512-7n59qFpghG4uazrF9qtGKBZXn7Oz4sOMm8dwNWDQY96Xlm2oX67eipqcblDj+oY1lLCbf1oltMZFpUso66Kl1A==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: eslint: ^8.57.0 || ^9.0.0 @@ -871,10 +905,15 @@ packages: resolution: {integrity: sha512-Uholz7tWhXmA4r6epo+vaeV7yjdKy5QFCERMjs1kMVsLRKIrSdM6o21W2He9ftp5PP6aWOVpD5zvrvuHZC0bMQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@typescript-eslint/type-utils@8.11.0': - resolution: {integrity: sha512-ItiMfJS6pQU0NIKAaybBKkuVzo6IdnAhPFZA/2Mba/uBjuPQPet/8+zh5GtLHwmuFRShZx+8lhIs7/QeDHflOg==} + '@typescript-eslint/scope-manager@8.15.0': + resolution: {integrity: sha512-QRGy8ADi4J7ii95xz4UoiymmmMd/zuy9azCaamnZ3FM8T5fZcex8UfJcjkiEZjJSztKfEBe3dZ5T/5RHAmw2mA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@typescript-eslint/type-utils@8.15.0': + resolution: {integrity: sha512-UU6uwXDoI3JGSXmcdnP5d8Fffa2KayOhUUqr/AiBnG1Gl7+7ut/oyagVeSkh7bxQ0zSXV9ptRh/4N15nkCqnpw==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: + eslint: ^8.57.0 || ^9.0.0 typescript: '*' peerDependenciesMeta: typescript: @@ -884,6 +923,10 @@ packages: resolution: {integrity: sha512-tn6sNMHf6EBAYMvmPUaKaVeYvhUsrE6x+bXQTxjQRp360h1giATU0WvgeEys1spbvb5R+VpNOZ+XJmjD8wOUHw==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + '@typescript-eslint/types@8.15.0': + resolution: {integrity: sha512-n3Gt8Y/KyJNe0S3yDCD2RVKrHBC4gTUcLTebVBXacPy091E6tNspFLKRXlk3hwT4G55nfr1n2AdFqi/XMxzmPQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + '@typescript-eslint/typescript-estree@8.11.0': resolution: {integrity: sha512-yHC3s1z1RCHoCz5t06gf7jH24rr3vns08XXhfEqzYpd6Hll3z/3g23JRi0jM8A47UFKNc3u/y5KIMx8Ynbjohg==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} @@ -893,16 +936,39 @@ packages: typescript: optional: true + '@typescript-eslint/typescript-estree@8.15.0': + resolution: {integrity: sha512-1eMp2JgNec/niZsR7ioFBlsh/Fk0oJbhaqO0jRyQBMgkz7RrFfkqF9lYYmBoGBaSiLnu8TAPQTwoTUiSTUW9dg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + typescript: '*' + peerDependenciesMeta: + typescript: + optional: true + '@typescript-eslint/utils@8.11.0': resolution: {integrity: sha512-CYiX6WZcbXNJV7UNB4PLDIBtSdRmRI/nb0FMyqHPTQD1rMjA0foPLaPUV39C/MxkTd/QKSeX+Gb34PPsDVC35g==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: eslint: ^8.57.0 || ^9.0.0 + '@typescript-eslint/utils@8.15.0': + resolution: {integrity: sha512-k82RI9yGhr0QM3Dnq+egEpz9qB6Un+WLYhmoNcvl8ltMEededhh7otBVVIDDsEEttauwdY/hQoSsOv13lxrFzQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 + typescript: '*' + peerDependenciesMeta: + typescript: + optional: true + '@typescript-eslint/visitor-keys@8.11.0': resolution: {integrity: sha512-EaewX6lxSjRJnc+99+dqzTeoDZUfyrA52d2/HRrkI830kgovWsmIiTfmr0NZorzqic7ga+1bS60lRBUgR3n/Bw==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + '@typescript-eslint/visitor-keys@8.15.0': + resolution: {integrity: sha512-h8vYOulWec9LhpwfAdZf2bjr8xIp0KNKnpgqSz0qqYYKAW/QZKw3ktRndbiAtUz4acH4QLQavwZBYCc0wulA/Q==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + '@ungap/structured-clone@1.2.0': resolution: {integrity: sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==} @@ -913,17 +979,17 @@ packages: vite: ^5.0.0 vue: ^3.2.25 - '@vitest/coverage-v8@2.1.3': - resolution: {integrity: sha512-2OJ3c7UPoFSmBZwqD2VEkUw6A/tzPF0LmW0ZZhhB8PFxuc+9IBG/FaSM+RLEenc7ljzFvGN+G0nGQoZnh7sy2A==} + '@vitest/coverage-v8@2.1.5': + resolution: {integrity: sha512-/RoopB7XGW7UEkUndRXF87A9CwkoZAJW01pj8/3pgmDVsjMH2IKy6H1A38po9tmUlwhSyYs0az82rbKd9Yaynw==} peerDependencies: - '@vitest/browser': 2.1.3 - vitest: 2.1.3 + '@vitest/browser': 2.1.5 + vitest: 2.1.5 peerDependenciesMeta: '@vitest/browser': optional: true - '@vitest/eslint-plugin@1.1.7': - resolution: {integrity: sha512-pTWGW3y6lH2ukCuuffpan6kFxG6nIuoesbhMiQxskyQMRcCN5t9SXsKrNHvEw3p8wcCsgJoRqFZVkOTn6TjclA==} + '@vitest/eslint-plugin@1.1.10': + resolution: {integrity: sha512-uScH5Kz5v32vvtQYB2iodpoPg2mGASK+VKpjlc2IUgE0+16uZKqVKi2vQxjxJ6sMCQLBs4xhBFZlmZBszsmfKQ==} peerDependencies: '@typescript-eslint/utils': '>= 8.0' eslint: '>= 8.57.0' @@ -935,14 +1001,13 @@ packages: vitest: optional: true - '@vitest/expect@2.1.3': - resolution: {integrity: sha512-SNBoPubeCJhZ48agjXruCI57DvxcsivVDdWz+SSsmjTT4QN/DfHk3zB/xKsJqMs26bLZ/pNRLnCf0j679i0uWQ==} + '@vitest/expect@2.1.5': + resolution: {integrity: sha512-nZSBTW1XIdpZvEJyoP/Sy8fUg0b8od7ZpGDkTUcfJ7wz/VoZAFzFfLyxVxGFhUjJzhYqSbIpfMtl/+k/dpWa3Q==} - '@vitest/mocker@2.1.3': - resolution: {integrity: sha512-eSpdY/eJDuOvuTA3ASzCjdithHa+GIF1L4PqtEELl6Qa3XafdMLBpBlZCIUCX2J+Q6sNmjmxtosAG62fK4BlqQ==} + '@vitest/mocker@2.1.5': + resolution: {integrity: sha512-XYW6l3UuBmitWqSUXTNXcVBUCRytDogBsWuNXQijc00dtnU/9OqpXWp4OJroVrad/gLIomAq9aW8yWDBtMthhQ==} peerDependencies: - '@vitest/spy': 2.1.3 - msw: ^2.3.5 + msw: ^2.4.9 vite: ^5.0.0 peerDependenciesMeta: msw: @@ -950,20 +1015,20 @@ packages: vite: optional: true - '@vitest/pretty-format@2.1.3': - resolution: {integrity: sha512-XH1XdtoLZCpqV59KRbPrIhFCOO0hErxrQCMcvnQete3Vibb9UeIOX02uFPfVn3Z9ZXsq78etlfyhnkmIZSzIwQ==} + '@vitest/pretty-format@2.1.5': + resolution: {integrity: sha512-4ZOwtk2bqG5Y6xRGHcveZVr+6txkH7M2e+nPFd6guSoN638v/1XQ0K06eOpi0ptVU/2tW/pIU4IoPotY/GZ9fw==} - '@vitest/runner@2.1.3': - resolution: {integrity: sha512-JGzpWqmFJ4fq5ZKHtVO3Xuy1iF2rHGV4d/pdzgkYHm1+gOzNZtqjvyiaDGJytRyMU54qkxpNzCx+PErzJ1/JqQ==} + '@vitest/runner@2.1.5': + resolution: {integrity: sha512-pKHKy3uaUdh7X6p1pxOkgkVAFW7r2I818vHDthYLvUyjRfkKOU6P45PztOch4DZarWQne+VOaIMwA/erSSpB9g==} - '@vitest/snapshot@2.1.3': - resolution: {integrity: sha512-qWC2mWc7VAXmjAkEKxrScWHWFyCQx/cmiZtuGqMi+WwqQJ2iURsVY4ZfAK6dVo6K2smKRU6l3BPwqEBvhnpQGg==} + '@vitest/snapshot@2.1.5': + resolution: {integrity: sha512-zmYw47mhfdfnYbuhkQvkkzYroXUumrwWDGlMjpdUr4jBd3HZiV2w7CQHj+z7AAS4VOtWxI4Zt4bWt4/sKcoIjg==} - '@vitest/spy@2.1.3': - resolution: {integrity: sha512-Nb2UzbcUswzeSP7JksMDaqsI43Sj5+Kry6ry6jQJT4b5gAK+NS9NED6mDb8FlMRCX8m5guaHCDZmqYMMWRy5nQ==} + '@vitest/spy@2.1.5': + resolution: {integrity: sha512-aWZF3P0r3w6DiYTVskOYuhBc7EMc3jvn1TkBg8ttylFFRqNN2XGD7V5a4aQdk6QiUzZQ4klNBSpCLJgWNdIiNw==} - '@vitest/utils@2.1.3': - resolution: {integrity: sha512-xpiVfDSg1RrYT0tX6czgerkpcKFmFOF/gCr30+Mve5V2kewCy4Prn1/NDMSRwaSmT7PRaOF83wu+bEtsY1wrvA==} + '@vitest/utils@2.1.5': + resolution: {integrity: sha512-yfj6Yrp0Vesw2cwJbP+cl04OC+IHFsuQsrsJBL9pyGeQXE56v1UAOQco+SR55Vf1nQzfV0QJg1Qum7AaWUwwYg==} '@vue/compiler-core@3.5.12': resolution: {integrity: sha512-ISyBTRMmMYagUxhcpyEH0hpXRd/KqDU4ymofPgl2XAkY9ZhQ+h0ovEZJIiPop13UmR/54oA2cgMDjgroRelaEw==} @@ -1067,6 +1132,11 @@ packages: engines: {node: '>=0.4.0'} hasBin: true + acorn@8.14.0: + resolution: {integrity: sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA==} + engines: {node: '>=0.4.0'} + hasBin: true + ajv@6.12.6: resolution: {integrity: sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==} @@ -1284,6 +1354,10 @@ packages: resolution: {integrity: sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==} engines: {node: '>= 8'} + cross-spawn@7.0.6: + resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==} + engines: {node: '>= 8'} + cssesc@3.0.0: resolution: {integrity: sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==} engines: {node: '>=4'} @@ -1348,6 +1422,9 @@ packages: electron-to-chromium@1.5.46: resolution: {integrity: sha512-1XDk0Z8/YRgB2t5GeEg8DPK592DLjVmd/5uwAu6c/S4Z0CUwV/RwYqe5GWxQqcoN3bJ5U7hYMiMRPZzpCzSBhQ==} + emoji-regex-xs@1.0.0: + resolution: {integrity: sha512-LRlerrMYoIDrT6jgpeZ2YYl/L8EulRTt5hQcYjy5AInh7HWXKimpqx68aknBFpGL2+/IcogTcaydJEgaTmOpDg==} + emoji-regex@10.4.0: resolution: {integrity: sha512-EC+0oUMY1Rqm4O6LLrgjtYDvcVYTy7chDnM4Q7030tP4Kwj3u/pR6gP9ygnp2CJMK5Gq+9Q2oqmrFJAz01DXjw==} @@ -1406,6 +1483,12 @@ packages: peerDependencies: eslint: '>=6.0.0' + eslint-compat-utils@0.6.3: + resolution: {integrity: sha512-9IDdksh5pUYP2ZLi7mOdROxVjLY8gY2qKxprmrJ/5Dyqud7M/IFKxF3o0VLlRhITm1pK6Fk7NiBxE39M/VlUcw==} + engines: {node: '>=12'} + peerDependencies: + eslint: '>=6.0.0' + eslint-config-flat-gitignore@0.3.0: resolution: {integrity: sha512-0Ndxo4qGhcewjTzw52TK06Mc00aDtHNTdeeW2JfONgDcLkRO/n/BteMRzNVpLQYxdCC/dFEilfM9fjjpGIJ9Og==} peerDependencies: @@ -1417,6 +1500,17 @@ packages: eslint-import-resolver-node@0.3.9: resolution: {integrity: sha512-WFj2isz22JahUv+B788TlO3N6zL3nNJGU8CcZbPZvVEkBPaJdCV4vy5wyghty5ROFbCRnm132v8BScu5/1BQ8g==} + eslint-json-compat-utils@0.2.1: + resolution: {integrity: sha512-YzEodbDyW8DX8bImKhAcCeu/L31Dd/70Bidx2Qex9OFUtgzXLqtfWL4Hr5fM/aCCB8QUZLuJur0S9k6UfgFkfg==} + engines: {node: '>=12'} + peerDependencies: + '@eslint/json': '*' + eslint: '*' + jsonc-eslint-parser: ^2.4.0 + peerDependenciesMeta: + '@eslint/json': + optional: true + eslint-merge-processors@0.1.0: resolution: {integrity: sha512-IvRXXtEajLeyssvW4wJcZ2etxkR9mUf4zpNwgI+m/Uac9RfXHskuJefkHUcawVzePnd6xp24enp5jfgdHzjRdQ==} peerDependencies: @@ -1438,26 +1532,26 @@ packages: peerDependencies: eslint: '>=8' - eslint-plugin-import-x@4.3.1: - resolution: {integrity: sha512-5TriWkXulDl486XnYYRgsL+VQoS/7mhN/2ci02iLCuL7gdhbiWxnsuL/NTcaKY9fpMgsMFjWZBtIGW7pb+RX0g==} + eslint-plugin-import-x@4.4.3: + resolution: {integrity: sha512-QBprHvhLsfDhP++2T1NnjsOUt6bLDX3NMHaYwAB1FD3xmYTkdFH+HS1OamGhz28jLkRyIZa6UNAzTxbHnJwz5w==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: eslint: ^8.57.0 || ^9.0.0 - eslint-plugin-jsdoc@50.4.3: - resolution: {integrity: sha512-uWtwFxGRv6B8sU63HZM5dAGDhgsatb+LONwmILZJhdRALLOkCX2HFZhdL/Kw2ls8SQMAVEfK+LmnEfxInRN8HA==} + eslint-plugin-jsdoc@50.5.0: + resolution: {integrity: sha512-xTkshfZrUbiSHXBwZ/9d5ulZ2OcHXxSvm/NPo494H/hadLRJwOq5PMV0EUpMqsb9V+kQo+9BAgi6Z7aJtdBp2A==} engines: {node: '>=18'} peerDependencies: eslint: ^7.0.0 || ^8.0.0 || ^9.0.0 - eslint-plugin-jsonc@2.16.0: - resolution: {integrity: sha512-Af/ZL5mgfb8FFNleH6KlO4/VdmDuTqmM+SPnWcdoWywTetv7kq+vQe99UyQb9XO3b0OWLVuTH7H0d/PXYCMdSg==} + eslint-plugin-jsonc@2.18.2: + resolution: {integrity: sha512-SDhJiSsWt3nItl/UuIv+ti4g3m4gpGkmnUJS9UWR3TrpyNsIcnJoBRD7Kof6cM4Rk3L0wrmY5Tm3z7ZPjR2uGg==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} peerDependencies: eslint: '>=6.0.0' - eslint-plugin-n@17.11.1: - resolution: {integrity: sha512-93IUD82N6tIEgjztVI/l3ElHtC2wTa9boJHrD8iN+NyDxjxz/daZUZKfkedjBZNdg6EqDk4irybUsiPwDqXAEA==} + eslint-plugin-n@17.13.2: + resolution: {integrity: sha512-MhBAKkT01h8cOXcTBTlpuR7bxH5OBUNpUXefsvwSVEy46cY4m/Kzr2osUCQvA3zJFD6KuCeNNDv0+HDuWk/OcA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: eslint: '>=8.23.0' @@ -1485,8 +1579,8 @@ packages: vue-eslint-parser: optional: true - eslint-plugin-regexp@2.6.0: - resolution: {integrity: sha512-FCL851+kislsTEQEMioAlpDuK5+E5vs0hi1bF8cFlPlHcEjeRhuAzEsGikXRreE+0j4WhW2uO54MqTjXtYOi3A==} + eslint-plugin-regexp@2.7.0: + resolution: {integrity: sha512-U8oZI77SBtH8U3ulZ05iu0qEzIizyEDXd+BWHvyVxTOjGwcDcvy/kEpgFG4DYca2ByRLiVPFZ2GeH7j1pdvZTA==} engines: {node: ^18 || >=20} peerDependencies: eslint: '>=8.44.0' @@ -1512,14 +1606,14 @@ packages: '@typescript-eslint/eslint-plugin': optional: true - eslint-plugin-vue@9.29.1: - resolution: {integrity: sha512-MH/MbVae4HV/tM8gKAVWMPJbYgW04CK7SuzYRrlNERpxbO0P3+Zdsa2oAcFBW6xNu7W6lIkGOsFAMCRTYmrlWQ==} + eslint-plugin-vue@9.31.0: + resolution: {integrity: sha512-aYMUCgivhz1o4tLkRHj5oq9YgYPM4/EJc0M7TAKRLCUA5OYxRLAhYEVD2nLtTwLyixEFI+/QXSvKU9ESZFgqjQ==} engines: {node: ^14.17.0 || >=16.0.0} peerDependencies: eslint: ^6.2.0 || ^7.0.0 || ^8.0.0 || ^9.0.0 - eslint-plugin-yml@1.14.0: - resolution: {integrity: sha512-ESUpgYPOcAYQO9czugcX5OqRvn/ydDVwGCPXY4YjPqc09rHaUVUA6IE6HLQys4rXk/S+qx3EwTd1wHCwam/OWQ==} + eslint-plugin-yml@1.15.0: + resolution: {integrity: sha512-leC8APYVOsKyWUlvRwVhewytK5wS70BfMqIaUplFstRfzCoVp0YoEroV4cUEvQrBj93tQ3M9LcjO/ewr6D4kjA==} engines: {node: ^14.17.0 || >=16.0.0} peerDependencies: eslint: '>=6.0.0' @@ -1534,8 +1628,8 @@ packages: resolution: {integrity: sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} - eslint-scope@8.1.0: - resolution: {integrity: sha512-14dSvlhaVhKKsa9Fx1l8A17s7ah7Ef7wCakJ10LYk6+GYmP9yDti2oq2SEwcyndt6knfcZyhyxwY3i9yL78EQw==} + eslint-scope@8.2.0: + resolution: {integrity: sha512-PHlWUfG6lvPc3yvP5A4PNyBL1W8fkDUccmI21JUu/+GKZBoH/W5u6usENXUrWFRsyoW5ACUjFGgAFQp5gUlb/A==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} eslint-visitor-keys@3.4.3: @@ -1546,8 +1640,12 @@ packages: resolution: {integrity: sha512-Q7lok0mqMUSf5a/AdAZkA5a/gHcO6snwQClVNNvFKCAVlxXucdU8pKydU5ZVZjBx5xr37vGbFFWtLQYreLzrZg==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - eslint@9.13.0: - resolution: {integrity: sha512-EYZK6SX6zjFHST/HRytOdA/zE72Cq/bfw45LSyuwrdvcclb/gqV8RRQxywOBEWO2+WDpva6UZa4CcDeJKzUCFA==} + eslint-visitor-keys@4.2.0: + resolution: {integrity: sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + eslint@9.15.0: + resolution: {integrity: sha512-7CrWySmIibCgT1Os28lUU6upBshZ+GxybLOrmRzi08kS8MBuO8QA7pXEgYgY5W8vK3e74xv0lpjo9DbaGU9Rkw==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} hasBin: true peerDependencies: @@ -1560,6 +1658,10 @@ packages: resolution: {integrity: sha512-upbkBJbckcCNBDBDXEbuhjbP68n+scUd3k/U2EkyM9nw+I/jPiL4cLF/Al06CF96wRltFda16sxDFrxsI1v0/g==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + espree@10.3.0: + resolution: {integrity: sha512-0QYC8b24HWY8zjRnDTL6RiHfDbAWn63qb4LMj1Z4b076A4une81+z03Kg7l7mn/48PUTqoLptSXez8oknU8Clg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + espree@9.6.1: resolution: {integrity: sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} @@ -1593,6 +1695,10 @@ packages: resolution: {integrity: sha512-VyhnebXciFV2DESc+p6B+y0LjSm0krU4OgJN44qFAhBY0TJ+1V61tYD2+wHusZ6F9n5K+vl8k0sTy7PEfV4qpg==} engines: {node: '>=16.17'} + expect-type@1.1.0: + resolution: {integrity: sha512-bFi65yM+xZgk+u/KRIpekdSYkTB5W1pEf0Lt8Q8Msh7b+eQ7LXVtIB1Bkm4fvclDEL1b2CZkMhv2mOeF8tMdkA==} + engines: {node: '>=12.0.0'} + fast-deep-equal@3.1.3: resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} @@ -1720,8 +1826,8 @@ packages: resolution: {integrity: sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==} engines: {node: '>=18'} - globals@15.11.0: - resolution: {integrity: sha512-yeyNSjdbyVaWurlwCpcA6XNBrHTMIeDdj0/hnvX/OLJ9ekOXYbLsLinH/MucQyGvNnXhidTdNhTtJaffL2sMfw==} + globals@15.12.0: + resolution: {integrity: sha512-1+gLErljJFhbOVyaetcwJiJ4+eLe45S2E7P5UiZ9xGfeq3ATQf5DOv9G7MH3gGbKQLkzmNh2DxfZwLdw+j6oTQ==} engines: {node: '>=18'} graceful-fs@4.2.11: @@ -1760,8 +1866,8 @@ packages: resolution: {integrity: sha512-AXcZb6vzzrFAUE61HnN4mpLqd/cSIwNQjtNWR0euPm6y0iqx3G4gOXaIDdtdDwZmhwe82LA6+zinmW4UBWVePQ==} engines: {node: '>=16.17.0'} - husky@9.1.6: - resolution: {integrity: sha512-sqbjZKK7kf44hfdE94EoX8MZNk0n7HeW37O4YrVGCF4wzgQjp+akPAkfUK5LZ6KuR/6sqeAVuXHji+RzQgOn5A==} + husky@9.1.7: + resolution: {integrity: sha512-5gs5ytaNjBrh5Ow3zrvdUUY+0VxIuWVL4i9irt6friV+BqdCfmV11CQTWMiBYWHbXhco+J1kHfTOUkePhCDvMA==} engines: {node: '>=18'} hasBin: true @@ -2235,8 +2341,8 @@ packages: resolution: {integrity: sha512-VXJjc87FScF88uafS3JllDgvAm+c/Slfz06lorj2uAY34rlUu0Nt+v8wreiImcrgAjjIHp1rXpTDlLOGw29WwQ==} engines: {node: '>=18'} - oniguruma-to-js@0.4.3: - resolution: {integrity: sha512-X0jWUcAlxORhOqqBREgPMgnshB7ZGYszBNspP+tS9hPD3l13CdaXcHbgImoHUHlrvGx/7AvFEkTRhAGYh+jzjQ==} + oniguruma-to-es@0.4.1: + resolution: {integrity: sha512-rNcEohFz095QKGRovP/yqPIKc+nP+Sjs4YTHMv33nMePGKrq/r2eu9Yh4646M5XluGJsUnmwoXuiXE69KDs+fQ==} optionator@0.9.4: resolution: {integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==} @@ -2392,8 +2498,14 @@ packages: resolution: {integrity: sha512-J8rn6v4DBb2nnFqkqwy6/NnTYMcgLA+sLr0iIO41qpv0n+ngb7ksag2tMRl0inb1bbO/esUwzW1vbJi7K0sI0g==} engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0} - regex@4.3.3: - resolution: {integrity: sha512-r/AadFO7owAq1QJVeZ/nq9jNS1vyZt+6t1p/E59B56Rn2GCya+gr1KSyOzNL/er+r+B7phv5jG2xU2Nz1YkmJg==} + regex-recursion@4.2.1: + resolution: {integrity: sha512-QHNZyZAeKdndD1G3bKAbBEKOSSK4KOHQrAJ01N1LJeb0SoH4DJIeFhp0uUpETgONifS4+P3sOgoA1dhzgrQvhA==} + + regex-utilities@2.3.0: + resolution: {integrity: sha512-8VhliFJAWRaUiVvREIiW2NXXTmHs4vMNnSzuJVhscgmGav3g9VDxLrQndI3dZZVVdp0ZO/5v0xmX516/7M9cng==} + + regex@5.0.2: + resolution: {integrity: sha512-/pczGbKIQgfTMRV0XjABvc5RzLqQmwqxLHdQao2RTXPk+pmTXB2P0IaUHYdYyk412YLwUIkaeMd5T+RzVgTqnQ==} regexp-ast-analysis@0.7.1: resolution: {integrity: sha512-sZuz1dYW/ZsfG17WSAG7eS85r5a0dDsvg+7BiiYR5o6lKCAtUrEwdmRmaGF6rwVj3LcmAeYkOWKEPlbPzN3Y3A==} @@ -2478,8 +2590,8 @@ packages: resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} engines: {node: '>=8'} - shiki@1.22.1: - resolution: {integrity: sha512-PbJ6XxrWLMwB2rm3qdjIHNm3zq4SfFnOx0B3rEoi4AN8AUngsdyZ1tRe5slMPtn6jQkbUURLNZPpLR7Do3k78g==} + shiki@1.23.1: + resolution: {integrity: sha512-8kxV9TH4pXgdKGxNOkrSMydn1Xf6It8lsle0fiqxf7a1149K1WGtdOu3Zb91T5r1JpvRPxqxU3C2XdZZXQnrig==} siginfo@2.0.0: resolution: {integrity: sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==} @@ -2541,8 +2653,8 @@ packages: stackback@0.0.2: resolution: {integrity: sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==} - std-env@3.7.0: - resolution: {integrity: sha512-JPbdCEQLj1w5GilpiHAx3qJvFndqybBysA3qUOnznweH4QbNYUsW/ea8QzSrnh0vNsezMMw5bcVool8lM0gwzg==} + std-env@3.8.0: + resolution: {integrity: sha512-Bc3YwwCB+OzldMxOXJIIvC6cPRWr/LxOp48CdQTOkPyk/t4JWWJbrilwBd7RJzKV8QW7tJkcgAmeuLLJugl5/w==} string-argv@0.3.2: resolution: {integrity: sha512-aqD2Q0144Z+/RqG52NeHEkZauTAUWJO8c6yTftGJKO3Tja5tUgIfmIl6kExvhtxSDP7fXB6DvzkfMpCd/F3G+Q==} @@ -2618,9 +2730,6 @@ packages: resolution: {integrity: sha512-te/NtwBwfiNRLf9Ijqx3T0nlqZiQ2XrrtBvu+cLL8ZRrGkO0NHTug8MYFKyoSrv/sHTaSKfilUkizV6XhxMJ3g==} engines: {node: '>=8'} - text-table@0.2.0: - resolution: {integrity: sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==} - through@2.3.8: resolution: {integrity: sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==} @@ -2735,13 +2844,13 @@ packages: vfile@6.0.3: resolution: {integrity: sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q==} - vite-node@2.1.3: - resolution: {integrity: sha512-I1JadzO+xYX887S39Do+paRePCKoiDrWRRjp9kkG5he0t7RXNvPAJPCQSJqbGN4uCrFFeS3Kj3sLqY8NMYBEdA==} + vite-node@2.1.5: + resolution: {integrity: sha512-rd0QIgx74q4S1Rd56XIiL2cYEdyWn13cunYBIuqh9mpmQr7gGS0IxXoP8R6OaZtNQQLyXSWbd4rXKYUbhFpK5w==} engines: {node: ^18.0.0 || >=20.0.0} hasBin: true - vite@5.4.10: - resolution: {integrity: sha512-1hvaPshuPUtxeQ0hsVH3Mud0ZanOLwVTneA1EgbAM5LhaZEqyPWGRQ7BtaMvUrTDeEaC8pxtj6a6jku3x4z6SQ==} + vite@5.4.11: + resolution: {integrity: sha512-c7jFQRklXua0mTzneGW9QVyxFjUgwcihC4bXEtujIo2ouWCe1Ajt/amn2PCxYnhYfd5k09JX3SB7OYWFKYqj8Q==} engines: {node: ^18.0.0 || >=20.0.0} hasBin: true peerDependencies: @@ -2771,8 +2880,8 @@ packages: terser: optional: true - vitepress@1.4.1: - resolution: {integrity: sha512-C2rQ7PMlDVqgsaHOa0uJtgGGWaGv74QMaGL62lxKbtFkYtosJB5HAfZ8+pEbfzzvLemYaYwaiQdFLBlexK2sFw==} + vitepress@1.5.0: + resolution: {integrity: sha512-q4Q/G2zjvynvizdB3/bupdYkCJe2umSAMv9Ju4d92E6/NXJ59z70xB0q5p/4lpRyAwflDsbwy1mLV9Q5+nlB+g==} hasBin: true peerDependencies: markdown-it-mathjax3: ^4 @@ -2783,15 +2892,15 @@ packages: postcss: optional: true - vitest@2.1.3: - resolution: {integrity: sha512-Zrxbg/WiIvUP2uEzelDNTXmEMJXuzJ1kCpbDvaKByFA9MNeO95V+7r/3ti0qzJzrxdyuUw5VduN7k+D3VmVOSA==} + vitest@2.1.5: + resolution: {integrity: sha512-P4ljsdpuzRTPI/kbND2sDZ4VmieerR2c9szEZpjc+98Z9ebvnXmM5+0tHEKqYZumXqlvnmfWsjeFOjXVriDG7A==} engines: {node: ^18.0.0 || >=20.0.0} hasBin: true peerDependencies: '@edge-runtime/vm': '*' '@types/node': ^18.0.0 || >=20.0.0 - '@vitest/browser': 2.1.3 - '@vitest/ui': 2.1.3 + '@vitest/browser': 2.1.5 + '@vitest/ui': 2.1.5 happy-dom: '*' jsdom: '*' peerDependenciesMeta: @@ -2876,8 +2985,8 @@ packages: engines: {node: '>= 14'} hasBin: true - yaml@2.6.0: - resolution: {integrity: sha512-a6ae//JvKDEra2kdi1qzCyrJW/WZCgFi8ydDV+eXExl95t+5R+ijnqHJbz9tmMh8FUjx3iv2fCQ4dclAQlO2UQ==} + yaml@2.6.1: + resolution: {integrity: sha512-7r0XPzioN/Q9kXBro/XPnA6kznR73DHq+GXh5ON7ZozRO6aMjbmiBuKste2wslTFkC5d1dw0GooOCepZXJ2SAg==} engines: {node: '>= 14'} hasBin: true @@ -3020,45 +3129,46 @@ snapshots: '@jridgewell/gen-mapping': 0.3.5 '@jridgewell/trace-mapping': 0.3.25 - '@antfu/eslint-config@3.8.0(@typescript-eslint/utils@8.11.0(eslint@9.13.0(jiti@1.21.6))(typescript@5.6.3))(@vue/compiler-sfc@3.5.12)(eslint@9.13.0(jiti@1.21.6))(typescript@5.6.3)(vitest@2.1.3(@types/node@22.8.1))': + '@antfu/eslint-config@3.9.2(@typescript-eslint/utils@8.15.0(eslint@9.15.0(jiti@1.21.6))(typescript@5.6.3))(@vue/compiler-sfc@3.5.12)(eslint@9.15.0(jiti@1.21.6))(typescript@5.6.3)(vitest@2.1.5(@types/node@22.9.1))': dependencies: '@antfu/install-pkg': 0.4.1 - '@clack/prompts': 0.7.0 - '@eslint-community/eslint-plugin-eslint-comments': 4.4.1(eslint@9.13.0(jiti@1.21.6)) + '@clack/prompts': 0.8.1 + '@eslint-community/eslint-plugin-eslint-comments': 4.4.1(eslint@9.15.0(jiti@1.21.6)) '@eslint/markdown': 6.2.1 - '@stylistic/eslint-plugin': 2.9.0(eslint@9.13.0(jiti@1.21.6))(typescript@5.6.3) - '@typescript-eslint/eslint-plugin': 8.11.0(@typescript-eslint/parser@8.11.0(eslint@9.13.0(jiti@1.21.6))(typescript@5.6.3))(eslint@9.13.0(jiti@1.21.6))(typescript@5.6.3) - '@typescript-eslint/parser': 8.11.0(eslint@9.13.0(jiti@1.21.6))(typescript@5.6.3) - '@vitest/eslint-plugin': 1.1.7(@typescript-eslint/utils@8.11.0(eslint@9.13.0(jiti@1.21.6))(typescript@5.6.3))(eslint@9.13.0(jiti@1.21.6))(typescript@5.6.3)(vitest@2.1.3(@types/node@22.8.1)) - eslint: 9.13.0(jiti@1.21.6) - eslint-config-flat-gitignore: 0.3.0(eslint@9.13.0(jiti@1.21.6)) + '@stylistic/eslint-plugin': 2.11.0(eslint@9.15.0(jiti@1.21.6))(typescript@5.6.3) + '@typescript-eslint/eslint-plugin': 8.15.0(@typescript-eslint/parser@8.15.0(eslint@9.15.0(jiti@1.21.6))(typescript@5.6.3))(eslint@9.15.0(jiti@1.21.6))(typescript@5.6.3) + '@typescript-eslint/parser': 8.15.0(eslint@9.15.0(jiti@1.21.6))(typescript@5.6.3) + '@vitest/eslint-plugin': 1.1.10(@typescript-eslint/utils@8.15.0(eslint@9.15.0(jiti@1.21.6))(typescript@5.6.3))(eslint@9.15.0(jiti@1.21.6))(typescript@5.6.3)(vitest@2.1.5(@types/node@22.9.1)) + eslint: 9.15.0(jiti@1.21.6) + eslint-config-flat-gitignore: 0.3.0(eslint@9.15.0(jiti@1.21.6)) eslint-flat-config-utils: 0.4.0 - eslint-merge-processors: 0.1.0(eslint@9.13.0(jiti@1.21.6)) - eslint-plugin-antfu: 2.7.0(eslint@9.13.0(jiti@1.21.6)) - eslint-plugin-command: 0.2.6(eslint@9.13.0(jiti@1.21.6)) - eslint-plugin-import-x: 4.3.1(eslint@9.13.0(jiti@1.21.6))(typescript@5.6.3) - eslint-plugin-jsdoc: 50.4.3(eslint@9.13.0(jiti@1.21.6)) - eslint-plugin-jsonc: 2.16.0(eslint@9.13.0(jiti@1.21.6)) - eslint-plugin-n: 17.11.1(eslint@9.13.0(jiti@1.21.6)) + eslint-merge-processors: 0.1.0(eslint@9.15.0(jiti@1.21.6)) + eslint-plugin-antfu: 2.7.0(eslint@9.15.0(jiti@1.21.6)) + eslint-plugin-command: 0.2.6(eslint@9.15.0(jiti@1.21.6)) + eslint-plugin-import-x: 4.4.3(eslint@9.15.0(jiti@1.21.6))(typescript@5.6.3) + eslint-plugin-jsdoc: 50.5.0(eslint@9.15.0(jiti@1.21.6)) + eslint-plugin-jsonc: 2.18.2(eslint@9.15.0(jiti@1.21.6)) + eslint-plugin-n: 17.13.2(eslint@9.15.0(jiti@1.21.6)) eslint-plugin-no-only-tests: 3.3.0 - eslint-plugin-perfectionist: 3.9.1(eslint@9.13.0(jiti@1.21.6))(typescript@5.6.3)(vue-eslint-parser@9.4.3(eslint@9.13.0(jiti@1.21.6))) - eslint-plugin-regexp: 2.6.0(eslint@9.13.0(jiti@1.21.6)) - eslint-plugin-toml: 0.11.1(eslint@9.13.0(jiti@1.21.6)) - eslint-plugin-unicorn: 56.0.0(eslint@9.13.0(jiti@1.21.6)) - eslint-plugin-unused-imports: 4.1.4(@typescript-eslint/eslint-plugin@8.11.0(@typescript-eslint/parser@8.11.0(eslint@9.13.0(jiti@1.21.6))(typescript@5.6.3))(eslint@9.13.0(jiti@1.21.6))(typescript@5.6.3))(eslint@9.13.0(jiti@1.21.6)) - eslint-plugin-vue: 9.29.1(eslint@9.13.0(jiti@1.21.6)) - eslint-plugin-yml: 1.14.0(eslint@9.13.0(jiti@1.21.6)) - eslint-processor-vue-blocks: 0.1.2(@vue/compiler-sfc@3.5.12)(eslint@9.13.0(jiti@1.21.6)) - globals: 15.11.0 + eslint-plugin-perfectionist: 3.9.1(eslint@9.15.0(jiti@1.21.6))(typescript@5.6.3)(vue-eslint-parser@9.4.3(eslint@9.15.0(jiti@1.21.6))) + eslint-plugin-regexp: 2.7.0(eslint@9.15.0(jiti@1.21.6)) + eslint-plugin-toml: 0.11.1(eslint@9.15.0(jiti@1.21.6)) + eslint-plugin-unicorn: 56.0.0(eslint@9.15.0(jiti@1.21.6)) + eslint-plugin-unused-imports: 4.1.4(@typescript-eslint/eslint-plugin@8.15.0(@typescript-eslint/parser@8.15.0(eslint@9.15.0(jiti@1.21.6))(typescript@5.6.3))(eslint@9.15.0(jiti@1.21.6))(typescript@5.6.3))(eslint@9.15.0(jiti@1.21.6)) + eslint-plugin-vue: 9.31.0(eslint@9.15.0(jiti@1.21.6)) + eslint-plugin-yml: 1.15.0(eslint@9.15.0(jiti@1.21.6)) + eslint-processor-vue-blocks: 0.1.2(@vue/compiler-sfc@3.5.12)(eslint@9.15.0(jiti@1.21.6)) + globals: 15.12.0 jsonc-eslint-parser: 2.4.0 local-pkg: 0.5.0 parse-gitignore: 2.0.0 picocolors: 1.1.1 toml-eslint-parser: 0.10.0 - vue-eslint-parser: 9.4.3(eslint@9.13.0(jiti@1.21.6)) + vue-eslint-parser: 9.4.3(eslint@9.15.0(jiti@1.21.6)) yaml-eslint-parser: 1.2.3 yargs: 17.7.2 transitivePeerDependencies: + - '@eslint/json' - '@typescript-eslint/utils' - '@vue/compiler-sfc' - supports-color @@ -3099,17 +3209,17 @@ snapshots: picocolors: 1.1.1 sisteransi: 1.0.5 - '@clack/prompts@0.7.0': + '@clack/prompts@0.8.1': dependencies: '@clack/core': 0.3.4 picocolors: 1.1.1 sisteransi: 1.0.5 - '@commitlint/cli@19.5.0(@types/node@22.8.1)(typescript@5.6.3)': + '@commitlint/cli@19.6.0(@types/node@22.9.1)(typescript@5.6.3)': dependencies: '@commitlint/format': 19.5.0 - '@commitlint/lint': 19.5.0 - '@commitlint/load': 19.5.0(@types/node@22.8.1)(typescript@5.6.3) + '@commitlint/lint': 19.6.0 + '@commitlint/load': 19.5.0(@types/node@22.9.1)(typescript@5.6.3) '@commitlint/read': 19.5.0 '@commitlint/types': 19.5.0 tinyexec: 0.3.1 @@ -3118,7 +3228,7 @@ snapshots: - '@types/node' - typescript - '@commitlint/config-conventional@19.5.0': + '@commitlint/config-conventional@19.6.0': dependencies: '@commitlint/types': 19.5.0 conventional-changelog-conventionalcommits: 7.0.2 @@ -3144,19 +3254,19 @@ snapshots: '@commitlint/types': 19.5.0 chalk: 5.3.0 - '@commitlint/is-ignored@19.5.0': + '@commitlint/is-ignored@19.6.0': dependencies: '@commitlint/types': 19.5.0 semver: 7.6.3 - '@commitlint/lint@19.5.0': + '@commitlint/lint@19.6.0': dependencies: - '@commitlint/is-ignored': 19.5.0 + '@commitlint/is-ignored': 19.6.0 '@commitlint/parse': 19.5.0 - '@commitlint/rules': 19.5.0 + '@commitlint/rules': 19.6.0 '@commitlint/types': 19.5.0 - '@commitlint/load@19.5.0(@types/node@22.8.1)(typescript@5.6.3)': + '@commitlint/load@19.5.0(@types/node@22.9.1)(typescript@5.6.3)': dependencies: '@commitlint/config-validator': 19.5.0 '@commitlint/execute-rule': 19.5.0 @@ -3164,7 +3274,7 @@ snapshots: '@commitlint/types': 19.5.0 chalk: 5.3.0 cosmiconfig: 9.0.0(typescript@5.6.3) - cosmiconfig-typescript-loader: 5.1.0(@types/node@22.8.1)(cosmiconfig@9.0.0(typescript@5.6.3))(typescript@5.6.3) + cosmiconfig-typescript-loader: 5.1.0(@types/node@22.9.1)(cosmiconfig@9.0.0(typescript@5.6.3))(typescript@5.6.3) lodash.isplainobject: 4.0.6 lodash.merge: 4.6.2 lodash.uniq: 4.5.0 @@ -3197,7 +3307,7 @@ snapshots: lodash.mergewith: 4.6.2 resolve-from: 5.0.0 - '@commitlint/rules@19.5.0': + '@commitlint/rules@19.6.0': dependencies: '@commitlint/ensure': 19.5.0 '@commitlint/message': 19.5.0 @@ -3320,24 +3430,26 @@ snapshots: '@esbuild/win32-x64@0.21.5': optional: true - '@eslint-community/eslint-plugin-eslint-comments@4.4.1(eslint@9.13.0(jiti@1.21.6))': + '@eslint-community/eslint-plugin-eslint-comments@4.4.1(eslint@9.15.0(jiti@1.21.6))': dependencies: escape-string-regexp: 4.0.0 - eslint: 9.13.0(jiti@1.21.6) + eslint: 9.15.0(jiti@1.21.6) ignore: 5.3.2 - '@eslint-community/eslint-utils@4.4.1(eslint@9.13.0(jiti@1.21.6))': + '@eslint-community/eslint-utils@4.4.1(eslint@9.15.0(jiti@1.21.6))': dependencies: - eslint: 9.13.0(jiti@1.21.6) + eslint: 9.15.0(jiti@1.21.6) eslint-visitor-keys: 3.4.3 '@eslint-community/regexpp@4.11.2': {} - '@eslint/compat@1.2.1(eslint@9.13.0(jiti@1.21.6))': + '@eslint-community/regexpp@4.12.1': {} + + '@eslint/compat@1.2.1(eslint@9.15.0(jiti@1.21.6))': optionalDependencies: - eslint: 9.13.0(jiti@1.21.6) + eslint: 9.15.0(jiti@1.21.6) - '@eslint/config-array@0.18.0': + '@eslint/config-array@0.19.0': dependencies: '@eslint/object-schema': 2.1.4 debug: 4.3.7 @@ -3345,13 +3457,13 @@ snapshots: transitivePeerDependencies: - supports-color - '@eslint/core@0.7.0': {} + '@eslint/core@0.9.0': {} - '@eslint/eslintrc@3.1.0': + '@eslint/eslintrc@3.2.0': dependencies: ajv: 6.12.6 debug: 4.3.7 - espree: 10.2.0 + espree: 10.3.0 globals: 14.0.0 ignore: 5.3.2 import-fresh: 3.3.0 @@ -3361,7 +3473,7 @@ snapshots: transitivePeerDependencies: - supports-color - '@eslint/js@9.13.0': {} + '@eslint/js@9.15.0': {} '@eslint/markdown@6.2.1': dependencies: @@ -3378,17 +3490,29 @@ snapshots: dependencies: levn: 0.4.1 - '@humanfs/core@0.19.0': {} + '@eslint/plugin-kit@0.2.3': + dependencies: + levn: 0.4.1 + + '@humanfs/core@0.19.1': {} - '@humanfs/node@0.16.5': + '@humanfs/node@0.16.6': dependencies: - '@humanfs/core': 0.19.0 + '@humanfs/core': 0.19.1 '@humanwhocodes/retry': 0.3.1 '@humanwhocodes/module-importer@1.0.1': {} '@humanwhocodes/retry@0.3.1': {} + '@humanwhocodes/retry@0.4.1': {} + + '@iconify-json/simple-icons@1.2.12': + dependencies: + '@iconify/types': 2.0.0 + + '@iconify/types@2.0.0': {} + '@isaacs/cliui@8.0.2': dependencies: string-width: 5.1.2 @@ -3542,6 +3666,10 @@ snapshots: '@octokit/core': 6.1.2 '@octokit/types': 13.6.1 + '@octokit/plugin-request-log@5.3.1(@octokit/core@6.1.2)': + dependencies: + '@octokit/core': 6.1.2 + '@octokit/plugin-rest-endpoint-methods@13.2.6(@octokit/core@6.1.2)': dependencies: '@octokit/core': 6.1.2 @@ -3571,6 +3699,13 @@ snapshots: '@octokit/types': 13.6.1 universal-user-agent: 7.0.2 + '@octokit/rest@21.0.2': + dependencies: + '@octokit/core': 6.1.2 + '@octokit/plugin-paginate-rest': 11.3.5(@octokit/core@6.1.2) + '@octokit/plugin-request-log': 5.3.1(@octokit/core@6.1.2) + '@octokit/plugin-rest-endpoint-methods': 13.2.6(@octokit/core@6.1.2) + '@octokit/types@13.6.1': dependencies: '@octokit/openapi-types': 22.2.0 @@ -3636,43 +3771,43 @@ snapshots: '@rollup/rollup-win32-x64-msvc@4.24.0': optional: true - '@shikijs/core@1.22.1': + '@shikijs/core@1.23.1': dependencies: - '@shikijs/engine-javascript': 1.22.1 - '@shikijs/engine-oniguruma': 1.22.1 - '@shikijs/types': 1.22.1 + '@shikijs/engine-javascript': 1.23.1 + '@shikijs/engine-oniguruma': 1.23.1 + '@shikijs/types': 1.23.1 '@shikijs/vscode-textmate': 9.3.0 '@types/hast': 3.0.4 hast-util-to-html: 9.0.3 - '@shikijs/engine-javascript@1.22.1': + '@shikijs/engine-javascript@1.23.1': dependencies: - '@shikijs/types': 1.22.1 + '@shikijs/types': 1.23.1 '@shikijs/vscode-textmate': 9.3.0 - oniguruma-to-js: 0.4.3 + oniguruma-to-es: 0.4.1 - '@shikijs/engine-oniguruma@1.22.1': + '@shikijs/engine-oniguruma@1.23.1': dependencies: - '@shikijs/types': 1.22.1 + '@shikijs/types': 1.23.1 '@shikijs/vscode-textmate': 9.3.0 - '@shikijs/transformers@1.22.1': + '@shikijs/transformers@1.23.1': dependencies: - shiki: 1.22.1 + shiki: 1.23.1 - '@shikijs/types@1.22.1': + '@shikijs/types@1.23.1': dependencies: '@shikijs/vscode-textmate': 9.3.0 '@types/hast': 3.0.4 '@shikijs/vscode-textmate@9.3.0': {} - '@stylistic/eslint-plugin@2.9.0(eslint@9.13.0(jiti@1.21.6))(typescript@5.6.3)': + '@stylistic/eslint-plugin@2.11.0(eslint@9.15.0(jiti@1.21.6))(typescript@5.6.3)': dependencies: - '@typescript-eslint/utils': 8.11.0(eslint@9.13.0(jiti@1.21.6))(typescript@5.6.3) - eslint: 9.13.0(jiti@1.21.6) - eslint-visitor-keys: 4.1.0 - espree: 10.2.0 + '@typescript-eslint/utils': 8.15.0(eslint@9.15.0(jiti@1.21.6))(typescript@5.6.3) + eslint: 9.15.0(jiti@1.21.6) + eslint-visitor-keys: 4.2.0 + espree: 10.3.0 estraverse: 5.3.0 picomatch: 4.0.2 transitivePeerDependencies: @@ -3683,7 +3818,7 @@ snapshots: '@types/conventional-commits-parser@5.0.0': dependencies: - '@types/node': 22.8.1 + '@types/node': 22.9.1 '@types/debug@4.1.12': dependencies: @@ -3712,7 +3847,7 @@ snapshots: '@types/ms@0.7.34': {} - '@types/node@22.8.1': + '@types/node@22.9.1': dependencies: undici-types: 6.19.8 @@ -3722,15 +3857,15 @@ snapshots: '@types/web-bluetooth@0.0.20': {} - '@typescript-eslint/eslint-plugin@8.11.0(@typescript-eslint/parser@8.11.0(eslint@9.13.0(jiti@1.21.6))(typescript@5.6.3))(eslint@9.13.0(jiti@1.21.6))(typescript@5.6.3)': + '@typescript-eslint/eslint-plugin@8.15.0(@typescript-eslint/parser@8.15.0(eslint@9.15.0(jiti@1.21.6))(typescript@5.6.3))(eslint@9.15.0(jiti@1.21.6))(typescript@5.6.3)': dependencies: '@eslint-community/regexpp': 4.11.2 - '@typescript-eslint/parser': 8.11.0(eslint@9.13.0(jiti@1.21.6))(typescript@5.6.3) - '@typescript-eslint/scope-manager': 8.11.0 - '@typescript-eslint/type-utils': 8.11.0(eslint@9.13.0(jiti@1.21.6))(typescript@5.6.3) - '@typescript-eslint/utils': 8.11.0(eslint@9.13.0(jiti@1.21.6))(typescript@5.6.3) - '@typescript-eslint/visitor-keys': 8.11.0 - eslint: 9.13.0(jiti@1.21.6) + '@typescript-eslint/parser': 8.15.0(eslint@9.15.0(jiti@1.21.6))(typescript@5.6.3) + '@typescript-eslint/scope-manager': 8.15.0 + '@typescript-eslint/type-utils': 8.15.0(eslint@9.15.0(jiti@1.21.6))(typescript@5.6.3) + '@typescript-eslint/utils': 8.15.0(eslint@9.15.0(jiti@1.21.6))(typescript@5.6.3) + '@typescript-eslint/visitor-keys': 8.15.0 + eslint: 9.15.0(jiti@1.21.6) graphemer: 1.4.0 ignore: 5.3.2 natural-compare: 1.4.0 @@ -3740,14 +3875,14 @@ snapshots: transitivePeerDependencies: - supports-color - '@typescript-eslint/parser@8.11.0(eslint@9.13.0(jiti@1.21.6))(typescript@5.6.3)': + '@typescript-eslint/parser@8.15.0(eslint@9.15.0(jiti@1.21.6))(typescript@5.6.3)': dependencies: - '@typescript-eslint/scope-manager': 8.11.0 - '@typescript-eslint/types': 8.11.0 - '@typescript-eslint/typescript-estree': 8.11.0(typescript@5.6.3) - '@typescript-eslint/visitor-keys': 8.11.0 + '@typescript-eslint/scope-manager': 8.15.0 + '@typescript-eslint/types': 8.15.0 + '@typescript-eslint/typescript-estree': 8.15.0(typescript@5.6.3) + '@typescript-eslint/visitor-keys': 8.15.0 debug: 4.3.7 - eslint: 9.13.0(jiti@1.21.6) + eslint: 9.15.0(jiti@1.21.6) optionalDependencies: typescript: 5.6.3 transitivePeerDependencies: @@ -3758,20 +3893,27 @@ snapshots: '@typescript-eslint/types': 8.11.0 '@typescript-eslint/visitor-keys': 8.11.0 - '@typescript-eslint/type-utils@8.11.0(eslint@9.13.0(jiti@1.21.6))(typescript@5.6.3)': + '@typescript-eslint/scope-manager@8.15.0': dependencies: - '@typescript-eslint/typescript-estree': 8.11.0(typescript@5.6.3) - '@typescript-eslint/utils': 8.11.0(eslint@9.13.0(jiti@1.21.6))(typescript@5.6.3) + '@typescript-eslint/types': 8.15.0 + '@typescript-eslint/visitor-keys': 8.15.0 + + '@typescript-eslint/type-utils@8.15.0(eslint@9.15.0(jiti@1.21.6))(typescript@5.6.3)': + dependencies: + '@typescript-eslint/typescript-estree': 8.15.0(typescript@5.6.3) + '@typescript-eslint/utils': 8.15.0(eslint@9.15.0(jiti@1.21.6))(typescript@5.6.3) debug: 4.3.7 + eslint: 9.15.0(jiti@1.21.6) ts-api-utils: 1.3.0(typescript@5.6.3) optionalDependencies: typescript: 5.6.3 transitivePeerDependencies: - - eslint - supports-color '@typescript-eslint/types@8.11.0': {} + '@typescript-eslint/types@8.15.0': {} + '@typescript-eslint/typescript-estree@8.11.0(typescript@5.6.3)': dependencies: '@typescript-eslint/types': 8.11.0 @@ -3787,30 +3929,62 @@ snapshots: transitivePeerDependencies: - supports-color - '@typescript-eslint/utils@8.11.0(eslint@9.13.0(jiti@1.21.6))(typescript@5.6.3)': + '@typescript-eslint/typescript-estree@8.15.0(typescript@5.6.3)': dependencies: - '@eslint-community/eslint-utils': 4.4.1(eslint@9.13.0(jiti@1.21.6)) + '@typescript-eslint/types': 8.15.0 + '@typescript-eslint/visitor-keys': 8.15.0 + debug: 4.3.7 + fast-glob: 3.3.2 + is-glob: 4.0.3 + minimatch: 9.0.5 + semver: 7.6.3 + ts-api-utils: 1.3.0(typescript@5.6.3) + optionalDependencies: + typescript: 5.6.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/utils@8.11.0(eslint@9.15.0(jiti@1.21.6))(typescript@5.6.3)': + dependencies: + '@eslint-community/eslint-utils': 4.4.1(eslint@9.15.0(jiti@1.21.6)) '@typescript-eslint/scope-manager': 8.11.0 '@typescript-eslint/types': 8.11.0 '@typescript-eslint/typescript-estree': 8.11.0(typescript@5.6.3) - eslint: 9.13.0(jiti@1.21.6) + eslint: 9.15.0(jiti@1.21.6) transitivePeerDependencies: - supports-color - typescript + '@typescript-eslint/utils@8.15.0(eslint@9.15.0(jiti@1.21.6))(typescript@5.6.3)': + dependencies: + '@eslint-community/eslint-utils': 4.4.1(eslint@9.15.0(jiti@1.21.6)) + '@typescript-eslint/scope-manager': 8.15.0 + '@typescript-eslint/types': 8.15.0 + '@typescript-eslint/typescript-estree': 8.15.0(typescript@5.6.3) + eslint: 9.15.0(jiti@1.21.6) + optionalDependencies: + typescript: 5.6.3 + transitivePeerDependencies: + - supports-color + '@typescript-eslint/visitor-keys@8.11.0': dependencies: '@typescript-eslint/types': 8.11.0 eslint-visitor-keys: 3.4.3 + '@typescript-eslint/visitor-keys@8.15.0': + dependencies: + '@typescript-eslint/types': 8.15.0 + eslint-visitor-keys: 4.2.0 + '@ungap/structured-clone@1.2.0': {} - '@vitejs/plugin-vue@5.1.4(vite@5.4.10(@types/node@22.8.1))(vue@3.5.12(typescript@5.6.3))': + '@vitejs/plugin-vue@5.1.4(vite@5.4.11(@types/node@22.9.1))(vue@3.5.12(typescript@5.6.3))': dependencies: - vite: 5.4.10(@types/node@22.8.1) + vite: 5.4.11(@types/node@22.9.1) vue: 3.5.12(typescript@5.6.3) - '@vitest/coverage-v8@2.1.3(vitest@2.1.3(@types/node@22.8.1))': + '@vitest/coverage-v8@2.1.5(vitest@2.1.5(@types/node@22.9.1))': dependencies: '@ampproject/remapping': 2.3.0 '@bcoe/v8-coverage': 0.2.3 @@ -3821,58 +3995,58 @@ snapshots: istanbul-reports: 3.1.7 magic-string: 0.30.12 magicast: 0.3.5 - std-env: 3.7.0 + std-env: 3.8.0 test-exclude: 7.0.1 tinyrainbow: 1.2.0 - vitest: 2.1.3(@types/node@22.8.1) + vitest: 2.1.5(@types/node@22.9.1) transitivePeerDependencies: - supports-color - '@vitest/eslint-plugin@1.1.7(@typescript-eslint/utils@8.11.0(eslint@9.13.0(jiti@1.21.6))(typescript@5.6.3))(eslint@9.13.0(jiti@1.21.6))(typescript@5.6.3)(vitest@2.1.3(@types/node@22.8.1))': + '@vitest/eslint-plugin@1.1.10(@typescript-eslint/utils@8.15.0(eslint@9.15.0(jiti@1.21.6))(typescript@5.6.3))(eslint@9.15.0(jiti@1.21.6))(typescript@5.6.3)(vitest@2.1.5(@types/node@22.9.1))': dependencies: - '@typescript-eslint/utils': 8.11.0(eslint@9.13.0(jiti@1.21.6))(typescript@5.6.3) - eslint: 9.13.0(jiti@1.21.6) + '@typescript-eslint/utils': 8.15.0(eslint@9.15.0(jiti@1.21.6))(typescript@5.6.3) + eslint: 9.15.0(jiti@1.21.6) optionalDependencies: typescript: 5.6.3 - vitest: 2.1.3(@types/node@22.8.1) + vitest: 2.1.5(@types/node@22.9.1) - '@vitest/expect@2.1.3': + '@vitest/expect@2.1.5': dependencies: - '@vitest/spy': 2.1.3 - '@vitest/utils': 2.1.3 + '@vitest/spy': 2.1.5 + '@vitest/utils': 2.1.5 chai: 5.1.2 tinyrainbow: 1.2.0 - '@vitest/mocker@2.1.3(@vitest/spy@2.1.3)(vite@5.4.10(@types/node@22.8.1))': + '@vitest/mocker@2.1.5(vite@5.4.11(@types/node@22.9.1))': dependencies: - '@vitest/spy': 2.1.3 + '@vitest/spy': 2.1.5 estree-walker: 3.0.3 magic-string: 0.30.12 optionalDependencies: - vite: 5.4.10(@types/node@22.8.1) + vite: 5.4.11(@types/node@22.9.1) - '@vitest/pretty-format@2.1.3': + '@vitest/pretty-format@2.1.5': dependencies: tinyrainbow: 1.2.0 - '@vitest/runner@2.1.3': + '@vitest/runner@2.1.5': dependencies: - '@vitest/utils': 2.1.3 + '@vitest/utils': 2.1.5 pathe: 1.1.2 - '@vitest/snapshot@2.1.3': + '@vitest/snapshot@2.1.5': dependencies: - '@vitest/pretty-format': 2.1.3 + '@vitest/pretty-format': 2.1.5 magic-string: 0.30.12 pathe: 1.1.2 - '@vitest/spy@2.1.3': + '@vitest/spy@2.1.5': dependencies: tinyspy: 3.0.2 - '@vitest/utils@2.1.3': + '@vitest/utils@2.1.5': dependencies: - '@vitest/pretty-format': 2.1.3 + '@vitest/pretty-format': 2.1.5 loupe: 3.1.2 tinyrainbow: 1.2.0 @@ -3988,8 +4162,14 @@ snapshots: dependencies: acorn: 8.13.0 + acorn-jsx@5.3.2(acorn@8.14.0): + dependencies: + acorn: 8.14.0 + acorn@8.13.0: {} + acorn@8.14.0: {} + ajv@6.12.6: dependencies: fast-deep-equal: 3.1.3 @@ -4188,9 +4368,9 @@ snapshots: dependencies: browserslist: 4.24.2 - cosmiconfig-typescript-loader@5.1.0(@types/node@22.8.1)(cosmiconfig@9.0.0(typescript@5.6.3))(typescript@5.6.3): + cosmiconfig-typescript-loader@5.1.0(@types/node@22.9.1)(cosmiconfig@9.0.0(typescript@5.6.3))(typescript@5.6.3): dependencies: - '@types/node': 22.8.1 + '@types/node': 22.9.1 cosmiconfig: 9.0.0(typescript@5.6.3) jiti: 1.21.6 typescript: 5.6.3 @@ -4210,6 +4390,12 @@ snapshots: shebang-command: 2.0.0 which: 2.0.2 + cross-spawn@7.0.6: + dependencies: + path-key: 3.1.1 + shebang-command: 2.0.0 + which: 2.0.2 + cssesc@3.0.0: {} csstype@3.1.3: {} @@ -4252,6 +4438,8 @@ snapshots: electron-to-chromium@1.5.46: {} + emoji-regex-xs@1.0.0: {} + emoji-regex@10.4.0: {} emoji-regex@8.0.0: {} @@ -4309,15 +4497,20 @@ snapshots: escape-string-regexp@5.0.0: {} - eslint-compat-utils@0.5.1(eslint@9.13.0(jiti@1.21.6)): + eslint-compat-utils@0.5.1(eslint@9.15.0(jiti@1.21.6)): + dependencies: + eslint: 9.15.0(jiti@1.21.6) + semver: 7.6.3 + + eslint-compat-utils@0.6.3(eslint@9.15.0(jiti@1.21.6)): dependencies: - eslint: 9.13.0(jiti@1.21.6) + eslint: 9.15.0(jiti@1.21.6) semver: 7.6.3 - eslint-config-flat-gitignore@0.3.0(eslint@9.13.0(jiti@1.21.6)): + eslint-config-flat-gitignore@0.3.0(eslint@9.15.0(jiti@1.21.6)): dependencies: - '@eslint/compat': 1.2.1(eslint@9.13.0(jiti@1.21.6)) - eslint: 9.13.0(jiti@1.21.6) + '@eslint/compat': 1.2.1(eslint@9.15.0(jiti@1.21.6)) + eslint: 9.15.0(jiti@1.21.6) find-up-simple: 1.0.0 eslint-flat-config-utils@0.4.0: @@ -4332,33 +4525,39 @@ snapshots: transitivePeerDependencies: - supports-color - eslint-merge-processors@0.1.0(eslint@9.13.0(jiti@1.21.6)): + eslint-json-compat-utils@0.2.1(eslint@9.15.0(jiti@1.21.6))(jsonc-eslint-parser@2.4.0): + dependencies: + eslint: 9.15.0(jiti@1.21.6) + esquery: 1.6.0 + jsonc-eslint-parser: 2.4.0 + + eslint-merge-processors@0.1.0(eslint@9.15.0(jiti@1.21.6)): dependencies: - eslint: 9.13.0(jiti@1.21.6) + eslint: 9.15.0(jiti@1.21.6) - eslint-plugin-antfu@2.7.0(eslint@9.13.0(jiti@1.21.6)): + eslint-plugin-antfu@2.7.0(eslint@9.15.0(jiti@1.21.6)): dependencies: '@antfu/utils': 0.7.10 - eslint: 9.13.0(jiti@1.21.6) + eslint: 9.15.0(jiti@1.21.6) - eslint-plugin-command@0.2.6(eslint@9.13.0(jiti@1.21.6)): + eslint-plugin-command@0.2.6(eslint@9.15.0(jiti@1.21.6)): dependencies: '@es-joy/jsdoccomment': 0.48.0 - eslint: 9.13.0(jiti@1.21.6) + eslint: 9.15.0(jiti@1.21.6) - eslint-plugin-es-x@7.8.0(eslint@9.13.0(jiti@1.21.6)): + eslint-plugin-es-x@7.8.0(eslint@9.15.0(jiti@1.21.6)): dependencies: - '@eslint-community/eslint-utils': 4.4.1(eslint@9.13.0(jiti@1.21.6)) + '@eslint-community/eslint-utils': 4.4.1(eslint@9.15.0(jiti@1.21.6)) '@eslint-community/regexpp': 4.11.2 - eslint: 9.13.0(jiti@1.21.6) - eslint-compat-utils: 0.5.1(eslint@9.13.0(jiti@1.21.6)) + eslint: 9.15.0(jiti@1.21.6) + eslint-compat-utils: 0.5.1(eslint@9.15.0(jiti@1.21.6)) - eslint-plugin-import-x@4.3.1(eslint@9.13.0(jiti@1.21.6))(typescript@5.6.3): + eslint-plugin-import-x@4.4.3(eslint@9.15.0(jiti@1.21.6))(typescript@5.6.3): dependencies: - '@typescript-eslint/utils': 8.11.0(eslint@9.13.0(jiti@1.21.6))(typescript@5.6.3) + '@typescript-eslint/utils': 8.11.0(eslint@9.15.0(jiti@1.21.6))(typescript@5.6.3) debug: 4.3.7 doctrine: 3.0.0 - eslint: 9.13.0(jiti@1.21.6) + eslint: 9.15.0(jiti@1.21.6) eslint-import-resolver-node: 0.3.9 get-tsconfig: 4.8.1 is-glob: 4.0.3 @@ -4370,14 +4569,14 @@ snapshots: - supports-color - typescript - eslint-plugin-jsdoc@50.4.3(eslint@9.13.0(jiti@1.21.6)): + eslint-plugin-jsdoc@50.5.0(eslint@9.15.0(jiti@1.21.6)): dependencies: '@es-joy/jsdoccomment': 0.49.0 are-docs-informative: 0.0.2 comment-parser: 1.4.1 debug: 4.3.7 escape-string-regexp: 4.0.0 - eslint: 9.13.0(jiti@1.21.6) + eslint: 9.15.0(jiti@1.21.6) espree: 10.2.0 esquery: 1.6.0 parse-imports: 2.2.1 @@ -4387,75 +4586,78 @@ snapshots: transitivePeerDependencies: - supports-color - eslint-plugin-jsonc@2.16.0(eslint@9.13.0(jiti@1.21.6)): + eslint-plugin-jsonc@2.18.2(eslint@9.15.0(jiti@1.21.6)): dependencies: - '@eslint-community/eslint-utils': 4.4.1(eslint@9.13.0(jiti@1.21.6)) - eslint: 9.13.0(jiti@1.21.6) - eslint-compat-utils: 0.5.1(eslint@9.13.0(jiti@1.21.6)) + '@eslint-community/eslint-utils': 4.4.1(eslint@9.15.0(jiti@1.21.6)) + eslint: 9.15.0(jiti@1.21.6) + eslint-compat-utils: 0.6.3(eslint@9.15.0(jiti@1.21.6)) + eslint-json-compat-utils: 0.2.1(eslint@9.15.0(jiti@1.21.6))(jsonc-eslint-parser@2.4.0) espree: 9.6.1 graphemer: 1.4.0 jsonc-eslint-parser: 2.4.0 natural-compare: 1.4.0 synckit: 0.6.2 + transitivePeerDependencies: + - '@eslint/json' - eslint-plugin-n@17.11.1(eslint@9.13.0(jiti@1.21.6)): + eslint-plugin-n@17.13.2(eslint@9.15.0(jiti@1.21.6)): dependencies: - '@eslint-community/eslint-utils': 4.4.1(eslint@9.13.0(jiti@1.21.6)) + '@eslint-community/eslint-utils': 4.4.1(eslint@9.15.0(jiti@1.21.6)) enhanced-resolve: 5.17.1 - eslint: 9.13.0(jiti@1.21.6) - eslint-plugin-es-x: 7.8.0(eslint@9.13.0(jiti@1.21.6)) + eslint: 9.15.0(jiti@1.21.6) + eslint-plugin-es-x: 7.8.0(eslint@9.15.0(jiti@1.21.6)) get-tsconfig: 4.8.1 - globals: 15.11.0 + globals: 15.12.0 ignore: 5.3.2 minimatch: 9.0.5 semver: 7.6.3 eslint-plugin-no-only-tests@3.3.0: {} - eslint-plugin-perfectionist@3.9.1(eslint@9.13.0(jiti@1.21.6))(typescript@5.6.3)(vue-eslint-parser@9.4.3(eslint@9.13.0(jiti@1.21.6))): + eslint-plugin-perfectionist@3.9.1(eslint@9.15.0(jiti@1.21.6))(typescript@5.6.3)(vue-eslint-parser@9.4.3(eslint@9.15.0(jiti@1.21.6))): dependencies: '@typescript-eslint/types': 8.11.0 - '@typescript-eslint/utils': 8.11.0(eslint@9.13.0(jiti@1.21.6))(typescript@5.6.3) - eslint: 9.13.0(jiti@1.21.6) + '@typescript-eslint/utils': 8.11.0(eslint@9.15.0(jiti@1.21.6))(typescript@5.6.3) + eslint: 9.15.0(jiti@1.21.6) minimatch: 9.0.5 natural-compare-lite: 1.4.0 optionalDependencies: - vue-eslint-parser: 9.4.3(eslint@9.13.0(jiti@1.21.6)) + vue-eslint-parser: 9.4.3(eslint@9.15.0(jiti@1.21.6)) transitivePeerDependencies: - supports-color - typescript - eslint-plugin-regexp@2.6.0(eslint@9.13.0(jiti@1.21.6)): + eslint-plugin-regexp@2.7.0(eslint@9.15.0(jiti@1.21.6)): dependencies: - '@eslint-community/eslint-utils': 4.4.1(eslint@9.13.0(jiti@1.21.6)) + '@eslint-community/eslint-utils': 4.4.1(eslint@9.15.0(jiti@1.21.6)) '@eslint-community/regexpp': 4.11.2 comment-parser: 1.4.1 - eslint: 9.13.0(jiti@1.21.6) + eslint: 9.15.0(jiti@1.21.6) jsdoc-type-pratt-parser: 4.1.0 refa: 0.12.1 regexp-ast-analysis: 0.7.1 scslre: 0.3.0 - eslint-plugin-toml@0.11.1(eslint@9.13.0(jiti@1.21.6)): + eslint-plugin-toml@0.11.1(eslint@9.15.0(jiti@1.21.6)): dependencies: debug: 4.3.7 - eslint: 9.13.0(jiti@1.21.6) - eslint-compat-utils: 0.5.1(eslint@9.13.0(jiti@1.21.6)) + eslint: 9.15.0(jiti@1.21.6) + eslint-compat-utils: 0.5.1(eslint@9.15.0(jiti@1.21.6)) lodash: 4.17.21 toml-eslint-parser: 0.10.0 transitivePeerDependencies: - supports-color - eslint-plugin-unicorn@56.0.0(eslint@9.13.0(jiti@1.21.6)): + eslint-plugin-unicorn@56.0.0(eslint@9.15.0(jiti@1.21.6)): dependencies: '@babel/helper-validator-identifier': 7.25.9 - '@eslint-community/eslint-utils': 4.4.1(eslint@9.13.0(jiti@1.21.6)) + '@eslint-community/eslint-utils': 4.4.1(eslint@9.15.0(jiti@1.21.6)) ci-info: 4.0.0 clean-regexp: 1.0.0 core-js-compat: 3.38.1 - eslint: 9.13.0(jiti@1.21.6) + eslint: 9.15.0(jiti@1.21.6) esquery: 1.6.0 - globals: 15.11.0 + globals: 15.12.0 indent-string: 4.0.0 is-builtin-module: 3.2.1 jsesc: 3.0.2 @@ -4466,48 +4668,48 @@ snapshots: semver: 7.6.3 strip-indent: 3.0.0 - eslint-plugin-unused-imports@4.1.4(@typescript-eslint/eslint-plugin@8.11.0(@typescript-eslint/parser@8.11.0(eslint@9.13.0(jiti@1.21.6))(typescript@5.6.3))(eslint@9.13.0(jiti@1.21.6))(typescript@5.6.3))(eslint@9.13.0(jiti@1.21.6)): + eslint-plugin-unused-imports@4.1.4(@typescript-eslint/eslint-plugin@8.15.0(@typescript-eslint/parser@8.15.0(eslint@9.15.0(jiti@1.21.6))(typescript@5.6.3))(eslint@9.15.0(jiti@1.21.6))(typescript@5.6.3))(eslint@9.15.0(jiti@1.21.6)): dependencies: - eslint: 9.13.0(jiti@1.21.6) + eslint: 9.15.0(jiti@1.21.6) optionalDependencies: - '@typescript-eslint/eslint-plugin': 8.11.0(@typescript-eslint/parser@8.11.0(eslint@9.13.0(jiti@1.21.6))(typescript@5.6.3))(eslint@9.13.0(jiti@1.21.6))(typescript@5.6.3) + '@typescript-eslint/eslint-plugin': 8.15.0(@typescript-eslint/parser@8.15.0(eslint@9.15.0(jiti@1.21.6))(typescript@5.6.3))(eslint@9.15.0(jiti@1.21.6))(typescript@5.6.3) - eslint-plugin-vue@9.29.1(eslint@9.13.0(jiti@1.21.6)): + eslint-plugin-vue@9.31.0(eslint@9.15.0(jiti@1.21.6)): dependencies: - '@eslint-community/eslint-utils': 4.4.1(eslint@9.13.0(jiti@1.21.6)) - eslint: 9.13.0(jiti@1.21.6) + '@eslint-community/eslint-utils': 4.4.1(eslint@9.15.0(jiti@1.21.6)) + eslint: 9.15.0(jiti@1.21.6) globals: 13.24.0 natural-compare: 1.4.0 nth-check: 2.1.1 postcss-selector-parser: 6.1.2 semver: 7.6.3 - vue-eslint-parser: 9.4.3(eslint@9.13.0(jiti@1.21.6)) + vue-eslint-parser: 9.4.3(eslint@9.15.0(jiti@1.21.6)) xml-name-validator: 4.0.0 transitivePeerDependencies: - supports-color - eslint-plugin-yml@1.14.0(eslint@9.13.0(jiti@1.21.6)): + eslint-plugin-yml@1.15.0(eslint@9.15.0(jiti@1.21.6)): dependencies: debug: 4.3.7 - eslint: 9.13.0(jiti@1.21.6) - eslint-compat-utils: 0.5.1(eslint@9.13.0(jiti@1.21.6)) + eslint: 9.15.0(jiti@1.21.6) + eslint-compat-utils: 0.5.1(eslint@9.15.0(jiti@1.21.6)) lodash: 4.17.21 natural-compare: 1.4.0 yaml-eslint-parser: 1.2.3 transitivePeerDependencies: - supports-color - eslint-processor-vue-blocks@0.1.2(@vue/compiler-sfc@3.5.12)(eslint@9.13.0(jiti@1.21.6)): + eslint-processor-vue-blocks@0.1.2(@vue/compiler-sfc@3.5.12)(eslint@9.15.0(jiti@1.21.6)): dependencies: '@vue/compiler-sfc': 3.5.12 - eslint: 9.13.0(jiti@1.21.6) + eslint: 9.15.0(jiti@1.21.6) eslint-scope@7.2.2: dependencies: esrecurse: 4.3.0 estraverse: 5.3.0 - eslint-scope@8.1.0: + eslint-scope@8.2.0: dependencies: esrecurse: 4.3.0 estraverse: 5.3.0 @@ -4516,28 +4718,30 @@ snapshots: eslint-visitor-keys@4.1.0: {} - eslint@9.13.0(jiti@1.21.6): + eslint-visitor-keys@4.2.0: {} + + eslint@9.15.0(jiti@1.21.6): dependencies: - '@eslint-community/eslint-utils': 4.4.1(eslint@9.13.0(jiti@1.21.6)) - '@eslint-community/regexpp': 4.11.2 - '@eslint/config-array': 0.18.0 - '@eslint/core': 0.7.0 - '@eslint/eslintrc': 3.1.0 - '@eslint/js': 9.13.0 - '@eslint/plugin-kit': 0.2.1 - '@humanfs/node': 0.16.5 + '@eslint-community/eslint-utils': 4.4.1(eslint@9.15.0(jiti@1.21.6)) + '@eslint-community/regexpp': 4.12.1 + '@eslint/config-array': 0.19.0 + '@eslint/core': 0.9.0 + '@eslint/eslintrc': 3.2.0 + '@eslint/js': 9.15.0 + '@eslint/plugin-kit': 0.2.3 + '@humanfs/node': 0.16.6 '@humanwhocodes/module-importer': 1.0.1 - '@humanwhocodes/retry': 0.3.1 + '@humanwhocodes/retry': 0.4.1 '@types/estree': 1.0.6 '@types/json-schema': 7.0.15 ajv: 6.12.6 chalk: 4.1.2 - cross-spawn: 7.0.3 + cross-spawn: 7.0.6 debug: 4.3.7 escape-string-regexp: 4.0.0 - eslint-scope: 8.1.0 - eslint-visitor-keys: 4.1.0 - espree: 10.2.0 + eslint-scope: 8.2.0 + eslint-visitor-keys: 4.2.0 + espree: 10.3.0 esquery: 1.6.0 esutils: 2.0.3 fast-deep-equal: 3.1.3 @@ -4552,7 +4756,6 @@ snapshots: minimatch: 3.1.2 natural-compare: 1.4.0 optionator: 0.9.4 - text-table: 0.2.0 optionalDependencies: jiti: 1.21.6 transitivePeerDependencies: @@ -4564,6 +4767,12 @@ snapshots: acorn-jsx: 5.3.2(acorn@8.13.0) eslint-visitor-keys: 4.1.0 + espree@10.3.0: + dependencies: + acorn: 8.14.0 + acorn-jsx: 5.3.2(acorn@8.14.0) + eslint-visitor-keys: 4.2.0 + espree@9.6.1: dependencies: acorn: 8.13.0 @@ -4602,6 +4811,8 @@ snapshots: signal-exit: 4.1.0 strip-final-newline: 3.0.0 + expect-type@1.1.0: {} + fast-deep-equal@3.1.3: {} fast-glob@3.3.2: @@ -4729,7 +4940,7 @@ snapshots: globals@14.0.0: {} - globals@15.11.0: {} + globals@15.12.0: {} graceful-fs@4.2.11: {} @@ -4769,7 +4980,7 @@ snapshots: human-signals@5.0.0: {} - husky@9.1.6: {} + husky@9.1.7: {} ignore@5.3.2: {} @@ -5396,9 +5607,11 @@ snapshots: dependencies: mimic-function: 5.0.1 - oniguruma-to-js@0.4.3: + oniguruma-to-es@0.4.1: dependencies: - regex: 4.3.3 + emoji-regex-xs: 1.0.0 + regex: 5.0.2 + regex-recursion: 4.2.1 optionator@0.9.4: dependencies: @@ -5539,7 +5752,15 @@ snapshots: dependencies: '@eslint-community/regexpp': 4.11.2 - regex@4.3.3: {} + regex-recursion@4.2.1: + dependencies: + regex-utilities: 2.3.0 + + regex-utilities@2.3.0: {} + + regex@5.0.2: + dependencies: + regex-utilities: 2.3.0 regexp-ast-analysis@0.7.1: dependencies: @@ -5626,12 +5847,12 @@ snapshots: shebang-regex@3.0.0: {} - shiki@1.22.1: + shiki@1.23.1: dependencies: - '@shikijs/core': 1.22.1 - '@shikijs/engine-javascript': 1.22.1 - '@shikijs/engine-oniguruma': 1.22.1 - '@shikijs/types': 1.22.1 + '@shikijs/core': 1.23.1 + '@shikijs/engine-javascript': 1.23.1 + '@shikijs/engine-oniguruma': 1.23.1 + '@shikijs/types': 1.23.1 '@shikijs/vscode-textmate': 9.3.0 '@types/hast': 3.0.4 @@ -5692,7 +5913,7 @@ snapshots: stackback@0.0.2: {} - std-env@3.7.0: {} + std-env@3.8.0: {} string-argv@0.3.2: {} @@ -5766,8 +5987,6 @@ snapshots: text-extensions@2.4.0: {} - text-table@0.2.0: {} - through@2.3.8: {} tinybench@2.9.0: {} @@ -5868,12 +6087,13 @@ snapshots: '@types/unist': 3.0.3 vfile-message: 4.0.2 - vite-node@2.1.3(@types/node@22.8.1): + vite-node@2.1.5(@types/node@22.9.1): dependencies: cac: 6.7.14 debug: 4.3.7 + es-module-lexer: 1.5.4 pathe: 1.1.2 - vite: 5.4.10(@types/node@22.8.1) + vite: 5.4.11(@types/node@22.9.1) transitivePeerDependencies: - '@types/node' - less @@ -5885,24 +6105,25 @@ snapshots: - supports-color - terser - vite@5.4.10(@types/node@22.8.1): + vite@5.4.11(@types/node@22.9.1): dependencies: esbuild: 0.21.5 postcss: 8.4.47 rollup: 4.24.0 optionalDependencies: - '@types/node': 22.8.1 + '@types/node': 22.9.1 fsevents: 2.3.3 - vitepress@1.4.1(@algolia/client-search@4.24.0)(@types/node@22.8.1)(axios@1.7.7)(postcss@8.4.47)(search-insights@2.17.2)(typescript@5.6.3): + vitepress@1.5.0(@algolia/client-search@4.24.0)(@types/node@22.9.1)(axios@1.7.7)(postcss@8.4.47)(search-insights@2.17.2)(typescript@5.6.3): dependencies: '@docsearch/css': 3.6.2 '@docsearch/js': 3.6.2(@algolia/client-search@4.24.0)(search-insights@2.17.2) - '@shikijs/core': 1.22.1 - '@shikijs/transformers': 1.22.1 - '@shikijs/types': 1.22.1 + '@iconify-json/simple-icons': 1.2.12 + '@shikijs/core': 1.23.1 + '@shikijs/transformers': 1.23.1 + '@shikijs/types': 1.23.1 '@types/markdown-it': 14.1.2 - '@vitejs/plugin-vue': 5.1.4(vite@5.4.10(@types/node@22.8.1))(vue@3.5.12(typescript@5.6.3)) + '@vitejs/plugin-vue': 5.1.4(vite@5.4.11(@types/node@22.9.1))(vue@3.5.12(typescript@5.6.3)) '@vue/devtools-api': 7.5.4 '@vue/shared': 3.5.12 '@vueuse/core': 11.1.0(vue@3.5.12(typescript@5.6.3)) @@ -5910,8 +6131,8 @@ snapshots: focus-trap: 7.6.0 mark.js: 8.11.1 minisearch: 7.1.0 - shiki: 1.22.1 - vite: 5.4.10(@types/node@22.8.1) + shiki: 1.23.1 + vite: 5.4.11(@types/node@22.9.1) vue: 3.5.12(typescript@5.6.3) optionalDependencies: postcss: 8.4.47 @@ -5943,29 +6164,30 @@ snapshots: - typescript - universal-cookie - vitest@2.1.3(@types/node@22.8.1): + vitest@2.1.5(@types/node@22.9.1): dependencies: - '@vitest/expect': 2.1.3 - '@vitest/mocker': 2.1.3(@vitest/spy@2.1.3)(vite@5.4.10(@types/node@22.8.1)) - '@vitest/pretty-format': 2.1.3 - '@vitest/runner': 2.1.3 - '@vitest/snapshot': 2.1.3 - '@vitest/spy': 2.1.3 - '@vitest/utils': 2.1.3 + '@vitest/expect': 2.1.5 + '@vitest/mocker': 2.1.5(vite@5.4.11(@types/node@22.9.1)) + '@vitest/pretty-format': 2.1.5 + '@vitest/runner': 2.1.5 + '@vitest/snapshot': 2.1.5 + '@vitest/spy': 2.1.5 + '@vitest/utils': 2.1.5 chai: 5.1.2 debug: 4.3.7 + expect-type: 1.1.0 magic-string: 0.30.12 pathe: 1.1.2 - std-env: 3.7.0 + std-env: 3.8.0 tinybench: 2.9.0 tinyexec: 0.3.1 tinypool: 1.0.1 tinyrainbow: 1.2.0 - vite: 5.4.10(@types/node@22.8.1) - vite-node: 2.1.3(@types/node@22.8.1) + vite: 5.4.11(@types/node@22.9.1) + vite-node: 2.1.5(@types/node@22.9.1) why-is-node-running: 2.3.0 optionalDependencies: - '@types/node': 22.8.1 + '@types/node': 22.9.1 transitivePeerDependencies: - less - lightningcss @@ -5981,10 +6203,10 @@ snapshots: dependencies: vue: 3.5.12(typescript@5.6.3) - vue-eslint-parser@9.4.3(eslint@9.13.0(jiti@1.21.6)): + vue-eslint-parser@9.4.3(eslint@9.15.0(jiti@1.21.6)): dependencies: debug: 4.3.7 - eslint: 9.13.0(jiti@1.21.6) + eslint: 9.15.0(jiti@1.21.6) eslint-scope: 7.2.2 eslint-visitor-keys: 3.4.3 espree: 9.6.1 @@ -6041,11 +6263,11 @@ snapshots: dependencies: eslint-visitor-keys: 3.4.3 lodash: 4.17.21 - yaml: 2.6.0 + yaml: 2.6.1 yaml@2.5.1: {} - yaml@2.6.0: {} + yaml@2.6.1: {} yargs-parser@21.1.1: {} diff --git a/src/cli.ts b/src/cli.ts index 0ab4e23..cb3f53c 100644 --- a/src/cli.ts +++ b/src/cli.ts @@ -6,16 +6,23 @@ import { prepareCmd, main as prepareFn, prepareOpts } from './commands/prepare.j import { addOptions, parseOptions } from './utils/commands.js' import { globalOpts } from './commands/global.js' +// const options = { +// fetchOpts: [ +// createOption('-b, --branch ', configSchema.shape.branch._def.description) +// .default(configSchema.shape.branch._def.defaultValue()), +// createOption('-g, --git-provider ', configSchema.shape.gitProvider._def.description) +// .default(configSchema.shape.gitProvider._def.defaultValue()), +// createOption('-r, --repos-filter ', configSchema.shape.reposFilter._def.description), +// ], +// } + export function getProgram() { const pm = new Command() .name('docpress') .description('Build your doc website faster than light ⚡️⚡️⚡️') .version(`${pkg.version}`) - .addCommand((fetchCmd)) - .addCommand(prepareCmd) - .addCommand(buildCmd) .action(async (opts, _cmd) => { - const parsedOpts = parseOptions(['fetch', 'prepare'], opts) + const parsedOpts = parseOptions('global', opts) await fetchFn(parsedOpts) await prepareFn(parsedOpts) await buildFn() @@ -27,6 +34,9 @@ export function getProgram() { }) .enablePositionalOptions() .passThroughOptions() + .addCommand(fetchCmd) + .addCommand(prepareCmd) + .addCommand(buildCmd) addOptions(pm, [...fetchOpts, ...prepareOpts, ...globalOpts]) @@ -35,5 +45,5 @@ export function getProgram() { export function main() { const pm = getProgram() - pm.parseAsync() + pm.parseAsync(process.argv) } diff --git a/src/commands/fetch.spec.ts b/src/commands/fetch.spec.ts index 935ea39..a90b776 100644 --- a/src/commands/fetch.spec.ts +++ b/src/commands/fetch.spec.ts @@ -3,6 +3,7 @@ import { createOption } from 'commander' import { fetchDoc } from '../lib/fetch.js' import { fetchOptsSchema } from '../schemas/fetch.js' import { log } from '../utils/logger.js' +import { configSchema } from '../schemas/global.js' import * as fetchMod from './fetch.js' import { globalOpts } from './global.js' @@ -38,7 +39,8 @@ describe('fetchCmd', () => { it('should call main function when action is triggered', async () => { fetchCmd.action(main) - await fetchCmd.parseAsync(['-u', 'testUser'], { from: 'user' }) + // await fetchCmd.parseAsync(['-U', 'testUser'], { from: 'user' }) + await fetchCmd.parseAsync() expect(main).toHaveBeenCalled() }) }) @@ -78,10 +80,10 @@ describe('fetchOpts', () => { const branchOption = fetchOpts.find(opt => opt.flags.includes('--branch')) const gitProviderOption = fetchOpts.find(opt => opt.flags.includes('--git-provider')) - expect(branchOption?.description).toBe(fetchOptsSchema.shape.branch._def.description) - expect(branchOption?.defaultValue).toBe(fetchOptsSchema.shape.branch._def.defaultValue()) + expect(branchOption?.description).toBe(fetchOptsSchema.innerType().shape.branch._def.description) + expect(branchOption?.defaultValue).toBe(configSchema.shape.branch._def.defaultValue()) - expect(gitProviderOption?.description).toBe(fetchOptsSchema.shape.gitProvider._def.description) - expect(gitProviderOption?.defaultValue).toBe(fetchOptsSchema.shape.gitProvider._def.defaultValue()) + expect(gitProviderOption?.description).toBe(fetchOptsSchema.innerType().shape.gitProvider._def.description) + expect(gitProviderOption?.defaultValue).toBe(configSchema.shape.gitProvider._def.defaultValue()) }) }) diff --git a/src/commands/fetch.ts b/src/commands/fetch.ts index 832ebc3..8ac0bac 100644 --- a/src/commands/fetch.ts +++ b/src/commands/fetch.ts @@ -1,31 +1,29 @@ import { createCommand, createOption } from 'commander' import { log } from '../utils/logger.js' import { addOptions, parseOptions } from '../utils/commands.js' -import { fetchOptsSchema } from '../schemas/fetch.js' +import type { FetchOpts } from '../schemas/fetch.js' import { fetchDoc } from '../lib/fetch.js' -import type { Config } from '../schemas/global.js' +import { configSchema } from '../schemas/global.js' import { globalOpts } from './global.js' const cmdName = 'fetch' export const fetchOpts = [ - createOption('-b, --branch ', fetchOptsSchema.shape.branch._def.description) - .default(fetchOptsSchema.shape.branch._def.defaultValue()), - createOption('-g, --git-provider ', fetchOptsSchema.shape.gitProvider._def.description) - .default(fetchOptsSchema.shape.gitProvider._def.defaultValue()), - createOption('-r, --repos-filter ', fetchOptsSchema.shape.reposFilter._def.description), - createOption('-T, --token ', fetchOptsSchema.shape.token._def.description), - createOption('-u, --username ', fetchOptsSchema.shape.username._def.description), + createOption('-b, --branch ', configSchema.shape.branch._def.description) + .default(configSchema.shape.branch._def.defaultValue()), + createOption('-g, --git-provider ', configSchema.shape.gitProvider._def.description) + .default(configSchema.shape.gitProvider._def.defaultValue()), + createOption('-r, --repos-filter ', configSchema.shape.reposFilter._def.description), ] export const fetchCmd = addOptions(createCommand(cmdName), [...fetchOpts, ...globalOpts]) .description('Fetch docs with the given username and git provider.') .action(async (opts) => { - const parsedOpts = parseOptions([cmdName], opts) + const parsedOpts = parseOptions(cmdName, opts) await main(parsedOpts) }) -export async function main(opts: Config) { +export async function main(opts: FetchOpts) { const { username, reposFilter, token, branch, gitProvider } = opts log(`\n-> Start fetching documentation files. This may take a moment, especially for larger repositories.`, 'info') diff --git a/src/commands/global.spec.ts b/src/commands/global.spec.ts index aba1410..afe1d26 100644 --- a/src/commands/global.spec.ts +++ b/src/commands/global.spec.ts @@ -1,30 +1,37 @@ -import { describe, expect, it, vi } from 'vitest' +import { describe, expect, it } from 'vitest' import { globalOptsSchema } from '../schemas/global.js' import { globalOpts } from './global.js' -vi.mock('../schemas/global.js', () => ({ - globalOptsSchema: { - shape: { - config: { - _def: { description: 'Path to the configuration file' }, - }, - }, - }, -})) +// vi.mock('../schemas/global.js', () => ({ +// globalOptsSchema: { +// shape: { +// config: { +// _def: { description: 'Path to the docpress configuration file.' }, +// }, +// token: { +// _def: { description: 'Git provider token used to collect data' }, +// }, +// username: { +// _def: { description: 'Git provider username used to collect data.' }, +// }, +// }, +// }, +// })) describe('globalOpts', () => { - it('should contain one option', () => { - expect(globalOpts).toHaveLength(1) + it('should contain 3 options', () => { + expect(globalOpts).toHaveLength(3) }) it('should define the correct option with flags and argument', () => { - const option = globalOpts[0] - expect(option.flags).toBe('-C, --config ') + expect(globalOpts[0].flags).toBe('-C, --config ') + expect(globalOpts[1].flags).toBe('-T, --token ') + expect(globalOpts[2].flags).toBe('-U, --username ') }) it('should have the correct description from the schema', () => { const option = globalOpts[0] - const expectedDescription = globalOptsSchema.shape.config._def.description + const expectedDescription = globalOptsSchema.innerType().shape.config._def.description expect(option.description).toBe(expectedDescription) }) }) diff --git a/src/commands/global.ts b/src/commands/global.ts index b89d620..c909566 100644 --- a/src/commands/global.ts +++ b/src/commands/global.ts @@ -2,5 +2,7 @@ import { createOption } from 'commander' import { globalOptsSchema } from '../schemas/global.js' export const globalOpts = [ - createOption('-C, --config ', globalOptsSchema.shape.config._def.description), + createOption('-C, --config ', globalOptsSchema.innerType().shape.config._def.description), + createOption('-T, --token ', globalOptsSchema.innerType().shape.token._def.description), + createOption('-U, --username ', globalOptsSchema.innerType().shape.username._def.description), ] diff --git a/src/commands/prepare.spec.ts b/src/commands/prepare.spec.ts index 6f0dbb6..fae06dc 100644 --- a/src/commands/prepare.spec.ts +++ b/src/commands/prepare.spec.ts @@ -1,7 +1,7 @@ import { resolve } from 'node:path' import { readFileSync } from 'node:fs' import { beforeEach, describe, expect, it, vi } from 'vitest' -import { DOCPRESS_DIR } from '../utils/const.js' +import { DOCPRESS_DIR, TEMPLATE_THEME, VITEPRESS_THEME, VITEPRESS_USER_THEME } from '../utils/const.js' import type { PrepareOpts } from '../schemas/prepare.js' import { prepareOptsSchema } from '../schemas/prepare.js' import { getUserInfos, getUserRepos } from '../utils/functions.js' @@ -142,10 +142,15 @@ describe('main', () => { expect(addContent).toHaveBeenCalledWith(mockOpts.extraPublicContent, resolve(DOCPRESS_DIR, 'public')) }) - it('should log and add extra theme if provided', async () => { + it('should log and add both template and extra themes if provided', async () => { await main(mockOpts) - expect(log).toHaveBeenCalledWith(` Add extras Vitepress theme files.`, 'info') - expect(addContent).toHaveBeenCalledWith(mockOpts.extraTheme, resolve(DOCPRESS_DIR, '.vitepress/theme')) + expect(log).toHaveBeenCalledWith(` Add Docpress theme files.`, 'info') + expect(addContent).toHaveBeenCalledWith(TEMPLATE_THEME, resolve(VITEPRESS_THEME)) + + if (mockOpts.extraTheme) { + expect(log).toHaveBeenCalledWith(` Add extras Vitepress theme files.`, 'info') + expect(addContent).toHaveBeenCalledWith(mockOpts.extraTheme, resolve(VITEPRESS_USER_THEME)) + } }) it('should call getVitepressConfig with sidebar, nav, and vitepressConfig options', async () => { @@ -164,7 +169,7 @@ describe('prepareOpts', () => { const extraThemeOption = prepareOpts.find(opt => opt.flags.includes('--extra-theme')) const extraHeaderPagesOption = prepareOpts.find(opt => opt.flags.includes('--extra-header-pages')) - expect(extraThemeOption?.description).toBe(prepareOptsSchema.shape.extraTheme._def.description) - expect(extraHeaderPagesOption?.description).toBe(prepareOptsSchema.shape.extraHeaderPages._def.description) + expect(extraThemeOption?.description).toBe(prepareOptsSchema.innerType().shape.extraTheme._def.description) + expect(extraHeaderPagesOption?.description).toBe(prepareOptsSchema.innerType().shape.extraHeaderPages._def.description) }) }) diff --git a/src/commands/prepare.ts b/src/commands/prepare.ts index cfc9bfa..3fdd5b0 100644 --- a/src/commands/prepare.ts +++ b/src/commands/prepare.ts @@ -1,12 +1,12 @@ import { resolve } from 'node:path' import { createCommand, createOption } from 'commander' -import { DOCPRESS_DIR } from '../utils/const.js' +import type { EnhancedRepository } from '../lib/fetch.js' +import { configSchema } from '../schemas/global.js' +import { DOCPRESS_DIR, TEMPLATE_THEME, VITEPRESS_THEME, VITEPRESS_USER_THEME } from '../utils/const.js' import { getUserInfos, getUserRepos } from '../utils/functions.js' import { addOptions, parseOptions } from '../utils/commands.js' import { getVitepressConfig } from '../lib/vitepress.js' -import { addContent, addExtraPages, generateVitepressFiles, transformDoc } from '../lib/prepare.js' -import { prepareOptsSchema } from '../schemas/prepare.js' -import type { EnhancedRepository } from '../lib/fetch.js' +import { addContent, addExtraPages, generateVitepressFiles, processForks, transformDoc } from '../lib/prepare.js' import type { Page } from '../lib/prepare.js' import type { PrepareOpts } from '../schemas/prepare.js' import { log } from '../utils/logger.js' @@ -15,45 +15,63 @@ import { globalOpts } from './global.js' const cmdName = 'prepare' export const prepareOpts = [ - createOption('-c, --extra-public-content ', prepareOptsSchema.shape.extraPublicContent._def.description), - createOption('-p, --extra-header-pages ', prepareOptsSchema.shape.extraHeaderPages._def.description), - createOption('-t, --extra-theme ', prepareOptsSchema.shape.extraTheme._def.description), - createOption('-v, --vitepress-config ', prepareOptsSchema.shape.vitepressConfig._def.description), + createOption('-c, --extra-public-content ', configSchema.shape.extraPublicContent._def.description), + createOption('-f, --forks', configSchema.shape.forks._def.description), + createOption('-p, --extra-header-pages ', configSchema.shape.extraHeaderPages._def.description), + createOption('-t, --extra-theme ', configSchema.shape.extraTheme._def.description), + createOption('-v, --vitepress-config ', configSchema.shape.vitepressConfig._def.description), ] export const prepareCmd = addOptions(createCommand(cmdName), [...prepareOpts, ...globalOpts]) .description('Transform doc to the target vitepress format.') .action(async (opts) => { - const parsedOpts = parseOptions([cmdName], opts) + const parsedOpts = parseOptions(cmdName, opts) await main(parsedOpts) }) export async function main(opts: PrepareOpts) { - const { extraHeaderPages, extraPublicContent, extraTheme, vitepressConfig } = opts + const { extraHeaderPages, extraPublicContent, extraTheme, vitepressConfig, forks, token, username } = opts log(`\n-> Start transform files to prepare Vitepress build.`, 'info') const user = getUserInfos() const repositories = getUserRepos() - .reduce((acc: EnhancedRepository[], cur) => { - if (cur.clone_url && !cur.fork && !cur.private && cur.docpress.includes.length && !cur.docpress.filtered) { - return [...acc, cur] + .reduce(({ internals, forks }: { internals: EnhancedRepository[], forks: EnhancedRepository[] }, cur) => { + const { clone_url, private: privateRepo, fork, docpress } = cur + if (clone_url && !privateRepo && !docpress.filtered) { + if (!fork && docpress.includes.length) { + return { internals: [...internals, cur], forks } + } else if (fork) { + return { internals, forks: [...forks, cur] } + } } - return acc - }, []) + return { internals, forks } + }, { internals: [], forks: [] }) - const { sidebar, index } = transformDoc(repositories, user) + const { sidebar, index } = transformDoc(repositories.internals, user) const nav: Page[] = [] if (extraHeaderPages) { - nav.push(...addExtraPages(extraHeaderPages)) + const pages = addExtraPages(extraHeaderPages) + if (forks) { + nav.push(...pages.filter(p => p.link !== '/forks')) + } else { + nav.push(...pages) + } } if (extraPublicContent) { log(` Add extras Vitepress public folder content.`, 'info') addContent(extraPublicContent, resolve(DOCPRESS_DIR, 'public')) } + log(` Add Docpress theme files.`, 'info') + addContent(TEMPLATE_THEME, resolve(VITEPRESS_THEME)) if (extraTheme) { log(` Add extras Vitepress theme files.`, 'info') - addContent(extraTheme, resolve(DOCPRESS_DIR, '.vitepress/theme')) + addContent(extraTheme, resolve(VITEPRESS_USER_THEME)) + } + if (forks && username) { + log(` Add fork page to display external contributions.`, 'info') + await processForks(repositories.forks, username, token) + nav.push({ text: 'Forks', link: '/forks' }) } const config = getVitepressConfig(sidebar, nav, vitepressConfig) diff --git a/src/env.d.ts b/src/env.d.ts new file mode 100644 index 0000000..91b861e --- /dev/null +++ b/src/env.d.ts @@ -0,0 +1,12 @@ +/// + +interface ImportMeta { + glob: (pattern: string, options?: { eager?: boolean }) => Record Promise> + globEager: (pattern: string) => Record +} + +declare module '*.vue' { + import type { DefineComponent } from 'vue' + const component: DefineComponent<{}, {}, any> + export default component +} \ No newline at end of file diff --git a/src/lib/fetch.spec.ts b/src/lib/fetch.spec.ts index 2b88b90..8fb8266 100644 --- a/src/lib/fetch.spec.ts +++ b/src/lib/fetch.spec.ts @@ -130,8 +130,14 @@ describe('getDoc', () => { await getDoc(repos, ['repo1']) - expect(cloneRepo).toHaveBeenCalledWith('https://github.com/testUser/repo1', '/path/to/repo1', 'main', ['README.md']) - expect(cloneRepo).not.toHaveBeenCalledWith('https://github.com/testUser/repo2', expect.anything(), expect.anything(), expect.anything()) + expect(cloneRepo).toHaveBeenCalledWith( + repos[0].name, + repos[0].clone_url, + repos[0].docpress.projectPath, + repos[0].docpress.branch, + repos[0].docpress.includes, + ) + expect(cloneRepo).not.toHaveBeenCalledWith(repos[1].name, expect.anything(), expect.anything(), expect.anything(), expect.anything()) }) it('should warn if no repositories are provided', async () => { @@ -200,7 +206,7 @@ describe('enhanceRepositories', () => { expect(getResult('repo1')?.docpress.filtered).toBe(false) expect(getResult('repo2')?.docpress.filtered).toBe(true) expect(getResult('repo3')?.docpress.filtered).toBe(true) - expect(getResult('repo4')?.docpress.filtered).toBe(true) + expect(getResult('repo4')?.docpress.filtered).toBe(false) expect(getResult('repo5')?.docpress.filtered).toBe(true) }) }) diff --git a/src/lib/fetch.ts b/src/lib/fetch.ts index 135b584..fa6a996 100644 --- a/src/lib/fetch.ts +++ b/src/lib/fetch.ts @@ -1,5 +1,6 @@ import { resolve } from 'node:path' import { writeFileSync } from 'node:fs' +import type { GlobalOpts } from '../schemas/global.js' import type { FetchOpts } from '../schemas/fetch.js' import { checkHttpStatus, createDir } from '../utils/functions.js' import { DOCPRESS_DIR, DOCS_DIR, USER_INFOS, USER_REPOS_INFOS } from '../utils/const.js' @@ -17,7 +18,7 @@ export type EnhancedRepository = Awaited>['repos'][n } } -export async function checkDoc(repoOwner: FetchOpts['username'], repoName: string, branch: FetchOpts['branch']) { +export async function checkDoc(repoOwner: GlobalOpts['username'], repoName: string, branch: FetchOpts['branch']) { const rootReadmeUrl = `https://github.com/${repoOwner}/${repoName}/tree/${branch}/README.md` const docsFolderUrl = `https://github.com/${repoOwner}/${repoName}/tree/${branch}/docs` const docsReadmeUrl = `https://github.com/${repoOwner}/${repoName}/tree/${branch}/docs/01-readme.md` @@ -107,8 +108,7 @@ export async function getDoc(repos?: EnhancedRepository[], reposFilter?: FetchOp repos .filter(repo => !isRepoFiltered(repo, reposFilter)) .map(async (repo) => { - log(` Clone repository '${repo.name}'.`, 'info') - await cloneRepo(repo.clone_url as string, repo.docpress.projectPath, repo.docpress.branch, repo.docpress.includes) + await cloneRepo(repo.name, repo.clone_url as string, repo.docpress.projectPath, repo.docpress.branch, repo.docpress.includes) }), ) } @@ -119,6 +119,7 @@ export function isRepoFiltered(repo: EnhancedRepository | Awaited repo.name === filter.substring(1)) const isIncluded = !reposFilter || reposFilter?.filter(filter => !filter.startsWith('!')).includes(repo.name) + || (repo.fork && !isExcluded) || (hasOnlyExclusions && !isExcluded) const isFiltered = isExcluded || !isIncluded @@ -127,7 +128,7 @@ export function isRepoFiltered(repo: EnhancedRepository | Awaited ({ resolve: vi.fn((...args) => args.join('/')) })) vi.mock('../utils/functions.js') +vi.mock('../utils/logger.js') describe('getInfos', () => { const mockUser = { login: 'testUser' } @@ -40,6 +43,75 @@ describe('getInfos', () => { }) }) +describe('getContributors', () => { + const mockRepository = { + name: 'repo1', + owner: { login: 'testOwner' }, + } as EnhancedRepository + const mockToken = 'testToken' + + beforeEach(() => { + vi.clearAllMocks() + }) + + it('should return repository source and contributors on success', async () => { + const mockRepoData = { source: { owner: { login: 'test-source-owner' } }, name: 'repo1' } + const mockContributorsData = [{ login: 'contributor1' }, { login: 'contributor2' }] + + const mockOctokit = { + request: vi.fn() + .mockResolvedValueOnce({ data: mockRepoData }) + .mockResolvedValueOnce({ data: mockContributorsData }), + } + + ;(Octokit as any).mockImplementation(() => mockOctokit) + + const result = await getContributors({ repository: mockRepository, token: mockToken }) + + expect(result).toEqual({ + source: mockRepoData.source, + contributors: mockContributorsData, + }) + }) + + it('should log a warning and return empty contributors if contributors request fails with non-404', async () => { + const mockRepoData = { source: { owner: { login: 'test-source-owner' } }, name: 'repo1' } + + const mockOctokit = { + request: vi.fn() + .mockResolvedValueOnce({ data: mockRepoData }) + .mockRejectedValueOnce(new Error('500 Server Error')), + } + + ;(Octokit as any).mockImplementation(() => mockOctokit) + + const result = await getContributors({ repository: mockRepository, token: mockToken }) + + expect(log).toHaveBeenCalledWith(` Failed to get contributors infos for repository '${mockRepository.name}'.`, 'warn') + expect(result).toEqual({ + source: mockRepoData.source, + contributors: [], + }) + }) + + it('should return empty contributors if source owner login is undefined', async () => { + const mockRepoData = { source: { owner: {} }, name: 'repo1' } + + const mockOctokit = { + request: vi.fn().mockResolvedValueOnce({ data: mockRepoData }), + } + + ;(Octokit as any).mockImplementation(() => mockOctokit) + + const result = await getContributors({ repository: mockRepository, token: mockToken }) + + expect(result).toEqual({ + source: mockRepoData.source, + contributors: [], + }) + }) +}) + describe('cloneRepo', () => { beforeEach(() => { vi.clearAllMocks() @@ -55,11 +127,12 @@ describe('cloneRepo', () => { ;(simpleGit as any).mockImplementation(() => mockGit) + const repoName = 'repo1' const projectDir = 'testDir' const includes = ['docs/file1.md', 'docs/file2.md'] const branch = 'main' - await cloneRepo('https://github.com/testUser/repo.git', projectDir, branch, includes) + await cloneRepo(repoName, 'https://github.com/testUser/repo.git', projectDir, branch, includes) expect(createDir).toHaveBeenCalledWith(projectDir, { clean: true }) expect(mockGit.init).toHaveBeenCalled() @@ -80,11 +153,12 @@ describe('cloneRepo', () => { ;(simpleGit as any).mockImplementation(() => mockGit) + const repoName = 'repo1' const projectDir = 'testDir' const includes = ['docs/file1.md'] const branch = 'main' - await cloneRepo('https://github.com/testUser/repo.git', projectDir, branch, includes) + await cloneRepo(repoName, 'https://github.com/testUser/repo.git', projectDir, branch, includes) expect(cpSync).toHaveBeenCalledWith(`${projectDir}/docs`, projectDir, { recursive: true }) expect(rmSync).toHaveBeenCalledWith(`${projectDir}/docs`, { recursive: true }) @@ -100,11 +174,12 @@ describe('cloneRepo', () => { ;(simpleGit as any).mockImplementation(() => mockGit) - const consoleSpy = vi.spyOn(console, 'error').mockImplementation(() => {}) + // const consoleSpy = vi.spyOn(console, 'error').mockImplementation(() => {}) - await cloneRepo('https://github.com/testUser/repo.git', 'testDir', 'main', ['docs']) + await cloneRepo('repo1', 'https://github.com/testUser/repo.git', 'testDir', 'main', ['docs']) - expect(consoleSpy).toHaveBeenCalledWith(expect.stringContaining(`Error when cloning repository: ${gitError}`)) - consoleSpy.mockRestore() + // expect(consoleSpy).toHaveBeenCalledWith(expect.stringContaining(`Error when cloning repository: ${gitError}`)) + // consoleSpy.mockRestore() + expect(log).toHaveBeenCalledWith(expect.stringContaining(`Error when cloning repository: ${gitError}`), 'error') }) }) diff --git a/src/lib/git.ts b/src/lib/git.ts index 1884074..2440eaf 100644 --- a/src/lib/git.ts +++ b/src/lib/git.ts @@ -1,21 +1,53 @@ import { appendFileSync, cpSync, rmSync } from 'node:fs' import { resolve } from 'node:path' -import { Octokit } from 'octokit' +import { Octokit } from '@octokit/rest' import { simpleGit } from 'simple-git' -import { createDir } from '../utils/functions.js' +import type { GetResponseTypeFromEndpointMethod } from '@octokit/types' +import type { GlobalOpts } from '../schemas/global.js' import type { FetchOpts } from '../schemas/fetch.js' +import { createDir } from '../utils/functions.js' import { log } from '../utils/logger.js' +import type { EnhancedRepository } from './fetch.js' -export async function getInfos({ username, token, branch }: Pick) { +export async function getInfos({ username, token, branch }: Pick & Pick & Required>) { log(` Get infos for username '${username}'.`, 'info') const octokit = new Octokit({ auth: token }) + log(` Get user infos.`, 'debug') const { data: user } = await octokit.request('GET /users/{username}', { username }) + log(` Get repositories infos.`, 'debug') const { data: repos } = await octokit.request('GET /users/{username}/repos', { username, sort: 'full_name' }) return { user, repos, branch } } -export async function cloneRepo(url: string, projectDir: string, branch: string, includes: string[]) { +export async function getContributors({ repository, token }: { repository: EnhancedRepository, token: GlobalOpts['token'] }) { + log(` Get contributors infos for repository '${repository.name}'.`, 'info') + const octokit = new Octokit({ + auth: token, + log: { + debug: () => {}, + info: () => {}, + warn: (message) => { + if (!message.includes('404')) console.warn(message) + }, + error: (message) => { + if (!message.includes('404')) console.error(message) + }, + }, + }) + const { data: repo } = await octokit.request('GET /repos/{owner}/{repo}', { owner: repository.owner.login, repo: repository.name }) + if (!repo.source?.owner.login) { + return { source: repo.source, contributors: [] } + } + const { data: contributors } = await octokit.request('GET /repos/{owner}/{repo}/contributors', { owner: repo.source.owner.login, repo: repo.name }).catch((_error) => { + log(` Failed to get contributors infos for repository '${repository.name}'.`, 'warn') + return { data: [] } as unknown as GetResponseTypeFromEndpointMethod + }) + + return { source: repo.source, contributors } +} + +export async function cloneRepo(name: string, url: string, projectDir: string, branch: string, includes: string[]) { createDir(projectDir, { clean: true }) try { @@ -25,9 +57,11 @@ export async function cloneRepo(url: string, projectDir: string, branch: string, .addRemote('origin', url) for (const item of includes) { + log(` Add '${item}' to '${name}' sparse-checkout file.`, 'debug') appendFileSync(resolve(projectDir, '.git/info/sparse-checkout'), `${item}\n`, 'utf8') } + log(` Clone repository '${name}'.`, 'info') await git.pull('origin', branch) if (includes.some(item => item.includes('docs'))) { diff --git a/src/lib/prepare.spec.ts b/src/lib/prepare.spec.ts index 640dad6..91a5869 100644 --- a/src/lib/prepare.spec.ts +++ b/src/lib/prepare.spec.ts @@ -1,10 +1,12 @@ import type { Dirent } from 'node:fs' -import { cpSync, readdirSync, readFileSync, statSync, writeFileSync } from 'node:fs' -import { beforeEach, describe, expect, it, vi } from 'vitest' +import { cpSync, existsSync, mkdirSync, readdirSync, readFileSync, rmSync, statSync, writeFileSync } from 'node:fs' +import { resolve } from 'node:path' +import { afterAll, beforeAll, beforeEach, describe, expect, it, vi } from 'vitest' import { getMdFiles, type getUserInfos, type getUserRepos } from '../utils/functions.js' import { addContent, addExtraPages, + addForkPage, // addSources, generateFeatures, generateIndex, @@ -12,8 +14,11 @@ import { generateSidebarProject, generateVitepressFiles, parseVitepressConfig, + processForks, transformDoc, } from './prepare.js' +import type { EnhancedRepository } from './fetch.js' +import type { getInfos } from './git.js' vi.mock('node:fs') vi.mock('../utils/regex.js') @@ -24,11 +29,27 @@ vi.mock('../utils/functions.js', async importOriginal => ({ getMdFiles: vi.fn(), })) vi.mock('../utils/const.js', () => ({ - DOCS_DIR: '/mock/docs', - INDEX_FILE: '/mock/docs/index.md', - VITEPRESS_CONFIG: '/mock/config.js', + DOCS_DIR: '/tmp/docpress/mock/docs', + INDEX_FILE: '/tmp/docpress/mock/docs/index.md', + FORKS_FILE: '/tmp/docpress/mock/docs/forks.md', + VITEPRESS_CONFIG: '/tmp/docpress/mock/config.js', +})) +vi.mock('./git.js', async importOriginal => ({ + ...await importOriginal(), + getContributors: () => ({ + source: { + name: 'test-repo', + owner: { login: 'test-user' }, + html_url: 'https://github.com/test/repo', + description: 'Test repo description', + stargazers_count: 10, + }, + contributors: [{ login: 'test-user', contributions: 10 }], + }), })) +const tempDir = resolve(__dirname, 'temp-test-dir') + // describe('addSources', () => { // it('should append source content to a file', () => { // addSources('https://example.com/repo', '/mock/output/readme.md') @@ -201,7 +222,7 @@ describe('addContent', () => { describe('parseVitepressConfig', () => { it('should parse Vitepress configuration from JSON file', () => { - vi.mocked(readFileSync).mockReturnValue(JSON.stringify({ title: 'My Project' })) + vi.mocked(readFileSync).mockReturnValueOnce(JSON.stringify({ title: 'My Project' })) const config = parseVitepressConfig('/mock/config.json') expect(config).toEqual({ title: 'My Project' }) }) @@ -218,12 +239,99 @@ describe('generateVitepressFiles', () => { generateVitepressFiles(vitepressConfig, index) expect(writeFileSync).toHaveBeenCalledWith( - '/mock/config.js', + '/tmp/docpress/mock/config.js', expect.stringContaining('export default {'), ) expect(writeFileSync).toHaveBeenCalledWith( - '/mock/docs/index.md', + '/tmp/docpress/mock/docs/index.md', expect.stringContaining('layout: home'), ) }) }) + +describe('addForkPage', () => { + beforeAll(() => { + if (!existsSync(tempDir)) mkdirSync(tempDir) + }) + afterAll(() => { + rmSync(tempDir, { recursive: true, force: true }) + }) + + const mockForks = [ + { + repository: { + name: 'example-repo', + owner: { login: 'example-user' }, + html_url: 'https://github.com/example/repo', + description: 'An example repository', + stargazers_count: 42, + }, + contributions: 5, + }, + ] as { repository: Awaited>['repos'][number], contributions: number }[] + + it('should generate a forks page file', () => { + addForkPage(mockForks) + + expect(writeFileSync).toHaveBeenCalledWith( + '/tmp/docpress/mock/docs/forks.md', + expect.stringContaining('layout: fork-page'), + ) + expect(writeFileSync).toHaveBeenCalledWith( + '/tmp/docpress/mock/docs/forks.md', + expect.stringContaining('example-repo'), + ) + expect(writeFileSync).toHaveBeenCalledWith( + '/tmp/docpress/mock/docs/forks.md', + expect.stringContaining('example-user'), + ) + expect(writeFileSync).toHaveBeenCalledWith( + '/tmp/docpress/mock/docs/forks.md', + expect.stringContaining('example-user'), + ) + expect(writeFileSync).toHaveBeenCalledWith( + '/tmp/docpress/mock/docs/forks.md', + expect.stringContaining('An example repository'), + ) + expect(writeFileSync).toHaveBeenCalledWith( + '/tmp/docpress/mock/docs/forks.md', + expect.stringContaining('5'), + ) + }) +}) + +describe('processForks', () => { + beforeAll(() => { + if (!existsSync(tempDir)) mkdirSync(tempDir) + }) + afterAll(() => { + rmSync(tempDir, { recursive: true, force: true }) + }) + + const mockRepositories = [ + { + name: 'test-repo', + owner: { login: 'test-user' }, + docpress: { projectPath: '/test/path', branch: 'main' }, + html_url: 'https://github.com/test/repo', + }, + ] as EnhancedRepository[] + const mockUsername = 'test-user' + + it('should process forks and generate the forks page', async () => { + await processForks(mockRepositories, mockUsername) + + expect(writeFileSync).toHaveBeenCalledWith( + '/tmp/docpress/mock/docs/forks.md', + expect.stringContaining('test-repo'), + ) + expect(writeFileSync).toHaveBeenCalledWith( + '/tmp/docpress/mock/docs/forks.md', + expect.stringContaining('test-user'), + ) + expect(writeFileSync).toHaveBeenCalledWith( + '/tmp/docpress/mock/docs/forks.md', + expect.stringContaining('Test repo description'), + ) + }) +}) diff --git a/src/lib/prepare.ts b/src/lib/prepare.ts index 973ec49..4273339 100644 --- a/src/lib/prepare.ts +++ b/src/lib/prepare.ts @@ -2,12 +2,15 @@ import { basename, dirname, parse, resolve } from 'node:path' import { appendFileSync, cpSync, readdirSync, readFileSync, renameSync, statSync, writeFileSync } from 'node:fs' import YAML from 'yaml' import type { defineConfig } from 'vitepress' +import type { GlobalOpts } from '../schemas/global.js' import type { getUserInfos } from '../utils/functions.js' import { createDir, extractFiles, getMdFiles, prettify } from '../utils/functions.js' -import { DOCS_DIR, INDEX_FILE, VITEPRESS_CONFIG } from '../utils/const.js' +import { DOCS_DIR, FORKS_FILE, INDEX_FILE, VITEPRESS_CONFIG } from '../utils/const.js' import { replaceReadmePath, replaceRelativePath } from '../utils/regex.js' import { log } from '../utils/logger.js' import type { EnhancedRepository } from './fetch.js' +import type { getInfos } from './git.js' +import { getContributors } from './git.js' export interface Page { text: string @@ -98,6 +101,7 @@ export function transformDoc(repositories: EnhancedRepository[], user: ReturnTyp for (const repository of repositories) { log(` Replace urls for repository '${repository.name}'.`, 'info') getMdFiles([repository.docpress.projectPath]).forEach((file) => { + log(` Processing file '${basename(file)}' for repository '${repository.name}'.`, 'debug') replaceRelativePath(file, `https://github.com/${repository.owner.login}/${repository.name}/tree/${repository.docpress.branch}`) if (basename(file).toLowerCase() === 'readme.md') { @@ -127,6 +131,7 @@ export function transformDoc(repositories: EnhancedRepository[], user: ReturnTyp let sourceFile if (arr.length > 1) { sourceFile = resolve(repository.docpress.projectPath, 'sources.md') + log(` Add sources for repository '${repository.name}'.`, 'info') addSources(repository.html_url, sourceFile) return generateSidebarPages( @@ -160,6 +165,7 @@ export function addExtraPages(paths: string[]) { log(` Add extras Vitepress headers pages.`, 'info') for (const file of files) { + log(` Processing file '${file}'.`, 'debug') const src = resolve(process.cwd(), file) const dest = resolve(DOCS_DIR, prettify(basename(file), { mode: 'lowercase', removeIdx: true })) cpSync(src, dest) @@ -177,8 +183,10 @@ export function addContent(paths: string | string[], dir: string, fn?: () => voi const files = extractFiles(absolutePath) for (const file of files) { + const formattedFile = file.replace(absolutePath, '.') + log(` Processing file '${formattedFile}' for entry '${path}'.`, 'debug') const src = resolve(process.cwd(), file) - const dest = resolve(dir, file.replace(absolutePath, '.')) + const dest = resolve(dir, formattedFile) if (fn) { fn() } @@ -187,6 +195,38 @@ export function addContent(paths: string | string[], dir: string, fn?: () => voi } } +export function addForkPage(forks: { repository: Awaited>['repos'][number], contributions: number }[]) { + const separator = '---\n' + const header = 'layout: fork-page\nrepoList:\n' + const text = '\n# External contributions\n\nThis gallery is a visual representation of the collaborative work done across a variety of open-source projects, each driven by a shared passion for innovation and community growth.\n\nEvery tile below represents a unique project where contributions have been made-ranging from code enhancements to documentation improvements. Each project includes a summary of its goals, features, and links to GitHub for direct access.\n\nThis page serves as both a portfolio of past work and a resource for revisiting projects that have made a meaningful impact.\n' + const frontmatter = forks.map(({ repository, contributions }) => { + const { name, owner, html_url, description, stargazers_count } = repository + return { name, owner: owner.login, html_url, description, stargazers_count, contributions } + }) + log(` Generate forks page.`, 'info') + writeFileSync(FORKS_FILE, separator.concat(header, YAML.stringify(frontmatter), separator, text)) +} + +type Source = Required>['source']> + +type AdaptedLicense = Omit['license'], 'spdx_id'> & { spdx_id?: string } + +type AdaptedRepository = Omit, 'license'> & { license: AdaptedLicense } + +export async function processForks(repositories: EnhancedRepository[], username: GlobalOpts['username'], token?: GlobalOpts['token']) { + const forks = await Promise.all( + repositories.map(async (repository) => { + const { source, contributors } = await getContributors({ repository, token }) + return { + contributions: contributors?.find(contributor => contributor.login === username)?.contributions ?? 0, + repository: source as AdaptedRepository, + } + }), + ).then(f => f.filter(({ repository, contributions }) => !!repository && contributions)) + + addForkPage(forks) +} + export function parseVitepressConfig(path: string) { return JSON.parse(readFileSync(resolve(process.cwd(), path)).toString()) } diff --git a/src/schemas/build.ts b/src/schemas/build.ts index 05594fe..4b23ba3 100644 --- a/src/schemas/build.ts +++ b/src/schemas/build.ts @@ -1,5 +1,6 @@ -import { z } from 'zod' +import { cliSchema } from './global.js' -export const buildOptsSchema = z.object({}) +export const buildOptsSchema = cliSchema + .pick({}) export type BuildOpts = Zod.infer diff --git a/src/schemas/fetch.ts b/src/schemas/fetch.ts index fdaa5a2..c1391a5 100644 --- a/src/schemas/fetch.ts +++ b/src/schemas/fetch.ts @@ -1,27 +1,15 @@ -import { z } from 'zod' -import { prettifyEnum } from '../utils/functions.js' +// import type { GlobalOpts } from './global.js' +import { applyGlobalOptsTransform, cliSchema } from './global.js' -const providers = ['github'] as const - -export const fetchOptsSchema = z.object({ - branch: z.string() - .describe('Branch used to collect Git provider data.') - .optional() - .default('main'), - gitProvider: z.enum(providers) - .describe(`Git provider used to retrieve data. Values should be ${prettifyEnum(providers)}.`) - .optional() - .default('github'), - reposFilter: z.string() - .transform(repos => repos.split(',')) - .or(z.string().array()) - .describe('List of comma separated repositories to retrieve from Git provider. Default to all user\'s public repositories.') - .optional(), - token: z.string() - .describe('Git provider token used to collect data.') - .optional(), - username: z.string() - .describe('Git provider username used to collect data.'), -}) +export const fetchOptsSchema = cliSchema + .pick({ + branch: true, + config: true, + gitProvider: true, + reposFilter: true, + token: true, + username: true, + }) + .transform(applyGlobalOptsTransform) export type FetchOpts = Zod.infer diff --git a/src/schemas/global.ts b/src/schemas/global.ts index 5ad7bde..74b99ff 100644 --- a/src/schemas/global.ts +++ b/src/schemas/global.ts @@ -1,23 +1,101 @@ -import { readFileSync } from 'node:fs' -import { resolve } from 'node:path' import { z } from 'zod' -import { buildOptsSchema } from './build.js' -import type { FetchOpts } from './fetch.js' -import { fetchOptsSchema } from './fetch.js' -import type { PrepareOpts } from './prepare.js' -import { prepareOptsSchema } from './prepare.js' - -export const globalOptsSchema = z.object({ - config: z.string() - .describe('Path to the docpress configuration file.') - .transform(path => JSON.parse(readFileSync(resolve(process.cwd(), path)).toString()) as FetchOpts & PrepareOpts) - .optional(), -}) +import { fromZodError } from 'zod-validation-error' +import type { UserConfig } from 'vitepress' +import { loadConfigFile, prettifyEnum, splitByComma } from '../utils/functions.js' +import { log } from '../utils/logger.js' -export type GlobalOpts = Zod.infer +const providers = ['github'] as const -export const configSchema = fetchOptsSchema - .merge(prepareOptsSchema) - .merge(buildOptsSchema) +export const configSchema = z.object({ + // Global + username: z.string() + .describe('Git provider username used to collect data.'), + // Fetch + branch: z.string() + .describe('Branch used to collect Git provider data.') + .default('main'), + gitProvider: z.enum(providers) + .describe(`Git provider used to retrieve data. Values should be ${prettifyEnum(providers)}.`) + .default('github'), + reposFilter: z.array(z.string()) + .describe('List of comma separated repositories to retrieve from Git provider. Default to all user\'s public repositories.'), + // Prepare + extraHeaderPages: z.array(z.string()) + .describe('List of comma separated additional files or directories to process Vitepress header pages.'), + extraPublicContent: z.array(z.string()) + .describe('List of comma separated additional files or directories to process Vitepress public folder.'), + extraTheme: z.array(z.string()) + .describe('List of comma separated additional files or directories to use as Vitepress theme.'), + forks: z.boolean() + .describe('Whether or not to create the dedicated fork page that aggregate external contributions.') + .default(false), + vitepressConfig: z.any() + .describe('Path to the vitepress configuration file.'), +}) export type Config = Zod.infer + +export function applyGlobalOptsTransform(data: Cli) { + const { config, vitepressConfig, token, ...rest } = data + + try { + const loadedDPConfig = configSchema.partial().parse(loadConfigFile(config)) as Config + const loadedVPConfig = loadConfigFile(vitepressConfig) as UserConfig + const defaultConfig = configSchema.partial().required({ branch: true, gitProvider: true }).parse({}) + const vpConfig = { + ...loadedVPConfig, + ...loadedDPConfig.vitepressConfig, + } + const mergedConfig = { + ...loadedDPConfig, + ...defaultConfig, + ...rest, + ...(Object.keys(vpConfig).length && { vitepressConfig: vpConfig }), + } + const parsedConfig = configSchema.partial().required({ username: true }).parse(mergedConfig) + + return { ...parsedConfig, token } + } catch (error) { + log(` An error occurred while checking configuration.\n ${fromZodError(error).toString()}`, 'error') + process.exit(1) + } +} + +export const cliSchema = configSchema + .partial() + .extend({ + config: z.string() + .describe('Path to the docpress configuration file.') + .optional(), + token: z.string() + .describe('Git provider token used to collect data.') + .optional(), + reposFilter: z.string() + .describe(configSchema.shape.reposFilter.description || '') + .transform(splitByComma) + .optional(), + extraHeaderPages: z.string() + .describe(configSchema.shape.extraHeaderPages.description || '') + .transform(splitByComma) + .optional(), + extraPublicContent: z.string() + .describe(configSchema.shape.extraPublicContent.description || '') + .transform(splitByComma) + .optional(), + extraTheme: z.string() + .describe(configSchema.shape.extraTheme.description || '') + .transform(splitByComma) + .optional(), + vitepressConfig: z.string() + .describe(configSchema.shape.vitepressConfig.description || '') + .optional(), + }) + +export type Cli = Zod.infer + +export const globalOptsSchema = cliSchema + .partial() + .transform(applyGlobalOptsTransform) + +export type GlobalOpts = Required, 'branch' | 'gitProvider'>> + & Omit, 'branch' | 'gitProvider'> diff --git a/src/schemas/prepare.ts b/src/schemas/prepare.ts index 9020658..adb2612 100644 --- a/src/schemas/prepare.ts +++ b/src/schemas/prepare.ts @@ -1,25 +1,18 @@ -import { readFileSync } from 'node:fs' -import { resolve } from 'node:path' -import type { defineConfig } from 'vitepress' -import { z } from 'zod' +// import type { GlobalOpts } from './global.js' +import { applyGlobalOptsTransform, cliSchema } from './global.js' -export const prepareOptsSchema = z.object({ - extraHeaderPages: z.string() - .describe('List of comma separated additional files or directories to process Vitepress header pages.') - .transform(paths => paths.split(',')) - .optional(), - extraPublicContent: z.string() - .describe('List of comma separated additional files or directories to process Vitepress public folder.') - .transform(paths => paths.split(',')) - .optional(), - extraTheme: z.string() - .describe('List of comma separated additional files or directories to use as Vitepress theme.') - .transform(paths => paths.split(',')) - .optional(), - vitepressConfig: z.string() - .describe('Path to the vitepress configuration file.') - .transform(path => JSON.parse(readFileSync(resolve(process.cwd(), path)).toString()) as ReturnType) - .optional(), -}) +export const prepareOptsSchema = cliSchema + .pick({ + extraHeaderPages: true, + extraPublicContent: true, + extraTheme: true, + config: true, + gitProvider: true, + reposFilter: true, + token: true, + username: true, + vitepressConfig: true, + }) + .transform(applyGlobalOptsTransform) export type PrepareOpts = Zod.infer diff --git a/src/schemas/schemas.spec.ts b/src/schemas/schemas.spec.ts index 7e61bd5..60c9760 100644 --- a/src/schemas/schemas.spec.ts +++ b/src/schemas/schemas.spec.ts @@ -6,6 +6,12 @@ import { prepareOptsSchema } from './prepare.js' vi.mock('fs') +const defaultConfig = { + branch: 'main', + token: undefined, + gitProvider: 'github', +} + describe('globalOptsSchema', () => { it('should validate valid global options', () => { const validData = { @@ -16,7 +22,6 @@ describe('globalOptsSchema', () => { branch: 'main', gitProvider: 'github', reposFilter: ['repo1', 'repo2'], - token: 'your_token', username: 'your_username', extraHeaderPages: ['header1.md', 'header2.md'], extraPublicContent: ['public1', 'public2'], @@ -25,38 +30,77 @@ describe('globalOptsSchema', () => { const result = globalOptsSchema.parse(validData) expect(result).toEqual({ - ...validData, - config: { - branch: 'main', - gitProvider: 'github', - reposFilter: ['repo1', 'repo2'], - token: 'your_token', - username: 'your_username', - extraHeaderPages: ['header1.md', 'header2.md'], - extraPublicContent: ['public1', 'public2'], - extraTheme: ['theme1', 'theme2'], - }, + ...defaultConfig, + branch: 'main', + gitProvider: 'github', + reposFilter: ['repo1', 'repo2'], + username: 'your_username', + extraHeaderPages: ['header1.md', 'header2.md'], + extraPublicContent: ['public1', 'public2'], + extraTheme: ['theme1', 'theme2'], }) }) - it('should throw an error for invalid config path', () => { + it('should return empty config for invalid config path', () => { const invalidData = { config: './invalid-config.json', + username: 'user1', } vi.mocked(readFileSync).mockImplementation(() => { throw new Error('File not found') }) - expect(() => globalOptsSchema.parse(invalidData)).toThrow('File not found') + const result = globalOptsSchema.parse(invalidData) + expect(result).toEqual({ + ...defaultConfig, + username: invalidData.username, + }) }) it('should handle optional config field correctly', () => { - const partialData = {} + const partialData = { + username: 'user1', + } const result = globalOptsSchema.parse(partialData) expect(result).toEqual({ - config: undefined, + ...defaultConfig, + username: partialData.username, + }) + }) + + it('should correctly split comma-separated strings into arrays', () => { + const dataWithCommaSeparatedValues = { + username: 'user1', + reposFilter: 'repo1,repo2,repo3', + extraHeaderPages: 'header1.md,header2.md', + extraPublicContent: 'public1,public2', + extraTheme: 'theme1,theme2', + } + + const result = globalOptsSchema.parse(dataWithCommaSeparatedValues) + expect(result).toEqual({ + ...defaultConfig, + username: 'user1', + reposFilter: ['repo1', 'repo2', 'repo3'], + extraHeaderPages: ['header1.md', 'header2.md'], + extraPublicContent: ['public1', 'public2'], + extraTheme: ['theme1', 'theme2'], + }) + }) + + it('should apply defaults for branch and gitProvider if not provided', () => { + const dataWithoutBranchAndGitProvider = { + username: 'user1', + reposFilter: 'repo1', + } + + const result = globalOptsSchema.parse(dataWithoutBranchAndGitProvider) + expect(result).toEqual({ + ...defaultConfig, + username: 'user1', + reposFilter: ['repo1'], }) }) }) @@ -66,32 +110,77 @@ describe('configSchema', () => { const validCombinedData = { branch: 'main', gitProvider: 'github', - reposFilter: 'repo1,repo2', + forks: false, token: 'your_token', username: 'your_username', - extraHeaderPages: 'header1.md,header2.md', - extraPublicContent: 'public1,public2', - extraTheme: 'theme1,theme2', - } - const transformedData = { reposFilter: ['repo1', 'repo2'], extraHeaderPages: ['header1.md', 'header2.md'], extraPublicContent: ['public1', 'public2'], extraTheme: ['theme1', 'theme2'], } + const { token: _token, ...dataWithoutToken } = validCombinedData const result = configSchema.parse(validCombinedData) - expect(result).toEqual({ ...validCombinedData, ...transformedData }) + expect(result).toEqual(dataWithoutToken) }) it('should throw an error for missing required fields', () => { + const invalidData = {} + + expect(() => configSchema.parse(invalidData)).toThrow() + }) + + it('should return default values when fields are missing', () => { + const partialData = { + username: 'your_username', + reposFilter: [], + extraHeaderPages: [], + extraPublicContent: [], + extraTheme: [], + } + + const result = configSchema.parse(partialData) + expect(result).toEqual({ + ...defaultConfig, + ...partialData, + username: 'your_username', + forks: false, + }) + }) + + it('should throw an error if gitProvider is not in the allowed enum values', () => { const invalidData = { + username: 'user1', + gitProvider: 'invalid_provider', } expect(() => configSchema.parse(invalidData)).toThrow() }) + + it('should allow empty arrays for optional list fields', () => { + const dataWithEmptyArrays = { + username: 'user1', + reposFilter: [], + extraHeaderPages: [], + extraPublicContent: [], + extraTheme: [], + } + const { token: _token, ...rest } = defaultConfig + + const result = configSchema.parse(dataWithEmptyArrays) + expect(result).toEqual({ + ...rest, + forks: false, + username: 'user1', + reposFilter: [], + extraHeaderPages: [], + extraPublicContent: [], + extraTheme: [], + }) + }) }) +// foo describe('fetchOptsSchema', () => { it('should validate valid fetch options', () => { const validData = { @@ -113,9 +202,8 @@ describe('fetchOptsSchema', () => { const result = fetchOptsSchema.parse(partialData) expect(result).toEqual({ - branch: 'main', - gitProvider: 'github', - username: 'user123', + ...defaultConfig, + username: partialData.username, }) }) @@ -143,7 +231,6 @@ describe('fetchOptsSchema', () => { const invalidData = { branch: 'develop', gitProvider: 'bitbucket', - username: 'user123', } expect(() => fetchOptsSchema.parse(invalidData)).toThrow() @@ -153,6 +240,7 @@ describe('fetchOptsSchema', () => { describe('prepareOptsSchema', () => { it('should validate valid prepare options', () => { const validData = { + username: 'user1', extraHeaderPages: 'header1.md,header2.md', extraPublicContent: 'public1,public2', extraTheme: 'theme1,theme2', @@ -163,6 +251,7 @@ describe('prepareOptsSchema', () => { const result = prepareOptsSchema.parse(validData) expect(result).toEqual({ + ...defaultConfig, ...validData, extraHeaderPages: ['header1.md', 'header2.md'], extraPublicContent: ['public1', 'public2'], @@ -173,6 +262,7 @@ describe('prepareOptsSchema', () => { it('should apply default values for optional fields', () => { const partialData = { + username: 'user1', vitepressConfig: './vitepress.config.json', } @@ -180,16 +270,15 @@ describe('prepareOptsSchema', () => { const result = prepareOptsSchema.parse(partialData) expect(result).toEqual({ + ...defaultConfig, ...partialData, - extraHeaderPages: undefined, - extraPublicContent: undefined, - extraTheme: undefined, vitepressConfig: { title: 'VitePress Config' }, }) }) it('should transform string fields into arrays', () => { const dataWithStrings = { + username: 'user1', extraHeaderPages: 'header1.md,header2.md', extraPublicContent: 'public1,public2', extraTheme: 'theme1,theme2', @@ -203,6 +292,7 @@ describe('prepareOptsSchema', () => { it('should throw an error for invalid vitepressConfig path', () => { const invalidData = { + username: 'user1', vitepressConfig: './invalid-config.json', } @@ -210,6 +300,10 @@ describe('prepareOptsSchema', () => { throw new Error('File not found') }) - expect(() => prepareOptsSchema.parse(invalidData)).toThrow('File not found') + const result = globalOptsSchema.parse(invalidData) + expect(result).toEqual({ + ...defaultConfig, + username: invalidData.username, + }) }) }) diff --git a/src/templates/theme/index.ts b/src/templates/theme/index.ts new file mode 100644 index 0000000..34a6a24 --- /dev/null +++ b/src/templates/theme/index.ts @@ -0,0 +1,22 @@ +import type { Theme } from 'vitepress' +import DefaultTheme from 'vitepress/theme' +import ForkPage from './layouts/ForkPage.vue' + +const userFiles = import.meta.glob('./extras/**/*', { eager: true }) +const processedFiles = new Set() + +export default { + enhanceApp({ app }) { + Object.entries(userFiles).forEach(([path, file]) => { + if (!processedFiles.has(path)) { + processedFiles.add(path) + if (path.endsWith('.vue')) { + const componentName = path.replace(/^.*[\\/]/, '').replace('.vue', '') + app.component(componentName, (file as { default: any }).default) + } + } + }) + app.component('fork-page', ForkPage) + }, + extends: DefaultTheme, +} satisfies Theme diff --git a/src/templates/theme/layouts/ForkPage.vue b/src/templates/theme/layouts/ForkPage.vue new file mode 100644 index 0000000..05b4a63 --- /dev/null +++ b/src/templates/theme/layouts/ForkPage.vue @@ -0,0 +1,130 @@ + + + + + diff --git a/src/utils/commands.spec.ts b/src/utils/commands.spec.ts index f0ee2d2..d112a19 100644 --- a/src/utils/commands.spec.ts +++ b/src/utils/commands.spec.ts @@ -1,12 +1,21 @@ import { readFileSync } from 'node:fs' import { describe, expect, it, vi } from 'vitest' import { Command, Option } from 'commander' -import type { GlobalOpts } from '../schemas/global.js' +import type { Config, GlobalOpts } from '../schemas/global.js' import type { FetchOpts } from '../schemas/fetch.js' import type { PrepareOpts } from '../schemas/prepare.js' import { addOptions, parseOptions } from './commands.js' +import * as fnMod from './functions.js' vi.mock('fs') +// vi.mock(import('./functions.js'), async (importOriginal) => { +// const mod = await importOriginal() +// return { +// ...mod, +// loadConfigFile: vi.fn(), +// } +// }) +const loadConfigFileMock = vi.spyOn(fnMod, 'loadConfigFile') describe('parseOptions', () => { it('should parse valid fetch options', () => { @@ -18,7 +27,7 @@ describe('parseOptions', () => { username: 'username', } as unknown as FetchOpts - const result = parseOptions(['fetch'], validFetchOptions) + const result = parseOptions('fetch', validFetchOptions) expect(result).toEqual({ ...validFetchOptions, reposFilter: ['repo1', 'repo2'], @@ -32,14 +41,15 @@ describe('parseOptions', () => { username: 'username', } - expect(() => parseOptions(['fetch'], invalidFetchOptions as any)).toThrow() + expect(() => parseOptions('fetch', invalidFetchOptions as any)).toThrow() }) it('should parse valid global options', () => { const validGlobalOptions = { config: './valid-config.json', + token: 'private-token', } as unknown as GlobalOpts - const config = { + const config: Config = { branch: 'main', gitProvider: 'github', username: 'username', @@ -48,24 +58,25 @@ describe('parseOptions', () => { extraTheme: [], reposFilter: [], vitepressConfig: {}, - token: 'private-token', - } as GlobalOpts['config'] + forks: false, + } ;(readFileSync as any).mockReturnValue(JSON.stringify(config)) - const result = parseOptions(['global'], validGlobalOptions) + const result = parseOptions('global', validGlobalOptions) - expect(result).toEqual({ config }) + expect(result).toEqual({ ...config, token: validGlobalOptions.token }) }) it('should parse valid build options', () => { const validBuildOptions = {} - const result = parseOptions(['build'], validBuildOptions) + const result = parseOptions('build', validBuildOptions) expect(result).toEqual(validBuildOptions) }) it('should parse valid prepare options', () => { const validPrepareOptions = { + username: 'user1', extraHeaderPages: 'header1.md,header2.md', extraPublicContent: 'public1,public2', extraTheme: 'theme1,theme2', @@ -77,11 +88,15 @@ describe('parseOptions', () => { description: 'Docpress', } - ;(readFileSync as any).mockReturnValue(JSON.stringify(vitepressConfig)) - const result = parseOptions(['prepare'], validPrepareOptions) + ;(loadConfigFileMock as any).mockReturnValue(vitepressConfig) + ;(readFileSync as any).mockReturnValue(vitepressConfig) + const result = parseOptions('prepare', validPrepareOptions) expect(result).toEqual({ - ...validPrepareOptions, + username: validPrepareOptions.username, + token: undefined, + gitProvider: 'github', + branch: 'main', extraHeaderPages: ['header1.md', 'header2.md'], extraPublicContent: ['public1', 'public2'], extraTheme: ['theme1', 'theme2'], @@ -94,8 +109,8 @@ describe('addOptions', () => { it('should add options to a command', () => { const command = new Command() const options = [ - new Option('-t, --token ', 'Git provider token'), - new Option('-u, --username ', 'Git provider username'), + new Option('-T, --token ', 'Git provider token'), + new Option('-U, --username ', 'Git provider username'), ] addOptions(command, options) @@ -106,8 +121,8 @@ describe('addOptions', () => { })) expect(addedOptions).toEqual([ - { flags: '-t, --token ', description: 'Git provider token' }, - { flags: '-u, --username ', description: 'Git provider username' }, + { flags: '-T, --token ', description: 'Git provider token' }, + { flags: '-U, --username ', description: 'Git provider username' }, ]) }) }) diff --git a/src/utils/commands.ts b/src/utils/commands.ts index 46d8487..ef83d19 100644 --- a/src/utils/commands.ts +++ b/src/utils/commands.ts @@ -1,5 +1,5 @@ import type { Command, Option } from 'commander' -import { fromError } from 'zod-validation-error' +import { fromZodError } from 'zod-validation-error' import type { GlobalOpts } from '../schemas/global.js' import { globalOptsSchema } from '../schemas/global.js' import type { BuildOpts } from '../schemas/build.js' @@ -26,65 +26,19 @@ export const options = { global: globalOptsSchema, } -type MergeOptions = (T extends [infer First, ...infer Rest] - ? First extends Cmd - ? Rest extends Cmd[] - ? Options[First] & MergeOptions - : Options[First] - : never - : unknown) - -export function parseOptions(cmds: [...T], opts: MergeOptions): MergeOptions { +export function parseOptions(cmd: T, opts: Options[T]) { log(`Initializing Docpress...`, 'info', 'blue') log(`\n\n-> Checking for required environment settings and configurations.`, 'info') - function parseSingleOption(cmd: K, singleOpts: Options[K]): Options[K] { - const res = options[cmd].safeParse({ - ...globalOptsSchema.parse(singleOpts).config, - ...singleOpts, - }) - if (res.success) { - return res.data as Options[K] - } - log(` An error occurred while checking configuration: ${fromError(res.error).toString()}`, 'error') - process.exit(0) + const res = options[cmd].safeParse(opts) + if (res.error) { + log(` An error occurred while checking configuration.\n ${fromZodError(res.error).toString()}`, 'error') + process.exit(1) } - - const mergedResult = cmds.reduce((acc, singleCmd) => { - const singleResult = parseSingleOption(singleCmd, opts as Options[typeof singleCmd]) - return Object.assign(acc, singleResult) - }, {}) - log(' Setup complete! Ready to process your documentation.', 'info') - return mergedResult as MergeOptions + return res.data as Options[T] } -// export function parseOptions(cmds: T, opts: T extends Cmd[] ? MergeOptions : Options[T]): T extends Cmd[] ? MergeOptions : Options[T] { -// log(`Initializing Docpress...`, 'info', 'blue') -// log(`\n\n-> Checking for required environment settings and configurations.`, 'info') - -// function parseSingleOption(cmd: K, singleOpts: Options[K]): Options[K] { -// const res = options[cmd].safeParse({ -// ...globalOptsSchema.parse(singleOpts).config, -// ...singleOpts, -// }) -// if (res.success) { -// return res.data as Options[K] -// } -// log(` An error occurred while checking configuration: ${fromError(res.error).toString()}`, 'error') -// process.exit(0) -// } - -// const cmdArray = Array.isArray(cmds) ? cmds : [cmds] -// const mergedResult = cmdArray.reduce((acc, singleCmd) => { -// const singleResult = parseSingleOption(singleCmd, opts as Options[typeof singleCmd]) -// return Object.assign(acc, singleResult) -// }, {}) - -// log(' Setup complete! Ready to process your documentation.', 'info') -// return mergedResult as T extends Cmd[] ? MergeOptions : Options[T] -// } - export function addOptions(cmd: Command, opts: Option[]) { opts.forEach(opt => cmd.addOption(opt)) return cmd diff --git a/src/utils/const.ts b/src/utils/const.ts index 7574d00..af862f3 100644 --- a/src/utils/const.ts +++ b/src/utils/const.ts @@ -10,4 +10,12 @@ export const DOCS_DIR = resolve(process.cwd(), 'docpress/docs') export const INDEX_FILE = resolve(process.cwd(), 'docpress/docs/index.md') +export const FORKS_FILE = resolve(process.cwd(), 'docpress/docs/forks.md') + export const VITEPRESS_CONFIG = resolve(process.cwd(), 'docpress/.vitepress/config.ts') + +export const VITEPRESS_THEME = resolve(process.cwd(), 'docpress/.vitepress/theme') + +export const VITEPRESS_USER_THEME = resolve(process.cwd(), 'docpress/.vitepress/theme/extras') + +export const TEMPLATE_THEME = resolve(import.meta.dirname, '../templates/theme') diff --git a/src/utils/functions.spec.ts b/src/utils/functions.spec.ts index af5307b..31a71af 100644 --- a/src/utils/functions.spec.ts +++ b/src/utils/functions.spec.ts @@ -1,8 +1,9 @@ import { existsSync, mkdirSync, readdirSync, readFileSync, statSync } from 'node:fs' +import { resolve } from 'node:path' import { afterEach, describe, expect, it, vi } from 'vitest' import axios from 'axios' import { rimrafSync } from 'rimraf' -import { checkHttpStatus, createDir, deepMerge, extractFiles, getMdFiles, getUserInfos, getUserRepos, isDir, isFile, prettify, prettifyEnum } from './functions.js' +import { checkHttpStatus, createDir, deepMerge, extractFiles, getMdFiles, getUserInfos, getUserRepos, isDir, isFile, isObject, loadConfigFile, prettify, prettifyEnum, splitByComma } from './functions.js' vi.mock('axios') vi.mock('fs') @@ -414,3 +415,71 @@ describe('deepMerge', () => { expect(result).toEqual({ a: { x: 1, y: 2 }, b: 2, c: 3 }) }) }) + +describe('isObject', () => { + it('should return true for an object', () => { + expect(isObject({ key: 'value' })).toBe(true) + }) + + it('should return false for null', () => { + expect(isObject(null)).oneOf([false, null]) + }) + + it('should return false for an array', () => { + expect(isObject([1, 2, 3])).toBe(false) + }) + + it('should return false for a string', () => { + expect(isObject('string')).toBe(false) + }) + + it('should return false for a number', () => { + expect(isObject(42)).toBe(false) + }) +}) + +describe('loadConfigFile', () => { + const mockConfigContent = JSON.stringify({ key: 'value' }) + const mockPath = 'config.json' + + it('should return an empty object if configPath is undefined', () => { + expect(loadConfigFile()).toEqual({}) + }) + + it('should return parsed config object if file exists', () => { + vi.spyOn(process, 'cwd').mockReturnValue('/mock') + ;(readFileSync as any).mockReturnValue(mockConfigContent) + + const result = loadConfigFile(mockPath) + + expect(result).toEqual({ key: 'value' }) + expect(readFileSync).toHaveBeenCalledWith(resolve('/mock', mockPath), 'utf8') + }) + + it('should return an empty object if file does not exist or JSON parsing fails', () => { + vi.spyOn(process, 'cwd').mockReturnValue('/mock') + ;(readFileSync as any).mockImplementation(() => { throw new Error('File not found') }) + + const result = loadConfigFile(mockPath) + + expect(result).toEqual({}) + }) +}) + +describe('splitByComma', () => { + it('should split a comma-separated string into an array', () => { + expect(splitByComma('a,b,c')).toEqual(['a', 'b', 'c']) + }) + + it('should handle empty strings by returning an array with an empty string', () => { + expect(splitByComma('')).toEqual(['']) + }) + + it('should handle strings without commas by returning a single-element array', () => { + expect(splitByComma('single')).toEqual(['single']) + }) + + it('should handle multiple consecutive commas by returning empty elements', () => { + expect(splitByComma('a,,b,,c')).toEqual(['a', '', 'b', '', 'c']) + }) +}) diff --git a/src/utils/functions.ts b/src/utils/functions.ts index 20dbc88..a5e25da 100644 --- a/src/utils/functions.ts +++ b/src/utils/functions.ts @@ -1,5 +1,5 @@ import { existsSync, mkdirSync, readdirSync, readFileSync, statSync } from 'node:fs' -import { basename, join } from 'node:path' +import { basename, join, resolve } from 'node:path' import axios from 'axios' import { rimrafSync } from 'rimraf' import type { EnhancedRepository } from '../lib/fetch.js' @@ -128,6 +128,23 @@ export function deepMerge | null>(...objects: T[]) }, {} as T) } -function isObject(val: any): val is object { +export function isObject(val: any): val is object { return val && typeof val === 'object' && !Array.isArray(val) } + +export function loadConfigFile(configPath?: string) { + if (!configPath) { + return {} + } + try { + const fullPath = resolve(process.cwd(), configPath) + const fileContents = readFileSync(fullPath, 'utf8') + return JSON.parse(fileContents) + } catch (_error) { + return {} + } +} + +export function splitByComma(s: string) { + return s.split(',') +} diff --git a/tsconfig.json b/tsconfig.json index f10545e..721a860 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -4,6 +4,7 @@ "rootDir": "./src", "types": [ "node", + "vite/client", "vitest", "vitest/globals" ], @@ -12,7 +13,7 @@ "incremental": true, "target": "ESNext", "jsx": "preserve", - "lib": ["ESNext"], + "lib": ["ESNext", "DOM"], "module": "NodeNext", "moduleResolution": "NodeNext", "resolveJsonModule": true, @@ -46,6 +47,7 @@ }, "include": [ "./src/**/*.ts", + "./src/**/*.d.ts", ], "exclude": [ "./src/**/*.spec.ts", diff --git a/vitest.config.ts b/vitest.config.ts index 164b26f..a862260 100644 --- a/vitest.config.ts +++ b/vitest.config.ts @@ -16,7 +16,9 @@ export default defineConfig({ exclude: [ ...configDefaults.exclude, '**/*.spec.ts', + '**/*.d.ts', '**/types.ts', + 'src/templates', ], }, onConsoleLog: () => false,