├── .c8rc.json
├── .editorconfig
├── .gitattributes
├── .github
├── ISSUE_TEMPLATE
│ ├── 01-bug-report.yml
│ └── 02-feature-request.yml
├── PULL_REQUEST_TEMPLATE.md
├── dependabot.yml
└── workflows
│ ├── ci.yml
│ ├── codeql.yml
│ ├── codespell.yml
│ ├── compare-builds.yml
│ ├── dependency-review.yml
│ ├── generate.yml
│ └── scorecard.yml
├── .gitignore
├── .husky
└── pre-commit
├── .lintstagedrc.json
├── .nvmrc
├── .prettierignore
├── .prettierrc.json
├── .vercelignore
├── CODEOWNERS
├── CODE_OF_CONDUCT.md
├── CONTRIBUTING.md
├── LICENSE
├── README.md
├── bin
├── cli.mjs
├── commands
│ ├── __tests__
│ │ └── index.test.mjs
│ ├── generate.mjs
│ ├── index.mjs
│ ├── interactive.mjs
│ └── types.d.ts
└── utils.mjs
├── codecov.yml
├── eslint.config.mjs
├── npm-shrinkwrap.json
├── package.json
├── scripts
├── compare-builds
│ └── web.mjs
├── vercel-build.sh
└── vercel-prepare.sh
├── shiki.config.mjs
└── src
├── __tests__
├── generators.test.mjs
├── metadata.test.mjs
└── streaming.test.mjs
├── constants.mjs
├── generators.mjs
├── generators
├── __tests__
│ └── index.test.mjs
├── addon-verify
│ ├── constants.mjs
│ ├── index.mjs
│ └── utils
│ │ ├── __tests__
│ │ ├── generateFileList.test.mjs
│ │ └── section.test.mjs
│ │ ├── generateFileList.mjs
│ │ └── section.mjs
├── api-links
│ ├── __tests__
│ │ ├── fixtures.test.mjs
│ │ ├── fixtures.test.mjs.snapshot
│ │ └── fixtures
│ │ │ ├── buffer.js
│ │ │ ├── class.js
│ │ │ ├── exports.js
│ │ │ ├── mod.js
│ │ │ ├── prototype.js
│ │ │ ├── reverse.js
│ │ │ └── root.js
│ ├── constants.mjs
│ ├── index.mjs
│ ├── types.d.ts
│ └── utils
│ │ ├── checkIndirectReferences.mjs
│ │ ├── extractExports.mjs
│ │ └── findDefinitions.mjs
├── ast-js
│ └── index.mjs
├── ast
│ └── index.mjs
├── index.mjs
├── json-simple
│ └── index.mjs
├── jsx-ast
│ ├── constants.mjs
│ ├── index.mjs
│ └── utils
│ │ ├── __tests__
│ │ ├── ast.test.mjs
│ │ ├── buildBarProps.test.mjs
│ │ └── buildPropertyTable.test.mjs
│ │ ├── ast.mjs
│ │ ├── buildBarProps.mjs
│ │ ├── buildContent.mjs
│ │ ├── buildPropertyTable.mjs
│ │ ├── buildSignature.mjs
│ │ ├── getSortedHeadNodes.mjs
│ │ └── transformer.mjs
├── legacy-html-all
│ └── index.mjs
├── legacy-html
│ ├── assets
│ │ ├── api.js
│ │ ├── js-flavor-cjs.svg
│ │ ├── js-flavor-esm.svg
│ │ └── style.css
│ ├── index.mjs
│ ├── template.html
│ ├── types.d.ts
│ └── utils
│ │ ├── __tests__
│ │ └── safeCopy.test.mjs
│ │ ├── buildContent.mjs
│ │ ├── buildDropdowns.mjs
│ │ ├── buildExtraContent.mjs
│ │ ├── replaceTemplateValues.mjs
│ │ ├── safeCopy.mjs
│ │ └── tableOfContents.mjs
├── legacy-json-all
│ ├── index.mjs
│ └── types.d.ts
├── legacy-json
│ ├── constants.mjs
│ ├── index.mjs
│ ├── types.d.ts
│ └── utils
│ │ ├── __tests__
│ │ ├── buildHierarchy.test.mjs
│ │ ├── buildSection.test.mjs
│ │ ├── parseList.test.mjs
│ │ └── parseSignature.test.mjs
│ │ ├── buildHierarchy.mjs
│ │ ├── buildSection.mjs
│ │ ├── parseList.mjs
│ │ └── parseSignature.mjs
├── llms-txt
│ ├── index.mjs
│ ├── template.txt
│ └── utils
│ │ ├── __tests__
│ │ └── buildApiDocLink.test.mjs
│ │ └── buildApiDocLink.mjs
├── man-page
│ ├── constants.mjs
│ ├── index.mjs
│ ├── template.1
│ └── utils
│ │ ├── __tests__
│ │ └── converter.test.mjs
│ │ └── converter.mjs
├── metadata
│ ├── constants.mjs
│ ├── index.mjs
│ └── utils
│ │ └── parse.mjs
├── orama-db
│ ├── __tests__
│ │ └── index.test.mjs
│ ├── constants.mjs
│ ├── index.mjs
│ └── types.d.ts
├── types.d.ts
└── web
│ ├── constants.mjs
│ ├── index.mjs
│ ├── template.html
│ ├── ui
│ ├── components
│ │ ├── CodeBox.jsx
│ │ ├── MetaBar
│ │ │ ├── index.jsx
│ │ │ └── index.module.css
│ │ ├── NavBar.jsx
│ │ ├── SearchBox
│ │ │ └── index.jsx
│ │ └── SideBar
│ │ │ ├── index.jsx
│ │ │ └── index.module.css
│ ├── constants.mjs
│ ├── hooks
│ │ ├── useOrama.mjs
│ │ └── useTheme.mjs
│ ├── index.css
│ ├── package.json
│ └── types.d.ts
│ └── utils
│ ├── bundle.mjs
│ ├── chunks.mjs
│ ├── css.mjs
│ ├── data.mjs
│ ├── generate.mjs
│ └── processing.mjs
├── logger
├── __tests__
│ ├── logger.test.mjs
│ └── transports
│ │ ├── console.test.mjs
│ │ └── github.test.mjs
├── constants.mjs
├── index.mjs
├── logger.mjs
├── transports
│ ├── console.mjs
│ ├── github.mjs
│ └── index.mjs
├── types.d.ts
└── utils
│ ├── colors.mjs
│ └── time.mjs
├── metadata.mjs
├── parsers
├── __tests__
│ └── markdown.test.mjs
├── javascript.mjs
├── json.mjs
└── markdown.mjs
├── streaming.mjs
├── threading
├── __tests__
│ └── parallel.test.mjs
├── chunk-worker.mjs
├── index.mjs
└── parallel.mjs
├── types.d.ts
└── utils
├── __tests__
├── generators.test.mjs
├── parser.test.mjs
└── unist.test.mjs
├── array.mjs
├── generators.mjs
├── highlighter.mjs
├── parser.mjs
├── parser
├── __tests__
│ └── index.test.mjs
├── constants.mjs
├── index.mjs
├── slugger.mjs
└── typeMap.json
├── queries
├── __tests__
│ └── index.test.mjs
├── constants.mjs
└── index.mjs
├── remark.mjs
└── unist.mjs
/.c8rc.json:
--------------------------------------------------------------------------------
1 | {
2 | "all": true,
3 | "exclude": [
4 | "eslint.config.mjs",
5 | "**/fixtures",
6 | "src/generators/legacy-html/assets",
7 | "src/generators/web/ui",
8 | "**/*.d.ts"
9 | ]
10 | }
11 |
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | root = true
2 |
3 | [*]
4 | indent_style = space
5 | indent_size = 2
6 | end_of_line = lf
7 | charset = utf-8
8 | trim_trailing_whitespace = true
9 | insert_final_newline = true
10 | max_line_length = 80
11 |
--------------------------------------------------------------------------------
/.gitattributes:
--------------------------------------------------------------------------------
1 | # Enforce Unix newlines
2 | * text=auto eol=lf
3 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/01-bug-report.yml:
--------------------------------------------------------------------------------
1 | name: Report a Technical/Visual Issue on the Node.js API Docs
2 | description: 'Is something not working as expected? Did you encounter a glitch or a bug with the docs?'
3 | labels: [bug]
4 | body:
5 | - type: markdown
6 | attributes:
7 | value: |
8 | Thanks for reporting an issue you've found with Node.js' API docs.
9 | Please fill in the template below. If unsure about something, just do as best
10 | as you're able. If you are reporting a visual glitch, it will be much easier
11 | for us to fix it when you attach a screenshot as well.
12 | - type: input
13 | attributes:
14 | label: 'URL:'
15 | description: The URL of the page you are reporting an issue on.
16 | placeholder: https://nodejs.org/api/
17 | validations:
18 | required: true
19 | - type: input
20 | attributes:
21 | label: 'Browser Name:'
22 | description: What kind of browser are you using?
23 | placeholder: Chrome
24 | validations:
25 | required: true
26 | - type: input
27 | attributes:
28 | label: 'Browser Version:'
29 | description: What version of browser are you using?
30 | placeholder: '103.0.5060.134'
31 | validations:
32 | required: true
33 | - type: input
34 | attributes:
35 | label: 'Operating System:'
36 | description: What kind of operation system are you using
37 | (Write it in full, with version number)?
38 | placeholder: 'Windows 10, 21H2, 19044.1826'
39 | validations:
40 | required: true
41 | - type: textarea
42 | attributes:
43 | label: 'How to reproduce the issue:'
44 | placeholder: |
45 | 1. What I did.
46 | 2. What I expected to happen.
47 | 3. What I actually got.
48 | 4. If possible, images or videos are welcome.
49 | validations:
50 | required: true
51 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/02-feature-request.yml:
--------------------------------------------------------------------------------
1 | name: Suggest a new feature or improvement for Node.js' docs.
2 | description: 'Do you have an idea or a suggestion and you want to share?'
3 | labels: [feature request]
4 | body:
5 | - type: markdown
6 | attributes:
7 | value: |
8 | You have an idea how to improve the docs? That's awesome!
9 | Before submitting, please have a look at the existing issues if there's already
10 | something related to your suggestion.
11 | - type: textarea
12 | attributes:
13 | label: 'Enter your suggestions in details:'
14 | placeholder: |
15 | 1. What I expected to happen.
16 | 2. Your reason (if possible, images or videos are welcome).
17 | 3. What I plan to do (Optional but better).
18 | validations:
19 | required: true
20 |
--------------------------------------------------------------------------------
/.github/PULL_REQUEST_TEMPLATE.md:
--------------------------------------------------------------------------------
1 |
4 |
5 | ## Description
6 |
7 |
8 |
9 | ## Validation
10 |
11 |
12 |
13 | ## Related Issues
14 |
15 |
19 |
20 | ### Check List
21 |
22 |
27 |
28 | - [ ] I have read the [Contributing Guidelines](https://github.com/nodejs/api-docs-tooling/blob/main/CONTRIBUTING.md) and made commit messages that follow the guideline.
29 | - [ ] I have run `node --run test` and all tests passed.
30 | - [ ] I have check code formatting with `node --run format` & `node --run lint`.
31 | - [ ] I've covered new added functionality with unit tests if necessary.
32 |
--------------------------------------------------------------------------------
/.github/dependabot.yml:
--------------------------------------------------------------------------------
1 | # We define the interval for the updates to be monthly
2 | # because we don't want to have too many updates
3 | # and the project is currently in a development stage
4 |
5 | version: 2
6 | updates:
7 | - package-ecosystem: github-actions
8 | directory: '/'
9 | schedule:
10 | interval: monthly
11 | commit-message:
12 | prefix: meta
13 | cooldown:
14 | default-days: 3
15 | open-pull-requests-limit: 10
16 |
17 | - package-ecosystem: npm
18 | directory: '/'
19 | versioning-strategy: increase
20 | schedule:
21 | interval: monthly
22 | commit-message:
23 | prefix: meta
24 | cooldown:
25 | default-days: 3
26 | groups:
27 | orama:
28 | patterns:
29 | - '@orama/*'
30 | cli:
31 | patterns:
32 | - 'commander'
33 | - '@clack/prompts'
34 | lint:
35 | patterns:
36 | - 'prettier'
37 | - 'eslint'
38 | - 'eslint-*'
39 | - 'lint-staged'
40 | - '@eslint/*'
41 | unist:
42 | patterns:
43 | - 'unified'
44 | - 'unist-*'
45 | - 'vfile'
46 | remark:
47 | patterns:
48 | - 'remark-*'
49 | - 'shiki'
50 | rehype:
51 | patterns:
52 | - 'rehype-*'
53 | ast:
54 | patterns:
55 | - 'estree-*'
56 | - 'hast-*'
57 | - 'mdast-*'
58 | - 'hastscript'
59 | - 'acorn'
60 | recma:
61 | patterns:
62 | - 'recma-*'
63 | compiling:
64 | patterns:
65 | - '@minify-html/node'
66 | - '@rollup/*'
67 | - 'rolldown'
68 | - 'lightningcss'
69 | react:
70 | patterns:
71 | - 'preact'
72 | - 'preact-*'
73 | open-pull-requests-limit: 10
74 |
--------------------------------------------------------------------------------
/.github/workflows/ci.yml:
--------------------------------------------------------------------------------
1 | name: CI
2 |
3 | on:
4 | push:
5 | branches: [main]
6 | pull_request:
7 | branches: [main]
8 |
9 | permissions:
10 | contents: read
11 |
12 | env:
13 | FORCE_COLOR: 1
14 |
15 | jobs:
16 | quality:
17 | name: Lint & Format
18 | runs-on: ubuntu-latest
19 |
20 | steps:
21 | - name: Harden Runner
22 | uses: step-security/harden-runner@95d9a5deda9de15063e7595e9719c11c38c90ae2 # v2.13.2
23 | with:
24 | egress-policy: audit
25 |
26 | - name: Checkout code
27 | uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0
28 |
29 | - name: Setup Node.js
30 | uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v6.0.0
31 | with:
32 | node-version-file: '.nvmrc'
33 | cache: 'npm'
34 |
35 | - name: Install dependencies
36 | run: npm ci
37 |
38 | - name: Run linting
39 | run: node --run lint
40 |
41 | - name: Check formatting
42 | run: node --run format:check
43 |
44 | test:
45 | name: Test & Coverage
46 | runs-on: ubuntu-latest
47 |
48 | steps:
49 | - name: Harden Runner
50 | uses: step-security/harden-runner@95d9a5deda9de15063e7595e9719c11c38c90ae2 # v2.13.2
51 | with:
52 | egress-policy: audit
53 |
54 | - name: Checkout code
55 | uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0
56 |
57 | - name: Setup Node.js
58 | uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v6.0.0
59 | with:
60 | node-version-file: '.nvmrc'
61 | cache: 'npm'
62 |
63 | - name: Install dependencies
64 | run: npm ci
65 |
66 | - name: Run tests with coverage
67 | run: node --run test:ci
68 |
69 | - name: Upload coverage to Codecov
70 | if: always()
71 | uses: codecov/codecov-action@5a1091511ad55cbe89839c7260b706298ca349f7 # v5.5.1
72 | with:
73 | files: ./coverage/lcov.info
74 |
75 | - name: Upload test results to Codecov
76 | if: always()
77 | uses: codecov/test-results-action@47f89e9acb64b76debcd5ea40642d25a4adced9f # v1.1.1
78 | with:
79 | files: ./junit.xml
80 |
--------------------------------------------------------------------------------
/.github/workflows/codeql.yml:
--------------------------------------------------------------------------------
1 | # For most projects, this workflow file will not need changing; you simply need
2 | # to commit it to your repository.
3 | #
4 | # You may wish to alter this file to override the set of languages analyzed,
5 | # or to provide custom queries or build logic.
6 | #
7 | # ******** NOTE ********
8 | # We have attempted to detect the languages in your repository. Please check
9 | # the `language` matrix defined below to confirm you have the correct set of
10 | # supported CodeQL languages.
11 | #
12 | name: 'CodeQL'
13 |
14 | on:
15 | push:
16 | branches: ['main']
17 | pull_request:
18 | # The branches below must be a subset of the branches above
19 | branches: ['main']
20 | schedule:
21 | - cron: '0 0 * * 1'
22 |
23 | permissions:
24 | contents: read
25 |
26 | jobs:
27 | analyze:
28 | name: Analyze
29 | runs-on: ubuntu-latest
30 | permissions:
31 | actions: read
32 | contents: read
33 | security-events: write
34 |
35 | strategy:
36 | fail-fast: false
37 | matrix:
38 | language: ['javascript', 'typescript']
39 | # Learn more about CodeQL language support at https://aka.ms/codeql-docs/language-support
40 |
41 | steps:
42 | - name: Harden Runner
43 | uses: step-security/harden-runner@95d9a5deda9de15063e7595e9719c11c38c90ae2 # v2.13.2
44 | with:
45 | egress-policy: audit
46 |
47 | - name: Checkout repository
48 | uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0
49 |
50 | # Initializes the CodeQL tools for scanning.
51 | - name: Initialize CodeQL
52 | uses: github/codeql-action/init@3599b3baa15b485a2e49ef411a7a4bb2452e7f93 # v3.30.5
53 | with:
54 | languages: ${{ matrix.language }}
55 | # If you wish to specify custom queries, you can do so here or in a config file.
56 | # By default, queries listed here will override any specified in a config file.
57 | # Prefix the list here with "+" to use these queries and those in the config file.
58 |
59 | # Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
60 | # If this step fails, then you should remove it and run the build manually (see below)
61 | - name: Autobuild
62 | uses: github/codeql-action/autobuild@3599b3baa15b485a2e49ef411a7a4bb2452e7f93 # v3.30.5
63 |
64 | # ℹ️ Command-line programs to run using the OS shell.
65 | # 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun
66 |
67 | # If the Autobuild fails above, remove it and uncomment the following three lines.
68 | # modify them (or add more) to build your code if your project, please refer to the EXAMPLE below for guidance.
69 |
70 | # - run: |
71 | # echo "Run, Build Application using script"
72 | # ./location_of_script_within_repo/buildscript.sh
73 |
74 | - name: Perform CodeQL Analysis
75 | uses: github/codeql-action/analyze@3599b3baa15b485a2e49ef411a7a4bb2452e7f93 # v3.30.5
76 | with:
77 | category: '/language:${{matrix.language}}'
78 |
--------------------------------------------------------------------------------
/.github/workflows/codespell.yml:
--------------------------------------------------------------------------------
1 | # https://github.com/codespell-project/actions-codespell
2 | name: codespell
3 | on: [pull_request, push]
4 | permissions:
5 | contents: read
6 |
7 | jobs:
8 | codespell:
9 | runs-on: ubuntu-latest
10 | steps:
11 | - name: Harden Runner
12 | uses: step-security/harden-runner@95d9a5deda9de15063e7595e9719c11c38c90ae2 # v2.13.2
13 | with:
14 | egress-policy: audit
15 |
16 | - uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0
17 | - uses: codespell-project/actions-codespell@8f01853be192eb0f849a5c7d721450e7a467c579 # v2.2
18 | with:
19 | ignore_words_list: crate,raison
20 | exclude_file: .gitignore
21 | skip: package-lock.json, ./src/generators/mandoc/template.1
22 |
--------------------------------------------------------------------------------
/.github/workflows/dependency-review.yml:
--------------------------------------------------------------------------------
1 | # Dependency Review Action
2 | #
3 | # This Action will scan dependency manifest files that change as part of a Pull Request,
4 | # surfacing known-vulnerable versions of the packages declared or updated in the PR.
5 | # Once installed, if the workflow run is marked as required,
6 | # PRs introducing known-vulnerable packages will be blocked from merging.
7 | #
8 | # Source repository: https://github.com/actions/dependency-review-action
9 | name: Review Dependencies
10 |
11 | on:
12 | pull_request_target:
13 | branches:
14 | - main
15 |
16 | permissions:
17 | contents: read
18 |
19 | jobs:
20 | dependency-review:
21 | runs-on: ubuntu-latest
22 | steps:
23 | - name: Harden Runner
24 | uses: step-security/harden-runner@95d9a5deda9de15063e7595e9719c11c38c90ae2 # v2.13.2
25 | with:
26 | egress-policy: audit
27 |
28 | - name: Git Checkout
29 | uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0
30 |
31 | - name: Review Dependencies
32 | uses: actions/dependency-review-action@3c4e3dcb1aa7874d2c16be7d79418e9b7efd6261 # v4.8.2
33 |
--------------------------------------------------------------------------------
/.github/workflows/generate.yml:
--------------------------------------------------------------------------------
1 | name: Generate Docs
2 |
3 | on:
4 | push:
5 | branches: [main]
6 | pull_request:
7 | branches: [main]
8 | workflow_dispatch:
9 |
10 | concurrency:
11 | group: ${{ github.workflow }}-${{ github.ref }}
12 | cancel-in-progress: true
13 |
14 | permissions:
15 | contents: read
16 |
17 | jobs:
18 | generate:
19 | runs-on: ubuntu-latest
20 | strategy:
21 | matrix:
22 | include:
23 | - target: man-page
24 | input: './node/doc/api/cli.md'
25 | - target: addon-verify
26 | input: './node/doc/api/addons.md'
27 | - target: api-links
28 | input: './node/lib/*.js'
29 | - target: orama-db
30 | input: './node/doc/api/*.md'
31 | - target: json-simple
32 | input: './node/doc/api/*.md'
33 | - target: legacy-json
34 | input: './node/doc/api/*.md'
35 | - target: legacy-html
36 | input: './node/doc/api/*.md'
37 | - target: web
38 | input: './node/doc/api/*.md'
39 | - target: llms-txt
40 | input: './node/doc/api/*.md'
41 | fail-fast: false
42 |
43 | steps:
44 | - name: Harden Runner
45 | uses: step-security/harden-runner@95d9a5deda9de15063e7595e9719c11c38c90ae2 # v2.13.2
46 | with:
47 | egress-policy: audit
48 |
49 | - name: Git Checkout
50 | uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0
51 | with:
52 | persist-credentials: false
53 |
54 | - name: Git Checkout
55 | uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0
56 | with:
57 | persist-credentials: false
58 | repository: nodejs/node
59 | sparse-checkout: |
60 | doc/api
61 | lib
62 | .
63 | path: node
64 |
65 | - name: Setup Node.js
66 | uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v6.0.0
67 | with:
68 | node-version-file: '.nvmrc'
69 | cache: 'npm'
70 |
71 | - name: Install dependencies
72 | run: npm ci
73 |
74 | - name: Create output directory
75 | run: mkdir -p out/${{ matrix.target }}
76 |
77 | - name: Generate ${{ matrix.target }}
78 | run: |
79 | node bin/cli.mjs generate \
80 | -t ${{ matrix.target }} \
81 | -i "${{ matrix.input }}" \
82 | -o "out/${{ matrix.target }}" \
83 | -c ./node/CHANGELOG.md \
84 | --index ./node/doc/api/index.md \
85 | --log-level debug
86 |
87 | - name: Upload ${{ matrix.target }} artifacts
88 | uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0
89 | with:
90 | name: ${{ matrix.target }}
91 | path: out/${{ matrix.target }}
92 |
--------------------------------------------------------------------------------
/.github/workflows/scorecard.yml:
--------------------------------------------------------------------------------
1 | # This workflow uses actions that are not certified by GitHub. They are provided
2 | # by a third party and are governed by separate terms of service, privacy
3 | # policy and support documentation.
4 |
5 | name: OpenSSF Scorecard Review
6 | on:
7 | # For Branch-Protection check. Only the default branch is supported. See
8 | # https://github.com/ossf/scorecard/blob/main/docs/checks.md#branch-protection
9 | branch_protection_rule:
10 | # To guarantee that the Maintained check is occasionally updated. See
11 | # https://github.com/ossf/scorecard/blob/main/docs/checks.md#maintained
12 | schedule:
13 | - cron: '20 7 * * 2'
14 | push:
15 | branches:
16 | - main
17 |
18 | # Declare default permissions as read only.
19 | permissions: read-all
20 |
21 | jobs:
22 | analysis:
23 | name: Scorecard analysis
24 | runs-on: ubuntu-latest
25 | permissions:
26 | # Needed to upload the results to code-scanning dashboard.
27 | security-events: write
28 | # Needed to publish results and get a badge (see publish_results below).
29 | id-token: write
30 | contents: read
31 | actions: read
32 |
33 | steps:
34 | - name: Harden Runner
35 | uses: step-security/harden-runner@95d9a5deda9de15063e7595e9719c11c38c90ae2 # v2.13.2
36 | with:
37 | egress-policy: audit
38 |
39 | - name: Git Checkout
40 | uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0
41 | with:
42 | persist-credentials: false
43 |
44 | - name: Run Scorecard Analysis
45 | uses: ossf/scorecard-action@4eaacf0543bb3f2c246792bd56e8cdeffafb205a # v2.4.3
46 | with:
47 | results_file: results.sarif
48 | results_format: sarif
49 | publish_results: true
50 |
51 | # Upload the results as artifacts (optional). Commenting out will disable uploads of run results in SARIF
52 | # format to the repository Actions tab.
53 | - name: Upload Artifacts
54 | uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0
55 | with:
56 | name: SARIF file
57 | path: results.sarif
58 | retention-days: 5
59 |
60 | # Upload the results to GitHub's code scanning dashboard.
61 | - name: Upload Scan Results
62 | uses: github/codeql-action/upload-sarif@3599b3baa15b485a2e49ef411a7a4bb2452e7f93 # v3.30.5
63 | with:
64 | sarif_file: results.sarif
65 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Vendor Folders
2 | node_modules
3 | npm-debug.log
4 |
5 | # Default Output Directory
6 | out
7 |
8 | # Tests
9 | coverage
10 | junit.xml
11 |
12 | # Debugging
13 | .clinic
14 | isolate-*
15 |
16 | # Node's Source Folder
17 | node
18 |
--------------------------------------------------------------------------------
/.husky/pre-commit:
--------------------------------------------------------------------------------
1 | npx lint-staged
2 |
--------------------------------------------------------------------------------
/.lintstagedrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "*.{js,mjs,jsx}": ["eslint --fix", "prettier --write"],
3 | "*.{json,yml}": ["prettier --write"]
4 | }
5 |
--------------------------------------------------------------------------------
/.nvmrc:
--------------------------------------------------------------------------------
1 | 24
2 |
--------------------------------------------------------------------------------
/.prettierignore:
--------------------------------------------------------------------------------
1 | npm-shrinkwrap.json
2 |
3 | # Tests files
4 | src/generators/api-links/__tests__/fixtures/
5 | *.snapshot
6 |
7 | # Templates
8 | src/generators/web/template.html
9 |
10 | # Output
11 | out/
12 |
--------------------------------------------------------------------------------
/.prettierrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "tabWidth": 2,
3 | "useTabs": false,
4 | "semi": true,
5 | "singleQuote": true,
6 | "jsxSingleQuote": false,
7 | "trailingComma": "es5",
8 | "bracketSpacing": true,
9 | "bracketSameLine": false,
10 | "arrowParens": "avoid"
11 | }
12 |
--------------------------------------------------------------------------------
/.vercelignore:
--------------------------------------------------------------------------------
1 | # Ignored the cloned `node` folder
2 | node
3 |
--------------------------------------------------------------------------------
/CODEOWNERS:
--------------------------------------------------------------------------------
1 | * @nodejs/web-infra
2 |
--------------------------------------------------------------------------------
/CODE_OF_CONDUCT.md:
--------------------------------------------------------------------------------
1 | # Code of Conduct
2 |
3 | - [Node.js Code of Conduct](https://github.com/nodejs/admin/blob/HEAD/CODE_OF_CONDUCT.md)
4 | - [Node.js Moderation Policy](https://github.com/nodejs/admin/blob/HEAD/Moderation-Policy.md)
5 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright Node.js Website WG contributors. All rights reserved.
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
7 | deal in the Software without restriction, including without limitation the
8 | rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
9 | sell 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
13 | all 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
20 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
21 | IN THE SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 | @nodejs/doc-kit is a tool to generate API documentation of Node.js. See this issue for more information.
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 | ## Usage
31 |
32 | Local invocation:
33 |
34 | ```sh
35 | $ npx doc-kit --help
36 | ```
37 |
38 | ```sh
39 | $ node bin/cli.mjs --help
40 | ```
41 |
42 | ```
43 | Usage: @nodejs/doc-kit [options] [command]
44 |
45 | CLI tool to generate the Node.js API documentation
46 |
47 | Options:
48 | -h, --help display help for command
49 |
50 | Commands:
51 | generate [options] Generate API docs
52 | interactive Launch guided CLI wizard
53 | help [command] display help for command
54 | ```
55 |
56 | ### `generate`
57 |
58 | ```
59 | Usage: @nodejs/doc-kit generate [options]
60 |
61 | Generate API docs
62 |
63 | Options:
64 | -i, --input Input file patterns (glob)
65 | --ignore [patterns...] Ignore patterns (comma-separated)
66 | -o, --output Output directory
67 | -p, --threads (default: "12")
68 | -v, --version Target Node.js version (default: "v22.14.0")
69 | -c, --changelog Changelog URL or path (default: "https://raw.githubusercontent.com/nodejs/node/HEAD/CHANGELOG.md")
70 | --git-ref Git ref/commit URL (default: "https://github.com/nodejs/node/tree/HEAD")
71 | -t, --target [modes...] Target generator modes (choices: "json-simple", "legacy-html", "legacy-html-all", "man-page", "legacy-json", "legacy-json-all", "addon-verify", "api-links", "orama-db", "llms-txt")
72 | -h, --help display help for command
73 | ```
74 |
75 | ### `interactive`
76 |
77 | ```
78 | Usage: @nodejs/doc-kit interactive [options]
79 |
80 | Launch guided CLI wizard
81 |
82 | Options:
83 | -h, --help display help for command
84 | ```
85 |
86 | ## Examples
87 |
88 | ### Legacy
89 |
90 | To generate a 1:1 match with the [legacy tooling](https://github.com/nodejs/node/tree/main/tools/doc), use the `legacy-html`, `legacy-json`, `legacy-html-all`, and `legacy-json-all` generators.
91 |
92 | ```sh
93 | npx doc-kit generate \
94 | -t legacy-html \
95 | -t legacy-json \
96 | -i "path/to/node/doc/api/*.md" \
97 | -o out \
98 | --index path/to/node/doc/api/index.md
99 | ```
100 |
101 | ### Redesigned
102 |
103 | To generate [our redesigned documentation pages](https://nodejs-api-docs-tooling.vercel.app), use the `web` and `orama-db` (for search) generators.
104 |
105 | ```sh
106 | npx doc-kit generate \
107 | -t web \
108 | -t orama-db \
109 | -i "path/to/node/doc/api/*.md" \
110 | -o out \
111 | --index path/to/node/doc/api/index.md
112 | ```
113 |
114 | > [!TIP]
115 | > In order to use the search functionality, you _must_ serve the output directory.
116 | >
117 | > ```sh
118 | > npx serve out
119 | > ```
120 |
--------------------------------------------------------------------------------
/bin/cli.mjs:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env node
2 |
3 | import process from 'node:process';
4 |
5 | import { Command, Option } from 'commander';
6 |
7 | import commands from './commands/index.mjs';
8 | import { errorWrap } from './utils.mjs';
9 | import { LogLevel } from '../src/logger/constants.mjs';
10 | import logger from '../src/logger/index.mjs';
11 |
12 | const logLevelOption = new Option('--log-level ', 'Log level')
13 | .choices(Object.keys(LogLevel))
14 | .default('info');
15 |
16 | const program = new Command()
17 | .name('@nodejs/doc-kit')
18 | .description('CLI tool to generate the Node.js API documentation')
19 | .addOption(logLevelOption)
20 | .hook('preAction', cmd => logger.setLogLevel(cmd.opts().logLevel));
21 |
22 | // Registering commands
23 | commands.forEach(({ name, description, options, action }) => {
24 | const cmd = program.command(name).description(description);
25 |
26 | // Add options to the command
27 | Object.values(options).forEach(({ flags, desc, prompt }) => {
28 | const option = new Option(flags.join(', '), desc).default(
29 | prompt.initialValue
30 | );
31 |
32 | if (prompt.required) {
33 | option.makeOptionMandatory();
34 | }
35 |
36 | if (prompt.type === 'multiselect') {
37 | option.choices(prompt.options.map(({ value }) => value));
38 | }
39 |
40 | cmd.addOption(option);
41 | });
42 |
43 | // Set the action for the command
44 | cmd.action(errorWrap(action));
45 | });
46 |
47 | // Parse and execute command-line arguments
48 | program.parse(process.argv);
49 |
--------------------------------------------------------------------------------
/bin/commands/__tests__/index.test.mjs:
--------------------------------------------------------------------------------
1 | import assert from 'node:assert/strict';
2 | import { describe, it } from 'node:test';
3 |
4 | import { Option } from 'commander';
5 |
6 | import commands from '../index.mjs';
7 |
8 | describe('Commands', () => {
9 | it('should have unique command names', () => {
10 | const names = new Set();
11 |
12 | commands.forEach(({ name }) => {
13 | assert.equal(names.has(name), false, `Duplicate command name: "${name}"`);
14 | names.add(name);
15 | });
16 | });
17 |
18 | it('should use correct option names', () => {
19 | commands.forEach(({ name: cmdName, options }) => {
20 | Object.entries(options).forEach(([optName, { flags }]) => {
21 | const expectedName = new Option(flags.at(-1)).attributeName();
22 | assert.equal(
23 | optName,
24 | expectedName,
25 | `In "${cmdName}" command: option "${flags}" should be named "${expectedName}", not "${optName}"`
26 | );
27 | });
28 | });
29 | });
30 | });
31 |
--------------------------------------------------------------------------------
/bin/commands/index.mjs:
--------------------------------------------------------------------------------
1 | import generate from './generate.mjs';
2 | import interactive from './interactive.mjs';
3 |
4 | export default [generate, interactive];
5 |
--------------------------------------------------------------------------------
/bin/commands/types.d.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Represents a command-line option for the CLI.
3 | */
4 | export interface Option {
5 | flags: string[];
6 | desc: string;
7 | prompt?: {
8 | type: 'text' | 'confirm' | 'select' | 'multiselect';
9 | message: string;
10 | variadic?: boolean;
11 | required?: boolean;
12 | initialValue?: boolean;
13 | options?: { label: string; value: string }[];
14 | };
15 | }
16 |
17 | /**
18 | * Represents a command-line subcommand
19 | */
20 | export interface Command {
21 | options: { [key: string]: Option };
22 | name: string;
23 | description: string;
24 | action: Function;
25 | }
26 |
--------------------------------------------------------------------------------
/bin/utils.mjs:
--------------------------------------------------------------------------------
1 | import logger from '../src/logger/index.mjs';
2 |
3 | /**
4 | * Wraps a function to catch both synchronous and asynchronous errors.
5 | *
6 | * @param {Function} fn - The function to wrap. Can be synchronous or return a Promise.
7 | * @returns {Function} A new function that handles errors and logs them.
8 | */
9 | export const errorWrap =
10 | fn =>
11 | async (...args) => {
12 | try {
13 | return await fn(...args);
14 | } catch (err) {
15 | logger.error(err);
16 | process.exit(1);
17 | }
18 | };
19 |
--------------------------------------------------------------------------------
/codecov.yml:
--------------------------------------------------------------------------------
1 | coverage:
2 | # https://docs.codecov.com/docs/commit-status
3 | status:
4 | patch: false
5 | project:
6 | default:
7 | # TODO(@avivkeller): Once our coverage > 80%,
8 | # increase this to 80%, and increase on increments
9 | target: 70%
10 |
--------------------------------------------------------------------------------
/eslint.config.mjs:
--------------------------------------------------------------------------------
1 | import pluginJs from '@eslint/js';
2 | import { defineConfig } from 'eslint/config';
3 | import importX from 'eslint-plugin-import-x';
4 | import jsdoc from 'eslint-plugin-jsdoc';
5 | import react from 'eslint-plugin-react-x';
6 | import globals from 'globals';
7 |
8 | export default defineConfig([
9 | pluginJs.configs.recommended,
10 | importX.flatConfigs.recommended,
11 | react.configs.recommended,
12 | {
13 | ignores: ['out/', 'src/generators/api-links/__tests__/fixtures/'],
14 | },
15 | {
16 | files: ['**/*.{mjs,jsx}'],
17 | plugins: { jsdoc },
18 | languageOptions: {
19 | ecmaVersion: 'latest',
20 | parserOptions: {
21 | ecmaFeatures: {
22 | jsx: true,
23 | },
24 | },
25 | globals: { ...globals.nodeBuiltin },
26 | },
27 | rules: {
28 | 'object-shorthand': 'error',
29 | 'import-x/namespace': 'off',
30 | 'import-x/no-named-as-default': 'off',
31 | 'import-x/no-named-as-default-member': 'off',
32 | 'import-x/no-unresolved': 'off',
33 | 'import-x/order': [
34 | 'error',
35 | {
36 | groups: [
37 | 'builtin',
38 | 'external',
39 | 'internal',
40 | ['sibling', 'parent'],
41 | 'index',
42 | 'unknown',
43 | ],
44 | 'newlines-between': 'always',
45 | alphabetize: {
46 | order: 'asc',
47 | caseInsensitive: true,
48 | },
49 | },
50 | ],
51 | // We use [] as default props.
52 | 'react-x/no-unstable-default-props': 'off',
53 | curly: ['error', 'all'],
54 | },
55 | },
56 | {
57 | files: ['src/**/*.mjs', 'bin/**/*.mjs'],
58 | plugins: {
59 | jsdoc,
60 | },
61 | languageOptions: {
62 | ecmaVersion: 'latest',
63 | globals: { ...globals.nodeBuiltin },
64 | },
65 | rules: {
66 | 'jsdoc/check-alignment': 'error',
67 | 'jsdoc/check-indentation': 'error',
68 | 'jsdoc/require-jsdoc': [
69 | 'error',
70 | {
71 | require: {
72 | FunctionDeclaration: true,
73 | MethodDefinition: true,
74 | ClassDeclaration: true,
75 | ArrowFunctionExpression: true,
76 | FunctionExpression: true,
77 | },
78 | },
79 | ],
80 | 'jsdoc/require-param': 'error',
81 | },
82 | },
83 | // Override rules for test files to disable JSDoc rules
84 | {
85 | files: ['**/__tests__/**'],
86 | rules: {
87 | 'jsdoc/check-alignment': 'off',
88 | 'jsdoc/check-indentation': 'off',
89 | 'jsdoc/require-jsdoc': 'off',
90 | 'jsdoc/require-param': 'off',
91 | },
92 | },
93 | {
94 | files: [
95 | 'src/generators/legacy-html/assets/*.js',
96 | 'src/generators/web/ui/**/*',
97 | ],
98 | languageOptions: {
99 | globals: {
100 | ...globals.browser,
101 | // SERVER and CLIENT denote server-only and client-only
102 | // codepaths in our web generator
103 | CLIENT: 'readonly',
104 | SERVER: 'readonly',
105 | },
106 | ecmaVersion: 'latest',
107 | },
108 | },
109 | ]);
110 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@nodejs/doc-kit",
3 | "repository": {
4 | "type": "git",
5 | "url": "git+https://github.com/nodejs/api-docs-tooling.git"
6 | },
7 | "scripts": {
8 | "lint": "eslint . --no-warn-ignored",
9 | "lint:fix": "eslint --fix . --no-warn-ignored",
10 | "format": "prettier .",
11 | "format:write": "prettier --write .",
12 | "format:check": "prettier --check .",
13 | "test": "node --test --experimental-test-module-mocks",
14 | "test:coverage": "c8 npm test",
15 | "test:ci": "c8 --reporter=lcov node --test --experimental-test-module-mocks --test-reporter=@reporters/github --test-reporter-destination=stdout --test-reporter=junit --test-reporter-destination=junit.xml --test-reporter=spec --test-reporter-destination=stdout",
16 | "test:update-snapshots": "node --test --experimental-test-module-mocks --test-update-snapshots",
17 | "test:watch": "node --test --experimental-test-module-mocks --watch",
18 | "prepare": "husky || exit 0",
19 | "run": "node bin/cli.mjs",
20 | "watch": "node --watch bin/cli.mjs"
21 | },
22 | "main": "./src/index.mjs",
23 | "bin": {
24 | "doc-kit": "./bin/cli.mjs"
25 | },
26 | "devDependencies": {
27 | "@eslint/js": "^9.39.1",
28 | "@reporters/github": "^1.11.0",
29 | "@types/mdast": "^4.0.4",
30 | "@types/node": "^24.10.1",
31 | "c8": "^10.1.3",
32 | "eslint": "^9.39.1",
33 | "eslint-import-resolver-node": "^0.3.9",
34 | "eslint-plugin-import-x": "^4.16.1",
35 | "eslint-plugin-jsdoc": "^61.4.1",
36 | "husky": "^9.1.7",
37 | "lint-staged": "^16.2.7",
38 | "prettier": "3.7.4"
39 | },
40 | "dependencies": {
41 | "@actions/core": "^1.11.1",
42 | "@clack/prompts": "^0.11.0",
43 | "@heroicons/react": "^2.2.0",
44 | "@minify-html/node": "^0.16.4",
45 | "@node-core/rehype-shiki": "1.3.0",
46 | "@node-core/ui-components": "1.4.1",
47 | "@orama/orama": "^3.1.16",
48 | "@orama/ui": "^1.5.3",
49 | "@rollup/plugin-virtual": "^3.0.2",
50 | "acorn": "^8.15.0",
51 | "commander": "^14.0.2",
52 | "dedent": "^1.7.0",
53 | "eslint-plugin-react-x": "^2.3.12",
54 | "estree-util-to-js": "^2.0.0",
55 | "estree-util-visit": "^2.0.0",
56 | "github-slugger": "^2.0.0",
57 | "glob": "^13.0.0",
58 | "globals": "^16.5.0",
59 | "hast-util-to-string": "^3.0.1",
60 | "hastscript": "^9.0.1",
61 | "lightningcss": "^1.30.2",
62 | "mdast-util-slice-markdown": "^2.0.1",
63 | "piscina": "^5.1.4",
64 | "preact": "^11.0.0-beta.0",
65 | "preact-render-to-string": "^6.6.3",
66 | "reading-time": "^1.5.0",
67 | "recma-jsx": "^1.0.1",
68 | "rehype-raw": "^7.0.0",
69 | "rehype-recma": "^1.0.0",
70 | "rehype-stringify": "^10.0.1",
71 | "remark-gfm": "^4.0.1",
72 | "remark-parse": "^11.0.0",
73 | "remark-rehype": "^11.1.2",
74 | "remark-stringify": "^11.0.0",
75 | "rolldown": "^1.0.0-beta.53",
76 | "semver": "^7.7.3",
77 | "shiki": "^3.19.0",
78 | "to-vfile": "^8.0.0",
79 | "unified": "^11.0.5",
80 | "unist-builder": "^4.0.0",
81 | "unist-util-find-after": "^5.0.0",
82 | "unist-util-position": "^5.0.0",
83 | "unist-util-remove": "^4.0.0",
84 | "unist-util-select": "^5.1.0",
85 | "unist-util-visit": "^5.0.0",
86 | "vfile": "^6.0.3",
87 | "yaml": "^2.8.2"
88 | }
89 | }
90 |
--------------------------------------------------------------------------------
/scripts/vercel-build.sh:
--------------------------------------------------------------------------------
1 | node bin/cli.mjs generate \
2 | -t orama-db \
3 | -t legacy-json \
4 | -t llms-txt \
5 | -t web \
6 | -i "./node/doc/api/*.md" \
7 | -o "./out" \
8 | -c "./node/CHANGELOG.md" \
9 | --index "./node/doc/api/index.md" \
10 | --log-level debug
11 |
12 | rm -rf node/
13 |
--------------------------------------------------------------------------------
/scripts/vercel-prepare.sh:
--------------------------------------------------------------------------------
1 | # Clone the repository with no checkout and shallow history
2 | git clone --depth 1 --filter=blob:none --sparse https://github.com/nodejs/node.git
3 |
4 | # Move into the cloned directory
5 | cd node
6 |
7 | # Enable sparse checkout and specify the folder
8 | git sparse-checkout set lib doc/api .
9 |
10 | # Move back out
11 | cd ..
12 |
13 | # Install npm dependencies
14 | npm ci
15 |
16 | # Create the ./out directory
17 | mkdir -p out
18 |
--------------------------------------------------------------------------------
/shiki.config.mjs:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | import cLanguage from 'shiki/langs/c.mjs';
4 | import coffeeScriptLanguage from 'shiki/langs/coffeescript.mjs';
5 | import cPlusPlusLanguage from 'shiki/langs/cpp.mjs';
6 | import diffLanguage from 'shiki/langs/diff.mjs';
7 | import dockerLanguage from 'shiki/langs/docker.mjs';
8 | import httpLanguage from 'shiki/langs/http.mjs';
9 | import javaScriptLanguage from 'shiki/langs/javascript.mjs';
10 | import jsonLanguage from 'shiki/langs/json.mjs';
11 | import powershellLanguage from 'shiki/langs/powershell.mjs';
12 | import shellScriptLanguage from 'shiki/langs/shellscript.mjs';
13 | import shellSessionLanguage from 'shiki/langs/shellsession.mjs';
14 | import typeScriptLanguage from 'shiki/langs/typescript.mjs';
15 | import lightTheme from 'shiki/themes/catppuccin-latte.mjs';
16 | import darkTheme from 'shiki/themes/catppuccin-mocha.mjs';
17 |
18 | /**
19 | * Creates a Shiki configuration for the API Docs tooling
20 | *
21 | * @type {import('@shikijs/core').HighlighterCoreOptions}
22 | */
23 | export default {
24 | // Only register the themes we need, to support light/dark theme
25 | themes: [lightTheme, darkTheme],
26 | // Only register the languages that the API docs use
27 | // and override the JavaScript language with the aliases
28 | langs: [
29 | ...httpLanguage,
30 | ...jsonLanguage,
31 | ...typeScriptLanguage,
32 | ...shellScriptLanguage,
33 | ...powershellLanguage,
34 | ...shellSessionLanguage,
35 | ...dockerLanguage,
36 | ...diffLanguage,
37 | ...cLanguage,
38 | ...cPlusPlusLanguage,
39 | ...coffeeScriptLanguage,
40 | { ...javaScriptLanguage[0], aliases: ['mjs', 'cjs', 'js'] },
41 | ],
42 | };
43 |
--------------------------------------------------------------------------------
/src/__tests__/metadata.test.mjs:
--------------------------------------------------------------------------------
1 | import { strictEqual, deepStrictEqual } from 'node:assert';
2 | import { describe, it } from 'node:test';
3 |
4 | import GitHubSlugger from 'github-slugger';
5 | import { u } from 'unist-builder';
6 | import { VFile } from 'vfile';
7 |
8 | import createMetadata from '../metadata.mjs';
9 |
10 | describe('createMetadata', () => {
11 | it('should set the heading correctly', () => {
12 | const slugger = new GitHubSlugger();
13 | const metadata = createMetadata(slugger);
14 | const heading = u('heading', {
15 | type: 'heading',
16 | data: {
17 | text: 'Test Heading',
18 | type: 'test',
19 | name: 'test',
20 | depth: 1,
21 | },
22 | });
23 | metadata.setHeading(heading);
24 | strictEqual(metadata.create(new VFile(), {}).heading.data, heading.data);
25 | });
26 |
27 | it('should set the stability correctly', () => {
28 | const slugger = new GitHubSlugger();
29 | const metadata = createMetadata(slugger);
30 | const stability = {
31 | type: 'root',
32 | data: { index: 2, description: '' },
33 | children: [],
34 | };
35 | metadata.addStability(stability);
36 | const actual = metadata.create(new VFile(), {}).stability;
37 | deepStrictEqual(actual, {
38 | children: [stability],
39 | type: 'root',
40 | });
41 | });
42 |
43 | it('should create a metadata entry correctly', () => {
44 | const slugger = new GitHubSlugger();
45 | const metadata = createMetadata(slugger);
46 | const apiDoc = new VFile({ path: 'test.md' });
47 | const section = { type: 'root', children: [] };
48 | const heading = {
49 | type: 'heading',
50 | data: {
51 | text: 'Test Heading',
52 | type: 'test',
53 | name: 'test',
54 | depth: 1,
55 | },
56 | };
57 | const stability = {
58 | type: 'root',
59 | data: { index: 2, description: '' },
60 | children: [],
61 | };
62 | const properties = { source_link: 'test.com' };
63 | metadata.setHeading(heading);
64 | metadata.addStability(stability);
65 | metadata.updateProperties(properties);
66 | const expected = {
67 | added_in: undefined,
68 | api: 'test',
69 | api_doc_source: 'doc/api/test.md',
70 | changes: [],
71 | content: section,
72 | deprecated_in: undefined,
73 | heading,
74 | n_api_version: undefined,
75 | introduced_in: undefined,
76 | llm_description: undefined,
77 | removed_in: undefined,
78 | slug: 'test-heading',
79 | source_link: 'test.com',
80 | stability: { type: 'root', children: [stability] },
81 | tags: [],
82 | updates: [],
83 | yaml_position: {},
84 | };
85 | const actual = metadata.create(apiDoc, section);
86 | deepStrictEqual(actual, expected);
87 | });
88 |
89 | it('should be serializable', () => {
90 | const { create } = createMetadata(new GitHubSlugger());
91 | const actual = create(new VFile({ path: 'test.md' }), {
92 | type: 'root',
93 | children: [],
94 | });
95 | deepStrictEqual(structuredClone(actual), actual);
96 | });
97 | });
98 |
--------------------------------------------------------------------------------
/src/constants.mjs:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | // The current running version of Node.js (Environment)
4 | export const NODE_VERSION = process.version;
5 |
6 | // This is the Node.js CHANGELOG to be consumed to generate a list of all major Node.js versions
7 | export const NODE_CHANGELOG_URL =
8 | 'https://raw.githubusercontent.com/nodejs/node/HEAD/CHANGELOG.md';
9 |
10 | // The base URL for the Node.js website
11 | export const BASE_URL = 'https://nodejs.org/';
12 |
13 | // This is the Node.js Base URL for viewing a file within GitHub UI
14 | export const DOC_NODE_BLOB_BASE_URL =
15 | 'https://github.com/nodejs/node/blob/HEAD/';
16 |
17 | // This is the Node.js API docs base URL for editing a file on GitHub UI
18 | export const DOC_API_BLOB_EDIT_BASE_URL =
19 | 'https://github.com/nodejs/node/edit/main/doc/api/';
20 |
21 | // Base URL for a specific Node.js version within the Node.js API docs
22 | export const DOC_API_BASE_URL_VERSION = 'https://nodejs.org/docs/latest-v';
23 |
--------------------------------------------------------------------------------
/src/generators/__tests__/index.test.mjs:
--------------------------------------------------------------------------------
1 | import assert from 'node:assert/strict';
2 | import { describe, it } from 'node:test';
3 |
4 | import semver from 'semver';
5 |
6 | import { allGenerators } from '../index.mjs';
7 |
8 | const validDependencies = Object.keys(allGenerators);
9 | const generatorEntries = Object.entries(allGenerators);
10 |
11 | describe('All Generators', () => {
12 | it('should have keys matching their name property', () => {
13 | generatorEntries.forEach(([key, generator]) => {
14 | assert.equal(
15 | key,
16 | generator.name,
17 | `Generator key "${key}" does not match its name property "${generator.name}"`
18 | );
19 | });
20 | });
21 |
22 | it('should have valid semver versions', () => {
23 | generatorEntries.forEach(([key, generator]) => {
24 | const isValid = semver.valid(generator.version);
25 | assert.ok(
26 | isValid,
27 | `Generator "${key}" has invalid semver version: "${generator.version}"`
28 | );
29 | });
30 | });
31 |
32 | it('should have valid dependsOn references', () => {
33 | generatorEntries.forEach(([key, generator]) => {
34 | if (generator.dependsOn) {
35 | assert.ok(
36 | validDependencies.includes(generator.dependsOn),
37 | `Generator "${key}" depends on "${generator.dependsOn}" which is not a valid generator`
38 | );
39 | }
40 | });
41 | });
42 |
43 | it('should have ast generator as a top-level generator with no dependencies', () => {
44 | assert.ok(allGenerators.ast, 'ast generator should exist');
45 | assert.equal(
46 | allGenerators.ast.dependsOn,
47 | undefined,
48 | 'ast generator should have no dependencies'
49 | );
50 | });
51 | });
52 |
--------------------------------------------------------------------------------
/src/generators/addon-verify/constants.mjs:
--------------------------------------------------------------------------------
1 | export const EXTRACT_CODE_FILENAME_COMMENT = /^\/\/\s+(.*\.(?:cc|h|js))[\r\n]/;
2 |
--------------------------------------------------------------------------------
/src/generators/addon-verify/index.mjs:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | import { mkdir, writeFile } from 'node:fs/promises';
4 | import { join } from 'node:path';
5 |
6 | import { visit } from 'unist-util-visit';
7 |
8 | import { EXTRACT_CODE_FILENAME_COMMENT } from './constants.mjs';
9 | import { generateFileList } from './utils/generateFileList.mjs';
10 | import {
11 | generateSectionFolderName,
12 | isBuildableSection,
13 | normalizeSectionName,
14 | } from './utils/section.mjs';
15 |
16 | /**
17 | * This generator generates a file list from code blocks extracted from
18 | * `doc/api/addons.md` to facilitate C++ compilation and JavaScript runtime
19 | * validations.
20 | *
21 | * @typedef {Array} Input
22 | *
23 | * @type {GeneratorMetadata}
24 | */
25 | export default {
26 | name: 'addon-verify',
27 |
28 | version: '1.0.0',
29 |
30 | description:
31 | 'Generates a file list from code blocks extracted from `doc/api/addons.md` to facilitate C++ compilation and JavaScript runtime validations',
32 |
33 | dependsOn: 'metadata',
34 |
35 | /**
36 | * Generates a file list from code blocks.
37 | *
38 | * @param {Input} input
39 | * @param {Partial} options
40 | */
41 | async generate(input, { output }) {
42 | const sectionsCodeBlocks = input.reduce((addons, node) => {
43 | const sectionName = node.heading.data.name;
44 |
45 | const content = node.content;
46 |
47 | visit(content, childNode => {
48 | if (childNode.type === 'code') {
49 | const filename = childNode.value.match(EXTRACT_CODE_FILENAME_COMMENT);
50 |
51 | if (filename === null) {
52 | return;
53 | }
54 |
55 | if (!addons[sectionName]) {
56 | addons[sectionName] = [];
57 | }
58 |
59 | addons[sectionName].push({
60 | name: filename[1],
61 | content: childNode.value,
62 | });
63 | }
64 | });
65 |
66 | return addons;
67 | }, {});
68 |
69 | const files = await Promise.all(
70 | Object.entries(sectionsCodeBlocks)
71 | .filter(([, codeBlocks]) => isBuildableSection(codeBlocks))
72 | .flatMap(async ([sectionName, codeBlocks], index) => {
73 | const files = generateFileList(codeBlocks);
74 |
75 | if (output) {
76 | const normalizedSectionName = normalizeSectionName(sectionName);
77 |
78 | const folderName = generateSectionFolderName(
79 | normalizedSectionName,
80 | index
81 | );
82 |
83 | await mkdir(join(output, folderName), { recursive: true });
84 |
85 | for (const file of files) {
86 | await writeFile(
87 | join(output, folderName, file.name),
88 | file.content
89 | );
90 | }
91 | }
92 |
93 | return files;
94 | })
95 | );
96 |
97 | return files;
98 | },
99 | };
100 |
--------------------------------------------------------------------------------
/src/generators/addon-verify/utils/__tests__/generateFileList.test.mjs:
--------------------------------------------------------------------------------
1 | import assert from 'node:assert/strict';
2 | import { describe, it } from 'node:test';
3 |
4 | import { generateFileList } from '../generateFileList.mjs';
5 |
6 | describe('generateFileList', () => {
7 | it('should transform test.js files with updated require paths', () => {
8 | const codeBlocks = [
9 | {
10 | name: 'test.js',
11 | content: "const addon = require('./build/Release/addon');",
12 | },
13 | ];
14 |
15 | const result = generateFileList(codeBlocks);
16 | const testFile = result.find(file => file.name === 'test.js');
17 |
18 | assert(testFile.content.includes("'use strict';"));
19 | assert(testFile.content.includes('`./build/${common.buildType}/addon`'));
20 | assert(!testFile.content.includes("'./build/Release/addon'"));
21 | });
22 |
23 | it('should preserve other files unchanged', () => {
24 | const codeBlocks = [{ name: 'addon.cc', content: '#include ' }];
25 |
26 | const result = generateFileList(codeBlocks);
27 |
28 | assert.equal(
29 | result.find(file => file.name === 'addon.cc').content,
30 | '#include '
31 | );
32 | });
33 |
34 | it('should add binding.gyp file', () => {
35 | const codeBlocks = [{ name: 'addon.cc', content: 'code' }];
36 |
37 | const result = generateFileList(codeBlocks);
38 | const bindingFile = result.find(file => file.name === 'binding.gyp');
39 |
40 | assert(bindingFile);
41 | const config = JSON.parse(bindingFile.content);
42 | assert.equal(config.targets[0].target_name, 'addon');
43 | assert(config.targets[0].sources.includes('addon.cc'));
44 | });
45 |
46 | it('should handle empty input', () => {
47 | const result = generateFileList([]);
48 |
49 | assert.equal(result.length, 1);
50 | assert.equal(result[0].name, 'binding.gyp');
51 | });
52 | });
53 |
--------------------------------------------------------------------------------
/src/generators/addon-verify/utils/__tests__/section.test.mjs:
--------------------------------------------------------------------------------
1 | import assert from 'node:assert/strict';
2 | import { describe, it } from 'node:test';
3 |
4 | import {
5 | isBuildableSection,
6 | normalizeSectionName,
7 | generateSectionFolderName,
8 | } from '../section.mjs';
9 |
10 | describe('isBuildableSection', () => {
11 | it('should return true when both .cc and .js files are present', () => {
12 | const codeBlocks = [
13 | { name: 'addon.cc', content: 'C++ code' },
14 | { name: 'test.js', content: 'JS code' },
15 | ];
16 |
17 | assert.equal(isBuildableSection(codeBlocks), true);
18 | });
19 |
20 | it('should return false when only .cc file is present', () => {
21 | const codeBlocks = [{ name: 'addon.cc', content: 'C++ code' }];
22 |
23 | assert.equal(isBuildableSection(codeBlocks), false);
24 | });
25 |
26 | it('should return false when only .js file is present', () => {
27 | const codeBlocks = [{ name: 'test.js', content: 'JS code' }];
28 |
29 | assert.equal(isBuildableSection(codeBlocks), false);
30 | });
31 |
32 | it('should return false for empty array', () => {
33 | assert.equal(isBuildableSection([]), false);
34 | });
35 | });
36 |
37 | describe('normalizeSectionName', () => {
38 | it('should convert to lowercase and replace spaces with underscores', () => {
39 | assert.equal(normalizeSectionName('Hello World'), 'hello_world');
40 | });
41 |
42 | it('should remove non-word characters', () => {
43 | assert.equal(normalizeSectionName('Test-Section!@#'), 'testsection');
44 | });
45 |
46 | it('should handle empty string', () => {
47 | assert.equal(normalizeSectionName(''), '');
48 | });
49 |
50 | it('should handle mixed cases and special characters', () => {
51 | assert.equal(
52 | normalizeSectionName('My Test & Example #1'),
53 | 'my_test__example_1'
54 | );
55 | });
56 | });
57 |
58 | describe('generateSectionFolderName', () => {
59 | it('should generate folder name with padded index', () => {
60 | assert.equal(generateSectionFolderName('hello_world', 0), '01_hello_world');
61 | });
62 |
63 | it('should pad single digit indices', () => {
64 | assert.equal(generateSectionFolderName('test', 5), '06_test');
65 | });
66 |
67 | it('should not pad double digit indices', () => {
68 | assert.equal(generateSectionFolderName('example', 15), '16_example');
69 | });
70 |
71 | it('should handle empty section name', () => {
72 | assert.equal(generateSectionFolderName('', 0), '01_');
73 | });
74 | });
75 |
--------------------------------------------------------------------------------
/src/generators/addon-verify/utils/generateFileList.mjs:
--------------------------------------------------------------------------------
1 | import dedent from 'dedent';
2 |
3 | /**
4 | * Updates JavaScript files with correct require paths for the build tree.
5 | *
6 | * @param {string} content Original code
7 | * @returns {string}
8 | */
9 | const updateJsRequirePaths = content => {
10 | return dedent`
11 | 'use strict';
12 | const common = require('../../common');
13 | ${content.replace(
14 | "'./build/Release/addon'",
15 | '`./build/${common.buildType}/addon`'
16 | )}`;
17 | };
18 |
19 | /**
20 | * Creates a binding.gyp configuration for C++ addon compilation.
21 | *
22 | * @param {string[]} sourceFiles List of source file names
23 | * @returns {string}
24 | */
25 | const createBindingGyp = sourceFiles => {
26 | const config = {
27 | targets: [
28 | {
29 | target_name: 'addon',
30 | sources: sourceFiles,
31 | includes: ['../common.gypi'],
32 | },
33 | ],
34 | };
35 |
36 | return JSON.stringify(config);
37 | };
38 |
39 | /**
40 | * Generates required files list from section's code blocks for C++ addon
41 | * compilation.
42 | *
43 | * @param {{name: string, content: string}[]} codeBlocks Array of code blocks
44 | * @returns {{name: string, content: string}[]}
45 | */
46 | export const generateFileList = codeBlocks => {
47 | const files = codeBlocks.map(({ name, content }) => {
48 | return {
49 | name,
50 | content: name === 'test.js' ? updateJsRequirePaths(content) : content,
51 | };
52 | });
53 |
54 | files.push({
55 | name: 'binding.gyp',
56 | content: createBindingGyp(files.map(({ name }) => name)),
57 | });
58 |
59 | return files;
60 | };
61 |
--------------------------------------------------------------------------------
/src/generators/addon-verify/utils/section.mjs:
--------------------------------------------------------------------------------
1 | /**
2 | * Checks if a section contains the required code blocks for building.
3 | * A buildable section must contain at least one C++ (.cc) file and one
4 | * JavaScript (.js) file.
5 | *
6 | * @param {Array<{name: string; content: string}>} codeBlocks Array of code blocks
7 | * @returns {boolean}
8 | */
9 | export const isBuildableSection = codeBlocks => {
10 | return (
11 | codeBlocks.some(codeBlock => codeBlock.name.endsWith('.cc')) &&
12 | codeBlocks.some(codeBlock => codeBlock.name.endsWith('.js'))
13 | );
14 | };
15 |
16 | /**
17 | * Normalizes a section name.
18 | *
19 | * @param {string} sectionName Original section name
20 | * @returns {string}
21 | */
22 | export const normalizeSectionName = sectionName => {
23 | return sectionName.toLowerCase().replace(/\s/g, '_').replace(/\W/g, '');
24 | };
25 |
26 | /**
27 | * Generates a standardized folder name for a section.
28 | *
29 | * @param {string} sectionName Normalized section name
30 | * @param {number} index Zero-based section index
31 | * @returns {string}
32 | */
33 | export const generateSectionFolderName = (sectionName, index) => {
34 | const identifier = String(index + 1).padStart(2, '0');
35 |
36 | return `${identifier}_${sectionName}`;
37 | };
38 |
--------------------------------------------------------------------------------
/src/generators/api-links/__tests__/fixtures.test.mjs:
--------------------------------------------------------------------------------
1 | import { readdir } from 'node:fs/promises';
2 | import { cpus } from 'node:os';
3 | import { basename, extname, join } from 'node:path';
4 | import { after, before, describe, it } from 'node:test';
5 |
6 | import createWorkerPool from '../../../threading/index.mjs';
7 | import createParallelWorker from '../../../threading/parallel.mjs';
8 | import astJs from '../../ast-js/index.mjs';
9 | import apiLinks from '../index.mjs';
10 |
11 | const FIXTURES_DIRECTORY = join(import.meta.dirname, 'fixtures');
12 | const fixtures = await readdir(FIXTURES_DIRECTORY);
13 |
14 | const sourceFiles = fixtures
15 | .filter(fixture => extname(fixture) === '.js')
16 | .map(fixture => join(FIXTURES_DIRECTORY, fixture));
17 |
18 | describe('api links', () => {
19 | const threads = cpus().length;
20 | let pool;
21 |
22 | before(() => {
23 | pool = createWorkerPool(threads);
24 | });
25 |
26 | after(async () => {
27 | await pool.destroy();
28 | });
29 |
30 | describe('should work correctly for all fixtures', () => {
31 | sourceFiles.forEach(sourceFile => {
32 | it(`${basename(sourceFile)}`, async t => {
33 | const worker = createParallelWorker('ast-js', pool, {
34 | threads,
35 | chunkSize: 10,
36 | });
37 |
38 | // Collect results from the async generator
39 | const astJsResults = [];
40 |
41 | for await (const chunk of astJs.generate(undefined, {
42 | input: [sourceFile],
43 | worker,
44 | })) {
45 | astJsResults.push(...chunk);
46 | }
47 |
48 | const actualOutput = await apiLinks.generate(astJsResults, {
49 | gitRef: 'https://github.com/nodejs/node/tree/HEAD',
50 | });
51 |
52 | for (const [k, v] of Object.entries(actualOutput)) {
53 | actualOutput[k] = v.replace(/.*(?=lib\/)/, '');
54 | }
55 |
56 | t.assert.snapshot(actualOutput);
57 | });
58 | });
59 | });
60 | });
61 |
--------------------------------------------------------------------------------
/src/generators/api-links/__tests__/fixtures.test.mjs.snapshot:
--------------------------------------------------------------------------------
1 | exports[`api links > should work correctly for all fixtures > buffer.js 1`] = `
2 | {
3 | "buffer.Buffer": "lib/buffer.js#L5",
4 | "buf.instanceMethod": "lib/buffer.js#L8"
5 | }
6 | `;
7 |
8 | exports[`api links > should work correctly for all fixtures > class.js 1`] = `
9 | {
10 | "Class": "lib/class.js#L5",
11 | "new Class": "lib/class.js#L6",
12 | "class.method": "lib/class.js#L7"
13 | }
14 | `;
15 |
16 | exports[`api links > should work correctly for all fixtures > exports.js 1`] = `
17 | {
18 | "exports.fn1": "lib/exports.js#L8",
19 | "exports.fn2": "lib/exports.js#L10",
20 | "exports.Buffer": "lib/exports.js#L5",
21 | "exports.fn3": "lib/exports.js#L12"
22 | }
23 | `;
24 |
25 | exports[`api links > should work correctly for all fixtures > mod.js 1`] = `
26 | {
27 | "mod.foo": "lib/mod.js#L5"
28 | }
29 | `;
30 |
31 | exports[`api links > should work correctly for all fixtures > prototype.js 1`] = `
32 | {
33 | "prototype.Class": "lib/prototype.js#L5",
34 | "Class.classMethod": "lib/prototype.js#L8",
35 | "class.instanceMethod": "lib/prototype.js#L9"
36 | }
37 | `;
38 |
39 | exports[`api links > should work correctly for all fixtures > reverse.js 1`] = `
40 | {
41 | "asserts": "lib/reverse.js#L8",
42 | "asserts.ok": "lib/reverse.js#L5",
43 | "asserts.strictEqual": "lib/reverse.js#L12"
44 | }
45 | `;
46 |
47 | exports[`api links > should work correctly for all fixtures > root.js 1`] = `
48 | {}
49 | `;
50 |
--------------------------------------------------------------------------------
/src/generators/api-links/__tests__/fixtures/buffer.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | // Buffer instance methods are exported as 'buf'.
4 |
5 | function Buffer() {
6 | }
7 |
8 | Buffer.prototype.instanceMethod = function() {}
9 |
10 | module.exports = {
11 | Buffer
12 | };
13 |
--------------------------------------------------------------------------------
/src/generators/api-links/__tests__/fixtures/class.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | // An exported class using ES2015 class syntax.
4 |
5 | class Class {
6 | constructor() {};
7 | method() {};
8 | }
9 |
10 | module.exports = {
11 | Class
12 | };
13 |
--------------------------------------------------------------------------------
/src/generators/api-links/__tests__/fixtures/exports.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | // Support `exports` as an alternative to `module.exports`.
4 |
5 | function Buffer() {};
6 |
7 | exports.Buffer = Buffer;
8 | exports.fn1 = function fn1() {};
9 |
10 | var fn2 = exports.fn2 = function() {};
11 |
12 | function fn3() {};
13 | exports.fn3 = fn3;
14 |
--------------------------------------------------------------------------------
/src/generators/api-links/__tests__/fixtures/mod.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | // A module may export one or more methods.
4 |
5 | function foo() {
6 | }
7 |
8 |
9 | module.exports = {
10 | foo
11 | };
12 |
--------------------------------------------------------------------------------
/src/generators/api-links/__tests__/fixtures/prototype.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | // An exported class using classic prototype syntax.
4 |
5 | function Class() {
6 | }
7 |
8 | Class.classMethod = function() {}
9 | Class.prototype.instanceMethod = function() {}
10 |
11 | module.exports = {
12 | Class
13 | };
14 |
--------------------------------------------------------------------------------
/src/generators/api-links/__tests__/fixtures/reverse.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | // Parallel assignment to the exported variable and module.exports.
4 |
5 | function ok() {
6 | }
7 |
8 | const asserts = module.exports = ok;
9 |
10 | asserts.ok = ok;
11 |
12 | asserts.strictEqual = function() {
13 | }
14 |
--------------------------------------------------------------------------------
/src/generators/api-links/__tests__/fixtures/root.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | // Set root member
4 | let foo = true;
5 | foo = false;
6 |
7 | // Return outside of function
8 | if (!foo) {
9 | return;
10 | }
11 |
--------------------------------------------------------------------------------
/src/generators/api-links/constants.mjs:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | // Checks if a string is a valid name for a constructor in JavaScript
4 | export const CONSTRUCTOR_EXPRESSION = /^[A-Z]/;
5 |
--------------------------------------------------------------------------------
/src/generators/api-links/index.mjs:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | import { writeFile } from 'node:fs/promises';
4 | import { basename, join } from 'node:path';
5 |
6 | import { checkIndirectReferences } from './utils/checkIndirectReferences.mjs';
7 | import { extractExports } from './utils/extractExports.mjs';
8 | import { findDefinitions } from './utils/findDefinitions.mjs';
9 |
10 | /**
11 | * This generator is responsible for mapping publicly accessible functions in
12 | * Node.js to their source locations in the Node.js repository.
13 | *
14 | * This is a top-level generator. It takes in the raw AST tree of the JavaScript
15 | * source files. It outputs a `apilinks.json` file into the specified output
16 | * directory.
17 | *
18 | * @typedef {Array} Input
19 | *
20 | * @type {GeneratorMetadata>}
21 | */
22 | export default {
23 | name: 'api-links',
24 |
25 | version: '1.0.0',
26 |
27 | description:
28 | 'Creates a mapping of publicly accessible functions to their source locations in the Node.js repository.',
29 |
30 | // Unlike the rest of the generators, this utilizes Javascript sources being
31 | // passed into the input field rather than Markdown.
32 | dependsOn: 'ast-js',
33 |
34 | /**
35 | * Generates the `apilinks.json` file.
36 | *
37 | * @param {Input} input
38 | * @param {Partial} options
39 | */
40 | async generate(input, { output, gitRef }) {
41 | /**
42 | * @type Record
43 | */
44 | const definitions = {};
45 |
46 | input.forEach(program => {
47 | /**
48 | * Mapping of definitions to their line number
49 | *
50 | * @type {Record}
51 | * @example { 'someclass.foo': 10 }
52 | */
53 | const nameToLineNumberMap = {};
54 |
55 | // `http.js` -> `http`
56 | const baseName = basename(program.path, '.js');
57 |
58 | const exports = extractExports(program, baseName, nameToLineNumberMap);
59 |
60 | findDefinitions(program, baseName, nameToLineNumberMap, exports);
61 |
62 | checkIndirectReferences(program, exports, nameToLineNumberMap);
63 |
64 | const fullGitUrl = `${gitRef}/lib/${baseName}.js`;
65 |
66 | // Add the exports we found in this program to our output
67 | Object.keys(nameToLineNumberMap).forEach(key => {
68 | const lineNumber = nameToLineNumberMap[key];
69 |
70 | definitions[key] = `${fullGitUrl}#L${lineNumber}`;
71 | });
72 | });
73 |
74 | if (output) {
75 | const out = join(output, 'apilinks.json');
76 |
77 | await writeFile(out, JSON.stringify(definitions));
78 | }
79 |
80 | return definitions;
81 | },
82 | };
83 |
--------------------------------------------------------------------------------
/src/generators/api-links/types.d.ts:
--------------------------------------------------------------------------------
1 | export interface ProgramExports {
2 | ctors: Array;
3 | identifiers: Array;
4 | indirects: Record;
5 | }
6 |
--------------------------------------------------------------------------------
/src/generators/api-links/utils/checkIndirectReferences.mjs:
--------------------------------------------------------------------------------
1 | import { visit } from 'estree-util-visit';
2 |
3 | /**
4 | * @param {import('acorn').Program} program
5 | * @param {import('../types.d.ts').ProgramExports} exports
6 | * @param {Record} nameToLineNumberMap
7 | */
8 | export function checkIndirectReferences(program, exports, nameToLineNumberMap) {
9 | if (Object.keys(exports.indirects).length === 0) {
10 | return;
11 | }
12 |
13 | visit(program, node => {
14 | if (!node.loc || node.type !== 'FunctionDeclaration') {
15 | return;
16 | }
17 |
18 | const name = node.id.name;
19 |
20 | if (name in exports.indirects) {
21 | nameToLineNumberMap[exports.indirects[name]] = node.loc.start.line;
22 | }
23 | });
24 | }
25 |
--------------------------------------------------------------------------------
/src/generators/ast-js/index.mjs:
--------------------------------------------------------------------------------
1 | import { extname } from 'node:path';
2 |
3 | import { globSync } from 'glob';
4 | import { read } from 'to-vfile';
5 |
6 | import { parseJsSource } from '../../parsers/javascript.mjs';
7 |
8 | /**
9 | * This generator parses Javascript sources passed into the generator's input
10 | * field. This is separate from the Markdown parsing step since it's not as
11 | * commonly used and can take up a significant amount of memory.
12 | *
13 | * Putting this with the rest of the generators allows it to be lazily loaded
14 | * so we're only parsing the Javascript sources when we need to.
15 | *
16 | * @typedef {unknown} Input
17 | * @typedef {Array} Output
18 | *
19 | * @type {GeneratorMetadata}
20 | */
21 | export default {
22 | name: 'ast-js',
23 |
24 | version: '1.0.0',
25 |
26 | description: 'Parses Javascript source files passed into the input.',
27 |
28 | /**
29 | * Process a chunk of JavaScript files in a worker thread.
30 | * Parses JS source files into AST representations.
31 | *
32 | * @param {string[]} inputSlice - Sliced input paths for this chunk
33 | * @param {number[]} itemIndices - Indices into the sliced array
34 | * @returns {Promise