├── .github └── workflows │ └── node.js.yml ├── LICENSE ├── README.md ├── dist └── index.cjs ├── docs └── api.md ├── example ├── explain-source.js ├── explain.js ├── render-source.js ├── render.js ├── using-config-explain.js ├── using-config-input.js ├── using-config-render.js └── using-config.json ├── index.js ├── lib ├── explain.js ├── jsdoc-command.js ├── render.js └── temp-file.js ├── package-lock.json ├── package.json └── test ├── caching.js ├── explain.js ├── fixture ├── buggy │ ├── bad-doclet-syntax.js │ ├── broken-javascript.js │ └── ignore-with-value.js ├── class-all │ ├── 0-src.js │ └── 1-jsdoc.json └── folder with spaces │ └── fake-jsdoc.js ├── lib └── fixture.js └── render.js /.github/workflows/node.js.yml: -------------------------------------------------------------------------------- 1 | # For more information see: https://help.github.com/actions/language-and-framework-guides/using-nodejs-with-github-actions 2 | 3 | name: Node.js CI 4 | 5 | on: 6 | push: 7 | branches: [ master, next ] 8 | pull_request: 9 | branches: [ master ] 10 | 11 | jobs: 12 | build: 13 | 14 | runs-on: ${{ matrix.os }} 15 | 16 | strategy: 17 | matrix: 18 | os: [ubuntu-latest, windows-latest] 19 | node-version: [12, 14, 16, 18, 20, 22, 23] 20 | 21 | steps: 22 | - uses: actions/checkout@v4 23 | - name: Use Node.js ${{ matrix.node-version }} 24 | uses: actions/setup-node@v4 25 | with: 26 | node-version: ${{ matrix.node-version }} 27 | cache: 'npm' 28 | - run: npm install 29 | - run: npm i -D @75lb/nature 30 | - run: npm run test:ci 31 | 32 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015-24 Lloyd Brookes <75pound@gmail.com> 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![view on npm](https://badgen.net/npm/v/jsdoc-api)](https://www.npmjs.org/package/jsdoc-api) 2 | [![npm module downloads](https://badgen.net/npm/dt/jsdoc-api)](https://www.npmjs.org/package/jsdoc-api) 3 | [![Gihub repo dependents](https://badgen.net/github/dependents-repo/jsdoc2md/jsdoc-api)](https://github.com/jsdoc2md/jsdoc-api/network/dependents?dependent_type=REPOSITORY) 4 | [![Gihub package dependents](https://badgen.net/github/dependents-pkg/jsdoc2md/jsdoc-api)](https://github.com/jsdoc2md/jsdoc-api/network/dependents?dependent_type=PACKAGE) 5 | [![Node.js CI](https://github.com/jsdoc2md/jsdoc-api/actions/workflows/node.js.yml/badge.svg)](https://github.com/jsdoc2md/jsdoc-api/actions/workflows/node.js.yml) 6 | [![js-standard-style](https://img.shields.io/badge/code%20style-standard-brightgreen.svg)](https://github.com/feross/standard) 7 | 8 | ***Upgraders, please check the [release notes](https://github.com/jsdoc2md/jsdoc-api/releases).*** 9 | 10 | # jsdoc-api 11 | 12 | A programmatic interface for [jsdoc3](https://github.com/jsdoc3/jsdoc) with a few features: 13 | 14 | - Asynchronous 'explain' and 'render documentation' methods (the two main jsdoc operations). 15 | - Input (source code) can supplied as a string or set of file names and/or globs. 16 | - Optional caching, dramatically speeding up future invocations with the same input. 17 | 18 | ## Synopsis 19 | 20 | To output an array of json objects, each representing a doclet, use [.explain()](https://github.com/jsdoc2md/jsdoc-api/blob/master/docs/api.md#module_jsdoc-api--jsdoc.explain). Pass in an array of file names and/or glob expressions. Use the `cache: true` flag for a faster, more efficient invocation (cached output from a prior invocation will be returned if the input has not changed). 21 | 22 | ```js 23 | import jsdoc from 'jsdoc-api' 24 | 25 | const data = await jsdoc.explain({ files: ['index.js', 'lib/*.js'], cache: true }) 26 | console.log(data) 27 | ``` 28 | 29 | Typical output (truncated): 30 | 31 | ```js 32 | [ 33 | { 34 | comment: '/**\n' + 35 | ' * The [cache-point](https://github.com/75lb/cache-point) instance used when `cache: true` is specified on `.explain()`.\n' + 36 | ' * @type {external:cache-point}\n' + 37 | ' */', 38 | meta: { 39 | range: [ 491, 554 ], 40 | filename: 'index.js', 41 | lineno: 21, 42 | columnno: 6, 43 | path: '/Users/lloyd/Documents/jsdoc2md/jsdoc-api', 44 | code: { id: 'astnode100000027', name: 'cache', type: 'NewExpression', value: '' } 45 | }, 46 | description: 'The [cache-point](https://github.com/75lb/cache-point) instance used when `cache: true` is specified on `.explain()`.', 47 | type: { names: [ 'external:cache-point' ] }, 48 | name: 'cache', 49 | longname: 'module:jsdoc-api~cache', 50 | kind: 'constant', 51 | scope: 'inner', 52 | memberof: 'module:jsdoc-api', 53 | params: [] 54 | }, 55 | // etc 56 | // etc 57 | ] 58 | ``` 59 | 60 | As an alternative to passing in file names/globs (above), you can pass in one or more source code strings. 61 | 62 | ```js 63 | import jsdoc from 'jsdoc-api' 64 | 65 | const data = await jsdoc.explain({ source: '/** example doclet */ \n var example = true' }) 66 | console.log(data) 67 | ``` 68 | 69 | Output: 70 | 71 | ```js 72 | [ 73 | { 74 | comment: '/** example doclet */', 75 | meta: { 76 | range: [ 28, 42 ], 77 | filename: '934b1fbe2810.js', 78 | lineno: 2, 79 | columnno: 5, 80 | path: '/var/folders/bt/jgn73jf50vsb5gj92dk00v3r0000gn/T/jsdoc-api-W854dk', 81 | code: { id: 'astnode100000003', name: 'example', type: 'Literal', value: true } 82 | }, 83 | description: 'example doclet', 84 | name: 'example', 85 | longname: 'example', 86 | kind: 'member', 87 | scope: 'global', 88 | params: [] 89 | }, 90 | { kind: 'package', longname: 'package:undefined', files: [ '/var/folders/bt/jgn73jf50vsb5gj92dk00v3r0000gn/T/jsdoc-api-W854dk/934b1fbe2810.js' ] } 91 | ] 92 | ``` 93 | 94 | Finally, use the `render()` method to invocate jsdoc directly, generating your documentation. 95 | 96 | ```js 97 | import jsdoc from 'jsdoc-api' 98 | 99 | await jsdoc.render({ files: ['index.js', 'lib/something.js'], destination: 'jsdoc-output' }) 100 | ``` 101 | 102 | If you need to use a specific `jsdoc` version or fork, specify its path via `JSDOC_PATH` and jsdoc-api will use it instead of the default. 103 | 104 | ```sh 105 | $ export JSDOC_PATH=./node_modules/.bin/jsdoc # An alternative jsdoc version you installed 106 | $ node my-jsdoc-api-script.js # Run your jsdoc-api app as usual 107 | ``` 108 | 109 | See the [API documentation](https://github.com/jsdoc2md/jsdoc-api/blob/master/docs/api.md) for further details. See the [example folder](https://github.com/jsdoc2md/jsdoc-api/tree/master/example) for code examples. 110 | 111 | * * * 112 | 113 | © 2015-24 [Lloyd Brookes](https://github.com/75lb) \<75pound@gmail.com\>. 114 | 115 | Tested by [test-runner](https://github.com/test-runner-js/test-runner). Documented by [jsdoc-to-markdown](https://github.com/jsdoc2md/jsdoc-to-markdown). 116 | -------------------------------------------------------------------------------- /dist/index.cjs: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var Cache = require('cache-point'); 4 | var arrayify = require('array-back'); 5 | var path = require('path'); 6 | var fs = require('node:fs'); 7 | var os = require('os'); 8 | var crypto = require('crypto'); 9 | var FileSet = require('file-set'); 10 | var assert = require('assert'); 11 | var walkBack = require('walk-back'); 12 | var currentModulePaths = require('current-module-paths'); 13 | var toSpawnArgs = require('object-to-spawn-args'); 14 | var cp = require('child_process'); 15 | var util = require('node:util'); 16 | 17 | var _documentCurrentScript = typeof document !== 'undefined' ? document.currentScript : null; 18 | class TempFile { 19 | constructor (source) { 20 | this.path = path.join(TempFile.tempFileDir, crypto.randomBytes(6).toString('hex') + '.js'); 21 | fs.writeFileSync(this.path, source); 22 | } 23 | 24 | delete () { 25 | try { 26 | fs.unlinkSync(this.path); 27 | } catch (err) { 28 | // already deleted 29 | } 30 | } 31 | 32 | static tempFileDir = path.join(os.homedir(), '.jsdoc-api/temp') 33 | static cacheDir = path.join(os.homedir(), '.jsdoc-api/cache') 34 | 35 | static createTmpDirs () { 36 | /* No longer using os.tmpdir(). See: https://github.com/jsdoc2md/jsdoc-api/issues/19 */ 37 | fs.mkdirSync(TempFile.tempFileDir, { recursive: true }); 38 | fs.mkdirSync(TempFile.cacheDir, { recursive: true }); 39 | } 40 | } 41 | 42 | const { __dirname: __dirname$1 } = currentModulePaths((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('index.cjs', document.baseURI).href))); 43 | 44 | class JsdocCommand { 45 | constructor (options = {}, cache) { 46 | options.files = arrayify(options.files); 47 | options.source = arrayify(options.source); 48 | assert.ok( 49 | options.files.length || options.source.length || options.configure, 50 | 'Must set at least one of .files, .source or .configure' 51 | ); 52 | 53 | this.cache = cache; 54 | this.tempFiles = []; 55 | 56 | const jsdocOptions = Object.assign({}, options); 57 | delete jsdocOptions.files; 58 | delete jsdocOptions.source; 59 | delete jsdocOptions.cache; 60 | 61 | /* see: https://github.com/jsdoc2md/jsdoc-api/issues/22 */ 62 | if (!jsdocOptions.pedantic) { 63 | delete jsdocOptions.pedantic; 64 | } 65 | 66 | this.options = options; 67 | this.jsdocOptions = jsdocOptions; 68 | 69 | this.jsdocPath = process.env.JSDOC_PATH || walkBack( 70 | path.join(__dirname$1, '..'), 71 | path.join('node_modules', 'jsdoc', 'jsdoc.js') 72 | ); 73 | } 74 | 75 | async execute () { 76 | this.inputFileSet = new FileSet(); 77 | await this.inputFileSet.add(this.options.files); 78 | /* node-glob v9+ (used by file-set) no longer sorts the output by default. We will continue to sort the file list, for now, as it's what the user expected by default. The user's system locale is used. */ 79 | const collator = new Intl.Collator(); 80 | this.inputFileSet.files.sort(collator.compare); 81 | 82 | if (this.options.source.length) { 83 | this.tempFiles = this.options.source.map(source => new TempFile(source)); 84 | this.tempFileSet = new FileSet(); 85 | await this.tempFileSet.add(this.tempFiles.map(t => t.path)); 86 | } 87 | 88 | let result; 89 | try { 90 | result = await this.getOutput(); 91 | } finally { 92 | /* run even if getOutput fails */ 93 | if (this.tempFiles) { 94 | for (const tempFile of this.tempFiles) { 95 | tempFile.delete(); 96 | } 97 | } 98 | } 99 | return result 100 | } 101 | } 102 | 103 | util.promisify(cp.exec); 104 | 105 | class Explain extends JsdocCommand { 106 | async getOutput () { 107 | if (this.options.cache && !this.options.source.length) { 108 | try { 109 | return await this.readCache() 110 | } catch (err) { 111 | if (err.code === 'ENOENT') { 112 | return this._runJsdoc() 113 | } else { 114 | throw err 115 | } 116 | } 117 | } else { 118 | return this._runJsdoc() 119 | } 120 | } 121 | 122 | async _runJsdoc () { 123 | const jsdocArgs = [ 124 | this.jsdocPath, 125 | ...toSpawnArgs(this.jsdocOptions), 126 | '-X', 127 | ...(this.options.source.length ? this.tempFileSet.files : this.inputFileSet.files) 128 | ]; 129 | let jsdocOutput = { stdout: '', stderr: '' }; 130 | 131 | const code = await new Promise((resolve, reject) => { 132 | const handle = cp.spawn('node', jsdocArgs); 133 | handle.stdout.setEncoding('utf8'); 134 | handle.stderr.setEncoding('utf8'); 135 | handle.stdout.on('data', chunk => { 136 | jsdocOutput.stdout += chunk; 137 | }); 138 | handle.stderr.on('data', chunk => { 139 | jsdocOutput.stderr += chunk; 140 | }); 141 | handle.on('exit', (code) => { 142 | resolve(code); 143 | }); 144 | handle.on('error', reject); 145 | }); 146 | try { 147 | if (code > 0) { 148 | throw new Error('jsdoc exited with non-zero code: ' + code) 149 | } else { 150 | const explainOutput = JSON.parse(jsdocOutput.stdout); 151 | if (this.options.cache) { 152 | await this.cache.write(this.cacheKey, explainOutput); 153 | } 154 | return explainOutput 155 | } 156 | } catch (err) { 157 | const firstLineOfStdout = jsdocOutput.stdout.split(/\r?\n/)[0]; 158 | const jsdocErr = new Error(jsdocOutput.stderr.trim() || firstLineOfStdout || 'Jsdoc failed.'); 159 | jsdocErr.name = 'JSDOC_ERROR'; 160 | jsdocErr.cause = err; 161 | throw jsdocErr 162 | } 163 | } 164 | 165 | async readCache () { 166 | if (this.cache) { 167 | /* Create the cache key then check the cache for a match, returning pre-generated output if so. 168 | The current cache key is a union of the input file names plus their content - this could be expensive when processing a lot of files. 169 | */ 170 | const promises = this.inputFileSet.files.map(file => { 171 | return fs.promises.readFile(file, 'utf8') 172 | }); 173 | const contents = await Promise.all(promises); 174 | this.cacheKey = contents.concat(this.inputFileSet.files); 175 | return this.cache.read(this.cacheKey) 176 | } else { 177 | return Promise.reject() 178 | } 179 | } 180 | } 181 | 182 | class Render extends JsdocCommand { 183 | async getOutput () { 184 | return new Promise((resolve, reject) => { 185 | const jsdocArgs = toSpawnArgs(this.jsdocOptions) 186 | .concat(this.options.source.length ? this.tempFiles.map(t => t.path) : this.options.files); 187 | 188 | jsdocArgs.unshift(this.jsdocPath); 189 | const handle = cp.spawn('node', jsdocArgs, { stdio: 'inherit' }); 190 | handle.on('close', resolve); 191 | }) 192 | } 193 | } 194 | 195 | /** 196 | * @module jsdoc-api 197 | * @typicalname jsdoc 198 | */ 199 | 200 | TempFile.createTmpDirs(); 201 | 202 | /** 203 | * @external cache-point 204 | * @see https://github.com/75lb/cache-point 205 | */ 206 | /** 207 | * The [cache-point](https://github.com/75lb/cache-point) instance used when `cache: true` is specified on `.explain()`. 208 | * @type {external:cache-point} 209 | */ 210 | const cache = new Cache({ dir: TempFile.cacheDir }); 211 | 212 | /** 213 | * @alias module:jsdoc-api 214 | */ 215 | const jsdoc = { 216 | cache, 217 | 218 | /** 219 | * Returns a promise for the jsdoc explain output. 220 | * 221 | * @param [options] {module:jsdoc-api~JsdocOptions} 222 | * @fulfil {object[]} - jsdoc explain output 223 | * @returns {Promise} 224 | */ 225 | async explain (options) { 226 | options = new JsdocOptions(options); 227 | const command = new Explain(options, cache); 228 | return command.execute() 229 | }, 230 | 231 | /** 232 | * Render jsdoc documentation. 233 | * 234 | * @param [options] {module:jsdoc-api~JsdocOptions} 235 | * @prerequisite Requires node v0.12 or above 236 | * @example 237 | * await jsdoc.render({ files: 'lib/*', destination: 'api-docs' }) 238 | */ 239 | async render (options) { 240 | options = new JsdocOptions(options); 241 | const command = new Render(options); 242 | return command.execute() 243 | } 244 | }; 245 | 246 | /** 247 | * The jsdoc options, common for all operations. 248 | * @typicalname options 249 | */ 250 | class JsdocOptions { 251 | constructor (options = {}) { 252 | /** 253 | * One or more filenames to process. Either `files`, `source` or `configure` must be supplied. 254 | * @type {string|string[]} 255 | */ 256 | this.files = arrayify(options.files); 257 | 258 | /** 259 | * A string or an array of strings containing source code to process. Either `files`, `source` or `configure` must be supplied. 260 | * @type {string|string[]} 261 | */ 262 | this.source = options.source; 263 | 264 | /** 265 | * Set to `true` to cache the output - future invocations with the same input will return immediately. 266 | * @type {boolean} 267 | * @default 268 | */ 269 | this.cache = options.cache; 270 | 271 | /** 272 | * Only display symbols with the given access: "public", "protected", "private" or "undefined", or "all" for all access levels. Default: all except "private". 273 | * @type {string} 274 | */ 275 | this.access = options.access; 276 | 277 | /** 278 | * The path to the configuration file. Default: path/to/jsdoc/conf.json. Either `files`, `source` or `configure` must be supplied. 279 | * @type {string} 280 | */ 281 | this.configure = options.configure; 282 | 283 | /** 284 | * The path to the output folder. Use "console" to dump data to the console. Default: ./out/. 285 | * @type {string} 286 | */ 287 | this.destination = options.destination; 288 | 289 | /** 290 | * Assume this encoding when reading all source files. Default: utf8. 291 | * @type {string} 292 | */ 293 | this.encoding = options.encoding; 294 | 295 | /** 296 | * Display symbols marked with the @private tag. Equivalent to "--access all". Default: false. 297 | * @type {boolean} 298 | */ 299 | this.private = options.private; 300 | 301 | /** 302 | * The path to the project's package file. Default: path/to/sourcefiles/package.json 303 | * @type {string} 304 | */ 305 | this.package = options.package; 306 | 307 | /** 308 | * Treat errors as fatal errors, and treat warnings as errors. Default: false. 309 | * @type {boolean} 310 | */ 311 | this.pedantic = options.pedantic; 312 | 313 | /** 314 | * A query string to parse and store in jsdoc.env.opts.query. Example: foo=bar&baz=true. 315 | * @type {string} 316 | */ 317 | this.query = options.query; 318 | 319 | /** 320 | * Recurse into subdirectories when scanning for source files and tutorials. 321 | * @type {boolean} 322 | */ 323 | this.recurse = options.recurse; 324 | 325 | /** 326 | * The path to the project's README file. Default: path/to/sourcefiles/README.md. 327 | * @type {string} 328 | */ 329 | this.readme = options.readme; 330 | 331 | /* Warning to avoid a common mistake where dmd templates are passed in.. a jsdoc template must be a filename. */ 332 | if (typeof options.template === 'string' && options.template.split(/\r?\n/).length !== 1) { 333 | console.warn('Suspicious `options.template` value - the jsdoc `template` option must be a file path.'); 334 | console.warn(options.template); 335 | } 336 | 337 | /** 338 | * The path to the template to use. Default: path/to/jsdoc/templates/default. 339 | * @type {string} 340 | */ 341 | this.template = options.template; 342 | 343 | /** 344 | * Directory in which JSDoc should search for tutorials. 345 | * @type {string} 346 | */ 347 | this.tutorials = options.tutorials; 348 | } 349 | } 350 | 351 | module.exports = jsdoc; 352 | -------------------------------------------------------------------------------- /docs/api.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | ## jsdoc-api 4 | 5 | * [jsdoc-api](#module_jsdoc-api) 6 | * [jsdoc](#exp_module_jsdoc-api--jsdoc) ⏏ 7 | * _static_ 8 | * [.explain([options])](#module_jsdoc-api--jsdoc.explain) ⇒ Promise 9 | * [.render([options])](#module_jsdoc-api--jsdoc.render) 10 | * _inner_ 11 | * [~JsdocOptions](#module_jsdoc-api--jsdoc..JsdocOptions) 12 | * [.files](#module_jsdoc-api--jsdoc..JsdocOptions+files) : string \| Array.<string> 13 | * [.source](#module_jsdoc-api--jsdoc..JsdocOptions+source) : string \| Array.<string> 14 | * [.cache](#module_jsdoc-api--jsdoc..JsdocOptions+cache) : boolean 15 | * [.access](#module_jsdoc-api--jsdoc..JsdocOptions+access) : string 16 | * [.configure](#module_jsdoc-api--jsdoc..JsdocOptions+configure) : string 17 | * [.destination](#module_jsdoc-api--jsdoc..JsdocOptions+destination) : string 18 | * [.encoding](#module_jsdoc-api--jsdoc..JsdocOptions+encoding) : string 19 | * [.private](#module_jsdoc-api--jsdoc..JsdocOptions+private) : boolean 20 | * [.package](#module_jsdoc-api--jsdoc..JsdocOptions+package) : string 21 | * [.pedantic](#module_jsdoc-api--jsdoc..JsdocOptions+pedantic) : boolean 22 | * [.query](#module_jsdoc-api--jsdoc..JsdocOptions+query) : string 23 | * [.recurse](#module_jsdoc-api--jsdoc..JsdocOptions+recurse) : boolean 24 | * [.readme](#module_jsdoc-api--jsdoc..JsdocOptions+readme) : string 25 | * [.template](#module_jsdoc-api--jsdoc..JsdocOptions+template) : string 26 | * [.tutorials](#module_jsdoc-api--jsdoc..JsdocOptions+tutorials) : string 27 | * [~cache](#module_jsdoc-api--jsdoc..cache) : [cache-point](https://github.com/75lb/cache-point) 28 | 29 | 30 | 31 | ### jsdoc ⏏ 32 | **Kind**: Exported constant 33 | 34 | 35 | #### jsdoc.explain([options]) ⇒ Promise 36 | Returns a promise for the jsdoc explain output. 37 | 38 | **Kind**: static method of [jsdoc](#exp_module_jsdoc-api--jsdoc) 39 | **Fulfil**: object[] - jsdoc explain output 40 | 41 | | Param | Type | 42 | | --- | --- | 43 | | [options] | [JsdocOptions](#module_jsdoc-api--jsdoc..JsdocOptions) | 44 | 45 | 46 | 47 | #### jsdoc.render([options]) 48 | Render jsdoc documentation. 49 | 50 | **Kind**: static method of [jsdoc](#exp_module_jsdoc-api--jsdoc) 51 | **Prerequisite**: Requires node v0.12 or above 52 | 53 | | Param | Type | 54 | | --- | --- | 55 | | [options] | [JsdocOptions](#module_jsdoc-api--jsdoc..JsdocOptions) | 56 | 57 | **Example** 58 | ```js 59 | await jsdoc.render({ files: 'lib/*', destination: 'api-docs' }) 60 | ``` 61 | 62 | 63 | #### jsdoc~JsdocOptions 64 | The jsdoc options, common for all operations. 65 | 66 | **Kind**: inner class of [jsdoc](#exp_module_jsdoc-api--jsdoc) 67 | 68 | * [~JsdocOptions](#module_jsdoc-api--jsdoc..JsdocOptions) 69 | * [.files](#module_jsdoc-api--jsdoc..JsdocOptions+files) : string \| Array.<string> 70 | * [.source](#module_jsdoc-api--jsdoc..JsdocOptions+source) : string \| Array.<string> 71 | * [.cache](#module_jsdoc-api--jsdoc..JsdocOptions+cache) : boolean 72 | * [.access](#module_jsdoc-api--jsdoc..JsdocOptions+access) : string 73 | * [.configure](#module_jsdoc-api--jsdoc..JsdocOptions+configure) : string 74 | * [.destination](#module_jsdoc-api--jsdoc..JsdocOptions+destination) : string 75 | * [.encoding](#module_jsdoc-api--jsdoc..JsdocOptions+encoding) : string 76 | * [.private](#module_jsdoc-api--jsdoc..JsdocOptions+private) : boolean 77 | * [.package](#module_jsdoc-api--jsdoc..JsdocOptions+package) : string 78 | * [.pedantic](#module_jsdoc-api--jsdoc..JsdocOptions+pedantic) : boolean 79 | * [.query](#module_jsdoc-api--jsdoc..JsdocOptions+query) : string 80 | * [.recurse](#module_jsdoc-api--jsdoc..JsdocOptions+recurse) : boolean 81 | * [.readme](#module_jsdoc-api--jsdoc..JsdocOptions+readme) : string 82 | * [.template](#module_jsdoc-api--jsdoc..JsdocOptions+template) : string 83 | * [.tutorials](#module_jsdoc-api--jsdoc..JsdocOptions+tutorials) : string 84 | 85 | 86 | 87 | ##### options.files : string \| Array.<string> 88 | One or more filenames to process. Either `files`, `source` or `configure` must be supplied. 89 | 90 | **Kind**: instance property of [JsdocOptions](#module_jsdoc-api--jsdoc..JsdocOptions) 91 | 92 | 93 | ##### options.source : string \| Array.<string> 94 | A string or an array of strings containing source code to process. Either `files`, `source` or `configure` must be supplied. 95 | 96 | **Kind**: instance property of [JsdocOptions](#module_jsdoc-api--jsdoc..JsdocOptions) 97 | 98 | 99 | ##### options.cache : boolean 100 | Set to `true` to cache the output - future invocations with the same input will return immediately. 101 | 102 | **Kind**: instance property of [JsdocOptions](#module_jsdoc-api--jsdoc..JsdocOptions) 103 | 104 | 105 | ##### options.access : string 106 | Only display symbols with the given access: "public", "protected", "private" or "undefined", or "all" for all access levels. Default: all except "private". 107 | 108 | **Kind**: instance property of [JsdocOptions](#module_jsdoc-api--jsdoc..JsdocOptions) 109 | 110 | 111 | ##### options.configure : string 112 | The path to the configuration file. Default: path/to/jsdoc/conf.json. Either `files`, `source` or `configure` must be supplied. 113 | 114 | **Kind**: instance property of [JsdocOptions](#module_jsdoc-api--jsdoc..JsdocOptions) 115 | 116 | 117 | ##### options.destination : string 118 | The path to the output folder. Use "console" to dump data to the console. Default: ./out/. 119 | 120 | **Kind**: instance property of [JsdocOptions](#module_jsdoc-api--jsdoc..JsdocOptions) 121 | 122 | 123 | ##### options.encoding : string 124 | Assume this encoding when reading all source files. Default: utf8. 125 | 126 | **Kind**: instance property of [JsdocOptions](#module_jsdoc-api--jsdoc..JsdocOptions) 127 | 128 | 129 | ##### options.private : boolean 130 | Display symbols marked with the @private tag. Equivalent to "--access all". Default: false. 131 | 132 | **Kind**: instance property of [JsdocOptions](#module_jsdoc-api--jsdoc..JsdocOptions) 133 | 134 | 135 | ##### options.package : string 136 | The path to the project's package file. Default: path/to/sourcefiles/package.json 137 | 138 | **Kind**: instance property of [JsdocOptions](#module_jsdoc-api--jsdoc..JsdocOptions) 139 | 140 | 141 | ##### options.pedantic : boolean 142 | Treat errors as fatal errors, and treat warnings as errors. Default: false. 143 | 144 | **Kind**: instance property of [JsdocOptions](#module_jsdoc-api--jsdoc..JsdocOptions) 145 | 146 | 147 | ##### options.query : string 148 | A query string to parse and store in jsdoc.env.opts.query. Example: foo=bar&baz=true. 149 | 150 | **Kind**: instance property of [JsdocOptions](#module_jsdoc-api--jsdoc..JsdocOptions) 151 | 152 | 153 | ##### options.recurse : boolean 154 | Recurse into subdirectories when scanning for source files and tutorials. 155 | 156 | **Kind**: instance property of [JsdocOptions](#module_jsdoc-api--jsdoc..JsdocOptions) 157 | 158 | 159 | ##### options.readme : string 160 | The path to the project's README file. Default: path/to/sourcefiles/README.md. 161 | 162 | **Kind**: instance property of [JsdocOptions](#module_jsdoc-api--jsdoc..JsdocOptions) 163 | 164 | 165 | ##### options.template : string 166 | The path to the template to use. Default: path/to/jsdoc/templates/default. 167 | 168 | **Kind**: instance property of [JsdocOptions](#module_jsdoc-api--jsdoc..JsdocOptions) 169 | 170 | 171 | ##### options.tutorials : string 172 | Directory in which JSDoc should search for tutorials. 173 | 174 | **Kind**: instance property of [JsdocOptions](#module_jsdoc-api--jsdoc..JsdocOptions) 175 | 176 | 177 | #### jsdoc~cache : [cache-point](https://github.com/75lb/cache-point) 178 | The [cache-point](https://github.com/75lb/cache-point) instance used when `cache: true` is specified on `.explain()`. 179 | 180 | **Kind**: inner constant of [jsdoc](#exp_module_jsdoc-api--jsdoc) 181 | -------------------------------------------------------------------------------- /example/explain-source.js: -------------------------------------------------------------------------------- 1 | import jsdoc from 'jsdoc-api' 2 | 3 | const data = await jsdoc.explain({ source: '/** example doclet */ \n var example = true' }) 4 | console.log(data) 5 | -------------------------------------------------------------------------------- /example/explain.js: -------------------------------------------------------------------------------- 1 | import jsdoc from 'jsdoc-api' 2 | 3 | const data = await jsdoc.explain({ files: process.argv.slice(2), cache: true }) 4 | console.log(data) 5 | -------------------------------------------------------------------------------- /example/render-source.js: -------------------------------------------------------------------------------- 1 | import jsdoc from 'jsdoc-api' 2 | 3 | const source = [ 4 | `import Foo from "foo" 5 | /** 6 | * FooPrime is some child class 7 | * @class 8 | * @param {Object} - an input 9 | * @extends Foo 10 | */ 11 | function FooPrime() {} 12 | export default FooPrime 13 | `, 14 | `import Foo from "foo" 15 | /** 16 | * FooSecond is some other child class 17 | * @class 18 | * @param {Object} - an input 19 | * @extends Foo 20 | */ 21 | function FooSecond() {} 22 | export default FooSecond 23 | `] 24 | 25 | await jsdoc.render({ source, destination: 'source-output' }) 26 | -------------------------------------------------------------------------------- /example/render.js: -------------------------------------------------------------------------------- 1 | import jsdoc from 'jsdoc-api' 2 | 3 | await jsdoc.render({ files: ['.'], destination: 'jsdoc-output' }) 4 | -------------------------------------------------------------------------------- /example/using-config-explain.js: -------------------------------------------------------------------------------- 1 | import jsdoc from 'jsdoc-api' 2 | 3 | const data = await jsdoc.explain({ cache: true, configure: './example/using-config.json' }) 4 | console.log(data) 5 | 6 | /* 7 | The `using-config.json` file looks like this: 8 | 9 | { 10 | "source": { 11 | "include": [ "example/using-config-input.js" ] 12 | }, 13 | "opts": { 14 | "destination": "./config-out/" 15 | } 16 | } 17 | */ 18 | -------------------------------------------------------------------------------- /example/using-config-input.js: -------------------------------------------------------------------------------- 1 | /** 2 | * A documented function. 3 | */ 4 | function something () {} 5 | -------------------------------------------------------------------------------- /example/using-config-render.js: -------------------------------------------------------------------------------- 1 | import jsdoc from 'jsdoc-api' 2 | 3 | await jsdoc.render({ configure: './example/using-config.json' }) 4 | -------------------------------------------------------------------------------- /example/using-config.json: -------------------------------------------------------------------------------- 1 | { 2 | "source": { 3 | "include": [ "example/using-config-input.js" ] 4 | }, 5 | "opts": { 6 | "destination": "./config-out/" 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @module jsdoc-api 3 | * @typicalname jsdoc 4 | */ 5 | import Cache from 'cache-point' 6 | import Explain from './lib/explain.js' 7 | import Render from './lib/render.js' 8 | import arrayify from 'array-back' 9 | import TempFile from './lib/temp-file.js' 10 | 11 | TempFile.createTmpDirs() 12 | 13 | /** 14 | * @external cache-point 15 | * @see https://github.com/75lb/cache-point 16 | */ 17 | /** 18 | * The [cache-point](https://github.com/75lb/cache-point) instance used when `cache: true` is specified on `.explain()`. 19 | * @type {external:cache-point} 20 | */ 21 | const cache = new Cache({ dir: TempFile.cacheDir }) 22 | 23 | /** 24 | * @alias module:jsdoc-api 25 | */ 26 | const jsdoc = { 27 | cache, 28 | 29 | /** 30 | * Returns a promise for the jsdoc explain output. 31 | * 32 | * @param [options] {module:jsdoc-api~JsdocOptions} 33 | * @fulfil {object[]} - jsdoc explain output 34 | * @returns {Promise} 35 | */ 36 | async explain (options) { 37 | options = new JsdocOptions(options) 38 | const command = new Explain(options, cache) 39 | return command.execute() 40 | }, 41 | 42 | /** 43 | * Render jsdoc documentation. 44 | * 45 | * @param [options] {module:jsdoc-api~JsdocOptions} 46 | * @prerequisite Requires node v0.12 or above 47 | * @example 48 | * await jsdoc.render({ files: 'lib/*', destination: 'api-docs' }) 49 | */ 50 | async render (options) { 51 | options = new JsdocOptions(options) 52 | const command = new Render(options) 53 | return command.execute() 54 | } 55 | } 56 | 57 | /** 58 | * The jsdoc options, common for all operations. 59 | * @typicalname options 60 | */ 61 | class JsdocOptions { 62 | constructor (options = {}) { 63 | /** 64 | * One or more filenames to process. Either `files`, `source` or `configure` must be supplied. 65 | * @type {string|string[]} 66 | */ 67 | this.files = arrayify(options.files) 68 | 69 | /** 70 | * A string or an array of strings containing source code to process. Either `files`, `source` or `configure` must be supplied. 71 | * @type {string|string[]} 72 | */ 73 | this.source = options.source 74 | 75 | /** 76 | * Set to `true` to cache the output - future invocations with the same input will return immediately. 77 | * @type {boolean} 78 | * @default 79 | */ 80 | this.cache = options.cache 81 | 82 | /** 83 | * Only display symbols with the given access: "public", "protected", "private" or "undefined", or "all" for all access levels. Default: all except "private". 84 | * @type {string} 85 | */ 86 | this.access = options.access 87 | 88 | /** 89 | * The path to the configuration file. Default: path/to/jsdoc/conf.json. Either `files`, `source` or `configure` must be supplied. 90 | * @type {string} 91 | */ 92 | this.configure = options.configure 93 | 94 | /** 95 | * The path to the output folder. Use "console" to dump data to the console. Default: ./out/. 96 | * @type {string} 97 | */ 98 | this.destination = options.destination 99 | 100 | /** 101 | * Assume this encoding when reading all source files. Default: utf8. 102 | * @type {string} 103 | */ 104 | this.encoding = options.encoding 105 | 106 | /** 107 | * Display symbols marked with the @private tag. Equivalent to "--access all". Default: false. 108 | * @type {boolean} 109 | */ 110 | this.private = options.private 111 | 112 | /** 113 | * The path to the project's package file. Default: path/to/sourcefiles/package.json 114 | * @type {string} 115 | */ 116 | this.package = options.package 117 | 118 | /** 119 | * Treat errors as fatal errors, and treat warnings as errors. Default: false. 120 | * @type {boolean} 121 | */ 122 | this.pedantic = options.pedantic 123 | 124 | /** 125 | * A query string to parse and store in jsdoc.env.opts.query. Example: foo=bar&baz=true. 126 | * @type {string} 127 | */ 128 | this.query = options.query 129 | 130 | /** 131 | * Recurse into subdirectories when scanning for source files and tutorials. 132 | * @type {boolean} 133 | */ 134 | this.recurse = options.recurse 135 | 136 | /** 137 | * The path to the project's README file. Default: path/to/sourcefiles/README.md. 138 | * @type {string} 139 | */ 140 | this.readme = options.readme 141 | 142 | /* Warning to avoid a common mistake where dmd templates are passed in.. a jsdoc template must be a filename. */ 143 | if (typeof options.template === 'string' && options.template.split(/\r?\n/).length !== 1) { 144 | console.warn('Suspicious `options.template` value - the jsdoc `template` option must be a file path.') 145 | console.warn(options.template) 146 | } 147 | 148 | /** 149 | * The path to the template to use. Default: path/to/jsdoc/templates/default. 150 | * @type {string} 151 | */ 152 | this.template = options.template 153 | 154 | /** 155 | * Directory in which JSDoc should search for tutorials. 156 | * @type {string} 157 | */ 158 | this.tutorials = options.tutorials 159 | } 160 | } 161 | 162 | export default jsdoc 163 | -------------------------------------------------------------------------------- /lib/explain.js: -------------------------------------------------------------------------------- 1 | import JsdocCommand from './jsdoc-command.js' 2 | import toSpawnArgs from 'object-to-spawn-args' 3 | import cp from 'child_process' 4 | import util from 'node:util' 5 | import { promises as fs } from 'node:fs' 6 | const exec = util.promisify(cp.exec) 7 | 8 | class Explain extends JsdocCommand { 9 | async getOutput () { 10 | if (this.options.cache && !this.options.source.length) { 11 | try { 12 | return await this.readCache() 13 | } catch (err) { 14 | if (err.code === 'ENOENT') { 15 | return this._runJsdoc() 16 | } else { 17 | throw err 18 | } 19 | } 20 | } else { 21 | return this._runJsdoc() 22 | } 23 | } 24 | 25 | async _runJsdoc () { 26 | const jsdocArgs = [ 27 | this.jsdocPath, 28 | ...toSpawnArgs(this.jsdocOptions), 29 | '-X', 30 | ...(this.options.source.length ? this.tempFileSet.files : this.inputFileSet.files) 31 | ] 32 | let jsdocOutput = { stdout: '', stderr: '' } 33 | 34 | const code = await new Promise((resolve, reject) => { 35 | const handle = cp.spawn('node', jsdocArgs) 36 | handle.stdout.setEncoding('utf8') 37 | handle.stderr.setEncoding('utf8') 38 | handle.stdout.on('data', chunk => { 39 | jsdocOutput.stdout += chunk 40 | }) 41 | handle.stderr.on('data', chunk => { 42 | jsdocOutput.stderr += chunk 43 | }) 44 | handle.on('exit', (code) => { 45 | resolve(code) 46 | }) 47 | handle.on('error', reject) 48 | }) 49 | try { 50 | if (code > 0) { 51 | throw new Error('jsdoc exited with non-zero code: ' + code) 52 | } else { 53 | const explainOutput = JSON.parse(jsdocOutput.stdout) 54 | if (this.options.cache) { 55 | await this.cache.write(this.cacheKey, explainOutput) 56 | } 57 | return explainOutput 58 | } 59 | } catch (err) { 60 | const firstLineOfStdout = jsdocOutput.stdout.split(/\r?\n/)[0] 61 | const jsdocErr = new Error(jsdocOutput.stderr.trim() || firstLineOfStdout || 'Jsdoc failed.') 62 | jsdocErr.name = 'JSDOC_ERROR' 63 | jsdocErr.cause = err 64 | throw jsdocErr 65 | } 66 | } 67 | 68 | async readCache () { 69 | if (this.cache) { 70 | /* Create the cache key then check the cache for a match, returning pre-generated output if so. 71 | The current cache key is a union of the input file names plus their content - this could be expensive when processing a lot of files. 72 | */ 73 | const promises = this.inputFileSet.files.map(file => { 74 | return fs.readFile(file, 'utf8') 75 | }) 76 | const contents = await Promise.all(promises) 77 | this.cacheKey = contents.concat(this.inputFileSet.files) 78 | return this.cache.read(this.cacheKey) 79 | } else { 80 | return Promise.reject() 81 | } 82 | } 83 | } 84 | 85 | export default Explain 86 | -------------------------------------------------------------------------------- /lib/jsdoc-command.js: -------------------------------------------------------------------------------- 1 | import arrayify from 'array-back' 2 | import path from 'path' 3 | import TempFile from './temp-file.js' 4 | import FileSet from 'file-set' 5 | import assert from 'assert' 6 | import walkBack from 'walk-back' 7 | import currentModulePaths from 'current-module-paths' 8 | 9 | const { __dirname } = currentModulePaths(import.meta.url) 10 | 11 | class JsdocCommand { 12 | constructor (options = {}, cache) { 13 | options.files = arrayify(options.files) 14 | options.source = arrayify(options.source) 15 | assert.ok( 16 | options.files.length || options.source.length || options.configure, 17 | 'Must set at least one of .files, .source or .configure' 18 | ) 19 | 20 | this.cache = cache 21 | this.tempFiles = [] 22 | 23 | const jsdocOptions = Object.assign({}, options) 24 | delete jsdocOptions.files 25 | delete jsdocOptions.source 26 | delete jsdocOptions.cache 27 | 28 | /* see: https://github.com/jsdoc2md/jsdoc-api/issues/22 */ 29 | if (!jsdocOptions.pedantic) { 30 | delete jsdocOptions.pedantic 31 | } 32 | 33 | this.options = options 34 | this.jsdocOptions = jsdocOptions 35 | 36 | this.jsdocPath = process.env.JSDOC_PATH || walkBack( 37 | path.join(__dirname, '..'), 38 | path.join('node_modules', 'jsdoc', 'jsdoc.js') 39 | ) 40 | } 41 | 42 | async execute () { 43 | this.inputFileSet = new FileSet() 44 | await this.inputFileSet.add(this.options.files) 45 | /* node-glob v9+ (used by file-set) no longer sorts the output by default. We will continue to sort the file list, for now, as it's what the user expected by default. The user's system locale is used. */ 46 | const collator = new Intl.Collator() 47 | this.inputFileSet.files.sort(collator.compare) 48 | 49 | if (this.options.source.length) { 50 | this.tempFiles = this.options.source.map(source => new TempFile(source)) 51 | this.tempFileSet = new FileSet() 52 | await this.tempFileSet.add(this.tempFiles.map(t => t.path)) 53 | } 54 | 55 | let result 56 | try { 57 | result = await this.getOutput() 58 | } finally { 59 | /* run even if getOutput fails */ 60 | if (this.tempFiles) { 61 | for (const tempFile of this.tempFiles) { 62 | tempFile.delete() 63 | } 64 | } 65 | } 66 | return result 67 | } 68 | } 69 | 70 | export default JsdocCommand 71 | -------------------------------------------------------------------------------- /lib/render.js: -------------------------------------------------------------------------------- 1 | import JsdocCommand from './jsdoc-command.js' 2 | import toSpawnArgs from 'object-to-spawn-args' 3 | import { spawn } from 'child_process' 4 | 5 | class Render extends JsdocCommand { 6 | async getOutput () { 7 | return new Promise((resolve, reject) => { 8 | const jsdocArgs = toSpawnArgs(this.jsdocOptions) 9 | .concat(this.options.source.length ? this.tempFiles.map(t => t.path) : this.options.files) 10 | 11 | jsdocArgs.unshift(this.jsdocPath) 12 | const handle = spawn('node', jsdocArgs, { stdio: 'inherit' }) 13 | handle.on('close', resolve) 14 | }) 15 | } 16 | } 17 | 18 | export default Render 19 | -------------------------------------------------------------------------------- /lib/temp-file.js: -------------------------------------------------------------------------------- 1 | import fs from 'node:fs' 2 | import os from 'os' 3 | import crypto from 'crypto' 4 | import path from 'path' 5 | 6 | class TempFile { 7 | constructor (source) { 8 | this.path = path.join(TempFile.tempFileDir, crypto.randomBytes(6).toString('hex') + '.js') 9 | fs.writeFileSync(this.path, source) 10 | } 11 | 12 | delete () { 13 | try { 14 | fs.unlinkSync(this.path) 15 | } catch (err) { 16 | // already deleted 17 | } 18 | } 19 | 20 | static tempFileDir = path.join(os.homedir(), '.jsdoc-api/temp') 21 | static cacheDir = path.join(os.homedir(), '.jsdoc-api/cache') 22 | 23 | static createTmpDirs () { 24 | /* No longer using os.tmpdir(). See: https://github.com/jsdoc2md/jsdoc-api/issues/19 */ 25 | fs.mkdirSync(TempFile.tempFileDir, { recursive: true }) 26 | fs.mkdirSync(TempFile.cacheDir, { recursive: true }) 27 | } 28 | } 29 | 30 | export default TempFile 31 | -------------------------------------------------------------------------------- /package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "jsdoc-api", 3 | "version": "9.3.4", 4 | "lockfileVersion": 3, 5 | "requires": true, 6 | "packages": { 7 | "": { 8 | "name": "jsdoc-api", 9 | "version": "9.3.4", 10 | "license": "MIT", 11 | "dependencies": { 12 | "array-back": "^6.2.2", 13 | "cache-point": "^3.0.0", 14 | "current-module-paths": "^1.1.2", 15 | "file-set": "^5.2.2", 16 | "jsdoc": "^4.0.4", 17 | "object-to-spawn-args": "^2.0.1", 18 | "walk-back": "^5.1.1" 19 | }, 20 | "engines": { 21 | "node": ">=12.17" 22 | }, 23 | "peerDependencies": { 24 | "@75lb/nature": "latest" 25 | }, 26 | "peerDependenciesMeta": { 27 | "@75lb/nature": { 28 | "optional": true 29 | } 30 | } 31 | }, 32 | "node_modules/@babel/helper-string-parser": { 33 | "version": "7.25.9", 34 | "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.25.9.tgz", 35 | "integrity": "sha512-4A/SCr/2KLd5jrtOMFzaKjVtAei3+2r/NChoBNoZ3EyP/+GlhoaEGoWOZUmFmoITP7zOJyHIMm+DYRd8o3PvHA==", 36 | "license": "MIT", 37 | "engines": { 38 | "node": ">=6.9.0" 39 | } 40 | }, 41 | "node_modules/@babel/helper-validator-identifier": { 42 | "version": "7.25.9", 43 | "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.25.9.tgz", 44 | "integrity": "sha512-Ed61U6XJc3CVRfkERJWDz4dJwKe7iLmmJsbOGu9wSloNSFttHV0I8g6UAgb7qnK5ly5bGLPd4oXZlxCdANBOWQ==", 45 | "license": "MIT", 46 | "engines": { 47 | "node": ">=6.9.0" 48 | } 49 | }, 50 | "node_modules/@babel/parser": { 51 | "version": "7.26.2", 52 | "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.26.2.tgz", 53 | "integrity": "sha512-DWMCZH9WA4Maitz2q21SRKHo9QXZxkDsbNZoVD62gusNtNBBqDg9i7uOhASfTfIGNzW+O+r7+jAlM8dwphcJKQ==", 54 | "license": "MIT", 55 | "dependencies": { 56 | "@babel/types": "^7.26.0" 57 | }, 58 | "bin": { 59 | "parser": "bin/babel-parser.js" 60 | }, 61 | "engines": { 62 | "node": ">=6.0.0" 63 | } 64 | }, 65 | "node_modules/@babel/types": { 66 | "version": "7.26.0", 67 | "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.26.0.tgz", 68 | "integrity": "sha512-Z/yiTPj+lDVnF7lWeKCIJzaIkI0vYO87dMpZ4bg4TDrFe4XXLFWL1TbXU27gBP3QccxV9mZICCrnjnYlJjXHOA==", 69 | "license": "MIT", 70 | "dependencies": { 71 | "@babel/helper-string-parser": "^7.25.9", 72 | "@babel/helper-validator-identifier": "^7.25.9" 73 | }, 74 | "engines": { 75 | "node": ">=6.9.0" 76 | } 77 | }, 78 | "node_modules/@jsdoc/salty": { 79 | "version": "0.2.8", 80 | "resolved": "https://registry.npmjs.org/@jsdoc/salty/-/salty-0.2.8.tgz", 81 | "integrity": "sha512-5e+SFVavj1ORKlKaKr2BmTOekmXbelU7dC0cDkQLqag7xfuTPuGMUFx7KWJuv4bYZrTsoL2Z18VVCOKYxzoHcg==", 82 | "license": "Apache-2.0", 83 | "dependencies": { 84 | "lodash": "^4.17.21" 85 | }, 86 | "engines": { 87 | "node": ">=v12.0.0" 88 | } 89 | }, 90 | "node_modules/@nodelib/fs.scandir": { 91 | "version": "2.1.5", 92 | "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", 93 | "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", 94 | "license": "MIT", 95 | "dependencies": { 96 | "@nodelib/fs.stat": "2.0.5", 97 | "run-parallel": "^1.1.9" 98 | }, 99 | "engines": { 100 | "node": ">= 8" 101 | } 102 | }, 103 | "node_modules/@nodelib/fs.stat": { 104 | "version": "2.0.5", 105 | "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", 106 | "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", 107 | "license": "MIT", 108 | "engines": { 109 | "node": ">= 8" 110 | } 111 | }, 112 | "node_modules/@nodelib/fs.walk": { 113 | "version": "1.2.8", 114 | "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", 115 | "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", 116 | "license": "MIT", 117 | "dependencies": { 118 | "@nodelib/fs.scandir": "2.1.5", 119 | "fastq": "^1.6.0" 120 | }, 121 | "engines": { 122 | "node": ">= 8" 123 | } 124 | }, 125 | "node_modules/@types/linkify-it": { 126 | "version": "5.0.0", 127 | "resolved": "https://registry.npmjs.org/@types/linkify-it/-/linkify-it-5.0.0.tgz", 128 | "integrity": "sha512-sVDA58zAw4eWAffKOaQH5/5j3XeayukzDk+ewSsnv3p4yJEZHCCzMDiZM8e0OUrRvmpGZ85jf4yDHkHsgBNr9Q==", 129 | "license": "MIT" 130 | }, 131 | "node_modules/@types/markdown-it": { 132 | "version": "14.1.2", 133 | "resolved": "https://registry.npmjs.org/@types/markdown-it/-/markdown-it-14.1.2.tgz", 134 | "integrity": "sha512-promo4eFwuiW+TfGxhi+0x3czqTYJkG8qB17ZUJiVF10Xm7NLVRSLUsfRTU/6h1e24VvRnXCx+hG7li58lkzog==", 135 | "license": "MIT", 136 | "dependencies": { 137 | "@types/linkify-it": "^5", 138 | "@types/mdurl": "^2" 139 | } 140 | }, 141 | "node_modules/@types/mdurl": { 142 | "version": "2.0.0", 143 | "resolved": "https://registry.npmjs.org/@types/mdurl/-/mdurl-2.0.0.tgz", 144 | "integrity": "sha512-RGdgjQUZba5p6QEFAVx2OGb8rQDL/cPRG7GiedRzMcJ1tYnUANBncjbSB1NRGwbvjcPeikRABz2nshyPk1bhWg==", 145 | "license": "MIT" 146 | }, 147 | "node_modules/argparse": { 148 | "version": "2.0.1", 149 | "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", 150 | "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", 151 | "license": "Python-2.0" 152 | }, 153 | "node_modules/array-back": { 154 | "version": "6.2.2", 155 | "resolved": "https://registry.npmjs.org/array-back/-/array-back-6.2.2.tgz", 156 | "integrity": "sha512-gUAZ7HPyb4SJczXAMUXMGAvI976JoK3qEx9v1FTmeYuJj0IBiaKttG1ydtGKdkfqWkIkouke7nG8ufGy77+Cvw==", 157 | "license": "MIT", 158 | "engines": { 159 | "node": ">=12.17" 160 | } 161 | }, 162 | "node_modules/bluebird": { 163 | "version": "3.7.2", 164 | "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz", 165 | "integrity": "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==", 166 | "license": "MIT" 167 | }, 168 | "node_modules/braces": { 169 | "version": "3.0.3", 170 | "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", 171 | "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", 172 | "license": "MIT", 173 | "dependencies": { 174 | "fill-range": "^7.1.1" 175 | }, 176 | "engines": { 177 | "node": ">=8" 178 | } 179 | }, 180 | "node_modules/cache-point": { 181 | "version": "3.0.0", 182 | "resolved": "https://registry.npmjs.org/cache-point/-/cache-point-3.0.0.tgz", 183 | "integrity": "sha512-LDGNWYv/tqRWAAZxMy75PIYynaIuhcyoyjJtwA7X5uMZjdzvGm+XmTey/GXUy2EJ+lwc2eBFzFYxjvNYyE/0Iw==", 184 | "license": "MIT", 185 | "dependencies": { 186 | "array-back": "^6.2.2" 187 | }, 188 | "engines": { 189 | "node": ">=12.17" 190 | }, 191 | "peerDependencies": { 192 | "@75lb/nature": "^0.1.1" 193 | }, 194 | "peerDependenciesMeta": { 195 | "@75lb/nature": { 196 | "optional": true 197 | } 198 | } 199 | }, 200 | "node_modules/catharsis": { 201 | "version": "0.9.0", 202 | "resolved": "https://registry.npmjs.org/catharsis/-/catharsis-0.9.0.tgz", 203 | "integrity": "sha512-prMTQVpcns/tzFgFVkVp6ak6RykZyWb3gu8ckUpd6YkTlacOd3DXGJjIpD4Q6zJirizvaiAjSSHlOsA+6sNh2A==", 204 | "license": "MIT", 205 | "dependencies": { 206 | "lodash": "^4.17.15" 207 | }, 208 | "engines": { 209 | "node": ">= 10" 210 | } 211 | }, 212 | "node_modules/current-module-paths": { 213 | "version": "1.1.2", 214 | "resolved": "https://registry.npmjs.org/current-module-paths/-/current-module-paths-1.1.2.tgz", 215 | "integrity": "sha512-H4s4arcLx/ugbu1XkkgSvcUZax0L6tXUqnppGniQb8l5VjUKGHoayXE5RiriiPhYDd+kjZnaok1Uig13PKtKYQ==", 216 | "license": "MIT", 217 | "engines": { 218 | "node": ">=12.17" 219 | } 220 | }, 221 | "node_modules/entities": { 222 | "version": "4.5.0", 223 | "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", 224 | "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", 225 | "license": "BSD-2-Clause", 226 | "engines": { 227 | "node": ">=0.12" 228 | }, 229 | "funding": { 230 | "url": "https://github.com/fb55/entities?sponsor=1" 231 | } 232 | }, 233 | "node_modules/escape-string-regexp": { 234 | "version": "2.0.0", 235 | "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz", 236 | "integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==", 237 | "license": "MIT", 238 | "engines": { 239 | "node": ">=8" 240 | } 241 | }, 242 | "node_modules/fast-glob": { 243 | "version": "3.3.2", 244 | "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.2.tgz", 245 | "integrity": "sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==", 246 | "license": "MIT", 247 | "dependencies": { 248 | "@nodelib/fs.stat": "^2.0.2", 249 | "@nodelib/fs.walk": "^1.2.3", 250 | "glob-parent": "^5.1.2", 251 | "merge2": "^1.3.0", 252 | "micromatch": "^4.0.4" 253 | }, 254 | "engines": { 255 | "node": ">=8.6.0" 256 | } 257 | }, 258 | "node_modules/fastq": { 259 | "version": "1.17.1", 260 | "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.17.1.tgz", 261 | "integrity": "sha512-sRVD3lWVIXWg6By68ZN7vho9a1pQcN/WBFaAAsDDFzlJjvoGx0P8z7V1t72grFJfJhu3YPZBuu25f7Kaw2jN1w==", 262 | "license": "ISC", 263 | "dependencies": { 264 | "reusify": "^1.0.4" 265 | } 266 | }, 267 | "node_modules/file-set": { 268 | "version": "5.2.2", 269 | "resolved": "https://registry.npmjs.org/file-set/-/file-set-5.2.2.tgz", 270 | "integrity": "sha512-/KgJI1V/QaDK4enOk/E2xMFk1cTWJghEr7UmWiRZfZ6upt6gQCfMn4jJ7aOm64OKurj4TaVnSSgSDqv5ZKYA3A==", 271 | "license": "MIT", 272 | "dependencies": { 273 | "array-back": "^6.2.2", 274 | "fast-glob": "^3.3.2" 275 | }, 276 | "engines": { 277 | "node": ">=12.17" 278 | }, 279 | "peerDependencies": { 280 | "@75lb/nature": "latest" 281 | }, 282 | "peerDependenciesMeta": { 283 | "@75lb/nature": { 284 | "optional": true 285 | } 286 | } 287 | }, 288 | "node_modules/fill-range": { 289 | "version": "7.1.1", 290 | "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", 291 | "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", 292 | "license": "MIT", 293 | "dependencies": { 294 | "to-regex-range": "^5.0.1" 295 | }, 296 | "engines": { 297 | "node": ">=8" 298 | } 299 | }, 300 | "node_modules/glob-parent": { 301 | "version": "5.1.2", 302 | "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", 303 | "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", 304 | "license": "ISC", 305 | "dependencies": { 306 | "is-glob": "^4.0.1" 307 | }, 308 | "engines": { 309 | "node": ">= 6" 310 | } 311 | }, 312 | "node_modules/graceful-fs": { 313 | "version": "4.2.11", 314 | "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", 315 | "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", 316 | "license": "ISC" 317 | }, 318 | "node_modules/is-extglob": { 319 | "version": "2.1.1", 320 | "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", 321 | "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", 322 | "license": "MIT", 323 | "engines": { 324 | "node": ">=0.10.0" 325 | } 326 | }, 327 | "node_modules/is-glob": { 328 | "version": "4.0.3", 329 | "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", 330 | "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", 331 | "license": "MIT", 332 | "dependencies": { 333 | "is-extglob": "^2.1.1" 334 | }, 335 | "engines": { 336 | "node": ">=0.10.0" 337 | } 338 | }, 339 | "node_modules/is-number": { 340 | "version": "7.0.0", 341 | "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", 342 | "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", 343 | "license": "MIT", 344 | "engines": { 345 | "node": ">=0.12.0" 346 | } 347 | }, 348 | "node_modules/js2xmlparser": { 349 | "version": "4.0.2", 350 | "resolved": "https://registry.npmjs.org/js2xmlparser/-/js2xmlparser-4.0.2.tgz", 351 | "integrity": "sha512-6n4D8gLlLf1n5mNLQPRfViYzu9RATblzPEtm1SthMX1Pjao0r9YI9nw7ZIfRxQMERS87mcswrg+r/OYrPRX6jA==", 352 | "license": "Apache-2.0", 353 | "dependencies": { 354 | "xmlcreate": "^2.0.4" 355 | } 356 | }, 357 | "node_modules/jsdoc": { 358 | "version": "4.0.4", 359 | "resolved": "https://registry.npmjs.org/jsdoc/-/jsdoc-4.0.4.tgz", 360 | "integrity": "sha512-zeFezwyXeG4syyYHbvh1A967IAqq/67yXtXvuL5wnqCkFZe8I0vKfm+EO+YEvLguo6w9CDUbrAXVtJSHh2E8rw==", 361 | "license": "Apache-2.0", 362 | "dependencies": { 363 | "@babel/parser": "^7.20.15", 364 | "@jsdoc/salty": "^0.2.1", 365 | "@types/markdown-it": "^14.1.1", 366 | "bluebird": "^3.7.2", 367 | "catharsis": "^0.9.0", 368 | "escape-string-regexp": "^2.0.0", 369 | "js2xmlparser": "^4.0.2", 370 | "klaw": "^3.0.0", 371 | "markdown-it": "^14.1.0", 372 | "markdown-it-anchor": "^8.6.7", 373 | "marked": "^4.0.10", 374 | "mkdirp": "^1.0.4", 375 | "requizzle": "^0.2.3", 376 | "strip-json-comments": "^3.1.0", 377 | "underscore": "~1.13.2" 378 | }, 379 | "bin": { 380 | "jsdoc": "jsdoc.js" 381 | }, 382 | "engines": { 383 | "node": ">=12.0.0" 384 | } 385 | }, 386 | "node_modules/klaw": { 387 | "version": "3.0.0", 388 | "resolved": "https://registry.npmjs.org/klaw/-/klaw-3.0.0.tgz", 389 | "integrity": "sha512-0Fo5oir+O9jnXu5EefYbVK+mHMBeEVEy2cmctR1O1NECcCkPRreJKrS6Qt/j3KC2C148Dfo9i3pCmCMsdqGr0g==", 390 | "license": "MIT", 391 | "dependencies": { 392 | "graceful-fs": "^4.1.9" 393 | } 394 | }, 395 | "node_modules/linkify-it": { 396 | "version": "5.0.0", 397 | "resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-5.0.0.tgz", 398 | "integrity": "sha512-5aHCbzQRADcdP+ATqnDuhhJ/MRIqDkZX5pyjFHRRysS8vZ5AbqGEoFIb6pYHPZ+L/OC2Lc+xT8uHVVR5CAK/wQ==", 399 | "license": "MIT", 400 | "dependencies": { 401 | "uc.micro": "^2.0.0" 402 | } 403 | }, 404 | "node_modules/lodash": { 405 | "version": "4.17.21", 406 | "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", 407 | "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", 408 | "license": "MIT" 409 | }, 410 | "node_modules/markdown-it": { 411 | "version": "14.1.0", 412 | "resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-14.1.0.tgz", 413 | "integrity": "sha512-a54IwgWPaeBCAAsv13YgmALOF1elABB08FxO9i+r4VFk5Vl4pKokRPeX8u5TCgSsPi6ec1otfLjdOpVcgbpshg==", 414 | "license": "MIT", 415 | "dependencies": { 416 | "argparse": "^2.0.1", 417 | "entities": "^4.4.0", 418 | "linkify-it": "^5.0.0", 419 | "mdurl": "^2.0.0", 420 | "punycode.js": "^2.3.1", 421 | "uc.micro": "^2.1.0" 422 | }, 423 | "bin": { 424 | "markdown-it": "bin/markdown-it.mjs" 425 | } 426 | }, 427 | "node_modules/markdown-it-anchor": { 428 | "version": "8.6.7", 429 | "resolved": "https://registry.npmjs.org/markdown-it-anchor/-/markdown-it-anchor-8.6.7.tgz", 430 | "integrity": "sha512-FlCHFwNnutLgVTflOYHPW2pPcl2AACqVzExlkGQNsi4CJgqOHN7YTgDd4LuhgN1BFO3TS0vLAruV1Td6dwWPJA==", 431 | "license": "Unlicense", 432 | "peerDependencies": { 433 | "@types/markdown-it": "*", 434 | "markdown-it": "*" 435 | } 436 | }, 437 | "node_modules/marked": { 438 | "version": "4.3.0", 439 | "resolved": "https://registry.npmjs.org/marked/-/marked-4.3.0.tgz", 440 | "integrity": "sha512-PRsaiG84bK+AMvxziE/lCFss8juXjNaWzVbN5tXAm4XjeaS9NAHhop+PjQxz2A9h8Q4M/xGmzP8vqNwy6JeK0A==", 441 | "license": "MIT", 442 | "bin": { 443 | "marked": "bin/marked.js" 444 | }, 445 | "engines": { 446 | "node": ">= 12" 447 | } 448 | }, 449 | "node_modules/mdurl": { 450 | "version": "2.0.0", 451 | "resolved": "https://registry.npmjs.org/mdurl/-/mdurl-2.0.0.tgz", 452 | "integrity": "sha512-Lf+9+2r+Tdp5wXDXC4PcIBjTDtq4UKjCPMQhKIuzpJNW0b96kVqSwW0bT7FhRSfmAiFYgP+SCRvdrDozfh0U5w==", 453 | "license": "MIT" 454 | }, 455 | "node_modules/merge2": { 456 | "version": "1.4.1", 457 | "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", 458 | "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", 459 | "license": "MIT", 460 | "engines": { 461 | "node": ">= 8" 462 | } 463 | }, 464 | "node_modules/micromatch": { 465 | "version": "4.0.8", 466 | "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", 467 | "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", 468 | "license": "MIT", 469 | "dependencies": { 470 | "braces": "^3.0.3", 471 | "picomatch": "^2.3.1" 472 | }, 473 | "engines": { 474 | "node": ">=8.6" 475 | } 476 | }, 477 | "node_modules/mkdirp": { 478 | "version": "1.0.4", 479 | "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", 480 | "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", 481 | "license": "MIT", 482 | "bin": { 483 | "mkdirp": "bin/cmd.js" 484 | }, 485 | "engines": { 486 | "node": ">=10" 487 | } 488 | }, 489 | "node_modules/object-to-spawn-args": { 490 | "version": "2.0.1", 491 | "resolved": "https://registry.npmjs.org/object-to-spawn-args/-/object-to-spawn-args-2.0.1.tgz", 492 | "integrity": "sha512-6FuKFQ39cOID+BMZ3QaphcC8Y4cw6LXBLyIgPU+OhIYwviJamPAn+4mITapnSBQrejB+NNp+FMskhD8Cq+Ys3w==", 493 | "license": "MIT", 494 | "engines": { 495 | "node": ">=8.0.0" 496 | } 497 | }, 498 | "node_modules/picomatch": { 499 | "version": "2.3.1", 500 | "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", 501 | "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", 502 | "license": "MIT", 503 | "engines": { 504 | "node": ">=8.6" 505 | }, 506 | "funding": { 507 | "url": "https://github.com/sponsors/jonschlinkert" 508 | } 509 | }, 510 | "node_modules/punycode.js": { 511 | "version": "2.3.1", 512 | "resolved": "https://registry.npmjs.org/punycode.js/-/punycode.js-2.3.1.tgz", 513 | "integrity": "sha512-uxFIHU0YlHYhDQtV4R9J6a52SLx28BCjT+4ieh7IGbgwVJWO+km431c4yRlREUAsAmt/uMjQUyQHNEPf0M39CA==", 514 | "license": "MIT", 515 | "engines": { 516 | "node": ">=6" 517 | } 518 | }, 519 | "node_modules/queue-microtask": { 520 | "version": "1.2.3", 521 | "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", 522 | "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", 523 | "funding": [ 524 | { 525 | "type": "github", 526 | "url": "https://github.com/sponsors/feross" 527 | }, 528 | { 529 | "type": "patreon", 530 | "url": "https://www.patreon.com/feross" 531 | }, 532 | { 533 | "type": "consulting", 534 | "url": "https://feross.org/support" 535 | } 536 | ], 537 | "license": "MIT" 538 | }, 539 | "node_modules/requizzle": { 540 | "version": "0.2.4", 541 | "resolved": "https://registry.npmjs.org/requizzle/-/requizzle-0.2.4.tgz", 542 | "integrity": "sha512-JRrFk1D4OQ4SqovXOgdav+K8EAhSB/LJZqCz8tbX0KObcdeM15Ss59ozWMBWmmINMagCwmqn4ZNryUGpBsl6Jw==", 543 | "license": "MIT", 544 | "dependencies": { 545 | "lodash": "^4.17.21" 546 | } 547 | }, 548 | "node_modules/reusify": { 549 | "version": "1.0.4", 550 | "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", 551 | "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", 552 | "license": "MIT", 553 | "engines": { 554 | "iojs": ">=1.0.0", 555 | "node": ">=0.10.0" 556 | } 557 | }, 558 | "node_modules/run-parallel": { 559 | "version": "1.2.0", 560 | "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", 561 | "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", 562 | "funding": [ 563 | { 564 | "type": "github", 565 | "url": "https://github.com/sponsors/feross" 566 | }, 567 | { 568 | "type": "patreon", 569 | "url": "https://www.patreon.com/feross" 570 | }, 571 | { 572 | "type": "consulting", 573 | "url": "https://feross.org/support" 574 | } 575 | ], 576 | "license": "MIT", 577 | "dependencies": { 578 | "queue-microtask": "^1.2.2" 579 | } 580 | }, 581 | "node_modules/strip-json-comments": { 582 | "version": "3.1.1", 583 | "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", 584 | "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", 585 | "license": "MIT", 586 | "engines": { 587 | "node": ">=8" 588 | }, 589 | "funding": { 590 | "url": "https://github.com/sponsors/sindresorhus" 591 | } 592 | }, 593 | "node_modules/to-regex-range": { 594 | "version": "5.0.1", 595 | "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", 596 | "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", 597 | "license": "MIT", 598 | "dependencies": { 599 | "is-number": "^7.0.0" 600 | }, 601 | "engines": { 602 | "node": ">=8.0" 603 | } 604 | }, 605 | "node_modules/uc.micro": { 606 | "version": "2.1.0", 607 | "resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-2.1.0.tgz", 608 | "integrity": "sha512-ARDJmphmdvUk6Glw7y9DQ2bFkKBHwQHLi2lsaH6PPmz/Ka9sFOBsBluozhDltWmnv9u/cF6Rt87znRTPV+yp/A==", 609 | "license": "MIT" 610 | }, 611 | "node_modules/underscore": { 612 | "version": "1.13.7", 613 | "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.13.7.tgz", 614 | "integrity": "sha512-GMXzWtsc57XAtguZgaQViUOzs0KTkk8ojr3/xAxXLITqf/3EMwxC0inyETfDFjH/Krbhuep0HNbbjI9i/q3F3g==", 615 | "license": "MIT" 616 | }, 617 | "node_modules/walk-back": { 618 | "version": "5.1.1", 619 | "resolved": "https://registry.npmjs.org/walk-back/-/walk-back-5.1.1.tgz", 620 | "integrity": "sha512-e/FRLDVdZQWFrAzU6Hdvpm7D7m2ina833gIKLptQykRK49mmCYHLHq7UqjPDbxbKLZkTkW1rFqbengdE3sLfdw==", 621 | "license": "MIT", 622 | "engines": { 623 | "node": ">=12.17" 624 | } 625 | }, 626 | "node_modules/xmlcreate": { 627 | "version": "2.0.4", 628 | "resolved": "https://registry.npmjs.org/xmlcreate/-/xmlcreate-2.0.4.tgz", 629 | "integrity": "sha512-nquOebG4sngPmGPICTS5EnxqhKbCmz5Ox5hsszI2T6U5qdrJizBc+0ilYSEjTSzU0yZcmvppztXe/5Al5fUwdg==", 630 | "license": "Apache-2.0" 631 | } 632 | } 633 | } 634 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "jsdoc-api", 3 | "author": "Lloyd Brookes <75pound@gmail.com>", 4 | "version": "9.3.4", 5 | "description": "A programmatic interface for jsdoc", 6 | "repository": { 7 | "type": "git", 8 | "url": "git+https://github.com/jsdoc2md/jsdoc-api.git" 9 | }, 10 | "type": "module", 11 | "exports": { 12 | "import": "./index.js", 13 | "require": "./dist/index.cjs" 14 | }, 15 | "license": "MIT", 16 | "keywords": [ 17 | "jsdoc", 18 | "api", 19 | "programmatic", 20 | "interface", 21 | "javascript", 22 | "documentation" 23 | ], 24 | "engines": { 25 | "node": ">=12.17" 26 | }, 27 | "scripts": { 28 | "test": "npm run dist && npm run test:ci", 29 | "test:ci": "75lb-nature test-runner test/caching.js test/explain.js test/render.js", 30 | "dist": "75lb-nature cjs-build index.js", 31 | "docs": "75lb-nature jsdoc2md index.js lib/*.js > docs/api.md" 32 | }, 33 | "dependencies": { 34 | "array-back": "^6.2.2", 35 | "cache-point": "^3.0.0", 36 | "current-module-paths": "^1.1.2", 37 | "file-set": "^5.2.2", 38 | "jsdoc": "^4.0.4", 39 | "object-to-spawn-args": "^2.0.1", 40 | "walk-back": "^5.1.1" 41 | }, 42 | "peerDependencies": { 43 | "@75lb/nature": "latest" 44 | }, 45 | "peerDependenciesMeta": { 46 | "@75lb/nature": { 47 | "optional": true 48 | } 49 | }, 50 | "standard": { 51 | "ignore": [ 52 | "tmp", 53 | "test/fixture", 54 | "dist" 55 | ] 56 | }, 57 | "files": [ 58 | "index.js", 59 | "lib", 60 | "dist" 61 | ] 62 | } 63 | -------------------------------------------------------------------------------- /test/caching.js: -------------------------------------------------------------------------------- 1 | import jsdoc from 'jsdoc-api' 2 | import Fixture from './lib/fixture.js' 3 | import { readdirSync, readFileSync } from 'fs' 4 | import { strict as a } from 'assert' 5 | import path from 'path' 6 | 7 | /* tests need to run with a maxConcurrency of 1 as `jsdoc.cache` is shared between tests */ 8 | const [test, only, skip] = [new Map(), new Map(), new Map()] 9 | 10 | test.set('.explain({ files, cache: true })', async function () { 11 | const f = new Fixture('class-all') 12 | jsdoc.cache.dir = 'tmp/test/cache1' 13 | await jsdoc.cache.clear() 14 | let output = await jsdoc.explain({ files: f.sourcePath, cache: true }) 15 | output = Fixture.normaliseNewLines(output) 16 | const cachedFiles = readdirSync(jsdoc.cache.dir).map(file => path.resolve(jsdoc.cache.dir, file)) 17 | a.equal(cachedFiles.length, 1) 18 | a.deepEqual(output, f.getExpectedOutput(output)) 19 | let cachedData = JSON.parse(readFileSync(cachedFiles[0], 'utf8')) 20 | cachedData = Fixture.normaliseNewLines(cachedData) 21 | Fixture.removeFileSpecificData(cachedData) 22 | a.deepEqual( 23 | cachedData, 24 | f.getExpectedOutput(output) 25 | ) 26 | }) 27 | 28 | test.set('.explain({ source, cache: true }) - Ensure correct output (#147)', async function () { 29 | await jsdoc.cache.clear() 30 | jsdoc.cache.dir = 'tmp/test/cache2' 31 | let one = jsdoc.explain({ source: '/**\n * Function one\n */\nfunction one () {}\n', cache: true }) 32 | let two = jsdoc.explain({ source: '/**\n * Function two\n */\nfunction two () {}\n', cache: true }) 33 | let three = jsdoc.explain({ source: '/**\n * Function three\n */\nfunction three () {}\n', cache: true }) 34 | const output = await Promise.all([one, two, three]) 35 | a.equal(output[0][0].description, 'Function one') 36 | a.equal(output[1][0].description, 'Function two') 37 | a.equal(output[2][0].description, 'Function three') 38 | 39 | /* ensure it works correctly the second time */ 40 | one = jsdoc.explain({ source: '/**\n * Function one\n */\nfunction one () {}\n', cache: true }) 41 | two = jsdoc.explain({ source: '/**\n * Function two\n */\nfunction two () {}\n', cache: true }) 42 | three = jsdoc.explain({ source: '/**\n * Function three\n */\nfunction three () {}\n', cache: true }) 43 | const output2 = await Promise.all([one, two, three]) 44 | a.equal(output2[0][0].description, 'Function one') 45 | a.equal(output2[1][0].description, 'Function two') 46 | a.equal(output2[2][0].description, 'Function three') 47 | }) 48 | 49 | export { test, only, skip } 50 | -------------------------------------------------------------------------------- /test/explain.js: -------------------------------------------------------------------------------- 1 | import jsdoc from 'jsdoc-api' 2 | import Fixture from './lib/fixture.js' 3 | import { strict as a } from 'assert' 4 | import path from 'path' 5 | 6 | const [test, only, skip] = [new Map(), new Map(), new Map()] 7 | 8 | test.set('.explain({ files })', async function () { 9 | const f = new Fixture('class-all') 10 | let output = await jsdoc.explain({ files: f.sourcePath }) 11 | output = Fixture.normaliseNewLines(output) 12 | a.deepEqual(output, f.getExpectedOutput(output)) 13 | }) 14 | 15 | test.set('.explain({ source })', async function () { 16 | const f = new Fixture('class-all') 17 | let output = await jsdoc.explain({ source: f.getSource() }) 18 | output = Fixture.normaliseNewLines(output) 19 | a.deepEqual(output, f.getExpectedOutput(output)) 20 | }) 21 | 22 | test.set(".explain: file doesn't exist", async function () { 23 | try { 24 | await jsdoc.explain({ files: 'sdfafafirifrj' }) 25 | a.fail('should not reach here') 26 | } catch (err) { 27 | a.equal(err.name, 'JSDOC_ERROR') 28 | } 29 | }) 30 | 31 | test.set('.explain: invalid doclet syntax', async function () { 32 | const input = path.resolve('test', 'fixture', 'buggy', 'bad-doclet-syntax.js') 33 | try { 34 | await jsdoc.explain({ files: input }) 35 | a.fail('should not reach here') 36 | } catch (err) { 37 | a.equal(err.name, 'JSDOC_ERROR') 38 | } 39 | }) 40 | 41 | test.set('.explain({ files }): generate a warning', async function () { 42 | return jsdoc.explain({ files: 'test/fixture/buggy/ignore-with-value.js' }) 43 | }) 44 | 45 | test.set('.explain({ files }): files is empty', async function () { 46 | a.rejects( 47 | () => jsdoc.explain({ files: [] }), 48 | /Must set at least one of .files, .source or .configure/ 49 | ) 50 | }) 51 | 52 | test.set('Spaces in jsdoc command path', async function () { 53 | process.env.JSDOC_PATH = 'test/fixture/folder with spaces/fake-jsdoc.js' 54 | const f = new Fixture('class-all') 55 | let output = await jsdoc.explain({ files: f.sourcePath }) 56 | a.equal(output.length, 4) 57 | process.env.JSDOC_PATH = '' 58 | }) 59 | 60 | export { test, only, skip } 61 | -------------------------------------------------------------------------------- /test/fixture/buggy/bad-doclet-syntax.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @param {89*&^(Gjhdsbfk)} 3 | */ 4 | function badSyntax () {} 5 | -------------------------------------------------------------------------------- /test/fixture/buggy/broken-javascript.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Documenting some broken code 3 | */ 4 | function badJavascriptSyntax () { 5 | const a = { one: 1, two: 2 } 6 | const b = { ..a } // should be three fullstops 7 | } 8 | -------------------------------------------------------------------------------- /test/fixture/buggy/ignore-with-value.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @ignore in favour of iOS' one 3 | * A handle to an image picker popover. 4 | */ 5 | var CameraPopoverHandle = 1; 6 | -------------------------------------------------------------------------------- /test/fixture/class-all/0-src.js: -------------------------------------------------------------------------------- 1 | /** 2 | the constructor description 3 | @class 4 | @classdesc a class with all of the things 5 | @param {object} - an input 6 | @param [options] {object} - optional shit 7 | @author 75lb <75pound@gmail.com> 8 | @deprecated 9 | @since v0.10.28 10 | @extends {Number} 11 | @example 12 | ```js 13 | var yeah = new Everything(true) 14 | ``` 15 | */ 16 | function All (input, second) { 17 | /** 18 | the ingredients on top 19 | @default 20 | @type {string} 21 | @since v1.0.0 22 | */ 23 | this.topping = 'mud, lettuce' 24 | 25 | /** 26 | the general size 27 | */ 28 | this.size = 'medium' 29 | } 30 | 31 | /** 32 | This function has all tags set 33 | @deprecated 34 | @param {string} - The input string 35 | @param {object} - a second input 36 | @author Lloyd <75pound@gmail.com> 37 | @since v0.10.28 38 | @returns {object | string} this return has several types 39 | @example 40 | ```js 41 | all.allTogether(true) 42 | ``` 43 | */ 44 | All.prototype.allThings = function (one, two) { 45 | /** 46 | a rarseclart inner 47 | */ 48 | var some = 'bs code' 49 | } 50 | -------------------------------------------------------------------------------- /test/fixture/class-all/1-jsdoc.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "comment": "/**\nthe constructor description\n@class\n@classdesc a class with all of the things\n@param {object} - an input\n@param [options] {object} - optional shit\n@author 75lb <75pound@gmail.com>\n@deprecated\n@since v0.10.28\n@extends {Number}\n@example\n```js\nvar yeah = new Everything(true)\n```\n*/", 4 | "meta": { 5 | "range": [ 6 | 283, 7 | 481 8 | ], 9 | "filename": "0-src.js", 10 | "lineno": 16, 11 | "columnno": 0, 12 | "path": "/Users/lloydb/Documents/jsdoc2md/jsdoc-api/test/fixture/class-all", 13 | "code": { 14 | "id": "astnode100000002", 15 | "name": "All", 16 | "type": "FunctionDeclaration", 17 | "paramnames": [ 18 | "input", 19 | "second" 20 | ] 21 | }, 22 | "vars": { 23 | "this.topping": "All#topping", 24 | "this.size": "All#size" 25 | } 26 | }, 27 | "description": "the constructor description", 28 | "kind": "class", 29 | "classdesc": "a class with all of the things", 30 | "params": [ 31 | { 32 | "type": { 33 | "names": [ 34 | "object" 35 | ] 36 | }, 37 | "description": "an input", 38 | "name": "input" 39 | }, 40 | { 41 | "type": { 42 | "names": [ 43 | "object" 44 | ] 45 | }, 46 | "optional": true, 47 | "description": "optional shit", 48 | "name": "options" 49 | } 50 | ], 51 | "author": [ 52 | "75lb <75pound@gmail.com>" 53 | ], 54 | "deprecated": true, 55 | "since": "v0.10.28", 56 | "augments": [ 57 | "Number" 58 | ], 59 | "examples": [ 60 | "```js\nvar yeah = new Everything(true)\n```" 61 | ], 62 | "name": "All", 63 | "longname": "All", 64 | "scope": "global" 65 | }, 66 | { 67 | "comment": "/**\n the ingredients on top\n @default\n @type {string}\n @since v1.0.0\n */", 68 | "meta": { 69 | "range": [ 70 | 396, 71 | 425 72 | ], 73 | "filename": "0-src.js", 74 | "lineno": 23, 75 | "columnno": 2, 76 | "path": "/Users/lloydb/Documents/jsdoc2md/jsdoc-api/test/fixture/class-all", 77 | "code": { 78 | "id": "astnode100000008", 79 | "name": "this.topping", 80 | "type": "Literal", 81 | "value": "mud, lettuce", 82 | "paramnames": [] 83 | } 84 | }, 85 | "description": "the ingredients on top", 86 | "defaultvalue": "mud, lettuce", 87 | "type": { 88 | "names": [ 89 | "string" 90 | ] 91 | }, 92 | "since": "v1.0.0", 93 | "name": "topping", 94 | "longname": "All#topping", 95 | "kind": "member", 96 | "memberof": "All", 97 | "scope": "instance" 98 | }, 99 | { 100 | "comment": "/**\n the general size\n */", 101 | "meta": { 102 | "range": [ 103 | 459, 104 | 479 105 | ], 106 | "filename": "0-src.js", 107 | "lineno": 28, 108 | "columnno": 2, 109 | "path": "/Users/lloydb/Documents/jsdoc2md/jsdoc-api/test/fixture/class-all", 110 | "code": { 111 | "id": "astnode100000014", 112 | "name": "this.size", 113 | "type": "Literal", 114 | "value": "medium", 115 | "paramnames": [] 116 | } 117 | }, 118 | "description": "the general size", 119 | "name": "size", 120 | "longname": "All#size", 121 | "kind": "member", 122 | "memberof": "All", 123 | "scope": "instance" 124 | }, 125 | { 126 | "comment": "/**\nThis function has all tags set\n@deprecated\n@param {string} - The input string\n@param {object} - a second input\n@author Lloyd <75pound@gmail.com>\n@since v0.10.28\n@returns {object | string} this return has several types\n@example\n```js\nall.allTogether(true)\n```\n*/", 127 | "meta": { 128 | "range": [ 129 | 749, 130 | 853 131 | ], 132 | "filename": "0-src.js", 133 | "lineno": 44, 134 | "columnno": 0, 135 | "path": "/Users/lloydb/Documents/jsdoc2md/jsdoc-api/test/fixture/class-all", 136 | "code": { 137 | "id": "astnode100000020", 138 | "name": "All.prototype.allThings", 139 | "type": "FunctionExpression", 140 | "paramnames": [ 141 | "one", 142 | "two" 143 | ] 144 | }, 145 | "vars": { 146 | "some": "All#allThings~some" 147 | } 148 | }, 149 | "description": "This function has all tags set", 150 | "deprecated": true, 151 | "params": [ 152 | { 153 | "type": { 154 | "names": [ 155 | "string" 156 | ] 157 | }, 158 | "description": "The input string", 159 | "name": "one" 160 | }, 161 | { 162 | "type": { 163 | "names": [ 164 | "object" 165 | ] 166 | }, 167 | "description": "a second input", 168 | "name": "two" 169 | } 170 | ], 171 | "author": [ 172 | "Lloyd <75pound@gmail.com>" 173 | ], 174 | "since": "v0.10.28", 175 | "returns": [ 176 | { 177 | "type": { 178 | "names": [ 179 | "object", 180 | "string" 181 | ] 182 | }, 183 | "description": "this return has several types" 184 | } 185 | ], 186 | "examples": [ 187 | "```js\nall.allTogether(true)\n```" 188 | ], 189 | "name": "allThings", 190 | "longname": "All#allThings", 191 | "kind": "function", 192 | "memberof": "All", 193 | "scope": "instance" 194 | }, 195 | { 196 | "comment": "/**\n a rarseclart inner\n */", 197 | "meta": { 198 | "range": [ 199 | 835, 200 | 851 201 | ], 202 | "filename": "0-src.js", 203 | "lineno": 48, 204 | "columnno": 6, 205 | "path": "/Users/lloydb/Documents/jsdoc2md/jsdoc-api/test/fixture/class-all", 206 | "code": { 207 | "id": "astnode100000031", 208 | "name": "some", 209 | "type": "Literal", 210 | "value": "bs code" 211 | } 212 | }, 213 | "description": "a rarseclart inner", 214 | "name": "some", 215 | "longname": "All#allThings~some", 216 | "kind": "member", 217 | "memberof": "All#allThings", 218 | "scope": "inner", 219 | "params": [] 220 | }, 221 | { 222 | "kind": "package", 223 | "longname": "package:undefined", 224 | "files": [ 225 | "/Users/lloydb/Documents/jsdoc2md/jsdoc-api/test/fixture/class-all/0-src.js" 226 | ] 227 | } 228 | ] 229 | -------------------------------------------------------------------------------- /test/fixture/folder with spaces/fake-jsdoc.js: -------------------------------------------------------------------------------- 1 | console.log(JSON.stringify(process.argv)) 2 | -------------------------------------------------------------------------------- /test/lib/fixture.js: -------------------------------------------------------------------------------- 1 | import path from 'path' 2 | import fs from 'fs' 3 | import arrayify from 'array-back' 4 | 5 | class Fixture { 6 | constructor (name, filePath) { 7 | this.folder = path.resolve('test', 'fixture', name) 8 | this.sourcePath = path.resolve(this.folder, filePath || '0-src.js') 9 | 10 | this.getSource = function () { 11 | return fs.readFileSync(this.sourcePath, 'utf-8') 12 | } 13 | 14 | this.getExpectedOutput = function (output) { 15 | const expectedOutput = JSON.parse(fs.readFileSync(path.resolve(this.folder, '1-jsdoc.json'), 'utf-8')) 16 | Fixture.removeFileSpecificData(expectedOutput) 17 | if (output) Fixture.removeFileSpecificData(output) 18 | return expectedOutput 19 | } 20 | 21 | this.createReadStream = function () { 22 | return fs.createReadStream(this.sourcePath) 23 | } 24 | } 25 | 26 | static createTmpFolder (folder) { 27 | try { 28 | fs.statSync(folder) 29 | /* rmdirSync is node v12 friendly */ 30 | fs.rmdirSync(folder, { recursive: true }) 31 | fs.mkdirSync(folder) 32 | } catch (err) { 33 | fs.mkdirSync(folder) 34 | } 35 | } 36 | 37 | static removeFileSpecificData () { 38 | arrayify(arguments).forEach(function (input) { 39 | if (input) { 40 | input.forEach(function (i) { 41 | delete i.meta 42 | delete i.files 43 | }) 44 | } 45 | }) 46 | } 47 | 48 | static normaliseNewLines (doclets) { 49 | const input = JSON.stringify(doclets, null, ' ') 50 | /* Normalise all newlines to posix style to avoid differences while testing on Windows */ 51 | let result = input.replace(/\\r?\\n/gm, '\\n') 52 | /* Additional check for naked \r characters created by jsdoc */ 53 | /* See: https://github.com/jsdoc2md/dmd/issues/102 */ 54 | result = result.replace(/\\r(?!\\n)/g, '\\n') 55 | return JSON.parse(result) 56 | } 57 | } 58 | 59 | export default Fixture 60 | -------------------------------------------------------------------------------- /test/render.js: -------------------------------------------------------------------------------- 1 | import jsdoc from 'jsdoc-api' 2 | import Fixture from './lib/fixture.js' 3 | import { statSync } from 'fs' 4 | import { strict as a } from 'assert' 5 | 6 | const [test, only, skip] = [new Map(), new Map(), new Map()] 7 | 8 | test.set('.render({ files })', async function () { 9 | Fixture.createTmpFolder('tmp') 10 | Fixture.createTmpFolder('tmp/render') 11 | const f = new Fixture('class-all') 12 | await jsdoc.render({ files: f.sourcePath, destination: 'tmp/render/out' }) 13 | a.doesNotThrow(function () { 14 | statSync('./tmp/render/out/index.html') 15 | }) 16 | }) 17 | 18 | test.set('.render({ source, destination })', async function () { 19 | Fixture.createTmpFolder('tmp') 20 | Fixture.createTmpFolder('tmp/render') 21 | const f = new Fixture('class-all') 22 | await jsdoc.render({ source: f.getSource(), destination: 'tmp/render/out' }) 23 | a.doesNotThrow(function () { 24 | statSync('./tmp/render/out/index.html') 25 | }) 26 | }) 27 | 28 | test.set('.render({ source[], destination })', async function () { 29 | Fixture.createTmpFolder('tmp/renderSource') 30 | const sources = [ 31 | `import Foo from "foo" 32 | /** 33 | * FooPrime is some child class 34 | * @class 35 | * @param {Object} - an input 36 | * @extends Foo 37 | */ 38 | function FooPrime() {} 39 | export default FooPrime 40 | `, 41 | `import Foo from "foo" 42 | /** 43 | * FooSecond is some other child class 44 | * @class 45 | * @param {Object} - an input 46 | * @extends Foo 47 | */ 48 | function FooSecond() {} 49 | export default FooSecond 50 | `] 51 | await jsdoc.render({ source: sources, destination: 'tmp/renderSource/out' }) 52 | a.doesNotThrow(function () { 53 | statSync('./tmp/renderSource/out/FooPrime.html') 54 | statSync('./tmp/renderSource/out/FooSecond.html') 55 | }) 56 | }) 57 | 58 | export { test, only, skip } 59 | --------------------------------------------------------------------------------