diff --git a/.c8rc.json b/.c8rc.json index 3f34a68a..6d5e8598 100644 --- a/.c8rc.json +++ b/.c8rc.json @@ -2,8 +2,8 @@ "all": true, "exclude": [ "**/fixtures", - "packages/core/src/generators/legacy-html/assets", - "packages/core/src/generators/web/ui", + "packages/legacy/src/generators/html/assets", + "packages/react/src/generators/web/ui", "**/*.d.ts" ] } diff --git a/.github/workflows/codespell.yml b/.github/workflows/codespell.yml index 291f6725..7ff52da9 100644 --- a/.github/workflows/codespell.yml +++ b/.github/workflows/codespell.yml @@ -18,4 +18,4 @@ jobs: with: ignore_words_list: crate,raison exclude_file: .gitignore - skip: package-lock.json, ./packages/core/src/generators/mandoc/template.1 + skip: package-lock.json, ./packages/extras/src/generators/man-page/template.1 diff --git a/.github/workflows/generate.yml b/.github/workflows/generate.yml index 0d60eed6..c5704420 100644 --- a/.github/workflows/generate.yml +++ b/.github/workflows/generate.yml @@ -56,7 +56,7 @@ jobs: echo "run_id=$ID" >> $GITHUB_OUTPUT - name: Download metadata artifact - if: ${{ github.event_name == 'pull_request' }} + if: ${{ steps.main.outputs.run_id }} uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 with: name: commit @@ -64,7 +64,7 @@ jobs: github-token: ${{ secrets.GITHUB_TOKEN }} - id: pr - if: ${{ github.event_name == 'pull_request' }} + if: ${{ steps.main.outputs.run_id }} run: | SHA=$(cat commit) echo "sha=$SHA" >> "$GITHUB_OUTPUT" @@ -76,36 +76,36 @@ jobs: fail-fast: false matrix: include: - - target: '@node-core/doc-kit/generators/man-page' + - target: '@doc-kittens/extras/man-page' input: './node/doc/api/cli.md' - - target: '@node-core/doc-kit/generators/addon-verify' + - target: '@doc-kittens/extras/addon-verify' input: './node/doc/api/addons.md' - - target: '@node-core/doc-kit/generators/api-links' + - target: '@doc-kittens/website/api-links' input: './node/lib/*.js' compare: object-assertion - - target: '@node-core/doc-kit/generators/orama-db' + - target: '@doc-kittens/react/orama-db' input: './node/doc/api/*.md' compare: file-size - - target: '@node-core/doc-kit/generators/json-simple' + - target: '@doc-kittens/extras/json-simple' input: './node/doc/api/*.md' - - target: '@node-core/doc-kit/generators/legacy-json' + - target: '@doc-kittens/legacy/json' input: './node/doc/api/*.md' compare: object-assertion - - target: '@node-core/doc-kit/generators/legacy-html' + - target: '@doc-kittens/legacy/html' input: './node/doc/api/*.md' compare: file-size - - target: '@node-core/doc-kit/generators/web' + - target: '@doc-kittens/react/web' input: './node/doc/api/*.md' compare: file-size - - target: '@node-core/doc-kit/generators/llms-txt' + - target: '@doc-kittens/website/llms-txt' input: './node/doc/api/*.md' compare: file-size steps: diff --git a/.prettierignore b/.prettierignore index 2ef371a0..49f941bc 100644 --- a/.prettierignore +++ b/.prettierignore @@ -1,14 +1,14 @@ npm-shrinkwrap.json # Tests files -packages/core/src/generators/api-links/__tests__/fixtures/ +packages/website/src/generators/api-links/__tests__/fixtures/ *.snapshot # Templates -packages/core/src/generators/web/template.html +packages/legacy/src/generators/web/template.html # Output out/ # Generated Files -packages/core/src/generators/metadata/maps/mdn.json +packages/internal/src/generators/metadata/maps/mdn.json \ No newline at end of file diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index b9b29890..0ba0bd9b 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -90,7 +90,7 @@ The steps below will give you a general idea of how to prepare your local enviro ```bash node packages/core/bin/cli.mjs generate \ - -t legacy-html \ + -t @doc-kittens/legacy/html \ -i ../node/doc/api/fs.md \ -o out \ --index ../node/doc/api/index.md \ @@ -110,7 +110,7 @@ The steps below will give you a general idea of how to prepare your local enviro Add `--log-level debug` before the `generate` subcommand to see the full pipeline trace: ```bash - node packages/core/bin/cli.mjs --log-level debug generate -t legacy-html -i ../node/doc/api/fs.md -o out + node packages/core/bin/cli.mjs --log-level debug generate -t @doc-kittens/legacy/html -i ../node/doc/api/fs.md -o out ``` > [!TIP] diff --git a/README.md b/README.md index 47a3c9c2..843a37e9 100644 --- a/README.md +++ b/README.md @@ -68,7 +68,7 @@ Options: -v, --version Target Node.js version (default: "v22.14.0") -c, --changelog Changelog URL or path (default: "https://raw.githubusercontent.com/nodejs/node/HEAD/CHANGELOG.md") --git-ref Git ref/commit URL (default: "https://github.com/nodejs/node/tree/HEAD") - -t, --target Target generator(s) (e.g. @node-core/doc-kit/generators/web) + -t, --target Target generator(s) (e.g. @doc-kittens/react/web) -h, --help display help for command ``` @@ -87,12 +87,12 @@ Options: ### Legacy -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. +To generate a 1:1 match with the [legacy tooling](https://github.com/nodejs/node/tree/main/tools/doc), use the generators from the [`@doc-kittens/legacy`](packages/legacy) package. ```sh npx doc-kit generate \ - -t @node-core/doc-kit/generators/legacy-html \ - -t @node-core/doc-kit/generators/legacy-json \ + -t @doc-kittens/legacy/html \ + -t @doc-kittens/legacy/json \ -i "path/to/node/doc/api/*.md" \ -o out \ --index path/to/node/doc/api/index.md @@ -104,8 +104,8 @@ To generate [our redesigned documentation pages](https://nodejs-api-docs-tooling ```sh npx doc-kit generate \ - -t @node-core/doc-kit/generators/web \ - -t @node-core/doc-kit/generators/orama-db \ + -t @doc-kittens/react/web \ + -t @doc-kittens/react/orama-db \ -i "path/to/node/doc/api/*.md" \ -o out \ --index path/to/node/doc/api/index.md diff --git a/docs/configuration.md b/docs/configuration.md index 26353b43..1bb1ea83 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -100,17 +100,17 @@ Configurations are merged in the following order (earlier sources take precedenc CLI options map to configuration properties: -| CLI Option | Config Property | Example | -| ---------------------- | ------------------ | ---------------------------------------------------- | -| `--input ` | `global.input` | `--input src/` | -| `--output ` | `global.output` | `--output dist/` | -| `--ignore ` | `global.ignore[]` | `--ignore test/` | -| `--minify` | `global.minify` | `--minify` | -| `--git-ref ` | `global.ref` | `--git-ref v20.0.0` | -| `--version ` | `global.version` | `--version 20.0.0` | -| `--changelog ` | `global.changelog` | `--changelog https://...` | -| `--index ` | `global.index` | `--index file://...` | -| `--type-map ` | `metadata.typeMap` | `--type-map file://...` | -| `--target ` | `target` | `--target @node-core/doc-kit/generators/legacy-json` | -| `--threads ` | `threads` | `--threads 4` | -| `--chunk-size ` | `chunkSize` | `--chunk-size 10` | +| CLI Option | Config Property | Example | +| ---------------------- | ------------------ | ----------------------------------- | +| `--input ` | `global.input` | `--input src/` | +| `--output ` | `global.output` | `--output dist/` | +| `--ignore ` | `global.ignore[]` | `--ignore test/` | +| `--minify` | `global.minify` | `--minify` | +| `--git-ref ` | `global.ref` | `--git-ref v20.0.0` | +| `--version ` | `global.version` | `--version 20.0.0` | +| `--changelog ` | `global.changelog` | `--changelog https://...` | +| `--index ` | `global.index` | `--index file://...` | +| `--type-map ` | `metadata.typeMap` | `--type-map file://...` | +| `--target ` | `target` | `--target @doc-kittens/legacy/json` | +| `--threads ` | `threads` | `--threads 4` | +| `--chunk-size ` | `chunkSize` | `--chunk-size 10` | diff --git a/docs/generators.md b/docs/generators.md index 1c64c928..965577e7 100644 --- a/docs/generators.md +++ b/docs/generators.md @@ -40,10 +40,18 @@ A generator is a single module (`index.mjs`) that exports its metadata and logic ### Step 1: Create the Generator Directory -Create a new directory in `src/generators/`: +Generators live inside one of the workspace packages under `packages//src/generators/`. The existing groupings are: + +- `@doc-kittens/legacy` — historical JSON/HTML formats +- `@doc-kittens/internal` — `ast`, `ast-js`, `metadata` (foundational generators consumed by everything else) +- `@doc-kittens/react` — React/JSX-based generators (`web`, `orama-db`, `jsx-ast`) +- `@doc-kittens/website` — public website outputs (`sitemap`, `llms-txt`, `api-links`) +- `@doc-kittens/extras` — specialised one-offs (`addon-verify`, `json-simple`, `man-page`) + +Place your new generator in the package whose theme it matches, or create a new workspace package if none fit. ``` -src/generators/my-format/ +packages//src/generators/my-format/ ├── index.mjs # Generator entry point (required) ├── constants.mjs # Constants (optional) ├── types.d.ts # TypeScript types (required) @@ -77,16 +85,16 @@ export type Generator = GeneratorMetadata< Create `index.mjs` with your generator's metadata and logic: ```javascript -// src/generators/my-format/index.mjs +// packages//src/generators/my-format/index.mjs 'use strict'; import { writeFile } from 'node:fs/promises'; import { join } from 'node:path'; -import getConfig from '../../utils/configuration/index.mjs'; +import getConfig from '@node-core/doc-kit/src/utils/configuration/index.mjs'; export const name = 'my-format'; -export const dependsOn = '@node-core/doc-kit/generators/metadata'; +export const dependsOn = '@doc-kittens/internal/metadata'; export const defaultConfiguration = { myCustomOption: 'myDefaultValue', }; @@ -130,7 +138,7 @@ function transformToMyFormat(entries, version) { ### Step 4: Register the Generator -Add an entry to the `exports` map in `packages/core/package.json`. If you follow the `index.mjs` convention, the wildcard pattern `"./generators/*": "./src/generators/*/index.mjs"` handles this automatically — no changes needed. +Add a short subpath entry to the `exports` map in your package's `package.json`, e.g. `"./my-format": "./src/generators/my-format/index.mjs"`. The `./src/*` wildcard already exposes utilities and types under the longer path form for cross-package imports. ## Parallel Processing with Workers @@ -139,11 +147,11 @@ For generators processing large datasets, implement parallel processing using wo ### Implementing Worker-Based Processing ```javascript -// src/generators/parallel-generator/index.mjs -import getConfig from '../../utils/configuration/index.mjs'; +// packages//src/generators/parallel-generator/index.mjs +import getConfig from '@node-core/doc-kit/src/utils/configuration/index.mjs'; export const name = 'parallel-generator'; -export const dependsOn = '@node-core/doc-kit/generators/metadata'; +export const dependsOn = '@doc-kittens/internal/metadata'; /** * Process a chunk of items in a worker thread. @@ -215,7 +223,7 @@ Generators can yield results as they're produced using async generators. Export ```javascript // src/generators/streaming-generator/index.mjs export const name = 'streaming-generator'; -export const dependsOn = '@node-core/doc-kit/generators/metadata'; +export const dependsOn = '@doc-kittens/internal/metadata'; /** * Process a chunk of data @@ -254,7 +262,7 @@ Some generators must collect all input before processing: ```javascript // src/generators/batch-generator/index.mjs export const name = 'batch-generator'; -export const dependsOn = '@node-core/doc-kit/generators/jsx-ast'; +export const dependsOn = '@doc-kittens/react/jsx-ast'; /** * Non-streaming - returns Promise instead of AsyncGenerator @@ -285,7 +293,7 @@ Use non-streaming when: ```javascript // src/generators/my-generator/index.mjs export const name = 'my-generator'; -export const dependsOn = '@node-core/doc-kit/generators/metadata'; +export const dependsOn = '@doc-kittens/internal/metadata'; export async function generate(input, worker) { // input contains the output from the metadata generator @@ -303,12 +311,12 @@ export const name = 'ast'; // Step 2: Extract metadata from AST // src/generators/metadata/index.mjs export const name = 'metadata'; -export const dependsOn = '@node-core/doc-kit/generators/ast'; +export const dependsOn = '@doc-kittens/internal/ast'; // Step 3: Generate HTML from metadata // src/generators/html-generator/index.mjs export const name = 'html-generator'; -export const dependsOn = '@node-core/doc-kit/generators/metadata'; +export const dependsOn = '@doc-kittens/internal/metadata'; ``` ### Multiple Consumers diff --git a/eslint.config.mjs b/eslint.config.mjs index 80cc8fc8..2e44b359 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -10,10 +10,7 @@ export default defineConfig([ importX.flatConfigs.recommended, react.configs.recommended, { - ignores: [ - 'out/', - 'packages/core/src/generators/api-links/__tests__/fixtures/', - ], + ignores: ['out/', '**/fixtures/'], }, { files: ['**/*.{mjs,jsx}'], @@ -94,8 +91,8 @@ export default defineConfig([ }, { files: [ - 'packages/core/src/generators/legacy-html/assets/*.js', - 'packages/core/src/generators/web/ui/**/*', + 'packages/legacy/src/generators/html/assets/*.js', + 'packages/react/src/utils/web/ui/**/*', ], languageOptions: { globals: { diff --git a/package-lock.json b/package-lock.json index 2b4014ee..ceaf0316 100644 --- a/package-lock.json +++ b/package-lock.json @@ -83,6 +83,26 @@ "node": ">=18" } }, + "node_modules/@doc-kittens/extras": { + "resolved": "packages/extras", + "link": true + }, + "node_modules/@doc-kittens/internal": { + "resolved": "packages/internal", + "link": true + }, + "node_modules/@doc-kittens/legacy": { + "resolved": "packages/legacy", + "link": true + }, + "node_modules/@doc-kittens/react": { + "resolved": "packages/react", + "link": true + }, + "node_modules/@doc-kittens/website": { + "resolved": "packages/website", + "link": true + }, "node_modules/@emnapi/core": { "version": "1.7.1", "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.7.1.tgz", @@ -521,12 +541,6 @@ "@jridgewell/sourcemap-codec": "^1.4.14" } }, - "node_modules/@minify-html/wasm": { - "version": "0.18.1", - "resolved": "https://registry.npmjs.org/@minify-html/wasm/-/wasm-0.18.1.tgz", - "integrity": "sha512-GBkBOJxe7duO+z2b00SP83EewOI+Qm4MsnajXHw4yT7/J+TuG3jLEatBHKnT59Zq4CgXBRpdkv/2hlCGnyqAzg==", - "license": "MIT" - }, "node_modules/@napi-rs/nice": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/@napi-rs/nice/-/nice-1.1.1.tgz", @@ -4677,6 +4691,7 @@ "version": "6.0.2", "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, "license": "ISC", "dependencies": { "is-glob": "^4.0.3" @@ -5501,6 +5516,7 @@ }, "node_modules/lightningcss-wasm/node_modules/napi-wasm": { "version": "1.1.3", + "extraneous": true, "inBundle": true, "license": "MIT" }, @@ -8746,49 +8762,23 @@ "version": "1.2.0", "dependencies": { "@actions/core": "^3.0.0", - "@heroicons/react": "^2.2.0", - "@minify-html/wasm": "^0.18.1", "@node-core/rehype-shiki": "^1.4.1", - "@node-core/ui-components": "^1.6.3", - "@orama/orama": "^3.1.18", - "@orama/ui": "^1.5.4", - "@rollup/plugin-virtual": "^3.0.2", "@swc/html-wasm": "^1.15.18", - "acorn": "^8.16.0", "commander": "^14.0.3", "dedent": "^1.7.2", - "estree-util-to-js": "^2.0.0", - "estree-util-visit": "^2.0.0", - "github-slugger": "^2.0.0", - "glob-parent": "^6.0.2", - "hast-util-to-string": "^3.0.1", "hastscript": "^9.0.1", - "lightningcss-wasm": "^1.32.0", - "mdast-util-slice-markdown": "^2.0.1", "piscina": "^5.1.4", - "preact": "^10.29.0", - "preact-render-to-string": "^6.6.6", - "reading-time": "^1.5.0", - "recma-jsx": "^1.0.1", - "rehype-raw": "^7.0.0", - "rehype-recma": "^1.0.0", "rehype-stringify": "^10.0.1", "remark-gfm": "^4.0.1", "remark-parse": "^11.0.0", "remark-rehype": "^11.1.2", "remark-stringify": "^11.0.0", - "rolldown": "^1.0.0-rc.10", "semver": "^7.7.4", "shiki": "^4.0.2", - "tinyglobby": "^0.2.15", "unified": "^11.0.5", "unist-builder": "^4.0.0", - "unist-util-find-after": "^5.0.0", "unist-util-position": "^5.0.0", - "unist-util-remove": "^4.0.0", - "unist-util-select": "^5.1.0", - "unist-util-visit": "^5.1.0", - "yaml": "^2.8.3" + "unist-util-visit": "^5.1.0" }, "bin": { "doc-kit": "packages/core/bin/cli.mjs" @@ -8800,6 +8790,82 @@ "@types/semver": "^7.7.1", "c8": "^11.0.0" } + }, + "packages/extras": { + "name": "@doc-kittens/extras", + "version": "1.0.0", + "dependencies": { + "@doc-kittens/internal": "*", + "@node-core/doc-kit": "*", + "dedent": "^1.7.2", + "unist-util-remove": "^4.0.0", + "unist-util-visit": "^5.1.0" + } + }, + "packages/internal": { + "name": "@doc-kittens/internal", + "version": "1.0.0", + "dependencies": { + "@node-core/doc-kit": "*", + "acorn": "^8.16.0", + "github-slugger": "^2.0.0", + "unist-builder": "^4.0.0", + "unist-util-find-after": "^5.0.0", + "unist-util-remove": "^4.0.0", + "unist-util-select": "^5.1.0", + "unist-util-visit": "^5.1.0", + "yaml": "^2.8.3" + } + }, + "packages/legacy": { + "name": "@doc-kittens/legacy", + "version": "1.0.0", + "dependencies": { + "@doc-kittens/internal": "*", + "@node-core/doc-kit": "*" + } + }, + "packages/react": { + "name": "@doc-kittens/react", + "version": "1.0.0", + "dependencies": { + "@doc-kittens/internal": "*", + "@doc-kittens/legacy": "*", + "@heroicons/react": "^2.2.0", + "@node-core/doc-kit": "*", + "@node-core/rehype-shiki": "^1.4.1", + "@node-core/ui-components": "^1.6.3", + "@orama/orama": "^3.1.18", + "@orama/ui": "^1.5.4", + "@rollup/plugin-virtual": "^3.0.2", + "estree-util-to-js": "^2.0.0", + "hast-util-to-string": "^3.0.1", + "hastscript": "^9.0.1", + "lightningcss-wasm": "^1.32.0", + "mdast-util-slice-markdown": "^2.0.1", + "preact": "^10.29.0", + "preact-render-to-string": "^6.6.6", + "reading-time": "^1.5.0", + "recma-jsx": "^1.0.1", + "recma-stringify": "^1.0.0", + "rehype-raw": "^7.0.0", + "rehype-recma": "^1.0.0", + "rehype-stringify": "^10.0.1", + "rolldown": "^1.0.0-rc.10", + "semver": "^7.7.4", + "unified": "^11.0.5", + "unist-builder": "^4.0.0", + "unist-util-visit": "^5.1.0" + } + }, + "packages/website": { + "name": "@doc-kittens/website", + "version": "1.0.0", + "dependencies": { + "@doc-kittens/internal": "*", + "@node-core/doc-kit": "*", + "estree-util-visit": "^2.0.0" + } } } } diff --git a/packages/core/package.json b/packages/core/package.json index 7f08821e..62ee9426 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -19,57 +19,28 @@ "watch": "node --watch packages/core/bin/cli.mjs" }, "main": "./src/generators.mjs", - "exports": { - "./generators/*": "./src/generators/*/index.mjs" - }, "bin": { "doc-kit": "./packages/core/bin/cli.mjs" }, "dependencies": { "@actions/core": "^3.0.0", - "@heroicons/react": "^2.2.0", - "@minify-html/wasm": "^0.18.1", "@node-core/rehype-shiki": "^1.4.1", - "@node-core/ui-components": "^1.6.3", - "@orama/orama": "^3.1.18", - "@orama/ui": "^1.5.4", - "@rollup/plugin-virtual": "^3.0.2", "@swc/html-wasm": "^1.15.18", - "acorn": "^8.16.0", "commander": "^14.0.3", "dedent": "^1.7.2", - "estree-util-to-js": "^2.0.0", - "estree-util-visit": "^2.0.0", - "github-slugger": "^2.0.0", - "glob-parent": "^6.0.2", - "hast-util-to-string": "^3.0.1", "hastscript": "^9.0.1", - "lightningcss-wasm": "^1.32.0", - "mdast-util-slice-markdown": "^2.0.1", "piscina": "^5.1.4", - "preact": "^10.29.0", - "preact-render-to-string": "^6.6.6", - "reading-time": "^1.5.0", - "recma-jsx": "^1.0.1", - "rehype-raw": "^7.0.0", - "rehype-recma": "^1.0.0", "rehype-stringify": "^10.0.1", "remark-gfm": "^4.0.1", "remark-parse": "^11.0.0", "remark-rehype": "^11.1.2", "remark-stringify": "^11.0.0", - "rolldown": "^1.0.0-rc.10", "semver": "^7.7.4", "shiki": "^4.0.2", - "tinyglobby": "^0.2.15", "unified": "^11.0.5", "unist-builder": "^4.0.0", - "unist-util-find-after": "^5.0.0", "unist-util-position": "^5.0.0", - "unist-util-remove": "^4.0.0", - "unist-util-select": "^5.1.0", - "unist-util-visit": "^5.1.0", - "yaml": "^2.8.3" + "unist-util-visit": "^5.1.0" }, "devDependencies": { "@reporters/github": "^1.12.0", diff --git a/packages/core/src/generators/api-links/__tests__/fixtures/prototype.js b/packages/core/src/generators/api-links/__tests__/fixtures/prototype.js deleted file mode 100644 index 40218e84..00000000 --- a/packages/core/src/generators/api-links/__tests__/fixtures/prototype.js +++ /dev/null @@ -1,13 +0,0 @@ -'use strict'; - -// An exported class using classic prototype syntax. - -function Class() { -} - -Class.classMethod = function() {} -Class.prototype.instanceMethod = function() {} - -module.exports = { - Class -}; diff --git a/packages/core/src/generators/legacy-html/utils/__tests__/slugger.test.mjs b/packages/core/src/generators/legacy-html/utils/__tests__/slugger.test.mjs deleted file mode 100644 index 852aff71..00000000 --- a/packages/core/src/generators/legacy-html/utils/__tests__/slugger.test.mjs +++ /dev/null @@ -1,39 +0,0 @@ -'use strict'; - -import assert from 'node:assert/strict'; -import { describe, it } from 'node:test'; - -import { createLegacySlugger } from '../slugger.mjs'; - -describe('createLegacySlugger', () => { - it('prefixes with api stem and uses underscores', () => { - const getLegacySlug = createLegacySlugger(); - assert.strictEqual(getLegacySlug('File System', 'fs'), 'fs_file_system'); - }); - - it('replaces special characters with underscores', () => { - const getLegacySlug = createLegacySlugger(); - assert.strictEqual( - getLegacySlug('fs.readFile(path)', 'fs'), - 'fs_fs_readfile_path' - ); - }); - - it('strips leading and trailing underscores', () => { - const getLegacySlug = createLegacySlugger(); - assert.strictEqual(getLegacySlug('Hello', 'fs'), 'fs_hello'); - }); - - it('prefixes with underscore when result starts with non-alpha', () => { - const getLegacySlug = createLegacySlugger(); - assert.strictEqual(getLegacySlug('123 test', '0num'), '_0num_123_test'); - }); - - it('deduplicates with a counter for identical titles', () => { - const getLegacySlug = createLegacySlugger(); - assert.strictEqual(getLegacySlug('Hello', 'fs'), 'fs_hello'); - assert.strictEqual(getLegacySlug('Hello', 'fs'), 'fs_hello_1'); - assert.strictEqual(getLegacySlug('Hello', 'fs'), 'fs_hello_2'); - assert.strictEqual(getLegacySlug('World', 'fs'), 'fs_world'); - }); -}); diff --git a/packages/core/src/generators/legacy-html/utils/buildExtraContent.mjs b/packages/core/src/generators/legacy-html/utils/buildExtraContent.mjs deleted file mode 100644 index 6f9ef45f..00000000 --- a/packages/core/src/generators/legacy-html/utils/buildExtraContent.mjs +++ /dev/null @@ -1,66 +0,0 @@ -'use strict'; - -import { h as createElement } from 'hastscript'; -import { u as createTree } from 'unist-builder'; - -/** - * Generates the Stability Overview table based on the API metadata nodes. - * - * @param {Array} headMetadata The API metadata nodes to be used for the Stability Overview - */ -const buildStabilityOverview = headMetadata => { - const headNodesWithStability = headMetadata.filter(entry => entry.stability); - - const mappedHeadNodesIntoTable = headNodesWithStability.map( - ({ heading, api, stability }) => { - return createElement( - 'tr', - createElement( - 'td.module_stability', - createElement('a', { href: `${api}.html` }, heading.data.name) - ), - createElement( - `td.api_stability.api_stability_${parseInt(stability.data.index)}`, - // Grabs the first sentence of the description - // to be used as a summary of the Stability Index - `(${stability.data.index}) ${stability.data.description.split('. ')[0]}` - ) - ); - } - ); - - return createElement( - 'table', - createElement( - 'thead', - createElement( - 'tr', - createElement('th', 'API'), - createElement('th', 'Stability') - ) - ), - createElement('tbody', mappedHeadNodesIntoTable) - ); -}; - -/** - * Generates extra "special" HTML content based on extra metadata that a node may have. - * - * @param {Array} headNodes The API metadata nodes to be used for the Stability Overview - * @param {import('../../metadata/types').MetadataEntry} node The current API metadata node to be transformed into HTML content - * @returns {import('unist').Parent} The HTML AST tree for the extra content - */ -export default (headNodes, node) => { - return createTree('root', [ - (node.tags ?? []).map(tag => { - switch (tag) { - case 'STABILITY_OVERVIEW_SLOT_BEGIN': - return buildStabilityOverview(headNodes); - case 'STABILITY_OVERVIEW_SLOT_END': - return createTree('root'); - default: - return createTree('root'); - } - }), - ]); -}; diff --git a/packages/core/src/generators/legacy-html/utils/replaceTemplateValues.mjs b/packages/core/src/generators/legacy-html/utils/replaceTemplateValues.mjs deleted file mode 100644 index baec0754..00000000 --- a/packages/core/src/generators/legacy-html/utils/replaceTemplateValues.mjs +++ /dev/null @@ -1,43 +0,0 @@ -'use strict'; - -import { - buildToC, - buildNavigation, - buildVersions, - buildGitHub, -} from './buildDropdowns.mjs'; -import tableOfContents from './tableOfContents.mjs'; -import { populate } from '../../../utils/configuration/templates.mjs'; - -/** - * Replaces the template values in the API template with the given values. - * @param {string} apiTemplate - The HTML template string - * @param {import('../types').TemplateValues} values - The values to replace the template values with - * @param {import('../../../utils/configuration/types').GlobalConfiguration} config - * @param {{ skipGitHub?: boolean; skipGtocPicker?: boolean }} [options] - Optional settings - * @returns {string} The replaced template values - */ -export const replaceTemplateValues = ( - apiTemplate, - { path, api, added, section, toc, nav, content }, - config, - { skipGitHub = false, skipGtocPicker = false } = {} -) => { - return apiTemplate - .replace('__ID__', api) - .replace(/__FILENAME__/g, api) - .replace('__SECTION__', section) - .replace(/__VERSION__/g, `v${config.version.version}`) - .replace(/__TOC__/g, tableOfContents.wrapToC(toc)) - .replace(/__GTOC__/g, nav) - .replace('__CONTENT__', content) - .replace(/__TOC_PICKER__/g, buildToC(toc)) - .replace(/__GTOC_PICKER__/g, skipGtocPicker ? '' : buildNavigation(nav)) - .replace('__ALTDOCS__', buildVersions(path, added, config.changelog)) - .replace( - '__EDIT_ON_GITHUB__', - skipGitHub - ? '' - : buildGitHub(populate(config.editURL, { ...config, path })) - ); -}; diff --git a/packages/core/src/generators/legacy-html/utils/slugger.mjs b/packages/core/src/generators/legacy-html/utils/slugger.mjs deleted file mode 100644 index f6a79e4f..00000000 --- a/packages/core/src/generators/legacy-html/utils/slugger.mjs +++ /dev/null @@ -1,23 +0,0 @@ -'use strict'; - -/** - * Creates a stateful slugger for legacy anchor links. - * - * Generates underscore-separated slugs in the form `{apiStem}_{text}`, - * appending `_{n}` for duplicates to preserve historical anchor compatibility. - * - * @returns {(text: string, apiStem: string) => string} - */ -export const createLegacySlugger = - (counters = {}) => - (text, apiStem) => { - const id = `${apiStem}_${text}` - .toLowerCase() - .replace(/^[^a-z0-9]+|[^a-z0-9]+$/g, '') - .replace(/[^a-z0-9]+/g, '_') - .replace(/^\d/, '_$&'); - - counters[id] ??= -1; - const count = ++counters[id]; - return count > 0 ? `${id}_${count}` : id; - }; diff --git a/packages/core/src/generators/legacy-json/utils/__tests__/buildHierarchy.test.mjs b/packages/core/src/generators/legacy-json/utils/__tests__/buildHierarchy.test.mjs deleted file mode 100644 index d6c6441a..00000000 --- a/packages/core/src/generators/legacy-json/utils/__tests__/buildHierarchy.test.mjs +++ /dev/null @@ -1,50 +0,0 @@ -import assert from 'node:assert/strict'; -import { describe, it } from 'node:test'; - -import { findParent, buildHierarchy } from '../buildHierarchy.mjs'; - -describe('findParent', () => { - it('finds parent with lower depth', () => { - const entries = [{ heading: { depth: 1 } }, { heading: { depth: 2 } }]; - const parent = findParent(entries[1], entries, 0); - assert.equal(parent, entries[0]); - }); - - it('throws when no parent exists', () => { - const entries = [{ heading: { depth: 2 } }]; - assert.throws(() => findParent(entries[0], entries, -1)); - }); -}); - -describe('buildHierarchy', () => { - it('returns empty array for empty input', () => { - assert.deepEqual(buildHierarchy([]), []); - }); - - it('keeps root entries at top level', () => { - const entries = [{ heading: { depth: 1 } }, { heading: { depth: 1 } }]; - const result = buildHierarchy(entries); - assert.equal(result.length, 2); - }); - - it('nests children under parents', () => { - const entries = [{ heading: { depth: 1 } }, { heading: { depth: 2 } }]; - const result = buildHierarchy(entries); - - assert.equal(result.length, 1); - assert.equal(result[0].hierarchyChildren.length, 1); - assert.equal(result[0].hierarchyChildren[0], entries[1]); - }); - - it('handles multiple levels', () => { - const entries = [ - { heading: { depth: 1 } }, - { heading: { depth: 2 } }, - { heading: { depth: 3 } }, - ]; - const result = buildHierarchy(entries); - - assert.equal(result.length, 1); - assert.equal(result[0].hierarchyChildren[0].hierarchyChildren.length, 1); - }); -}); diff --git a/packages/core/src/generators/legacy-json/utils/__tests__/buildSection.test.mjs b/packages/core/src/generators/legacy-json/utils/__tests__/buildSection.test.mjs deleted file mode 100644 index 71eeadc3..00000000 --- a/packages/core/src/generators/legacy-json/utils/__tests__/buildSection.test.mjs +++ /dev/null @@ -1,138 +0,0 @@ -'use strict'; - -import assert from 'node:assert/strict'; -import { describe, test } from 'node:test'; - -import { UNPROMOTED_KEYS } from '../../constants.mjs'; -import { promoteMiscChildren } from '../buildSection.mjs'; - -describe('promoteMiscChildren', () => { - /** - * @template {object} T - * - * @param {T} base - * @param {'section'|'parent'} [type='section'] - * @returns {T} - */ - function buildReadOnlySection(base, type = 'section') { - return new Proxy(base, { - set(_, key) { - throw new Error(`${type} property '${String(key)} modified`); - }, - }); - } - - test('ignores non-misc section', () => { - const section = buildReadOnlySection({ - type: 'text', - }); - - const parent = buildReadOnlySection( - { - type: 'text', - }, - 'parent' - ); - - promoteMiscChildren(section, parent); - }); - - test('ignores misc parent', () => { - const section = buildReadOnlySection({ - type: 'misc', - }); - - const parent = buildReadOnlySection( - { - type: 'misc', - }, - 'parent' - ); - - promoteMiscChildren(section, parent); - }); - - test('ignores keys in UNPROMOTED_KEYS', () => { - const sectionRaw = { - type: 'misc', - promotableKey: 'this should be promoted', - }; - - UNPROMOTED_KEYS.forEach(key => { - if (key === 'type') { - return; - } - - sectionRaw[key] = 'this should be ignored'; - }); - - const section = buildReadOnlySection(sectionRaw); - - const parent = { - type: 'module', - }; - - promoteMiscChildren(section, parent); - - UNPROMOTED_KEYS.forEach(key => { - if (key === 'type') { - return; - } - - if (parent[key]) { - throw new Error(`'${key}' was promoted`); - } - }); - - assert.strictEqual(parent.promotableKey, section.promotableKey); - }); - - describe('merges properties correctly', () => { - test('pushes child property if parent is an array', () => { - const section = buildReadOnlySection({ - type: 'misc', - someValue: 'bar', - }); - - const parent = { - type: 'module', - someValue: ['foo'], - }; - - promoteMiscChildren(section, parent); - - assert.deepStrictEqual(parent.someValue, ['foo', 'bar']); - }); - - test('ignores child property if parent has a value that is not an array', () => { - const section = buildReadOnlySection({ - type: 'misc', - someValue: 'bar', - }); - - const parent = { - type: 'module', - someValue: 'foo', - }; - - promoteMiscChildren(section, parent); - - assert.strictEqual(parent.someValue, 'foo'); - }); - - test('promotes child property if parent does not have the property', () => { - const section = buildReadOnlySection({ - type: 'misc', - someValue: 'bar', - }); - - const parent = { - type: 'module', - }; - - promoteMiscChildren(section, parent); - - assert.deepStrictEqual(parent.someValue, 'bar'); - }); - }); -}); diff --git a/packages/core/src/generators/legacy-json/utils/buildHierarchy.mjs b/packages/core/src/generators/legacy-json/utils/buildHierarchy.mjs deleted file mode 100644 index 0f79125a..00000000 --- a/packages/core/src/generators/legacy-json/utils/buildHierarchy.mjs +++ /dev/null @@ -1,78 +0,0 @@ -/** - * Recursively finds the most suitable parent entry for a given `entry` based on heading depth. - * - * @param {import('../../metadata/types').MetadataEntry} entry - * @param {import('../../metadata/types').MetadataEntry[]} entries - * @param {number} startIdx - * @returns {import('../types.d.ts').HierarchizedEntry} - */ -export function findParent(entry, entries, startIdx) { - // Base case: if we're at the beginning of the list, no valid parent exists. - if (startIdx < 0) { - throw new Error( - `Cannot find a suitable parent for entry at index ${startIdx + 1}` - ); - } - - const candidateParent = entries[startIdx]; - const candidateDepth = candidateParent.heading.depth; - - // If we find a suitable parent, return it. - if (candidateDepth < entry.heading.depth) { - candidateParent.hierarchyChildren ??= []; - return candidateParent; - } - - // Recurse upwards to find a suitable parent. - return findParent(entry, entries, startIdx - 1); -} - -/** - * We need the files to be in a hierarchy based off of depth, but they're - * given to us flattened. So, let's fix that. - * - * Assuming that {@link entries} is in the same order as the elements are in - * the markdown, we can use the entry's depth property to reassemble the - * hierarchy. - * - * If depth <= 1, it's a top-level element (aka a root). - * - * If it's depth is greater than the previous entry's depth, it's a child of - * the previous entry. Otherwise (if it's less than or equal to the previous - * entry's depth), we need to find the entry that it was the greater than. We - * can do this by just looping through entries in reverse starting at the - * current index - 1. - * - * @param {Array} entries - * @returns {Array} - */ -export function buildHierarchy(entries) { - const roots = []; - - // Main loop to construct the hierarchy. - for (let i = 0; i < entries.length; i++) { - const entry = entries[i]; - const currentDepth = entry.heading.depth; - - // Top-level entries are added directly to roots. - if (currentDepth <= 1) { - roots.push(entry); - continue; - } - - // For non-root entries, find the appropriate parent. - const previousEntry = entries[i - 1]; - const previousDepth = previousEntry.heading.depth; - - if (currentDepth > previousDepth) { - previousEntry.hierarchyChildren ??= []; - previousEntry.hierarchyChildren.push(entry); - } else { - // Use recursive helper to find the nearest valid parent. - const parent = findParent(entry, entries, i - 2); - parent.hierarchyChildren.push(entry); - } - } - - return roots; -} diff --git a/packages/core/src/generators/legacy-json/utils/buildSection.mjs b/packages/core/src/generators/legacy-json/utils/buildSection.mjs deleted file mode 100644 index 624cbc4b..00000000 --- a/packages/core/src/generators/legacy-json/utils/buildSection.mjs +++ /dev/null @@ -1,209 +0,0 @@ -import { buildHierarchy } from './buildHierarchy.mjs'; -import { parseList } from './parseList.mjs'; -import { enforceArray } from '../../../utils/array.mjs'; -import { getRemarkRehype } from '../../../utils/remark.mjs'; -import { transformNodesToString } from '../../../utils/unist.mjs'; -import { SECTION_TYPE_PLURALS, UNPROMOTED_KEYS } from '../constants.mjs'; - -/** - * Promotes children properties to the parent level if the section type is 'misc'. - * @param {import('../types.d.ts').Section} section - The section to promote. - * @param {import('../types.d.ts').Section} parent - The parent section. - */ -export const promoteMiscChildren = (section, parent) => { - // Only promote if the current section is of type 'misc' and the parent is not 'misc' - if (section.type === 'misc' && parent.type !== 'misc') { - Object.entries(section).forEach(([key, value]) => { - // Only promote certain keys - if (!UNPROMOTED_KEYS.includes(key)) { - // Merge the section's properties into the parent section - if (parent[key] && Array.isArray(parent[key])) { - parent[key] = parent[key].concat(value); - } else { - parent[key] ||= value; - } - } - }); - } -}; - -/** - * - */ -export const createSectionBuilder = () => { - const html = getRemarkRehype(); - - /** - * Creates metadata from a hierarchized entry. - * @param {import('../types.d.ts').HierarchizedEntry} entry - The entry to create metadata from. - * @returns {import('../types.d.ts').Meta | undefined} The created metadata, or undefined if all fields are empty. - */ - const createMeta = ({ - added = [], - napiVersion = [], - deprecated = [], - removed = [], - changes = [], - }) => { - const meta = {}; - - if (added?.length) { - meta.added = enforceArray(added); - } - - meta.changes = changes; - - if (typeof napiVersion === 'number' || napiVersion?.length) { - meta.napiVersion = enforceArray(napiVersion); - } - - if (deprecated?.length) { - meta.deprecated = enforceArray(deprecated); - } - - if (removed?.length) { - meta.removed = enforceArray(removed); - } - - // Check if there are any non-empty fields in the meta object - const atLeastOneNonEmptyField = - changes?.length || Object.keys(meta).length > 1; - - // Return undefined if the meta object is completely empty - return atLeastOneNonEmptyField ? meta : undefined; - }; - - /** - * Creates a section from an entry and its heading. - * @param {import('../types.d.ts').HierarchizedEntry} entry - The AST entry. - * @param {import('../../metadata/types').HeadingNode} head - The head node of the entry. - * @returns {import('../types.d.ts').Section} The created section. - */ - const createSection = (entry, head) => { - const section = { - textRaw: transformNodesToString(head.children), - name: head.data.name, - introduced_in: entry.introduced_in, - type: head.data.type, - }; - - const meta = createMeta(entry); - - if (meta !== undefined) { - section.meta = meta; - } - - return section; - }; - - /** - * Parses stability metadata and adds it to the section. - * @param {import('../types.d.ts').Section} section - The section to update. - * @param {Array} nodes - The remaining AST nodes. - * @param {import('../types.d.ts').HierarchizedEntry} entry - The entry providing stability information. - */ - const parseStability = (section, nodes, { stability, content }) => { - if (stability) { - section.stability = Number(stability.data.index); - section.stabilityText = stability.data.description; - - const stabilityIdx = content.children.indexOf(stability); - - if (stabilityIdx) { - nodes.splice(stabilityIdx - 1, 1); - } - } - }; - - /** - * Adds a description to the section. - * @param {import('../types.d.ts').Section} section - The section to update. - * @param {Array} nodes - The remaining AST nodes. - */ - const addDescription = (section, nodes) => { - if (!nodes.length) { - return; - } - - const rendered = html.stringify( - html.runSync({ type: 'root', children: nodes }) - ); - - section.shortDesc = section.desc || undefined; - section.desc = rendered || undefined; - }; - - /** - * Adds additional metadata to the section based on its type. - * @param {import('../types.d.ts').Section} section - The section to update. - * @param {import('../types.d.ts').Section} parent - The parent section. - * @param {import('../../metadata/types').HeadingNode} heading - The heading node of the section. - */ - const addAdditionalMetadata = (section, parent, heading) => { - if (!section.type || section.type === 'module') { - section.name = section.textRaw.toLowerCase().trim().replace(/\s+/g, '_'); - } - - if (!section.type) { - section.displayName = heading.data.name; - section.type = parent.type === 'misc' ? 'misc' : 'module'; - } - }; - - /** - * Adds the section to its parent section. - * @param {import('../types.d.ts').Section} section - The section to add. - * @param {import('../types.d.ts').Section} parent - The parent section. - */ - const addToParent = (section, parent) => { - const key = SECTION_TYPE_PLURALS[section.type] || 'properties'; - - parent[key] ??= []; - parent[key].push(section); - }; - - /** - * Processes children of a given entry and updates the section. - * @param {import('../types.d.ts').HierarchizedEntry} entry - The current entry. - * @param {import('../types.d.ts').Section} section - The current section. - */ - const handleChildren = ({ hierarchyChildren }, section) => - hierarchyChildren?.forEach(child => handleEntry(child, section)); - - /** - * Handles an entry and updates the parent section. - * @param {import('../types.d.ts').HierarchizedEntry} entry - The entry to process. - * @param {import('../types.d.ts').Section} parent - The parent section. - */ - const handleEntry = (entry, parent) => { - const [headingNode, ...nodes] = structuredClone(entry.content.children); - const section = createSection(entry, headingNode); - - parseStability(section, nodes, entry); - parseList(section, nodes); - addDescription(section, nodes); - handleChildren(entry, section); - addAdditionalMetadata(section, parent, headingNode); - addToParent(section, parent); - promoteMiscChildren(section, parent); - }; - - /** - * Builds the module section from head metadata and entries. - * @param {import('../../metadata/types').MetadataEntry} head - The head metadata entry. - * @param {Array} entries - The list of metadata entries. - * @returns {import('../types.d.ts').ModuleSection} The constructed module section. - */ - return (head, entries) => { - const rootModule = { - type: 'module', - api: head.api, - // TODO(@avivkeller): This should be configurable - source: `doc/api/${head.api}.md`, - }; - - buildHierarchy(entries).forEach(entry => handleEntry(entry, rootModule)); - - return rootModule; - }; -}; diff --git a/packages/core/src/generators/sitemap/utils/createPageSitemapEntry.mjs b/packages/core/src/generators/sitemap/utils/createPageSitemapEntry.mjs deleted file mode 100644 index fb74faa4..00000000 --- a/packages/core/src/generators/sitemap/utils/createPageSitemapEntry.mjs +++ /dev/null @@ -1,18 +0,0 @@ -import { populate } from '../../../utils/configuration/templates.mjs'; - -/** - * Builds an API doc sitemap url. - * - * @param {import('../../metadata/types').MetadataEntry} entry - * @param {import('../../../utils/configuration/types').Configuration['sitemap']} config - * @returns {import('../types').SitemapEntry} - */ -export const createPageSitemapEntry = (entry, config, lastmod) => ({ - loc: populate(config.pageURL, { - ...config, - path: entry.path, - }), - lastmod, - changefreq: 'weekly', - priority: '0.8', -}); diff --git a/packages/core/src/generators/types.d.ts b/packages/core/src/generators/types.d.ts index 1265d77e..1a478ecd 100644 --- a/packages/core/src/generators/types.d.ts +++ b/packages/core/src/generators/types.d.ts @@ -1,97 +1,95 @@ -declare global { +/** + * ParallelWorker interface for distributing work across Node.js worker threads. + * Streams results as chunks complete, enabling pipeline parallelism. + */ +export interface ParallelWorker { /** - * ParallelWorker interface for distributing work across Node.js worker threads. - * Streams results as chunks complete, enabling pipeline parallelism. + * Processes items in parallel across worker threads and yields results + * as each chunk completes. Enables downstream processing to begin + * while upstream chunks are still being processed. + * + * @param items - Items to process (determines chunk distribution) + * @param opts - Additional options to pass to workers + * @yields Each chunk's results as they complete */ - export interface ParallelWorker { - /** - * Processes items in parallel across worker threads and yields results - * as each chunk completes. Enables downstream processing to begin - * while upstream chunks are still being processed. - * - * @param items - Items to process (determines chunk distribution) - * @param opts - Additional options to pass to workers - * @yields Each chunk's results as they complete - */ - stream( - items: T[], - opts?: Record - ): AsyncGenerator; - } + stream( + items: T[], + opts?: Record + ): AsyncGenerator; +} - export interface ParallelTaskOptions { - generatorSpecifier: string; - input: unknown[]; - itemIndices: number[]; - } +export interface ParallelTaskOptions { + generatorSpecifier: string; + input: unknown[]; + itemIndices: number[]; +} - /** - * Type for the generate function of a generator - * @template I - Input type - * @template O - Output type (can be AsyncGenerator or Promise) - */ - export type Generate = ( - input: I, - worker: ParallelWorker - ) => O extends AsyncGenerator ? O : Promise; +/** + * Type for the generate function of a generator + * @template I - Input type + * @template O - Output type (can be AsyncGenerator or Promise) + */ +export type Generate = ( + input: I, + worker: ParallelWorker +) => O extends AsyncGenerator ? O : Promise; - /** - * Type for the optional processChunk function of a generator - * @template I - Input type - * @template O - Output type - * @template D - Dependencies type - */ - export type ProcessChunk = ( - slicedInput: I[], - itemIndices: number[], - dependencies: D - ) => Promise; +/** + * Type for the optional processChunk function of a generator + * @template I - Input type + * @template O - Output type + * @template D - Dependencies type + */ +export type ProcessChunk = ( + slicedInput: I[], + itemIndices: number[], + dependencies: D +) => Promise; - export type GeneratorMetadata< - C extends any, - G extends Generate, - P extends ProcessChunk | undefined = undefined, - > = { - readonly defaultConfiguration?: C; +export type GeneratorMetadata< + C extends any, + G extends Generate, + P extends ProcessChunk | undefined = undefined, +> = { + readonly defaultConfiguration?: C; - // The name of the Generator - name: string; + // The name of the Generator + name: string; - /** - * The import specifier of the generator this one depends on. - * For example, '@node-core/doc-kit/generators/metadata'. - * - * If undefined, this is a top-level generator with no dependencies. - */ - dependsOn?: string; + /** + * The import specifier of the generator this one depends on. + * For example, '@doc-kittens/internal/metadata'. + * + * If undefined, this is a top-level generator with no dependencies. + */ + dependsOn?: string; - /** - * Generators are abstract and the different generators have different sort of inputs and outputs. - * For example, a MDX generator would take the raw AST and output MDX with React Components; - * Whereas a JSON generator would take the raw AST and output JSON; - * Then a React generator could receive either the raw AST or the MDX output and output React Components. - * (depending if they support such I/O) - * - * Hence you can combine different generators to achieve different outputs. - */ - generate: G; + /** + * Generators are abstract and the different generators have different sort of inputs and outputs. + * For example, a MDX generator would take the raw AST and output MDX with React Components; + * Whereas a JSON generator would take the raw AST and output JSON; + * Then a React generator could receive either the raw AST or the MDX output and output React Components. + * (depending if they support such I/O) + * + * Hence you can combine different generators to achieve different outputs. + */ + generate: G; - /** - * Optional method for chunk-level parallelization using real worker threads. - * Called by chunk-worker.mjs when processing items in parallel. - * - * Generators that implement this method can have their work distributed - * across multiple worker threads for true parallel processing. - * - * Input is automatically sliced to only include items at the specified indices, - * reducing serialization overhead. The itemIndices are remapped to 0-based - * indices into the sliced array. - * - * @param slicedInput - Sliced input containing only items for this chunk - * @param itemIndices - Array of 0-based indices into slicedInput - * @param dependencies - Generator options (without worker, which isn't serializable) - * @returns Array of results for the processed items - */ - processChunk?: P; - }; -} + /** + * Optional method for chunk-level parallelization using real worker threads. + * Called by chunk-worker.mjs when processing items in parallel. + * + * Generators that implement this method can have their work distributed + * across multiple worker threads for true parallel processing. + * + * Input is automatically sliced to only include items at the specified indices, + * reducing serialization overhead. The itemIndices are remapped to 0-based + * indices into the sliced array. + * + * @param slicedInput - Sliced input containing only items for this chunk + * @param itemIndices - Array of 0-based indices into slicedInput + * @param dependencies - Generator options (without worker, which isn't serializable) + * @returns Array of results for the processed items + */ + processChunk?: P; +}; diff --git a/packages/core/src/generators/web/template.html b/packages/core/src/generators/web/template.html deleted file mode 100644 index 71d0de0e..00000000 --- a/packages/core/src/generators/web/template.html +++ /dev/null @@ -1,31 +0,0 @@ - - - - - - - - {{title}} - - - - - - - - - - - - - - - - - - -
{{dehydrated}}
- - - diff --git a/packages/core/src/loader.mjs b/packages/core/src/loader.mjs index d0904cc2..09593738 100644 --- a/packages/core/src/loader.mjs +++ b/packages/core/src/loader.mjs @@ -8,7 +8,7 @@ const cache = new Map(); * Imports the single entry point which exports generate, processChunk, * name, dependsOn, and defaultConfiguration. * - * @param {string} specifier - Full import specifier (e.g. '@node-core/doc-kit/generators/ast') + * @param {string} specifier - Full import specifier (e.g. '@doc-kittens/internal/ast') * @returns {Promise} The loaded generator */ export const loadGenerator = async specifier => { diff --git a/packages/core/src/threading/__tests__/parallel.test.mjs b/packages/core/src/threading/__tests__/parallel.test.mjs index 3d0d56d3..0c212e9b 100644 --- a/packages/core/src/threading/__tests__/parallel.test.mjs +++ b/packages/core/src/threading/__tests__/parallel.test.mjs @@ -39,9 +39,9 @@ async function collectChunks(generator) { return chunks; } -const metadataSpecifier = '@node-core/doc-kit/generators/metadata'; +const metadataSpecifier = '@doc-kittens/internal/metadata'; const metadataGenerator = await loadGenerator(metadataSpecifier); -const astJsSpecifier = '@node-core/doc-kit/generators/ast-js'; +const astJsSpecifier = '@doc-kittens/internal/ast-js'; const astJsGenerator = await loadGenerator(astJsSpecifier); describe('createParallelWorker', () => { diff --git a/packages/core/src/utils/__tests__/generators.test.mjs b/packages/core/src/utils/__tests__/generators.test.mjs index cd0c48eb..d6ef025b 100644 --- a/packages/core/src/utils/__tests__/generators.test.mjs +++ b/packages/core/src/utils/__tests__/generators.test.mjs @@ -6,7 +6,6 @@ import { getVersionFromSemVer, coerceSemVer, getCompatibleVersions, - legacyToJSON, } from '../generators.mjs'; describe('groupNodesByModule', () => { @@ -80,46 +79,3 @@ describe('getCompatibleVersions', () => { assert.equal(result.length, 2); }); }); - -describe('legacyToJSON', () => { - const base = { - type: 'module', - source: 'lib/fs.js', - introduced_in: 'v0.10.0', - meta: {}, - stability: 2, - stabilityText: 'Stable', - classes: [], - methods: ['readFile'], - properties: [], - miscs: [], - modules: ['fs'], - globals: [], - }; - - it('serialises a normal section with all keys', () => { - const result = JSON.parse(legacyToJSON({ ...base, api: 'fs' })); - assert.ok('type' in result); - assert.ok('methods' in result); - assert.ok('modules' in result); - }); - - it('omits modules key for index sections', () => { - const result = JSON.parse(legacyToJSON({ ...base, api: 'index' })); - assert.ok(!('modules' in result)); - }); - - it('uses all.json key order when api is null', () => { - const result = JSON.parse(legacyToJSON({ ...base, api: null })); - // all.json only includes miscs, modules, classes, globals, methods - assert.ok('miscs' in result); - assert.ok('modules' in result); - assert.ok(!('type' in result)); - assert.ok(!('source' in result)); - }); - - it('passes extra args to JSON.stringify (e.g. indentation)', () => { - const result = legacyToJSON({ ...base, api: 'fs' }, null, 2); - assert.ok(result.includes('\n')); - }); -}); diff --git a/packages/core/src/utils/configuration/__tests__/index.test.mjs b/packages/core/src/utils/configuration/__tests__/index.test.mjs index 942255dc..3664577c 100644 --- a/packages/core/src/utils/configuration/__tests__/index.test.mjs +++ b/packages/core/src/utils/configuration/__tests__/index.test.mjs @@ -35,14 +35,14 @@ const { const createMockGenerators = () => new Map([ [ - '@node-core/doc-kit/generators/json', + '@node-core/doc-kit/src/generators/json', { name: 'json', defaultConfiguration: { format: 'json' } }, ], [ - '@node-core/doc-kit/generators/html', + '@node-core/doc-kit/src/generators/html', { name: 'html', defaultConfiguration: { format: 'html' } }, ], - ['@node-core/doc-kit/generators/markdown', { name: 'markdown' }], + ['@node-core/doc-kit/src/generators/markdown', { name: 'markdown' }], ]); // Helper to reset all mocks diff --git a/packages/core/src/utils/generators.mjs b/packages/core/src/utils/generators.mjs index 837c345a..9b36ac31 100644 --- a/packages/core/src/utils/generators.mjs +++ b/packages/core/src/utils/generators.mjs @@ -6,10 +6,10 @@ import { coerce, major } from 'semver'; * Groups all the API metadata nodes by module (`api` property) so that we can process each different file * based on the module it belongs to. * - * @param {Array} nodes The API metadata Nodes to be grouped + * @param {Array} nodes The API metadata Nodes to be grouped */ export const groupNodesByModule = nodes => { - /** @type {Map>} */ + /** @type {Map>} */ const groupedNodes = new Map(); for (const node of nodes) { @@ -81,55 +81,3 @@ export const getCompatibleVersions = (introduced, releases) => { */ export const leftHandAssign = (target, source) => Object.keys(source).forEach(k => k in target || (target[k] = source[k])); - -/** - * Transforms an object to JSON output consistent with the JSON version. - * @param {import('../generators/legacy-json/types').Section} section - The source object - * @param {any[]} args - * @returns {string} - The JSON output - */ -export const legacyToJSON = ( - { - api, - type, - source, - introduced_in, - meta, - stability, - stabilityText, - classes, - methods, - properties, - miscs, - modules, - globals, - }, - ...args -) => - JSON.stringify( - api == null - ? { - // all.json special order - miscs, - modules, - classes, - globals, - methods, - } - : { - type, - source, - introduced_in, - meta, - stability, - stabilityText, - classes, - methods, - properties, - miscs, - // index.json shouldn't have a `modules` key: - ...(api === 'index' ? undefined : { modules }), - globals, - }, - ...args - ); diff --git a/packages/core/src/utils/remark.mjs b/packages/core/src/utils/remark.mjs index 2192516b..0fa17365 100644 --- a/packages/core/src/utils/remark.mjs +++ b/packages/core/src/utils/remark.mjs @@ -1,10 +1,5 @@ 'use strict'; -import rehypeShikiji from '@node-core/rehype-shiki/plugin'; -import recmaJsx from 'recma-jsx'; -import recmaStringify from 'recma-stringify'; -import rehypeRaw from 'rehype-raw'; -import rehypeRecma from 'rehype-recma'; import rehypeStringify from 'rehype-stringify'; import remarkGfm from 'remark-gfm'; import remarkParse from 'remark-parse'; @@ -12,11 +7,7 @@ import remarkRehype from 'remark-rehype'; import remarkStringify from 'remark-stringify'; import { unified } from 'unified'; -import syntaxHighlighter, { highlighter } from './highlighter.mjs'; -import { AST_NODE_TYPES } from '../generators/jsx-ast/constants.mjs'; -import transformElements from '../generators/jsx-ast/utils/transformer.mjs'; - -const passThrough = ['element', ...Object.values(AST_NODE_TYPES.MDX)]; +import syntaxHighlighter from './highlighter.mjs'; /** * Retrieves an instance of Remark configured to parse GFM (GitHub Flavored Markdown) @@ -25,9 +16,11 @@ export const getRemark = () => unified().use(remarkParse).use(remarkGfm).use(remarkStringify); /** - * Retrieves an instance of Remark configured to output stringified HTML code + * Retrieves an instance of Remark configured to output stringified HTML code. + * + * @param {{ passThrough?: string[] }} [options] */ -export const getRemarkRehype = () => +export const getRemarkRehype = ({ passThrough = [] } = {}) => unified() .use(remarkParse) // We make Rehype ignore existing HTML nodes (just the node itself, not its children) @@ -41,41 +34,15 @@ export const getRemarkRehype = () => /** * Retrieves an instance of Remark configured to output stringified HTML code - * including parsing Code Boxes with syntax highlighting + * including parsing Code Boxes with syntax highlighting. + * + * @param {{ passThrough?: string[] }} [options] */ -export const getRemarkRehypeWithShiki = () => +export const getRemarkRehypeWithShiki = ({ passThrough = [] } = {}) => unified() .use(remarkParse) - // We make Rehype ignore existing HTML nodes (just the node itself, not its children) - // as these are nodes we manually created during the rehype process - // We also allow dangerous HTML to be passed through, since we have HTML within our Markdown - // and we trust the sources of the Markdown files .use(remarkRehype, { allowDangerousHtml: true, passThrough }) // This is a custom ad-hoc within the Shiki Rehype plugin, used to highlight code // and transform them into HAST nodes .use(syntaxHighlighter) - // We allow dangerous HTML to be passed through, since we have HTML within our Markdown - // and we trust the sources of the Markdown files .use(rehypeStringify, { allowDangerousHtml: true }); - -const singletonShiki = await rehypeShikiji({ highlighter }); - -/** - * Retrieves an instance of Remark configured to output JSX code. - * including parsing Code Boxes with syntax highlighting - */ -export const getRemarkRecma = () => - unified() - .use(remarkParse) - // We make Rehype ignore existing HTML nodes, and JSX nodes - // as these are nodes we manually created during the generation process - // We also allow dangerous HTML to be passed through, since we have HTML within our Markdown - // and we trust the sources of the Markdown files - .use(remarkRehype, { allowDangerousHtml: true, passThrough }) - // Any `raw` HTML in the markdown must be converted to AST in order for Recma to understand it - .use(rehypeRaw, { passThrough }) - .use(() => singletonShiki) - .use(transformElements) - .use(rehypeRecma) - .use(recmaJsx) - .use(recmaStringify); diff --git a/packages/extras/README.md b/packages/extras/README.md new file mode 100644 index 00000000..2e7dee3c --- /dev/null +++ b/packages/extras/README.md @@ -0,0 +1,19 @@ +# `@doc-kittens/extras` — Sphynx + +> _Fun fact: The Sphynx is hairless, distinctive, and a little odd — same goes for these one-off generators that don't fit anywhere else._ + +`@doc-kittens/extras` collects the specialised documentation generators that don't fit the legacy, internal, react, or website packages. + +## Generators + +| Export | Description | +| ---------------------------------- | ------------------------------------------------------------- | +| `@doc-kittens/extras/addon-verify` | Extracts buildable C++ addon source files from API docs | +| `@doc-kittens/extras/json-simple` | Emits a slimmed-down JSON view of `MetadataEntry[]` | +| `@doc-kittens/extras/man-page` | Emits a Node.js man page (`node.1`) from the CLI/env metadata | + +## Installation + +```sh +npm install @doc-kittens/extras +``` diff --git a/packages/extras/package.json b/packages/extras/package.json new file mode 100644 index 00000000..b188c45d --- /dev/null +++ b/packages/extras/package.json @@ -0,0 +1,27 @@ +{ + "name": "@doc-kittens/extras", + "type": "module", + "version": "1.0.0", + "description": "Specialised documentation generators (addon-verify, json-simple, man-page) for @doc-kittens", + "repository": { + "type": "git", + "url": "git+https://github.com/nodejs/doc-kit.git", + "directory": "packages/extras" + }, + "exports": { + "./addon-verify": "./src/generators/addon-verify/index.mjs", + "./json-simple": "./src/generators/json-simple/index.mjs", + "./man-page": "./src/generators/man-page/index.mjs", + "./src/*": "./src/*" + }, + "imports": { + "#core/*": "@node-core/doc-kit/src/*" + }, + "dependencies": { + "@node-core/doc-kit": "*", + "@doc-kittens/internal": "*", + "dedent": "^1.7.2", + "unist-util-remove": "^4.0.0", + "unist-util-visit": "^5.1.0" + } +} diff --git a/packages/core/src/generators/addon-verify/README.md b/packages/extras/src/generators/addon-verify/README.md similarity index 100% rename from packages/core/src/generators/addon-verify/README.md rename to packages/extras/src/generators/addon-verify/README.md diff --git a/packages/core/src/generators/addon-verify/index.mjs b/packages/extras/src/generators/addon-verify/index.mjs similarity index 83% rename from packages/core/src/generators/addon-verify/index.mjs rename to packages/extras/src/generators/addon-verify/index.mjs index 8b888999..b31a1729 100644 --- a/packages/core/src/generators/addon-verify/index.mjs +++ b/packages/extras/src/generators/addon-verify/index.mjs @@ -3,20 +3,20 @@ import { mkdir } from 'node:fs/promises'; import { join } from 'node:path'; +import getConfig from '#core/utils/configuration/index.mjs'; +import { writeFile } from '#core/utils/file.mjs'; import { visit } from 'unist-util-visit'; -import { EXTRACT_CODE_FILENAME_COMMENT } from './constants.mjs'; -import { generateFileList } from './utils/generateFileList.mjs'; +import { EXTRACT_CODE_FILENAME_COMMENT } from '../../utils/addon-verify/constants.mjs'; +import { generateFileList } from '../../utils/addon-verify/fileList.mjs'; import { generateSectionFolderName, isBuildableSection, normalizeSectionName, -} from './utils/section.mjs'; -import getConfig from '../../utils/configuration/index.mjs'; -import { writeFile } from '../../utils/file.mjs'; +} from '../../utils/addon-verify/section.mjs'; export const name = 'addon-verify'; -export const dependsOn = '@node-core/doc-kit/generators/metadata'; +export const dependsOn = '@doc-kittens/internal/metadata'; /** * Generates a file list from code blocks. * diff --git a/packages/core/src/generators/addon-verify/types.d.ts b/packages/extras/src/generators/addon-verify/types.d.ts similarity index 56% rename from packages/core/src/generators/addon-verify/types.d.ts rename to packages/extras/src/generators/addon-verify/types.d.ts index c893d8d0..47265d4b 100644 --- a/packages/core/src/generators/addon-verify/types.d.ts +++ b/packages/extras/src/generators/addon-verify/types.d.ts @@ -1,4 +1,4 @@ -import type { MetadataEntry } from '../metadata/types'; +import type { MetadataEntry } from '@doc-kittens/internal/src/generators/metadata/types'; export type Generator = GeneratorMetadata< {}, diff --git a/packages/core/src/generators/json-simple/README.md b/packages/extras/src/generators/json-simple/README.md similarity index 100% rename from packages/core/src/generators/json-simple/README.md rename to packages/extras/src/generators/json-simple/README.md diff --git a/packages/core/src/generators/json-simple/index.mjs b/packages/extras/src/generators/json-simple/index.mjs similarity index 84% rename from packages/core/src/generators/json-simple/index.mjs rename to packages/extras/src/generators/json-simple/index.mjs index 41c04ff0..83f08b20 100644 --- a/packages/core/src/generators/json-simple/index.mjs +++ b/packages/extras/src/generators/json-simple/index.mjs @@ -2,14 +2,13 @@ import { join } from 'node:path'; +import getConfig from '#core/utils/configuration/index.mjs'; +import { writeFile } from '#core/utils/file.mjs'; +import { UNIST } from '#core/utils/queries/index.mjs'; import { remove } from 'unist-util-remove'; -import getConfig from '../../utils/configuration/index.mjs'; -import { writeFile } from '../../utils/file.mjs'; -import { UNIST } from '../../utils/queries/index.mjs'; - export const name = 'json-simple'; -export const dependsOn = '@node-core/doc-kit/generators/metadata'; +export const dependsOn = '@doc-kittens/internal/metadata'; /** * Generates the simplified JSON version of the API docs diff --git a/packages/core/src/generators/json-simple/types.d.ts b/packages/extras/src/generators/json-simple/types.d.ts similarity index 56% rename from packages/core/src/generators/json-simple/types.d.ts rename to packages/extras/src/generators/json-simple/types.d.ts index 2eec8938..f7ff1bbe 100644 --- a/packages/core/src/generators/json-simple/types.d.ts +++ b/packages/extras/src/generators/json-simple/types.d.ts @@ -1,4 +1,4 @@ -import type { MetadataEntry } from '../metadata/types'; +import type { MetadataEntry } from '@doc-kittens/internal/src/generators/metadata/types'; export type Generator = GeneratorMetadata< {}, diff --git a/packages/core/src/generators/man-page/README.md b/packages/extras/src/generators/man-page/README.md similarity index 100% rename from packages/core/src/generators/man-page/README.md rename to packages/extras/src/generators/man-page/README.md diff --git a/packages/core/src/generators/man-page/index.mjs b/packages/extras/src/generators/man-page/index.mjs similarity index 81% rename from packages/core/src/generators/man-page/index.mjs rename to packages/extras/src/generators/man-page/index.mjs index 89c83337..19a54e18 100644 --- a/packages/core/src/generators/man-page/index.mjs +++ b/packages/extras/src/generators/man-page/index.mjs @@ -3,15 +3,16 @@ import { readFile } from 'node:fs/promises'; import { join } from 'node:path'; +import getConfig from '#core/utils/configuration/index.mjs'; +import { writeFile } from '#core/utils/file.mjs'; + import { convertOptionToMandoc, convertEnvVarToMandoc, -} from './utils/converter.mjs'; -import getConfig from '../../utils/configuration/index.mjs'; -import { writeFile } from '../../utils/file.mjs'; +} from '../../utils/man-page/converter.mjs'; export const name = 'man-page'; -export const dependsOn = '@node-core/doc-kit/generators/metadata'; +export const dependsOn = '@doc-kittens/internal/metadata'; export const defaultConfiguration = { fileName: 'node.1', cliOptionsHeaderSlug: 'options', @@ -20,10 +21,10 @@ export const defaultConfiguration = { }; /** - * @param {Array} components + * @param {Array} components * @param {number} start * @param {number} end - * @param {(element: import('../metadata/types').MetadataEntry) => string} convert + * @param {(element: import('@doc-kittens/internal/src/generators/metadata/types').MetadataEntry) => string} convert * @returns {string} */ function extractMandoc(components, start, end, convert) { diff --git a/packages/core/src/generators/man-page/template.1 b/packages/extras/src/generators/man-page/template.1 similarity index 100% rename from packages/core/src/generators/man-page/template.1 rename to packages/extras/src/generators/man-page/template.1 diff --git a/packages/core/src/generators/man-page/types.d.ts b/packages/extras/src/generators/man-page/types.d.ts similarity index 72% rename from packages/core/src/generators/man-page/types.d.ts rename to packages/extras/src/generators/man-page/types.d.ts index 3bab3f6f..6ea16854 100644 --- a/packages/core/src/generators/man-page/types.d.ts +++ b/packages/extras/src/generators/man-page/types.d.ts @@ -1,4 +1,4 @@ -import { MetadataEntry } from '../metadata/types'; +import { MetadataEntry } from '@doc-kittens/internal/src/generators/metadata/types'; export type Generator = GeneratorMetadata< { diff --git a/packages/core/src/generators/addon-verify/utils/__tests__/generateFileList.test.mjs b/packages/extras/src/utils/addon-verify/__tests__/fileList.test.mjs similarity index 96% rename from packages/core/src/generators/addon-verify/utils/__tests__/generateFileList.test.mjs rename to packages/extras/src/utils/addon-verify/__tests__/fileList.test.mjs index d32f1a6f..5b388492 100644 --- a/packages/core/src/generators/addon-verify/utils/__tests__/generateFileList.test.mjs +++ b/packages/extras/src/utils/addon-verify/__tests__/fileList.test.mjs @@ -1,7 +1,7 @@ import assert from 'node:assert/strict'; import { describe, it } from 'node:test'; -import { generateFileList } from '../generateFileList.mjs'; +import { generateFileList } from '../fileList.mjs'; describe('generateFileList', () => { it('should transform test.js files with updated require paths', () => { diff --git a/packages/core/src/generators/addon-verify/utils/__tests__/section.test.mjs b/packages/extras/src/utils/addon-verify/__tests__/section.test.mjs similarity index 100% rename from packages/core/src/generators/addon-verify/utils/__tests__/section.test.mjs rename to packages/extras/src/utils/addon-verify/__tests__/section.test.mjs diff --git a/packages/core/src/generators/addon-verify/constants.mjs b/packages/extras/src/utils/addon-verify/constants.mjs similarity index 100% rename from packages/core/src/generators/addon-verify/constants.mjs rename to packages/extras/src/utils/addon-verify/constants.mjs diff --git a/packages/core/src/generators/addon-verify/utils/generateFileList.mjs b/packages/extras/src/utils/addon-verify/fileList.mjs similarity index 100% rename from packages/core/src/generators/addon-verify/utils/generateFileList.mjs rename to packages/extras/src/utils/addon-verify/fileList.mjs diff --git a/packages/core/src/generators/addon-verify/utils/section.mjs b/packages/extras/src/utils/addon-verify/section.mjs similarity index 100% rename from packages/core/src/generators/addon-verify/utils/section.mjs rename to packages/extras/src/utils/addon-verify/section.mjs diff --git a/packages/core/src/generators/man-page/utils/__tests__/converter.test.mjs b/packages/extras/src/utils/man-page/__tests__/converter.test.mjs similarity index 100% rename from packages/core/src/generators/man-page/utils/__tests__/converter.test.mjs rename to packages/extras/src/utils/man-page/__tests__/converter.test.mjs diff --git a/packages/core/src/generators/man-page/utils/converter.mjs b/packages/extras/src/utils/man-page/converter.mjs similarity index 94% rename from packages/core/src/generators/man-page/utils/converter.mjs rename to packages/extras/src/utils/man-page/converter.mjs index 2662ad7d..5731ad9e 100644 --- a/packages/core/src/generators/man-page/utils/converter.mjs +++ b/packages/extras/src/utils/man-page/converter.mjs @@ -122,7 +122,7 @@ const formatFlag = flag => * This function formats command-line options, including flags and descriptions, * for display in Unix manual pages using Mandoc. * - * @param {import('../../metadata/types').MetadataEntry} element - The metadata entry containing details about the API option. + * @param {import('@doc-kittens/internal/src/generators/metadata/types').MetadataEntry} element - The metadata entry containing details about the API option. * @returns {string} The Mandoc formatted string representing the API option, including flags and content. */ export function convertOptionToMandoc(element) { @@ -146,7 +146,7 @@ export function convertOptionToMandoc(element) { * This function formats environment variables for Unix manual pages, converting * the variable name and value, along with any associated descriptions, into Mandoc. * - * @param {import('../../metadata/types').MetadataEntry} element - The metadata entry containing details about the environment variable. + * @param {import('@doc-kittens/internal/src/generators/metadata/types').MetadataEntry} element - The metadata entry containing details about the environment variable. * @returns {string} The Mandoc formatted representation of the environment variable and its content. */ export function convertEnvVarToMandoc(element) { diff --git a/packages/internal/README.md b/packages/internal/README.md new file mode 100644 index 00000000..c00ce3c3 --- /dev/null +++ b/packages/internal/README.md @@ -0,0 +1,19 @@ +# `@doc-kittens/internal` — Russian Blue + +> _Fun fact: The Russian Blue is renowned for its quiet, reserved nature — much like the internal generators that quietly power everything downstream._ + +`@doc-kittens/internal` provides the foundational documentation generators (AST construction, AST-JS extraction, and metadata aggregation) that other `@doc-kittens` packages build on top of. + +## Generators + +| Export | Description | +| -------------------------------- | ------------------------------------------------------------------------- | +| `@doc-kittens/internal/ast` | Parses Markdown files into a unified AST tree | +| `@doc-kittens/internal/ast-js` | Extracts an AST from JavaScript source files (acorn-based) | +| `@doc-kittens/internal/metadata` | Walks the AST and produces `MetadataEntry[]` consumed by other generators | + +## Installation + +```sh +npm install @doc-kittens/internal +``` diff --git a/packages/internal/package.json b/packages/internal/package.json new file mode 100644 index 00000000..5bcc2f19 --- /dev/null +++ b/packages/internal/package.json @@ -0,0 +1,31 @@ +{ + "name": "@doc-kittens/internal", + "type": "module", + "version": "1.0.0", + "description": "Internal documentation generators (AST, AST-JS, metadata) for @doc-kittens", + "repository": { + "type": "git", + "url": "git+https://github.com/nodejs/doc-kit.git", + "directory": "packages/internal" + }, + "exports": { + "./ast": "./src/generators/ast/index.mjs", + "./ast-js": "./src/generators/ast-js/index.mjs", + "./metadata": "./src/generators/metadata/index.mjs", + "./src/*": "./src/*" + }, + "imports": { + "#core/*": "@node-core/doc-kit/src/*" + }, + "dependencies": { + "@node-core/doc-kit": "*", + "acorn": "^8.16.0", + "github-slugger": "^2.0.0", + "unist-builder": "^4.0.0", + "unist-util-find-after": "^5.0.0", + "unist-util-remove": "^4.0.0", + "unist-util-select": "^5.1.0", + "unist-util-visit": "^5.1.0", + "yaml": "^2.8.3" + } +} diff --git a/packages/core/src/generators/ast-js/README.md b/packages/internal/src/generators/ast-js/README.md similarity index 100% rename from packages/core/src/generators/ast-js/README.md rename to packages/internal/src/generators/ast-js/README.md diff --git a/packages/core/src/generators/ast-js/index.mjs b/packages/internal/src/generators/ast-js/index.mjs similarity index 95% rename from packages/core/src/generators/ast-js/index.mjs rename to packages/internal/src/generators/ast-js/index.mjs index 3e666101..93c0e08b 100644 --- a/packages/core/src/generators/ast-js/index.mjs +++ b/packages/internal/src/generators/ast-js/index.mjs @@ -3,11 +3,10 @@ import { readFile } from 'node:fs/promises'; import { extname } from 'node:path'; +import getConfig from '#core/utils/configuration/index.mjs'; import { parse } from 'acorn'; import { globSync } from 'tinyglobby'; -import getConfig from '../../utils/configuration/index.mjs'; - export const name = 'ast-js'; /** diff --git a/packages/core/src/generators/ast-js/types.d.ts b/packages/internal/src/generators/ast-js/types.d.ts similarity index 100% rename from packages/core/src/generators/ast-js/types.d.ts rename to packages/internal/src/generators/ast-js/types.d.ts diff --git a/packages/core/src/generators/ast/README.md b/packages/internal/src/generators/ast/README.md similarity index 100% rename from packages/core/src/generators/ast/README.md rename to packages/internal/src/generators/ast/README.md diff --git a/packages/core/src/generators/ast/index.mjs b/packages/internal/src/generators/ast/index.mjs similarity index 85% rename from packages/core/src/generators/ast/index.mjs rename to packages/internal/src/generators/ast/index.mjs index 46ef0e50..155a7c62 100644 --- a/packages/core/src/generators/ast/index.mjs +++ b/packages/internal/src/generators/ast/index.mjs @@ -3,14 +3,14 @@ import { readFile } from 'node:fs/promises'; import { relative, sep } from 'node:path/posix'; +import getConfig from '#core/utils/configuration/index.mjs'; +import { withExt } from '#core/utils/file.mjs'; +import { QUERIES } from '#core/utils/queries/index.mjs'; +import { getRemark } from '#core/utils/remark.mjs'; import globParent from 'glob-parent'; import { globSync } from 'tinyglobby'; -import { STABILITY_INDEX_URL } from './constants.mjs'; -import getConfig from '../../utils/configuration/index.mjs'; -import { withExt } from '../../utils/file.mjs'; -import { QUERIES } from '../../utils/queries/index.mjs'; -import { getRemark } from '../../utils/remark.mjs'; +import { STABILITY_INDEX_URL } from '../../utils/ast/constants.mjs'; export const name = 'ast'; diff --git a/packages/core/src/generators/ast/types.d.ts b/packages/internal/src/generators/ast/types.d.ts similarity index 100% rename from packages/core/src/generators/ast/types.d.ts rename to packages/internal/src/generators/ast/types.d.ts diff --git a/packages/core/src/generators/metadata/README.md b/packages/internal/src/generators/metadata/README.md similarity index 100% rename from packages/core/src/generators/metadata/README.md rename to packages/internal/src/generators/metadata/README.md diff --git a/packages/core/src/generators/metadata/index.mjs b/packages/internal/src/generators/metadata/index.mjs similarity index 81% rename from packages/core/src/generators/metadata/index.mjs rename to packages/internal/src/generators/metadata/index.mjs index 97c23b11..f9b3a02f 100644 --- a/packages/core/src/generators/metadata/index.mjs +++ b/packages/internal/src/generators/metadata/index.mjs @@ -1,11 +1,12 @@ 'use strict'; -import { parseApiDoc } from './utils/parse.mjs'; -import { parseTypeMap } from '../../parsers/json.mjs'; -import getConfig from '../../utils/configuration/index.mjs'; +import { parseTypeMap } from '#core/parsers/json.mjs'; +import getConfig from '#core/utils/configuration/index.mjs'; + +import { parseApiDoc } from '../../utils/metadata/parse.mjs'; export const name = 'metadata'; -export const dependsOn = '@node-core/doc-kit/generators/ast'; +export const dependsOn = '@doc-kittens/internal/ast'; export const defaultConfiguration = { typeMap: import.meta.resolve('./typeMap.json'), }; diff --git a/packages/core/src/generators/metadata/types.d.ts b/packages/internal/src/generators/metadata/types.d.ts similarity index 100% rename from packages/core/src/generators/metadata/types.d.ts rename to packages/internal/src/generators/metadata/types.d.ts diff --git a/packages/core/src/generators/ast/constants.mjs b/packages/internal/src/utils/ast/constants.mjs similarity index 100% rename from packages/core/src/generators/ast/constants.mjs rename to packages/internal/src/utils/ast/constants.mjs diff --git a/packages/core/src/generators/metadata/utils/__tests__/parse.test.mjs b/packages/internal/src/utils/metadata/__tests__/parse.test.mjs similarity index 100% rename from packages/core/src/generators/metadata/utils/__tests__/parse.test.mjs rename to packages/internal/src/utils/metadata/__tests__/parse.test.mjs diff --git a/packages/core/src/generators/metadata/utils/__tests__/slugger.test.mjs b/packages/internal/src/utils/metadata/__tests__/slugger.test.mjs similarity index 100% rename from packages/core/src/generators/metadata/utils/__tests__/slugger.test.mjs rename to packages/internal/src/utils/metadata/__tests__/slugger.test.mjs diff --git a/packages/core/src/generators/metadata/utils/__tests__/transformers.test.mjs b/packages/internal/src/utils/metadata/__tests__/transformers.test.mjs similarity index 100% rename from packages/core/src/generators/metadata/utils/__tests__/transformers.test.mjs rename to packages/internal/src/utils/metadata/__tests__/transformers.test.mjs diff --git a/packages/core/src/generators/metadata/utils/__tests__/yaml.test.mjs b/packages/internal/src/utils/metadata/__tests__/yaml.test.mjs similarity index 100% rename from packages/core/src/generators/metadata/utils/__tests__/yaml.test.mjs rename to packages/internal/src/utils/metadata/__tests__/yaml.test.mjs diff --git a/packages/core/src/generators/metadata/constants.mjs b/packages/internal/src/utils/metadata/constants.mjs similarity index 100% rename from packages/core/src/generators/metadata/constants.mjs rename to packages/internal/src/utils/metadata/constants.mjs diff --git a/packages/core/src/generators/metadata/maps/builtin.json b/packages/internal/src/utils/metadata/maps/builtin.json similarity index 100% rename from packages/core/src/generators/metadata/maps/builtin.json rename to packages/internal/src/utils/metadata/maps/builtin.json diff --git a/packages/core/src/generators/metadata/maps/mdn.json b/packages/internal/src/utils/metadata/maps/mdn.json similarity index 100% rename from packages/core/src/generators/metadata/maps/mdn.json rename to packages/internal/src/utils/metadata/maps/mdn.json diff --git a/packages/core/src/generators/metadata/utils/parse.mjs b/packages/internal/src/utils/metadata/parse.mjs similarity index 95% rename from packages/core/src/generators/metadata/utils/parse.mjs rename to packages/internal/src/utils/metadata/parse.mjs index 7a3d98b2..0032fccf 100644 --- a/packages/core/src/generators/metadata/utils/parse.mjs +++ b/packages/internal/src/utils/metadata/parse.mjs @@ -2,6 +2,9 @@ import { basename, sep } from 'node:path/posix'; +import { UNIST } from '#core/utils/queries/index.mjs'; +import { getRemark } from '#core/utils/remark.mjs'; +import { relative } from '#core/utils/url.mjs'; import { slug } from 'github-slugger'; import { u as createTree } from 'unist-builder'; import { findAfter } from 'unist-util-find-after'; @@ -9,6 +12,7 @@ import { remove } from 'unist-util-remove'; import { selectAll } from 'unist-util-select'; import { SKIP, visit } from 'unist-util-visit'; +import { IGNORE_STABILITY_STEMS } from './constants.mjs'; import createNodeSlugger from './slugger.mjs'; import { transformNodeToHeading } from './transformers.mjs'; import { @@ -19,10 +23,6 @@ import { visitTextWithUnixManualNode, visitYAML, } from './visitors.mjs'; -import { UNIST } from '../../../utils/queries/index.mjs'; -import { getRemark } from '../../../utils/remark.mjs'; -import { relative } from '../../../utils/url.mjs'; -import { IGNORE_STABILITY_STEMS } from '../constants.mjs'; // Creates an instance of the Remark processor with GFM support const remarkProcessor = getRemark(); diff --git a/packages/core/src/generators/metadata/utils/slugger.mjs b/packages/internal/src/utils/metadata/slugger.mjs similarity index 93% rename from packages/core/src/generators/metadata/utils/slugger.mjs rename to packages/internal/src/utils/metadata/slugger.mjs index 40c7ec7c..d194cb62 100644 --- a/packages/core/src/generators/metadata/utils/slugger.mjs +++ b/packages/internal/src/utils/metadata/slugger.mjs @@ -2,7 +2,7 @@ import GitHubSlugger, { slug as defaultSlugFn } from 'github-slugger'; -import { DOC_API_SLUGS_REPLACEMENTS } from '../constants.mjs'; +import { DOC_API_SLUGS_REPLACEMENTS } from './constants.mjs'; /** * Creates a modified version of the GitHub Slugger diff --git a/packages/core/src/generators/metadata/utils/transformers.mjs b/packages/internal/src/utils/metadata/transformers.mjs similarity index 96% rename from packages/core/src/generators/metadata/utils/transformers.mjs rename to packages/internal/src/utils/metadata/transformers.mjs index 8b8e51a3..c1883faf 100644 --- a/packages/core/src/generators/metadata/utils/transformers.mjs +++ b/packages/internal/src/utils/metadata/transformers.mjs @@ -1,12 +1,13 @@ +import { transformNodesToString } from '#core/utils/unist.mjs'; + import { DOC_MAN_BASE_URL, DOC_API_HEADING_TYPES, TYPE_GENERIC_REGEX, -} from '../constants.mjs'; +} from './constants.mjs'; +import BUILTIN_TYPE_MAP from './maps/builtin.json' with { type: 'json' }; +import MDN_TYPE_MAP from './maps/mdn.json' with { type: 'json' }; import { slug } from './slugger.mjs'; -import { transformNodesToString } from '../../../utils/unist.mjs'; -import BUILTIN_TYPE_MAP from '../maps/builtin.json' with { type: 'json' }; -import MDN_TYPE_MAP from '../maps/mdn.json' with { type: 'json' }; /** * @param {string} text The inner text diff --git a/packages/core/src/generators/metadata/utils/visitors.mjs b/packages/internal/src/utils/metadata/visitors.mjs similarity index 95% rename from packages/core/src/generators/metadata/utils/visitors.mjs rename to packages/internal/src/utils/metadata/visitors.mjs index 0008e1de..5d0c9293 100644 --- a/packages/core/src/generators/metadata/utils/visitors.mjs +++ b/packages/internal/src/utils/metadata/visitors.mjs @@ -1,5 +1,9 @@ 'use strict'; +import { lazy } from '#core/utils/misc.mjs'; +import { QUERIES } from '#core/utils/queries/index.mjs'; +import { getRemark } from '#core/utils/remark.mjs'; +import { transformNodesToString } from '#core/utils/unist.mjs'; import { SKIP } from 'unist-util-visit'; import { @@ -7,10 +11,6 @@ import { transformUnixManualToLink, } from './transformers.mjs'; import { extractYamlContent, parseYAMLIntoMetadata } from './yaml.mjs'; -import { lazy } from '../../../utils/misc.mjs'; -import { QUERIES } from '../../../utils/queries/index.mjs'; -import { getRemark } from '../../../utils/remark.mjs'; -import { transformNodesToString } from '../../../utils/unist.mjs'; const remark = lazy(getRemark); /** diff --git a/packages/core/src/generators/metadata/utils/yaml.mjs b/packages/internal/src/utils/metadata/yaml.mjs similarity index 96% rename from packages/core/src/generators/metadata/utils/yaml.mjs rename to packages/internal/src/utils/metadata/yaml.mjs index 6b9ba9d9..b362558c 100644 --- a/packages/core/src/generators/metadata/utils/yaml.mjs +++ b/packages/internal/src/utils/metadata/yaml.mjs @@ -1,9 +1,8 @@ 'use strict'; +import { QUERIES } from '#core/utils/queries/index.mjs'; import yaml from 'yaml'; -import { QUERIES } from '../../../utils/queries/index.mjs'; - /** * Extracts raw YAML content from a node * diff --git a/packages/legacy/README.md b/packages/legacy/README.md new file mode 100644 index 00000000..32d0c010 --- /dev/null +++ b/packages/legacy/README.md @@ -0,0 +1,20 @@ +# `@doc-kittens/legacy` — Siamese + +> _Fun fact: The Siamese is one of the oldest recognized breeds._ + +`@doc-kittens/legacy` provides the legacy JSON and HTML documentation generators for the `@doc-kittens` ecosystem. These generators produce output compatible with the historical Node.js API documentation format. + +## Generators + +| Export | Description | +| ------------------------------ | -------------------------------------------------- | +| `@doc-kittens/legacy/json` | Generates per-module legacy JSON files | +| `@doc-kittens/legacy/json/all` | Aggregates all modules into a single `all.json` | +| `@doc-kittens/legacy/html` | Generates per-module legacy HTML pages with assets | +| `@doc-kittens/legacy/html/all` | Aggregates all modules into a single `all.html` | + +## Installation + +```sh +npm install @doc-kittens/legacy +``` diff --git a/packages/legacy/package.json b/packages/legacy/package.json new file mode 100644 index 00000000..1b7148c4 --- /dev/null +++ b/packages/legacy/package.json @@ -0,0 +1,25 @@ +{ + "name": "@doc-kittens/legacy", + "type": "module", + "version": "1.0.0", + "description": "Sphinx — Legacy documentation generators (JSON and HTML) for @doc-kittens", + "repository": { + "type": "git", + "url": "git+https://github.com/nodejs/doc-kit.git", + "directory": "packages/legacy" + }, + "exports": { + "./json": "./src/generators/json/index.mjs", + "./json/all": "./src/generators/json-all/index.mjs", + "./html": "./src/generators/html/index.mjs", + "./html/all": "./src/generators/html-all/index.mjs", + "./src/*": "./src/*" + }, + "imports": { + "#core/*": "@node-core/doc-kit/src/*" + }, + "dependencies": { + "@node-core/doc-kit": "*", + "@doc-kittens/internal": "*" + } +} diff --git a/packages/core/src/generators/legacy-html-all/README.md b/packages/legacy/src/generators/html-all/README.md similarity index 75% rename from packages/core/src/generators/legacy-html-all/README.md rename to packages/legacy/src/generators/html-all/README.md index b2a74df0..1bb07952 100644 --- a/packages/core/src/generators/legacy-html-all/README.md +++ b/packages/legacy/src/generators/html-all/README.md @@ -1,10 +1,10 @@ -## `legacy-html-all` Generator +# `@doc-kittens/legacy/html/all` -The `legacy-html-all` generator creates a single `all.html` file containing all API documentation modules in one file, based on the output from the `legacy-html` generator. +The `legacy-html-all` generator creates a single `all.html` file containing every API documentation module in one page, based on the output from [`legacy-html`](../html/README.md). -### Configuring +Depends on: `@doc-kittens/legacy/html` -The `legacy-html-all` generator accepts the following configuration options: +## Configuring | Name | Type | Default | Description | | -------------- | --------- | ---------------------------- | ---------------------------------------------- | diff --git a/packages/core/src/generators/legacy-html-all/index.mjs b/packages/legacy/src/generators/html-all/index.mjs similarity index 78% rename from packages/core/src/generators/legacy-html-all/index.mjs rename to packages/legacy/src/generators/html-all/index.mjs index 43527413..f3d40bd5 100644 --- a/packages/core/src/generators/legacy-html-all/index.mjs +++ b/packages/legacy/src/generators/html-all/index.mjs @@ -3,15 +3,16 @@ import { readFile, writeFile } from 'node:fs/promises'; import { join } from 'node:path'; -import getConfig from '../../utils/configuration/index.mjs'; -import { minifyHTML } from '../../utils/html-minifier.mjs'; -import { getRemarkRehype } from '../../utils/remark.mjs'; -import { defaultConfiguration as legacyHtmlDefaults } from '../legacy-html/index.mjs'; -import { replaceTemplateValues } from '../legacy-html/utils/replaceTemplateValues.mjs'; -import tableOfContents from '../legacy-html/utils/tableOfContents.mjs'; +import getConfig from '#core/utils/configuration/index.mjs'; +import { minifyHTML } from '#core/utils/html-minifier.mjs'; +import { getRemarkRehype } from '#core/utils/remark.mjs'; + +import replaceTemplateValues from '../../utils/html/template.mjs'; +import buildToC, { parseNavigationNode } from '../../utils/html/toc.mjs'; +import { defaultConfiguration as legacyHtmlDefaults } from '../html/index.mjs'; export const name = 'legacy-html-all'; -export const dependsOn = '@node-core/doc-kit/generators/legacy-html'; +export const dependsOn = '@doc-kittens/legacy/html'; export const defaultConfiguration = { templatePath: legacyHtmlDefaults.templatePath, }; @@ -48,9 +49,9 @@ export async function generate(input) { // Generates the global Table of Contents (Sidebar Navigation) const parsedSideNav = remarkWithRehype.processSync( - tableOfContents(sideNavigationFromValues, { + buildToC(sideNavigationFromValues, { maxDepth: 1, - parser: tableOfContents.parseNavigationNode, + parser: parseNavigationNode, }) ); diff --git a/packages/core/src/generators/legacy-html-all/types.d.ts b/packages/legacy/src/generators/html-all/types.d.ts similarity index 80% rename from packages/core/src/generators/legacy-html-all/types.d.ts rename to packages/legacy/src/generators/html-all/types.d.ts index ed3978fa..6ba8850d 100644 --- a/packages/core/src/generators/legacy-html-all/types.d.ts +++ b/packages/legacy/src/generators/html-all/types.d.ts @@ -1,3 +1,5 @@ +import { GeneratorMetadata, Generate } from '#core/generators/types'; + export interface TemplateValues { api: string; added: string; diff --git a/packages/core/src/generators/legacy-html/README.md b/packages/legacy/src/generators/html/README.md similarity index 85% rename from packages/core/src/generators/legacy-html/README.md rename to packages/legacy/src/generators/html/README.md index 891ea5d5..60264a84 100644 --- a/packages/core/src/generators/legacy-html/README.md +++ b/packages/legacy/src/generators/html/README.md @@ -1,10 +1,8 @@ -## `legacy-html` Generator +# `@doc-kittens/legacy/html` -The `legacy-html` generator creates legacy HTML documentation pages for Node.js API documentation with included assets and styles for retro-compatibility. +The `legacy-html` generator creates one legacy HTML documentation page per API module, including bundled assets and styles, for retro-compatibility with the historical Node.js documentation format. -### Configuring - -The `legacy-html` generator accepts the following configuration options: +## Configuring | Name | Type | Default | Description | | ----------------------- | ---------- | --------------------------------------------- | ------------------------------------------------------------------------ | diff --git a/packages/core/src/generators/legacy-html/assets/api.js b/packages/legacy/src/generators/html/assets/api.js similarity index 100% rename from packages/core/src/generators/legacy-html/assets/api.js rename to packages/legacy/src/generators/html/assets/api.js diff --git a/packages/core/src/generators/legacy-html/assets/js-flavor-cjs.svg b/packages/legacy/src/generators/html/assets/js-flavor-cjs.svg similarity index 100% rename from packages/core/src/generators/legacy-html/assets/js-flavor-cjs.svg rename to packages/legacy/src/generators/html/assets/js-flavor-cjs.svg diff --git a/packages/core/src/generators/legacy-html/assets/js-flavor-esm.svg b/packages/legacy/src/generators/html/assets/js-flavor-esm.svg similarity index 100% rename from packages/core/src/generators/legacy-html/assets/js-flavor-esm.svg rename to packages/legacy/src/generators/html/assets/js-flavor-esm.svg diff --git a/packages/core/src/generators/legacy-html/assets/style.css b/packages/legacy/src/generators/html/assets/style.css similarity index 100% rename from packages/core/src/generators/legacy-html/assets/style.css rename to packages/legacy/src/generators/html/assets/style.css diff --git a/packages/core/src/generators/legacy-html/index.mjs b/packages/legacy/src/generators/html/index.mjs similarity index 82% rename from packages/core/src/generators/legacy-html/index.mjs rename to packages/legacy/src/generators/html/index.mjs index 332d0df1..18daf736 100644 --- a/packages/core/src/generators/legacy-html/index.mjs +++ b/packages/legacy/src/generators/html/index.mjs @@ -3,18 +3,22 @@ import { readFile, cp } from 'node:fs/promises'; import { basename, join } from 'node:path'; -import buildContent from './utils/buildContent.mjs'; -import { replaceTemplateValues } from './utils/replaceTemplateValues.mjs'; -import tableOfContents from './utils/tableOfContents.mjs'; -import getConfig from '../../utils/configuration/index.mjs'; -import { GITHUB_EDIT_URL } from '../../utils/configuration/templates.mjs'; -import { writeFile } from '../../utils/file.mjs'; -import { groupNodesByModule } from '../../utils/generators.mjs'; -import { minifyHTML } from '../../utils/html-minifier.mjs'; -import { getRemarkRehypeWithShiki } from '../../utils/remark.mjs'; +import getConfig from '#core/utils/configuration/index.mjs'; +import { GITHUB_EDIT_URL } from '#core/utils/configuration/templates.mjs'; +import { writeFile } from '#core/utils/file.mjs'; +import { groupNodesByModule } from '#core/utils/generators.mjs'; +import { minifyHTML } from '#core/utils/html-minifier.mjs'; +import { getRemarkRehypeWithShiki } from '#core/utils/remark.mjs'; + +import buildContent from '../../utils/html/content.mjs'; +import replaceTemplateValues from '../../utils/html/template.mjs'; +import buildToC, { + parseNavigationNode, + parseToCNode, +} from '../../utils/html/toc.mjs'; export const name = 'legacy-html'; -export const dependsOn = '@node-core/doc-kit/generators/metadata'; +export const dependsOn = '@doc-kittens/internal/metadata'; export const defaultConfiguration = { templatePath: join(import.meta.dirname, 'template.html'), additionalPathsToCopy: [join(import.meta.dirname, 'assets')], @@ -54,9 +58,9 @@ export async function processChunk(slicedInput, itemIndices, navigation) { const toc = String( remarkRehypeProcessor.processSync( - tableOfContents(nodes, { + buildToC(nodes, { maxDepth: 5, - parser: tableOfContents.parseToCNode, + parser: parseToCNode, }) ) ); @@ -106,9 +110,9 @@ export async function* generate(input, worker) { const navigation = String( remarkRehypeProcessor.processSync( - tableOfContents(indexOfFiles, { + buildToC(indexOfFiles, { maxDepth: 1, - parser: tableOfContents.parseNavigationNode, + parser: parseNavigationNode, }) ) ); diff --git a/packages/core/src/generators/legacy-html/template.html b/packages/legacy/src/generators/html/template.html similarity index 100% rename from packages/core/src/generators/legacy-html/template.html rename to packages/legacy/src/generators/html/template.html diff --git a/packages/core/src/generators/legacy-html/types.d.ts b/packages/legacy/src/generators/html/types.d.ts similarity index 75% rename from packages/core/src/generators/legacy-html/types.d.ts rename to packages/legacy/src/generators/html/types.d.ts index 19b2cf7d..72b0f9b6 100644 --- a/packages/core/src/generators/legacy-html/types.d.ts +++ b/packages/legacy/src/generators/html/types.d.ts @@ -1,4 +1,9 @@ -import type { MetadataEntry } from '../metadata/types'; +import { + GeneratorMetadata, + ProcessChunk, + Generate, +} from '#core/generators/types'; +import { MetadataEntry } from '@doc-kittens/internal/src/generators/metadata/types'; export interface TemplateValues { api: string; diff --git a/packages/core/src/generators/legacy-json-all/README.md b/packages/legacy/src/generators/json-all/README.md similarity index 59% rename from packages/core/src/generators/legacy-json-all/README.md rename to packages/legacy/src/generators/json-all/README.md index a18a64ec..71ad4df0 100644 --- a/packages/core/src/generators/legacy-json-all/README.md +++ b/packages/legacy/src/generators/json-all/README.md @@ -1,10 +1,16 @@ -## `legacy-json-all` Generator +# `@doc-kittens/legacy/json/all` -The `legacy-json-all` generator consolidates data from the `legacy-json` generator into a single `all.json` file containing all API modules. +The `legacy-json-all` generator consolidates the output of [`legacy-json`](../json/README.md) into a single `all.json` file containing all API modules. -### Configuring +Depends on: `@doc-kittens/legacy/json` -The `legacy-json-all` generator accepts the following configuration options: +## Import + +```js +import generator from '@doc-kittens/legacy/json/all'; +``` + +## Configuring | Name | Type | Default | Description | | -------- | --------- | ------- | ---------------------------------------------------- | diff --git a/packages/core/src/generators/legacy-json-all/index.mjs b/packages/legacy/src/generators/json-all/index.mjs similarity index 91% rename from packages/core/src/generators/legacy-json-all/index.mjs rename to packages/legacy/src/generators/json-all/index.mjs index ae11fddf..ca33a6e0 100644 --- a/packages/core/src/generators/legacy-json-all/index.mjs +++ b/packages/legacy/src/generators/json-all/index.mjs @@ -3,11 +3,12 @@ import { writeFile } from 'node:fs/promises'; import { join } from 'node:path'; -import getConfig from '../../utils/configuration/index.mjs'; -import { legacyToJSON } from '../../utils/generators.mjs'; +import getConfig from '#core/utils/configuration/index.mjs'; + +import { legacyToJSON } from '../../utils/json/legacyToJSON.mjs'; export const name = 'legacy-json-all'; -export const dependsOn = '@node-core/doc-kit/generators/legacy-json'; +export const dependsOn = '@doc-kittens/legacy/json'; export const defaultConfiguration = { minify: false, }; diff --git a/packages/core/src/generators/legacy-json-all/types.d.ts b/packages/legacy/src/generators/json-all/types.d.ts similarity index 80% rename from packages/core/src/generators/legacy-json-all/types.d.ts rename to packages/legacy/src/generators/json-all/types.d.ts index d95ffd14..0d2763a3 100644 --- a/packages/core/src/generators/legacy-json-all/types.d.ts +++ b/packages/legacy/src/generators/json-all/types.d.ts @@ -1,9 +1,11 @@ +import { GeneratorMetadata, Generate } from '#core/generators/types'; + import { MiscSection, Section, SignatureSection, ModuleSection, -} from '../legacy-json/types'; +} from '../json/types'; export interface Output { miscs: Array; diff --git a/packages/core/src/generators/legacy-json/README.md b/packages/legacy/src/generators/json/README.md similarity index 62% rename from packages/core/src/generators/legacy-json/README.md rename to packages/legacy/src/generators/json/README.md index 97f906a5..7c44a6cf 100644 --- a/packages/core/src/generators/legacy-json/README.md +++ b/packages/legacy/src/generators/json/README.md @@ -1,10 +1,14 @@ -## `legacy-json` Generator +# `@doc-kittens/legacy/json` -The `legacy-json` generator creates legacy JSON files for the API documentation for retro-compatibility with the previous documentation format. +The `legacy-json` generator creates one legacy JSON file per API module for retro-compatibility with the historical Node.js documentation format. -### Configuring +## Import -The `legacy-json` generator accepts the following configuration options: +```js +import generator from '@doc-kittens/legacy/json'; +``` + +## Configuring | Name | Type | Default | Description | | -------- | --------- | -------- | ------------------------------------------------ | diff --git a/packages/core/src/generators/legacy-json/index.mjs b/packages/legacy/src/generators/json/index.mjs similarity index 84% rename from packages/core/src/generators/legacy-json/index.mjs rename to packages/legacy/src/generators/json/index.mjs index 69ec68ff..d068b510 100644 --- a/packages/core/src/generators/legacy-json/index.mjs +++ b/packages/legacy/src/generators/json/index.mjs @@ -3,12 +3,14 @@ import { writeFile } from 'node:fs/promises'; import { join } from 'node:path'; -import { createSectionBuilder } from './utils/buildSection.mjs'; -import getConfig from '../../utils/configuration/index.mjs'; -import { groupNodesByModule, legacyToJSON } from '../../utils/generators.mjs'; +import getConfig from '#core/utils/configuration/index.mjs'; +import { groupNodesByModule } from '#core/utils/generators.mjs'; + +import { legacyToJSON } from '../../utils/json/legacyToJSON.mjs'; +import { createSectionBuilder } from '../../utils/json/section.mjs'; export const name = 'legacy-json'; -export const dependsOn = '@node-core/doc-kit/generators/metadata'; +export const dependsOn = '@doc-kittens/internal/metadata'; export const defaultConfiguration = { ref: 'main', minify: false, diff --git a/packages/core/src/generators/legacy-json/types.d.ts b/packages/legacy/src/generators/json/types.d.ts similarity index 96% rename from packages/core/src/generators/legacy-json/types.d.ts rename to packages/legacy/src/generators/json/types.d.ts index ed92c6ce..2cd99bfb 100644 --- a/packages/core/src/generators/legacy-json/types.d.ts +++ b/packages/legacy/src/generators/json/types.d.ts @@ -1,5 +1,10 @@ -import { ListItem } from '@types/mdast'; -import { MetadataEntry } from '../metadata/types'; +import { ListItem } from 'mdast'; +import { + GeneratorMetadata, + ProcessChunk, + Generate, +} from '#core/generators/types'; +import { MetadataEntry } from '@doc-kittens/internal/src/generators/metadata/types'; /** * Represents an entry in a hierarchical structure, extending from MetadataEntry. diff --git a/packages/legacy/src/utils/html/__tests__/content.test.mjs b/packages/legacy/src/utils/html/__tests__/content.test.mjs new file mode 100644 index 00000000..83119c0d --- /dev/null +++ b/packages/legacy/src/utils/html/__tests__/content.test.mjs @@ -0,0 +1,81 @@ +'use strict'; + +import assert from 'node:assert/strict'; +import { describe, it } from 'node:test'; + +import { + createLegacySlugger, + buildHtmlTypeLink, + buildStabilityOverview, +} from '../content.mjs'; + +describe('createLegacySlugger', () => { + it('prefixes with api stem and uses underscores', () => { + const getLegacySlug = createLegacySlugger(); + assert.strictEqual(getLegacySlug('File System', 'fs'), 'fs_file_system'); + }); + + it('replaces special characters with underscores', () => { + const getLegacySlug = createLegacySlugger(); + assert.strictEqual( + getLegacySlug('fs.readFile(path)', 'fs'), + 'fs_fs_readfile_path' + ); + }); + + it('strips leading and trailing underscores', () => { + const getLegacySlug = createLegacySlugger(); + assert.strictEqual(getLegacySlug('Hello', 'fs'), 'fs_hello'); + }); + + it('prefixes with underscore when result starts with non-alpha', () => { + const getLegacySlug = createLegacySlugger(); + assert.strictEqual(getLegacySlug('123 test', '0num'), '_0num_123_test'); + }); + + it('deduplicates with a counter for identical titles', () => { + const getLegacySlug = createLegacySlugger(); + assert.strictEqual(getLegacySlug('Hello', 'fs'), 'fs_hello'); + assert.strictEqual(getLegacySlug('Hello', 'fs'), 'fs_hello_1'); + assert.strictEqual(getLegacySlug('Hello', 'fs'), 'fs_hello_2'); + assert.strictEqual(getLegacySlug('World', 'fs'), 'fs_world'); + }); +}); + +describe('buildHtmlTypeLink', () => { + it('leaves unrelated html values untouched', () => { + const node = { value: '

no types here

' }; + buildHtmlTypeLink(node); + assert.strictEqual(node.value, '

no types here

'); + }); +}); + +describe('buildStabilityOverview', () => { + it('skips entries without a stability entry', () => { + const table = buildStabilityOverview([ + { api: 'fs', heading: { data: {} } }, + ]); + const tbody = table.children.find(c => c.tagName === 'tbody'); + assert.deepStrictEqual(tbody.children, []); + }); + + it('renders a row per head node that has stability info', () => { + const table = buildStabilityOverview([ + { + api: 'fs', + heading: { data: { name: 'File System' } }, + stability: { + data: { index: '2', description: 'Stable. Safe to use.' }, + }, + }, + ]); + const tbody = table.children.find(c => c.tagName === 'tbody'); + assert.strictEqual(tbody.children.length, 1); + const [moduleCell, stabilityCell] = tbody.children[0].children; + assert.ok(moduleCell.properties.className.includes('module_stability')); + assert.ok( + stabilityCell.properties.className.includes('api_stability_2'), + 'should include parsed stability index class' + ); + }); +}); diff --git a/packages/legacy/src/utils/html/__tests__/toc.test.mjs b/packages/legacy/src/utils/html/__tests__/toc.test.mjs new file mode 100644 index 00000000..791c87d9 --- /dev/null +++ b/packages/legacy/src/utils/html/__tests__/toc.test.mjs @@ -0,0 +1,113 @@ +'use strict'; + +import assert from 'node:assert/strict'; +import { describe, it } from 'node:test'; + +import buildToC, { + parseNavigationNode, + parseToCNode, + wrapToC, +} from '../toc.mjs'; + +describe('buildToC', () => { + const identityParser = ({ heading }) => heading.data.name; + + it('returns an empty string when there are no entries', () => { + assert.strictEqual( + buildToC([], { maxDepth: 5, parser: identityParser }), + '' + ); + }); + + it('filters out entries whose heading has no name', () => { + const entries = [ + { heading: { depth: 1, data: { name: 'A' } } }, + { heading: { depth: 1, data: {} } }, + { heading: { depth: 1, data: { name: 'B' } } }, + ]; + + assert.strictEqual( + buildToC(entries, { maxDepth: 5, parser: identityParser }), + '- A\n- B\n' + ); + }); + + it('indents entries based on their heading depth', () => { + const entries = [ + { heading: { depth: 1, data: { name: 'Root' } } }, + { heading: { depth: 2, data: { name: 'Child' } } }, + { heading: { depth: 3, data: { name: 'Grandchild' } } }, + ]; + + assert.strictEqual( + buildToC(entries, { maxDepth: 5, parser: identityParser }), + '- Root\n - Child\n - Grandchild\n' + ); + }); + + it('skips entries deeper than maxDepth', () => { + const entries = [ + { heading: { depth: 1, data: { name: 'A' } } }, + { heading: { depth: 2, data: { name: 'B' } } }, + { heading: { depth: 3, data: { name: 'C' } } }, + ]; + + assert.strictEqual( + buildToC(entries, { maxDepth: 2, parser: identityParser }), + '- A\n - B\n' + ); + }); +}); + +describe('parseNavigationNode', () => { + it('renders a navigation link for the api', () => { + const html = parseNavigationNode({ + api: 'fs', + heading: { data: { name: 'File system' } }, + }); + + assert.strictEqual( + html, + 'File system' + ); + }); +}); + +describe('parseToCNode', () => { + it('renders a plain link when no stability is present', () => { + const html = parseToCNode({ + api: 'fs', + heading: { data: { slug: 'fs_readfile', text: 'fs.readFile()' } }, + }); + + assert.strictEqual(html, 'fs.readFile()'); + }); + + it('wraps the link in a stability span when stability info is provided', () => { + const html = parseToCNode({ + api: 'fs', + stability: { data: { index: '2' } }, + heading: { data: { slug: 'fs_readfile', text: 'fs.readFile()' } }, + }); + + assert.strictEqual( + html, + 'fs.readFile()' + ); + }); +}); + +describe('wrapToC', () => { + it('wraps a non-empty ToC in the details element', () => { + assert.strictEqual( + wrapToC('
  • entry
'), + '' + ); + }); + + it('returns an empty string for empty ToC input', () => { + assert.strictEqual(wrapToC(''), ''); + assert.strictEqual(wrapToC(null), ''); + assert.strictEqual(wrapToC(undefined), ''); + }); +}); diff --git a/packages/core/src/generators/legacy-html/utils/buildContent.mjs b/packages/legacy/src/utils/html/content.mjs similarity index 60% rename from packages/core/src/generators/legacy-html/utils/buildContent.mjs rename to packages/legacy/src/utils/html/content.mjs index 32776bad..74a3be64 100644 --- a/packages/core/src/generators/legacy-html/utils/buildContent.mjs +++ b/packages/legacy/src/utils/html/content.mjs @@ -1,22 +1,103 @@ 'use strict'; +import getConfig from '#core/utils/configuration/index.mjs'; +import { + GITHUB_BLOB_URL, + populate, +} from '#core/utils/configuration/templates.mjs'; +import { QUERIES, UNIST } from '#core/utils/queries/index.mjs'; import { h as createElement } from 'hastscript'; import { u as createTree } from 'unist-builder'; import { SKIP, visit } from 'unist-util-visit'; -import buildExtraContent from './buildExtraContent.mjs'; -import { createLegacySlugger } from './slugger.mjs'; -import getConfig from '../../../utils/configuration/index.mjs'; -import { - GITHUB_BLOB_URL, - populate, -} from '../../../utils/configuration/templates.mjs'; -import { QUERIES, UNIST } from '../../../utils/queries/index.mjs'; +/** + * Creates a stateful slugger for legacy anchor links. + * + * Generates underscore-separated slugs in the form `{apiStem}_{text}`, + * appending `_{n}` for duplicates to preserve historical anchor compatibility. + * + * @returns {(text: string, apiStem: string) => string} + */ +export const createLegacySlugger = + (counters = {}) => + (text, apiStem) => { + const id = `${apiStem}_${text}` + .toLowerCase() + .replace(/^[^a-z0-9]+|[^a-z0-9]+$/g, '') + .replace(/[^a-z0-9]+/g, '_') + .replace(/^\d/, '_$&'); + + counters[id] ??= -1; + const count = ++counters[id]; + return count > 0 ? `${id}_${count}` : id; + }; + +/** + * Generates the Stability Overview table based on the API metadata nodes. + * + * @param {Array} headMetadata The API metadata nodes to be used for the Stability Overview + */ +export const buildStabilityOverview = headMetadata => { + const headNodesWithStability = headMetadata.filter(entry => entry.stability); + + const mappedHeadNodesIntoTable = headNodesWithStability.map( + ({ heading, api, stability }) => { + return createElement( + 'tr', + createElement( + 'td.module_stability', + createElement('a', { href: `${api}.html` }, heading.data.name) + ), + createElement( + `td.api_stability.api_stability_${parseInt(stability.data.index)}`, + // Grabs the first sentence of the description + // to be used as a summary of the Stability Index + `(${stability.data.index}) ${stability.data.description.split('. ')[0]}` + ) + ); + } + ); + + return createElement( + 'table', + createElement( + 'thead', + createElement( + 'tr', + createElement('th', 'API'), + createElement('th', 'Stability') + ) + ), + createElement('tbody', mappedHeadNodesIntoTable) + ); +}; + +/** + * Generates extra "special" HTML content based on extra metadata that a node may have. + * + * @param {Array} headNodes The API metadata nodes to be used for the Stability Overview + * @param {import('../../../metadata/types').MetadataEntry} node The current API metadata node to be transformed into HTML content + * @returns {import('unist').Parent} The HTML AST tree for the extra content + */ +const buildExtraContent = (headNodes, node) => { + return createTree('root', [ + (node.tags ?? []).map(tag => { + switch (tag) { + case 'STABILITY_OVERVIEW_SLOT_BEGIN': + return buildStabilityOverview(headNodes); + case 'STABILITY_OVERVIEW_SLOT_END': + return createTree('root'); + default: + return createTree('root'); + } + }), + ]); +}; /** * Builds a Markdown heading for a given node * - * @param {import('../../metadata/types').HeadingNode} node The node to build the Markdown heading for + * @param {import('../../../metadata/types').HeadingNode} node The node to build the Markdown heading for * @param {number} index The index of the current node * @param {import('unist').Parent} parent The parent node of the current node * @returns {import('hast').Element} The HTML AST tree of the heading content @@ -51,7 +132,7 @@ const buildHeading = ({ data, children, depth }, index, parent, legacySlug) => { /** * Builds an HTML Stability element * - * @param {import('../../metadata/types').StabilityNode} node The HTML AST tree of the Stability Index content + * @param {import('../../../metadata/types').StabilityNode} node The HTML AST tree of the Stability Index content * @param {number} index The index of the current node * @param {import('unist').Parent} parent The parent node of the current node */ @@ -76,7 +157,7 @@ const buildStability = ({ children, data }, index, parent) => { * * @param {import('@types/mdast').Html} node The node containing the HTML content */ -const buildHtmlTypeLink = node => { +export const buildHtmlTypeLink = node => { node.value = node.value.replace( QUERIES.linksWithTypes, (_, type, link) => `<${type}>` @@ -86,10 +167,10 @@ const buildHtmlTypeLink = node => { /** * Creates a history table row. * - * @param {import('../../metadata/types').ChangeEntry} change + * @param {import('../../../metadata/types').ChangeEntry} change * @param {import('unified').Processor} remark */ -const createHistoryTableRow = ( +export const createHistoryTableRow = ( { version: changeVersions, description }, remark ) => { @@ -107,28 +188,25 @@ const createHistoryTableRow = ( /** * Builds the Metadata Properties into content * - * @param {import('../../metadata/types').MetadataEntry} node The node to build the properties from + * @param {import('../../../metadata/types').MetadataEntry} node The node to build the properties from * @param {import('unified').Processor} remark The Remark instance to be used to process changes table * @returns {import('unist').Parent} The HTML AST tree of the properties content */ -const buildMetadataElement = (node, remark) => { +export const buildMetadataElement = (node, remark) => { const config = getConfig('legacy-html'); - const metadataElement = createElement('div.api_metadata'); // We use a `span` element to display the source link as a clickable link to the source within Node.js if (typeof node.source_link === 'string') { // Creates the source link URL with the base URL and the source link const sourceLink = `${populate(GITHUB_BLOB_URL, config)}${node.source_link}`; - - // Creates the source link element with the source link and the source link text - const sourceLinkElement = createElement('span', [ - createElement('b', 'Source Code: '), - createElement('a', { href: sourceLink }, node.source_link), - ]); - - // Appends the source link element to the metadata element - metadataElement.children.push(sourceLinkElement); + metadataElement.children.push( + // Creates the source link element with the source link and the source link text + createElement('span', [ + createElement('b', 'Source Code: '), + createElement('a', { href: sourceLink }, node.source_link), + ]) + ); } // We use a `span` element to display the added in version @@ -136,12 +214,10 @@ const buildMetadataElement = (node, remark) => { const addedIn = Array.isArray(node.added) ? node.added.join(', ') : node.added; - // Creates the added in element with the added in version - const addedinElement = createElement('span', ['Added in: ', addedIn]); - - // Appends the added in element to the metadata element - metadataElement.children.push(addedinElement); + metadataElement.children.push( + createElement('span', ['Added in: ', addedIn]) + ); } // We use a `span` element to display the deprecated in version @@ -149,15 +225,10 @@ const buildMetadataElement = (node, remark) => { const deprecatedIn = Array.isArray(node.deprecated) ? node.deprecated.join(', ') : node.deprecated; - // Creates the deprecated in element with the deprecated in version - const deprecatedInElement = createElement('span', [ - 'Deprecated in: ', - deprecatedIn, - ]); - - // Appends the deprecated in element to the metadata element - metadataElement.children.push(deprecatedInElement); + metadataElement.children.push( + createElement('span', ['Deprecated in: ', deprecatedIn]) + ); } // We use a `span` element to display the removed in version @@ -165,24 +236,21 @@ const buildMetadataElement = (node, remark) => { const removedIn = Array.isArray(node.removed) ? node.removed.join(', ') : node.removed; - // Creates the removed in element with the removed in version - const removedInElement = createElement('span', ['Removed in: ', removedIn]); - - // Appends the removed in element to the metadata element - metadataElement.children.push(removedInElement); + metadataElement.children.push( + createElement('span', ['Removed in: ', removedIn]) + ); } // We use a `span` element to display the N-API version if it is available if (typeof node.napiVersion === 'number') { // Creates the N-API version element with the N-API version - const nApiVersionElement = createElement('span', [ - createElement('b', 'N-API Version: '), - node.napiVersion, - ]); - - // Appends the source n-api element to the metadata element - metadataElement.children.push(nApiVersionElement); + metadataElement.children.push( + createElement('span', [ + createElement('b', 'N-API Version: '), + node.napiVersion, + ]) + ); } // If there are changes, we create a `details` element with a `table` element to display the changes @@ -194,21 +262,20 @@ const buildMetadataElement = (node, remark) => { createHistoryTableRow(change, remark) ); - const historyDetailsElement = createElement('details.changelog', [ - createElement('summary', 'History'), - createElement('table', [ - createElement('thead', [ - createElement('tr', [ - createElement('th', 'Version'), - createElement('th', 'Changes'), + metadataElement.children.push( + createElement('details.changelog', [ + createElement('summary', 'History'), + createElement('table', [ + createElement('thead', [ + createElement('tr', [ + createElement('th', 'Version'), + createElement('th', 'Changes'), + ]), ]), + createElement('tbody', historyEntries), ]), - createElement('tbody', historyEntries), - ]), - ]); - - // Appends the history details element to the metadata element - metadataElement.children.push(historyDetailsElement); + ]) + ); } // Parses and processes the mixed Markdown/HTML content into an HTML AST tree @@ -218,11 +285,11 @@ const buildMetadataElement = (node, remark) => { /** * Builds the whole content of a given node (API module) * - * @param {Array} headNodes The API metadata Nodes that are considered the "head" of each module - * @param {Array} metadataEntries The API metadata Nodes to be transformed into HTML content + * @param {Array} headNodes The API metadata Nodes that are considered the "head" of each module + * @param {Array} metadataEntries The API metadata Nodes to be transformed into HTML content * @param {import('unified').Processor} remark The Remark instance to be used to process */ -export default (headNodes, metadataEntries, remark) => { +const buildContent = (headNodes, metadataEntries, remark) => { const getLegacySlug = createLegacySlugger(); // Creates the root node for the content @@ -270,3 +337,5 @@ export default (headNodes, metadataEntries, remark) => { // Stringifies the processed nodes to return the final Markdown content return remark.stringify(processedNodes); }; + +export default buildContent; diff --git a/packages/core/src/generators/legacy-html/utils/buildDropdowns.mjs b/packages/legacy/src/utils/html/template.mjs similarity index 64% rename from packages/core/src/generators/legacy-html/utils/buildDropdowns.mjs rename to packages/legacy/src/utils/html/template.mjs index 7a2c3be8..0aacb8bb 100644 --- a/packages/core/src/generators/legacy-html/utils/buildDropdowns.mjs +++ b/packages/legacy/src/utils/html/template.mjs @@ -1,11 +1,13 @@ 'use strict'; -import getConfig from '../../../utils/configuration/index.mjs'; -import { populate } from '../../../utils/configuration/templates.mjs'; +import getConfig from '#core/utils/configuration/index.mjs'; +import { populate } from '#core/utils/configuration/templates.mjs'; import { getCompatibleVersions, getVersionFromSemVer, -} from '../../../utils/generators.mjs'; +} from '#core/utils/generators.mjs'; + +import { wrapToC } from './toc.mjs'; /** * Builds the Dropdown for the current Table of Contents @@ -50,7 +52,7 @@ export const buildNavigation = navigationContents => * * @param {string} path The current API node name * @param {string} added The version the API was added - * @param {Array} versions All available Node.js releases + * @param {Array} versions All available Node.js releases */ export const buildVersions = (path, added, versions) => { const config = getConfig('legacy-html'); @@ -87,7 +89,42 @@ export const buildVersions = (path, added, versions) => { * * @param {string} url */ -export const buildGitHub = url => +const buildGitHub = url => `
  • ` + `` + `Edit on GitHub
  • `; + +/** + * Replaces the template values in the API template with the given values. + * @param {string} apiTemplate - The HTML template string + * @param {import('../../generators/html/types').TemplateValues} values - The values to replace the template values with + * @param {import('#core/utils/configuration/types').GlobalConfiguration} config + * @param {{ skipGitHub?: boolean; skipGtocPicker?: boolean }} [options] - Optional settings + * @returns {string} The replaced template values + */ +const replaceTemplateValues = ( + apiTemplate, + { path, api, added, section, toc: tocContent, nav, content }, + config, + { skipGitHub = false, skipGtocPicker = false } = {} +) => { + return apiTemplate + .replace('__ID__', api) + .replace(/__FILENAME__/g, api) + .replace('__SECTION__', section) + .replace(/__VERSION__/g, `v${config.version.version}`) + .replace(/__TOC__/g, wrapToC(tocContent)) + .replace(/__GTOC__/g, nav) + .replace('__CONTENT__', content) + .replace(/__TOC_PICKER__/g, buildToC(tocContent)) + .replace(/__GTOC_PICKER__/g, skipGtocPicker ? '' : buildNavigation(nav)) + .replace('__ALTDOCS__', buildVersions(path, added, config.changelog)) + .replace( + '__EDIT_ON_GITHUB__', + skipGitHub + ? '' + : buildGitHub(populate(config.editURL, { ...config, path })) + ); +}; + +export default replaceTemplateValues; diff --git a/packages/core/src/generators/legacy-html/utils/tableOfContents.mjs b/packages/legacy/src/utils/html/toc.mjs similarity index 72% rename from packages/core/src/generators/legacy-html/utils/tableOfContents.mjs rename to packages/legacy/src/utils/html/toc.mjs index 6a8f86d9..db6767ca 100644 --- a/packages/core/src/generators/legacy-html/utils/tableOfContents.mjs +++ b/packages/legacy/src/utils/html/toc.mjs @@ -8,10 +8,10 @@ * * This generates a Markdown string containing a list as the ToC for the API documentation. * - * @param {Array} entries The API metadata nodes to be used for the ToC - * @param {{ maxDepth: number; parser: (metadata: import('../../metadata/types').MetadataEntry) => string }} options The optional ToC options + * @param {Array} entries The API metadata nodes to be used for the ToC + * @param {{ maxDepth: number; parser: (metadata: import('../../../metadata/types').MetadataEntry) => string }} options The optional ToC options */ -const tableOfContents = (entries, options) => { +const buildToC = (entries, options) => { // Filter out the entries that have a name property / or that have empty content const validEntries = entries.filter(({ heading }) => heading.data.name); @@ -33,17 +33,17 @@ const tableOfContents = (entries, options) => { /** * Builds the Label with extra metadata to be used in the ToC * - * @param {import('../../metadata/types').MetadataEntry} metadata The current node that is being parsed + * @param {import('../../../metadata/types').MetadataEntry} metadata The current node that is being parsed */ -tableOfContents.parseNavigationNode = ({ api, heading }) => +export const parseNavigationNode = ({ api, heading }) => `${heading.data.name}`; /** * Builds the Label with extra metadata to be used in the ToC * - * @param {import('../../metadata/types').MetadataEntry} metadata + * @param {import('../../../metadata/types').MetadataEntry} metadata */ -tableOfContents.parseToCNode = ({ stability, api, heading }) => { +export const parseToCNode = ({ stability, api, heading }) => { const fullSlug = `${api}.html#${heading.data.slug}`; if (stability) { @@ -61,17 +61,17 @@ tableOfContents.parseToCNode = ({ stability, api, heading }) => { * Wraps the Table of Contents (ToC) with a template * used for rendering within the page template * - * @param {string} toc + * @param {string} tocContent */ -tableOfContents.wrapToC = toc => { - if (toc && toc.length > 0) { +export const wrapToC = tocContent => { + if (tocContent && tocContent.length > 0) { return ( `` + `Table of contents${tocContent}` ); } return ''; }; -export default tableOfContents; +export default buildToC; diff --git a/packages/legacy/src/utils/json/__tests__/legacyToJSON.test.mjs b/packages/legacy/src/utils/json/__tests__/legacyToJSON.test.mjs new file mode 100644 index 00000000..d09962b1 --- /dev/null +++ b/packages/legacy/src/utils/json/__tests__/legacyToJSON.test.mjs @@ -0,0 +1,46 @@ +import assert from 'node:assert/strict'; +import { describe, it } from 'node:test'; + +import { legacyToJSON } from '../legacyToJSON.mjs'; + +describe('legacyToJSON', () => { + const base = { + type: 'module', + source: 'lib/fs.js', + introduced_in: 'v0.10.0', + meta: {}, + stability: 2, + stabilityText: 'Stable', + classes: [], + methods: ['readFile'], + properties: [], + miscs: [], + modules: ['fs'], + globals: [], + }; + + it('serialises a normal section with all keys', () => { + const result = JSON.parse(legacyToJSON({ ...base, api: 'fs' })); + assert.ok('type' in result); + assert.ok('methods' in result); + assert.ok('modules' in result); + }); + + it('omits modules key for index sections', () => { + const result = JSON.parse(legacyToJSON({ ...base, api: 'index' })); + assert.ok(!('modules' in result)); + }); + + it('uses all.json key order when api is null', () => { + const result = JSON.parse(legacyToJSON({ ...base, api: null })); + assert.ok('miscs' in result); + assert.ok('modules' in result); + assert.ok(!('type' in result)); + assert.ok(!('source' in result)); + }); + + it('passes extra args to JSON.stringify (e.g. indentation)', () => { + const result = legacyToJSON({ ...base, api: 'fs' }, null, 2); + assert.ok(result.includes('\n')); + }); +}); diff --git a/packages/core/src/generators/legacy-json/utils/__tests__/parseList.test.mjs b/packages/legacy/src/utils/json/__tests__/list.test.mjs similarity index 74% rename from packages/core/src/generators/legacy-json/utils/__tests__/parseList.test.mjs rename to packages/legacy/src/utils/json/__tests__/list.test.mjs index 648b2723..4e8097a0 100644 --- a/packages/core/src/generators/legacy-json/utils/__tests__/parseList.test.mjs +++ b/packages/legacy/src/utils/json/__tests__/list.test.mjs @@ -5,15 +5,15 @@ import { transformTypeReferences, extractPattern, parseListItem, - parseList, -} from '../parseList.mjs'; + parseTypedList, +} from '../list.mjs'; const validTypedList = [ - { type: 'inlineCode', value: 'option' }, // inline code - { type: 'text', value: ' ' }, // space + { type: 'inlineCode', value: 'option' }, + { type: 'text', value: ' ' }, { type: 'link', - children: [{ type: 'text', value: '' }], // link with < value + children: [{ type: 'text', value: '' }], }, { type: 'text', value: ' option description' }, ]; @@ -28,6 +28,10 @@ describe('transformTypeReferences', () => { const result = transformTypeReferences('`` | ``'); assert.equal(result, '{string|number}'); }); + + it('leaves unrelated text untouched', () => { + assert.equal(transformTypeReferences('no refs here'), 'no refs here'); + }); }); describe('extractPattern', () => { @@ -56,6 +60,12 @@ describe('extractPattern', () => { assert.equal(result, 'no match'); assert.equal(current.missing, undefined); }); + + it('strips a trailing period from the captured value', () => { + const current = {}; + extractPattern('name: value.', /name:\s*(\S+)/, 'name', current); + assert.equal(current.name, 'value'); + }); }); describe('parseListItem', () => { @@ -89,7 +99,7 @@ describe('parseListItem', () => { }); }); -describe('parseList', () => { +describe('parseTypedList', () => { it('processes property sections', () => { const section = { type: 'property', name: 'test' }; const nodes = [ @@ -108,7 +118,7 @@ describe('parseList', () => { }, ]; - parseList(section, nodes); + parseTypedList(section, nodes); assert.ok(section.textRaw); }); @@ -130,7 +140,7 @@ describe('parseList', () => { }, ]; - parseList(section, nodes); + parseTypedList(section, nodes); assert.ok(Array.isArray(section.params)); }); @@ -146,7 +156,6 @@ describe('parseList', () => { type: 'paragraph', children: validTypedList, }, - // This is a nested typed list { type: 'list', children: [ @@ -166,7 +175,30 @@ describe('parseList', () => { }, ]; - parseList(section, nodes); + parseTypedList(section, nodes); assert.equal(section.params[0].options.length, 1); }); + + it('leaves unknown section types unchanged and keeps the list in place', () => { + const section = { type: 'unknown' }; + const nodes = [ + { + type: 'list', + children: [ + { + children: [ + { + type: 'paragraph', + children: [{ type: 'text', value: 'ignored' }], + }, + ], + }, + ], + }, + ]; + + parseTypedList(section, nodes); + assert.equal(nodes.length, 1, 'list should not be spliced out'); + assert.deepStrictEqual(section, { type: 'unknown' }); + }); }); diff --git a/packages/legacy/src/utils/json/__tests__/section.test.mjs b/packages/legacy/src/utils/json/__tests__/section.test.mjs new file mode 100644 index 00000000..95319be7 --- /dev/null +++ b/packages/legacy/src/utils/json/__tests__/section.test.mjs @@ -0,0 +1,177 @@ +'use strict'; + +import assert from 'node:assert/strict'; +import { describe, it, test } from 'node:test'; + +import { UNPROMOTED_KEYS } from '../constants.mjs'; +import { + findParent, + buildHierarchy, + promoteMiscChildren, +} from '../section.mjs'; + +describe('findParent', () => { + it('finds parent with lower depth', () => { + const entries = [{ heading: { depth: 1 } }, { heading: { depth: 2 } }]; + const parent = findParent(entries[1], entries, 0); + assert.equal(parent, entries[0]); + }); + + it('throws when no parent exists', () => { + const entries = [{ heading: { depth: 2 } }]; + assert.throws(() => findParent(entries[0], entries, -1)); + }); +}); + +describe('buildHierarchy', () => { + it('returns empty array for empty input', () => { + assert.deepEqual(buildHierarchy([]), []); + }); + + it('keeps root entries at top level', () => { + const entries = [{ heading: { depth: 1 } }, { heading: { depth: 1 } }]; + const result = buildHierarchy(entries); + assert.equal(result.length, 2); + }); + + it('nests children under parents', () => { + const entries = [{ heading: { depth: 1 } }, { heading: { depth: 2 } }]; + const result = buildHierarchy(entries); + + assert.equal(result.length, 1); + assert.equal(result[0].hierarchyChildren.length, 1); + assert.equal(result[0].hierarchyChildren[0], entries[1]); + }); + + it('handles multiple levels', () => { + const entries = [ + { heading: { depth: 1 } }, + { heading: { depth: 2 } }, + { heading: { depth: 3 } }, + ]; + const result = buildHierarchy(entries); + + assert.equal(result.length, 1); + assert.equal(result[0].hierarchyChildren[0].hierarchyChildren.length, 1); + }); + + it('re-attaches siblings to the nearest valid ancestor', () => { + const entries = [ + { heading: { depth: 1 } }, + { heading: { depth: 2 } }, + { heading: { depth: 3 } }, + { heading: { depth: 2 } }, + ]; + const result = buildHierarchy(entries); + + assert.equal(result.length, 1); + assert.equal(result[0].hierarchyChildren.length, 2); + assert.equal(result[0].hierarchyChildren[1], entries[3]); + }); +}); + +describe('promoteMiscChildren', () => { + /** + * @template {object} T + * @param {T} base + * @param {'section'|'parent'} [type='section'] + * @returns {T} + */ + const buildReadOnlySection = (base, type = 'section') => + new Proxy(base, { + set(_, key) { + throw new Error(`${type} property '${String(key)} modified`); + }, + }); + + test('ignores non-misc section', () => { + const section = buildReadOnlySection({ type: 'text' }); + const parent = buildReadOnlySection({ type: 'text' }, 'parent'); + + promoteMiscChildren(section, parent); + }); + + test('ignores misc parent', () => { + const section = buildReadOnlySection({ type: 'misc' }); + const parent = buildReadOnlySection({ type: 'misc' }, 'parent'); + + promoteMiscChildren(section, parent); + }); + + test('ignores keys in UNPROMOTED_KEYS', () => { + const sectionRaw = { + type: 'misc', + promotableKey: 'this should be promoted', + }; + + UNPROMOTED_KEYS.forEach(key => { + if (key === 'type') { + return; + } + sectionRaw[key] = 'this should be ignored'; + }); + + const section = buildReadOnlySection(sectionRaw); + const parent = { type: 'module' }; + + promoteMiscChildren(section, parent); + + UNPROMOTED_KEYS.forEach(key => { + if (key === 'type') { + return; + } + if (parent[key]) { + throw new Error(`'${key}' was promoted`); + } + }); + + assert.strictEqual(parent.promotableKey, section.promotableKey); + }); + + describe('merges properties correctly', () => { + test('pushes child property if parent is an array', () => { + const section = buildReadOnlySection({ + type: 'misc', + someValue: 'bar', + }); + + const parent = { + type: 'module', + someValue: ['foo'], + }; + + promoteMiscChildren(section, parent); + + assert.deepStrictEqual(parent.someValue, ['foo', 'bar']); + }); + + test('ignores child property if parent has a value that is not an array', () => { + const section = buildReadOnlySection({ + type: 'misc', + someValue: 'bar', + }); + + const parent = { + type: 'module', + someValue: 'foo', + }; + + promoteMiscChildren(section, parent); + + assert.strictEqual(parent.someValue, 'foo'); + }); + + test('promotes child property if parent does not have the property', () => { + const section = buildReadOnlySection({ + type: 'misc', + someValue: 'bar', + }); + + const parent = { type: 'module' }; + + promoteMiscChildren(section, parent); + + assert.deepStrictEqual(parent.someValue, 'bar'); + }); + }); +}); diff --git a/packages/core/src/generators/legacy-json/utils/__tests__/parseSignature.test.mjs b/packages/legacy/src/utils/json/__tests__/signature.test.mjs similarity index 95% rename from packages/core/src/generators/legacy-json/utils/__tests__/parseSignature.test.mjs rename to packages/legacy/src/utils/json/__tests__/signature.test.mjs index 6f973b28..a88e45a2 100644 --- a/packages/core/src/generators/legacy-json/utils/__tests__/parseSignature.test.mjs +++ b/packages/legacy/src/utils/json/__tests__/signature.test.mjs @@ -6,7 +6,22 @@ import parseSignature, { findParameter, parseParameters, parseNameAndOptionalStatus, -} from '../parseSignature.mjs'; + updateDepth, +} from '../signature.mjs'; + +describe('updateDepth', () => { + it('increments on opening bracket', () => { + assert.strictEqual(updateDepth(0, '['), 1); + }); + + it('decrements on closing bracket', () => { + assert.strictEqual(updateDepth(1, ']'), 0); + }); + + it('leaves depth unchanged for non-bracket characters', () => { + assert.strictEqual(updateDepth(3, 'x'), 3); + }); +}); describe('parseNameAndOptionalStatus', () => { const testCases = [ @@ -136,7 +151,6 @@ describe('findParameter', () => { const { paramName, index, params } = testCase.input; const result = findParameter(paramName, index, params); - // Check all expected properties for (const key in testCase.expected) { assert.equal(result[key], testCase.expected[key]); } diff --git a/packages/core/src/generators/legacy-json/constants.mjs b/packages/legacy/src/utils/json/constants.mjs similarity index 87% rename from packages/core/src/generators/legacy-json/constants.mjs rename to packages/legacy/src/utils/json/constants.mjs index 3b017d4d..44fc9785 100644 --- a/packages/core/src/generators/legacy-json/constants.mjs +++ b/packages/legacy/src/utils/json/constants.mjs @@ -31,3 +31,6 @@ export const SECTION_TYPE_PLURALS = { // The keys to not promote when promoting children. export const UNPROMOTED_KEYS = ['textRaw', 'name', 'type', 'desc', 'miscs']; + +// The characters that denote an optional parameter and how they change the optional depth. +export const OPTIONAL_LEVEL_CHANGES = { '[': 1, ']': -1 }; diff --git a/packages/legacy/src/utils/json/legacyToJSON.mjs b/packages/legacy/src/utils/json/legacyToJSON.mjs new file mode 100644 index 00000000..ee8e1ac5 --- /dev/null +++ b/packages/legacy/src/utils/json/legacyToJSON.mjs @@ -0,0 +1,53 @@ +'use strict'; + +/** + * Transforms an object to JSON output consistent with the JSON version. + * @param {import('../../generators/json/types').Section} section - The source object + * @param {any[]} args + * @returns {string} - The JSON output + */ +export const legacyToJSON = ( + { + api, + type, + source, + introduced_in, + meta, + stability, + stabilityText, + classes, + methods, + properties, + miscs, + modules, + globals, + }, + ...args +) => + JSON.stringify( + api == null + ? { + // all.json special order + miscs, + modules, + classes, + globals, + methods, + } + : { + type, + source, + introduced_in, + meta, + stability, + stabilityText, + classes, + methods, + properties, + miscs, + // index.json shouldn't have a `modules` key: + ...(api === 'index' ? undefined : { modules }), + globals, + }, + ...args + ); diff --git a/packages/core/src/generators/legacy-json/utils/parseList.mjs b/packages/legacy/src/utils/json/list.mjs similarity index 75% rename from packages/core/src/generators/legacy-json/utils/parseList.mjs rename to packages/legacy/src/utils/json/list.mjs index 5e1a5d89..3da71ff4 100644 --- a/packages/core/src/generators/legacy-json/utils/parseList.mjs +++ b/packages/legacy/src/utils/json/list.mjs @@ -1,13 +1,14 @@ +import { leftHandAssign } from '#core/utils/generators.mjs'; +import { QUERIES, UNIST } from '#core/utils/queries/index.mjs'; +import { transformNodesToString } from '#core/utils/unist.mjs'; + import { DEFAULT_EXPRESSION, LEADING_HYPHEN, NAME_EXPRESSION, TYPE_EXPRESSION, -} from '../constants.mjs'; -import parseSignature from './parseSignature.mjs'; -import { leftHandAssign } from '../../../utils/generators.mjs'; -import { QUERIES, UNIST } from '../../../utils/queries/index.mjs'; -import { transformNodesToString } from '../../../utils/unist.mjs'; +} from './constants.mjs'; +import parseSignature from './signature.mjs'; /** * Modifies type references in a string by replacing template syntax (`<...>`) with curly braces `{...}` @@ -15,12 +16,14 @@ import { transformNodesToString } from '../../../utils/unist.mjs'; * @param {string} string * @returns {string} */ -export function transformTypeReferences(string) { - return string.replace(/`<([^>]+)>`/g, '{$1}').replaceAll('} | {', '|'); -} +export const transformTypeReferences = string => + string.replace(/`<([^>]+)>`/g, '{$1}').replaceAll('} | {', '|'); /** - * Extracts and removes a specific pattern from a text string while storing the result in a key of the `current` object. + * Extracts the first capture group of `pattern` from `text`, stores it in + * `current[key]`, and returns the remainder of `text` with the match removed. + * Returns `text` unchanged when there is no match. + * * @param {string} text * @param {RegExp} pattern * @param {string} key @@ -39,12 +42,12 @@ export const extractPattern = (text, pattern, key, current) => { }; /** - * Parses an individual list item node to extract its properties + * Parses a single list item node into a structured parameter descriptor. * * @param {import('@types/mdast').ListItem} child - * @returns {import('../types').ParameterList} + * @returns {import('../../types').ParameterList} */ -export function parseListItem(child) { +export const parseListItem = child => { const current = {}; const subList = child.children.find(UNIST.isLooselyTypedList); @@ -80,15 +83,16 @@ export function parseListItem(child) { } return current; -} +}; /** - * Parses a list of nodes and updates the corresponding section object with the extracted information. - * Handles different section types such as methods, properties, and events differently. - * @param {import('../types').Section} section + * Parses the typed list for a section and populates the section's properties + * according to its type (method, property, event, etc.). + * + * @param {import('../../types').Section} section * @param {import('@types/mdast').RootContent[]} nodes */ -export function parseList(section, nodes) { +export const parseTypedList = (section, nodes) => { const listIdx = nodes.findIndex(UNIST.isStronglyTypedList); const list = nodes[listIdx]; @@ -113,7 +117,6 @@ export function parseList(section, nodes) { // For properties, update type and other details if values exist if (values.length) { delete values[0].name; - Object.assign(section, values[0]); } break; @@ -130,4 +133,4 @@ export function parseList(section, nodes) { if (removeList && list) { nodes.splice(listIdx, 1); } -} +}; diff --git a/packages/legacy/src/utils/json/section.mjs b/packages/legacy/src/utils/json/section.mjs new file mode 100644 index 00000000..c6486900 --- /dev/null +++ b/packages/legacy/src/utils/json/section.mjs @@ -0,0 +1,288 @@ +import { enforceArray } from '#core/utils/array.mjs'; +import { getRemarkRehype } from '#core/utils/remark.mjs'; +import { transformNodesToString } from '#core/utils/unist.mjs'; + +import { SECTION_TYPE_PLURALS, UNPROMOTED_KEYS } from './constants.mjs'; +import { parseTypedList } from './list.mjs'; + +/** + * Recursively finds the most suitable parent entry for a given `entry` based on heading depth. + * + * @param {import('../../../metadata/types').MetadataEntry} entry + * @param {import('../../../metadata/types').MetadataEntry[]} entries + * @param {number} startIdx + * @returns {import('../../types.d.ts').HierarchizedEntry} + */ +export const findParent = (entry, entries, startIdx) => { + // Base case: if we're at the beginning of the list, no valid parent exists. + if (startIdx < 0) { + throw new Error( + `Cannot find a suitable parent for entry at index ${startIdx + 1}` + ); + } + + const candidateParent = entries[startIdx]; + const candidateDepth = candidateParent.heading.depth; + + // If we find a suitable parent, return it. + if (candidateDepth < entry.heading.depth) { + candidateParent.hierarchyChildren ??= []; + return candidateParent; + } + + // Recurse upwards to find a suitable parent. + return findParent(entry, entries, startIdx - 1); +}; + +/** + * We need the files to be in a hierarchy based off of depth, but they're + * given to us flattened. So, let's fix that. + * + * Assuming that {@link entries} is in the same order as the elements are in + * the markdown, we can use the entry's depth property to reassemble the + * hierarchy. + * + * If depth <= 1, it's a top-level element (aka a root). + * + * If it's depth is greater than the previous entry's depth, it's a child of + * the previous entry. Otherwise (if it's less than or equal to the previous + * entry's depth), we need to find the entry that it was the greater than. We + * can do this by just looping through entries in reverse starting at the + * current index - 1. + * + * @param {Array} entries + * @returns {Array} + */ +export const buildHierarchy = entries => { + const roots = []; + + // Main loop to construct the hierarchy. + for (let i = 0; i < entries.length; i++) { + const entry = entries[i]; + const currentDepth = entry.heading.depth; + + // Top-level entries are added directly to roots. + if (currentDepth <= 1) { + roots.push(entry); + continue; + } + + // For non-root entries, find the appropriate parent. + const previousEntry = entries[i - 1]; + const previousDepth = previousEntry.heading.depth; + + if (currentDepth > previousDepth) { + previousEntry.hierarchyChildren ??= []; + previousEntry.hierarchyChildren.push(entry); + } else { + // Use recursive helper to find the nearest valid parent. + const parent = findParent(entry, entries, i - 2); + parent.hierarchyChildren.push(entry); + } + } + + return roots; +}; + +/** + * Promotes children properties to the parent level if the section type is 'misc'. + * @param {import('../../types.d.ts').Section} section - The section to promote. + * @param {import('../../types.d.ts').Section} parent - The parent section. + */ +export const promoteMiscChildren = (section, parent) => { + // Only promote if the current section is of type 'misc' and the parent is not 'misc' + if (section.type === 'misc' && parent.type !== 'misc') { + Object.entries(section).forEach(([key, value]) => { + // Only promote certain keys + if (!UNPROMOTED_KEYS.includes(key)) { + // Merge the section's properties into the parent section + if (parent[key] && Array.isArray(parent[key])) { + parent[key] = parent[key].concat(value); + } else { + parent[key] ||= value; + } + } + }); + } +}; + +/** + * @returns {(head: import('../../../metadata/types').MetadataEntry, entries: import('../../../metadata/types').MetadataEntry[]) => import('../../types.d.ts').ModuleSection} + */ +export const createSectionBuilder = () => { + const html = getRemarkRehype(); + + /** + * Creates metadata from a hierarchized entry. + * @param {import('../../types.d.ts').HierarchizedEntry} entry - The entry to create metadata from. + * @returns {import('../../types.d.ts').Meta | undefined} The created metadata, or undefined if all fields are empty. + */ + const createMeta = ({ + added = [], + napiVersion = [], + deprecated = [], + removed = [], + changes = [], + }) => { + const meta = {}; + + if (added?.length) { + meta.added = enforceArray(added); + } + + meta.changes = changes; + + if (typeof napiVersion === 'number' || napiVersion?.length) { + meta.napiVersion = enforceArray(napiVersion); + } + + if (deprecated?.length) { + meta.deprecated = enforceArray(deprecated); + } + + if (removed?.length) { + meta.removed = enforceArray(removed); + } + + // Check if there are any non-empty fields in the meta object + const atLeastOneNonEmptyField = + changes?.length || Object.keys(meta).length > 1; + + // Return undefined if the meta object is completely empty + return atLeastOneNonEmptyField ? meta : undefined; + }; + + /** + * Creates a section from an entry and its heading. + * @param {import('../../types.d.ts').HierarchizedEntry} entry - The AST entry. + * @param {import('../../../metadata/types').HeadingNode} head - The head node of the entry. + * @returns {import('../../types.d.ts').Section} The created section. + */ + const createSection = (entry, head) => { + const section = { + textRaw: transformNodesToString(head.children), + name: head.data.name, + introduced_in: entry.introduced_in, + type: head.data.type, + }; + + const meta = createMeta(entry); + + if (meta !== undefined) { + section.meta = meta; + } + + return section; + }; + + /** + * Parses stability metadata and adds it to the section. + * @param {import('../../types.d.ts').Section} section - The section to update. + * @param {Array} nodes - The remaining AST nodes. + * @param {import('../../types.d.ts').HierarchizedEntry} entry - The entry providing stability information. + */ + const parseStability = (section, nodes, { stability, content }) => { + if (stability) { + section.stability = Number(stability.data.index); + section.stabilityText = stability.data.description; + + const stabilityIdx = content.children.indexOf(stability); + + if (stabilityIdx) { + nodes.splice(stabilityIdx - 1, 1); + } + } + }; + + /** + * Adds a description to the section. + * @param {import('../../types.d.ts').Section} section - The section to update. + * @param {Array} nodes - The remaining AST nodes. + */ + const addDescription = (section, nodes) => { + if (!nodes.length) { + return; + } + + const rendered = html.stringify( + html.runSync({ type: 'root', children: nodes }) + ); + + section.shortDesc = section.desc || undefined; + section.desc = rendered || undefined; + }; + + /** + * Adds additional metadata to the section based on its type. + * @param {import('../../types.d.ts').Section} section - The section to update. + * @param {import('../../types.d.ts').Section} parent - The parent section. + * @param {import('../../../metadata/types').HeadingNode} heading - The heading node of the section. + */ + const addAdditionalMetadata = (section, parent, heading) => { + if (!section.type || section.type === 'module') { + section.name = section.textRaw.toLowerCase().trim().replace(/\s+/g, '_'); + } + + if (!section.type) { + section.displayName = heading.data.name; + section.type = parent.type === 'misc' ? 'misc' : 'module'; + } + }; + + /** + * Adds the section to its parent section. + * @param {import('../../types.d.ts').Section} section - The section to add. + * @param {import('../../types.d.ts').Section} parent - The parent section. + */ + const addToParent = (section, parent) => { + const key = SECTION_TYPE_PLURALS[section.type] || 'properties'; + + parent[key] ??= []; + parent[key].push(section); + }; + + /** + * Processes children of a given entry and updates the section. + * @param {import('../../types.d.ts').HierarchizedEntry} entry - The current entry. + * @param {import('../../types.d.ts').Section} section - The current section. + */ + const handleChildren = ({ hierarchyChildren }, section) => + hierarchyChildren?.forEach(child => handleEntry(child, section)); + + /** + * Handles an entry and updates the parent section. + * @param {import('../../types.d.ts').HierarchizedEntry} entry - The entry to process. + * @param {import('../../types.d.ts').Section} parent - The parent section. + */ + const handleEntry = (entry, parent) => { + const [headingNode, ...nodes] = structuredClone(entry.content.children); + const section = createSection(entry, headingNode); + + parseStability(section, nodes, entry); + parseTypedList(section, nodes); + addDescription(section, nodes); + handleChildren(entry, section); + addAdditionalMetadata(section, parent, headingNode); + addToParent(section, parent); + promoteMiscChildren(section, parent); + }; + + /** + * Builds the module section from head metadata and entries. + * @param {import('../../../metadata/types').MetadataEntry} head - The head metadata entry. + * @param {Array} entries - The list of metadata entries. + * @returns {import('../../types.d.ts').ModuleSection} The constructed module section. + */ + return (head, entries) => { + const rootModule = { + type: 'module', + api: head.api, + // TODO(@avivkeller): This should be configurable + source: `doc/api/${head.api}.md`, + }; + + buildHierarchy(entries).forEach(entry => handleEntry(entry, rootModule)); + + return rootModule; + }; +}; diff --git a/packages/core/src/generators/legacy-json/utils/parseSignature.mjs b/packages/legacy/src/utils/json/signature.mjs similarity index 74% rename from packages/core/src/generators/legacy-json/utils/parseSignature.mjs rename to packages/legacy/src/utils/json/signature.mjs index e71ebe24..409e047a 100644 --- a/packages/core/src/generators/legacy-json/utils/parseSignature.mjs +++ b/packages/legacy/src/utils/json/signature.mjs @@ -1,23 +1,21 @@ -'use strict'; - -import { PARAM_EXPRESSION } from '../constants.mjs'; - -const OPTIONAL_LEVEL_CHANGES = { '[': 1, ']': -1 }; +import { OPTIONAL_LEVEL_CHANGES, PARAM_EXPRESSION } from './constants.mjs'; /** * @param {Number} depth * @param {String} char * @returns {Number} */ -const updateDepth = (depth, char) => +export const updateDepth = (depth, char) => depth + (OPTIONAL_LEVEL_CHANGES[char] || 0); /** + * Extracts a parameter name and tracks optional-depth changes from leading/trailing brackets. + * * @param {string} parameterName * @param {number} optionalDepth * @returns {[string, number, boolean]} */ -export function parseNameAndOptionalStatus(parameterName, optionalDepth) { +export const parseNameAndOptionalStatus = (parameterName, optionalDepth) => { // Let's check if the parameter is optional & grab its name at the same time. // We need to see if there's any leading brackets in front of the parameter // name. While we're doing that, we can also get the index where the @@ -51,13 +49,15 @@ export function parseNameAndOptionalStatus(parameterName, optionalDepth) { ); return [actualName, optionalDepth, isParameterOptional]; -} +}; /** + * Splits a parameter name at the first `=` sign to extract a default value. + * * @param {string} parameterName * @returns {[string, string | undefined]} */ -export function parseDefaultValue(parameterName) { +export const parseDefaultValue = parameterName => { /** * @type {string | undefined} */ @@ -73,15 +73,18 @@ export function parseDefaultValue(parameterName) { } return [parameterName, defaultValue]; -} +}; /** + * Finds a parameter descriptor by name, falling back to index-based lookup + * and searching nested option lists before returning a bare `{ name }` object. + * * @param {string} parameterName * @param {number} index - * @param {Array} markdownParameters - * @returns {import('../types.d.ts').Parameter} + * @param {Array} markdownParameters + * @returns {import('../../types.d.ts').Parameter} */ -export function findParameter(parameterName, index, markdownParameters) { +export const findParameter = (parameterName, index, markdownParameters) => { const parameter = markdownParameters[index]; if (parameter?.name === parameterName) { return parameter; @@ -105,15 +108,18 @@ export function findParameter(parameterName, index, markdownParameters) { // Default return if no matches are found return { name: parameterName }; -} +}; /** + * Combines declared parameter names with their markdown-sourced descriptors, + * tracking optional depth and default values. + * * @param {string[]} declaredParameters - * @param {Array} markdownParameters + * @param {Array} markdownParameters */ -export function parseParameters(declaredParameters, markdownParameters) { +export const parseParameters = (declaredParameters, markdownParameters) => { /** - * @type {Array} + * @type {Array} */ let parameters = []; @@ -162,16 +168,19 @@ export function parseParameters(declaredParameters, markdownParameters) { }); return parameters; -} +}; /** - * @param {string} textRaw Something like `new buffer.Blob([sources[, options]])` - * @param {Array} markdownParameters + * @returns {import('../../types.d.ts').MethodSignature} */ -export default (textRaw, markdownParameters) => { +const parseSignature = (textRaw, markdownParameters) => { /** - * @type {import('../types.d.ts').MethodSignature} + * @type {import('../../types.d.ts').MethodSignature} */ const signature = { params: [] }; @@ -206,3 +215,5 @@ export default (textRaw, markdownParameters) => { return signature; }; + +export default parseSignature; diff --git a/packages/react/README.md b/packages/react/README.md new file mode 100644 index 00000000..df49728b --- /dev/null +++ b/packages/react/README.md @@ -0,0 +1,19 @@ +# `@doc-kittens/react` — Maine Coon + +> _Fun fact: The Maine Coon is among the largest domestic cat breeds — fitting for the most feature-rich generator package._ + +`@doc-kittens/react` provides the React/JSX-based documentation generators: a JSX AST builder, the Orama search index, and the standalone web bundle. + +## Generators + +| Export | Description | +| ----------------------------- | --------------------------------------------------------------------- | +| `@doc-kittens/react/jsx-ast` | Transforms `MetadataEntry[]` into a JSX AST consumed by `web` | +| `@doc-kittens/react/orama-db` | Builds an Orama search database from `MetadataEntry[]` | +| `@doc-kittens/react/web` | Bundles the JSX AST into HTML/CSS/JS for the standalone documentation | + +## Installation + +```sh +npm install @doc-kittens/react +``` diff --git a/packages/react/package.json b/packages/react/package.json new file mode 100644 index 00000000..c0f0af1d --- /dev/null +++ b/packages/react/package.json @@ -0,0 +1,49 @@ +{ + "name": "@doc-kittens/react", + "type": "module", + "version": "1.0.0", + "description": "React-based documentation generators (web, orama-db, jsx-ast) for @doc-kittens", + "repository": { + "type": "git", + "url": "git+https://github.com/nodejs/doc-kit.git", + "directory": "packages/react" + }, + "exports": { + "./web": "./src/generators/web/index.mjs", + "./orama-db": "./src/generators/orama-db/index.mjs", + "./jsx-ast": "./src/generators/jsx-ast/index.mjs", + "./src/*": "./src/*" + }, + "imports": { + "#core/*": "@node-core/doc-kit/src/*" + }, + "dependencies": { + "@node-core/doc-kit": "*", + "@doc-kittens/internal": "*", + "@doc-kittens/legacy": "*", + "@heroicons/react": "^2.2.0", + "@node-core/rehype-shiki": "^1.4.1", + "@node-core/ui-components": "^1.6.3", + "@orama/orama": "^3.1.18", + "@orama/ui": "^1.5.4", + "@rollup/plugin-virtual": "^3.0.2", + "estree-util-to-js": "^2.0.0", + "hast-util-to-string": "^3.0.1", + "hastscript": "^9.0.1", + "lightningcss-wasm": "^1.32.0", + "mdast-util-slice-markdown": "^2.0.1", + "preact": "^10.29.0", + "preact-render-to-string": "^6.6.6", + "reading-time": "^1.5.0", + "recma-jsx": "^1.0.1", + "recma-stringify": "^1.0.0", + "rehype-raw": "^7.0.0", + "rehype-recma": "^1.0.0", + "rehype-stringify": "^10.0.1", + "rolldown": "^1.0.0-rc.10", + "semver": "^7.7.4", + "unified": "^11.0.5", + "unist-builder": "^4.0.0", + "unist-util-visit": "^5.1.0" + } +} diff --git a/packages/core/src/generators/jsx-ast/README.md b/packages/react/src/generators/jsx-ast/README.md similarity index 100% rename from packages/core/src/generators/jsx-ast/README.md rename to packages/react/src/generators/jsx-ast/README.md diff --git a/packages/core/src/generators/jsx-ast/index.mjs b/packages/react/src/generators/jsx-ast/index.mjs similarity index 77% rename from packages/core/src/generators/jsx-ast/index.mjs rename to packages/react/src/generators/jsx-ast/index.mjs index a11baab6..a6f58cc7 100644 --- a/packages/core/src/generators/jsx-ast/index.mjs +++ b/packages/react/src/generators/jsx-ast/index.mjs @@ -1,13 +1,14 @@ -import { buildSideBarProps } from './utils/buildBarProps.mjs'; -import buildContent from './utils/buildContent.mjs'; -import { getSortedHeadNodes } from './utils/getSortedHeadNodes.mjs'; -import { GITHUB_EDIT_URL } from '../../utils/configuration/templates.mjs'; -import { groupNodesByModule } from '../../utils/generators.mjs'; -import { getRemarkRecma } from '../../utils/remark.mjs'; -import { relative } from '../../utils/url.mjs'; +import { GITHUB_EDIT_URL } from '#core/utils/configuration/templates.mjs'; +import { groupNodesByModule } from '#core/utils/generators.mjs'; +import { relative } from '#core/utils/url.mjs'; + +import { buildSideBarProps } from '../../utils/jsx-ast/barProps.mjs'; +import buildContent from '../../utils/jsx-ast/content.mjs'; +import { getRemarkRecma } from '../../utils/jsx-ast/remark.mjs'; +import { getSortedHeadNodes } from '../../utils/jsx-ast/sortedHeadNodes.mjs'; export const name = 'jsx-ast'; -export const dependsOn = '@node-core/doc-kit/generators/metadata'; +export const dependsOn = '@doc-kittens/internal/metadata'; export const defaultConfiguration = { ref: 'main', pageURL: '{baseURL}/latest-{version}/api{path}.html', diff --git a/packages/core/src/generators/jsx-ast/types.d.ts b/packages/react/src/generators/jsx-ast/types.d.ts similarity index 64% rename from packages/core/src/generators/jsx-ast/types.d.ts rename to packages/react/src/generators/jsx-ast/types.d.ts index 6fcc2961..01ec32c9 100644 --- a/packages/core/src/generators/jsx-ast/types.d.ts +++ b/packages/react/src/generators/jsx-ast/types.d.ts @@ -1,5 +1,5 @@ -import type { MetadataEntry } from '../metadata/types'; -import type { JSXContent } from './utils/buildContent.mjs'; +import type { MetadataEntry } from '@doc-kittens/internal/src/generators/metadata/types'; +import type { JSXContent } from '../../utils/jsx-ast/content.mjs'; export type Generator = GeneratorMetadata< { diff --git a/packages/core/src/generators/orama-db/README.md b/packages/react/src/generators/orama-db/README.md similarity index 100% rename from packages/core/src/generators/orama-db/README.md rename to packages/react/src/generators/orama-db/README.md diff --git a/packages/core/src/generators/orama-db/index.mjs b/packages/react/src/generators/orama-db/index.mjs similarity index 76% rename from packages/core/src/generators/orama-db/index.mjs rename to packages/react/src/generators/orama-db/index.mjs index 390c08a7..7edd786c 100644 --- a/packages/core/src/generators/orama-db/index.mjs +++ b/packages/react/src/generators/orama-db/index.mjs @@ -2,17 +2,17 @@ import { join } from 'node:path'; +import getConfig from '#core/utils/configuration/index.mjs'; +import { writeFile } from '#core/utils/file.mjs'; +import { groupNodesByModule } from '#core/utils/generators.mjs'; +import { transformNodeToString } from '#core/utils/unist.mjs'; import { create, save, insertMultiple } from '@orama/orama'; -import { SCHEMA } from './constants.mjs'; -import { buildHierarchicalTitle } from './utils/title.mjs'; -import getConfig from '../../utils/configuration/index.mjs'; -import { writeFile } from '../../utils/file.mjs'; -import { groupNodesByModule } from '../../utils/generators.mjs'; -import { transformNodeToString } from '../../utils/unist.mjs'; +import { SCHEMA } from '../../utils/orama-db/constants.mjs'; +import { buildHierarchicalTitle } from '../../utils/orama-db/title.mjs'; export const name = 'orama-db'; -export const dependsOn = '@node-core/doc-kit/generators/metadata'; +export const dependsOn = '@doc-kittens/internal/metadata'; /** * Generates the Orama database. * diff --git a/packages/core/src/generators/orama-db/types.d.ts b/packages/react/src/generators/orama-db/types.d.ts similarity index 86% rename from packages/core/src/generators/orama-db/types.d.ts rename to packages/react/src/generators/orama-db/types.d.ts index 8c8c14db..1f076cd8 100644 --- a/packages/core/src/generators/orama-db/types.d.ts +++ b/packages/react/src/generators/orama-db/types.d.ts @@ -1,5 +1,5 @@ import type { Orama, RawData } from '@orama/orama'; -import type { MetadataEntry } from '../metadata/types'; +import type { MetadataEntry } from '@doc-kittens/internal/src/generators/metadata/types'; /** * Schema for the Orama database entry diff --git a/packages/core/src/generators/web/README.md b/packages/react/src/generators/web/README.md similarity index 100% rename from packages/core/src/generators/web/README.md rename to packages/react/src/generators/web/README.md diff --git a/packages/core/src/generators/web/index.mjs b/packages/react/src/generators/web/index.mjs similarity index 69% rename from packages/core/src/generators/web/index.mjs rename to packages/react/src/generators/web/index.mjs index 293fd177..474d18a2 100644 --- a/packages/core/src/generators/web/index.mjs +++ b/packages/react/src/generators/web/index.mjs @@ -2,25 +2,27 @@ import { readFile } from 'node:fs/promises'; import { createRequire } from 'node:module'; -import { join } from 'node:path'; +import { join, resolve } from 'node:path'; -import createASTBuilder from './utils/generate.mjs'; -import { processJSXEntries } from './utils/processing.mjs'; -import getConfig from '../../utils/configuration/index.mjs'; -import { writeFile } from '../../utils/file.mjs'; +import getConfig from '#core/utils/configuration/index.mjs'; +import { writeFile } from '#core/utils/file.mjs'; + +import { ROOT } from '../../utils/web/constants.mjs'; +import createASTBuilder from '../../utils/web/generate.mjs'; +import { processJSXEntries } from '../../utils/web/processing.mjs'; export const name = 'web'; -export const dependsOn = '@node-core/doc-kit/generators/jsx-ast'; +export const dependsOn = '@doc-kittens/react/jsx-ast'; export const defaultConfiguration = { templatePath: join(import.meta.dirname, 'template.html'), title: 'Node.js', imports: { '#theme/Logo': '@node-core/ui-components/Common/NodejsLogo', - '#theme/Navigation': join(import.meta.dirname, './ui/components/NavBar'), - '#theme/Sidebar': join(import.meta.dirname, './ui/components/SideBar'), - '#theme/Metabar': join(import.meta.dirname, './ui/components/MetaBar'), - '#theme/Footer': join(import.meta.dirname, './ui/components/NoOp'), - '#theme/Layout': join(import.meta.dirname, './ui/components/Layout'), + '#theme/Navigation': resolve(ROOT, './ui/components/NavBar'), + '#theme/Sidebar': resolve(ROOT, './ui/components/SideBar'), + '#theme/Metabar': resolve(ROOT, './ui/components/MetaBar'), + '#theme/Footer': resolve(ROOT, './ui/components/NoOp'), + '#theme/Layout': resolve(ROOT, './ui/components/Layout'), }, }; diff --git a/packages/react/src/generators/web/template.html b/packages/react/src/generators/web/template.html new file mode 100644 index 00000000..5b106e30 --- /dev/null +++ b/packages/react/src/generators/web/template.html @@ -0,0 +1,57 @@ + + + + + + + {{title}} + + + + + + + + + + + + + + + + + + +
    {{dehydrated}}
    + + + diff --git a/packages/core/src/generators/web/types.d.ts b/packages/react/src/generators/web/types.d.ts similarity index 75% rename from packages/core/src/generators/web/types.d.ts rename to packages/react/src/generators/web/types.d.ts index 5feed60e..7d84c624 100644 --- a/packages/core/src/generators/web/types.d.ts +++ b/packages/react/src/generators/web/types.d.ts @@ -1,4 +1,4 @@ -import type { JSXContent } from '../jsx-ast/utils/buildContent.mjs'; +import type { JSXContent } from '../../utils/jsx-ast/content.mjs'; export type Generator = GeneratorMetadata< { diff --git a/packages/core/src/generators/jsx-ast/utils/__tests__/ast.test.mjs b/packages/react/src/utils/jsx-ast/__tests__/ast.test.mjs similarity index 98% rename from packages/core/src/generators/jsx-ast/utils/__tests__/ast.test.mjs rename to packages/react/src/utils/jsx-ast/__tests__/ast.test.mjs index 4c21fbff..932ee1e5 100644 --- a/packages/core/src/generators/jsx-ast/utils/__tests__/ast.test.mjs +++ b/packages/react/src/utils/jsx-ast/__tests__/ast.test.mjs @@ -1,8 +1,8 @@ import assert from 'node:assert/strict'; import { describe, it } from 'node:test'; -import { AST_NODE_TYPES } from '../../constants.mjs'; import { toESTree, createJSXElement, createAttributeNode } from '../ast.mjs'; +import { AST_NODE_TYPES } from '../constants.mjs'; describe('toESTree', () => { it('preserves existing JSX fragment nodes', () => { diff --git a/packages/core/src/generators/jsx-ast/utils/__tests__/buildBarProps.test.mjs b/packages/react/src/utils/jsx-ast/__tests__/barProps.test.mjs similarity index 92% rename from packages/core/src/generators/jsx-ast/utils/__tests__/buildBarProps.test.mjs rename to packages/react/src/utils/jsx-ast/__tests__/barProps.test.mjs index 3e5db799..20681d09 100644 --- a/packages/core/src/generators/jsx-ast/utils/__tests__/buildBarProps.test.mjs +++ b/packages/react/src/utils/jsx-ast/__tests__/barProps.test.mjs @@ -1,17 +1,16 @@ import assert from 'node:assert/strict'; import { describe, it, mock } from 'node:test'; +import { loadGenerator } from '#core/loader.mjs'; +import { setConfig } from '#core/utils/configuration/index.mjs'; +import * as generatorsExports from '#core/utils/generators.mjs'; import { SemVer } from 'semver'; -import { loadGenerator } from '../../../../loader.mjs'; -import { setConfig } from '../../../../utils/configuration/index.mjs'; -import * as generatorsExports from '../../../../utils/generators.mjs'; - mock.module('reading-time', { defaultExport: () => ({ text: '5 min read' }), }); -mock.module('../../../../utils/generators.mjs', { +mock.module('#core/utils/generators.mjs', { namedExports: { ...generatorsExports, getCompatibleVersions: () => [ @@ -29,9 +28,9 @@ const { buildMetaBarProps, formatVersionOptions, buildSideBarProps, -} = await import('../buildBarProps.mjs'); +} = await import('../barProps.mjs'); -const jsxAstSpecifier = '@node-core/doc-kit/generators/jsx-ast'; +const jsxAstSpecifier = '@doc-kittens/react/jsx-ast'; const jsxAstGenerator = await loadGenerator(jsxAstSpecifier); await setConfig( diff --git a/packages/core/src/generators/jsx-ast/utils/__tests__/buildContent.test.mjs b/packages/react/src/utils/jsx-ast/__tests__/content.test.mjs similarity index 93% rename from packages/core/src/generators/jsx-ast/utils/__tests__/buildContent.test.mjs rename to packages/react/src/utils/jsx-ast/__tests__/content.test.mjs index 5166a319..8853d43e 100644 --- a/packages/core/src/generators/jsx-ast/utils/__tests__/buildContent.test.mjs +++ b/packages/react/src/utils/jsx-ast/__tests__/content.test.mjs @@ -1,8 +1,9 @@ import assert from 'node:assert/strict'; import { describe, it } from 'node:test'; -import { setConfig } from '../../../../utils/configuration/index.mjs'; -import { transformHeadingNode } from '../buildContent.mjs'; +import { setConfig } from '#core/utils/configuration/index.mjs'; + +import { transformHeadingNode } from '../content.mjs'; const heading = { type: 'heading', diff --git a/packages/core/src/generators/jsx-ast/utils/__tests__/signature.test.mjs b/packages/react/src/utils/jsx-ast/__tests__/signature.test.mjs similarity index 100% rename from packages/core/src/generators/jsx-ast/utils/__tests__/signature.test.mjs rename to packages/react/src/utils/jsx-ast/__tests__/signature.test.mjs diff --git a/packages/core/src/generators/jsx-ast/utils/__tests__/types.test.mjs b/packages/react/src/utils/jsx-ast/__tests__/types.test.mjs similarity index 100% rename from packages/core/src/generators/jsx-ast/utils/__tests__/types.test.mjs rename to packages/react/src/utils/jsx-ast/__tests__/types.test.mjs diff --git a/packages/core/src/generators/jsx-ast/utils/ast.mjs b/packages/react/src/utils/jsx-ast/ast.mjs similarity index 98% rename from packages/core/src/generators/jsx-ast/utils/ast.mjs rename to packages/react/src/utils/jsx-ast/ast.mjs index b74d64aa..e1f71151 100644 --- a/packages/core/src/generators/jsx-ast/utils/ast.mjs +++ b/packages/react/src/utils/jsx-ast/ast.mjs @@ -1,6 +1,6 @@ import { u as createTree } from 'unist-builder'; -import { AST_NODE_TYPES } from '../constants.mjs'; +import { AST_NODE_TYPES } from './constants.mjs'; /** * @typedef {Object} JSXOptions diff --git a/packages/core/src/generators/jsx-ast/utils/buildBarProps.mjs b/packages/react/src/utils/jsx-ast/barProps.mjs similarity index 73% rename from packages/core/src/generators/jsx-ast/utils/buildBarProps.mjs rename to packages/react/src/utils/jsx-ast/barProps.mjs index 447f685d..6dc7959d 100644 --- a/packages/core/src/generators/jsx-ast/utils/buildBarProps.mjs +++ b/packages/react/src/utils/jsx-ast/barProps.mjs @@ -1,20 +1,20 @@ 'use strict'; -import readingTime from 'reading-time'; -import { visit } from 'unist-util-visit'; - -import getConfig from '../../../utils/configuration/index.mjs'; -import { populate } from '../../../utils/configuration/templates.mjs'; +import getConfig from '#core/utils/configuration/index.mjs'; +import { populate } from '#core/utils/configuration/templates.mjs'; import { getCompatibleVersions, getVersionFromSemVer, -} from '../../../utils/generators.mjs'; -import { TOC_MAX_HEADING_DEPTH } from '../constants.mjs'; +} from '#core/utils/generators.mjs'; +import readingTime from 'reading-time'; +import { visit } from 'unist-util-visit'; + +import { TOC_MAX_HEADING_DEPTH } from './constants.mjs'; /** * Generate a combined plain text string from all MDAST entries for estimating reading time. * - * @param {Array} entries - API documentation entries + * @param {Array} entries - API documentation entries */ export const extractTextContent = entries => { return entries.reduce((acc, entry) => { @@ -27,7 +27,7 @@ export const extractTextContent = entries => { /** * Determines if an entry should be included in the Table of Contents. - * @param {import('../../metadata/types').MetadataEntry} entry + * @param {import('@doc-kittens/internal/src/generators/metadata/types').MetadataEntry} entry */ const shouldIncludeEntryInToC = ({ heading }) => // Only include headings with text, @@ -37,7 +37,7 @@ const shouldIncludeEntryInToC = ({ heading }) => /** * Extracts and formats heading information from an API documentation entry. - * @param {import('../../metadata/types').MetadataEntry} entry + * @param {import('@doc-kittens/internal/src/generators/metadata/types').MetadataEntry} entry */ const extractHeading = entry => { const data = entry.heading.data; @@ -67,7 +67,7 @@ const extractHeading = entry => { /** * Build the list of heading metadata for sidebar navigation. * - * @param {Array} entries - All API metadata entries + * @param {Array} entries - All API metadata entries */ export const extractHeadings = entries => entries.filter(shouldIncludeEntryInToC).map(extractHeading); @@ -75,8 +75,8 @@ export const extractHeadings = entries => /** * Builds metadata for the meta bar (right panel). * - * @param {import('../../metadata/types').MetadataEntry} head - Main API metadata entry (used as reference point) - * @param {Array} entries - All documentation entries for a given API item + * @param {import('@doc-kittens/internal/src/generators/metadata/types').MetadataEntry} head - Main API metadata entry (used as reference point) + * @param {Array} entries - All documentation entries for a given API item */ export const buildMetaBarProps = (head, entries) => { const config = getConfig('jsx-ast'); @@ -96,7 +96,7 @@ export const buildMetaBarProps = (head, entries) => { /** * Converts a compatible version entry into a version label and link. * - * @param {Array} compatibleVersions - Compatible versions + * @param {Array} compatibleVersions - Compatible versions * @param {string} path - path for the version URL */ export const formatVersionOptions = (compatibleVersions, path) => { @@ -129,7 +129,7 @@ export const formatVersionOptions = (compatibleVersions, path) => { /** * Builds metadata for the sidebar (left panel). * - * @param {import('../../metadata/types').MetadataEntry} entry - Current documentation entry + * @param {import('@doc-kittens/internal/src/generators/metadata/types').MetadataEntry} entry - Current documentation entry * @param {Array<[string, string]>} docPages - Available doc pages for sidebar navigation */ export const buildSideBarProps = (entry, docPages) => { diff --git a/packages/core/src/generators/jsx-ast/constants.mjs b/packages/react/src/utils/jsx-ast/constants.mjs similarity index 100% rename from packages/core/src/generators/jsx-ast/constants.mjs rename to packages/react/src/utils/jsx-ast/constants.mjs diff --git a/packages/core/src/generators/jsx-ast/utils/buildContent.mjs b/packages/react/src/utils/jsx-ast/content.mjs similarity index 82% rename from packages/core/src/generators/jsx-ast/utils/buildContent.mjs rename to packages/react/src/utils/jsx-ast/content.mjs index 3091e80d..b442fe67 100644 --- a/packages/core/src/generators/jsx-ast/utils/buildContent.mjs +++ b/packages/react/src/utils/jsx-ast/content.mjs @@ -1,14 +1,19 @@ 'use strict'; +import { enforceArray } from '#core/utils/array.mjs'; +import getConfig from '#core/utils/configuration/index.mjs'; +import { + GITHUB_BLOB_URL, + populate, +} from '#core/utils/configuration/templates.mjs'; +import { UNIST } from '#core/utils/queries/index.mjs'; import { h as createElement } from 'hastscript'; import { slice } from 'mdast-util-slice-markdown'; import { u as createTree } from 'unist-builder'; import { SKIP, visit } from 'unist-util-visit'; import { createJSXElement } from './ast.mjs'; -import { buildMetaBarProps } from './buildBarProps.mjs'; -import { enforceArray } from '../../../utils/array.mjs'; -import { JSX_IMPORTS } from '../../web/constants.mjs'; +import { buildMetaBarProps } from './barProps.mjs'; import { STABILITY_LEVELS, LIFECYCLE_LABELS, @@ -18,22 +23,17 @@ import { ALERT_LEVELS, TYPES_WITH_METHOD_SIGNATURES, TYPE_PREFIX_LENGTH, -} from '../constants.mjs'; +} from './constants.mjs'; import { insertSignatureCodeBlock, createSignatureTable, getFullName, } from './signature.mjs'; -import getConfig from '../../../utils/configuration/index.mjs'; -import { - GITHUB_BLOB_URL, - populate, -} from '../../../utils/configuration/templates.mjs'; -import { UNIST } from '../../../utils/queries/index.mjs'; +import { JSX_IMPORTS } from '../web/constants.mjs'; /** * Processes lifecycle and change history data into a sorted array of change entries. - * @param {import('../../metadata/types').MetadataEntry} entry - The metadata entry + * @param {import('@doc-kittens/internal/src/generators/metadata/types').MetadataEntry} entry - The metadata entry * @param {import('unified').Processor} remark - The remark processor */ export const gatherChangeEntries = (entry, remark) => { @@ -57,7 +57,7 @@ export const gatherChangeEntries = (entry, remark) => { /** * Creates a JSX ChangeHistory element or returns null if no changes. - * @param {import('../../metadata/types').MetadataEntry} entry - The metadata entry + * @param {import('@doc-kittens/internal/src/generators/metadata/types').MetadataEntry} entry - The metadata entry * @param {import('unified').Processor} remark - The remark processor */ export const createChangeElement = (entry, remark) => { @@ -124,7 +124,7 @@ export const extractHeadingContent = content => { /** * Creates a heading wrapper element with anchors, icons, and optional change history. - * @param {import('../../metadata/types').HeadingNode} content - The content node to extract text from + * @param {import('@doc-kittens/internal/src/generators/metadata/types').HeadingNode} content - The content node to extract text from * @param {import('unist').Node|null} changeElement - The change history element, if available */ export const createHeadingElement = (content, changeElement) => { @@ -162,7 +162,7 @@ export const createHeadingElement = (content, changeElement) => { /** * Converts a stability note node to an AlertBox JSX element - * @param {import('../../metadata/types').StabilityNode} node - The stability node to transform + * @param {import('@doc-kittens/internal/src/generators/metadata/types').StabilityNode} node - The stability node to transform * @param {number} index - The index of the node in its parent's children array * @param {import('unist').Parent} parent - The parent node containing the stability node */ @@ -196,9 +196,9 @@ const getLevelFromDeprecationType = typeText => { /** * Transforms a heading node by injecting metadata, source links, and signatures. - * @param {import('../../metadata/types').MetadataEntry} entry - The API metadata entry + * @param {import('@doc-kittens/internal/src/generators/metadata/types').MetadataEntry} entry - The API metadata entry * @param {import('unified').Processor} remark - The remark processor - * @param {import('../../metadata/types').HeadingNode} node - The heading node to transform + * @param {import('@doc-kittens/internal/src/generators/metadata/types').HeadingNode} node - The heading node to transform * @param {number} index - The index of the node in its parent's children array * @param {import('unist').Parent} parent - The parent node containing the heading */ @@ -253,7 +253,7 @@ export const transformHeadingNode = async ( /** * Processes a single API documentation entry's content - * @param {import('../../metadata/types').MetadataEntry} entry - The API metadata entry to process + * @param {import('@doc-kittens/internal/src/generators/metadata/types').MetadataEntry} entry - The API metadata entry to process * @param {import('unified').Processor} remark - The remark processor */ export const processEntry = (entry, remark) => { @@ -281,8 +281,8 @@ export const processEntry = (entry, remark) => { /** * Builds the overall document layout tree - * @param {Array} entries - API documentation metadata entries - * @param {ReturnType} sideBarProps - Props for the sidebar component + * @param {Array} entries - API documentation metadata entries + * @param {ReturnType} sideBarProps - Props for the sidebar component * @param {ReturnType} metaBarProps - Props for the meta bar component * @param {import('unified').Processor} remark - The remark processor */ @@ -301,11 +301,11 @@ export const createDocumentLayout = ( ]); /** - * @typedef {import('estree').Node & { data: import('../../metadata/types').MetadataEntry }} JSXContent + * @typedef {import('estree').Node & { data: import('@doc-kittens/internal/src/generators/metadata/types').MetadataEntry }} JSXContent * * Transforms API metadata entries into processed MDX content - * @param {Array} metadataEntries - API documentation metadata entries - * @param {import('../../metadata/types').MetadataEntry} head - Main API metadata entry with version information + * @param {Array} metadataEntries - API documentation metadata entries + * @param {import('@doc-kittens/internal/src/generators/metadata/types').MetadataEntry} head - Main API metadata entry with version information * @param {Object} sideBarProps - Props for the sidebar component * @param {import('unified').Processor} remark - Remark processor instance for markdown processing * @returns {Promise} diff --git a/packages/react/src/utils/jsx-ast/remark.mjs b/packages/react/src/utils/jsx-ast/remark.mjs new file mode 100644 index 00000000..680c3558 --- /dev/null +++ b/packages/react/src/utils/jsx-ast/remark.mjs @@ -0,0 +1,38 @@ +'use strict'; + +import { highlighter } from '#core/utils/highlighter.mjs'; +import rehypeShikiji from '@node-core/rehype-shiki/plugin'; +import recmaJsx from 'recma-jsx'; +import recmaStringify from 'recma-stringify'; +import rehypeRaw from 'rehype-raw'; +import rehypeRecma from 'rehype-recma'; +import remarkParse from 'remark-parse'; +import remarkRehype from 'remark-rehype'; +import { unified } from 'unified'; + +import { AST_NODE_TYPES } from './constants.mjs'; +import transformElements from './transformer.mjs'; + +const passThrough = ['element', ...Object.values(AST_NODE_TYPES.MDX)]; + +const singletonShiki = await rehypeShikiji({ highlighter }); + +/** + * Retrieves an instance of Remark configured to output JSX code, + * including parsing Code Boxes with syntax highlighting. + */ +export const getRemarkRecma = () => + unified() + .use(remarkParse) + // We make Rehype ignore existing HTML nodes, and JSX nodes + // as these are nodes we manually created during the generation process + // We also allow dangerous HTML to be passed through, since we have HTML within our Markdown + // and we trust the sources of the Markdown files + .use(remarkRehype, { allowDangerousHtml: true, passThrough }) + // Any `raw` HTML in the markdown must be converted to AST in order for Recma to understand it + .use(rehypeRaw, { passThrough }) + .use(() => singletonShiki) + .use(transformElements) + .use(rehypeRecma) + .use(recmaJsx) + .use(recmaStringify); diff --git a/packages/core/src/generators/jsx-ast/utils/signature.mjs b/packages/react/src/utils/jsx-ast/signature.mjs similarity index 83% rename from packages/core/src/generators/jsx-ast/utils/signature.mjs rename to packages/react/src/utils/jsx-ast/signature.mjs index 4d8df94e..94d17fa5 100644 --- a/packages/core/src/generators/jsx-ast/utils/signature.mjs +++ b/packages/react/src/utils/jsx-ast/signature.mjs @@ -1,18 +1,18 @@ +import { highlighter } from '#core/utils/highlighter.mjs'; +import { UNIST } from '#core/utils/queries/index.mjs'; +import { parseListItem } from '@doc-kittens/legacy/src/utils/json/list.mjs'; +import parseSignature from '@doc-kittens/legacy/src/utils/json/signature.mjs'; import { h as createElement } from 'hastscript'; import { createJSXElement } from './ast.mjs'; import { parseListIntoProperties } from './types.mjs'; -import { highlighter } from '../../../utils/highlighter.mjs'; -import { UNIST } from '../../../utils/queries/index.mjs'; -import { parseListItem } from '../../legacy-json/utils/parseList.mjs'; -import parseSignature from '../../legacy-json/utils/parseSignature.mjs'; -import { JSX_IMPORTS } from '../../web/constants.mjs'; +import { JSX_IMPORTS } from '../web/constants.mjs'; /** * Generates a string representation of a function or class signature. * * @param {string} functionName - The name of the function or class. - * @param {import('../../legacy-json/types').MethodSignature} signature - The parsed signature object. + * @param {import('@doc-kittens/legacy/src/generators/json/types').MethodSignature} signature - The parsed signature object. * @param {string} prefix - Optional prefix, i.e. `'new '` for constructors. */ export const generateSignature = ( @@ -52,7 +52,7 @@ export const generateSignature = ( * Creates a syntax-highlighted code block for a signature using rehype-shiki. * * @param {string} functionName - The function name to display. - * @param {import('../../legacy-json/types').MethodSignature} signature - Signature object with parameter and return type info. + * @param {import('@doc-kittens/legacy/src/generators/json/types').MethodSignature} signature - Signature object with parameter and return type info. * @param {string} prefix - Optional prefix like `'new '`. */ export const createSignatureCodeBlock = (functionName, signature, prefix) => { @@ -66,7 +66,7 @@ export const createSignatureCodeBlock = (functionName, signature, prefix) => { * Infers the "real" function name from a heading node. * Useful when auto-generated headings differ from code tokens. * - * @param {import('../../metadata/types').HeadingData} heading - Metadata with name and text fields. + * @param {import('@doc-kittens/internal/src/generators/metadata/types').HeadingData} heading - Metadata with name and text fields. * @param {any} fallback - Fallback value if inference fails. */ export const getFullName = ({ name, text }, fallback = name) => { @@ -91,7 +91,7 @@ export const getFullName = ({ name, text }, fallback = name) => { * Mutates the `children` array by injecting the signature HAST node. * * @param {import('@types/mdast').Parent} parent - The parent MDAST node (usually a section). - * @param {import('../../metadata/types').HeadingNode} heading - The heading node with metadata. + * @param {import('@doc-kittens/internal/src/generators/metadata/types').HeadingNode} heading - The heading node with metadata. * @param {number} idx - The index at which the heading occurs in `parent.children`. */ export const insertSignatureCodeBlock = ({ children }, { data }, idx) => { diff --git a/packages/core/src/generators/jsx-ast/utils/getSortedHeadNodes.mjs b/packages/react/src/utils/jsx-ast/sortedHeadNodes.mjs similarity index 59% rename from packages/core/src/generators/jsx-ast/utils/getSortedHeadNodes.mjs rename to packages/react/src/utils/jsx-ast/sortedHeadNodes.mjs index c0f0c54c..827909ba 100644 --- a/packages/core/src/generators/jsx-ast/utils/getSortedHeadNodes.mjs +++ b/packages/react/src/utils/jsx-ast/sortedHeadNodes.mjs @@ -1,11 +1,11 @@ 'use strict'; -import { OVERRIDDEN_POSITIONS } from '../constants.mjs'; +import { OVERRIDDEN_POSITIONS } from './constants.mjs'; /** * Sorts entries by OVERRIDDEN_POSITIONS and then heading name. - * @param {import('../../metadata/types').MetadataEntry} a - * @param {import('../../metadata/types').MetadataEntry} b + * @param {import('@doc-kittens/internal/src/generators/metadata/types').MetadataEntry} a + * @param {import('@doc-kittens/internal/src/generators/metadata/types').MetadataEntry} b * @returns {number} */ const headingSortFn = (a, b) => { @@ -29,8 +29,8 @@ const headingSortFn = (a, b) => { /** * Filters and sorts entries by OVERRIDDEN_POSITIONS and then heading name. - * @param {Array} entries - * @returns {Array} + * @param {Array} entries + * @returns {Array} */ export const getSortedHeadNodes = entries => entries.filter(node => node.heading.depth === 1).toSorted(headingSortFn); diff --git a/packages/core/src/generators/jsx-ast/utils/transformer.mjs b/packages/react/src/utils/jsx-ast/transformer.mjs similarity index 97% rename from packages/core/src/generators/jsx-ast/utils/transformer.mjs rename to packages/react/src/utils/jsx-ast/transformer.mjs index b5c732ff..5b3a7fac 100644 --- a/packages/core/src/generators/jsx-ast/utils/transformer.mjs +++ b/packages/react/src/utils/jsx-ast/transformer.mjs @@ -1,7 +1,7 @@ import { toString } from 'hast-util-to-string'; import { visit } from 'unist-util-visit'; -import { TAG_TRANSFORMS } from '../constants.mjs'; +import { TAG_TRANSFORMS } from './constants.mjs'; /** * @template {import('unist').Node} T diff --git a/packages/core/src/generators/jsx-ast/utils/types.mjs b/packages/react/src/utils/jsx-ast/types.mjs similarity index 93% rename from packages/core/src/generators/jsx-ast/utils/types.mjs rename to packages/react/src/utils/jsx-ast/types.mjs index 3b9c14dd..90e40677 100644 --- a/packages/core/src/generators/jsx-ast/utils/types.mjs +++ b/packages/react/src/utils/jsx-ast/types.mjs @@ -1,9 +1,9 @@ +import { QUERIES, UNIST } from '#core/utils/queries/index.mjs'; +import { transformNodesToString } from '#core/utils/unist.mjs'; +import { DEFAULT_EXPRESSION } from '@doc-kittens/legacy/src/utils/json/constants.mjs'; import { u as createTree } from 'unist-builder'; -import { QUERIES, UNIST } from '../../../utils/queries/index.mjs'; -import { transformNodesToString } from '../../../utils/unist.mjs'; -import { DEFAULT_EXPRESSION } from '../../legacy-json/constants.mjs'; -import { TRIMMABLE_PADDING_REGEX } from '../constants.mjs'; +import { TRIMMABLE_PADDING_REGEX } from './constants.mjs'; /** * Checks if the node is a union separator (`' | '`) or a type reference diff --git a/packages/core/src/generators/orama-db/utils/__tests__/title.test.mjs b/packages/react/src/utils/orama-db/__tests__/title.test.mjs similarity index 100% rename from packages/core/src/generators/orama-db/utils/__tests__/title.test.mjs rename to packages/react/src/utils/orama-db/__tests__/title.test.mjs diff --git a/packages/core/src/generators/orama-db/constants.mjs b/packages/react/src/utils/orama-db/constants.mjs similarity index 100% rename from packages/core/src/generators/orama-db/constants.mjs rename to packages/react/src/utils/orama-db/constants.mjs diff --git a/packages/core/src/generators/orama-db/utils/title.mjs b/packages/react/src/utils/orama-db/title.mjs similarity index 85% rename from packages/core/src/generators/orama-db/utils/title.mjs rename to packages/react/src/utils/orama-db/title.mjs index 4863ce36..19bd7ef6 100644 --- a/packages/core/src/generators/orama-db/utils/title.mjs +++ b/packages/react/src/utils/orama-db/title.mjs @@ -1,7 +1,7 @@ /** * Builds a hierarchical title chain based on heading depths * - * @param {import("../../metadata/types").MetadataEntry[]} headings - All headings sorted by order + * @param {import("@doc-kittens/internal/src/generators/metadata/types").MetadataEntry[]} headings - All headings sorted by order * @param {number} currentIndex - Index of current heading * @returns {string} Hierarchical title */ diff --git a/packages/core/src/generators/web/utils/bundle.mjs b/packages/react/src/utils/web/bundle.mjs similarity index 97% rename from packages/core/src/generators/web/utils/bundle.mjs rename to packages/react/src/utils/web/bundle.mjs index 6c8bccde..6ad20513 100644 --- a/packages/core/src/generators/web/utils/bundle.mjs +++ b/packages/react/src/utils/web/bundle.mjs @@ -1,12 +1,12 @@ import { resolve } from 'node:path'; +import getConfig from '#core/utils/configuration/index.mjs'; +import { lazy } from '#core/utils/misc.mjs'; import virtual from '@rollup/plugin-virtual'; import { build } from 'rolldown'; import cssLoader from './css.mjs'; import getStaticData from './data.mjs'; -import getConfig from '../../../utils/configuration/index.mjs'; -import { lazy } from '../../../utils/misc.mjs'; // Resolve node_modules relative to this package (doc-kit), not cwd. // We do this by finding where one of our dependencies (preact) is stored, diff --git a/packages/core/src/generators/web/utils/chunks.mjs b/packages/react/src/utils/web/chunks.mjs similarity index 100% rename from packages/core/src/generators/web/utils/chunks.mjs rename to packages/react/src/utils/web/chunks.mjs diff --git a/packages/core/src/generators/web/constants.mjs b/packages/react/src/utils/web/constants.mjs similarity index 100% rename from packages/core/src/generators/web/constants.mjs rename to packages/react/src/utils/web/constants.mjs diff --git a/packages/core/src/generators/web/utils/css.mjs b/packages/react/src/utils/web/css.mjs similarity index 100% rename from packages/core/src/generators/web/utils/css.mjs rename to packages/react/src/utils/web/css.mjs diff --git a/packages/core/src/generators/web/utils/data.mjs b/packages/react/src/utils/web/data.mjs similarity index 90% rename from packages/core/src/generators/web/utils/data.mjs rename to packages/react/src/utils/web/data.mjs index cd4fd465..f3527f52 100644 --- a/packages/core/src/generators/web/utils/data.mjs +++ b/packages/react/src/utils/web/data.mjs @@ -1,8 +1,7 @@ +import getConfig from '#core/utils/configuration/index.mjs'; +import { lazy } from '#core/utils/misc.mjs'; import { LANGS } from '@node-core/rehype-shiki'; -import getConfig from '../../../utils/configuration/index.mjs'; -import { lazy } from '../../../utils/misc.mjs'; - /** * Constructs a set of static, minimal data to send from server to client. * diff --git a/packages/core/src/generators/web/utils/generate.mjs b/packages/react/src/utils/web/generate.mjs similarity index 98% rename from packages/core/src/generators/web/utils/generate.mjs rename to packages/react/src/utils/web/generate.mjs index ee10ec3d..33642325 100644 --- a/packages/core/src/generators/web/utils/generate.mjs +++ b/packages/react/src/utils/web/generate.mjs @@ -1,6 +1,6 @@ import { resolve } from 'node:path'; -import { JSX_IMPORTS, ROOT } from '../constants.mjs'; +import { JSX_IMPORTS, ROOT } from './constants.mjs'; /** * Creates an ES Module `import` statement as a string, based on parameters. diff --git a/packages/core/src/generators/web/utils/processing.mjs b/packages/react/src/utils/web/processing.mjs similarity index 93% rename from packages/core/src/generators/web/utils/processing.mjs rename to packages/react/src/utils/web/processing.mjs index e25dc235..101b2872 100644 --- a/packages/core/src/generators/web/utils/processing.mjs +++ b/packages/react/src/utils/web/processing.mjs @@ -1,19 +1,19 @@ import { randomUUID } from 'node:crypto'; +import { minifyHTML } from '#core/utils/html-minifier.mjs'; +import { relative } from '#core/utils/url.mjs'; import { jsx, toJs } from 'estree-util-to-js'; import { transform } from 'lightningcss-wasm'; import bundleCode from './bundle.mjs'; import { createChunkedRequire } from './chunks.mjs'; -import { minifyHTML } from '../../../utils/html-minifier.mjs'; -import { relative } from '../../../utils/url.mjs'; -import { SPECULATION_RULES } from '../constants.mjs'; +import { SPECULATION_RULES } from './constants.mjs'; /** * Converts JSX AST entries to server and client JavaScript code. * This is the CPU-intensive step that can be parallelized. * - * @param {Array} entries - JSX AST entries + * @param {Array} entries - JSX AST entries * @param {function} buildServerProgram - Wraps code for server execution * @param {function} buildClientProgram - Wraps code for client hydration * @returns {{serverCodeMap: Map, clientCodeMap: Map}} @@ -80,7 +80,7 @@ export async function executeServerCode(serverCodeMap, requireFn) { * Processes a single JSX AST (Abstract Syntax Tree) entry to generate a complete * HTML page, including server-side rendered content, client-side JavaScript, and CSS. * - * @param {Array} entries - The JSX AST entry to process. + * @param {Array} entries - The JSX AST entry to process. * @param {string} template - The HTML template string that serves as the base for the output page. * @param {ReturnType} astBuilders - The AST generators * @param {ReturnType} requireFn - A Node.js `require` function. diff --git a/packages/core/src/generators/web/ui/components/CodeBox.jsx b/packages/react/src/utils/web/ui/components/CodeBox.jsx similarity index 100% rename from packages/core/src/generators/web/ui/components/CodeBox.jsx rename to packages/react/src/utils/web/ui/components/CodeBox.jsx diff --git a/packages/core/src/generators/web/ui/components/Layout/index.jsx b/packages/react/src/utils/web/ui/components/Layout/index.jsx similarity index 100% rename from packages/core/src/generators/web/ui/components/Layout/index.jsx rename to packages/react/src/utils/web/ui/components/Layout/index.jsx diff --git a/packages/core/src/generators/web/ui/components/MetaBar/index.jsx b/packages/react/src/utils/web/ui/components/MetaBar/index.jsx similarity index 100% rename from packages/core/src/generators/web/ui/components/MetaBar/index.jsx rename to packages/react/src/utils/web/ui/components/MetaBar/index.jsx diff --git a/packages/core/src/generators/web/ui/components/MetaBar/index.module.css b/packages/react/src/utils/web/ui/components/MetaBar/index.module.css similarity index 100% rename from packages/core/src/generators/web/ui/components/MetaBar/index.module.css rename to packages/react/src/utils/web/ui/components/MetaBar/index.module.css diff --git a/packages/core/src/generators/web/ui/components/NavBar.jsx b/packages/react/src/utils/web/ui/components/NavBar.jsx similarity index 100% rename from packages/core/src/generators/web/ui/components/NavBar.jsx rename to packages/react/src/utils/web/ui/components/NavBar.jsx diff --git a/packages/core/src/generators/web/ui/components/NoOp.jsx b/packages/react/src/utils/web/ui/components/NoOp.jsx similarity index 100% rename from packages/core/src/generators/web/ui/components/NoOp.jsx rename to packages/react/src/utils/web/ui/components/NoOp.jsx diff --git a/packages/core/src/generators/web/ui/components/SearchBox/index.jsx b/packages/react/src/utils/web/ui/components/SearchBox/index.jsx similarity index 100% rename from packages/core/src/generators/web/ui/components/SearchBox/index.jsx rename to packages/react/src/utils/web/ui/components/SearchBox/index.jsx diff --git a/packages/core/src/generators/web/ui/components/SearchBox/index.module.css b/packages/react/src/utils/web/ui/components/SearchBox/index.module.css similarity index 100% rename from packages/core/src/generators/web/ui/components/SearchBox/index.module.css rename to packages/react/src/utils/web/ui/components/SearchBox/index.module.css diff --git a/packages/core/src/generators/web/ui/components/SideBar/index.jsx b/packages/react/src/utils/web/ui/components/SideBar/index.jsx similarity index 100% rename from packages/core/src/generators/web/ui/components/SideBar/index.jsx rename to packages/react/src/utils/web/ui/components/SideBar/index.jsx diff --git a/packages/core/src/generators/web/ui/components/SideBar/index.module.css b/packages/react/src/utils/web/ui/components/SideBar/index.module.css similarity index 100% rename from packages/core/src/generators/web/ui/components/SideBar/index.module.css rename to packages/react/src/utils/web/ui/components/SideBar/index.module.css diff --git a/packages/core/src/generators/web/ui/constants.mjs b/packages/react/src/utils/web/ui/constants.mjs similarity index 100% rename from packages/core/src/generators/web/ui/constants.mjs rename to packages/react/src/utils/web/ui/constants.mjs diff --git a/packages/core/src/generators/web/ui/hooks/useOrama.mjs b/packages/react/src/utils/web/ui/hooks/useOrama.mjs similarity index 100% rename from packages/core/src/generators/web/ui/hooks/useOrama.mjs rename to packages/react/src/utils/web/ui/hooks/useOrama.mjs diff --git a/packages/core/src/generators/web/ui/hooks/useTheme.mjs b/packages/react/src/utils/web/ui/hooks/useTheme.mjs similarity index 100% rename from packages/core/src/generators/web/ui/hooks/useTheme.mjs rename to packages/react/src/utils/web/ui/hooks/useTheme.mjs diff --git a/packages/core/src/generators/web/ui/index.css b/packages/react/src/utils/web/ui/index.css similarity index 100% rename from packages/core/src/generators/web/ui/index.css rename to packages/react/src/utils/web/ui/index.css diff --git a/packages/core/src/generators/web/ui/package.json b/packages/react/src/utils/web/ui/package.json similarity index 100% rename from packages/core/src/generators/web/ui/package.json rename to packages/react/src/utils/web/ui/package.json diff --git a/packages/core/src/generators/web/ui/types.d.ts b/packages/react/src/utils/web/ui/types.d.ts similarity index 100% rename from packages/core/src/generators/web/ui/types.d.ts rename to packages/react/src/utils/web/ui/types.d.ts diff --git a/packages/website/README.md b/packages/website/README.md new file mode 100644 index 00000000..60b2aa9c --- /dev/null +++ b/packages/website/README.md @@ -0,0 +1,19 @@ +# `@doc-kittens/website` — British Shorthair + +> _Fun fact: The British Shorthair is famously easy-going and adaptable — perfect for generators destined to live on the public web._ + +`@doc-kittens/website` provides documentation generators tailored for public websites: sitemaps, LLM-friendly indexes, and API link metadata. + +## Generators + +| Export | Description | +| -------------------------------- | -------------------------------------------------------------------- | +| `@doc-kittens/website/sitemap` | Emits a `sitemap.xml` for the API documentation | +| `@doc-kittens/website/llms-txt` | Emits an `llms.txt` index summarising every API page | +| `@doc-kittens/website/api-links` | Emits `apilinks.json` mapping API symbols to source-line GitHub URLs | + +## Installation + +```sh +npm install @doc-kittens/website +``` diff --git a/packages/website/package.json b/packages/website/package.json new file mode 100644 index 00000000..67d04733 --- /dev/null +++ b/packages/website/package.json @@ -0,0 +1,25 @@ +{ + "name": "@doc-kittens/website", + "type": "module", + "version": "1.0.0", + "description": "Website-targeted documentation generators (sitemap, llms-txt, api-links) for @doc-kittens", + "repository": { + "type": "git", + "url": "git+https://github.com/nodejs/doc-kit.git", + "directory": "packages/website" + }, + "exports": { + "./sitemap": "./src/generators/sitemap/index.mjs", + "./llms-txt": "./src/generators/llms-txt/index.mjs", + "./api-links": "./src/generators/api-links/index.mjs", + "./src/*": "./src/*" + }, + "imports": { + "#core/*": "@node-core/doc-kit/src/*" + }, + "dependencies": { + "@node-core/doc-kit": "*", + "@doc-kittens/internal": "*", + "estree-util-visit": "^2.0.0" + } +} diff --git a/packages/core/src/generators/api-links/README.md b/packages/website/src/generators/api-links/README.md similarity index 100% rename from packages/core/src/generators/api-links/README.md rename to packages/website/src/generators/api-links/README.md diff --git a/packages/core/src/generators/api-links/index.mjs b/packages/website/src/generators/api-links/index.mjs similarity index 77% rename from packages/core/src/generators/api-links/index.mjs rename to packages/website/src/generators/api-links/index.mjs index e967b76d..72f361ce 100644 --- a/packages/core/src/generators/api-links/index.mjs +++ b/packages/website/src/generators/api-links/index.mjs @@ -2,18 +2,19 @@ import { basename, join } from 'node:path'; -import { checkIndirectReferences } from './utils/checkIndirectReferences.mjs'; -import { extractExports } from './utils/extractExports.mjs'; -import { findDefinitions } from './utils/findDefinitions.mjs'; -import getConfig from '../../utils/configuration/index.mjs'; +import getConfig from '#core/utils/configuration/index.mjs'; import { GITHUB_BLOB_URL, populate, -} from '../../utils/configuration/templates.mjs'; -import { withExt, writeFile } from '../../utils/file.mjs'; +} from '#core/utils/configuration/templates.mjs'; +import { withExt, writeFile } from '#core/utils/file.mjs'; + +import { findDefinitions } from '../../utils/api-links/definitions.mjs'; +import { extractExports } from '../../utils/api-links/exports.mjs'; +import { checkIndirectReferences } from '../../utils/api-links/indirectReferences.mjs'; export const name = 'api-links'; -export const dependsOn = '@node-core/doc-kit/generators/ast-js'; +export const dependsOn = '@doc-kittens/internal/ast-js'; export const defaultConfiguration = { sourceURL: `${GITHUB_BLOB_URL}lib/{fileName}`, }; diff --git a/packages/core/src/generators/api-links/types.d.ts b/packages/website/src/generators/api-links/types.d.ts similarity index 100% rename from packages/core/src/generators/api-links/types.d.ts rename to packages/website/src/generators/api-links/types.d.ts diff --git a/packages/core/src/generators/llms-txt/README.md b/packages/website/src/generators/llms-txt/README.md similarity index 100% rename from packages/core/src/generators/llms-txt/README.md rename to packages/website/src/generators/llms-txt/README.md diff --git a/packages/core/src/generators/llms-txt/index.mjs b/packages/website/src/generators/llms-txt/index.mjs similarity index 77% rename from packages/core/src/generators/llms-txt/index.mjs rename to packages/website/src/generators/llms-txt/index.mjs index d37bd214..a85f939f 100644 --- a/packages/core/src/generators/llms-txt/index.mjs +++ b/packages/website/src/generators/llms-txt/index.mjs @@ -3,12 +3,13 @@ import { readFile } from 'node:fs/promises'; import { join } from 'node:path'; -import { buildApiDocLink } from './utils/buildApiDocLink.mjs'; -import getConfig from '../../utils/configuration/index.mjs'; -import { writeFile } from '../../utils/file.mjs'; +import getConfig from '#core/utils/configuration/index.mjs'; +import { writeFile } from '#core/utils/file.mjs'; + +import { buildApiDocLink } from '../../utils/llms-txt/apiDocLink.mjs'; export const name = 'llms-txt'; -export const dependsOn = '@node-core/doc-kit/generators/metadata'; +export const dependsOn = '@doc-kittens/internal/metadata'; export const defaultConfiguration = { templatePath: join(import.meta.dirname, 'template.txt'), pageURL: '{baseURL}/latest/api{path}.md', diff --git a/packages/core/src/generators/llms-txt/template.txt b/packages/website/src/generators/llms-txt/template.txt similarity index 100% rename from packages/core/src/generators/llms-txt/template.txt rename to packages/website/src/generators/llms-txt/template.txt diff --git a/packages/core/src/generators/llms-txt/types.d.ts b/packages/website/src/generators/llms-txt/types.d.ts similarity index 64% rename from packages/core/src/generators/llms-txt/types.d.ts rename to packages/website/src/generators/llms-txt/types.d.ts index 85c720ed..e82f0156 100644 --- a/packages/core/src/generators/llms-txt/types.d.ts +++ b/packages/website/src/generators/llms-txt/types.d.ts @@ -1,4 +1,4 @@ -import { MetadataEntry } from '../metadata/types'; +import { MetadataEntry } from '@doc-kittens/internal/src/generators/metadata/types'; export type Generator = GeneratorMetadata< { diff --git a/packages/core/src/generators/sitemap/README.md b/packages/website/src/generators/sitemap/README.md similarity index 100% rename from packages/core/src/generators/sitemap/README.md rename to packages/website/src/generators/sitemap/README.md diff --git a/packages/core/src/generators/sitemap/entry-template.xml b/packages/website/src/generators/sitemap/entry-template.xml similarity index 100% rename from packages/core/src/generators/sitemap/entry-template.xml rename to packages/website/src/generators/sitemap/entry-template.xml diff --git a/packages/core/src/generators/sitemap/index.mjs b/packages/website/src/generators/sitemap/index.mjs similarity index 82% rename from packages/core/src/generators/sitemap/index.mjs rename to packages/website/src/generators/sitemap/index.mjs index 729961e6..e4cd11a6 100644 --- a/packages/core/src/generators/sitemap/index.mjs +++ b/packages/website/src/generators/sitemap/index.mjs @@ -3,13 +3,14 @@ import { readFile } from 'node:fs/promises'; import { join } from 'node:path'; -import { createPageSitemapEntry } from './utils/createPageSitemapEntry.mjs'; -import getConfig from '../../utils/configuration/index.mjs'; -import { populate } from '../../utils/configuration/templates.mjs'; -import { writeFile } from '../../utils/file.mjs'; +import getConfig from '#core/utils/configuration/index.mjs'; +import { populate } from '#core/utils/configuration/templates.mjs'; +import { writeFile } from '#core/utils/file.mjs'; + +import { createPageSitemapEntry } from '../../utils/sitemap/entry.mjs'; export const name = 'sitemap'; -export const dependsOn = '@node-core/doc-kit/generators/metadata'; +export const dependsOn = '@doc-kittens/internal/metadata'; export const defaultConfiguration = { indexURL: '{baseURL}/latest/api/', pageURL: '{indexURL}{path}.html', diff --git a/packages/core/src/generators/sitemap/template.xml b/packages/website/src/generators/sitemap/template.xml similarity index 100% rename from packages/core/src/generators/sitemap/template.xml rename to packages/website/src/generators/sitemap/template.xml diff --git a/packages/core/src/generators/sitemap/types.d.ts b/packages/website/src/generators/sitemap/types.d.ts similarity index 80% rename from packages/core/src/generators/sitemap/types.d.ts rename to packages/website/src/generators/sitemap/types.d.ts index abad2fd1..dbd60740 100644 --- a/packages/core/src/generators/sitemap/types.d.ts +++ b/packages/website/src/generators/sitemap/types.d.ts @@ -1,4 +1,4 @@ -import type { MetadataEntry } from '../metadata/types'; +import type { MetadataEntry } from '@doc-kittens/internal/src/generators/metadata/types'; export interface SitemapEntry { loc: string; diff --git a/packages/core/src/generators/api-links/__tests__/fixtures.test.mjs b/packages/website/src/utils/api-links/__tests__/fixtures.test.mjs similarity index 76% rename from packages/core/src/generators/api-links/__tests__/fixtures.test.mjs rename to packages/website/src/utils/api-links/__tests__/fixtures.test.mjs index 669e3dac..970d68f7 100644 --- a/packages/core/src/generators/api-links/__tests__/fixtures.test.mjs +++ b/packages/website/src/utils/api-links/__tests__/fixtures.test.mjs @@ -1,14 +1,14 @@ import { basename, join, relative, sep } from 'node:path'; import { after, before, describe, it } from 'node:test'; +import { loadGenerator } from '#core/loader.mjs'; +import createWorkerPool from '#core/threading/index.mjs'; +import createParallelWorker from '#core/threading/parallel.mjs'; +import { setConfig } from '#core/utils/configuration/index.mjs'; +import { generate as astJsGenerate } from '@doc-kittens/internal/ast-js'; import { globSync } from 'tinyglobby'; -import { loadGenerator } from '../../../loader.mjs'; -import createWorkerPool from '../../../threading/index.mjs'; -import createParallelWorker from '../../../threading/parallel.mjs'; -import { setConfig } from '../../../utils/configuration/index.mjs'; -import { generate as astJsGenerate } from '../../ast-js/index.mjs'; -import { generate as apiLinksGenerate } from '../index.mjs'; +import { generate as apiLinksGenerate } from '../../../generators/api-links/index.mjs'; const relativePath = relative(process.cwd(), import.meta.dirname); @@ -16,9 +16,9 @@ const sourceFiles = globSync('*.js', { cwd: new URL(import.meta.resolve('./fixtures')), }); -const astJsSpecifier = '@node-core/doc-kit/generators/ast-js'; +const astJsSpecifier = '@doc-kittens/internal/ast-js'; const astJsGenerator = await loadGenerator(astJsSpecifier); -const apiLinksSpecifier = '@node-core/doc-kit/generators/api-links'; +const apiLinksSpecifier = '@doc-kittens/website/api-links'; const apiLinksGenerator = await loadGenerator(apiLinksSpecifier); const loadedGenerators = new Map([ diff --git a/packages/core/src/generators/api-links/__tests__/fixtures.test.mjs.snapshot b/packages/website/src/utils/api-links/__tests__/fixtures.test.mjs.snapshot similarity index 100% rename from packages/core/src/generators/api-links/__tests__/fixtures.test.mjs.snapshot rename to packages/website/src/utils/api-links/__tests__/fixtures.test.mjs.snapshot diff --git a/packages/core/src/generators/api-links/__tests__/fixtures/buffer.js b/packages/website/src/utils/api-links/__tests__/fixtures/buffer.js similarity index 52% rename from packages/core/src/generators/api-links/__tests__/fixtures/buffer.js rename to packages/website/src/utils/api-links/__tests__/fixtures/buffer.js index 8ee44123..82fde832 100644 --- a/packages/core/src/generators/api-links/__tests__/fixtures/buffer.js +++ b/packages/website/src/utils/api-links/__tests__/fixtures/buffer.js @@ -2,11 +2,10 @@ // Buffer instance methods are exported as 'buf'. -function Buffer() { -} +function Buffer() {} -Buffer.prototype.instanceMethod = function() {} +Buffer.prototype.instanceMethod = function () {}; module.exports = { - Buffer + Buffer, }; diff --git a/packages/core/src/generators/api-links/__tests__/fixtures/class.js b/packages/website/src/utils/api-links/__tests__/fixtures/class.js similarity index 70% rename from packages/core/src/generators/api-links/__tests__/fixtures/class.js rename to packages/website/src/utils/api-links/__tests__/fixtures/class.js index 7db5c008..5d015f09 100644 --- a/packages/core/src/generators/api-links/__tests__/fixtures/class.js +++ b/packages/website/src/utils/api-links/__tests__/fixtures/class.js @@ -3,10 +3,10 @@ // An exported class using ES2015 class syntax. class Class { - constructor() {}; - method() {}; + constructor() {} + method() {} } module.exports = { - Class + Class, }; diff --git a/packages/core/src/generators/api-links/__tests__/fixtures/exports.js b/packages/website/src/utils/api-links/__tests__/fixtures/exports.js similarity index 65% rename from packages/core/src/generators/api-links/__tests__/fixtures/exports.js rename to packages/website/src/utils/api-links/__tests__/fixtures/exports.js index 880fdf6c..fd2ce909 100644 --- a/packages/core/src/generators/api-links/__tests__/fixtures/exports.js +++ b/packages/website/src/utils/api-links/__tests__/fixtures/exports.js @@ -2,12 +2,12 @@ // Support `exports` as an alternative to `module.exports`. -function Buffer() {}; +function Buffer() {} exports.Buffer = Buffer; exports.fn1 = function fn1() {}; -var fn2 = exports.fn2 = function() {}; +var fn2 = (exports.fn2 = function () {}); -function fn3() {}; +function fn3() {} exports.fn3 = fn3; diff --git a/packages/core/src/generators/api-links/__tests__/fixtures/mod.js b/packages/website/src/utils/api-links/__tests__/fixtures/mod.js similarity index 76% rename from packages/core/src/generators/api-links/__tests__/fixtures/mod.js rename to packages/website/src/utils/api-links/__tests__/fixtures/mod.js index 72606121..22f51eb6 100644 --- a/packages/core/src/generators/api-links/__tests__/fixtures/mod.js +++ b/packages/website/src/utils/api-links/__tests__/fixtures/mod.js @@ -2,10 +2,8 @@ // A module may export one or more methods. -function foo() { -} - +function foo() {} module.exports = { - foo + foo, }; diff --git a/packages/website/src/utils/api-links/__tests__/fixtures/prototype.js b/packages/website/src/utils/api-links/__tests__/fixtures/prototype.js new file mode 100644 index 00000000..5d601a35 --- /dev/null +++ b/packages/website/src/utils/api-links/__tests__/fixtures/prototype.js @@ -0,0 +1,12 @@ +'use strict'; + +// An exported class using classic prototype syntax. + +function Class() {} + +Class.classMethod = function () {}; +Class.prototype.instanceMethod = function () {}; + +module.exports = { + Class, +}; diff --git a/packages/core/src/generators/api-links/__tests__/fixtures/reverse.js b/packages/website/src/utils/api-links/__tests__/fixtures/reverse.js similarity index 52% rename from packages/core/src/generators/api-links/__tests__/fixtures/reverse.js rename to packages/website/src/utils/api-links/__tests__/fixtures/reverse.js index 5a61e50d..1e058196 100644 --- a/packages/core/src/generators/api-links/__tests__/fixtures/reverse.js +++ b/packages/website/src/utils/api-links/__tests__/fixtures/reverse.js @@ -2,12 +2,10 @@ // Parallel assignment to the exported variable and module.exports. -function ok() { -} +function ok() {} -const asserts = module.exports = ok; +const asserts = (module.exports = ok); asserts.ok = ok; -asserts.strictEqual = function() { -} +asserts.strictEqual = function () {}; diff --git a/packages/core/src/generators/api-links/__tests__/fixtures/root.js b/packages/website/src/utils/api-links/__tests__/fixtures/root.js similarity index 100% rename from packages/core/src/generators/api-links/__tests__/fixtures/root.js rename to packages/website/src/utils/api-links/__tests__/fixtures/root.js diff --git a/packages/core/src/generators/api-links/constants.mjs b/packages/website/src/utils/api-links/constants.mjs similarity index 100% rename from packages/core/src/generators/api-links/constants.mjs rename to packages/website/src/utils/api-links/constants.mjs diff --git a/packages/core/src/generators/api-links/utils/findDefinitions.mjs b/packages/website/src/utils/api-links/definitions.mjs similarity index 93% rename from packages/core/src/generators/api-links/utils/findDefinitions.mjs rename to packages/website/src/utils/api-links/definitions.mjs index a161e3ed..1dbbb6c8 100644 --- a/packages/core/src/generators/api-links/utils/findDefinitions.mjs +++ b/packages/website/src/utils/api-links/definitions.mjs @@ -7,7 +7,7 @@ import { visit } from 'estree-util-visit'; * * @param {import('acorn').ExpressionStatement} node * @param {Record} nameToLineNumberMap - * @param {import('../types').ProgramExports} exports + * @param {import('../../generators/api-links/types').ProgramExports} exports */ function handleAssignmentExpression(node, nameToLineNumberMap, exports) { const { expression } = node; @@ -89,7 +89,7 @@ function handleAssignmentExpression(node, nameToLineNumberMap, exports) { * @param {import('acorn').FunctionDeclaration} node * @param {string} basename * @param {Record} nameToLineNumberMap - * @param {import('../types').ProgramExports} exports + * @param {import('../../generators/api-links/types').ProgramExports} exports */ function handleFunctionDeclaration( node, @@ -113,7 +113,7 @@ function handleFunctionDeclaration( /** * @param {import('acorn').ClassDeclaration} node * @param {Record} nameToLineNumberMap - * @param {import('../types').ProgramExports} exports + * @param {import('../../generators/api-links/types').ProgramExports} exports */ function handleClassDeclaration(node, nameToLineNumberMap, exports) { if (!exports.ctors.includes(node.id.name)) { @@ -142,7 +142,7 @@ function handleClassDeclaration(node, nameToLineNumberMap, exports) { * @param {import('acorn').Program} program * @param {string} basename * @param {Record} nameToLineNumberMap - * @param {import('../types').ProgramExports} exports + * @param {import('../../generators/api-links/types').ProgramExports} exports */ export function findDefinitions( program, diff --git a/packages/core/src/generators/api-links/utils/extractExports.mjs b/packages/website/src/utils/api-links/exports.mjs similarity index 92% rename from packages/core/src/generators/api-links/utils/extractExports.mjs rename to packages/website/src/utils/api-links/exports.mjs index 60746eca..b52ed387 100644 --- a/packages/core/src/generators/api-links/utils/extractExports.mjs +++ b/packages/website/src/utils/api-links/exports.mjs @@ -2,7 +2,7 @@ import { visit } from 'estree-util-visit'; -import { CONSTRUCTOR_EXPRESSION } from '../constants.mjs'; +import { CONSTRUCTOR_EXPRESSION } from './constants.mjs'; /** * @see https://github.com/estree/estree/blob/master/es5.md#assignmentexpression @@ -10,7 +10,7 @@ import { CONSTRUCTOR_EXPRESSION } from '../constants.mjs'; * @param {import('acorn').ExpressionStatement} node * @param {string} basename * @param {Record} nameToLineNumberMap - * @returns {import('../types').ProgramExports | undefined} + * @returns {import('../../generators/api-links/types').ProgramExports | undefined} */ function handleExpression(node, basename, nameToLineNumberMap) { const { expression } = node; @@ -31,7 +31,7 @@ function handleExpression(node, basename, nameToLineNumberMap) { } /** - * @type {import('../types').ProgramExports} + * @type {import('../../generators/api-links/types').ProgramExports} */ const exports = { ctors: [], @@ -152,11 +152,11 @@ function handleExpression(node, basename, nameToLineNumberMap) { * @param {import('acorn').VariableDeclaration} node * @param {string} basename * @param {Record} nameToLineNumberMap - * @returns {import('../types').ProgramExports | undefined} + * @returns {import('../../generators/api-links/types').ProgramExports | undefined} */ function handleVariableDeclaration(node, basename, nameToLineNumberMap) { /** - * @type {import('../types').ProgramExports} + * @type {import('../../generators/api-links/types').ProgramExports} */ const exports = { ctors: [], @@ -215,11 +215,11 @@ function handleVariableDeclaration(node, basename, nameToLineNumberMap) { * @param {import('acorn').Program} program * @param {string} basename * @param {Record} nameToLineNumberMap - * @returns {import('../types').ProgramExports} + * @returns {import('../../generators/api-links/types').ProgramExports} */ export function extractExports(program, basename, nameToLineNumberMap) { /** - * @type {import('../types').ProgramExports} + * @type {import('../../generators/api-links/types').ProgramExports} */ const exports = { ctors: [], diff --git a/packages/core/src/generators/api-links/utils/checkIndirectReferences.mjs b/packages/website/src/utils/api-links/indirectReferences.mjs similarity index 87% rename from packages/core/src/generators/api-links/utils/checkIndirectReferences.mjs rename to packages/website/src/utils/api-links/indirectReferences.mjs index 982bb111..8e1b8620 100644 --- a/packages/core/src/generators/api-links/utils/checkIndirectReferences.mjs +++ b/packages/website/src/utils/api-links/indirectReferences.mjs @@ -2,7 +2,7 @@ import { visit } from 'estree-util-visit'; /** * @param {import('acorn').Program} program - * @param {import('../types.d.ts').ProgramExports} exports + * @param {import('../../generators/api-links/types.d.ts').ProgramExports} exports * @param {Record} nameToLineNumberMap */ export function checkIndirectReferences(program, exports, nameToLineNumberMap) { diff --git a/packages/core/src/generators/llms-txt/utils/__tests__/buildApiDocLink.test.mjs b/packages/website/src/utils/llms-txt/__tests__/apiDocLink.test.mjs similarity index 96% rename from packages/core/src/generators/llms-txt/utils/__tests__/buildApiDocLink.test.mjs rename to packages/website/src/utils/llms-txt/__tests__/apiDocLink.test.mjs index 6120f93e..6069616c 100644 --- a/packages/core/src/generators/llms-txt/utils/__tests__/buildApiDocLink.test.mjs +++ b/packages/website/src/utils/llms-txt/__tests__/apiDocLink.test.mjs @@ -1,7 +1,7 @@ import assert from 'node:assert/strict'; import { describe, it } from 'node:test'; -import { getEntryDescription, buildApiDocLink } from '../buildApiDocLink.mjs'; +import { getEntryDescription, buildApiDocLink } from '../apiDocLink.mjs'; describe('getEntryDescription', () => { it('returns llm_description when available', () => { diff --git a/packages/core/src/generators/llms-txt/utils/buildApiDocLink.mjs b/packages/website/src/utils/llms-txt/apiDocLink.mjs similarity index 71% rename from packages/core/src/generators/llms-txt/utils/buildApiDocLink.mjs rename to packages/website/src/utils/llms-txt/apiDocLink.mjs index 271249d0..be145ee3 100644 --- a/packages/core/src/generators/llms-txt/utils/buildApiDocLink.mjs +++ b/packages/website/src/utils/llms-txt/apiDocLink.mjs @@ -1,12 +1,12 @@ -import { populate } from '../../../utils/configuration/templates.mjs'; -import { transformNodeToString } from '../../../utils/unist.mjs'; +import { populate } from '#core/utils/configuration/templates.mjs'; +import { transformNodeToString } from '#core/utils/unist.mjs'; /** * Retrieves the description of a given API doc entry. It first checks whether * the entry has a llm_description property. If not, it extracts the first * paragraph from the entry's content. * - * @param {import('../../metadata/types').MetadataEntry} entry + * @param {import('@doc-kittens/internal/src/generators/metadata/types').MetadataEntry} entry * @returns {string} */ export const getEntryDescription = entry => { @@ -32,8 +32,8 @@ export const getEntryDescription = entry => { /** * Builds a markdown link for an API doc entry * - * @param {import('../../metadata/types').MetadataEntry} entry - * @param {import('../../../utils/configuration/types').Configuration['llms-txt']} + * @param {import('@doc-kittens/internal/src/generators/metadata/types').MetadataEntry} entry + * @param {import('#core/utils/configuration/types').Configuration['llms-txt']} * @returns {string} */ export const buildApiDocLink = (entry, config) => { diff --git a/packages/website/src/utils/sitemap/entry.mjs b/packages/website/src/utils/sitemap/entry.mjs new file mode 100644 index 00000000..b6e38db0 --- /dev/null +++ b/packages/website/src/utils/sitemap/entry.mjs @@ -0,0 +1,18 @@ +import { populate } from '#core/utils/configuration/templates.mjs'; + +/** + * Builds an API doc sitemap url. + * + * @param {import('@doc-kittens/internal/src/generators/metadata/types').MetadataEntry} entry + * @param {import('#core/utils/configuration/types').Configuration['sitemap']} config + * @returns {import('../../generators/sitemap/types').SitemapEntry} + */ +export const createPageSitemapEntry = (entry, config, lastmod) => ({ + loc: populate(config.pageURL, { + ...config, + path: entry.path, + }), + lastmod, + changefreq: 'weekly', + priority: '0.8', +}); diff --git a/scripts/vercel-build.sh b/scripts/vercel-build.sh index 4628191a..f9957dde 100755 --- a/scripts/vercel-build.sh +++ b/scripts/vercel-build.sh @@ -1,8 +1,8 @@ node packages/core/bin/cli.mjs generate \ - -t @node-core/doc-kit/generators/orama-db \ - -t @node-core/doc-kit/generators/legacy-json \ - -t @node-core/doc-kit/generators/llms-txt \ - -t @node-core/doc-kit/generators/web \ + -t @doc-kittens/react/orama-db \ + -t @doc-kittens/legacy/json \ + -t @doc-kittens/website/llms-txt \ + -t @doc-kittens/react/web \ -i "./node/doc/api/*.md" \ -o "./out" \ -c "./node/CHANGELOG.md" \ @@ -11,4 +11,4 @@ node packages/core/bin/cli.mjs generate \ cp ./node/doc/api/*.md "./out" -rm -rf node/ +# rm -rf node/