diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index 9c8c6a66..1be819a8 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -40,7 +40,7 @@ jobs: steps: - name: Harden Runner - uses: step-security/harden-runner@0080882f6c36860b6ba35c610c98ce87d4e2f26f # v2.10.2 + uses: step-security/harden-runner@4d991eb9b905ef189e4c376166672c3f2f230481 # v2.11.0 with: egress-policy: audit @@ -49,7 +49,7 @@ jobs: # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL - uses: github/codeql-action/init@48ab28a6f5dbc2a99bf1e0131198dd8f1df78169 # v3.28.0 + uses: github/codeql-action/init@b56ba49b26e50535fa1e7f7db0f4f7b4bf65d80d # v3.28.10 with: languages: ${{ matrix.language }} # If you wish to specify custom queries, you can do so here or in a config file. @@ -59,7 +59,7 @@ jobs: # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). # If this step fails, then you should remove it and run the build manually (see below) - name: Autobuild - uses: github/codeql-action/autobuild@48ab28a6f5dbc2a99bf1e0131198dd8f1df78169 # v3.28.0 + uses: github/codeql-action/autobuild@b56ba49b26e50535fa1e7f7db0f4f7b4bf65d80d # v3.28.10 # â„šī¸ Command-line programs to run using the OS shell. # 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun @@ -72,6 +72,6 @@ jobs: # ./location_of_script_within_repo/buildscript.sh - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@48ab28a6f5dbc2a99bf1e0131198dd8f1df78169 # v3.28.0 + uses: github/codeql-action/analyze@b56ba49b26e50535fa1e7f7db0f4f7b4bf65d80d # v3.28.10 with: category: '/language:${{matrix.language}}' diff --git a/.github/workflows/codespell.yml b/.github/workflows/codespell.yml index 8eb107e6..56527839 100644 --- a/.github/workflows/codespell.yml +++ b/.github/workflows/codespell.yml @@ -9,7 +9,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Harden Runner - uses: step-security/harden-runner@0080882f6c36860b6ba35c610c98ce87d4e2f26f # v2.10.2 + uses: step-security/harden-runner@4d991eb9b905ef189e4c376166672c3f2f230481 # v2.11.0 with: egress-policy: audit diff --git a/.github/workflows/dependency-review.yml b/.github/workflows/dependency-review.yml index a4566f81..78859108 100644 --- a/.github/workflows/dependency-review.yml +++ b/.github/workflows/dependency-review.yml @@ -21,7 +21,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Harden Runner - uses: step-security/harden-runner@0080882f6c36860b6ba35c610c98ce87d4e2f26f # v2.10.2 + uses: step-security/harden-runner@4d991eb9b905ef189e4c376166672c3f2f230481 # v2.11.0 with: egress-policy: audit diff --git a/.github/workflows/pr.yml b/.github/workflows/pr.yml index 04ec887c..506083dd 100644 --- a/.github/workflows/pr.yml +++ b/.github/workflows/pr.yml @@ -14,22 +14,26 @@ jobs: runs-on: ubuntu-latest steps: - name: Harden Runner - uses: step-security/harden-runner@0080882f6c36860b6ba35c610c98ce87d4e2f26f # v2.10.2 + uses: step-security/harden-runner@4d991eb9b905ef189e4c376166672c3f2f230481 # v2.11.0 with: egress-policy: audit - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + - name: Setup Node.js - uses: actions/setup-node@39370e3970a6d050c480ffad4ff0ed4d3fdee5af # v4.1.0 + uses: actions/setup-node@1d0ff469b7ec7b3cb9d8673fde0c81c44821de2a # v4.2.0 with: node-version-file: '.nvmrc' + cache: 'npm' + - name: Install dependencies run: npm ci + - name: Lint - run: npm run lint + run: node --run lint + - name: Format - run: npm run format - - name: Test - run: npm test + run: node --run format + - name: Test with coverage - run: npm run test:coverage + run: node --run test:coverage diff --git a/.github/workflows/scorecard.yml b/.github/workflows/scorecard.yml index a35607af..8c6ab800 100644 --- a/.github/workflows/scorecard.yml +++ b/.github/workflows/scorecard.yml @@ -32,7 +32,7 @@ jobs: steps: - name: Harden Runner - uses: step-security/harden-runner@0080882f6c36860b6ba35c610c98ce87d4e2f26f # v2.10.2 + uses: step-security/harden-runner@4d991eb9b905ef189e4c376166672c3f2f230481 # v2.11.0 with: egress-policy: audit @@ -42,7 +42,7 @@ jobs: persist-credentials: false - name: Run Scorecard Analysis - uses: ossf/scorecard-action@62b2cac7ed8198b15735ed49ab1e5cf35480ba46 # v2.4.0 + uses: ossf/scorecard-action@f49aabe0b5af0936a0987cfb85d86b75731b0186 # v2.4.1 with: results_file: results.sarif results_format: sarif @@ -51,7 +51,7 @@ jobs: # Upload the results as artifacts (optional). Commenting out will disable uploads of run results in SARIF # format to the repository Actions tab. - name: Upload Artifacts - uses: actions/upload-artifact@6f51ac03b9356f520e9adb1b1b7802705f340c2b # v4.5.0 + uses: actions/upload-artifact@4cec3d8aa04e39d1a68397de0c4cd6fb9dce8ec1 # v4.6.1 with: name: SARIF file path: results.sarif @@ -59,6 +59,6 @@ jobs: # Upload the results to GitHub's code scanning dashboard. - name: Upload Scan Results - uses: github/codeql-action/upload-sarif@48ab28a6f5dbc2a99bf1e0131198dd8f1df78169 # v3.28.0 + uses: github/codeql-action/upload-sarif@b56ba49b26e50535fa1e7f7db0f4f7b4bf65d80d # v3.28.10 with: sarif_file: results.sarif diff --git a/.gitignore b/.gitignore index 1dc047ad..ff95866d 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,6 @@ -# Commonly ignored Node.js files +# Vendor Folders node_modules npm-debug.log -.npm -.env.* + +# Default Output Directory +out diff --git a/README.md b/README.md index f6a9c1c4..73b7afb0 100644 --- a/README.md +++ b/README.md @@ -39,6 +39,6 @@ Options: -o, --output Specify the relative or absolute output directory -v, --version Specify the target version of Node.js, semver compliant (default: "v22.6.0") -c, --changelog Specify the path (file: or https://) to the CHANGELOG.md file (default: "https://raw.githubusercontent.com/nodejs/node/HEAD/CHANGELOG.md") - -t, --target [mode...] Set the processing target modes (choices: "json-simple", "legacy-html", "legacy-html-all", "man-page", "legacy-json", "legacy-json-all", "addon-verify") + -t, --target [mode...] Set the processing target modes (choices: "json-simple", "legacy-html", "legacy-html-all", "man-page", "legacy-json", "legacy-json-all", "addon-verify", "api-links") -h, --help display help for command ``` diff --git a/bin/cli.mjs b/bin/cli.mjs index 5bb332b9..ba6f8eaa 100755 --- a/bin/cli.mjs +++ b/bin/cli.mjs @@ -9,8 +9,8 @@ import { coerce } from 'semver'; import { DOC_NODE_CHANGELOG_URL, DOC_NODE_VERSION } from '../src/constants.mjs'; import createGenerator from '../src/generators.mjs'; import generators from '../src/generators/index.mjs'; -import createLoader from '../src/loader.mjs'; -import createParser from '../src/parser.mjs'; +import createMarkdownLoader from '../src/loaders/markdown.mjs'; +import createMarkdownParser from '../src/parsers/markdown.mjs'; import createNodeReleases from '../src/releases.mjs'; const availableGenerators = Object.keys(generators); @@ -68,8 +68,8 @@ program */ const { input, output, target = [], version, changelog } = program.opts(); -const { loadFiles } = createLoader(); -const { parseApiDocs } = createParser(); +const { loadFiles } = createMarkdownLoader(); +const { parseApiDocs } = createMarkdownParser(); const apiDocFiles = loadFiles(input); @@ -83,6 +83,8 @@ const { getAllMajors } = createNodeReleases(changelog); await runGenerators({ // A list of target modes for the API docs parser generators: target, + // Resolved `input` to be used + input: input, // Resolved `output` path to be used output: resolve(output), // Resolved SemVer of current Node.js version diff --git a/index.mjs b/index.mjs new file mode 100644 index 00000000..1ac923d5 --- /dev/null +++ b/index.mjs @@ -0,0 +1,24 @@ +import { Spinner } from "./src/utils/spinner.mjs"; +import * as prompts from '@clack/prompts'; + +prompts.intro('spinner start...'); + +const spinner = new Spinner(); +spinner.total = 100; + +spinner.start(); + +new Promise((resolve) => { + let progress = 0; + const timer = setInterval(() => { + progress = Math.min(spinner.total, progress + 1); + spinner.update(1, `Loading... ${progress}/${spinner.total}`); + if (progress >= spinner.total) { + clearInterval(timer); + resolve(true); + } + }, 100); +}).then(() => { + spinner.stop('Done'); + prompts.outro('spinner stop...'); +}); diff --git a/package-lock.json b/package-lock.json index fba79fb4..d5235286 100644 --- a/package-lock.json +++ b/package-lock.json @@ -6,6 +6,7 @@ "": { "name": "@node-core/api-docs-tooling", "dependencies": { + "@clack/prompts": "^0.9.1", "commander": "^13.0.0", "github-slugger": "^2.0.0", "glob": "^11.0.0", @@ -45,6 +46,27 @@ "prettier": "3.4.2" } }, + "node_modules/@clack/core": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/@clack/core/-/core-0.4.1.tgz", + "integrity": "sha512-Pxhij4UXg8KSr7rPek6Zowm+5M22rbd2g1nfojHJkxp5YkFqiZ2+YLEM/XGVIzvGOcM0nqjIFxrpDwWRZYWYjA==", + "license": "MIT", + "dependencies": { + "picocolors": "^1.0.0", + "sisteransi": "^1.0.5" + } + }, + "node_modules/@clack/prompts": { + "version": "0.9.1", + "resolved": "https://registry.npmjs.org/@clack/prompts/-/prompts-0.9.1.tgz", + "integrity": "sha512-JIpyaboYZeWYlyP0H+OoPPxd6nqueG/CmN6ixBiNFsIDHREevjIf0n0Ohh5gr5C8pEDknzgvz+pIJ8dMhzWIeg==", + "license": "MIT", + "dependencies": { + "@clack/core": "0.4.1", + "picocolors": "^1.0.0", + "sisteransi": "^1.0.5" + } + }, "node_modules/@es-joy/jsdoccomment": { "version": "0.49.0", "resolved": "https://registry.npmjs.org/@es-joy/jsdoccomment/-/jsdoccomment-0.49.0.tgz", @@ -100,13 +122,13 @@ } }, "node_modules/@eslint/config-array": { - "version": "0.19.0", - "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.19.0.tgz", - "integrity": "sha512-zdHg2FPIFNKPdcHWtiNT+jEFCHYVplAXRDlQDyqy0zGx/q2parwh7brGJSiTxRk/TSMkbM//zt/f5CHgyTyaSQ==", + "version": "0.19.1", + "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.19.1.tgz", + "integrity": "sha512-fo6Mtm5mWyKjA/Chy1BYTdn5mGJoDNjC7C64ug20ADsRDGrA85bN3uK3MaKbeRkRuuIEAR5N33Jr1pbm411/PA==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@eslint/object-schema": "^2.1.4", + "@eslint/object-schema": "^2.1.5", "debug": "^4.3.1", "minimatch": "^3.1.2" }, @@ -115,11 +137,14 @@ } }, "node_modules/@eslint/core": { - "version": "0.9.0", - "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.9.0.tgz", - "integrity": "sha512-7ATR9F0e4W85D/0w7cU0SNj7qkAexMG+bAHEZOjo9akvGuhHE2m7umzWzfnpa0XAg5Kxc1BWmtPMV67jJ+9VUg==", + "version": "0.10.0", + "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.10.0.tgz", + "integrity": "sha512-gFHJ+xBOo4G3WRlR1e/3G8A6/KZAH6zcE/hkLRCZTi/B9avAG365QhFA8uOGzTMqgTghpn7/fSnscW++dpMSAw==", "dev": true, "license": "Apache-2.0", + "dependencies": { + "@types/json-schema": "^7.0.15" + }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" } @@ -162,18 +187,19 @@ } }, "node_modules/@eslint/js": { - "version": "9.17.0", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.17.0.tgz", - "integrity": "sha512-Sxc4hqcs1kTu0iID3kcZDW3JHq2a77HO9P8CP6YEA/FpH3Ll8UXE2r/86Rz9YJLKme39S9vU5OWNjC6Xl0Cr3w==", + "version": "9.18.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.18.0.tgz", + "integrity": "sha512-fK6L7rxcq6/z+AaQMtiFTkvbHkBLNlwyRxHpKawP0x3u9+NC6MQTnFW+AdpwC6gfHTW0051cokQgtTN2FqlxQA==", "dev": true, + "license": "MIT", "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" } }, "node_modules/@eslint/object-schema": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.4.tgz", - "integrity": "sha512-BsWiH1yFGjXXS2yvrf5LyuoSIIbPrGUWob917o+BTKuZ7qJdxX8aJLRxs1fS9n6r7vESrq1OUqb68dANcFXuQQ==", + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.5.tgz", + "integrity": "sha512-o0bhxnL89h5Bae5T318nFoFzGy+YE5i/gGkoPAgkmTVdRKTiv3p8JHevPiPaMwoloKfEiiaHlawCqaZMqRm+XQ==", "dev": true, "license": "Apache-2.0", "engines": { @@ -181,12 +207,13 @@ } }, "node_modules/@eslint/plugin-kit": { - "version": "0.2.3", - "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.2.3.tgz", - "integrity": "sha512-2b/g5hRmpbb1o4GnTZax9N9m0FXzz9OV42ZzI4rDDMDuHUqigAiQCEWChBWCY4ztAGVRjoWT19v0yMmc5/L5kA==", + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.2.5.tgz", + "integrity": "sha512-lB05FkqEdUg2AA0xEbUz0SnkXT1LcCTa438W4IWTUh4hdOnVbQyOJ81OrDXsJk/LSiJHubgGEFoR5EHq1NsH1A==", "dev": true, "license": "Apache-2.0", "dependencies": { + "@eslint/core": "^0.10.0", "levn": "^0.4.1" }, "engines": { @@ -385,50 +412,73 @@ } }, "node_modules/@shikijs/core": { - "version": "1.24.4", - "resolved": "https://registry.npmjs.org/@shikijs/core/-/core-1.24.4.tgz", - "integrity": "sha512-jjLsld+xEEGYlxAXDyGwWsKJ1sw5Pc1pnp4ai2ORpjx2UX08YYTC0NNqQYO1PaghYaR+PvgMOGuvzw2he9sk0Q==", - "dependencies": { - "@shikijs/engine-javascript": "1.24.4", - "@shikijs/engine-oniguruma": "1.24.4", - "@shikijs/types": "1.24.4", - "@shikijs/vscode-textmate": "^9.3.1", + "version": "1.27.2", + "resolved": "https://registry.npmjs.org/@shikijs/core/-/core-1.27.2.tgz", + "integrity": "sha512-ns1dokDr0KE1lQ9mWd4rqaBkhSApk0qGCK1+lOqwnkQSkVZ08UGqXj1Ef8dAcTMZNFkN6PSNjkL5TYNX7pyPbQ==", + "license": "MIT", + "dependencies": { + "@shikijs/engine-javascript": "1.27.2", + "@shikijs/engine-oniguruma": "1.27.2", + "@shikijs/types": "1.27.2", + "@shikijs/vscode-textmate": "^10.0.1", "@types/hast": "^3.0.4", "hast-util-to-html": "^9.0.4" } }, "node_modules/@shikijs/engine-javascript": { - "version": "1.24.4", - "resolved": "https://registry.npmjs.org/@shikijs/engine-javascript/-/engine-javascript-1.24.4.tgz", - "integrity": "sha512-TClaQOLvo9WEMJv6GoUsykQ6QdynuKszuORFWCke8qvi6PeLm7FcD9+7y45UenysxEWYpDL5KJaVXTngTE+2BA==", + "version": "1.27.2", + "resolved": "https://registry.npmjs.org/@shikijs/engine-javascript/-/engine-javascript-1.27.2.tgz", + "integrity": "sha512-0JB7U5vJc16NShBdxv9hSSJYSKX79+32O7F4oXIxJLdYfomyFvx4B982ackUI9ftO9T3WwagkiiD3nOxOOLiGA==", + "license": "MIT", "dependencies": { - "@shikijs/types": "1.24.4", - "@shikijs/vscode-textmate": "^9.3.1", - "oniguruma-to-es": "0.8.1" + "@shikijs/types": "1.27.2", + "@shikijs/vscode-textmate": "^10.0.1", + "oniguruma-to-es": "^2.0.0" } }, "node_modules/@shikijs/engine-oniguruma": { - "version": "1.24.4", - "resolved": "https://registry.npmjs.org/@shikijs/engine-oniguruma/-/engine-oniguruma-1.24.4.tgz", - "integrity": "sha512-Do2ry6flp2HWdvpj2XOwwa0ljZBRy15HKZITzPcNIBOGSeprnA8gOooA/bLsSPuy8aJBa+Q/r34dMmC3KNL/zw==", + "version": "1.27.2", + "resolved": "https://registry.npmjs.org/@shikijs/engine-oniguruma/-/engine-oniguruma-1.27.2.tgz", + "integrity": "sha512-FZYKD1KN7srvpkz4lbGLOYWlyDU4Rd+2RtuKfABTkafAPOFr+J6umfIwY/TzOQqfNtWjL7SAwPAO0dcOraRLaQ==", + "license": "MIT", "dependencies": { - "@shikijs/types": "1.24.4", - "@shikijs/vscode-textmate": "^9.3.1" + "@shikijs/types": "1.27.2", + "@shikijs/vscode-textmate": "^10.0.1" + } + }, + "node_modules/@shikijs/langs": { + "version": "1.27.2", + "resolved": "https://registry.npmjs.org/@shikijs/langs/-/langs-1.27.2.tgz", + "integrity": "sha512-MSrknKL0DbeXvhtSigMLIzjPOOQfvK7fsbcRv2NUUB0EvuTTomY8/U+lAkczYrXY2+dygKOapJKk8ScFYbtoNw==", + "license": "MIT", + "dependencies": { + "@shikijs/types": "1.27.2" + } + }, + "node_modules/@shikijs/themes": { + "version": "1.27.2", + "resolved": "https://registry.npmjs.org/@shikijs/themes/-/themes-1.27.2.tgz", + "integrity": "sha512-Yw/uV7EijjWavIIZLoWneTAohcbBqEKj6XMX1bfMqO3llqTKsyXukPp1evf8qPqzUHY7ibauqEaQchhfi857mg==", + "license": "MIT", + "dependencies": { + "@shikijs/types": "1.27.2" } }, "node_modules/@shikijs/types": { - "version": "1.24.4", - "resolved": "https://registry.npmjs.org/@shikijs/types/-/types-1.24.4.tgz", - "integrity": "sha512-0r0XU7Eaow0PuDxuWC1bVqmWCgm3XqizIaT7SM42K03vc69LGooT0U8ccSR44xP/hGlNx4FKhtYpV+BU6aaKAA==", + "version": "1.27.2", + "resolved": "https://registry.npmjs.org/@shikijs/types/-/types-1.27.2.tgz", + "integrity": "sha512-DM9OWUyjmdYdnKDpaGB/GEn9XkToyK1tqxuqbmc5PV+5K8WjjwfygL3+cIvbkSw2v1ySwHDgqATq/+98pJ4Kyg==", + "license": "MIT", "dependencies": { - "@shikijs/vscode-textmate": "^9.3.1", + "@shikijs/vscode-textmate": "^10.0.1", "@types/hast": "^3.0.4" } }, "node_modules/@shikijs/vscode-textmate": { - "version": "9.3.1", - "resolved": "https://registry.npmjs.org/@shikijs/vscode-textmate/-/vscode-textmate-9.3.1.tgz", - "integrity": "sha512-79QfK1393x9Ho60QFyLti+QfdJzRQCVLFb97kOIV7Eo9vQU/roINgk7m24uv0a7AUvN//RDH36FLjjK48v0s9g==" + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/@shikijs/vscode-textmate/-/vscode-textmate-10.0.1.tgz", + "integrity": "sha512-fTIQwLF+Qhuws31iw7Ncl1R3HUDtGwIipiJ9iU+UsDUwMhegFcQKQHd51nZjb7CArq0MvON8rbgCGQYWHUKAdg==", + "license": "MIT" }, "node_modules/@types/debug": { "version": "4.1.12", @@ -476,10 +526,11 @@ "license": "MIT" }, "node_modules/@types/node": { - "version": "22.10.3", - "resolved": "https://registry.npmjs.org/@types/node/-/node-22.10.3.tgz", - "integrity": "sha512-DifAyw4BkrufCILvD3ucnuN8eydUfc/C1GlyrnI+LK6543w5/L3VeVgf05o3B4fqSXP1dKYLOZsKfutpxPzZrw==", + "version": "22.10.7", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.10.7.tgz", + "integrity": "sha512-V09KvXxFiutGp6B7XkpaDXlNadZxrzajcY50EuoLIpQ6WWYCSvf19lVIazzfIzQvhUN2HjX12spLojTnhuKlGg==", "dev": true, + "license": "MIT", "dependencies": { "undici-types": "~6.20.0" } @@ -540,6 +591,7 @@ "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-7.0.0.tgz", "integrity": "sha512-GdYO7a61mR0fOlAsvC9/rIHf7L96sBc6dEWzeOu+KAea5bZyQRPIpojrVoI4AXGJS/ycu/fBTdLrUkA4ODrvjw==", "dev": true, + "license": "MIT", "dependencies": { "environment": "^1.0.0" }, @@ -737,6 +789,7 @@ "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-5.0.0.tgz", "integrity": "sha512-aCj4O5wKyszjMmDT4tZj93kxyydN/K5zPWSCe6/0AV/AA1pqe5ZBIw0a2ZfPQV7lL5/yb5HsUreJ6UFAF1tEQw==", "dev": true, + "license": "MIT", "dependencies": { "restore-cursor": "^5.0.0" }, @@ -752,6 +805,7 @@ "resolved": "https://registry.npmjs.org/cli-truncate/-/cli-truncate-4.0.0.tgz", "integrity": "sha512-nPdaFdQ0h/GEigbPClz11D0v/ZJEwxmeVZGeMo3Z5StPtUTkA9o1lD6QwoirYiSDzbcwn2XcjwmCp68W1IS4TA==", "dev": true, + "license": "MIT", "dependencies": { "slice-ansi": "^5.0.0", "string-width": "^7.0.0" @@ -768,6 +822,7 @@ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz", "integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==", "dev": true, + "license": "MIT", "engines": { "node": ">=12" }, @@ -779,13 +834,15 @@ "version": "10.4.0", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.4.0.tgz", "integrity": "sha512-EC+0oUMY1Rqm4O6LLrgjtYDvcVYTy7chDnM4Q7030tP4Kwj3u/pR6gP9ygnp2CJMK5Gq+9Q2oqmrFJAz01DXjw==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/cli-truncate/node_modules/string-width": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/string-width/-/string-width-7.2.0.tgz", "integrity": "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==", "dev": true, + "license": "MIT", "dependencies": { "emoji-regex": "^10.3.0", "get-east-asian-width": "^1.0.0", @@ -803,6 +860,7 @@ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", "dev": true, + "license": "MIT", "dependencies": { "ansi-regex": "^6.0.1" }, @@ -835,7 +893,8 @@ "version": "2.0.20", "resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.20.tgz", "integrity": "sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/comma-separated-tokens": { "version": "2.0.3", @@ -851,6 +910,7 @@ "version": "13.0.0", "resolved": "https://registry.npmjs.org/commander/-/commander-13.0.0.tgz", "integrity": "sha512-oPYleIY8wmTVzkvQq10AEok6YcTC4sRUBl8F9gVuwchGVUCTbl/vhLTaQqutuuySYOsu8YTgV+OxKc/8Yvx+mQ==", + "license": "MIT", "engines": { "node": ">=18" } @@ -876,6 +936,7 @@ "version": "7.0.6", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "license": "MIT", "dependencies": { "path-key": "^3.1.0", "shebang-command": "^2.0.0", @@ -905,6 +966,7 @@ "version": "4.4.0", "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz", "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==", + "license": "MIT", "dependencies": { "ms": "^2.1.3" }, @@ -984,7 +1046,8 @@ "node_modules/emoji-regex-xs": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/emoji-regex-xs/-/emoji-regex-xs-1.0.0.tgz", - "integrity": "sha512-LRlerrMYoIDrT6jgpeZ2YYl/L8EulRTt5hQcYjy5AInh7HWXKimpqx68aknBFpGL2+/IcogTcaydJEgaTmOpDg==" + "integrity": "sha512-LRlerrMYoIDrT6jgpeZ2YYl/L8EulRTt5hQcYjy5AInh7HWXKimpqx68aknBFpGL2+/IcogTcaydJEgaTmOpDg==", + "license": "MIT" }, "node_modules/entities": { "version": "4.5.0", @@ -1003,6 +1066,7 @@ "resolved": "https://registry.npmjs.org/environment/-/environment-1.1.0.tgz", "integrity": "sha512-xUtoPkMggbz0MPyPiIWr1Kp4aeWJjDZ6SMvURhimjdZgsRuDplF5/s9hcgGhyXMhs+6vpnuoiZ2kFiu3FMnS8Q==", "dev": true, + "license": "MIT", "engines": { "node": ">=18" }, @@ -1011,9 +1075,9 @@ } }, "node_modules/es-module-lexer": { - "version": "1.5.4", - "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.5.4.tgz", - "integrity": "sha512-MVNK56NiMrOwitFB7cqDwq0CQutbw+0BvLshJSse0MUNU+y1FC3bUS/AQg7oUng+/wKrrki7JfmwtVHkVfPLlw==", + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.6.0.tgz", + "integrity": "sha512-qqnD1yMU6tk/jnaMosogGySTZP8YtUgAffA9nMN+E/rjxcfRQ6IEk7IiozUjgxKoFHBGjTLnrHB/YC45r/59EQ==", "dev": true, "license": "MIT" }, @@ -1031,18 +1095,19 @@ } }, "node_modules/eslint": { - "version": "9.17.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.17.0.tgz", - "integrity": "sha512-evtlNcpJg+cZLcnVKwsai8fExnqjGPicK7gnUtlNuzu+Fv9bI0aLpND5T44VLQtoMEnI57LoXO9XAkIXwohKrA==", + "version": "9.18.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.18.0.tgz", + "integrity": "sha512-+waTfRWQlSbpt3KWE+CjrPPYnbq9kfZIYUqapc0uBXyjTp8aYXZDsUH16m39Ryq3NjAVP4tjuF7KaukeqoCoaA==", "dev": true, + "license": "MIT", "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.12.1", "@eslint/config-array": "^0.19.0", - "@eslint/core": "^0.9.0", + "@eslint/core": "^0.10.0", "@eslint/eslintrc": "^3.2.0", - "@eslint/js": "9.17.0", - "@eslint/plugin-kit": "^0.2.3", + "@eslint/js": "9.18.0", + "@eslint/plugin-kit": "^0.2.5", "@humanfs/node": "^0.16.6", "@humanwhocodes/module-importer": "^1.0.1", "@humanwhocodes/retry": "^0.4.1", @@ -1103,10 +1168,11 @@ } }, "node_modules/eslint-plugin-jsdoc": { - "version": "50.6.1", - "resolved": "https://registry.npmjs.org/eslint-plugin-jsdoc/-/eslint-plugin-jsdoc-50.6.1.tgz", - "integrity": "sha512-UWyaYi6iURdSfdVVqvfOs2vdCVz0J40O/z/HTsv2sFjdjmdlUI/qlKLOTmwbPQ2tAfQnE5F9vqx+B+poF71DBQ==", + "version": "50.6.2", + "resolved": "https://registry.npmjs.org/eslint-plugin-jsdoc/-/eslint-plugin-jsdoc-50.6.2.tgz", + "integrity": "sha512-n7GNZ4czMAAbDg7DsDA7PvHo1IPIUwAXYmxTx6j/hTlXbt5V0x5q/kGkiJ7s4wA9SpB/yaiK8jF7CO237lOLew==", "dev": true, + "license": "BSD-3-Clause", "dependencies": { "@es-joy/jsdoccomment": "~0.49.0", "are-docs-informative": "^0.0.2", @@ -1225,7 +1291,8 @@ "version": "5.0.1", "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.1.tgz", "integrity": "sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/execa": { "version": "8.0.1", @@ -1363,6 +1430,7 @@ "resolved": "https://registry.npmjs.org/get-east-asian-width/-/get-east-asian-width-1.3.0.tgz", "integrity": "sha512-vpeMIQKxczTD/0s2CdEWHcb0eeJe6TFjxb+J5xgX7hScxqrGuyjmv4c1D4A/gelKfyox0gJJwIHF+fLjeaM8kQ==", "dev": true, + "license": "MIT", "engines": { "node": ">=18" }, @@ -1454,6 +1522,7 @@ "resolved": "https://registry.npmjs.org/globals/-/globals-15.14.0.tgz", "integrity": "sha512-OkToC372DtlQeje9/zHIo5CT8lRP/FUgEOKBEhU4e0abL7J7CD24fD9ohiLN5hagG/kWCYj4K5oaxxtj2Z0Dig==", "dev": true, + "license": "MIT", "engines": { "node": ">=18" }, @@ -1488,6 +1557,7 @@ "version": "9.0.4", "resolved": "https://registry.npmjs.org/hast-util-to-html/-/hast-util-to-html-9.0.4.tgz", "integrity": "sha512-wxQzXtdbhiwGAUKrnQJXlOPmHnEehzphwkK7aluUPQ+lEc1xefC8pblMgpp2w5ldBTEfveRIrADcrhGIWrlTDA==", + "license": "MIT", "dependencies": { "@types/hast": "^3.0.0", "@types/unist": "^3.0.0", @@ -1665,6 +1735,7 @@ "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-4.0.0.tgz", "integrity": "sha512-O4L094N2/dZ7xqVdrXhh9r1KODPJpFms8B5sGdJLPy664AgvXsreZUyCQQNItZRDlYug4xStLjNp/sz3HvBowQ==", "dev": true, + "license": "MIT", "engines": { "node": ">=12" }, @@ -1817,6 +1888,7 @@ "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.3.tgz", "integrity": "sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==", "dev": true, + "license": "MIT", "engines": { "node": ">=14" }, @@ -1825,10 +1897,11 @@ } }, "node_modules/lint-staged": { - "version": "15.3.0", - "resolved": "https://registry.npmjs.org/lint-staged/-/lint-staged-15.3.0.tgz", - "integrity": "sha512-vHFahytLoF2enJklgtOtCtIjZrKD/LoxlaUusd5nh7dWv/dkKQJY74ndFSzxCdv7g0ueGg1ORgTSt4Y9LPZn9A==", + "version": "15.4.1", + "resolved": "https://registry.npmjs.org/lint-staged/-/lint-staged-15.4.1.tgz", + "integrity": "sha512-P8yJuVRyLrm5KxCtFx+gjI5Bil+wO7wnTl7C3bXhvtTaAFGirzeB24++D0wGoUwxrUKecNiehemgCob9YL39NA==", "dev": true, + "license": "MIT", "dependencies": { "chalk": "~5.4.1", "commander": "~12.1.0", @@ -1856,6 +1929,7 @@ "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.4.1.tgz", "integrity": "sha512-zgVZuo2WcZgfUEmsn6eO3kINexW8RAE4maiQ8QNs8CtpPCSyMiYsULR3HQYkm3w8FIA3SberyMJMSldGsW+U3w==", "dev": true, + "license": "MIT", "engines": { "node": "^12.17.0 || ^14.13 || >=16.0.0" }, @@ -1868,6 +1942,7 @@ "resolved": "https://registry.npmjs.org/commander/-/commander-12.1.0.tgz", "integrity": "sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA==", "dev": true, + "license": "MIT", "engines": { "node": ">=18" } @@ -1877,6 +1952,7 @@ "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.6.1.tgz", "integrity": "sha512-7r0XPzioN/Q9kXBro/XPnA6kznR73DHq+GXh5ON7ZozRO6aMjbmiBuKste2wslTFkC5d1dw0GooOCepZXJ2SAg==", "dev": true, + "license": "ISC", "bin": { "yaml": "bin.mjs" }, @@ -1889,6 +1965,7 @@ "resolved": "https://registry.npmjs.org/listr2/-/listr2-8.2.5.tgz", "integrity": "sha512-iyAZCeyD+c1gPyE9qpFu8af0Y+MRtmKOncdGoA2S5EY8iFq99dmmvkNnHiWo+pj0s7yH7l3KPIgee77tKpXPWQ==", "dev": true, + "license": "MIT", "dependencies": { "cli-truncate": "^4.0.0", "colorette": "^2.0.20", @@ -1906,6 +1983,7 @@ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz", "integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==", "dev": true, + "license": "MIT", "engines": { "node": ">=12" }, @@ -1918,6 +1996,7 @@ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", "dev": true, + "license": "MIT", "engines": { "node": ">=12" }, @@ -1929,13 +2008,15 @@ "version": "10.4.0", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.4.0.tgz", "integrity": "sha512-EC+0oUMY1Rqm4O6LLrgjtYDvcVYTy7chDnM4Q7030tP4Kwj3u/pR6gP9ygnp2CJMK5Gq+9Q2oqmrFJAz01DXjw==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/listr2/node_modules/string-width": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/string-width/-/string-width-7.2.0.tgz", "integrity": "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==", "dev": true, + "license": "MIT", "dependencies": { "emoji-regex": "^10.3.0", "get-east-asian-width": "^1.0.0", @@ -1953,6 +2034,7 @@ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", "dev": true, + "license": "MIT", "dependencies": { "ansi-regex": "^6.0.1" }, @@ -1968,6 +2050,7 @@ "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-9.0.0.tgz", "integrity": "sha512-G8ura3S+3Z2G+mkgNRq8dqaFZAuxfsxpBB8OCTGRTCtp+l/v9nbFNmCUP1BZMts3G1142MsZfn6eeUKrr4PD1Q==", "dev": true, + "license": "MIT", "dependencies": { "ansi-styles": "^6.2.1", "string-width": "^7.0.0", @@ -2008,6 +2091,7 @@ "resolved": "https://registry.npmjs.org/log-update/-/log-update-6.1.0.tgz", "integrity": "sha512-9ie8ItPR6tjY5uYJh8K/Zrv/RMZ5VOlOWvtZdEHYSTFKZfIBPQa9tOAEeAWhd+AnIneLJ22w5fjOYtoutpWq5w==", "dev": true, + "license": "MIT", "dependencies": { "ansi-escapes": "^7.0.0", "cli-cursor": "^5.0.0", @@ -2027,6 +2111,7 @@ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz", "integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==", "dev": true, + "license": "MIT", "engines": { "node": ">=12" }, @@ -2039,6 +2124,7 @@ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", "dev": true, + "license": "MIT", "engines": { "node": ">=12" }, @@ -2050,13 +2136,15 @@ "version": "10.4.0", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.4.0.tgz", "integrity": "sha512-EC+0oUMY1Rqm4O6LLrgjtYDvcVYTy7chDnM4Q7030tP4Kwj3u/pR6gP9ygnp2CJMK5Gq+9Q2oqmrFJAz01DXjw==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/log-update/node_modules/is-fullwidth-code-point": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-5.0.0.tgz", "integrity": "sha512-OVa3u9kkBbw7b8Xw5F9P+D/T9X+Z4+JruYVNapTjPYZYUznQ5YfWeFkOj606XYYW8yugTfC8Pj0hYqvi4ryAhA==", "dev": true, + "license": "MIT", "dependencies": { "get-east-asian-width": "^1.0.0" }, @@ -2072,6 +2160,7 @@ "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-7.1.0.tgz", "integrity": "sha512-bSiSngZ/jWeX93BqeIAbImyTbEihizcwNjFoRUIY/T1wWQsfsm2Vw1agPKylXvQTU7iASGdHhyqRlqQzfz+Htg==", "dev": true, + "license": "MIT", "dependencies": { "ansi-styles": "^6.2.1", "is-fullwidth-code-point": "^5.0.0" @@ -2088,6 +2177,7 @@ "resolved": "https://registry.npmjs.org/string-width/-/string-width-7.2.0.tgz", "integrity": "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==", "dev": true, + "license": "MIT", "dependencies": { "emoji-regex": "^10.3.0", "get-east-asian-width": "^1.0.0", @@ -2105,6 +2195,7 @@ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", "dev": true, + "license": "MIT", "dependencies": { "ansi-regex": "^6.0.1" }, @@ -2120,6 +2211,7 @@ "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-9.0.0.tgz", "integrity": "sha512-G8ura3S+3Z2G+mkgNRq8dqaFZAuxfsxpBB8OCTGRTCtp+l/v9nbFNmCUP1BZMts3G1142MsZfn6eeUKrr4PD1Q==", "dev": true, + "license": "MIT", "dependencies": { "ansi-styles": "^6.2.1", "string-width": "^7.0.0", @@ -2993,6 +3085,7 @@ "resolved": "https://registry.npmjs.org/mimic-function/-/mimic-function-5.0.1.tgz", "integrity": "sha512-VP79XUPxV2CigYP3jWwAUFSku2aKqBH7uTAapFWCBqutsbmDo96KY5o8uh6U+/YSIn5OxJnXp73beVkpqMIGhA==", "dev": true, + "license": "MIT", "engines": { "node": ">=18" }, @@ -3025,7 +3118,8 @@ "node_modules/ms": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" }, "node_modules/natural-compare": { "version": "1.4.0", @@ -3102,13 +3196,14 @@ } }, "node_modules/oniguruma-to-es": { - "version": "0.8.1", - "resolved": "https://registry.npmjs.org/oniguruma-to-es/-/oniguruma-to-es-0.8.1.tgz", - "integrity": "sha512-dekySTEvCxCj0IgKcA2uUCO/e4ArsqpucDPcX26w9ajx+DvMWLc5eZeJaRQkd7oC/+rwif5gnT900tA34uN9Zw==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/oniguruma-to-es/-/oniguruma-to-es-2.1.0.tgz", + "integrity": "sha512-Iq/949c5IueVC5gQR7OYXs0uHsDIePcgZFlVRIVGfQcWwbKG+nsyWfthswdytShlRdkZADY+bWSi+BRyUL81gA==", + "license": "MIT", "dependencies": { "emoji-regex-xs": "^1.0.0", - "regex": "^5.0.2", - "regex-recursion": "^5.0.0" + "regex": "^5.1.1", + "regex-recursion": "^5.1.1" } }, "node_modules/optionator": { @@ -3249,6 +3344,12 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "license": "ISC" + }, "node_modules/picomatch": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", @@ -3290,6 +3391,7 @@ "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.4.2.tgz", "integrity": "sha512-e9MewbtFo+Fevyuxn/4rrcDAaq0IYxPGLvObpQjiZBMAzB9IGmzlnG9RZy3FFas+eBMu2vA0CszMeduow5dIuQ==", "dev": true, + "license": "MIT", "bin": { "prettier": "bin/prettier.cjs" }, @@ -3324,6 +3426,7 @@ "version": "5.1.1", "resolved": "https://registry.npmjs.org/regex/-/regex-5.1.1.tgz", "integrity": "sha512-dN5I359AVGPnwzJm2jN1k0W9LPZ+ePvoOeVMMfqIMFz53sSwXkxaJoxr50ptnsC771lK95BnTrVSZxq0b9yCGw==", + "license": "MIT", "dependencies": { "regex-utilities": "^2.3.0" } @@ -3332,6 +3435,7 @@ "version": "5.1.1", "resolved": "https://registry.npmjs.org/regex-recursion/-/regex-recursion-5.1.1.tgz", "integrity": "sha512-ae7SBCbzVNrIjgSbh7wMznPcQel1DNlDtzensnFxpiNpXt1U2ju/bHugH422r+4LAVS1FpW1YCwilmnNsjum9w==", + "license": "MIT", "dependencies": { "regex": "^5.1.1", "regex-utilities": "^2.3.0" @@ -3340,7 +3444,8 @@ "node_modules/regex-utilities": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/regex-utilities/-/regex-utilities-2.3.0.tgz", - "integrity": "sha512-8VhliFJAWRaUiVvREIiW2NXXTmHs4vMNnSzuJVhscgmGav3g9VDxLrQndI3dZZVVdp0ZO/5v0xmX516/7M9cng==" + "integrity": "sha512-8VhliFJAWRaUiVvREIiW2NXXTmHs4vMNnSzuJVhscgmGav3g9VDxLrQndI3dZZVVdp0ZO/5v0xmX516/7M9cng==", + "license": "MIT" }, "node_modules/rehype-stringify": { "version": "10.0.1", @@ -3445,6 +3550,7 @@ "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-5.1.0.tgz", "integrity": "sha512-oMA2dcrw6u0YfxJQXm342bFKX/E4sG9rbTzO9ptUcR/e8A33cHuvStiYOwH7fszkZlZ1z/ta9AAoPk2F4qIOHA==", "dev": true, + "license": "MIT", "dependencies": { "onetime": "^7.0.0", "signal-exit": "^4.1.0" @@ -3461,6 +3567,7 @@ "resolved": "https://registry.npmjs.org/onetime/-/onetime-7.0.0.tgz", "integrity": "sha512-VXJjc87FScF88uafS3JllDgvAm+c/Slfz06lorj2uAY34rlUu0Nt+v8wreiImcrgAjjIHp1rXpTDlLOGw29WwQ==", "dev": true, + "license": "MIT", "dependencies": { "mimic-function": "^5.0.0" }, @@ -3475,7 +3582,8 @@ "version": "1.4.1", "resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.4.1.tgz", "integrity": "sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/semver": { "version": "7.6.3", @@ -3511,15 +3619,18 @@ } }, "node_modules/shiki": { - "version": "1.24.4", - "resolved": "https://registry.npmjs.org/shiki/-/shiki-1.24.4.tgz", - "integrity": "sha512-aVGSFAOAr1v26Hh/+GBIsRVDWJ583XYV7CuNURKRWh9gpGv4OdbisZGq96B9arMYTZhTQkmRF5BrShOSTvNqhw==", - "dependencies": { - "@shikijs/core": "1.24.4", - "@shikijs/engine-javascript": "1.24.4", - "@shikijs/engine-oniguruma": "1.24.4", - "@shikijs/types": "1.24.4", - "@shikijs/vscode-textmate": "^9.3.1", + "version": "1.27.2", + "resolved": "https://registry.npmjs.org/shiki/-/shiki-1.27.2.tgz", + "integrity": "sha512-QtA1C41oEVixKog+V8I3ia7jjGls7oCZ8Yul8vdHrVBga5uPoyTtMvFF4lMMXIyAZo5A5QbXq91bot2vA6Q+eQ==", + "license": "MIT", + "dependencies": { + "@shikijs/core": "1.27.2", + "@shikijs/engine-javascript": "1.27.2", + "@shikijs/engine-oniguruma": "1.27.2", + "@shikijs/langs": "1.27.2", + "@shikijs/themes": "1.27.2", + "@shikijs/types": "1.27.2", + "@shikijs/vscode-textmate": "^10.0.1", "@types/hast": "^3.0.4" } }, @@ -3535,6 +3646,12 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/sisteransi": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz", + "integrity": "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==", + "license": "MIT" + }, "node_modules/slashes": { "version": "3.0.12", "resolved": "https://registry.npmjs.org/slashes/-/slashes-3.0.12.tgz", @@ -3547,6 +3664,7 @@ "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-5.0.0.tgz", "integrity": "sha512-FC+lgizVPfie0kkhqUScwRu1O/lF6NOgJmlCgK+/LYxDCTk8sGelYaHDhFcDN+Sn3Cv+3VSa4Byeo+IMCzpMgQ==", "dev": true, + "license": "MIT", "dependencies": { "ansi-styles": "^6.0.0", "is-fullwidth-code-point": "^4.0.0" @@ -3563,6 +3681,7 @@ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", "dev": true, + "license": "MIT", "engines": { "node": ">=12" }, @@ -3618,9 +3737,9 @@ } }, "node_modules/spdx-license-ids": { - "version": "3.0.20", - "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.20.tgz", - "integrity": "sha512-jg25NiDV/1fLtSgEgyvVyDunvaNHbuwF9lfNV17gSmPFAlYzdfNBlLtLzXTevwkPj7DhGbmN9VnmJIgLnhvaBw==", + "version": "3.0.21", + "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.21.tgz", + "integrity": "sha512-Bvg/8F5XephndSK3JffaRqdT+gyhfqIPwDHpX80tJrF8QQRYMo8sNMeaZ2Dp5+jhwKnUmIOyFFQfHRkjJm5nXg==", "dev": true, "license": "CC0-1.0" }, @@ -4201,6 +4320,7 @@ "version": "2.7.0", "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.7.0.tgz", "integrity": "sha512-+hSoy/QHluxmC9kCIJyL/uyFmLmc+e5CFR5Wa+bpIhIj85LVb9ZH2nVnqrHoSvKogwODv0ClqZkmiSSaIH5LTA==", + "license": "ISC", "bin": { "yaml": "bin.mjs" }, diff --git a/package.json b/package.json index 71f51fb3..4ec5aec8 100644 --- a/package.json +++ b/package.json @@ -17,31 +17,34 @@ "api-docs-tooling": "./bin/cli.mjs" }, "devDependencies": { - "@eslint/js": "^9.17.0", + "@eslint/js": "^9.21.0", "@types/mdast": "^4.0.4", - "@types/node": "^22.10.3", - "eslint": "^9.17.0", - "eslint-config-prettier": "^9.1.0", - "eslint-plugin-jsdoc": "^50.6.1", - "globals": "^15.14.0", + "@types/node": "^22.13.8", + "eslint": "^9.21.0", + "eslint-config-prettier": "^10.0.2", + "eslint-plugin-jsdoc": "^50.6.3", + "globals": "^16.0.0", "husky": "^9.1.7", - "lint-staged": "^15.3.0", - "prettier": "3.4.2" + "lint-staged": "^15.4.3", + "prettier": "3.5.2" }, "dependencies": { - "commander": "^13.0.0", + "acorn": "^8.14.0", + "commander": "^13.1.0", + "estree-util-visit": "^2.0.0", + "dedent": "^1.5.3", "github-slugger": "^2.0.0", - "glob": "^11.0.0", + "glob": "^11.0.1", "hast-util-to-string": "^3.0.1", - "hastscript": "^9.0.0", + "hastscript": "^9.0.1", "html-minifier-terser": "^7.2.0", "rehype-stringify": "^10.0.1", - "remark-gfm": "^4.0.0", + "remark-gfm": "^4.0.1", "remark-parse": "^11.0.0", "remark-rehype": "^11.1.1", "remark-stringify": "^11.0.0", - "semver": "^7.6.3", - "shiki": "^1.24.4", + "semver": "^7.7.1", + "shiki": "^2.2.0", "unified": "^11.0.5", "unist-builder": "^4.0.0", "unist-util-find-after": "^5.0.0", diff --git a/src/constants.mjs b/src/constants.mjs index 5bb7f0b9..803bd2db 100644 --- a/src/constants.mjs +++ b/src/constants.mjs @@ -125,27 +125,17 @@ export const DOC_SLUG_ENVIRONMENT = 'environment-variables-1'; // JavaScript globals types within the MDN JavaScript docs // @see DOC_MDN_BASE_URL_JS_GLOBALS export const DOC_TYPES_MAPPING_GLOBALS = { - AggregateError: 'AggregateError', - Array: 'Array', - ArrayBuffer: 'ArrayBuffer', - DataView: 'DataView', - Date: 'Date', - Error: 'Error', - EvalError: 'EvalError', - Function: 'Function', - Map: 'Map', - Object: 'Object', - Promise: 'Promise', - RangeError: 'RangeError', - ReferenceError: 'ReferenceError', - RegExp: 'RegExp', - Set: 'Set', - SharedArrayBuffer: 'SharedArrayBuffer', - SyntaxError: 'SyntaxError', - TypeError: 'TypeError', - TypedArray: 'TypedArray', - URIError: 'URIError', - Uint8Array: 'Uint8Array', + ...Object.fromEntries([ + 'AggregateError', 'Array', 'ArrayBuffer', 'DataView', 'Date', 'Error', + 'EvalError', 'Function', 'Map', 'NaN', 'Object', 'Promise', 'Proxy', 'RangeError', + 'ReferenceError', 'RegExp', 'Set', 'SharedArrayBuffer', 'SyntaxError', 'Symbol', + 'TypeError', 'URIError', 'WeakMap', 'WeakSet', + + 'TypedArray', + 'Float32Array', 'Float64Array', + 'Int8Array', 'Int16Array', 'Int32Array', + 'Uint8Array', 'Uint8ClampedArray', 'Uint16Array', 'Uint32Array', + ].map(e => [e, e])), bigint: 'BigInt', 'WebAssembly.Instance': 'WebAssembly/Instance', }; diff --git a/src/generators.mjs b/src/generators.mjs index 1e1c3453..931f5834 100644 --- a/src/generators.mjs +++ b/src/generators.mjs @@ -1,10 +1,20 @@ 'use strict'; -import availableGenerators from './generators/index.mjs'; +import publicGenerators from './generators/index.mjs'; +import astJs from './generators/ast-js/index.mjs'; + +const availableGenerators = { + ...publicGenerators, + // This one is a little special since we don't want it to run unless we need + // it and we also don't want it to be publicly accessible through the CLI. + 'ast-js': astJs, +}; /** * @typedef {{ ast: import('./generators/types.d.ts').GeneratorMetadata}} AstGenerator The AST "generator" is a facade for the AST tree and it isn't really a generator * @typedef {import('./generators/types.d.ts').AvailableGenerators & AstGenerator} AllGenerators A complete set of the available generators, including the AST one + * @param markdownInput + * @param jsInput * * This method creates a system that allows you to register generators * and then execute them in a specific order, keeping track of the @@ -18,9 +28,10 @@ import availableGenerators from './generators/index.mjs'; * Generators can also write to files. These would usually be considered * the final generators in the chain. * - * @param {ApiDocMetadataEntry} input The parsed API doc metadata entries + * @param {ApiDocMetadataEntry} markdownInput The parsed API doc metadata entries + * @param {Array} parsedJsFiles */ -const createGenerator = input => { +const createGenerator = markdownInput => { /** * We store all the registered generators to be processed * within a Record, so we can access their results at any time whenever needed @@ -28,7 +39,9 @@ const createGenerator = input => { * * @type {{ [K in keyof AllGenerators]: ReturnType }} */ - const cachedGenerators = { ast: Promise.resolve(input) }; + const cachedGenerators = { + ast: Promise.resolve(markdownInput), + }; /** * Runs the Generator engine with the provided top-level input and the given generator options diff --git a/src/generators/addon-verify/index.mjs b/src/generators/addon-verify/index.mjs index 4c9d3712..df242d74 100644 --- a/src/generators/addon-verify/index.mjs +++ b/src/generators/addon-verify/index.mjs @@ -5,18 +5,13 @@ import { join } from 'node:path'; import { visit } from 'unist-util-visit'; -import { updateFilesForBuild } from './utils/updateFilesForBuild.mjs'; +import { generateFileList } from './utils/generateFileList.mjs'; import { EXTRACT_CODE_FILENAME_COMMENT } from './constants.mjs'; - -/** - * Normalizes a section name. - * - * @param {string} sectionName Section name - * @returns {string} - */ -export function normalizeSectionName(sectionName) { - return sectionName.toLowerCase().replace(/\s/g, '_').replace(/\W/g, ''); -} +import { + generateSectionFolderName, + isBuildableSection, + normalizeSectionName, +} from './utils/section.mjs'; /** * This generator generates a file list from code blocks extracted from @@ -73,31 +68,26 @@ export default { const files = await Promise.all( Object.entries(sectionsCodeBlocks) - .filter(([, files]) => { - // Must have a .cc and a .js to be a valid test. - return ( - files.some(file => file.name.endsWith('.cc')) && - files.some(file => file.name.endsWith('.js')) - ); - }) - .flatMap(async ([sectionName, files], index) => { - const newFiles = updateFilesForBuild(files); + .filter(([, codeBlocks]) => isBuildableSection(codeBlocks)) + .flatMap(async ([sectionName, codeBlocks], index) => { + const files = generateFileList(codeBlocks); if (output) { const normalizedSectionName = normalizeSectionName(sectionName); - const identifier = String(index + 1).padStart(2, '0'); - - const folder = `${identifier}_${normalizedSectionName}`; + const folderName = generateSectionFolderName( + normalizedSectionName, + index + ); - await mkdir(join(output, folder), { recursive: true }); + await mkdir(join(output, folderName), { recursive: true }); - newFiles.forEach(async ({ name, content }) => { - await writeFile(join(output, folder, name), content); + files.forEach(async ({ name, content }) => { + await writeFile(join(output, folderName, name), content); }); } - return newFiles; + return files; }) ); diff --git a/src/generators/addon-verify/utils/generateFileList.mjs b/src/generators/addon-verify/utils/generateFileList.mjs new file mode 100644 index 00000000..14c56c67 --- /dev/null +++ b/src/generators/addon-verify/utils/generateFileList.mjs @@ -0,0 +1,60 @@ +import dedent from 'dedent'; + +/** + * Updates JavaScript files with correct require paths for the build tree. + * + * @param {string} content Original code + * @returns {string} + */ +const updateJsRequirePaths = content => { + return dedent` + 'use strict'; + const common = require('../../common'); + ${content.replace( + "'./build/Release/addon'", + '`./build/${common.buildType}/addon`' + )}`; +}; + +/** + * Creates a binding.gyp configuration for C++ addon compilation. + * + * @param {string[]} sourceFiles List of source file names + * @returns {string} + */ +const createBindingGyp = sourceFiles => { + const config = { + targets: [ + { + target_name: 'addon', + sources: sourceFiles, + includes: ['../common.gypi'], + }, + ], + }; + + return JSON.stringify(config); +}; + +/** + * Generates required files list from section's code blocks for C++ addon + * compilation. + * + * @param {{name: string, content: string}[]} codeBlocks Array of code blocks + * @returns {{name: string, content: string}[]} + */ +export const generateFileList = codeBlocks => { + const files = codeBlocks.map(({ name, content }) => { + return { + name, + content: name === 'test.js' ? updateJsRequirePaths(content) : content, + }; + }); + + files.push({ + name: 'binding.gyp', + content: createBindingGyp(files.map(({ name }) => name)), + }); + + return files; +}; diff --git a/src/generators/addon-verify/utils/section.mjs b/src/generators/addon-verify/utils/section.mjs new file mode 100644 index 00000000..2b622e4b --- /dev/null +++ b/src/generators/addon-verify/utils/section.mjs @@ -0,0 +1,37 @@ +/** + * Checks if a section contains the required code blocks for building. + * A buildable section must contain at least one C++ (.cc) file and one + * JavaScript (.js) file. + * + * @param {Array<{name: string; content: string}>} codeBlocks Array of code blocks + * @returns {boolean} + */ +export const isBuildableSection = codeBlocks => { + return ( + codeBlocks.some(codeBlock => codeBlock.name.endsWith('.cc')) && + codeBlocks.some(codeBlock => codeBlock.name.endsWith('.js')) + ); +}; + +/** + * Normalizes a section name. + * + * @param {string} sectionName Original section name + * @returns {string} + */ +export const normalizeSectionName = sectionName => { + return sectionName.toLowerCase().replace(/\s/g, '_').replace(/\W/g, ''); +}; + +/** + * Generates a standardized folder name for a section. + * + * @param {string} sectionName Normalized section name + * @param {number} index Zero-based section index + * @returns {string} + */ +export const generateSectionFolderName = (sectionName, index) => { + const identifier = String(index + 1).padStart(2, '0'); + + return `${identifier}_${sectionName}`; +}; diff --git a/src/generators/addon-verify/utils/updateFilesForBuild.mjs b/src/generators/addon-verify/utils/updateFilesForBuild.mjs deleted file mode 100644 index 3ab57ac8..00000000 --- a/src/generators/addon-verify/utils/updateFilesForBuild.mjs +++ /dev/null @@ -1,41 +0,0 @@ -/** - * Updates JavaScript files with correct require paths for the build tree - * and generates a `binding.gyp` file to compile C++ code. - * - * @param {{name: string, content: string}[]} files An array of file objects - * @returns {{name: string, content: string}[]} - */ -export const updateFilesForBuild = files => { - const updatedFiles = files.map(({ name, content }) => { - if (name === 'test.js') { - content = `'use strict'; -const common = require('../../common'); -${content.replace( - "'./build/Release/addon'", - - '`./build/${common.buildType}/addon`' -)} - `; - } - - return { - name: name, - content: content, - }; - }); - - updatedFiles.push({ - name: 'binding.gyp', - content: JSON.stringify({ - targets: [ - { - target_name: 'addon', - sources: files.map(({ name }) => name), - includes: ['../common.gypi'], - }, - ], - }), - }); - - return updatedFiles; -}; diff --git a/src/generators/api-links/constants.mjs b/src/generators/api-links/constants.mjs new file mode 100644 index 00000000..bff60720 --- /dev/null +++ b/src/generators/api-links/constants.mjs @@ -0,0 +1,4 @@ +'use strict'; + +// Checks if a string is a valid name for a constructor in JavaScript +export const CONSTRUCTOR_EXPRESSION = /^[A-Z]/; diff --git a/src/generators/api-links/index.mjs b/src/generators/api-links/index.mjs new file mode 100644 index 00000000..b0b00640 --- /dev/null +++ b/src/generators/api-links/index.mjs @@ -0,0 +1,105 @@ +'use strict'; + +import { basename, dirname, join } from 'node:path'; +import { writeFile } from 'node:fs/promises'; +import { + getBaseGitHubUrl, + getCurrentGitHash, +} from './utils/getBaseGitHubUrl.mjs'; +import { extractExports } from './utils/extractExports.mjs'; +import { findDefinitions } from './utils/findDefinitions.mjs'; +import { checkIndirectReferences } from './utils/checkIndirectReferences.mjs'; + +/** + * This generator is responsible for mapping publicly accessible functions in + * Node.js to their source locations in the Node.js repository. + * + * This is a top-level generator. It takes in the raw AST tree of the JavaScript + * source files. It outputs a `apilinks.json` file into the specified output + * directory. + * + * @typedef {Array} Input + * + * @type {import('../types.d.ts').GeneratorMetadata>} + */ +export default { + name: 'api-links', + + version: '1.0.0', + + description: + 'Creates a mapping of publicly accessible functions to their source locations in the Node.js repository.', + + // Unlike the rest of the generators, this utilizes Javascript sources being + // passed into the input field rather than Markdown. + dependsOn: 'ast-js', + + /** + * Generates the `apilinks.json` file. + * + * @param {Input} input + * @param {Partial} options + */ + async generate(input, { output }) { + /** + * @type Record + */ + const definitions = {}; + + /** + * @type {string} + */ + let baseGithubLink; + + if (input.length > 0) { + const repositoryDirectory = dirname(input[0].path); + + const repository = getBaseGitHubUrl(repositoryDirectory); + + const tag = getCurrentGitHash(repositoryDirectory); + + baseGithubLink = `${repository}/blob/${tag}`; + } + + input.forEach(program => { + /** + * Mapping of definitions to their line number + * @type {Record} + * @example { 'someclass.foo': 10 } + */ + const nameToLineNumberMap = {}; + + // `http.js` -> `http` + const programBasename = basename(program.path, '.js'); + + const exports = extractExports( + program, + programBasename, + nameToLineNumberMap + ); + + findDefinitions(program, programBasename, nameToLineNumberMap, exports); + + checkIndirectReferences(program, exports, nameToLineNumberMap); + + const githubLink = + `${baseGithubLink}/lib/${programBasename}.js`.replaceAll('\\', '/'); + + // Add the exports we found in this program to our output + Object.keys(nameToLineNumberMap).forEach(key => { + const lineNumber = nameToLineNumberMap[key]; + + definitions[key] = `${githubLink}#L${lineNumber}`; + }); + }); + + if (output) { + await writeFile( + join(output, 'apilinks.json'), + JSON.stringify(definitions) + ); + } + + return definitions; + }, +}; diff --git a/src/generators/api-links/types.d.ts b/src/generators/api-links/types.d.ts new file mode 100644 index 00000000..1fc8ea29 --- /dev/null +++ b/src/generators/api-links/types.d.ts @@ -0,0 +1,5 @@ +export interface ProgramExports { + ctors: Array; + identifiers: Array; + indirects: Record; +} diff --git a/src/generators/api-links/utils/checkIndirectReferences.mjs b/src/generators/api-links/utils/checkIndirectReferences.mjs new file mode 100644 index 00000000..982bb111 --- /dev/null +++ b/src/generators/api-links/utils/checkIndirectReferences.mjs @@ -0,0 +1,24 @@ +import { visit } from 'estree-util-visit'; + +/** + * @param {import('acorn').Program} program + * @param {import('../types.d.ts').ProgramExports} exports + * @param {Record} nameToLineNumberMap + */ +export function checkIndirectReferences(program, exports, nameToLineNumberMap) { + if (Object.keys(exports.indirects).length === 0) { + return; + } + + visit(program, node => { + if (!node.loc || node.type !== 'FunctionDeclaration') { + return; + } + + const name = node.id.name; + + if (name in exports.indirects) { + nameToLineNumberMap[exports.indirects[name]] = node.loc.start.line; + } + }); +} diff --git a/src/generators/api-links/utils/extractExports.mjs b/src/generators/api-links/utils/extractExports.mjs new file mode 100644 index 00000000..228eb5bc --- /dev/null +++ b/src/generators/api-links/utils/extractExports.mjs @@ -0,0 +1,265 @@ +'use strict'; + +import { visit } from 'estree-util-visit'; +import { CONSTRUCTOR_EXPRESSION } from '../constants.mjs'; + +/** + * @see https://github.com/estree/estree/blob/master/es5.md#assignmentexpression + * + * @param {import('acorn').ExpressionStatement} node + * @param {string} basename + * @param {Record} nameToLineNumberMap + * @returns {import('../types').ProgramExports | undefined} + */ +function handleExpression(node, basename, nameToLineNumberMap) { + const { expression } = node; + + if (expression.type !== 'AssignmentExpression') { + return; + } + + // `a=b`, lhs=`a` and rhs=`b` + let { left: lhs, right: rhs, loc } = expression; + + if (lhs.type !== 'MemberExpression') { + return undefined; + } + + if (lhs.object.type === 'MemberExpression') { + lhs = lhs.object; + } + + /** + * @type {import('../types').ProgramExports} + */ + const exports = { + ctors: [], + identifiers: [], + indirects: {}, + }; + + if (lhs.object.name === 'exports') { + // This is an assignment to a property in `module.exports` or `exports` + // (i.e. `module.exports.asd = ...`) + + switch (rhs.type) { + /** @see https://github.com/estree/estree/blob/master/es5.md#functionexpression */ + case 'FunctionExpression': { + // module.exports.something = () => {} + nameToLineNumberMap[`${basename}.${lhs.property.name}`] = + loc.start.line; + + break; + } + /** @see https://github.com/estree/estree/blob/master/es5.md#identifier */ + case 'Identifier': { + // Save this for later in case it's referenced + // module.exports.asd = something + if (rhs.name === lhs.property.name) { + exports.indirects[lhs.property.name] = + `${basename}.${lhs.property.name}`; + } + + break; + } + default: { + if (lhs.property.name !== undefined) { + // Something else, let's save it for when we're searching for + // declarations + exports.identifiers.push(lhs.property.name); + } + + break; + } + } + } else if (lhs.object.name === 'module' && lhs.property.name === 'exports') { + // This is an assignment to `module.exports` as a whole + // (i.e. `module.exports = {}`) + + // We need to move right until we find the value of the assignment. + // (if `a=b`, we want `b`) + while (rhs.type === 'AssignmentExpression') { + rhs = rhs.right; + } + + switch (rhs.type) { + /** @see https://github.com/estree/estree/blob/master/es5.md#newexpression */ + case 'NewExpression': { + // module.exports = new Asd() + exports.ctors.push(rhs.callee.name); + break; + } + /** @see https://github.com/estree/estree/blob/master/es5.md#objectexpression */ + case 'ObjectExpression': { + // module.exports = {} + // we need to go through all of the properties and register them + rhs.properties.forEach(({ value }) => { + switch (value.type) { + case 'Identifier': { + exports.identifiers.push(value.name); + + if (CONSTRUCTOR_EXPRESSION.test(value.name[0])) { + exports.ctors.push(value.name); + } + + break; + } + case 'CallExpression': { + if (value.callee.name !== 'deprecate') { + break; + } + + // Handle exports wrapped in the `deprecate` function + // Ex/ https://github.com/nodejs/node/blob/e96072ad57348ce423a8dd7639dcc3d1c34e847d/lib/buffer.js#L1334 + + exports.identifiers.push(value.arguments[0].name); + + break; + } + default: { + // Not relevant + } + } + }); + + break; + } + /** @see https://github.com/estree/estree/blob/master/es5.md#identifier */ + case 'Identifier': { + // Something else, let's save it for when we're searching for + // declarations + + if (rhs.name !== undefined) { + exports.identifiers.push(rhs.name); + } + + break; + } + default: { + // Not relevant + break; + } + } + } + + return exports; +} + +/** + * @see https://github.com/estree/estree/blob/master/es5.md#variabledeclaration + * + * @param {import('acorn').VariableDeclaration} node + * @param {string} basename + * @param {Record} nameToLineNumberMap + * @returns {import('../types').ProgramExports | undefined} + */ +function handleVariableDeclaration(node, basename, nameToLineNumberMap) { + /** + * @type {import('../types').ProgramExports} + */ + const exports = { + ctors: [], + identifiers: [], + indirects: {}, + }; + + node.declarations.forEach(({ init: lhs, id }) => { + while (lhs && lhs.type === 'AssignmentExpression') { + // Move left until we get to what we're assigning to + // (if `a=b`, we want `a`) + lhs = lhs.left; + } + + if (!lhs || lhs.type !== 'MemberExpression') { + // Doesn't exist or we're not writing to an object + // (aka it's just a regular variable like `const a = 123`) + return; + } + + switch (lhs.object.name) { + case 'exports': { + nameToLineNumberMap[`${basename}.${lhs.property.name}`] = + node.start.line; + + break; + } + case 'module': { + if (lhs.property.name !== 'exports') { + break; + } + + exports.ctors.push(id.name); + nameToLineNumberMap[id.name] = node.loc.start.line; + + break; + } + default: { + // Not relevant to us + break; + } + } + }); + + return exports; +} + +/** + * We need to find what a source file exports so we know what to include in + * the final result. We can do this by going through every statement in the + * program looking for assignments to `module.exports`. + * + * Noteworthy that exports can happen throughout the program so we need to + * go through the entire thing. + * + * @param {import('acorn').Program} program + * @param {string} basename + * @param {Record} nameToLineNumberMap + * @returns {import('../types').ProgramExports} + */ +export function extractExports(program, basename, nameToLineNumberMap) { + /** + * @type {import('../types').ProgramExports} + */ + const exports = { + ctors: [], + identifiers: [], + indirects: {}, + }; + + const TYPE_TO_HANDLER_MAP = { + /** + * @param {import('acorn').Node} node + */ + ExpressionStatement: node => + handleExpression(node, basename, nameToLineNumberMap), + + /** + * @param {import('acorn').Node} node + */ + VariableDeclaration: node => + handleVariableDeclaration(node, basename, nameToLineNumberMap), + }; + + visit(program, node => { + if (!node.loc) { + return; + } + + if (node.type in TYPE_TO_HANDLER_MAP) { + const handler = TYPE_TO_HANDLER_MAP[node.type]; + + const output = handler(node); + + if (output) { + exports.ctors.push(...output.ctors); + exports.identifiers.push(...output.identifiers); + + Object.keys(output.indirects).forEach(key => { + exports.indirects[key] = output.indirects[key]; + }); + } + } + }); + + return exports; +} diff --git a/src/generators/api-links/utils/findDefinitions.mjs b/src/generators/api-links/utils/findDefinitions.mjs new file mode 100644 index 00000000..a161e3ed --- /dev/null +++ b/src/generators/api-links/utils/findDefinitions.mjs @@ -0,0 +1,184 @@ +'use strict'; + +import { visit } from 'estree-util-visit'; + +/** + * @see https://github.com/estree/estree/blob/master/es5.md#expressionstatement + * + * @param {import('acorn').ExpressionStatement} node + * @param {Record} nameToLineNumberMap + * @param {import('../types').ProgramExports} exports + */ +function handleAssignmentExpression(node, nameToLineNumberMap, exports) { + const { expression } = node; + + if (expression.type !== 'AssignmentExpression') { + return; + } + + const { left: lhs, right: rhs } = expression; + + if (lhs.type !== 'MemberExpression') { + // Not an assignment to a member, not relevant to us + return; + } + + /** + * The property that's being written to + */ + let object; + + /** + * The lowercase name of the object that's being written to + */ + let objectName; + + switch (lhs.object.type) { + /** @see https://github.com/estree/estree/blob/master/es5.md#memberexpression */ + case 'MemberExpression': { + if (lhs.object.property.name !== 'prototype') { + return; + } + + // Something like `ClassName.prototype.asd = 123` + object = lhs.object.object; + + objectName = object.name ? object.name : object.object.name; + objectName = objectName.toLowerCase(); + + // Special case for buffer since some of the docs refer to it as `buf` + // https://github.com/nodejs/node/pull/22405#issuecomment-414452461 + if (objectName === 'buffer') { + objectName = 'buf'; + } + + break; + } + /** @see https://github.com/estree/estree/blob/master/es5.md#identifier */ + case 'Identifier': { + object = lhs.object; + objectName = object.name; + + break; + } + default: { + // Not relevant to us + return; + } + } + + if (!exports.ctors.includes(object.name)) { + // The object being written to isn't exported, not relevant to us + return; + } + + /** + * Name/key for this exported object that we're putting in the output + * @example `clientrequest._finish` + */ + const name = `${objectName}${lhs.computed ? `[${lhs.property.name}]` : `.${lhs.property.name}`}`; + + nameToLineNumberMap[name] = node.loc.start.line; + + if (lhs.property.name === rhs.name) { + exports.indirects[rhs.name] = name; + } +} + +/** + * @param {import('acorn').FunctionDeclaration} node + * @param {string} basename + * @param {Record} nameToLineNumberMap + * @param {import('../types').ProgramExports} exports + */ +function handleFunctionDeclaration( + node, + basename, + nameToLineNumberMap, + exports +) { + if (!exports.identifiers.includes(node.id.name)) { + // Function isn't exported, not relevant to us + return; + } + + if (basename.startsWith('_')) { + // Internal function, don't include it in the docs + return; + } + + nameToLineNumberMap[`${basename}.${node.id.name}`] = node.loc.start.line; +} + +/** + * @param {import('acorn').ClassDeclaration} node + * @param {Record} nameToLineNumberMap + * @param {import('../types').ProgramExports} exports + */ +function handleClassDeclaration(node, nameToLineNumberMap, exports) { + if (!exports.ctors.includes(node.id.name)) { + // Class isn't exported, not relevant to us + return; + } + + // WASI -> wASI, Agent -> agent + const name = node.id.name[0].toLowerCase() + node.id.name.substring(1); + + nameToLineNumberMap[node.id.name] = node.loc.start.line; + + node.body.body.forEach(({ key, type, kind, loc }) => { + if (!loc || type !== 'MethodDefinition') { + return; + } + + const outputKey = + kind === 'constructor' ? `new ${node.id.name}` : `${name}.${key.name}`; + + nameToLineNumberMap[outputKey] = loc.start.line; + }); +} + +/** + * @param {import('acorn').Program} program + * @param {string} basename + * @param {Record} nameToLineNumberMap + * @param {import('../types').ProgramExports} exports + */ +export function findDefinitions( + program, + basename, + nameToLineNumberMap, + exports +) { + const TYPE_TO_HANDLER_MAP = { + /** + * @param {import('acorn').Node} node + */ + ExpressionStatement: node => + handleAssignmentExpression(node, nameToLineNumberMap, exports), + + /** + * @param {import('acorn').Node} node + */ + FunctionDeclaration: node => + handleFunctionDeclaration(node, basename, nameToLineNumberMap, exports), + + /** + * @param {import('acorn').Node} node + */ + ClassDeclaration: node => + handleClassDeclaration(node, nameToLineNumberMap, exports), + }; + + visit(program, node => { + if (!node.loc) { + return; + } + + if (node.type in TYPE_TO_HANDLER_MAP) { + const handler = TYPE_TO_HANDLER_MAP[node.type]; + + handler(node); + } + }); +} diff --git a/src/generators/api-links/utils/getBaseGitHubUrl.mjs b/src/generators/api-links/utils/getBaseGitHubUrl.mjs new file mode 100644 index 00000000..5bbe313b --- /dev/null +++ b/src/generators/api-links/utils/getBaseGitHubUrl.mjs @@ -0,0 +1,35 @@ +'use strict'; + +import { execSync } from 'node:child_process'; + +/** + * @param {string} cwd + */ +export function getBaseGitHubUrl(cwd) { + let url = execSync('git remote get-url origin', { cwd }).toString().trim(); + + if (url.startsWith('git@')) { + // It's an ssh url, we need to transform it to be https + // Ex/ git@github.com:nodejs/node.git -> https://github.com/nodejs/node.git + let [, repository] = url.split(':'); + + url = `https://github.com/${repository}`; + } + + // https://github.com/nodejs/node.git -> https://github.com/nodejs/node + if (url.endsWith('.git')) { + url = url.substring(0, url.length - 4); + } + + return url; +} + +/** + * Grabs the current Git commit hash within a directory + * @param cwd + */ +export function getCurrentGitHash(cwd) { + const hash = execSync('git rev-parse HEAD', { cwd }).toString().trim(); + + return hash; +} diff --git a/src/generators/ast-js/index.mjs b/src/generators/ast-js/index.mjs new file mode 100644 index 00000000..440365a5 --- /dev/null +++ b/src/generators/ast-js/index.mjs @@ -0,0 +1,43 @@ +import createJsLoader from '../../loaders/javascript.mjs'; +import createJsParser from '../../parsers/javascript.mjs'; + +/** + * This generator parses Javascript sources passed into the generator's input + * field. This is separate from the Markdown parsing step since it's not as + * commonly used and can take up a significant amount of memory. + * + * Putting this with the rest of the generators allows it to be lazily loaded + * so we're only parsing the Javascript sources when we need to. + * + * @typedef {unknown} Input + * + * @type {import('../types.d.ts').GeneratorMetadata>} + */ +export default { + name: 'ast-js', + + version: '1.0.0', + + description: 'Parses Javascript source files passed into the input.', + + dependsOn: 'ast', + + /** + * @param {Input} _ + * @param {Partial} options + */ + async generate(_, options) { + const { loadFiles } = createJsLoader(); + + // Load all of the Javascript sources into memory + const sourceFiles = loadFiles(options.input ?? []); + + const { parseJsSources } = createJsParser(); + + // Parse the Javascript sources into ASTs + const parsedJsFiles = await parseJsSources(sourceFiles); + + // Return the ASTs so they can be used in another generator + return parsedJsFiles; + }, +}; diff --git a/src/generators/index.mjs b/src/generators/index.mjs index 45a0f54e..512271ce 100644 --- a/src/generators/index.mjs +++ b/src/generators/index.mjs @@ -7,6 +7,7 @@ import manPage from './man-page/index.mjs'; import legacyJson from './legacy-json/index.mjs'; import legacyJsonAll from './legacy-json-all/index.mjs'; import addonVerify from './addon-verify/index.mjs'; +import apiLinks from './api-links/index.mjs'; export default { 'json-simple': jsonSimple, @@ -16,4 +17,5 @@ export default { 'legacy-json': legacyJson, 'legacy-json-all': legacyJsonAll, 'addon-verify': addonVerify, + 'api-links': apiLinks, }; diff --git a/src/generators/legacy-html-all/index.mjs b/src/generators/legacy-html-all/index.mjs index 13607b4d..a2abdb2e 100644 --- a/src/generators/legacy-html-all/index.mjs +++ b/src/generators/legacy-html-all/index.mjs @@ -99,6 +99,7 @@ export default { const minified = await minify(generatedAllTemplate, { collapseWhitespace: true, minifyJS: true, + minifyCSS: true, }); if (output) { diff --git a/src/generators/legacy-html/assets/api.js b/src/generators/legacy-html/assets/api.js index 7bb67a21..611fa8c8 100644 --- a/src/generators/legacy-html/assets/api.js +++ b/src/generators/legacy-html/assets/api.js @@ -43,6 +43,7 @@ function closeAllPickers() { for (const picker of pickers) { picker.parentNode.classList.remove('expanded'); + picker.ariaExpanded = false; } window.removeEventListener('click', closeAllPickers); @@ -60,6 +61,7 @@ for (const picker of pickers) { const parentNode = picker.parentNode; + picker.ariaExpanded = parentNode.classList.contains('expanded'); picker.addEventListener('click', function (e) { e.preventDefault(); @@ -67,7 +69,7 @@ closeAllPickers as window event trigger already closed all the pickers, if it already closed there is nothing else to do here */ - if (parentNode.classList.contains('expanded')) { + if (picker.ariaExpanded === 'true') { return; } @@ -77,9 +79,11 @@ */ requestAnimationFrame(function () { + picker.ariaExpanded = true; parentNode.classList.add('expanded'); window.addEventListener('click', closeAllPickers); window.addEventListener('keydown', onKeyDown); + parentNode.querySelector('.picker a').focus(); }); }); } diff --git a/src/generators/legacy-html/assets/style.css b/src/generators/legacy-html/assets/style.css index a430b7b6..9086b6b4 100644 --- a/src/generators/legacy-html/assets/style.css +++ b/src/generators/legacy-html/assets/style.css @@ -137,11 +137,25 @@ code, .pre, span.type, a.type { - font-family: SFMono-Regular, Menlo, Consolas, 'Liberation Mono', 'Courier New', - monospace; + font-family: SFMono-Regular, Menlo, Consolas, 'Liberation Mono', + 'Courier New', monospace; font-size: 0.9em; } +.skip-to-content { + position: fixed; + top: -300%; +} + +.skip-to-content:focus { + display: block; + top: 0; + left: 0; + background-color: var(--green1); + padding: 1rem; + z-index: 999999; +} + #content { position: relative; } @@ -203,22 +217,15 @@ li.picker-header .picker-arrow { height: 0.6rem; border-top: 0.3rem solid transparent; border-bottom: 0.3rem solid transparent; - border-left: 0.6rem solid var(--color-links); + border-left: 0.6rem solid currentColor; border-right: none; margin: 0 0.2rem 0.05rem 0; } -li.picker-header a:focus .picker-arrow, -li.picker-header a:active .picker-arrow, -li.picker-header a:hover .picker-arrow { - border-left: 0.6rem solid var(--white); -} - -li.picker-header.expanded a:focus .picker-arrow, -li.picker-header.expanded a:active .picker-arrow, -li.picker-header.expanded a:hover .picker-arrow, +li.picker-header.expanded .picker-arrow, +:root:not(.has-js) li.picker-header:focus-within .picker-arrow, :root:not(.has-js) li.picker-header:hover .picker-arrow { - border-top: 0.6rem solid var(--white); + border-top: 0.6rem solid currentColor; border-bottom: none; border-left: 0.35rem solid transparent; border-right: 0.35rem solid transparent; @@ -226,11 +233,13 @@ li.picker-header.expanded a:hover .picker-arrow, } li.picker-header.expanded > a, +:root:not(.has-js) li.picker-header:focus-within > a, :root:not(.has-js) li.picker-header:hover > a { border-radius: 2px 2px 0 0; } li.picker-header.expanded > .picker, +:root:not(.has-js) li.picker-header:focus-within > .picker, :root:not(.has-js) li.picker-header:hover > .picker { display: block; z-index: 1; @@ -592,35 +601,26 @@ hr { margin: 0; } -.toc li a::before { - content: '■'; - color: var(--color-text-primary); - padding-right: 1em; - font-size: 0.9em; -} - -.toc li a:hover::before { - color: var(--white); -} - -.toc ul ul a { - padding-left: 1rem; +.toc > ul:first-child { + margin-left: 1rem; } -.toc ul ul ul a { - padding-left: 2rem; +.toc li { + display: list-item; + list-style: square; } -.toc ul ul ul ul a { - padding-left: 3rem; +.toc li a { + display: inline; + padding-left: 0; } -.toc ul ul ul ul ul a { - padding-left: 4rem; +.toc li a:hover::before { + color: var(--white); } -.toc ul ul ul ul ul ul a { - padding-left: 5rem; +.toc ul { + padding-left: 1rem; } #toc .stability_0::after, diff --git a/src/generators/legacy-html/index.mjs b/src/generators/legacy-html/index.mjs index 5bad9d01..33358c33 100644 --- a/src/generators/legacy-html/index.mjs +++ b/src/generators/legacy-html/index.mjs @@ -160,6 +160,7 @@ export default { const minified = await minify(result, { collapseWhitespace: true, minifyJS: true, + minifyCSS: true, }); await writeFile(join(output, `${node.api}.html`), minified); diff --git a/src/generators/legacy-html/template.html b/src/generators/legacy-html/template.html index ffcbe9c0..400fe835 100644 --- a/src/generators/legacy-html/template.html +++ b/src/generators/legacy-html/template.html @@ -39,6 +39,7 @@ + Skip to content