├── .github ├── FUNDING.yml └── workflows │ └── deploy.yml ├── .gitignore ├── .npmignore ├── .tshy ├── build.json ├── commonjs.json └── esm.json ├── .xo-config.json ├── README.md ├── docs ├── .vitepress │ └── config.mts ├── guide │ ├── commands.md │ ├── final.md │ ├── index.md │ ├── install.md │ ├── integration.md │ ├── sidebar.md │ └── vitepress.md ├── index.md └── public │ ├── vitepress.svg │ └── vitepress_jsdoc_logo.svg ├── fixup.js ├── handlebars ├── helpers │ └── lowercase.js └── partials │ ├── global-index-dl.hbs │ ├── header.hbs │ ├── link.hbs │ ├── member-index-grouped.hbs │ └── sig-link-html.hbs ├── package-lock.json ├── package.json ├── src ├── bin.ts ├── classes │ ├── argument-parser.ts │ ├── directory-manager.ts │ ├── directory-tree-builder.ts │ ├── docs-folder-manager.ts │ ├── file-processor.ts │ ├── file-watcher.ts │ ├── index.ts │ ├── node-directory-reader.ts │ ├── pattern-filter.ts │ ├── readme-manager.ts │ ├── rimraf-path-remover.ts │ └── watcher-manager.ts ├── index.ts ├── interfaces.ts ├── parsers │ ├── comment.ts │ ├── factories │ │ └── parser-factory.ts │ ├── file.ts │ ├── jsdoc.ts │ ├── plugin-options.ts │ └── vue.ts └── utilities │ ├── create-readme.ts │ ├── file-operations.ts │ ├── file-path.ts │ ├── file-reader.ts │ └── output.ts ├── test └── pass.test.ts └── tsconfig.json /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: blakmatrix 4 | patreon: sudomeditate 5 | open_collective: vitepress-jsdoc 6 | ko_fi: blakmatrix 7 | tidelift: npm/vitepress-jsdoc 8 | liberapay: blakmatrix 9 | issuehunt: blakmatrix 10 | 11 | -------------------------------------------------------------------------------- /.github/workflows/deploy.yml: -------------------------------------------------------------------------------- 1 | # Sample workflow for building and deploying a VitePress site to GitHub Pages 2 | # 3 | name: Deploy VitePress site to Pages 4 | 5 | on: 6 | # Runs on pushes targeting the `main` branch. Change this to `master` if you're 7 | # using the `master` branch as the default branch. 8 | push: 9 | branches: [main] 10 | 11 | # Allows you to run this workflow manually from the Actions tab 12 | workflow_dispatch: 13 | 14 | # Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages 15 | permissions: 16 | contents: read 17 | pages: write 18 | id-token: write 19 | 20 | # Allow only one concurrent deployment, skipping runs queued between the run in-progress and latest queued. 21 | # However, do NOT cancel in-progress runs as we want to allow these production deployments to complete. 22 | concurrency: 23 | group: pages 24 | cancel-in-progress: false 25 | 26 | jobs: 27 | # Build job 28 | build: 29 | runs-on: ubuntu-latest 30 | steps: 31 | - name: Checkout 32 | uses: actions/checkout@v3 33 | with: 34 | fetch-depth: 0 # Not needed if lastUpdated is not enabled 35 | # - uses: pnpm/action-setup@v2 # Uncomment this if you're using pnpm 36 | # - uses: oven-sh/setup-bun@v1 # Uncomment this if you're using Bun 37 | - name: Setup Node 38 | uses: actions/setup-node@v3 39 | with: 40 | node-version: 18 41 | cache: npm # or pnpm / yarn 42 | - name: Setup Pages 43 | uses: actions/configure-pages@v3 44 | - name: Install dependencies 45 | run: npm ci # or pnpm install / yarn install / bun install 46 | - name: Build with VitePress 47 | run: | 48 | npm run build # or pnpm docs:build / yarn docs:build / bun run docs:build 49 | touch docs/.vitepress/dist/.nojekyll 50 | - name: Upload artifact 51 | uses: actions/upload-pages-artifact@v2 52 | with: 53 | path: docs/.vitepress/dist 54 | 55 | # Deployment job 56 | deploy: 57 | environment: 58 | name: github-pages 59 | url: ${{ steps.deployment.outputs.page_url }} 60 | needs: build 61 | runs-on: ubuntu-latest 62 | name: Deploy 63 | steps: 64 | - name: Deploy to GitHub Pages 65 | id: deployment 66 | uses: actions/deploy-pages@v2 -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | # for Jetbrains IDEA ides 3 | .idea 4 | coverage 5 | .vercel 6 | dist 7 | docs/code 8 | docs/.vitepress/dist 9 | docs/.vitepress/cache -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | # ignore most things, include some others 2 | /* 3 | /.* 4 | !src/ 5 | !.github 6 | !bin/ 7 | !docs/ 8 | !package.json 9 | !package-lock.json 10 | !README.md 11 | !CONTRIBUTING.md 12 | !LICENSE 13 | !CHANGELOG.md 14 | !example/ 15 | !scripts/ 16 | !tap-snapshots/ 17 | !test/ 18 | !.travis.yml 19 | !.gitignore 20 | !.gitattributes 21 | !/tsconfig*.json 22 | !/fixup.sh 23 | !/.prettierignore 24 | !/typedoc.json -------------------------------------------------------------------------------- /.tshy/build.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tsconfig.json", 3 | "compilerOptions": { 4 | "rootDir": "../src", 5 | "target": "es2022", 6 | "module": "nodenext", 7 | "moduleResolution": "nodenext" 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /.tshy/commonjs.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./build.json", 3 | "include": [ 4 | "../src/**/*.ts", 5 | "../src/**/*.cts", 6 | "../src/**/*.tsx" 7 | ], 8 | "exclude": [ 9 | ".../src/**/*.mts" 10 | ], 11 | "compilerOptions": { 12 | "outDir": "../.tshy-build-tmp/commonjs" 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /.tshy/esm.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./build.json", 3 | "include": [ 4 | "../src/**/*.ts", 5 | "../src/**/*.mts", 6 | "../src/**/*.tsx" 7 | ], 8 | "compilerOptions": { 9 | "outDir": "../.tshy-build-tmp/esm" 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /.xo-config.json: -------------------------------------------------------------------------------- 1 | { 2 | "prettier": true, 3 | "space": 2, 4 | "rules": { 5 | "n/prefer-global/process": "off", 6 | "n/file-extension-in-import": "off" 7 | 8 | } 9 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |
2 | 3 | [![vitepress-jsdoc logo](https://blakmatrix.github.io/vitepress-jsdoc/vitepress_jsdoc_logo.svg "A tree as great as a man's embrace springs from a small shoot 4 | A terrace nine stories high begins with a pile of earth 5 | A journey of a thousand miles starts under one's feet. -Lao Tzu")](https://blakmatrix.github.io/vitepress-jsdoc/) 6 | 7 | [![npm][npm]][npm-url] 8 | 9 | [![node][node]][node-url] 10 | [![licenses][licenses]][licenses-url] 11 | [![PR's welcome][prs]][prs-url] 12 | [![XO code style][xo]][xo-url] 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | install size 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 |

vitepress-jsdoc

37 |

Code More, Document Faster: The Ultimate Vitepress Integration.

38 |

39 | vitepress-jsdoc is the definitive bridge between Vitepress and JSDoc-style commented codebases. Crafted for developers seeking a hassle-free documentation experience, it excels in swiftly generating comprehensive docs from your annotations. Beyond just documentation, it's your key to unlocking the full potential of Vitepress, seamlessly integrating your code insights into beautifully rendered pages. 40 |

41 |
42 | 43 | **Read the full documentation at [blakmatrix.github.io/vitepress-jsdoc/](https://blakmatrix.github.io/vitepress-jsdoc/)** 44 | 45 | 46 | ## Install 47 | 48 | ```shell 49 | npm install -D vitepress-jsdoc 50 | ``` 51 | 52 | ## Primary Usage: Command Line Tool 53 | 54 | The main usage of `vitepress-jsdoc` is as a command line tool. Here's a generic command line example: 55 | 56 | ```shell 57 | npx vitepress-jsdoc --source path/to/src --dist ./docs --folder code --title API 58 | ``` 59 | 60 | *Note: You will probably want to grab the handlbar partial and helpers from this project* 61 | 62 | ### Prebuild and Build Steps 63 | 64 | To ensure your documentation is up-to-date, consider adding a prebuild step using the following command: 65 | 66 | ```shell 67 | vitepress-jsdoc --source path/to/src --dist ./docs --folder code --readme path/to/README.md --exclude="**/*.json,**/*.hbs,**/*.d.ts,**/*.map,**/interfaces.*" --partials=path/to/handlebars/partials/*.hbs --helpers=path/to/handlebars/helpers/*.hbs 68 | ``` 69 | 70 | After the prebuild, you can build your documentation with: 71 | 72 | ```shell 73 | vitepress build docs 74 | ``` 75 | 76 | For development purposes, you can utilize `npx concurrently` to run both the Vitepress development server and the watch mode of `vitepress-jsdoc`: 77 | 78 | ```shell 79 | npx concurrently "vitepress dev docs" "vitepress-jsdoc --source path/to/src ... -watch" 80 | ``` 81 | 82 | Here's a partial package.json script to illustrate: 83 | 84 | ```json 85 | { 86 | "scripts": { 87 | "prebuild": "vitepress-jsdoc --source path/to/dist/esm --dist ./docs --folder code --readme path/to/README.md --exclude=\"**/*.json,**/*.hbs,**/*.d.ts,**/*.map,**/interfaces.*\" --partials=path/to/handlebars/partials/*.hbs --helpers=path/to/handlebars/helpers/*.hbs", 88 | "build": "vitepress build docs", 89 | // OR (using vitepress' default commands 90 | // - you might take `prebuild` above and rewrite it to `docs:preview`) 91 | "docs:build": "npm run prebuild && vitepress build docs", 92 | "docs:dev": "npx concurrently \"vitepress dev docs\" \"vitepress-jsdoc --source path/to/src ... -watch\"" 93 | } 94 | } 95 | ``` 96 | 97 | This package.json script provides both the prebuild and build steps combined in the docs:build command using the && approach. The docs:dev command runs both the Vitepress development server and the watch mode of vitepress-jsdoc concurrently. 98 | 99 | ## Plugin Mode (Beta) 100 | 101 | While `vitepress-jsdoc` can be integrated as a plugin into Vitepress, please note that this mode is currently in beta. During development, the module will function as expected. However, due to certain technical challenges in integrating with the default Vitepress transforms for markdown files, there might be limitations in this mode. 102 | 103 | For integration into Vitepress, the module mode is recommended: 104 | 105 | ```typescript 106 | // Example Vitepress Configuration 107 | import { defineConfig } from "vitepress"; 108 | import VitpressJsdocPlugin from "vitepress-jsdoc"; 109 | 110 | export default defineConfig({ 111 | vite: { 112 | plugins: [ 113 | VitpressJsdocPlugin({ 114 | folder: "code", 115 | source: "./src", 116 | dist: "./docs", 117 | title: "API", 118 | partials: ["./partials/*.hbs"], 119 | helpers: ["./helpers/*.js"], 120 | readme: "./README.md", 121 | exclude: "**/*.json,**/*.d.ts,**/*.map", 122 | }), 123 | ], 124 | }, 125 | }); 126 | 127 | ``` 128 | 129 | ## Live Example 130 | 131 | This entire project serves as a live example. You can view it [here](https://blakmatrix.github.io/vitepress-jsdoc/) or browse the files directly on [GitHub](https://github.com/blakmatrix/vitepress-jsdoc). 132 | 133 | ## Vitepress Configuration 134 | 135 | For a quick start with Vitepress: 136 | 137 | 1. Initialize Vitepress in your project with `npx vitepress init`. 138 | 2. Update your `config.mts` file as shown below. 139 | 3. Run the development server with `npm run docs:dev`. 140 | 4. Build for production with `npm run docs:build` (Note: the watch plugin will not run in this mode). 141 | 142 | ## Sidebar Configuration 143 | 144 | While `vitepress-jsdoc` is agnostic to sidebars, it's recommended to use `vitepress-sidebar` for a more enhanced experience. Configure your `vitepress` `config.mts` file as follows: 145 | 146 | ```ts 147 | import { defineConfig } from "vitepress"; 148 | import { generateSidebar } from "vitepress-sidebar"; 149 | 150 | const getSideBar = (): any => { 151 | const generatedSidebar = generateSidebar([ 152 | { 153 | documentRootPath: "docs", 154 | useTitleFromFileHeading: true, 155 | hyphenToSpace: true, 156 | keepMarkdownSyntaxFromTitle: true, 157 | }, 158 | ]); 159 | return generatedSidebar ?? []; 160 | }; 161 | 162 | export default defineConfig({ 163 | title: "", 164 | description: "", 165 | themeConfig: { 166 | nav: [ 167 | { text: "Home", link: "/" }, 168 | { text: "API", link: "/code/README" }, 169 | ], 170 | sidebar: getSideBar(), 171 | outline: { level: [2, 6] }, 172 | }, 173 | }); 174 | ``` 175 | 176 | ## Plugin/Command Options 177 | 178 | These are plugin/command options: 179 | 180 | | Option | Description | 181 | |-------------------|-------------| 182 | | `folder` | Folder name | 183 | | `source` | Source directory | 184 | | `dist` | Destination directory | 185 | | `title` | Title of your documentation | 186 | | `partials` | Path to partial templates for JSDoc config| 187 | | `helpers` | Path to helper scripts for JSDoc config | 188 | | `readme` | Path to custom README | 189 | | `exclude` | Pattern to exclude files/folders | 190 | 191 | ## Contributions 192 | 193 | We welcome and appreciate contributions from the community. If you have improvements, bug fixes, or other suggestions, please submit a pull request. 194 | 195 | If you find value in this project and wish to show your support in other ways, consider sponsoring us. Your sponsorship will help ensure the continued development and improvement of this project. 196 | 197 | [Sponsor this project](https://github.com/blakmatrix/vitepress-jsdoc?sponsor=1) 198 | 199 | 200 | ## License 201 | 202 | MIT. 203 | 204 | 205 | [npm]: https://img.shields.io/npm/v/vitepress-jsdoc.svg 206 | [npm-url]: https://npmjs.com/package/vitepress-jsdoc 207 | [node]: https://img.shields.io/node/v/vitepress-jsdoc.svg 208 | [node-url]: https://nodejs.org 209 | [prs]: https://img.shields.io/badge/PRs-welcome-brightgreen.svg 210 | [prs-url]: https://github.com/blakmatrix/vitepress-jsdoc/blob/master/CONTRIBUTING.md 211 | [licenses-url]: https://app.fossa.io/projects/git%2Bhttps%3A%2F%2Fgithub.com%2Fblakmatrix%2Fvitepress-jsdoc?ref=badge_shield 212 | [licenses]: https://app.fossa.io/api/projects/git%2Bhttps%3A%2F%2Fgithub.com%2Fblakmatrix%2Fvitepress-jsdoc.svg?type=shield 213 | [xo]: https://shields.io/badge/code_style-5ed9c7?logo=xo&labelColor=gray 214 | [xo-url]: https://github.com/xojs/xo -------------------------------------------------------------------------------- /docs/.vitepress/config.mts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from "vitepress"; 2 | 3 | import { generateSidebar } from "vitepress-sidebar"; 4 | import VitpressJsdocPlugin from "../../dist/esm/index"; 5 | 6 | 7 | const getSideBar = (): any => { 8 | const generatedSidebar = generateSidebar([ 9 | { 10 | documentRootPath: "docs", 11 | scanStartPath: "guide", 12 | resolvePath: "/guide/", 13 | useTitleFromFileHeading: true, 14 | hyphenToSpace: true, 15 | keepMarkdownSyntaxFromTitle: true, 16 | sortMenusByFrontmatterOrder: true, 17 | }, 18 | { 19 | documentRootPath: "docs", 20 | //scanStartPath: 'code', 21 | useTitleFromFileHeading: true, 22 | hyphenToSpace: true, 23 | excludeFolders: ["vitepress-how-to"], 24 | keepMarkdownSyntaxFromTitle: true, 25 | }, 26 | ]); 27 | return generatedSidebar ?? []; 28 | }; 29 | // https://vitepress.dev/reference/site-config 30 | export default defineConfig({ 31 | title: "vitepress-jsdoc", 32 | base: "/vitepress-jsdoc/", 33 | description: "vitepress-jsdoc - JSDoc Meets VitePress: Rapid Doc Generation.", 34 | themeConfig: { 35 | // https://vitepress.dev/reference/default-theme-config 36 | nav: [ 37 | { text: "Home", link: "/" }, 38 | { text: "Guide", link: "/guide/" }, 39 | { text: "Live Example & API", link: "/code/README" }, 40 | ], 41 | 42 | logo: "/vitepress_jsdoc_logo.svg", 43 | 44 | sidebar: getSideBar(), 45 | outline: { level: [2, 6] }, 46 | 47 | socialLinks: [ 48 | { icon: "github", link: "https://github.com/blakmatrix/vitepress-jsdoc" }, 49 | ], 50 | footer: { 51 | message: "Released under the MIT License.", 52 | copyright: "Copyright © 2023-present | Made by Farrin A. Reid with ❤️", 53 | }, 54 | }, 55 | vite: { 56 | plugins: [ 57 | 58 | VitpressJsdocPlugin({ 59 | folder: "code", 60 | source: "./dist/esm/", 61 | dist: "./docs", 62 | title: "API", 63 | partials: ["./dist/esm/partials/*.hbs"], 64 | helpers: ["./dist/esm/helpers/*.js"], 65 | readme: "./README.md", 66 | exclude: "**/*.json,**/*.hbs,**/*.d.ts,**/*.map,**/interfaces.*", 67 | }), 68 | 69 | ], 70 | }, 71 | }); 72 | -------------------------------------------------------------------------------- /docs/guide/commands.md: -------------------------------------------------------------------------------- 1 | --- 2 | order: 3 3 | --- 4 | # Using `vitepress-jsdoc` as a Command Line Tool 5 | 6 | For those who prefer the command line, this section will cover how to use `vitepress-jsdoc` as a standalone CLI tool, offering flexibility in your documentation generation process. 7 | 8 | ## Basic Command 9 | 10 | To use `vitepress-jsdoc` from the command line, you can utilize the following basic command: 11 | 12 | ```shell 13 | npx vitepress-jsdoc --source path/to/dist/esm --dist ./docs --folder code --readme path/to/README.md --exclude=\"**/*.json,**/*.hbs,**/*.d.ts,**/*.map,**/interfaces.*\" --partials=path/to/handlebars/partials/*.hbs --helpers=path/to/handlebars/helpers/*.hbs 14 | ``` 15 | 16 | This will run `vitepress-jsdoc` with default settings. 17 | 18 | ## Specifying Options 19 | 20 | You can also specify various options to customize the documentation generation process. Here's an example: 21 | 22 | ```shell 23 | npx vitepress-jsdoc --source ./src --dist ./docs --folder code --title API 24 | ``` 25 | 26 | In this example: 27 | - `--source ./src`: Specifies the source directory containing your code. 28 | - `--dist ./docs`: Specifies the destination directory where the generated documentation will be saved. 29 | - `--folder code`: Sets the folder name for the documentation. 30 | - `--title API`: Sets the title of your documentation. 31 | 32 | ## Additional Options 33 | 34 | There are several other options available to further customize the documentation generation process, such as specifying partial templates, helper scripts, custom README, and excluding specific files or folders. Refer to the [`vitepress-jsdoc` readme](/code/README.html#plugin-command-options) for a comprehensive list of available options. 35 | 36 | By using `vitepress-jsdoc` as a CLI tool, you have the flexibility to generate documentation as needed, without the need for a full Vitepress setup. 37 | -------------------------------------------------------------------------------- /docs/guide/final.md: -------------------------------------------------------------------------------- 1 | --- 2 | order: 6 3 | --- 4 | 5 | # Facing Issues or Want to Contribute? 6 | 7 | `vitepress-jsdoc` is a community-driven project, and we value your feedback and contributions. If you encounter any problems or have suggestions for improvements, here's how you can help: 8 | 9 | ## Reporting Issues 10 | 11 | If you come across a bug, unexpected behavior, or have a question about `vitepress-jsdoc`, please open an issue on our [GitHub repository](https://github.com/blakmatrix/vitepress-jsdoc). When reporting an issue: 12 | 13 | 1. **Be Specific**: Clearly describe the issue you're facing. Include steps to reproduce, if possible. 14 | 2. **Include Version Info**: Mention the version of `vitepress-jsdoc` you're using, along with any other relevant details about your environment. 15 | 3. **Screenshots & Logs**: If applicable, attach screenshots or logs to provide more context. 16 | 17 | ## Contributing via Pull Requests 18 | 19 | If you've made improvements to `vitepress-jsdoc` or fixed a bug, we'd love to see your contributions! Here's how you can submit a pull request: 20 | 21 | 1. Fork the [`vitepress-jsdoc` repository](https://github.com/blakmatrix/vitepress-jsdoc). 22 | 2. Create a new branch for your changes. 23 | 3. Commit your changes with a clear description of what you've done. 24 | 4. Open a pull request against the main branch of the `vitepress-jsdoc` repository. 25 | 26 | Before submitting, please ensure your code adheres to our coding standards and has been tested. 27 | 28 | ## Support the Project 29 | 30 | Your feedback and contributions directly impact the growth and success of `vitepress-jsdoc`. If you find this project valuable and wish to support it further, consider [sponsoring us](https://github.com/blakmatrix/vitepress-jsdoc?sponsor=1). 31 | 32 | --- 33 | 34 | Thank you for being a part of our community and helping make `vitepress-jsdoc` better for everyone! 35 | -------------------------------------------------------------------------------- /docs/guide/index.md: -------------------------------------------------------------------------------- 1 | # Getting Started with `vitepress-jsdoc` 2 | 3 | Welcome to the vitepress-jsdoc guide! This guide is designed to help you seamlessly integrate `vitepress-jsdoc` into your projects, ensuring efficient documentation generation from your JSDoc-style comments. We'll break down the process into bite-sized pieces, making it easy to follow and implement. 4 | 5 | ## Prerequisites 6 | 7 | Before diving into the installation and setup, ensure you have the following: 8 | 9 | * A javascript/typscript project setup that will contain your documentation and/or familiarity with Vitepress and Markdown. 10 | * Basic knowledge of JSDoc-style comments. -------------------------------------------------------------------------------- /docs/guide/install.md: -------------------------------------------------------------------------------- 1 | --- 2 | order: 1 3 | --- 4 | 5 | # Installation for Development 6 | 7 | In this section, we'll guide you through the process of installing vitepress-jsdoc for development purposes, ensuring you have all the necessary tools to get started. 8 | 9 | Before you begin, ensure you have the following installed: 10 | - [Node.js](https://nodejs.org/) 11 | - [npm](https://www.npmjs.com/) 12 | 13 | ## Installing vitepress-jsdoc 14 | 15 | To install `vitepress-jsdoc` for development, use the npm command: 16 | 17 | ```shell 18 | npm install -D vitepress-jsdoc 19 | ``` 20 | 21 | This command installs vitepress-jsdoc as a development dependency in your project, allowing you to utilize its features and integrate it with Vitepress in the subsequent steps. -------------------------------------------------------------------------------- /docs/guide/integration.md: -------------------------------------------------------------------------------- 1 | --- 2 | order: 5 3 | --- 4 | # Integrating the `vitepress-jsdoc` Plugin (Beta) 5 | 6 | Once Vitepress is set up, we'll delve into integrating the `vitepress-jsdoc` plugin, enhancing your documentation generation capabilities. 7 | 8 | ## Plugin Mode Integration 9 | 10 | For a seamless integration into Vitepress, it's recommended to use the plugin mode. This mode allows you to harness the full power of `vitepress-jsdoc` within your Vitepress project. (** Note: you will have problems building production docs atm, [please use these](/code/README.html#prebuild-and-build-steps) to build you production docs**) 11 | 12 | Here's an example configuration to integrate `vitepress-jsdoc`: 13 | 14 | ```typescript 15 | // Example Vitepress Configuration 16 | import { defineConfig } from "vitepress"; 17 | import VitpressJsdocPlugin from "vitepress-jsdoc"; 18 | 19 | export default defineConfig({ 20 | vite: { 21 | plugins: [ 22 | VitpressJsdocPlugin({ 23 | folder: "code", 24 | source: "./src", 25 | dist: "./docs", 26 | title: "API", 27 | partials: ["./partials/*.hbs"], 28 | helpers: ["./helpers/*.js"], 29 | readme: "./README.md", 30 | exclude: "**/*.json,**/*.d.ts,**/*.map", 31 | }), 32 | ], 33 | }, 34 | }); 35 | ``` 36 | 37 | This configuration imports the `vitepress-jsdoc` plugin and adds it to the Vitepress configuration. The options provided in the `VitpressJsdocPlugin` function allow you to customize the behavior of the plugin, such as specifying the source directory, destination directory, title, and more. 38 | 39 | By integrating the `vitepress-jsdoc` plugin in this manner, you can swiftly generate comprehensive documentation from your JSDoc-style annotations and seamlessly integrate your code insights into beautifully rendered Vitepress pages. 40 | -------------------------------------------------------------------------------- /docs/guide/sidebar.md: -------------------------------------------------------------------------------- 1 | --- 2 | order: 4 3 | --- 4 | 5 | # Sidebar Integration and Recommendations 6 | 7 | A well-organized sidebar can greatly enhance the user experience. We'll provide recommendations and guide you through the process of integrating a dynamic sidebar into your documentation. 8 | 9 | ## Install `vitepress-sidebar` 10 | 11 | For an enhanced documentation experience, we recommend using [`vitepress-sidebar`](https://github.com/jooy2/vitepress-sidebar). This tool automates the generation of sidebars based on your documentation structure. 12 | 13 | To install `vitepress-sidebar`, run the following command: 14 | 15 | ```bash 16 | npm i -D vitepress-sidebar 17 | ``` 18 | 19 | ## Configure your Vitepress 20 | 21 | After installing `vitepress-sidebar`, you'll need to configure it within your Vitepress project. Here's a step-by-step guide: 22 | 23 | 1. **Import Necessary Modules**: Start by importing the required modules in your `config.mts` file: 24 | 25 | ```typescript 26 | import { defineConfig } from "vitepress"; 27 | import { generateSidebar } from "vitepress-sidebar"; 28 | ``` 29 | 30 | 2. **Generate Sidebar**: Use the `generateSidebar` function to create a dynamic sidebar based on your documentation structure: 31 | 32 | ```typescript 33 | const getSideBar = (): any => { 34 | const generatedSidebar = generateSidebar([ 35 | { 36 | documentRootPath: "docs", 37 | useTitleFromFileHeading: true, 38 | hyphenToSpace: true, 39 | keepMarkdownSyntaxFromTitle: true, 40 | }, 41 | ]); 42 | return generatedSidebar ?? []; 43 | }; 44 | ``` 45 | 46 | 3. **Update Vitepress Configuration**: Integrate the generated sidebar into your Vitepress configuration: 47 | 48 | ```typescript 49 | export default defineConfig({ 50 | title: "", 51 | description: "", 52 | themeConfig: { 53 | nav: [ 54 | { text: "Home", link: "/" }, 55 | { text: "API", link: "/code/README" }, 56 | ], 57 | sidebar: getSideBar(), 58 | outline: { level: [2, 6] }, 59 | }, 60 | }); 61 | ``` 62 | 63 | With these steps, you'll have a dynamic sidebar integrated into your Vitepress documentation, enhancing the navigation experience for your users. 64 | 65 | 66 | -------------------------------------------------------------------------------- /docs/guide/vitepress.md: -------------------------------------------------------------------------------- 1 | --- 2 | order: 2 3 | --- 4 | 5 | # Setting Up Vitepress 6 | 7 | If you're new to Vitepress or need a refresher, this section will cover the basics of getting Vitepress up and running, serving as a foundation for the `vitepress-jsdoc` integration. 8 | 9 | ## Initializing Vitepress 10 | 11 | To start with Vitepress, you'll first need to initialize it in your project. This can be done using the following command: 12 | 13 | ```bash 14 | npx vitepress init 15 | ``` 16 | 17 | This command sets up the necessary Vitepress directories and configuration files in your project. 18 | 19 | ## Vitepress Configuration 20 | 21 | Once initialized, you'll have a `config.mts` file in your project. This file is where you'll define various Vitepress configurations. For a basic setup, you can leave the default configurations as they are. However, as you progress, you might want to customize it to suit your project's needs. 22 | 23 | ## Running Vitepress in Development Mode 24 | 25 | To see your Vitepress site in action, you can run the development server: 26 | 27 | ```bash 28 | npm run docs:dev 29 | ``` 30 | 31 | This command starts a local server, and you can view your Vitepress site by navigating to the provided URL in your browser. Any changes you make to your documentation will be reflected in real-time. 32 | 33 | ## Building Vitepress for Production 34 | 35 | Once you're satisfied with your documentation, you can build it for production using: 36 | 37 | ```bash 38 | npm run docs:build 39 | ``` 40 | 41 | This command generates static files for your Vitepress site, which can be deployed to any static file hosting service. 42 | -------------------------------------------------------------------------------- /docs/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | # https://vitepress.dev/reference/default-theme-home-page 3 | layout: home 4 | 5 | hero: 6 | name: "vitepress-jsdoc" 7 | text: "JSDocs at Vite Speed." 8 | tagline: JSDoc Meets VitePress: Rapid Doc Generation. 9 | actions: 10 | - theme: brand 11 | text: Guide 12 | link: /guide/ 13 | - theme: alt 14 | text: Live Example & API 15 | link: /code/README 16 | image: 17 | src: /vitepress_jsdoc_logo.svg 18 | alt: vitepress-jsdoc logo 19 | 20 | features: 21 | - icon: 22 | title: Seamless Integration with vitepress 23 | details: Seamlessly integrate JSDoc comments into your VitePress site. Our tool is purpose-built to harness the power of VitePress, ensuring rapid documentation generation without a hitch. Experience a unified development and documentation workflow like never before. 24 | link: "https://vitepress.dev/" 25 | linkText: "Vitepress site" 26 | - icon: 📖 27 | title: JSDoc Enhanced 28 | details: Dive deeper with vitepress-jsdoc. Harness the power of JSDoc annotations to generate comprehensive documentation. From detailed function descriptions to intricate class hierarchies, make your VitePress site a rich source of knowledge for your codebase. 29 | link: "https://jsdoc.app/" 30 | linkText: "Learn More about JSDoc" 31 | - icon: 🛠️ 32 | title: Built for Developers 33 | details: vitepress-jsdoc is crafted with developers in mind. From easy setup to intuitive commands, every aspect is designed to streamline your documentation process. Dive deep into the code, and let us handle the docs. 34 | link: "https://github.com/blakmatrix/vitepress-jsdoc" 35 | linkText: "GitHub Repository" 36 | - icon: 🤝 37 | title: "Active Community and Transparent Development" 38 | details: "With vitepress-jsdoc, you're never coding alone. Join an active community of developers, get swift answers to queries, and enjoy the benefits of open and transparent development. We value every contributor, and our maintenance is a labor of love." 39 | link: https://github.com/blakmatrix/vitepress-jsdoc/blob/master/CONTRIBUTING.md 40 | linkText: "Contribute to vitepress-jsdoc" 41 | 42 | 43 | --- 44 | 45 | 66 | -------------------------------------------------------------------------------- /docs/public/vitepress.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /docs/public/vitepress_jsdoc_logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 7 | 10 | 13 | 16 | 19 | 22 | 25 | 26 | 27 | 29 | 30 | 31 | 32 | 34 | 35 | 36 | 37 | 39 | 40 | 41 | 42 | 44 | 45 | 46 | 47 | 49 | 50 | 51 | 52 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | JSDOC 66 | -------------------------------------------------------------------------------- /fixup.js: -------------------------------------------------------------------------------- 1 | import { 2 | chmodSync, 3 | readdirSync, 4 | copyFileSync, 5 | statSync, 6 | mkdirSync, 7 | } from 'node:fs'; 8 | import {join} from 'node:path'; 9 | 10 | /** 11 | * Set execute permissions for a specified file. 12 | * 13 | * @param path - Path to the file. 14 | */ 15 | function setExecutePermissions(path) { 16 | chmodSync(path, 0o755); 17 | console.log(`Execute permissions set for: ${path}`); 18 | } 19 | 20 | /** 21 | * Copy files from source directory to target directory. 22 | * 23 | * @param sourceDir - Source directory path. 24 | * @param targetDir - Target directory path. 25 | */ 26 | function copyFiles(sourceDir, targetDir) { 27 | const items = readdirSync(sourceDir); 28 | 29 | for (const item of items) { 30 | const sourceItemPath = join(sourceDir, item); 31 | const targetItemPath = join(targetDir, item); 32 | const itemStat = statSync(sourceItemPath); 33 | 34 | if (itemStat.isDirectory()) { 35 | mkdirSync(targetItemPath, {recursive: true}); 36 | copyFiles(sourceItemPath, targetItemPath); 37 | } else { 38 | copyFileSync(sourceItemPath, targetItemPath); 39 | // Console.log( 40 | // `File successfully copied from: ${sourceItemPath} to ${targetItemPath}`, 41 | // ); 42 | } 43 | } 44 | 45 | console.log('Files succesfully coppied.'); 46 | } 47 | 48 | // Main execution 49 | 50 | const binPath = './dist/esm/bin.js'; 51 | setExecutePermissions(binPath); 52 | 53 | -------------------------------------------------------------------------------- /handlebars/helpers/lowercase.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable unicorn/prefer-module */ 2 | const Handlebars = require('handlebars'); 3 | 4 | Handlebars.registerHelper('lowercase', function (value) { 5 | if (!value) return value; // Return early if value is undefined or falsy 6 | 7 | if (typeof value === 'string') { 8 | const result = value.startsWith('#') 9 | ? '#' + value.slice(1).toLowerCase() 10 | : value.toLowerCase(); 11 | return result; 12 | } 13 | 14 | return value; 15 | }); 16 | -------------------------------------------------------------------------------- /handlebars/partials/global-index-dl.hbs: -------------------------------------------------------------------------------- 1 | {{#globals kind=kind ~}} 2 | {{#if @first~}} 3 | 4 | 5 | 6 | 7 | 8 | {{/if~}} 9 | {{/globals~}} -------------------------------------------------------------------------------- /handlebars/partials/header.hbs: -------------------------------------------------------------------------------- 1 | 2 | 3 | {{>heading-indent}}{{>sig-name}} 4 | -------------------------------------------------------------------------------- /handlebars/partials/link.hbs: -------------------------------------------------------------------------------- 1 | {{! usage: link to="namepath" html=true/false caption="optional caption"~}} 2 | 3 | {{~#if html~}} 4 | 5 | 6 | {{~#link to~}} 7 | {{#if url~}} 8 | {{#if ../../caption}}{{../../../caption}}{{else}}{{name}}{{/if}} 9 | {{~else~}} 10 | {{#if ../../caption}}{{../../../caption}}{{else}}{{name}}{{/if~}} 11 | {{/if~}} 12 | {{/link~}} 13 | 14 | 15 | {{~else~}} 16 | 17 | {{#link to~}} 18 | {{#if url~}} 19 | [{{#if ../../caption}}{{escape ../../../caption}}{{else}}{{escape name}}{{/if}}]({{{lowercase url}}}) 20 | {{~else~}} 21 | {{#if ../../caption}}{{escape ../../../caption}}{{else}}{{escape name}}{{/if~}} 22 | {{~/if~}} 23 | {{/link~}} 24 | 25 | {{/if~}} -------------------------------------------------------------------------------- /handlebars/partials/member-index-grouped.hbs: -------------------------------------------------------------------------------- 1 | [[toc]] -------------------------------------------------------------------------------- /handlebars/partials/sig-link-html.hbs: -------------------------------------------------------------------------------- 1 | {{#if name}}{{#sig no-gfm=true ~}} 2 | {{{@depOpen}~}} 3 | 4 | {{~{@codeOpen}~}} 5 | {{#if @prefix}}{{@prefix}} {{/if~}} 6 | {{@accessSymbol}}{{#if (isEvent)}}"{{{name}}}"{{else}}{{{name}}}{{/if~}} 7 | {{~#if @methodSign}}{{#if (isEvent)}} {{@methodSign}}{{else}}{{@methodSign}}{{/if}}{{/if~}} 8 | {{{@codeClose}~}} 9 | 10 | {{~#if @returnSymbol}} {{@returnSymbol}}{{/if~}} 11 | {{#if @returnTypes}} {{>linked-type-list types=@returnTypes html=true delimiter=" | " }}{{/if~}} 12 | {{#if @suffix}} {{@suffix}}{{/if~}} 13 | {{{@depClose}~}} 14 | {{~/sig}}{{/if~}} 15 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "vitepress-jsdoc", 3 | "version": "1.0.4", 4 | "files": [ 5 | "dist" 6 | ], 7 | "description": "A bridge between Vitepress and JSDoc-style commented codebases for hassle-free documentation.", 8 | "scripts": { 9 | "preversion": "npm test", 10 | "postversion": "npm publish", 11 | "prepublishOnly": "git push origin --follow-tags", 12 | "old:preprepare": "rm -rf dist", 13 | "postprepare": "node fixup.js", 14 | "prepare": "tshy", 15 | "pretest": "npm run prepare", 16 | "presnap": "npm run prepare", 17 | "test": "vitest run", 18 | "test:coverage": "vitest run --coverage", 19 | "lint": "xo", 20 | "lint:fix": "xo --fix", 21 | "dev": "vitepress dev docs", 22 | "build": "vitepress build docs", 23 | "preview": "vitepress preview docs", 24 | "prebuild": "./dist/esm/bin.js --source ./dist/esm --dist ./docs --folder code --readme ./README.md --exclude=\"**/*.json,**/*.hbs,**/*.d.ts,**/*.map,**/interfaces.*\"" 25 | }, 26 | "keywords": [ 27 | "vitepress", 28 | "jsdoc", 29 | "documentation", 30 | "plugin", 31 | "integration", 32 | "annotations", 33 | "codebase", 34 | "developer tools", 35 | "static", 36 | "vue" 37 | ], 38 | "engines": { 39 | "node": ">=14" 40 | }, 41 | "funding": [ 42 | { 43 | "type": "individual", 44 | "url": "https://github.com/blakmatrix/vitepress-jsdoc?sponsor=1" 45 | } 46 | ], 47 | "license": "MIT", 48 | "author": "Farrin Reid ", 49 | "repository": { 50 | "url": "git+https://github.com/blakmatrix/vitepress-jsdoc.git", 51 | "type": "git" 52 | }, 53 | "devDependencies": { 54 | "@types/jsdoc-to-markdown": "^7.0.4", 55 | "@types/micromatch": "^4.0.3", 56 | "@types/node": "^20.8.2", 57 | "tshy": "^1.2.2", 58 | "typescript": "^5.2.2", 59 | "vite": "^4.4.9", 60 | "vitepress": "^1.0.0-rc.20", 61 | "vitepress-sidebar": "^1.18.0", 62 | "vitest": "^0.34.6", 63 | "vue-docgen-cli": "^4.67.0", 64 | "xo": "^0.56.0" 65 | }, 66 | "dependencies": { 67 | "chokidar": "^3.5.3", 68 | "commander": "^11.0.0", 69 | "front-matter": "^4.0.2", 70 | "jsdoc-to-markdown": "^8.0.0", 71 | "micromatch": "^4.0.5", 72 | "mkdirp": "^3.0.1", 73 | "rimraf": "^5.0.5" 74 | }, 75 | "tshy": { 76 | "exports": { 77 | "./package.json": "./package.json", 78 | ".": "./src/index.ts" 79 | } 80 | }, 81 | "bin": { 82 | "vitepress-jsdoc": "dist/esm/bin.js" 83 | }, 84 | "exports": { 85 | "./package.json": "./package.json", 86 | ".": { 87 | "import": { 88 | "types": "./dist/esm/index.d.ts", 89 | "default": "./dist/esm/index.js" 90 | }, 91 | "require": { 92 | "types": "./dist/commonjs/index.d.ts", 93 | "default": "./dist/commonjs/index.js" 94 | } 95 | } 96 | }, 97 | "type": "module" 98 | } 99 | -------------------------------------------------------------------------------- /src/bin.ts: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | /** 4 | * @vitepress 5 | * --- 6 | * headline: CLI interface 7 | * --- 8 | */ 9 | import {program} from 'commander'; 10 | import {generate} from './index.js'; 11 | 12 | /** 13 | * @file This module provides the main CLI interface for generating markdown files for VitePress. 14 | */ 15 | 16 | /** 17 | * Configures the main details of the CLI program. 18 | * 19 | * @function 20 | * 21 | */ 22 | function configureProgram() { 23 | program.description('a CLI Tool to generate markdown files for vitepress'); 24 | } 25 | 26 | /** 27 | * Configures the available options for the CLI tool. 28 | * 29 | * @function 30 | */ 31 | function configureOptions() { 32 | program 33 | .description( 34 | 'CLI Tool to generate markdown files for VitePress from source files.', 35 | ) 36 | 37 | // Source and Destination 38 | .option( 39 | '-s, --source ', 40 | 'Specify the source folder containing .js or .js files. Default: ./src', 41 | ) 42 | .option( 43 | '-d, --dist ', 44 | 'Specify the destination folder for generated markdown files. Default: ./docs', 45 | ) 46 | 47 | // Documentation Structure 48 | .option( 49 | '-f, --folder ', 50 | 'Name of the folder inside the destination. This folder gets overwritten every time. Default: code', 51 | ) 52 | .option( 53 | '-t, --title ', 54 | 'Set the title for your documentation. Default: API', 55 | ) 56 | 57 | // Customization 58 | .option('-r, --readme ', 'Provide a path to your custom README file.') 59 | .option( 60 | '-i, --include ', 61 | 'Specify patterns to include specific files/folders. Separate multiple patterns with commas (e.g., *.test.js,include.js).', 62 | ) 63 | .option( 64 | '-e, --exclude ', 65 | 'Specify patterns to exclude specific files/folders. Separate multiple patterns with commas (e.g., *.test.js,exclude.js).', 66 | ) 67 | 68 | // Advanced Options 69 | .option( 70 | '-w, --watch', 71 | 'Enable watch mode to monitor changes in source files.', 72 | ) 73 | .option( 74 | '-rm, --rmPattern [patterns...]', 75 | 'Specify patterns for removing files. You can exclude or include files using glob patterns.', 76 | ) 77 | .option( 78 | '-p, --partials [files...]', 79 | 'Provide jsdoc2markdown partial templates. This will overwrite the default templates.', 80 | ) 81 | .option( 82 | '-h, --helpers [files...]', 83 | 'Provide jsdoc2markdown helpers. This will overwrite the default helpers.', 84 | ) 85 | .option( 86 | '-c, --jsDocConfigPath ', 87 | 'Specify the path to the JSDoc configuration file.', 88 | ) 89 | 90 | // Action to Execute 91 | .action(generate); 92 | } 93 | 94 | /** 95 | * Initializes and runs the CLI tool. 96 | * 97 | * @function 98 | * 99 | */ 100 | const bootAndRun = () => { 101 | const args = process.argv; 102 | configureProgram(); 103 | configureOptions(); 104 | program.parse(args); 105 | }; 106 | 107 | bootAndRun(); 108 | -------------------------------------------------------------------------------- /src/classes/argument-parser.ts: -------------------------------------------------------------------------------- 1 | import {type PluginOptions} from '../interfaces.js'; 2 | import {parsePluginOptions} from '../parsers/plugin-options.js'; 3 | 4 | /** 5 | * Represents the plugin options provided to the application. 6 | * @typedef PluginOptions 7 | * @property {string} dist - Destination path for the generated files. 8 | * @property {string} exclude - Patterns to exclude from processing. 9 | * @property {string} folder - Main folder for processing. 10 | * @property {string[]} helpers - List of helper functions or modules. 11 | * @property {string} include - Patterns to include for processing. 12 | * @property {string} jsDocConfigPath - Path to the JSDoc configuration file. 13 | * @property {string[]} partials - List of partial templates or modules. 14 | * @property {string} readme - Path to the README file. 15 | * @property {string[]} rmPattern - Patterns for files to be removed. 16 | * @property {string} source - Source path for the files to be processed. 17 | * @property {string} title - Title for the generated documentation. 18 | * @property {boolean} watch - Flag to determine if the application should watch for file changes. 19 | */ 20 | 21 | /** 22 | * The `ArgumentParser` class provides a mechanism to parse plugin options 23 | * for the application. It acts as a wrapper around the `parsePluginOptions` function, 24 | * ensuring a consistent interface for argument parsing throughout the application. 25 | */ 26 | export class ArgumentParser { 27 | /** 28 | * Parses the provided plugin options into a structured format. 29 | * 30 | * @param {PluginOptions} options - The plugin options to parse. 31 | * @returns {object} An object containing the parsed arguments. 32 | * 33 | * @example 34 | * const parser = new ArgumentParser(); 35 | * const parsedArgs = parser.parse(process.argv); 36 | */ 37 | parse(options: PluginOptions) { 38 | return parsePluginOptions(options); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/classes/directory-manager.ts: -------------------------------------------------------------------------------- 1 | import {type DirectoryTreeListOptions} from '../interfaces.js'; 2 | import {DirectoryTreeBuilder} from './directory-tree-builder.js'; 3 | 4 | /** 5 | * Options for listing directory tree contents. 6 | * @typedef DirectoryTreeListOptions 7 | * @property {string[]} [exclude] - Patterns to exclude from the tree. 8 | * @property {string[]} [include] - Patterns to include in the tree. 9 | * @property {string} [mainPath] - Main path for relative calculations. 10 | * @property {string} srcPath - Source path of the directory to list. 11 | * @property {FileTree[]} [tree] - Existing tree to append to, if available. 12 | */ 13 | 14 | /** 15 | * The `DirectoryManager` class provides functionalities to manage and interact 16 | * with directories. It leverages the `DirectoryTreeBuilder` to construct a 17 | * directory tree based on the provided options. 18 | */ 19 | export class DirectoryManager { 20 | /** 21 | * Creates an instance of the `DirectoryManager` class. 22 | * 23 | * @param {DirectoryTreeListOptions} options - Configuration options for directory management. 24 | */ 25 | constructor(private readonly options: DirectoryTreeListOptions) {} 26 | 27 | /** 28 | * Constructs a directory tree based on the provided options. 29 | * 30 | * @returns {Promise} A promise that resolves to the constructed directory tree. 31 | * 32 | * @example 33 | * const manager = new DirectoryManager({ srcPath: './src', exclude: ['node_modules'] }); 34 | * const tree = await manager.build(); 35 | */ 36 | async build() { 37 | const directoryTree = new DirectoryTreeBuilder(this.options); 38 | return directoryTree.build(); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/classes/directory-tree-builder.ts: -------------------------------------------------------------------------------- 1 | import path from 'node:path'; 2 | import { 3 | type DirectoryEntity, 4 | type DirectoryFile, 5 | type DirectoryReader, 6 | type DirectoryTreeListOptions, 7 | type FileTree, 8 | type FilterStrategy, 9 | } from '../interfaces.js'; 10 | import {NodeDirectoryReader} from './node-directory-reader.js'; 11 | import {PatternFilter} from './pattern-filter.js'; 12 | 13 | /** 14 | * Represents a directory entity with basic information. 15 | * @typedef DirectoryEntity 16 | * @property {Function} isDirectory - Function to check if the entity is a directory. 17 | * @property {string} name - Name of the directory entity. 18 | */ 19 | 20 | /** 21 | * Represents a file within a directory with its details. 22 | * @typedef DirectoryFile 23 | * @property {string} [ext] - File extension, if available. 24 | * @property {string} [folder] - Folder containing the file, if available. 25 | * @property {boolean} isDir - Flag to determine if the entity is a directory. 26 | * @property {string} name - Name of the file. 27 | * @property {string} path - Full path to the file. 28 | */ 29 | 30 | /** 31 | * Interface for reading directory contents. 32 | * @typedef DirectoryReader 33 | * @property {Function} isDirectory - Checks if the given entry is a directory. 34 | * @property {Function} readDirectory - Reads the directory at the given path and returns its entities. 35 | */ 36 | 37 | /** 38 | * Options for listing directory tree contents. 39 | * @typedef DirectoryTreeListOptions 40 | * @property {string[]} [exclude] - Patterns to exclude from the tree. 41 | * @property {string[]} [include] - Patterns to include in the tree. 42 | * @property {string} [mainPath] - Main path for relative calculations. 43 | * @property {string} srcPath - Source path of the directory to list. 44 | * @property {FileTree[]} [tree] - Existing tree to append to, if available. 45 | */ 46 | 47 | /** 48 | * Represents a node in a file tree structure. 49 | * @typedef FileTree 50 | * @property {FileTree[]} [children] - Child nodes of the current node. 51 | * @property {string} [ext] - File extension, if available. 52 | * @property {string} [fullPath] - Full path to the file or directory. 53 | * @property {string} name - Name of the file or directory. 54 | * @property {string} [path] - Relative path to the file or directory. 55 | */ 56 | 57 | /** 58 | * Strategy for filtering directory contents. 59 | * @typedef FilterStrategy 60 | * @property {Function} shouldInclude - Determines if a given entry should be included based on the strategy. 61 | */ 62 | 63 | /** 64 | * @typedef DirectoryTreeResult 65 | * @property {DirectoryFile[]} paths - The paths in the directory. 66 | * @property {FileTree[]} tree - The hierarchical tree structure of the directory. 67 | * @property {DirectoryFile[]} excluded - The files that were excluded based on the provided options. 68 | */ 69 | 70 | /** 71 | * The `DirectoryTreeBuilder` class provides functionalities to construct a hierarchical 72 | * representation of a directory and its contents based on the provided options. 73 | * It leverages filtering strategies and directory readers to achieve this. 74 | */ 75 | export class DirectoryTreeBuilder { 76 | private readonly filter: FilterStrategy; 77 | private readonly reader: DirectoryReader; 78 | private readonly options: DirectoryTreeListOptions; 79 | 80 | /** 81 | * Initializes a new instance of the DirectoryTreeBuilder class. 82 | * 83 | * @param {DirectoryTreeListOptions} options - Configuration options for building the directory tree. 84 | */ 85 | constructor(options: DirectoryTreeListOptions) { 86 | const {include = [], exclude = []} = options; 87 | this.filter = new PatternFilter(include, exclude); 88 | this.reader = new NodeDirectoryReader(); 89 | this.options = options; 90 | } 91 | 92 | /** 93 | * Constructs a hierarchical representation of the directory based on the provided options. 94 | * 95 | * @returns {Promise} 96 | * A promise that resolves to the directory tree, including paths, tree structure, and excluded files. 97 | */ 98 | public async build() { 99 | const {srcPath, mainPath = '', tree = []} = this.options; 100 | 101 | const paths: DirectoryFile[] = []; 102 | const excluded: DirectoryFile[] = []; 103 | 104 | const dirs: DirectoryEntity[] = await this.reader.readDirectory(srcPath); 105 | 106 | for (const dirent of dirs) { 107 | const fileDetails = this.getFileDetails(srcPath, dirent); 108 | 109 | if (this.shouldSkipFile(fileDetails.name)) continue; 110 | 111 | if (this.filter.shouldInclude(dirent, srcPath, mainPath)) { 112 | if (fileDetails.isDir) { 113 | const subTree: FileTree[] = []; 114 | this.options.srcPath = fileDetails.path; 115 | this.options.tree = subTree; 116 | // eslint-disable-next-line no-await-in-loop 117 | const result = await this.build(); 118 | tree.push({ 119 | name: fileDetails.name, 120 | children: subTree, 121 | }); 122 | paths.push(...result.paths); 123 | excluded.push(...result.excluded); 124 | } else { 125 | const treeEntry = this.createTreeEntry(fileDetails); 126 | tree.push(treeEntry); 127 | paths.push(fileDetails); 128 | } 129 | } else { 130 | excluded.push(fileDetails); 131 | } 132 | } 133 | 134 | return {paths, tree, excluded}; 135 | } 136 | 137 | /** 138 | * Retrieves detailed information about a directory entry. 139 | * 140 | * @param {string} srcPath - The source directory path. 141 | * @param {DirectoryEntity} dirent - The directory entry to retrieve details for. 142 | * @returns {DirectoryFile} Detailed information about the directory entry. 143 | */ 144 | private getFileDetails( 145 | srcPath: string, 146 | dirent: DirectoryEntity, 147 | ): DirectoryFile { 148 | const filePath = path.join(srcPath, dirent.name); 149 | const isDir = dirent.isDirectory(); 150 | const ext = path.extname(filePath); 151 | let name = path.basename(filePath, ext); 152 | const folder = path.dirname(filePath); 153 | if (name === 'index') { 154 | name = '__index__'; 155 | } 156 | 157 | return { 158 | isDir, 159 | name, 160 | path: filePath, 161 | ...(isDir ? {} : {ext, folder}), 162 | }; 163 | } 164 | 165 | /** 166 | * Checks whether a specific file should be omitted from the directory tree. 167 | * 168 | * @param {string} fileName - The name of the file to check. 169 | * @returns {boolean} True if the file should be skipped, otherwise false. 170 | */ 171 | private shouldSkipFile(fileName: string): boolean { 172 | return fileName.toLowerCase() === 'readme'; 173 | } 174 | 175 | /** 176 | * Creates a tree entry based on the provided file details. 177 | * 178 | * @param {DirectoryFile} file - Details of the file for which to create a tree entry. 179 | * @returns {FileTree} A tree entry representing the file. 180 | */ 181 | private createTreeEntry(file: DirectoryFile): FileTree { 182 | if (file.isDir) { 183 | return { 184 | name: file.name, 185 | children: [], 186 | }; 187 | } 188 | 189 | return { 190 | name: file.name, 191 | path: `/${file.name}`, 192 | fullPath: file.path, 193 | ext: file.ext, 194 | }; 195 | } 196 | } 197 | -------------------------------------------------------------------------------- /src/classes/docs-folder-manager.ts: -------------------------------------------------------------------------------- 1 | import { 2 | deleteDocsFolder, 3 | createDocsFolder, 4 | } from '../utilities/file-operations.js'; 5 | 6 | /** 7 | * The `DocsFolderManager` class provides functionalities to manage the documentation 8 | * folder. It offers methods to delete and create the docs folder, abstracting the 9 | * underlying operations. 10 | */ 11 | export class DocsFolderManager { 12 | /** 13 | * Deletes the specified documentation folder based on the provided pattern. 14 | * 15 | * @param {string} docsFolder - The path to the documentation folder to delete. 16 | * @param {string[]} rmPattern - An array of patterns specifying which files or directories to exclude from deletion. 17 | * @returns {Promise} A promise that resolves once the deletion is complete. 18 | * 19 | * @example 20 | * const manager = new DocsFolderManager(); 21 | * await manager.delete('./docs', ['exclude-pattern']); 22 | */ 23 | async delete(docsFolder: string, rmPattern: string[]) { 24 | return deleteDocsFolder(docsFolder, rmPattern); 25 | } 26 | 27 | /** 28 | * Creates the specified documentation folder. 29 | * 30 | * @param {string} docsFolder - The path to the documentation folder to create. 31 | * 32 | * @example 33 | * const manager = new DocsFolderManager(); 34 | * manager.create('./docs'); 35 | */ 36 | create(docsFolder: string) { 37 | createDocsFolder(docsFolder); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/classes/file-processor.ts: -------------------------------------------------------------------------------- 1 | import {printFiles, printStats} from '../utilities/output.js'; 2 | import {parseAndWriteFiles} from '../parsers/file.js'; 3 | import { 4 | type PluginOptions, 5 | type FolderData, 6 | type ParseReturn, 7 | } from '../interfaces.js'; 8 | /** 9 | * Represents the plugin options provided to the application. 10 | * @typedef PluginOptions 11 | * @property {string} dist - Destination path for the generated files. 12 | * @property {string} exclude - Patterns to exclude from processing. 13 | * @property {string} folder - Main folder for processing. 14 | * @property {string[]} helpers - List of helper functions or modules. 15 | * @property {string} include - Patterns to include for processing. 16 | * @property {string} jsDocConfigPath - Path to the JSDoc configuration file. 17 | * @property {string[]} partials - List of partial templates or modules. 18 | * @property {string} readme - Path to the README file. 19 | * @property {string[]} rmPattern - Patterns for files to be removed. 20 | * @property {string} source - Source path for the files to be processed. 21 | * @property {string} title - Title for the generated documentation. 22 | * @property {boolean} watch - Flag to determine if the application should watch for file changes. 23 | */ 24 | 25 | /** 26 | * Represents the data of a folder, including its paths, tree structure, and excluded files. 27 | * @typedef FolderData 28 | * @property {DirectoryFile[]} excluded - List of excluded files. 29 | * @property {DirectoryFile[]} paths - List of all paths in the folder. 30 | * @property {FileTree[]} tree - Tree structure of the folder. 31 | */ 32 | 33 | /** 34 | * Represents the return value after parsing a file. 35 | * @typedef ParseReturn 36 | * @property {string} content - Content of the parsed file. 37 | * @property {string} dest - Destination path for the parsed file. 38 | * @property {boolean} empty - Flag to determine if the file is empty. 39 | * @property {boolean} [excluded] - Flag to determine if the file was excluded. 40 | * @property {DirectoryFile} file - Details of the parsed file. 41 | * @property {string} relativePathDest - Relative destination path for the parsed file. 42 | * @property {string} relativePathSrc - Relative source path of the parsed file. 43 | * @property {boolean} [success] - Flag to determine if the parsing was successful. 44 | * @property {StatisticType} [type] - Type of statistic for the parsed file. 45 | */ 46 | 47 | /** 48 | * The `FileProcessor` class provides functionalities to process files 49 | * within a specified folder. It offers methods to print, parse, write, 50 | * and filter files based on the provided arguments and configurations. 51 | */ 52 | export class FileProcessor { 53 | /** 54 | * Processes the files within the specified folder based on the provided arguments. 55 | * 56 | * @param {FolderData} lsFolder - Data representing the folder and its contents. 57 | * @param {PluginOptions} options - plugin options to guide the file processing. 58 | * @returns {Promise} A promise that resolves to an array of processed files. 59 | * 60 | * @example 61 | * const processor = new FileProcessor(); 62 | * const processedFiles = await processor.processFiles(folderData, cliArgs); 63 | */ 64 | async processFiles( 65 | lsFolder: FolderData, 66 | options: PluginOptions, 67 | ): Promise { 68 | await printFiles(lsFolder); 69 | const result = await parseAndWriteFiles(lsFolder, options); 70 | return result.filter((entry): entry is ParseReturn => entry !== undefined); 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /src/classes/file-watcher.ts: -------------------------------------------------------------------------------- 1 | import readline from 'node:readline'; 2 | import chokidar from 'chokidar'; 3 | import {parseDirectoryFile} from '../parsers/file.js'; 4 | import { 5 | type DirectoryFile, 6 | type PluginOptions, 7 | type ParsedPluginOptions, 8 | } from '../interfaces.js'; 9 | import {parsePluginOptions} from '../parsers/plugin-options.js'; 10 | import {writeContentToFile} from '../utilities/file-operations.js'; 11 | import {createReadmeFile} from '../utilities/create-readme.js'; 12 | import {DirectoryTreeBuilder} from './directory-tree-builder.js'; 13 | 14 | /** 15 | * The `FileWatcher` class provides functionalities to monitor files and directories 16 | * for changes. It leverages the `chokidar` library to efficiently watch files and 17 | * react to changes by updating the documentation accordingly. 18 | */ 19 | class FileWatcher { 20 | private readonly parsedArgs: ParsedPluginOptions; 21 | 22 | /** 23 | * Initializes a new instance of the FileWatcher class. 24 | * 25 | * @param {PluginOptions} options - Configuration options and plugin options. 26 | */ 27 | constructor(private readonly options: PluginOptions) { 28 | this.options = options; 29 | this.parsedArgs = parsePluginOptions(options); 30 | } 31 | 32 | /** 33 | * Starts the file watching process if the "watch" argument is provided. 34 | * Outputs a message to the console indicating the start of the watching process. 35 | */ 36 | public watch() { 37 | if (!this.options.watch) return; 38 | 39 | console.log('\n---\n\n👀 watching files...'); 40 | const watcher = this.setupFileWatcher(); 41 | 42 | watcher.on('change', async (path) => { 43 | await this.handleFileChange(path); 44 | }); 45 | } 46 | 47 | /** 48 | * Configures and returns a file watcher instance targeting the source folder 49 | * and README files. 50 | * 51 | * @returns {chokidar.FSWatcher} An instance of the file watcher. 52 | */ 53 | private setupFileWatcher() { 54 | const {srcFolder} = this.parsedArgs; 55 | return chokidar.watch( 56 | [srcFolder, this.options.readme, `${srcFolder}/README.md`].filter( 57 | Boolean, 58 | ), 59 | { 60 | ignored: /(^|[/\\])\../, 61 | persistent: true, 62 | }, 63 | ); 64 | } 65 | 66 | /** 67 | * Handles events when a file changes. It updates the documentation and 68 | * outputs relevant messages to the console. 69 | * 70 | * @param {string} path - The path of the file that changed. 71 | */ 72 | private async handleFileChange(path: string) { 73 | const {srcFolder, include, exclude} = this.parsedArgs; 74 | const directoryTree = new DirectoryTreeBuilder({ 75 | srcPath: srcFolder, 76 | include, 77 | exclude, 78 | }); 79 | const lsFolder = await directoryTree.build(); 80 | const file = lsFolder.paths.find((p) => p.path === path); 81 | 82 | await this.clearConsole(); 83 | 84 | if (this.isReadmeFile(path, srcFolder)) { 85 | await createReadmeFile(this.options); 86 | } 87 | 88 | if (file) { 89 | console.log(`update ${file.name + file.ext}`); 90 | await this.updateDocumentationFile(file); 91 | } 92 | } 93 | 94 | /** 95 | * Clears the console to provide a clean output for subsequent messages. 96 | */ 97 | private async clearConsole() { 98 | readline.clearLine(process.stdout, 0); 99 | readline.cursorTo(process.stdout, 0); 100 | } 101 | 102 | /** 103 | * Determines if the specified file is a README file. 104 | * 105 | * @param {string} path - The path of the file to check. 106 | * @param {string} srcFolder - The source directory path. 107 | * @returns {boolean} True if the file is a README file, otherwise false. 108 | */ 109 | private isReadmeFile(path: string, srcFolder: string) { 110 | return path === 'README.md' || path === `${srcFolder}/README.md`; 111 | } 112 | 113 | /** 114 | * Processes and updates the documentation for the specified file. 115 | * 116 | * @param {DirectoryFile} file - Details of the file to update. 117 | */ 118 | private async updateDocumentationFile(file: DirectoryFile) { 119 | const data = await parseDirectoryFile(file, this.options); 120 | if (data) { 121 | await writeContentToFile(data, data.relativePathDest); 122 | } 123 | } 124 | } 125 | 126 | export default FileWatcher; 127 | -------------------------------------------------------------------------------- /src/classes/index.ts: -------------------------------------------------------------------------------- 1 | export * from './argument-parser.js'; 2 | export * from './directory-manager.js'; 3 | export * from './directory-tree-builder.js'; 4 | export * from './docs-folder-manager.js'; 5 | export * from './file-processor.js'; 6 | export * from './file-watcher.js'; 7 | export * from './node-directory-reader.js'; 8 | export * from './pattern-filter.js'; 9 | export * from './readme-manager.js'; 10 | export * from './watcher-manager.js'; 11 | -------------------------------------------------------------------------------- /src/classes/node-directory-reader.ts: -------------------------------------------------------------------------------- 1 | import fs from 'node:fs/promises'; 2 | import {type DirectoryReader, type DirectoryEntity} from '../interfaces.js'; 3 | 4 | /** 5 | * Represents a directory reader that leverages Node.js's file system module. 6 | * This class provides methods to read directory contents and determine the type of directory entries. 7 | * 8 | * @implements {DirectoryReader} 9 | */ 10 | export class NodeDirectoryReader implements DirectoryReader { 11 | /** 12 | * Reads the content of a specified directory and returns its entries. 13 | * 14 | * @param {string} srcPath - The path of the directory to read. 15 | * @returns {Promise} A promise that resolves to an array of directory entries. 16 | * @throws {Error} Throws an error if there's an issue reading the directory. 17 | * 18 | * @example 19 | * const reader = new NodeDirectoryReader(); 20 | * const entries = await reader.readDirectory('./src'); 21 | */ 22 | async readDirectory(srcPath: string) { 23 | return fs.readdir(srcPath, {withFileTypes: true}); 24 | } 25 | 26 | /** 27 | * Determines whether a given directory entry represents a directory. 28 | * 29 | * @param {DirectoryEntity} entry - The directory entry to check. 30 | * @returns {boolean} Returns true if the entry is a directory, false otherwise. 31 | * 32 | * @example 33 | * const reader = new NodeDirectoryReader(); 34 | * const isDir = reader.isDirectory(entry); 35 | */ 36 | isDirectory(entry: DirectoryEntity) { 37 | return entry.isDirectory(); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/classes/pattern-filter.ts: -------------------------------------------------------------------------------- 1 | import path from 'node:path'; 2 | import mm from 'micromatch'; 3 | import {type FilterStrategy, type DirectoryEntity} from '../interfaces.js'; 4 | 5 | /** 6 | * Represents a filter strategy that determines the inclusion of directory entries 7 | * based on specified include and exclude patterns. This class leverages the `micromatch` 8 | * library to perform pattern matching. 9 | * 10 | * @implements {FilterStrategy} 11 | */ 12 | export class PatternFilter implements FilterStrategy { 13 | /** 14 | * Initializes a new instance of the PatternFilter class. 15 | * 16 | * @param {string[]} include - Patterns that specify which entries to include. 17 | * @param {string[]} exclude - Patterns that specify which entries to exclude. 18 | */ 19 | constructor( 20 | private readonly include: string[], 21 | private readonly exclude: string[], 22 | ) {} 23 | 24 | /** 25 | * Determines whether a given directory entry should be included based on the 26 | * provided include and exclude patterns. 27 | * 28 | * @param {DirectoryEntity} entry - The directory entry to evaluate. 29 | * @param {string} srcPath - The source directory path. 30 | * @param {string} mainPath - The main directory path for relative path calculations. 31 | * @returns {boolean} Returns true if the entry matches the inclusion criteria, false otherwise. 32 | * 33 | * @example 34 | * const filter = new PatternFilter(['*.js'], ['test.js']); 35 | * const shouldInclude = filter.shouldInclude(entry, './src', './main'); 36 | */ 37 | shouldInclude( 38 | entry: DirectoryEntity, 39 | srcPath: string, 40 | mainPath: string, 41 | ): boolean { 42 | const baseSrc = mainPath || srcPath; 43 | return ( 44 | mm.every(path.join(srcPath.replace(baseSrc, ''), entry.name), [ 45 | ...this.include, 46 | ...this.exclude.map((ex) => '!' + ex), 47 | ]) || entry.isDirectory() 48 | ); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/classes/readme-manager.ts: -------------------------------------------------------------------------------- 1 | import {createReadmeFile} from '../utilities/create-readme.js'; 2 | import {type PluginOptions} from '../interfaces.js'; 3 | 4 | /** 5 | * The `ReadmeManager` class provides functionalities to manage the README file 6 | * of the project. It offers methods to create or update the README based on 7 | * the provided arguments and configurations. 8 | */ 9 | export class ReadmeManager { 10 | /** 11 | * Creates or updates the README file based on the provided arguments. 12 | * 13 | * @param {PluginOptions} options - Plugin options to guide the creation of the README. 14 | * @param {string[]} deletedPaths - An array of paths that were deleted, to be documented in the README. 15 | * @returns {Promise} A promise that resolves once the README is created or updated. 16 | * 17 | * @example 18 | * const readmeManager = new ReadmeManager(); 19 | * await readmeManager.create(cliArgs, deletedPathsArray); 20 | */ 21 | async create(options: PluginOptions, deletedPaths: string[]): Promise { 22 | await createReadmeFile(options, deletedPaths); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/classes/rimraf-path-remover.ts: -------------------------------------------------------------------------------- 1 | import {rimraf} from 'rimraf'; 2 | import {type PathRemover} from '../interfaces.js'; 3 | 4 | /** 5 | * Implementation of the `PathRemover` interface using the `rimraf` module. 6 | * This class provides methods to delete specified paths and retrieve the list of deleted paths. 7 | * 8 | * @class 9 | * @implements {PathRemover} 10 | * 11 | * @example 12 | * 13 | * const remover = new RimrafPathRemover(); 14 | * const pathsToDelete = ['./docs', '**\/*.tmp']; // remove backslash 15 | * const success = await remover.delete(pathsToDelete); 16 | * if (success) { 17 | * const deletedPaths = remover.getDeletedPaths(); 18 | * } 19 | */ 20 | export class RimrafPathRemover implements PathRemover { 21 | private readonly deletedPaths: string[] = []; 22 | 23 | /** 24 | * Deletes the specified paths. 25 | * 26 | * @param {string[]} paths - An array of paths or glob patterns to be deleted. 27 | * @returns {Promise} A promise that resolves with a boolean indicating whether all paths were successfully deleted. 28 | */ 29 | async delete(paths: string[]): Promise { 30 | const allDeleted = await rimraf(paths, { 31 | glob: true, 32 | filter: this.captureDeletedPaths.bind(this), 33 | }); 34 | return allDeleted; 35 | } 36 | 37 | /** 38 | * Retrieves the list of paths that were deleted. 39 | * 40 | * @returns {string[]} An array of paths that were deleted. 41 | */ 42 | getDeletedPaths(): string[] { 43 | return this.deletedPaths; 44 | } 45 | 46 | /** 47 | * Captures the path of a deleted entry. 48 | * 49 | * @private 50 | * @param {string} path - The path of the deleted entry. 51 | * @returns {boolean} Always returns true to proceed with the deletion. 52 | */ 53 | private captureDeletedPaths(path: string): boolean { 54 | this.deletedPaths.push(path); 55 | return true; // Always return true to proceed with the deletion 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /src/classes/watcher-manager.ts: -------------------------------------------------------------------------------- 1 | import {type PluginOptions} from '../interfaces.js'; 2 | import FileWatcher from './file-watcher.js'; 3 | 4 | /** 5 | * The `WatcherManager` class provides functionalities to watch files or directories 6 | * for changes. It leverages the `FileWatcher` to set up and manage the watching process 7 | * based on the provided plugin options. 8 | */ 9 | export class WatcherManager { 10 | /** 11 | * Creates an instance of the `WatcherManager` class. 12 | * 13 | * @param {PluginOptions} options - plugin options to guide the file watching process. 14 | */ 15 | constructor(private readonly options: PluginOptions) {} 16 | 17 | /** 18 | * Initializes and starts the file watching process based on the provided arguments. 19 | * 20 | * @example 21 | * const watcherManager = new WatcherManager(cliArgs); 22 | * watcherManager.watch(); 23 | */ 24 | watch() { 25 | const watcher = new FileWatcher(this.options); 26 | watcher.watch(); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * @vitepress 3 | * --- 4 | * headline: Main Index 5 | * --- 6 | */ 7 | 8 | // - import {type Plugin} from 'vite'; 9 | import {type PluginOptions} from './interfaces.js'; 10 | import {printStats} from './utilities/output.js'; 11 | import { 12 | ArgumentParser, 13 | DocsFolderManager, 14 | DirectoryManager, 15 | FileProcessor, 16 | ReadmeManager, 17 | WatcherManager, 18 | } from './classes/index.js'; 19 | 20 | /** 21 | * ## Generate Markdown Files 22 | * Orchestrates the process of reading, filtering, and writing files. 23 | * 24 | * - **options**: The plugin options of type `PluginOptions`. 25 | * 26 | * @example 27 | * ```typescript 28 | * generate(myCliArgs); 29 | * ``` 30 | */ 31 | export const generate = async (options: PluginOptions) => { 32 | const argParser = new ArgumentParser(); 33 | const parsedArgs = argParser.parse(options); 34 | const startTime = Date.now(); 35 | 36 | const docsManager = new DocsFolderManager(); 37 | const deletedPaths = await docsManager.delete( 38 | parsedArgs.docsFolder, 39 | parsedArgs.rmPattern, 40 | ); 41 | 42 | const dirManager = new DirectoryManager({ 43 | srcPath: parsedArgs.srcFolder, 44 | include: parsedArgs.include, 45 | exclude: parsedArgs.exclude, 46 | }); 47 | const lsFolder = await dirManager.build(); 48 | 49 | docsManager.create(parsedArgs.docsFolder); 50 | 51 | const fileProc = new FileProcessor(); 52 | const filteredResult = await fileProc.processFiles(lsFolder, options); 53 | 54 | printStats(lsFolder, filteredResult); 55 | 56 | const readme = new ReadmeManager(); 57 | await readme.create(options, deletedPaths); 58 | 59 | const resultTime = (Math.abs(startTime - Date.now()) / 1000).toFixed(2); 60 | console.log(`\n⏰ Time: ${resultTime}s`); 61 | 62 | const watcher = new WatcherManager(options); 63 | watcher.watch(); 64 | }; 65 | 66 | /** 67 | * Vitepress JSDoc Plugin. 68 | * 69 | * @function 70 | * @param {PluginOptions} options - The options for the plugin. 71 | * @returns {Plugin} Returns a Vite plugin object. 72 | * 73 | * @example 74 | * ```typescript 75 | * // Example Vitepress Configuration 76 | * import { defineConfig } from "vitepress"; 77 | * import VitpressJsdocPlugin from "vitepress-jsdoc"; 78 | * 79 | * export default defineConfig({ 80 | * vite: { 81 | * plugins: [ 82 | * VitpressJsdocPlugin({ 83 | * folder: "code", 84 | * source: "./dist/mjs/", 85 | * dist: "./docs", 86 | * title: "API", 87 | * partials: ["./dist/mjs/partials/*.hbs"], 88 | * helpers: ["./dist/mjs/helpers/*.js"], 89 | * readme: "./README.md", 90 | * exclude: "**\/*.json,**\/*.hbs,**\/*.d.js,**\/*.map,**\/interfaces.*", 91 | * }), 92 | * ], 93 | * }, 94 | * }); 95 | * ``` 96 | */ 97 | const plugin = (options: PluginOptions) /* : Plugin */ => { 98 | return { 99 | name: 'vitepress-jsdoc-plugin', 100 | 101 | // Hook for 'serve' 102 | configureServer(server: any) { 103 | // Call your watch function here 104 | options.watch = true; 105 | const watcher = new WatcherManager(options); 106 | watcher.watch(); 107 | }, 108 | 109 | // Hook for 'build' 110 | // async buildStart() { 111 | // await generate(options); 112 | // }, 113 | }; 114 | }; 115 | 116 | export default plugin; 117 | -------------------------------------------------------------------------------- /src/interfaces.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Represents the plugin options provided to the application. 3 | * @typedef PluginOptions 4 | * @property {string} dist - Destination path for the generated files. 5 | * @property {string} exclude - Patterns to exclude from processing. 6 | * @property {string} folder - Main folder for processing. 7 | * @property {string[]} helpers - List of helper functions or modules. 8 | * @property {string} include - Patterns to include for processing. 9 | * @property {string} jsDocConfigPath - Path to the JSDoc configuration file. 10 | * @property {string[]} partials - List of partial templates or modules. 11 | * @property {string} readme - Path to the README file. 12 | * @property {string[]} rmPattern - Patterns for files to be removed. 13 | * @property {string} source - Source path for the files to be processed. 14 | * @property {string} title - Title for the generated documentation. 15 | * @property {boolean} watch - Flag to determine if the application should watch for file changes. 16 | */ 17 | export type PluginOptions = { 18 | dist: string; 19 | exclude: string; 20 | folder: string; 21 | helpers: string[]; 22 | include?: string; 23 | jsDocConfigPath?: string; 24 | partials: string[]; 25 | readme: string; 26 | rmPattern?: string[]; 27 | source: string; 28 | title: string; 29 | watch?: boolean; 30 | }; 31 | 32 | /** 33 | * Represents a directory entity with basic information. 34 | * @typedef DirectoryEntity 35 | * @property {Function} isDirectory - Function to check if the entity is a directory. 36 | * @property {string} name - Name of the directory entity. 37 | */ 38 | export type DirectoryEntity = { 39 | isDirectory: () => boolean; 40 | name: string; 41 | }; 42 | 43 | /** 44 | * Represents a file within a directory with its details. 45 | * @typedef DirectoryFile 46 | * @property {string} [ext] - File extension, if available. 47 | * @property {string} [folder] - Folder containing the file, if available. 48 | * @property {boolean} isDir - Flag to determine if the entity is a directory. 49 | * @property {string} name - Name of the file. 50 | * @property {string} path - Full path to the file. 51 | */ 52 | export type DirectoryFile = { 53 | ext?: string; 54 | folder?: string; 55 | isDir: boolean; 56 | name: string; 57 | path: string; 58 | }; 59 | 60 | /** 61 | * Interface for reading directory contents. 62 | * @typedef DirectoryReader 63 | * @property {Function} isDirectory - Checks if the given entry is a directory. 64 | * @property {Function} readDirectory - Reads the directory at the given path and returns its entities. 65 | */ 66 | export type DirectoryReader = { 67 | isDirectory(entry: any): boolean; 68 | readDirectory(srcPath: string): Promise; 69 | }; 70 | 71 | /** 72 | * Options for listing directory tree contents. 73 | * @typedef DirectoryTreeListOptions 74 | * @property {string[]} [exclude] - Patterns to exclude from the tree. 75 | * @property {string[]} [include] - Patterns to include in the tree. 76 | * @property {string} [mainPath] - Main path for relative calculations. 77 | * @property {string} srcPath - Source path of the directory to list. 78 | * @property {FileTree[]} [tree] - Existing tree to append to, if available. 79 | */ 80 | export type DirectoryTreeListOptions = { 81 | exclude?: string[]; 82 | include?: string[]; 83 | mainPath?: string; 84 | srcPath: string; 85 | tree?: FileTree[]; 86 | }; 87 | 88 | /** 89 | * Represents a node in a file tree structure. 90 | * @typedef FileTree 91 | * @property {FileTree[]} [children] - Child nodes of the current node. 92 | * @property {string} [ext] - File extension, if available. 93 | * @property {string} [fullPath] - Full path to the file or directory. 94 | * @property {string} name - Name of the file or directory. 95 | * @property {string} [path] - Relative path to the file or directory. 96 | */ 97 | export type FileTree = { 98 | children?: FileTree[]; 99 | ext?: string; 100 | fullPath?: string; 101 | name: string; 102 | path?: string; 103 | }; 104 | 105 | /** 106 | * Strategy for filtering directory contents. 107 | * @typedef FilterStrategy 108 | * @property {Function} shouldInclude - Determines if a given entry should be included based on the strategy. 109 | */ 110 | export type FilterStrategy = { 111 | shouldInclude(entry: any, srcPath: string, mainPath: string): boolean; 112 | }; 113 | 114 | /** 115 | * Represents the data of a folder, including its paths, tree structure, and excluded files. 116 | * @typedef FolderData 117 | * @property {DirectoryFile[]} excluded - List of excluded files. 118 | * @property {DirectoryFile[]} paths - List of all paths in the folder. 119 | * @property {FileTree[]} tree - Tree structure of the folder. 120 | */ 121 | export type FolderData = { 122 | excluded: DirectoryFile[]; 123 | paths: DirectoryFile[]; 124 | tree: FileTree[]; 125 | }; 126 | 127 | /** 128 | * Represents the result of a parsing operation. 129 | * @typedef ParsePromiseResult 130 | * @property {ParseReturn|undefined} - The result of the parsing operation. 131 | */ 132 | export type ParsePromiseResult = ParseReturn | undefined; 133 | 134 | /** 135 | * Represents the return value after parsing a file. 136 | * @typedef ParseReturn 137 | * @property {string} content - Content of the parsed file. 138 | * @property {string} dest - Destination path for the parsed file. 139 | * @property {boolean} empty - Flag to determine if the file is empty. 140 | * @property {boolean} [excluded] - Flag to determine if the file was excluded. 141 | * @property {DirectoryFile} file - Details of the parsed file. 142 | * @property {string} relativePathDest - Relative destination path for the parsed file. 143 | * @property {string} relativePathSrc - Relative source path of the parsed file. 144 | * @property {boolean} [success] - Flag to determine if the parsing was successful. 145 | * @property {StatisticType} [type] - Type of statistic for the parsed file. 146 | */ 147 | export type ParseReturn = { 148 | content: string; 149 | dest: string; 150 | empty: boolean; 151 | excluded?: boolean; 152 | file: DirectoryFile; 153 | relativePathDest: string; 154 | relativePathSrc: string; 155 | success?: boolean; 156 | type?: StatisticType; 157 | }; 158 | 159 | /** 160 | * Represents a parser with a parse method. 161 | * @typedef Parser 162 | * @property {Function} parse - Parses a given file based on the provided configuration. 163 | */ 164 | export type Parser = { 165 | parse( 166 | file: DirectoryFile, 167 | config: ParserConfig, 168 | ): Promise; 169 | }; 170 | 171 | /** 172 | * Configuration for the parser. 173 | * @typedef ParserConfig 174 | * @property {string} docsFolder - Path to the documentation folder. 175 | * @property {string[]} [helpers] - List of helper functions or modules. 176 | * @property {string} [jsDocConfigPath] - Path to the JSDoc configuration file. 177 | * @property {string[]} [partials] - List of partial templates or modules. 178 | * @property {string} srcFolder - Source folder for the files to be parsed. 179 | */ 180 | export type ParserConfig = { 181 | docsFolder: string; 182 | helpers?: string[]; 183 | jsDocConfigPath?: string; 184 | partials?: string[]; 185 | srcFolder: string; 186 | }; 187 | 188 | /** 189 | * Represents the parsed plugin options. 190 | * @typedef ParsedPluginOptions 191 | * @property {string} codeFolder - Path to the code folder. 192 | * @property {string} docsFolder - Path to the documentation folder. 193 | * @property {string[]} exclude - Patterns to exclude from processing. 194 | * @property {string[]} helpers - List of helper functions or modules. 195 | * @property {string[]} include - Patterns to include for processing. 196 | * @property {string} jsDocConfigPath - Path to the JSDoc configuration file. 197 | * @property {string[]} partials - List of partial templates or modules. 198 | * @property {string} readme - Path to the README file. 199 | * @property {string[]} rmPattern - Patterns for files to be removed. 200 | * @property {string} srcFolder - Source path for the files to be processed. 201 | * @property {string} title - Title for the generated documentation. 202 | */ 203 | export type ParsedPluginOptions = { 204 | codeFolder: string; 205 | docsFolder: string; 206 | exclude: string[]; 207 | helpers: string[]; 208 | include: string[]; 209 | jsDocConfigPath: string; 210 | partials: string[]; 211 | readme: string; 212 | rmPattern: string[]; 213 | srcFolder: string; 214 | title: string; 215 | }; 216 | 217 | /** 218 | * Interface for removing directory contents. 219 | * @interface PathRemover 220 | * @function 221 | * @property {Function} delete - Deletes the specified paths and returns a boolean indicating success. 222 | * @property {Function} getDeletedPaths - Returns an array of paths that were deleted. 223 | */ 224 | export type PathRemover = { 225 | delete(paths: string[]): Promise; 226 | getDeletedPaths(): string[]; 227 | }; 228 | 229 | /** 230 | * Represents the types of statistics for parsed files. 231 | * @enum {string} 232 | */ 233 | export enum StatisticType { 234 | EMPTY = 'EMPTY', 235 | ERROR = 'ERROR', 236 | EXCLUDE = 'EXCLUDE', 237 | INCLUDE = 'INCLUDE', 238 | } 239 | -------------------------------------------------------------------------------- /src/parsers/comment.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * @vitepress 3 | * --- 4 | * headline: Parse Vitepress Comment 5 | * --- 6 | */ 7 | import fm, {type FrontMatterResult} from 'front-matter'; 8 | import {type DirectoryFile} from '../interfaces.js'; 9 | 10 | type VitepressAttributes = { 11 | title?: string; 12 | headline?: string; 13 | }; 14 | 15 | /** 16 | * Parses the content of a file to search for a @vitepress comment block and extracts the frontmatter data. 17 | * 18 | * @function 19 | * @param {string} fileContent - The content of the file to be parsed. 20 | * @returns {object} - An object containing the extracted frontmatter data and attributes. 21 | * @throws {Error} - Returns an object with null values if parsing fails. 22 | */ 23 | export const parseComment = ( 24 | fileContent: string, 25 | ): FrontMatterResult => { 26 | try { 27 | const allCommentBlocks = fileContent.match( 28 | /\/\*[\s\S]*?\*\/|([^:]|^)\/\/.*$/g, 29 | ); 30 | const vitepressBlock = allCommentBlocks?.filter((block: string) => { 31 | return block.split('\n').filter((line) => line.includes('@vitepress')) 32 | .length; 33 | })[0]; 34 | 35 | if (!vitepressBlock) { 36 | return { 37 | body: '', 38 | bodyBegin: 0, 39 | attributes: {}, 40 | }; 41 | } 42 | 43 | // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-call 44 | const parsed = (fm as any)( 45 | vitepressBlock 46 | .replaceAll('\n ', '\n') 47 | .replace('/*', '') 48 | .replace('*/', '') 49 | .replaceAll('@vitepress', '') 50 | .replaceAll(/\*\s?/g, '') 51 | .trim(), 52 | ); 53 | 54 | return parsed as FrontMatterResult; 55 | 56 | /* The code `return fm(...)` is calling the `fm` function from the `front-matter` 57 | library to parse the frontmatter data from the `vitepressBlock`. */ 58 | // return fm( 59 | // vitepressBlock 60 | // .replaceAll('\n ', '\n') 61 | // .replace('/*', '') 62 | // .replace('*/', '') 63 | // .replaceAll('@vitepress', '') 64 | // .replaceAll(/\*\s?/g, '') 65 | // .trim(), 66 | // ); 67 | } catch { 68 | return { 69 | body: '', 70 | bodyBegin: 0, 71 | attributes: {}, 72 | }; 73 | } 74 | }; 75 | 76 | /** 77 | * Parses the content of a file and constructs a structured markdown header based on the @vitepress comment block. 78 | * 79 | * @function 80 | * @param {string} content - The content of the file. 81 | * @param {DirectoryFile} file - The file object containing details like name and extension. 82 | * @returns {string} - A structured markdown header. 83 | */ 84 | export const parseVitepressFileHeader = ( 85 | content: string, 86 | file: DirectoryFile, 87 | ) => { 88 | const {frontmatter, attributes}: FrontMatterResult = 89 | parseComment(content); 90 | 91 | let fileContent = '---\n'; 92 | 93 | fileContent += attributes?.title ? '' : `title: ${file.name}`; 94 | 95 | if (frontmatter) { 96 | fileContent += attributes?.title ? '' : '\n'; 97 | fileContent += `${frontmatter}`; 98 | } 99 | 100 | fileContent += '\n---\n'; 101 | if (attributes?.title ?? file.ext !== '.vue') { 102 | let headline = file.name; 103 | 104 | if (attributes?.headline) { 105 | headline = attributes.headline; 106 | } else if (attributes?.title) { 107 | headline = attributes.title; 108 | } 109 | 110 | fileContent += `\n# ${headline}\n\n`; 111 | } 112 | 113 | return fileContent; 114 | }; 115 | -------------------------------------------------------------------------------- /src/parsers/factories/parser-factory.ts: -------------------------------------------------------------------------------- 1 | import {type Parser} from '../../interfaces.js'; 2 | import {JsDocParser} from '../jsdoc.js'; 3 | 4 | /** 5 | * Factory object for creating parser instances based on file types. 6 | * 7 | * @namespace parserFactory 8 | */ 9 | export const parserFactory = { 10 | /** 11 | * Creates and returns a parser instance based on the provided file type. 12 | * 13 | * @param {string} fileType - The type of the file (e.g., '.js', '.js', '.vue'). 14 | * @returns {Parser} - An instance of a parser corresponding to the file type. 15 | * @throws {Error} - Throws an error if the provided file type is unsupported. 16 | */ 17 | createParser(fileType: string): Parser { 18 | switch (fileType) { 19 | case '.js': 20 | case '.ts': { 21 | return new JsDocParser(); 22 | } 23 | 24 | default: { 25 | throw new Error(`Unsupported file type: "${fileType}"`); 26 | } 27 | } 28 | }, 29 | }; 30 | -------------------------------------------------------------------------------- /src/parsers/file.ts: -------------------------------------------------------------------------------- 1 | import { 2 | type PluginOptions, 3 | type DirectoryFile, 4 | type ParseReturn, 5 | type FolderData, 6 | type ParsePromiseResult, 7 | } from '../interfaces.js'; 8 | import {writeContentToFile} from '../utilities/file-operations.js'; 9 | import {parserFactory} from './factories/parser-factory.js'; 10 | import {parsePluginOptions} from './plugin-options.js'; 11 | 12 | export const parseAndWriteFiles = async ( 13 | lsFolder: FolderData, 14 | options: PluginOptions, 15 | ): Promise => { 16 | const folderData = lsFolder; 17 | 18 | const parsePromises = folderData.paths.map(async (file) => { 19 | const data = await parseDirectoryFile(file, options); 20 | if (data?.relativePathDest) { 21 | return writeContentToFile(data, data.relativePathDest); 22 | } 23 | 24 | return undefined; 25 | }); 26 | 27 | return Promise.all(parsePromises); 28 | }; 29 | 30 | /** 31 | * Parses the content of a directory file based on its extension and configuration. 32 | * 33 | * @param {DirectoryFile} file - The file object representing the directory file to be parsed. 34 | * @param {PluginOptions} options - The plugin options providing configuration for the parsing. 35 | * 36 | * @returns {Promise} A promise that resolves with the parsed content of the file, or undefined if the file is a directory or has no associated parser. 37 | */ 38 | export const parseDirectoryFile = async ( 39 | file: DirectoryFile, 40 | options: PluginOptions, 41 | ): Promise => { 42 | const {srcFolder, docsFolder, jsDocConfigPath, partials, helpers} = 43 | parsePluginOptions(options); 44 | 45 | if (!file.isDir && file.folder) { 46 | const parser = parserFactory.createParser(file.ext ?? ''); 47 | 48 | const config = { 49 | srcFolder, 50 | docsFolder, 51 | jsDocConfigPath, 52 | partials, 53 | helpers, 54 | }; 55 | 56 | return parser.parse(file, config); 57 | } 58 | }; 59 | -------------------------------------------------------------------------------- /src/parsers/jsdoc.ts: -------------------------------------------------------------------------------- 1 | import {join} from 'node:path'; 2 | import jsdoc2md from 'jsdoc-to-markdown'; 3 | import { 4 | type Parser, 5 | type DirectoryFile, 6 | type ParseReturn, 7 | type ParserConfig, 8 | } from '../interfaces.js'; 9 | import {readFileContent} from '../utilities/file-reader.js'; 10 | import { 11 | computePaths, 12 | getFileName, 13 | getFileFolder, 14 | } from '../utilities/file-path.js'; 15 | 16 | /** 17 | * The JsDocParser class provides functionality to parse files 18 | * and generate markdown content based on JSDoc comments. 19 | * 20 | * @implements {Parser} 21 | */ 22 | export class JsDocParser implements Parser { 23 | /** 24 | * Parses the provided file and returns the generated markdown content. 25 | * 26 | * @param {DirectoryFile} file - The file to be parsed. 27 | * @param {ParserConfig} config - The configuration for parsing. 28 | * @returns {Promise} - The parsed content or undefined. 29 | */ 30 | async parse( 31 | file: DirectoryFile, 32 | config: ParserConfig, 33 | ): Promise { 34 | const fileContent = await this.getFileContent(file); 35 | const markdownContent = await this.getMarkdownContent(file, config); 36 | const paths = this.getPaths(file, config); 37 | 38 | return { 39 | success: Boolean(markdownContent), 40 | file, 41 | empty: !markdownContent, 42 | content: fileContent + markdownContent, 43 | ...paths, 44 | }; 45 | } 46 | 47 | /** 48 | * Retrieves the content of the provided file. 49 | * 50 | * @private 51 | * @param {DirectoryFile} file - The file whose content is to be retrieved. 52 | * @returns {Promise} - The content of the file. 53 | */ 54 | private async getFileContent(file: DirectoryFile): Promise { 55 | return readFileContent(file); 56 | } 57 | 58 | /** 59 | * Generates markdown content based on JSDoc comments in the provided file. 60 | * 61 | * @private 62 | * @param {DirectoryFile} file - The file to be parsed for JSDoc comments. 63 | * @param {ParserConfig} config - The configuration for parsing. 64 | * @returns {Promise} - The generated markdown content. 65 | */ 66 | private async getMarkdownContent( 67 | file: DirectoryFile, 68 | config: ParserConfig, 69 | ): Promise { 70 | const relativePathSrc = getFileFolder(file); 71 | const {partialsPath, helpersPath} = this.getHandlebarsPaths(config); 72 | 73 | return jsdoc2md.render({ 74 | 'no-cache': Boolean(config.jsDocConfigPath), 75 | files: [ 76 | join(process.cwd(), relativePathSrc, getFileName(file) + file.ext), 77 | ], 78 | configure: config.jsDocConfigPath, 79 | partial: partialsPath, 80 | helper: helpersPath, 81 | }); 82 | } 83 | 84 | /** 85 | * Resolves the paths to handlebars partials and helpers. 86 | * 87 | * @private 88 | * @param {ParserConfig} config - The configuration containing paths. 89 | * @returns {Object} - An object containing paths to partials and helpers. 90 | */ 91 | private getHandlebarsPaths(config: ParserConfig): { 92 | partialsPath: string[]; 93 | helpersPath: string[]; 94 | } { 95 | return { 96 | partialsPath: 97 | config.partials && config.partials.length > 0 ? config.partials : [], 98 | helpersPath: 99 | config.helpers && config.helpers.length > 0 ? config.helpers : [], 100 | }; 101 | } 102 | 103 | /** 104 | * Computes the paths for the provided file based on the configuration. 105 | * 106 | * @private 107 | * @param {DirectoryFile} file - The file for which paths are to be computed. 108 | * @param {ParserConfig} config - The configuration for path computation. 109 | * @returns {Object} - An object containing the computed paths. 110 | */ 111 | private getPaths( 112 | file: DirectoryFile, 113 | config: ParserConfig, 114 | ): {relativePathDest: string; relativePathSrc: string; dest: string} { 115 | const {relativePathDest, folderInDest} = computePaths(file, config); 116 | const relativePathSrc = getFileFolder(file); 117 | 118 | return { 119 | relativePathDest, 120 | relativePathSrc, 121 | dest: folderInDest, 122 | }; 123 | } 124 | } 125 | -------------------------------------------------------------------------------- /src/parsers/plugin-options.ts: -------------------------------------------------------------------------------- 1 | import {type PluginOptions, type ParsedPluginOptions} from '../interfaces.js'; 2 | 3 | /** 4 | * Parses the provided plugin options 5 | * 6 | * @param {PluginOptions} options - The plugin options to be parsed. 7 | * @returns {ParsedPluginOptions} An object containing: 8 | * - include: Array of patterns to include. 9 | * - exclude: Array of patterns to exclude. 10 | * - srcFolder: The source folder path, with './' prefix removed. 11 | * - jsDocConfigPath: The path to the JSDoc configuration file. 12 | * - codeFolder: The folder where the code resides. 13 | * - docsFolder: The destination folder for the generated documentation. 14 | * - title: The title for the documentation. 15 | * - readme: The path to the README file. 16 | * - rmPattern: Array of patterns to remove. 17 | * - partials: Array of partial templates. 18 | * - helpers: Array of helper functions. 19 | */ 20 | export const parsePluginOptions = ( 21 | options: PluginOptions, 22 | ): ParsedPluginOptions => { 23 | return { 24 | include: (options.include ?? '').split(',').filter(Boolean), 25 | exclude: (options.exclude || '').split(',').filter(Boolean), 26 | srcFolder: options.source.replace('./', ''), 27 | jsDocConfigPath: options.jsDocConfigPath ?? '', 28 | codeFolder: options.folder, 29 | docsFolder: `${options.dist}/${options.folder}`, 30 | title: options.title, 31 | readme: options.readme, 32 | rmPattern: options.rmPattern ?? [], 33 | partials: options.partials || [], 34 | helpers: options.helpers || [], 35 | }; 36 | }; 37 | -------------------------------------------------------------------------------- /src/parsers/vue.ts: -------------------------------------------------------------------------------- 1 | // Import {join} from 'node:path'; 2 | // import compileTemplates from 'vue-docgen-cli/lib/compileTemplates'; 3 | // import {extractConfig} from 'vue-docgen-cli/lib/docgen'; 4 | // import {computePaths, getFileName, getFileFolder} from '../utilities/file-path'; 5 | // import { 6 | // type DirectoryFile, 7 | // type ParseReturn, 8 | // type Parser, 9 | // type ParserConfig, 10 | // } from '../interfaces'; 11 | 12 | // /** 13 | // * Class representing a parser for Vue files. 14 | // * @class 15 | // * @implements {Parser} 16 | // */ 17 | // export class VueParser implements Parser { 18 | // /** 19 | // * Parses the content of a Vue file and returns its documentation. 20 | // * 21 | // * @param {DirectoryFile} file - The file object representing the Vue file to be parsed. 22 | // * @param {ParserConfig} config - The configuration for the parsing process. 23 | // * 24 | // * @returns {Promise} A promise that resolves with the parsed content of the Vue file, or undefined if the parsing fails. 25 | // */ 26 | // async parse( 27 | // file: DirectoryFile, 28 | // config: ParserConfig, 29 | // ): Promise { 30 | // const fileName = getFileName(file); 31 | // const relativePathSrc = getFileFolder(file); 32 | // const vueDocGenCliConf = await extractConfig( 33 | // join(process.cwd(), relativePathSrc), 34 | // ); 35 | // const docgenConfig = { 36 | // ...vueDocGenCliConf, 37 | // components: fileName + file.ext, 38 | // }; 39 | 40 | // const data = await compileTemplates( 41 | // 'add', 42 | // join(docgenConfig.componentsRoot, fileName + file.ext), 43 | // docgenConfig, 44 | // fileName + file.ext, 45 | // ); 46 | 47 | // const {relativePathDest, folderInDest} = computePaths(file, config); 48 | 49 | // return { 50 | // success: Boolean(data.content), 51 | // file, 52 | // empty: !data.content, 53 | // content: data.content, 54 | // relativePathDest, 55 | // relativePathSrc, 56 | // dest: folderInDest, 57 | // }; 58 | // } 59 | // } 60 | -------------------------------------------------------------------------------- /src/utilities/create-readme.ts: -------------------------------------------------------------------------------- 1 | import fs from 'node:fs/promises'; 2 | import {parsePluginOptions} from '../parsers/plugin-options.js'; 3 | import {type PluginOptions} from '../interfaces.js'; 4 | 5 | /** 6 | * Creates or updates the README file for the documentation. If a custom README file is provided, 7 | * its content will be used; otherwise, a default welcome message will be written. 8 | * 9 | * @function 10 | * @async 11 | * 12 | * @param {PluginOptions} options - The plugin options providing configuration for the README creation. 13 | * @param {string[]} [deletedPaths] - An optional list of paths that were deleted. Used to check if the README was among them. 14 | * 15 | * @returns {Promise} A promise that resolves when the README file has been successfully created or updated. 16 | * 17 | * @throws Will throw an error if there's an issue reading from or writing to the file system. 18 | * 19 | * @example 20 | * 21 | * const options = { 22 | * srcFolder: './src', 23 | * codeFolder: 'code', 24 | * docsFolder: './docs', 25 | * title: 'My Documentation', 26 | * readme: './src/README.md' 27 | * }; 28 | * 29 | * createReadmeFile(options); 30 | */ 31 | export const createReadmeFile = async ( 32 | options: PluginOptions, 33 | deletedPaths?: string[], 34 | ) => { 35 | const {srcFolder, codeFolder, docsFolder, title, readme} = 36 | parsePluginOptions(options); 37 | 38 | let readMeContent = ` 39 | # Welcome to ${title} 40 | 41 | Thank you for checking out this project! This is a default README message, as a custom README was not provided. Feel free to contribute or reach out with any questions or suggestions. 42 | `; 43 | 44 | const readmePath = readme || `${srcFolder}/README.md`; 45 | 46 | try { 47 | readMeContent = await fs.readFile(readmePath, 'utf8'); 48 | if (deletedPaths?.some((p) => p.includes(`${codeFolder}/README.md`))) { 49 | console.log( 50 | `\n README ${readmePath.replace( 51 | 'README.md', 52 | '', 53 | )}README.md → ${docsFolder}/README.md`, 54 | ); 55 | } 56 | } catch { 57 | console.log(`\n README Add default README.md`); 58 | } 59 | 60 | try { 61 | await fs.writeFile(`${docsFolder}/README.md`, readMeContent); 62 | } catch (error) { 63 | console.error(`Error writing to ${docsFolder}/README.md:`, error); 64 | throw error; 65 | } 66 | }; 67 | -------------------------------------------------------------------------------- /src/utilities/file-operations.ts: -------------------------------------------------------------------------------- 1 | import fs from 'node:fs/promises'; 2 | import {join} from 'node:path'; 3 | import {mkdirp} from 'mkdirp'; 4 | import { 5 | type ParseReturn, 6 | StatisticType, 7 | type PathRemover, 8 | } from '../interfaces.js'; 9 | import {RimrafPathRemover} from '../classes/rimraf-path-remover.js'; 10 | 11 | /** 12 | * Deletes the specified documentation folder and any additional paths matching the provided patterns. 13 | * 14 | * @function 15 | * @async 16 | * 17 | * @param {string} docsFolder - The path to the documentation folder to be deleted. 18 | * @param {string[]} rmPattern - An array of glob patterns specifying additional paths to be deleted. 19 | * 20 | * @returns {Promise} A promise that resolves with an array of paths that were deleted. 21 | * 22 | * @throws Will throw an error if not all paths were successfully deleted. 23 | * 24 | * @example 25 | * 26 | * const docsFolder = './docs'; 27 | * const rmPattern = ['**\/*.tmp']; // remove backslash 28 | * 29 | * const deletedPaths = await deleteDocsFolder(docsFolder, rmPattern); 30 | */ 31 | export const deleteDocsFolder = async ( 32 | docsFolder: string, 33 | rmPattern: string[], 34 | ): Promise => { 35 | const remover: PathRemover = new RimrafPathRemover(); 36 | const patterns = [docsFolder, ...rmPattern]; 37 | 38 | const allDeleted = await remover.delete(patterns); 39 | 40 | if (!allDeleted) { 41 | throw new Error('Not all paths were successfully deleted.'); 42 | } 43 | 44 | return remover.getDeletedPaths(); 45 | }; 46 | 47 | export const createDocsFolder = (docsFolder: string) => { 48 | mkdirp.sync(docsFolder); 49 | }; 50 | 51 | /** 52 | * Writes the parsed content to a file on disk. 53 | * 54 | * @function 55 | * @async 56 | * 57 | * @param {ParseReturn | undefined} parseData - The parsed data to be written to the file. 58 | * @param {string} dest - The destination path where the file should be written. 59 | * 60 | * @returns {Promise} A promise that resolves with an object containing details of the saved file, or null if the operation fails. 61 | * The returned object includes the original parsed data and a type indicating the status of the operation (e.g., included, excluded, error). 62 | * 63 | * @throws Will throw an error if there's an issue creating directories or writing to the file system. 64 | * 65 | * @example 66 | * 67 | * const parseData = { 68 | * content: '# My Documentation', 69 | * file: {name: 'example', ext: '.md'}, 70 | * empty: false, 71 | * excluded: false 72 | * }; 73 | * const dest = './docs'; 74 | * 75 | * const result = await writeContentToFile(parseData, dest); 76 | */ 77 | export const writeContentToFile = async ( 78 | parseData: ParseReturn | undefined, 79 | dest: string, 80 | ): Promise => { 81 | const root = process.cwd(); 82 | dest = join(root, dest); 83 | 84 | let type = StatisticType.ERROR; 85 | 86 | if (parseData?.excluded) { 87 | type = StatisticType.EXCLUDE; 88 | } 89 | 90 | try { 91 | if (parseData?.content) { 92 | const path = `${join(dest, parseData.file.name)}.md`; 93 | 94 | mkdirp.sync(dest); 95 | await fs.writeFile(path, parseData.content, 'utf8'); 96 | 97 | type = parseData?.empty ? StatisticType.EMPTY : StatisticType.INCLUDE; 98 | } 99 | 100 | if (!parseData) { 101 | throw new Error('Data is undefined'); 102 | } 103 | 104 | if (!parseData.dest) { 105 | throw new Error('Destination is undefined'); 106 | } 107 | 108 | return { 109 | ...parseData, 110 | type, 111 | }; 112 | } catch {} 113 | 114 | return undefined; 115 | }; 116 | -------------------------------------------------------------------------------- /src/utilities/file-path.ts: -------------------------------------------------------------------------------- 1 | import {join} from 'node:path'; 2 | import {type DirectoryFile, type ParserConfig} from '../interfaces.js'; 3 | 4 | /** 5 | * @file This module provides utility functions for handling file paths. 6 | */ 7 | 8 | /** 9 | * Retrieves the file name. If the file name is '\_\_index\_\_', it returns 'index'. 10 | * 11 | * @function 12 | * 13 | * @param {DirectoryFile} file - The file object to retrieve the name from. 14 | * 15 | * @returns {string} The name of the file. 16 | * 17 | * @example 18 | * 19 | * const file = {name: '__index__'}; 20 | * const name = getFileName(file); // 'index' 21 | */ 22 | export const getFileName = (file: DirectoryFile): string => { 23 | return file.name === '__index__' ? 'index' : file.name; 24 | }; 25 | 26 | /** 27 | * Retrieves the folder of the file. 28 | * 29 | * @function 30 | * 31 | * @param {DirectoryFile} file - The file object to retrieve the folder from. 32 | * 33 | * @returns {string} The folder of the file. 34 | * 35 | * @example 36 | * 37 | * const file = {folder: '/src/components'}; 38 | * const folder = getFileFolder(file); // '/src/components' 39 | */ 40 | export const getFileFolder = (file: DirectoryFile): string => { 41 | return file.folder ?? ''; 42 | }; 43 | 44 | /** 45 | * Computes the relative destination path and the absolute folder path in the destination directory. 46 | * 47 | * @function 48 | * 49 | * @param {DirectoryFile} file - The file object to compute paths for. 50 | * @param {ParserConfig} config - The configuration object containing source and documentation folders. 51 | * 52 | * @returns {object} An object containing: 53 | * - relativePathDest: The relative path in the destination directory. 54 | * - folderInDest: The absolute path of the folder in the destination directory. 55 | * 56 | * @example 57 | * 58 | * const file = {folder: '/src/components'}; 59 | * const config = {srcFolder: '/src', docsFolder: '/docs'}; 60 | * const paths = computePaths(file, config); 61 | */ 62 | export const computePaths = (file: DirectoryFile, config: ParserConfig) => { 63 | const {srcFolder, docsFolder} = config; 64 | const relativePathDest = join( 65 | docsFolder, 66 | getFileFolder(file).replace(srcFolder, ''), 67 | ); 68 | const folderInDest = join(process.cwd(), relativePathDest); 69 | return {relativePathDest, folderInDest}; 70 | }; 71 | -------------------------------------------------------------------------------- /src/utilities/file-reader.ts: -------------------------------------------------------------------------------- 1 | import {join} from 'node:path'; 2 | import fs from 'node:fs/promises'; 3 | import {type DirectoryFile} from '../interfaces.js'; 4 | import {parseVitepressFileHeader} from '../parsers/comment.js'; 5 | import {getFileName, getFileFolder} from './file-path.js'; 6 | 7 | /** 8 | * @file This module provides functionality to read the content of a file and parse its Vitepress header. 9 | */ 10 | 11 | /** 12 | * Reads the content of a file and parses its Vitepress header. 13 | * 14 | * @function 15 | * @async 16 | * 17 | * @param {DirectoryFile} file - The file object representing the file to be read. 18 | * 19 | * @returns {Promise} A promise that resolves with the parsed content of the file. 20 | * 21 | * @throws Will throw an error if there's an issue reading the file or parsing its content. 22 | * 23 | * @example 24 | * 25 | * const file = {name: 'example', ext: '.md', folder: '/src/docs'}; 26 | * const content = await readFileContent(file); 27 | */ 28 | export const readFileContent = async (file: DirectoryFile): Promise => { 29 | try { 30 | const filePath = file.path; 31 | 32 | const content = await fs.readFile(filePath, 'utf8'); 33 | 34 | return parseVitepressFileHeader(content, file); 35 | } catch (error) { 36 | if (error instanceof Error) { 37 | console.error(`Error reading or parsing file: ${error.message}`); 38 | } else { 39 | console.error('An unknown error occurred:', error); 40 | } 41 | 42 | throw error; 43 | } 44 | }; 45 | -------------------------------------------------------------------------------- /src/utilities/output.ts: -------------------------------------------------------------------------------- 1 | import readline from 'node:readline'; 2 | import {type ParseReturn, type FolderData} from '../interfaces.js'; 3 | 4 | /** 5 | * Asynchronously prints the paths of files from the provided `FolderData` to the console. 6 | * For each file: 7 | * - If it's a directory, it's skipped. 8 | * - If it's a file, its path is printed to the console. 9 | * 10 | * @function 11 | * @async 12 | * @param {FolderData} lsFolder - The data structure containing paths and other folder-related information. 13 | * @returns {Promise} A promise that resolves once all file paths have been printed. 14 | * @example 15 | * 16 | * const folderData = { 17 | * paths: [...], 18 | * tree: [...], 19 | * excluded: [...] 20 | * }; 21 | * await printFiles(folderData); 22 | */ 23 | export const printFiles = async (lsFolder: FolderData) => { 24 | const printPromises: Array> = lsFolder.paths.map( 25 | async (file) => { 26 | return new Promise((resolve) => { 27 | if (file.isDir) { 28 | resolve(); 29 | } else { 30 | readline.clearLine(process.stdout, 0); 31 | readline.cursorTo(process.stdout, 0); 32 | process.stdout.write(` ${file.path} `); 33 | setTimeout(resolve, 20); 34 | } 35 | }); 36 | }, 37 | ); 38 | 39 | await Promise.all(printPromises); 40 | 41 | readline.clearLine(process.stdout, 0); 42 | readline.cursorTo(process.stdout, 0); 43 | }; 44 | 45 | /** 46 | * Prints statistics about the processed files to the console. 47 | * - First, it prints all excluded files with the label 'EXCLUDE'. 48 | * - Then, for each entry in the `result`: 49 | * - If there's no file associated with the entry, it's skipped. 50 | * - Otherwise, it logs the type of the entry and the source to destination mapping. 51 | * 52 | * @function 53 | * @param {FolderData} lsFolder - The data structure containing paths and other folder-related information. 54 | * @param {ParseReturn[]} result - An array of results from the parsing process. 55 | * @example 56 | * 57 | * const folderData = { 58 | * paths: [...], 59 | * tree: [...], 60 | * excluded: [...] 61 | * }; 62 | * const results = [...]; 63 | * printStats(folderData, results); 64 | */ 65 | export const printStats = (lsFolder: FolderData, result: ParseReturn[]) => { 66 | for (const file of lsFolder.excluded) { 67 | console.log(' EXCLUDE', `${file.folder}${file.name + file.ext}`); 68 | } 69 | 70 | console.log(); 71 | 72 | for (const entry of result) { 73 | if (!entry.file) continue; 74 | 75 | const sourceFilePath = `${entry.relativePathSrc}${entry.file.name}${entry.file.ext}`; 76 | const destinationFilePath = `${entry.relativePathDest}${entry.file.name}.md`; 77 | 78 | console.log( 79 | ` ${entry.type}`, 80 | `${sourceFilePath} -> ${destinationFilePath}`, 81 | ); 82 | } 83 | }; 84 | -------------------------------------------------------------------------------- /test/pass.test.ts: -------------------------------------------------------------------------------- 1 | import fs from 'node:fs'; 2 | import path from 'node:path'; 3 | import {test, expect} from 'vitest'; 4 | 5 | test('bin file existence', () => { 6 | const binPath = path.resolve('./dist/esm/bin.js'); 7 | expect(fs.existsSync(binPath)).toBe(true); 8 | }); 9 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "declaration": true, 4 | "declarationMap": true, 5 | "esModuleInterop": true, 6 | "forceConsistentCasingInFileNames": true, 7 | "inlineSources": true, 8 | "jsx": "react", 9 | "module": "nodenext", 10 | "moduleResolution": "nodenext", 11 | "noUncheckedIndexedAccess": true, 12 | "resolveJsonModule": true, 13 | "skipLibCheck": true, 14 | "sourceMap": true, 15 | "strict": true, 16 | "target": "es2022", 17 | } 18 | } 19 | --------------------------------------------------------------------------------