diff --git a/.c8rc.json b/.c8rc.json index 4ce81ec8..49031d7d 100644 --- a/.c8rc.json +++ b/.c8rc.json @@ -3,8 +3,8 @@ "exclude": [ "eslint.config.mjs", "**/fixtures", - "packages/core/src/generators/legacy-html/assets", - "packages/core/src/generators/web/ui", + "packages/legacy/src/legacy-html/assets", + "packages/react/src/html/ui", "**/*.d.ts" ] } diff --git a/.changeset/bootstrap-command.md b/.changeset/bootstrap-command.md new file mode 100644 index 00000000..d8fbfd11 --- /dev/null +++ b/.changeset/bootstrap-command.md @@ -0,0 +1,11 @@ +--- +'@nodejs/doc-kit': minor +--- + +Add `doc-kit bootstrap [generators...]`: sets a project up end to end — a +`doc-kit.config.mjs` whose naming and versioning are imported straight from +`package.json`, a documentation directory (detected, or created with a +starter page), a `.gitignore` entry for the output, and the generator +packages installed. `doc-kit bootstrap` followed by `doc-kit serve` is all a +new project needs. Prompts for its few decisions on a TTY; `--yes` accepts +the defaults. diff --git a/.changeset/configurable-navigation.md b/.changeset/configurable-navigation.md index cecf529c..869cdc5e 100644 --- a/.changeset/configurable-navigation.md +++ b/.changeset/configurable-navigation.md @@ -1,5 +1,5 @@ --- -'@node-core/doc-kit': patch +'@nodejs/doc-kit': patch --- Add `web.navigation`, which supplies the sidebar groups (`navigation.sidebar`) diff --git a/.changeset/curvy-items-smile.md b/.changeset/curvy-items-smile.md index 69111d7f..7b9492b4 100644 --- a/.changeset/curvy-items-smile.md +++ b/.changeset/curvy-items-smile.md @@ -1,5 +1,5 @@ --- -'@node-core/doc-kit': minor +'@nodejs/doc-kit': minor --- Discover and load configuration files with `cosmiconfig`. diff --git a/.changeset/did-you-know-that-the-world-is-round.md b/.changeset/did-you-know-that-the-world-is-round.md index 98ba0892..b890a4ab 100644 --- a/.changeset/did-you-know-that-the-world-is-round.md +++ b/.changeset/did-you-know-that-the-world-is-round.md @@ -1,5 +1,5 @@ --- -'@node-core/doc-kit': patch +'@nodejs/doc-kit': patch --- Close Orama search when the target link is on the same page diff --git a/.changeset/display-name-type-unions.md b/.changeset/display-name-type-unions.md index 8f91f546..f4b2cdb1 100644 --- a/.changeset/display-name-type-unions.md +++ b/.changeset/display-name-type-unions.md @@ -1,5 +1,5 @@ --- -'@node-core/doc-kit': patch +'@nodejs/doc-kit': patch --- Resolve unions and arrays of display-name types (`{HTTP/2 Headers Object | vm.Module}`, `{HTTP/2 Headers Object[]}`), and stop capturing prose such as `U+007B ({), and U+007D (}).` as a type annotation. diff --git a/.changeset/doc-kit-scope-move.md b/.changeset/doc-kit-scope-move.md new file mode 100644 index 00000000..dd10cf1a --- /dev/null +++ b/.changeset/doc-kit-scope-move.md @@ -0,0 +1,8 @@ +--- +'@nodejs/doc-kit': major +--- + +The doc-kit engine and CLI, previously published as `@node-core/doc-kit`, +are now published as `@nodejs/doc-kit`. The `@node-core/doc-kit` name now +contains only the Node.js-specific generators (`api-links`, `addon-verify`, +and `man-page`). diff --git a/.changeset/fix-relative-parent-path.md b/.changeset/fix-relative-parent-path.md index e3f7ef8e..7745c39b 100644 --- a/.changeset/fix-relative-parent-path.md +++ b/.changeset/fix-relative-parent-path.md @@ -1,5 +1,5 @@ --- -'@node-core/doc-kit': patch +'@nodejs/doc-kit': patch --- Fix `relative()` URL resolution when the target path is a prefix of the current diff --git a/.changeset/install-command.md b/.changeset/install-command.md new file mode 100644 index 00000000..6a3edf71 --- /dev/null +++ b/.changeset/install-command.md @@ -0,0 +1,10 @@ +--- +'@nodejs/doc-kit': minor +--- + +Add `doc-kit install [generators...]`: installs the packages providing the +given built-in generators (e.g. `doc-kit install html` installs +`@nodejs/doc-kit-generator-react`). Without arguments, it installs whatever +the `target` in your configuration file needs. The package manager is +detected from the project lockfile, and packages land in `devDependencies` +unless the project depends on doc-kit in production. diff --git a/.changeset/legacy-kitten-package.md b/.changeset/legacy-kitten-package.md new file mode 100644 index 00000000..b38a21ca --- /dev/null +++ b/.changeset/legacy-kitten-package.md @@ -0,0 +1,11 @@ +--- +'@nodejs/doc-kit-generator-legacy': major +'@nodejs/doc-kit': major +--- + +The legacy-format generators (`legacy-html`, `legacy-html-all`, +`legacy-json`, and `legacy-json-all`) now live in the new +`@nodejs/doc-kit-generator-legacy` package and are loaded via import specifiers such +as `@nodejs/doc-kit-generator-legacy/legacy-html`. The corresponding +`@nodejs/doc-kit/*` package exports have been removed. The CLI shorthand +names are unchanged. diff --git a/.changeset/monorepo-layout.md b/.changeset/monorepo-layout.md index 4341e42d..603f5bc6 100644 --- a/.changeset/monorepo-layout.md +++ b/.changeset/monorepo-layout.md @@ -1,5 +1,5 @@ --- -'@node-core/doc-kit': patch +'@nodejs/doc-kit': patch --- Moved the package into a `packages/core` workspace. diff --git a/.changeset/node-kitten-package.md b/.changeset/node-kitten-package.md new file mode 100644 index 00000000..1fa974c2 --- /dev/null +++ b/.changeset/node-kitten-package.md @@ -0,0 +1,10 @@ +--- +'@node-core/doc-kit': major +'@nodejs/doc-kit': major +--- + +The Node.js-specific generators (`api-links`, `addon-verify`, and +`man-page`) now live in the `@node-core/doc-kit` package and are loaded +via import specifiers such as `@node-core/doc-kit/man-page`. The +corresponding package exports have been removed from the doc-kit engine. +The CLI shorthand names are unchanged. diff --git a/.changeset/node-preset.md b/.changeset/node-preset.md new file mode 100644 index 00000000..664f1319 --- /dev/null +++ b/.changeset/node-preset.md @@ -0,0 +1,5 @@ +--- +'@node-core/doc-kit': minor +--- + +Add the `@node-core/doc-kit/config` preset diff --git a/.changeset/opt-out-banners.md b/.changeset/opt-out-banners.md index 652dee33..53f9497a 100644 --- a/.changeset/opt-out-banners.md +++ b/.changeset/opt-out-banners.md @@ -1,5 +1,5 @@ --- -'@node-core/doc-kit': minor +'@nodejs/doc-kit': minor --- Add banner opt-out diff --git a/.changeset/plain-defaults.md b/.changeset/plain-defaults.md new file mode 100644 index 00000000..56ae327b --- /dev/null +++ b/.changeset/plain-defaults.md @@ -0,0 +1,6 @@ +--- +'@nodejs/doc-kit': patch +'@nodejs/doc-kit-generator-react': minor +--- + +Defaults are now project-neutral instead of Node.js-specific diff --git a/.changeset/react-kitten-package.md b/.changeset/react-kitten-package.md new file mode 100644 index 00000000..25b93ea9 --- /dev/null +++ b/.changeset/react-kitten-package.md @@ -0,0 +1,12 @@ +--- +'@nodejs/doc-kit-generator-react': minor +'@nodejs/doc-kit': major +--- + +The React/JSX-based generators (`html` — previously `web` —, `jsx-ast`, +`llms-txt`, `sitemap`, and `orama-db`) now live in the new +`@nodejs/doc-kit-generator-react` package and are loaded via import specifiers such as +`@nodejs/doc-kit-generator-react/html`. The corresponding `@nodejs/doc-kit/*` +package exports have been removed. The `web` generator is renamed to `html`: +the CLI shorthand `web` keeps working as a deprecated alias, but the +configuration key is now `html` instead of `web`. diff --git a/.changeset/riscv64-warning-spacing.md b/.changeset/riscv64-warning-spacing.md index 58eff4c4..4b60d25e 100644 --- a/.changeset/riscv64-warning-spacing.md +++ b/.changeset/riscv64-warning-spacing.md @@ -1,5 +1,5 @@ --- -'@node-core/doc-kit': patch +'@nodejs/doc-kit': patch --- Fix missing spaces in the riscv64 multithreading warning message, which diff --git a/.changeset/serve-command.md b/.changeset/serve-command.md new file mode 100644 index 00000000..be9bcbfe --- /dev/null +++ b/.changeset/serve-command.md @@ -0,0 +1,8 @@ +--- +'@nodejs/doc-kit': minor +--- + +Add `doc-kit serve`: generates the documentation, serves the output locally +(port 3000 by default, falling back to the next available port or honoring +`--port`), and regenerates whenever the input files change. `--static` serves +an existing output directory as-is, without generating or watching. diff --git a/.changeset/short-deprecation-links.md b/.changeset/short-deprecation-links.md index 8af343c4..7b38dc5c 100644 --- a/.changeset/short-deprecation-links.md +++ b/.changeset/short-deprecation-links.md @@ -1,5 +1,5 @@ --- -'@node-core/doc-kit': patch +'@nodejs/doc-kit': patch --- Use short `DEP` codes for deprecation heading anchors. diff --git a/.changeset/spaced-union-types.md b/.changeset/spaced-union-types.md index 6c831a97..1498134d 100644 --- a/.changeset/spaced-union-types.md +++ b/.changeset/spaced-union-types.md @@ -1,5 +1,5 @@ --- -'@node-core/doc-kit': patch +'@nodejs/doc-kit': patch --- Space union separators in type annotation values (`{string|URL}` is now rendered as `string | URL`). diff --git a/.changeset/specifier-generator-loading.md b/.changeset/specifier-generator-loading.md index 039de74a..e6ee7d58 100644 --- a/.changeset/specifier-generator-loading.md +++ b/.changeset/specifier-generator-loading.md @@ -1,5 +1,5 @@ --- -'@node-core/doc-kit': minor +'@nodejs/doc-kit': minor --- Generators are now loaded dynamically by import specifier instead of a static diff --git a/.changeset/swc.md b/.changeset/swc.md index 9b9a2bf2..ec6c6c6e 100644 --- a/.changeset/swc.md +++ b/.changeset/swc.md @@ -1,5 +1,5 @@ --- -'@node-core/doc-kit': patch +'@nodejs/doc-kit': patch --- Switches `oxc-parser` for `@swc/wasm`, since `oxc-parser` does not provide the needed bindings. diff --git a/.changeset/tidy-deprecations-smile.md b/.changeset/tidy-deprecations-smile.md index d9e7c7bb..fa83f542 100644 --- a/.changeset/tidy-deprecations-smile.md +++ b/.changeset/tidy-deprecations-smile.md @@ -1,5 +1,5 @@ --- -'@node-core/doc-kit': patch +'@nodejs/doc-kit': patch --- Preserve deprecation codes in generated table-of-contents labels. diff --git a/.changeset/tidy-donuts-search.md b/.changeset/tidy-donuts-search.md index 9c48fd61..30e045d4 100644 --- a/.changeset/tidy-donuts-search.md +++ b/.changeset/tidy-donuts-search.md @@ -1,5 +1,5 @@ --- -'@node-core/doc-kit': patch +'@nodejs/doc-kit': patch --- Sync the URL hash when following same-page search hits diff --git a/.changeset/vite-web-generator.md b/.changeset/vite-web-generator.md index 49a47ea6..74f4838b 100644 --- a/.changeset/vite-web-generator.md +++ b/.changeset/vite-web-generator.md @@ -1,5 +1,5 @@ --- -'@node-core/doc-kit': minor +'@nodejs/doc-kit': minor --- Make the `web` generator bundler-neutral through a custom adapter contract, diff --git a/.changeset/yes-i-did-know-that-thank-you.md b/.changeset/yes-i-did-know-that-thank-you.md index b03124c5..370e2bef 100644 --- a/.changeset/yes-i-did-know-that-thank-you.md +++ b/.changeset/yes-i-did-know-that-thank-you.md @@ -1,5 +1,5 @@ --- -'@node-core/doc-kit': patch +'@nodejs/doc-kit': patch --- Render markdown `code` snippets in the sidebar diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 4702774f..4f68c425 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -141,7 +141,7 @@ jobs: path: node - name: Build docs - run: npx doc-kit generate -t web -i "./node/doc/api/assert.md" -o out + run: npx doc-kit generate -t web -i "./node/doc/api/assert.md" -o out --config-file ./e2e/doc-kit.config.mjs - name: Install Playwright browsers run: npx playwright install --with-deps diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 02b92068..74e7ee8a 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -56,9 +56,9 @@ jobs: env: SLACK_COLOR: '#43853D' SLACK_ICON: https://github.com/nodejs.png?size=48 - SLACK_TITLE: ':rocket: Package Published: @node-core/doc-kit' + SLACK_TITLE: ':rocket: doc-kit Packages Published' SLACK_MESSAGE: | - :package: *Package*: `@node-core/doc-kit` () + :package: *Packages*: `nodejs/doc-kit` workspace () :bust_in_silhouette: *Published by*: ${{ github.triggering_actor }} :octocat: *Commit*: SLACK_USERNAME: nodejs-bot diff --git a/.gitignore b/.gitignore index ef3c932b..35a39e94 100644 --- a/.gitignore +++ b/.gitignore @@ -18,7 +18,7 @@ junit.xml isolate-* # Node's Source Folder -node +/node # Playwright playwright-report/ diff --git a/.prettierignore b/.prettierignore index 86080826..34ea39e2 100644 --- a/.prettierignore +++ b/.prettierignore @@ -1,11 +1,11 @@ npm-shrinkwrap.json # Tests files -packages/core/src/generators/api-links/__tests__/fixtures/ +packages/node/src/api-links/__tests__/fixtures/ *.snapshot # Templates -packages/core/src/generators/web/template.html +packages/react/src/html/template.html # Output out/ diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 3b806375..faaf19da 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,6 +1,6 @@ -# `@node-core/doc-kit` Contributing Guide +# `@nodejs/doc-kit` Contributing Guide -Thank you for your interest in contributing to the `@node-core/doc-kit` project! We welcome contributions from everyone, and we appreciate your help in making this project better. +Thank you for your interest in contributing to the `@nodejs/doc-kit` project! We welcome contributions from everyone, and we appreciate your help in making this project better. ## Table of Contents @@ -29,7 +29,7 @@ Thank you for your interest in contributing to the `@node-core/doc-kit` project! ## Getting Started -The steps below will give you a general idea of how to prepare your local environment for the `@node-core/doc-kit` project and general steps for getting things done and landing your contribution. +The steps below will give you a general idea of how to prepare your local environment for the `@nodejs/doc-kit` project and general steps for getting things done and landing your contribution. ### Prerequisites @@ -77,7 +77,10 @@ This repository is an npm workspaces monorepo. The root package is private and holds the shared tooling (linting, formatting, tests, changesets); every published package lives under `packages/`: -- `packages/core`: [`@node-core/doc-kit`](packages/core) +- `packages/core`: [`@nodejs/doc-kit`](packages/core) — the doc-kit engine and CLI +- `packages/legacy`: [`@nodejs/doc-kit-generator-legacy`](packages/legacy) — the legacy-format generators +- `packages/node`: [`@node-core/doc-kit`](packages/node) — the Node.js-specific generators +- `packages/react`: [`@nodejs/doc-kit-generator-react`](packages/react) — the React/JSX-based generators Everything else at the root supports the repo rather than shipping to npm: `docs/` (the reference docs), `www/` (the documentation site), `scripts/` (build @@ -357,7 +360,7 @@ version in `package.json`, and writes the corresponding `CHANGELOG.md` entries. To ship a release, a maintainer merges that "Version Packages" PR. The same workflow then: -- publishes `@node-core/doc-kit` to npm (via [npm trusted publishing][] — no token required), +- publishes the workspace packages to npm (via [npm trusted publishing][] — no token required), - creates the matching `v` git tag, and - cuts a GitHub Release from the changelog. diff --git a/README.md b/README.md index 1bcadb89..7a35a8ab 100644 --- a/README.md +++ b/README.md @@ -9,7 +9,7 @@

- @node-core/doc-kit is a tool to generate API documentation of Node.js. See this issue for more information. + @nodejs/doc-kit is a tool to generate API documentation of Node.js. See this issue for more information.

@@ -44,20 +44,40 @@ $ node packages/core/bin/cli.mjs --help ``` ``` -Usage: @node-core/doc-kit [options] [command] +Usage: @nodejs/doc-kit [options] [command] -CLI tool to generate the Node.js API documentation +CLI tool to generate API documentation Options: - --log-level Log level (choices: "debug", "info", "warn", "error", - "fatal", default: "info") - -h, --help display help for command + --log-level Log level (choices: "debug", "info", + "warn", "error", "fatal", default: "info") + -h, --help display help for command Commands: - generate [options] Generate API docs - help [command] display help for command + generate [options] Generate API docs + install [options] [generators...] Install the packages providing built-in + generators (defaults to the targets in your + configuration file) + serve [options] Generate API docs, serve them locally, and + regenerate on changes + help [command] display help for command ``` +### `bootstrap` + +The fastest way to adopt doc-kit in a project: + +```sh +npx doc-kit bootstrap +npx doc-kit serve +``` + +`bootstrap` writes a `doc-kit.config.mjs` wired to your `package.json` +(naming, versioning, homepage), finds or creates your documentation +directory, ignores the output directory, and installs the packages for the +generators you pick. `serve` then gives you a live-reloading preview while +you write. + ### `generate` You must provide an input and at least one target through command-line options @@ -67,7 +87,7 @@ Running `generate` without the required values exits with an error pointing you to the help output. ``` -Usage: @node-core/doc-kit generate [options] +Usage: @nodejs/doc-kit generate [options] Generate API docs @@ -78,14 +98,14 @@ Options: (json-simple, legacy-html, legacy-html-all, man-page, legacy-json, legacy-json-all, addon-verify, api-links, orama-db, llms-txt, - sitemap, web) or an import specifier for a custom - generator + sitemap, html) or an import specifier for a + custom generator --ignore Ignore file patterns (glob) -o, --output The output directory -p, --threads Number of threads to use (minimum: 1) --chunk-size Number of items to process per worker thread (minimum: 1) - -v, --version Target Node.js version + -v, --version Target project version -c, --changelog Changelog URL or path --git-ref Git ref --index index.md URL or path @@ -94,6 +114,48 @@ Options: -h, --help display help for command ``` +The defaults are project-neutral. To produce output styled and structured the +way [nodejs.org](https://nodejs.org) builds its API docs, extend the Node.js +preset in your configuration file — see +[the configuration guide](docs/configuration.md#extending-presets): + +```mjs +export default { + extends: '@node-core/doc-kit/config', +}; +``` + +### `install` + +Built-in generators live in separate packages. `install` adds the packages +providing the given generators, using the package manager your lockfile +indicates: + +```sh +npx doc-kit install html orama-db +``` + +Without arguments, it installs whatever the `target` in your configuration +file needs. + +### `serve` + +`serve` accepts the same options as `generate`, plus a `--port`. It generates +the documentation, serves the output on (or the next +available port), and regenerates whenever the input files change — ideal while +writing documentation. + +```sh +npx doc-kit serve -t html -i "docs/*.md" -o out +``` + +To serve an already-generated output directory without regenerating or +watching, pass `--static`: + +```sh +npx doc-kit serve --static -o out +``` + ## Examples ### Legacy @@ -111,11 +173,11 @@ npx doc-kit generate \ ### Redesigned -To generate [our redesigned documentation pages](https://nodejs-api-docs-tooling.vercel.app), use the `web` and `orama-db` (for search) generators. +To generate [our redesigned documentation pages](https://nodejs-api-docs-tooling.vercel.app), use the `html` and `orama-db` (for search) generators. These generators live in the separate [`@nodejs/doc-kit-generator-react`](packages/react) package, which must be installed alongside this one. ```sh npx doc-kit generate \ - -t web \ + -t html \ -t orama-db \ -i "path/to/node/doc/api/*.md" \ -o out \ @@ -126,5 +188,5 @@ npx doc-kit generate \ > In order to use the search functionality, you _must_ serve the output directory. > > ```sh -> npx serve out +> npx doc-kit serve --static -o out > ``` diff --git a/beta/doc-kit.config.mjs b/beta/doc-kit.config.mjs index 07484c9c..13d2b824 100644 --- a/beta/doc-kit.config.mjs +++ b/beta/doc-kit.config.mjs @@ -1,5 +1,7 @@ export default { - web: { + extends: '@node-core/doc-kit/config', + + html: { remoteConfigUrl: 'https://raw.githubusercontent.com/nodejs/doc-kit/main/beta/site.json', diff --git a/docs/comparators.md b/docs/comparators.md index 6f87f379..1cafcd52 100644 --- a/docs/comparators.md +++ b/docs/comparators.md @@ -1,6 +1,6 @@ # Creating Comparators -This guide explains how to create build comparison scripts for `@node-core/doc-kit`. Comparators help identify differences between documentation builds, useful for CI/CD and regression testing. +This guide explains how to create build comparison scripts for `@nodejs/doc-kit`. Comparators help identify differences between documentation builds, useful for CI/CD and regression testing. ## Comparator Concepts @@ -35,7 +35,7 @@ scripts/comparators/ Comparators can be reused across multiple generators. You specify which comparator to use in the workflow file using the `compare` field. For example: -- `file-size.mjs` can compare output from `web`, `legacy-html`, or any generator +- `file-size.mjs` can compare output from `html`, `legacy-html`, or any generator - `object-assertion.mjs` can compare JSON output from `legacy-json`, `json-simple`, etc. - `my-comparator.mjs` would be a custom comparator for specific needs diff --git a/docs/configuration.md b/docs/configuration.md index eef68555..7ed61054 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -39,7 +39,7 @@ export default { // Targets, alternatively supplied by command line flags. Each entry is // either a built-in shorthand name or an import specifier resolving to a // generator module (e.g. '@my-scope/my-package/my-generator'). - target: ['orama-db', 'web'], + target: ['orama-db', 'html'], global: { version: '20.0.0', minify: true, @@ -114,6 +114,34 @@ export default { }; ``` +## Extending Presets + +A configuration file may declare `extends`: one or more presets whose values +are merged underneath its own. Each entry is either an import specifier of a +module whose default export is a configuration object, or a path relative to +the configuration file: + +```mjs +export default { + // Build the docs the way nodejs.org does — branding, URL layouts, + // and release history included + extends: '@node-core/doc-kit/config', + + html: { + // Your own values still win over the preset + title: '{project} {version} API Reference', + }, +}; +``` + +`extends` also accepts an array; later presets take precedence over earlier +ones, and the configuration file itself wins over all of them. + +The built-in defaults are deliberately project-neutral: no repository, +site URL, release history, or branding is assumed. The +[`@node-core/doc-kit/config`](https://github.com/nodejs/doc-kit/tree/main/packages/node) +preset opts back into everything Node.js-specific. + ## Configuration Merging Configurations are merged in the following order (higher sources take @@ -121,7 +149,8 @@ precedence): 1. **CLI options** (command-line arguments) 2. **Configuration file** (discovered or selected with `--config-file`) -3. **Default values** (built-in defaults) +3. **Presets** (listed in the configuration file's `extends`) +4. **Default values** (built-in defaults) ## CLI Options Mapping diff --git a/docs/generators.md b/docs/generators.md index 33f02c02..a3907501 100644 --- a/docs/generators.md +++ b/docs/generators.md @@ -1,6 +1,6 @@ # Creating Generators -This guide explains how to create new documentation generators for `@node-core/doc-kit`. +This guide explains how to create new documentation generators for `@nodejs/doc-kit`. ## Generator Concepts @@ -21,7 +21,7 @@ Raw Markdown Files ↓ [jsx-ast] - Convert to JSX AST ↓ - [web] - Generate HTML/CSS/JS bundles + [html] - Generate HTML/CSS/JS bundles ``` Each generator declares its dependency using the `dependsOn` field, allowing automatic pipeline construction. @@ -88,7 +88,7 @@ export default { // This generator depends on the metadata generator. Dependencies are // declared as import specifiers, so they can live in any package. - dependsOn: '@node-core/doc-kit/metadata', + dependsOn: '@nodejs/doc-kit/metadata', defaultConfiguration: { // If your generator supports a custom configuration, define the defaults here @@ -170,8 +170,8 @@ the import specifier it resolves to: ```javascript export const publicGenerators = { - 'json-simple': '@node-core/doc-kit/json-simple', - 'my-format': '@node-core/doc-kit/my-format', // Add this + 'json-simple': '@nodejs/doc-kit/json-simple', + 'my-format': '@nodejs/doc-kit/my-format', // Add this // ... other generators }; ``` @@ -199,7 +199,7 @@ export default { description: 'Processes data in parallel', - dependsOn: '@node-core/doc-kit/metadata', + dependsOn: '@nodejs/doc-kit/metadata', // Indicates this generator has a processChunk implementation hasParallelProcessor: true, @@ -296,7 +296,7 @@ export default { description: 'Streams results as they are ready', - dependsOn: '@node-core/doc-kit/metadata', + dependsOn: '@nodejs/doc-kit/metadata', hasParallelProcessor: true, @@ -357,7 +357,7 @@ export default { description: 'Requires all input at once', - dependsOn: '@node-core/doc-kit/jsx-ast', + dependsOn: '@nodejs/doc-kit-generator-react/jsx-ast', generate, }; @@ -403,7 +403,7 @@ export default { // This generator requires the metadata generator's output. The dependency // is an import specifier, so it may point at any installed package. - dependsOn: '@node-core/doc-kit/metadata', + dependsOn: '@nodejs/doc-kit/metadata', // ... other metadata @@ -434,7 +434,7 @@ export default { // packages/core/src/generators/metadata/index.mjs export default { name: 'metadata', - dependsOn: '@node-core/doc-kit/ast', // Depends on AST + dependsOn: '@nodejs/doc-kit/ast', // Depends on AST // Processes AST output }; @@ -442,7 +442,7 @@ export default { // packages/core/src/generators/html-generator/index.mjs export default { name: 'html-generator', - dependsOn: '@node-core/doc-kit/metadata', // Depends on metadata + dependsOn: '@nodejs/doc-kit/metadata', // Depends on metadata // Processes metadata output }; ``` diff --git a/docs/specification.md b/docs/specification.md index 1012e49f..a1f75ad5 100644 --- a/docs/specification.md +++ b/docs/specification.md @@ -5,7 +5,7 @@ **Authored By**: Aviv Keller () This document specifies the Markdown format consumed by -[`@node-core/doc-kit`][doc-kit]. It defines the structural, syntactic, and +[`@nodejs/doc-kit`][doc-kit]. It defines the structural, syntactic, and semantic rules that documents MUST follow to be correctly parsed. The format is a strict superset of [GitHub Flavored Markdown][gfm] (which itself is a strict superset of [CommonMark][commonmark]), adding conventions for API diff --git a/e2e/doc-kit.config.mjs b/e2e/doc-kit.config.mjs new file mode 100644 index 00000000..9db34eb8 --- /dev/null +++ b/e2e/doc-kit.config.mjs @@ -0,0 +1,3 @@ +export default { + extends: '@node-core/doc-kit/config', +}; diff --git a/eslint.config.mjs b/eslint.config.mjs index 197acf57..5f890af3 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -13,7 +13,7 @@ export default defineConfig([ ignores: [ 'www/out', 'out/', - 'packages/core/src/generators/api-links/__tests__/fixtures/', + 'packages/node/src/api-links/__tests__/fixtures/', ], }, { @@ -95,8 +95,8 @@ export default defineConfig([ }, { files: [ - 'packages/core/src/generators/legacy-html/assets/*.js', - 'packages/core/src/generators/web/ui/**/*', + 'packages/legacy/src/legacy-html/assets/*.js', + 'packages/react/src/html/ui/**/*', ], languageOptions: { globals: { diff --git a/package-lock.json b/package-lock.json index 3a8ce0c9..c03f0780 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,11 +1,11 @@ { - "name": "@node-core/doc-kit-monorepo", + "name": "@nodejs/doc-kit-monorepo", "version": "1.4.3", "lockfileVersion": 3, "requires": true, "packages": { "": { - "name": "@node-core/doc-kit-monorepo", + "name": "@nodejs/doc-kit-monorepo", "workspaces": [ "packages/*" ], @@ -1328,7 +1328,7 @@ } }, "node_modules/@node-core/doc-kit": { - "resolved": "packages/core", + "resolved": "packages/node", "link": true }, "node_modules/@node-core/rehype-shiki": { @@ -1387,6 +1387,18 @@ "node": ">=20" } }, + "node_modules/@nodejs/doc-kit": { + "resolved": "packages/core", + "link": true + }, + "node_modules/@nodejs/doc-kit-generator-legacy": { + "resolved": "packages/legacy", + "link": true + }, + "node_modules/@nodejs/doc-kit-generator-react": { + "resolved": "packages/react", + "link": true + }, "node_modules/@nodelib/fs.scandir": { "version": "2.1.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", @@ -11202,38 +11214,21 @@ } }, "packages/core": { - "name": "@node-core/doc-kit", - "version": "1.4.3", + "name": "@nodejs/doc-kit", + "version": "0.0.0", "dependencies": { - "@11ty/is-land": "^5.0.1", "@actions/core": "^3.0.0", - "@fontsource-variable/open-sans": "^5.3.0", - "@fontsource/ibm-plex-mono": "^5.3.0", - "@heroicons/react": "^2.2.0", "@node-core/rehype-shiki": "^1.4.3", - "@node-core/ui-components": "^1.7.4", - "@orama/orama": "^3.1.18", - "@orama/ui": "^1.5.4", "@swc/html-wasm": "^1.15.46", "@swc/wasm": "^1.15.46", "acorn": "^8.17.0", "commander": "^15.0.0", "cosmiconfig": "^9.0.2", "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", - "mdast-util-slice-markdown": "^2.0.1", "piscina": "^5.3.0", - "preact": "^10.29.7", - "preact-render-to-string": "^6.7.0", - "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-mdx": "^3.1.1", @@ -11250,11 +11245,79 @@ "unist-util-remove": "^4.0.0", "unist-util-select": "^5.1.0", "unist-util-visit": "^5.1.0", - "vite": "~8.1.5", "yaml": "^2.9.0" }, "bin": { "doc-kit": "bin/cli.mjs" + }, + "peerDependencies": { + "@node-core/doc-kit": "^1.4.3", + "@nodejs/doc-kit-generator-legacy": "^0.0.0", + "@nodejs/doc-kit-generator-react": "^0.0.0" + }, + "peerDependenciesMeta": { + "@node-core/doc-kit": { + "optional": true + }, + "@nodejs/doc-kit-generator-legacy": { + "optional": true + }, + "@nodejs/doc-kit-generator-react": { + "optional": true + } + } + }, + "packages/legacy": { + "name": "@nodejs/doc-kit-generator-legacy", + "version": "0.0.0", + "dependencies": { + "@nodejs/doc-kit": "^0.0.0", + "hastscript": "^9.0.1", + "unist-builder": "^4.0.0", + "unist-util-visit": "^5.1.0" + } + }, + "packages/node": { + "name": "@node-core/doc-kit", + "version": "1.4.3", + "dependencies": { + "@nodejs/doc-kit": "^0.0.0", + "dedent": "^1.7.2", + "estree-util-visit": "^2.0.0", + "unist-util-visit": "^5.1.0" + } + }, + "packages/react": { + "name": "@nodejs/doc-kit-generator-react", + "version": "0.0.0", + "dependencies": { + "@11ty/is-land": "^5.0.1", + "@fontsource-variable/open-sans": "^5.3.0", + "@fontsource/ibm-plex-mono": "^5.3.0", + "@heroicons/react": "^2.2.0", + "@node-core/rehype-shiki": "^1.4.3", + "@node-core/ui-components": "^1.7.4", + "@nodejs/doc-kit": "^0.0.0", + "@orama/orama": "^3.1.18", + "@orama/ui": "^1.5.4", + "estree-util-to-js": "^2.0.0", + "hast-util-to-string": "^3.0.1", + "hastscript": "^9.0.1", + "mdast-util-slice-markdown": "^2.0.1", + "preact": "^10.29.7", + "preact-render-to-string": "^6.7.0", + "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", + "remark-parse": "^11.0.0", + "remark-rehype": "^11.1.2", + "semver": "^7.8.5", + "unified": "^11.0.5", + "unist-builder": "^4.0.0", + "unist-util-visit": "^5.1.0", + "vite": "~8.1.5" } } } diff --git a/package.json b/package.json index d868527f..5bb1e54f 100644 --- a/package.json +++ b/package.json @@ -1,5 +1,5 @@ { - "name": "@node-core/doc-kit-monorepo", + "name": "@nodejs/doc-kit-monorepo", "private": true, "type": "module", "workspaces": [ diff --git a/packages/core/CHANGELOG.md b/packages/core/CHANGELOG.md index d326689b..92a34618 100644 --- a/packages/core/CHANGELOG.md +++ b/packages/core/CHANGELOG.md @@ -1,4 +1,4 @@ -# @node-core/doc-kit +# @nodejs/doc-kit ## 1.4.3 diff --git a/packages/core/README.md b/packages/core/README.md index 30c43306..3e26f00a 100644 --- a/packages/core/README.md +++ b/packages/core/README.md @@ -1,4 +1,4 @@ -# `@node-core/doc-kit` +# `@nodejs/doc-kit` The core of doc-kit: the CLI and the generators that turn API-shaped Markdown into HTML, JSON, man pages and more. @@ -10,7 +10,7 @@ references. ## Scripts Run these from this directory, or from the repository root with -`npm run - - - - -

${dehydrated}
- - - diff --git a/packages/core/src/server/__tests__/index.test.mjs b/packages/core/src/server/__tests__/index.test.mjs new file mode 100644 index 00000000..be54233b --- /dev/null +++ b/packages/core/src/server/__tests__/index.test.mjs @@ -0,0 +1,186 @@ +import assert from 'node:assert/strict'; +import { mkdtempSync, mkdirSync, writeFileSync } from 'node:fs'; +import { createServer } from 'node:net'; +import { tmpdir } from 'node:os'; +import { join } from 'node:path'; +import { describe, it, after } from 'node:test'; + +import { + createRebuildScheduler, + createStaticServer, + getContentType, + listenOnAvailablePort, + resolveStaticPath, +} from '../index.mjs'; + +const root = mkdtempSync(join(tmpdir(), 'doc-kit-server-')); + +writeFileSync(join(root, 'index.html'), '

home

'); +writeFileSync(join(root, 'page.html'), '

page

'); +writeFileSync(join(root, 'app.css'), 'body {}'); +mkdirSync(join(root, 'nested')); +writeFileSync(join(root, 'nested', 'index.html'), '

nested

'); +writeFileSync(join(tmpdir(), 'doc-kit-server-outside.txt'), 'secret'); + +describe('getContentType', () => { + it('should map known extensions', () => { + assert.equal(getContentType('page.html'), 'text/html; charset=utf-8'); + assert.equal(getContentType('a/b/style.css'), 'text/css; charset=utf-8'); + }); + + it('should fall back to octet-stream for unknown extensions', () => { + assert.equal(getContentType('archive.tar.zst'), 'application/octet-stream'); + }); +}); + +describe('resolveStaticPath', () => { + it('should serve the root index.html for /', () => { + assert.equal(resolveStaticPath(root, '/'), join(root, 'index.html')); + }); + + it('should serve exact files', () => { + assert.equal( + resolveStaticPath(root, '/page.html'), + join(root, 'page.html') + ); + }); + + it('should fall back to .html for extension-less URLs', () => { + assert.equal(resolveStaticPath(root, '/page'), join(root, 'page.html')); + }); + + it('should serve directory indexes', () => { + assert.equal( + resolveStaticPath(root, '/nested/'), + join(root, 'nested', 'index.html') + ); + }); + + it('should ignore query strings', () => { + assert.equal( + resolveStaticPath(root, '/page.html?version=1'), + join(root, 'page.html') + ); + }); + + it('should return null for missing files', () => { + assert.equal(resolveStaticPath(root, '/missing'), null); + }); + + it('should reject path traversal', () => { + assert.equal( + resolveStaticPath(root, '/../doc-kit-server-outside.txt'), + null + ); + assert.equal( + resolveStaticPath(root, '/%2e%2e/doc-kit-server-outside.txt'), + null + ); + }); +}); + +describe('createStaticServer', () => { + const server = createStaticServer(root); + + after(() => server.close()); + + it('should serve files with their content type', async () => { + const port = await listenOnAvailablePort(server, 0); + const response = await fetch(`http://localhost:${port}/page`); + + assert.equal(response.status, 200); + assert.equal( + response.headers.get('content-type'), + 'text/html; charset=utf-8' + ); + assert.equal(await response.text(), '

page

'); + }); + + it('should return 404 for unknown paths', async () => { + const { port } = server.address(); + const response = await fetch(`http://localhost:${port}/nope`); + + assert.equal(response.status, 404); + }); + + it('should reject non-GET methods', async () => { + const { port } = server.address(); + const response = await fetch(`http://localhost:${port}/page`, { + method: 'POST', + }); + + assert.equal(response.status, 405); + }); +}); + +describe('listenOnAvailablePort', () => { + it('should fall back to the next port when the preferred one is taken', async () => { + const blocker = createServer(); + await new Promise(resolve => blocker.listen(0, resolve)); + const { port: taken } = blocker.address(); + + const server = createStaticServer(root); + const port = await listenOnAvailablePort(server, taken); + + assert.equal(port, taken + 1); + + server.close(); + blocker.close(); + }); + + it('should throw when no port in the range is available', async () => { + const blocker = createServer(); + await new Promise(resolve => blocker.listen(0, resolve)); + const { port: taken } = blocker.address(); + + const server = createStaticServer(root); + + await assert.rejects( + listenOnAvailablePort(server, taken, 1), + /No available port/ + ); + + blocker.close(); + }); +}); + +describe('createRebuildScheduler', () => { + it('should coalesce bursts of events into one rebuild', async () => { + let runs = 0; + const schedule = createRebuildScheduler(async () => runs++, 10); + + schedule(); + schedule(); + schedule(); + + await new Promise(resolve => setTimeout(resolve, 50)); + assert.equal(runs, 1); + }); + + it('should queue exactly one follow-up when events arrive mid-rebuild', async () => { + let runs = 0; + let release; + const gate = new Promise(resolve => (release = resolve)); + + const schedule = createRebuildScheduler(async () => { + runs++; + + if (runs === 1) { + await gate; + } + }, 5); + + schedule(); + await new Promise(resolve => setTimeout(resolve, 20)); + + // The first rebuild is now in flight; these should fold into one rerun + schedule(); + schedule(); + await new Promise(resolve => setTimeout(resolve, 20)); + + release(); + await new Promise(resolve => setTimeout(resolve, 20)); + + assert.equal(runs, 2); + }); +}); diff --git a/packages/core/src/server/index.mjs b/packages/core/src/server/index.mjs new file mode 100644 index 00000000..2d33f10e --- /dev/null +++ b/packages/core/src/server/index.mjs @@ -0,0 +1,223 @@ +'use strict'; + +import { createReadStream, existsSync, statSync, watch } from 'node:fs'; +import { createServer } from 'node:http'; +import { extname, join, resolve, sep } from 'node:path'; + +import globParent from 'glob-parent'; + +/** + * Content types for the file extensions a generator is expected to emit. + * Anything else is served as a generic binary stream. + */ +const CONTENT_TYPES = { + '.css': 'text/css; charset=utf-8', + '.gif': 'image/gif', + '.html': 'text/html; charset=utf-8', + '.ico': 'image/x-icon', + '.jpeg': 'image/jpeg', + '.jpg': 'image/jpeg', + '.js': 'text/javascript; charset=utf-8', + '.json': 'application/json; charset=utf-8', + '.map': 'application/json; charset=utf-8', + '.md': 'text/markdown; charset=utf-8', + '.mjs': 'text/javascript; charset=utf-8', + '.png': 'image/png', + '.svg': 'image/svg+xml', + '.txt': 'text/plain; charset=utf-8', + '.webmanifest': 'application/manifest+json; charset=utf-8', + '.woff': 'font/woff', + '.woff2': 'font/woff2', + '.xml': 'application/xml; charset=utf-8', +}; + +/** + * Returns the `Content-Type` for a file based on its extension. + * + * @param {string} file - The file path + * @returns {string} + */ +export const getContentType = file => + CONTENT_TYPES[extname(file)] ?? 'application/octet-stream'; + +/** + * Resolves a request URL to a file inside the output directory, or `null` + * when nothing matches. Directory requests fall back to their `index.html`, + * and extension-less requests to `.html` — mirroring how generated + * pages link to each other. + * + * Paths resolving outside the root (e.g. `..` traversal) are rejected. + * + * @param {string} root - The directory being served + * @param {string} url - The request URL (path and optional query string) + * @returns {string | null} The file to serve, or `null` if none matches + */ +export const resolveStaticPath = (root, url) => { + const base = resolve(root); + + /** @type {string} */ + let pathname; + + try { + pathname = decodeURIComponent(new URL(url, 'http://localhost').pathname); + } catch { + return null; + } + + const requested = resolve(base, `.${pathname}`); + + if (requested !== base && !requested.startsWith(base + sep)) { + return null; + } + + const candidates = [ + requested, + join(requested, 'index.html'), + `${requested}.html`, + ]; + + for (const candidate of candidates) { + if (existsSync(candidate) && statSync(candidate).isFile()) { + return candidate; + } + } + + return null; +}; + +/** + * Creates an HTTP server that serves static files from a directory. + * + * @param {string} root - The directory to serve + * @returns {import('node:http').Server} + */ +export const createStaticServer = root => + createServer((request, response) => { + if (request.method !== 'GET' && request.method !== 'HEAD') { + response.writeHead(405, { 'content-type': 'text/plain; charset=utf-8' }); + response.end('Method not allowed'); + return; + } + + const file = resolveStaticPath(root, request.url); + + if (!file) { + response.writeHead(404, { 'content-type': 'text/plain; charset=utf-8' }); + response.end('Not found'); + return; + } + + response.writeHead(200, { 'content-type': getContentType(file) }); + + if (request.method === 'HEAD') { + response.end(); + return; + } + + createReadStream(file) + .on('error', () => response.destroy()) + .pipe(response); + }); + +/** + * Starts listening on the preferred port, walking up to the next port when + * the current one is taken. + * + * @param {import('node:http').Server} server - The server to bind + * @param {number} preferred - The preferred port + * @param {number} [attempts=10] - How many consecutive ports to try + * @returns {Promise} The port the server is listening on + */ +export const listenOnAvailablePort = async ( + server, + preferred, + attempts = 10 +) => { + for (let port = preferred; port < preferred + attempts; port++) { + try { + return await new Promise((onListening, onError) => { + server.once('error', onError); + server.listen(port, () => { + server.off('error', onError); + // `server.address().port` also resolves port 0 (OS-assigned) + onListening(server.address().port); + }); + }); + } catch (error) { + if (error.code !== 'EADDRINUSE') { + throw error; + } + } + } + + throw new Error( + `No available port between ${preferred} and ${preferred + attempts - 1}. ` + + 'Pass `--port` to pick a different range.' + ); +}; + +/** + * Wraps a rebuild function so bursts of file-system events coalesce into a + * single run: events within `delay` are debounced, and events arriving while + * a rebuild is in flight schedule exactly one follow-up run. + * + * @param {() => Promise} rebuild - The rebuild to run + * @param {number} [delay=300] - Debounce window in milliseconds + * @returns {() => void} The change handler to pass to a watcher + */ +export const createRebuildScheduler = (rebuild, delay = 300) => { + let timer; + let running = false; + let pending = false; + + /** + * Runs the rebuild, folding events that arrive mid-run into one follow-up. + */ + const run = async () => { + if (running) { + pending = true; + return; + } + + running = true; + + try { + await rebuild(); + } finally { + running = false; + + if (pending) { + pending = false; + run(); + } + } + }; + + return () => { + clearTimeout(timer); + timer = setTimeout(run, delay); + }; +}; + +/** + * Watches the directories containing the given glob patterns. + * + * @param {string[]} patterns - Input file patterns (glob) + * @param {() => void} onChange - Invoked on every file-system event + * @returns {{ dirs: string[], close: () => void }} The watched directories + * and a disposer + */ +export const watchPaths = (patterns, onChange) => { + const dirs = [ + ...new Set(patterns.map(pattern => globParent(pattern))), + ].filter(dir => existsSync(dir)); + + const watchers = dirs.map(dir => watch(dir, { recursive: true }, onChange)); + + /** + * Stops all file-system watchers. + */ + const close = () => watchers.forEach(watcher => watcher.close()); + + return { dirs, close }; +}; diff --git a/packages/core/src/utils/__tests__/bootstrap.test.mjs b/packages/core/src/utils/__tests__/bootstrap.test.mjs new file mode 100644 index 00000000..9b093060 --- /dev/null +++ b/packages/core/src/utils/__tests__/bootstrap.test.mjs @@ -0,0 +1,113 @@ +import assert from 'node:assert/strict'; +import { mkdtempSync, mkdirSync, writeFileSync } from 'node:fs'; +import { tmpdir } from 'node:os'; +import { join } from 'node:path'; +import { describe, it } from 'node:test'; + +import { + addIgnoredOutput, + buildConfigSource, + detectDocsDirectory, + findExistingConfig, + hasMarkdown, +} from '../bootstrap.mjs'; + +const scratch = () => mkdtempSync(join(tmpdir(), 'doc-kit-bootstrap-')); + +describe('hasMarkdown', () => { + it('should find Markdown at any depth', () => { + const dir = scratch(); + mkdirSync(join(dir, 'nested')); + writeFileSync(join(dir, 'nested', 'page.md'), '# hi'); + + assert.equal(hasMarkdown(dir), true); + }); + + it('should be false for empty or missing directories', () => { + assert.equal(hasMarkdown(scratch()), false); + assert.equal(hasMarkdown(join(scratch(), 'missing')), false); + }); +}); + +describe('detectDocsDirectory', () => { + it('should prefer a conventional directory that holds Markdown', () => { + const dir = scratch(); + mkdirSync(join(dir, 'docs')); + mkdirSync(join(dir, 'doc')); + writeFileSync(join(dir, 'doc', 'api.md'), '# api'); + + assert.equal(detectDocsDirectory(dir), 'doc'); + }); + + it('should fall back to an existing conventional directory', () => { + const dir = scratch(); + mkdirSync(join(dir, 'documentation')); + + assert.equal(detectDocsDirectory(dir), 'documentation'); + }); + + it('should default to docs for a fresh project', () => { + assert.equal(detectDocsDirectory(scratch()), 'docs'); + }); +}); + +describe('findExistingConfig', () => { + it('should detect an existing configuration file', () => { + const dir = scratch(); + writeFileSync(join(dir, 'doc-kit.config.mjs'), 'export default {};'); + + assert.equal(findExistingConfig(dir), 'doc-kit.config.mjs'); + assert.equal(findExistingConfig(scratch()), undefined); + }); +}); + +describe('buildConfigSource', () => { + it('should wire naming and versioning to package.json', () => { + const source = buildConfigSource({ + targets: ['html', 'orama-db'], + docsDir: 'docs', + output: 'out', + hasHomepage: true, + }); + + assert.match(source, /import packageJson from '.\/package.json'/); + assert.match(source, /target: \["html","orama-db"\]/); + assert.match(source, /project: packageJson.name/); + assert.match(source, /version: packageJson.version/); + assert.match(source, /input: \["docs\/\*\*\/\*.md"\]/); + assert.match(source, /baseURL: packageJson.homepage/); + }); + + it('should leave baseURL as a commented hint without a homepage', () => { + const source = buildConfigSource({ + targets: ['html'], + docsDir: 'docs', + output: 'out', + hasHomepage: false, + }); + + assert.match(source, /\/\/ baseURL: /); + assert.doesNotMatch(source, /baseURL: packageJson.homepage/); + }); +}); + +describe('addIgnoredOutput', () => { + it('should append to existing contents', () => { + assert.equal( + addIgnoredOutput('node_modules/\n', 'out'), + 'node_modules/\n\n# doc-kit output\nout/\n' + ); + }); + + it('should create contents when there is no .gitignore', () => { + assert.equal( + addIgnoredOutput(undefined, 'out'), + '# doc-kit output\nout/\n' + ); + }); + + it('should leave an already-ignored output alone', () => { + assert.equal(addIgnoredOutput('out/\n', 'out'), undefined); + assert.equal(addIgnoredOutput('out\n', 'out'), undefined); + }); +}); 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/__tests__/install.test.mjs b/packages/core/src/utils/__tests__/install.test.mjs new file mode 100644 index 00000000..7f4fffc1 --- /dev/null +++ b/packages/core/src/utils/__tests__/install.test.mjs @@ -0,0 +1,150 @@ +import assert from 'node:assert/strict'; +import { mkdtempSync, writeFileSync } from 'node:fs'; +import { tmpdir } from 'node:os'; +import { join } from 'node:path'; +import { describe, it } from 'node:test'; + +import { + buildInstallArguments, + detectPackageManager, + isDevInstall, + isPathTarget, + packageForSpecifier, + resolveGeneratorPackages, +} from '../install.mjs'; + +describe('packageForSpecifier', () => { + it('should extract scoped package names', () => { + assert.equal( + packageForSpecifier('@nodejs/doc-kit-generator-react/html'), + '@nodejs/doc-kit-generator-react' + ); + }); + + it('should extract unscoped package names', () => { + assert.equal(packageForSpecifier('some-package/generator'), 'some-package'); + }); +}); + +describe('isPathTarget', () => { + it('should detect relative and absolute paths and file URLs', () => { + assert.equal(isPathTarget('./my-generator.mjs'), true); + assert.equal(isPathTarget('/abs/generator.mjs'), true); + assert.equal(isPathTarget('file:///abs/generator.mjs'), true); + assert.equal(isPathTarget('@scope/package/sub'), false); + assert.equal(isPathTarget('package'), false); + }); +}); + +describe('resolveGeneratorPackages', () => { + it('should resolve built-in generators to their package, deduplicated', () => { + assert.deepEqual(resolveGeneratorPackages(['html', 'orama-db']), [ + '@nodejs/doc-kit-generator-react', + ]); + }); + + it('should resolve deprecated aliases', () => { + assert.deepEqual(resolveGeneratorPackages(['web']), [ + '@nodejs/doc-kit-generator-react', + ]); + }); + + it('should resolve generators from this package to no install', () => { + assert.deepEqual(resolveGeneratorPackages(['json-simple']), []); + }); + + it('should reject unknown names', () => { + assert.throws( + () => resolveGeneratorPackages(['legacy-htlm']), + /not a built-in generator/ + ); + }); + + it('should resolve third-party specifiers when allowed', () => { + assert.deepEqual( + resolveGeneratorPackages(['@scope/custom/generator'], true), + ['@scope/custom'] + ); + }); + + it('should skip filesystem targets when specifiers are allowed', () => { + assert.deepEqual(resolveGeneratorPackages(['./local.mjs'], true), []); + }); +}); + +describe('detectPackageManager', () => { + it('should detect the package manager from the lockfile', () => { + const dir = mkdtempSync(join(tmpdir(), 'doc-kit-install-')); + + assert.equal(detectPackageManager(dir), 'npm'); + + writeFileSync(join(dir, 'yarn.lock'), ''); + assert.equal(detectPackageManager(dir), 'yarn'); + + writeFileSync(join(dir, 'pnpm-lock.yaml'), ''); + assert.equal(detectPackageManager(dir), 'pnpm'); + }); +}); + +describe('isDevInstall', () => { + it('should default to dev without a manifest', () => { + const dir = mkdtempSync(join(tmpdir(), 'doc-kit-install-')); + + assert.equal(isDevInstall(dir), true); + }); + + it('should mirror a production install of this package', () => { + const dir = mkdtempSync(join(tmpdir(), 'doc-kit-install-')); + + writeFileSync( + join(dir, 'package.json'), + JSON.stringify({ dependencies: { '@nodejs/doc-kit': '^1.0.0' } }) + ); + + assert.equal(isDevInstall(dir), false); + }); + + it('should mirror a dev install of this package', () => { + const dir = mkdtempSync(join(tmpdir(), 'doc-kit-install-')); + + writeFileSync( + join(dir, 'package.json'), + JSON.stringify({ devDependencies: { '@nodejs/doc-kit': '^1.0.0' } }) + ); + + assert.equal(isDevInstall(dir), true); + }); +}); + +describe('buildInstallArguments', () => { + it('should build npm arguments', () => { + assert.deepEqual(buildInstallArguments('npm', ['a', 'b'], true), [ + 'install', + '--save-dev', + 'a', + 'b', + ]); + assert.deepEqual(buildInstallArguments('npm', ['a'], false), [ + 'install', + 'a', + ]); + }); + + it('should build yarn, pnpm, and bun arguments', () => { + assert.deepEqual(buildInstallArguments('yarn', ['a'], true), [ + 'add', + '--dev', + 'a', + ]); + assert.deepEqual(buildInstallArguments('pnpm', ['a'], true), [ + 'add', + '--save-dev', + 'a', + ]); + assert.deepEqual(buildInstallArguments('bun', ['a'], true), [ + 'add', + '--dev', + 'a', + ]); + }); +}); diff --git a/packages/core/src/utils/bootstrap.mjs b/packages/core/src/utils/bootstrap.mjs new file mode 100644 index 00000000..14f4bc52 --- /dev/null +++ b/packages/core/src/utils/bootstrap.mjs @@ -0,0 +1,154 @@ +'use strict'; + +import { existsSync, readdirSync } from 'node:fs'; +import { join } from 'node:path'; + +/** + * Directory names commonly used for documentation, in preference order. + */ +const DOCS_DIR_CANDIDATES = ['docs', 'doc', 'documentation']; + +/** + * Configuration file names that mark a project as already set up + * (the subset of cosmiconfig's search places doc-kit users encounter). + */ +const CONFIG_FILE_NAMES = [ + 'doc-kit.config.mjs', + 'doc-kit.config.js', + 'doc-kit.config.cjs', + '.doc-kitrc', + '.doc-kitrc.json', + '.doc-kitrc.mjs', + '.doc-kitrc.js', +]; + +/** + * A minimal, spec-shaped starter document so a fresh project has something + * to render immediately. + */ +export const STARTER_DOC = `# hello + +A one-line description of the module. + +## \`hello.greet(name)\` + +- \`name\` {string} The name to greet. +- Returns: {string} + +Greets \`name\`. +`; + +/** + * Whether a directory contains any Markdown files (at any depth). + * + * @param {string} directory - The directory to inspect + * @returns {boolean} + */ +export const hasMarkdown = directory => { + try { + return readdirSync(directory, { recursive: true }).some(entry => + String(entry).endsWith('.md') + ); + } catch { + return false; + } +}; + +/** + * Finds an existing doc-kit configuration file in a directory. + * + * @param {string} directory - The project directory + * @returns {string | undefined} The file name, if one exists + */ +export const findExistingConfig = directory => + CONFIG_FILE_NAMES.find(name => existsSync(join(directory, name))); + +/** + * Picks the documentation directory for a project: the first conventional + * directory that already holds Markdown, then the first that exists, and + * finally `docs` for a fresh start. + * + * @param {string} directory - The project directory + * @returns {string} The documentation directory, relative to the project + */ +export const detectDocsDirectory = directory => { + const existing = DOCS_DIR_CANDIDATES.filter(candidate => + existsSync(join(directory, candidate)) + ); + + return ( + existing.find(candidate => hasMarkdown(join(directory, candidate))) ?? + existing[0] ?? + DOCS_DIR_CANDIDATES[0] + ); +}; + +/** + * Builds the contents of a starter `doc-kit.config.mjs`. Naming and + * versioning are imported from `package.json` so they never drift from the + * project. + * + * @param {object} options + * @param {string[]} options.targets - Generator targets + * @param {string} options.docsDir - The documentation directory + * @param {string} options.output - The output directory + * @param {boolean} options.hasHomepage - Whether `package.json` declares a + * `homepage` to use as the site's base URL + * @returns {string} The configuration file source + */ +export const buildConfigSource = ({ + targets, + docsDir, + output, + hasHomepage, +}) => { + const baseURL = hasHomepage + ? ` + // The public URL of the published site, used for absolute links, + // sitemaps, and llms.txt + baseURL: packageJson.homepage,` + : ` + // Set to the public URL of the published site to enable absolute links, + // sitemaps, and llms.txt page URLs + // baseURL: 'https://example.com/docs',`; + + return `import packageJson from './package.json' with { type: 'json' }; + +/** @type {import('@nodejs/doc-kit/utils/configuration/types').Configuration} */ +export default { + target: ${JSON.stringify(targets)}, + + global: { + // Naming and versioning follow package.json + project: packageJson.name, + version: packageJson.version, + + input: [${JSON.stringify(`${docsDir}/**/*.md`)}], + output: ${JSON.stringify(output)}, +${baseURL} + }, +}; +`; +}; + +/** + * Adds the output directory to `.gitignore` contents when it is not already + * ignored. + * + * @param {string | undefined} contents - The current `.gitignore` contents, + * if the file exists + * @param {string} output - The output directory + * @returns {string | undefined} The new contents, or `undefined` when no + * change is needed + */ +export const addIgnoredOutput = (contents, output) => { + const lines = (contents ?? '').split('\n').map(line => line.trim()); + + if (lines.includes(output) || lines.includes(`${output}/`)) { + return undefined; + } + + const existing = contents ? `${contents.replace(/\n?$/, '\n')}\n` : ''; + + return `${existing}# doc-kit output\n${output}/\n`; +}; diff --git a/packages/core/src/utils/configuration/__tests__/index.test.mjs b/packages/core/src/utils/configuration/__tests__/index.test.mjs index a2cc957f..da73eab4 100644 --- a/packages/core/src/utils/configuration/__tests__/index.test.mjs +++ b/packages/core/src/utils/configuration/__tests__/index.test.mjs @@ -1,4 +1,7 @@ import assert from 'node:assert'; +import { mkdtempSync, writeFileSync } from 'node:fs'; +import { tmpdir } from 'node:os'; +import { join } from 'node:path'; import { describe, it, mock, beforeEach } from 'node:test'; // Mock dependencies @@ -96,6 +99,47 @@ describe('config.mjs', () => { ); assert.strictEqual(mockConfigSearch.mock.calls.length, 0); }); + + it('should merge extends presets underneath the config file', async () => { + const dir = mkdtempSync(join(tmpdir(), 'doc-kit-config-')); + + writeFileSync( + join(dir, 'base.mjs'), + 'export default { global: { project: "Base", ref: "base" }, html: { a: 1 } };' + ); + writeFileSync( + join(dir, 'other.mjs'), + 'export default { global: { project: "Other" }, html: { b: 2 } };' + ); + + mockConfigLoad.mock.mockImplementationOnce(async () => ({ + config: { + extends: ['./base.mjs', './other.mjs'], + global: { ref: 'own' }, + }, + filepath: join(dir, 'doc-kit.config.mjs'), + })); + + const result = await loadConfigFile('any'); + + // Later presets win over earlier ones; the file itself wins over all + assert.deepStrictEqual(result, { + global: { project: 'Other', ref: 'own' }, + html: { a: 1, b: 2 }, + }); + }); + + it('should resolve extends package specifiers from the config file', async () => { + mockConfigLoad.mock.mockImplementationOnce(async () => ({ + config: { extends: '@node-core/doc-kit/config' }, + filepath: join(process.cwd(), 'doc-kit.config.mjs'), + })); + + const result = await loadConfigFile('any'); + + assert.strictEqual(result.global.project, 'Node.js'); + assert.strictEqual(result.global.repository, 'nodejs/node'); + }); }); describe('createConfigFromCLIOptions', () => { @@ -250,6 +294,17 @@ describe('config.mjs', () => { assert.strictEqual(mockConfigSearch.mock.calls.length, 1); }); + it('should default to project-neutral values', async () => { + const config = await createRunConfiguration({}); + + // No repository, site, or release history is assumed; presets such as + // @node-core/doc-kit/config opt back into the Node.js values + assert.strictEqual(config.global.repository, undefined); + assert.strictEqual(config.global.baseURL, undefined); + assert.deepStrictEqual(config.global.changelog, []); + assert.strictEqual(typeof config.global.project, 'string'); + }); + it('should handle generator-specific overrides', async () => { mockConfigLoad.mock.mockImplementationOnce(async () => ({ config: createMockConfig({ diff --git a/packages/core/src/utils/configuration/index.mjs b/packages/core/src/utils/configuration/index.mjs index 965cfab5..5d00ee53 100644 --- a/packages/core/src/utils/configuration/index.mjs +++ b/packages/core/src/utils/configuration/index.mjs @@ -1,10 +1,13 @@ +import { readFileSync } from 'node:fs'; +import { createRequire } from 'node:module'; import { cpus } from 'node:os'; +import { dirname, isAbsolute, resolve } from 'node:path'; +import { pathToFileURL } from 'node:url'; import { isMainThread } from 'node:worker_threads'; import { cosmiconfig } from 'cosmiconfig'; import { coerce } from 'semver'; -import { CHANGELOG_URL, populate } from './templates.mjs'; import { loadGenerators, resolveGeneratorSpecifier, @@ -17,6 +20,20 @@ import { deepMerge } from '../misc.mjs'; const configExplorer = cosmiconfig('doc-kit'); +/** + * The name of the project being documented, from the manifest in the working + * directory. Generators use it for titles, logos, and templated text. + * + * @returns {string | undefined} + */ +const detectProject = () => { + try { + return JSON.parse(readFileSync('package.json', 'utf-8')).name; + } catch { + return undefined; + } +}; + /** * Get's the default configuration for the loaded generators * @@ -37,15 +54,13 @@ export const getDefaultConfig = (generators, config) => }, /** @type {import('./types').Configuration} */ ({ global: { + project: detectProject() ?? 'API Docs', version: process.version, minify: true, - repository: 'nodejs/node', ref: 'HEAD', - baseURL: 'https://nodejs.org/docs', - changelog: populate(CHANGELOG_URL, { - repository: 'nodejs/node', - ref: 'HEAD', - }), + // Without release history there is nothing to build a version picker + // from, so generators render single-version output. + changelog: [], pathsToCopy: ['assets', 'public', 'static'], }, @@ -58,6 +73,23 @@ export const getDefaultConfig = (generators, config) => }) ); +/** + * Resolves an `extends` entry of a configuration file into an importable + * URL: relative paths resolve against the configuration file, anything else + * resolves as a package import specifier (e.g. `@node-core/doc-kit/config`). + * + * @param {string} specifier - The `extends` entry + * @param {string} configFilePath - The configuration file it appears in + * @returns {string} A `file:` URL to import + */ +const resolveConfigExtends = (specifier, configFilePath) => { + if (specifier.startsWith('.') || isAbsolute(specifier)) { + return pathToFileURL(resolve(dirname(configFilePath), specifier)).href; + } + + return pathToFileURL(createRequire(configFilePath).resolve(specifier)).href; +}; + /** * Loads an explicit configuration file or searches for one using cosmiconfig. * @@ -69,7 +101,19 @@ export const loadConfigFile = async filePath => { ? await configExplorer.load(filePath) : await configExplorer.search(); - return result?.config ?? {}; + if (!result) { + return {}; + } + + let { extends: presets, ...config } = result.config ?? {}; + + for (const preset of enforceArray(presets ?? []).toReversed()) { + const module = await import(resolveConfigExtends(preset, result.filepath)); + + config = deepMerge(module.default ?? module, config); + } + + return config; }; /** diff --git a/packages/core/src/utils/configuration/types.d.ts b/packages/core/src/utils/configuration/types.d.ts index 3536ba05..d3b0c402 100644 --- a/packages/core/src/utils/configuration/types.d.ts +++ b/packages/core/src/utils/configuration/types.d.ts @@ -21,8 +21,14 @@ export type Configuration = { }; export type GlobalConfiguration = { - // The repository - repository: string; + // The name of the project being documented, used for titles, logos, and + // templated text (defaults to the `name` in the working directory's + // `package.json`) + project: string; + + // The repository (`owner/name`), used for source and edit links; when + // omitted, repository-specific UI (e.g. the GitHub link) is omitted + repository?: string; // The path to the input source files. This parameter accepts globs and can // be a glob when passed to a generator. @@ -49,8 +55,9 @@ export type GlobalConfiguration = { // A list of all the titles of all the documentation files index: Array<{ section: string; api: string }>; - // The base URL - baseURL: string | URL; + // The base URL of the published site; templates referencing `{baseURL}` + // (e.g. sitemap and llms-txt page URLs) need it to produce absolute URLs + baseURL?: string | URL; // Git ref (i.e. HEAD) ref: string; diff --git a/packages/core/src/utils/generators.mjs b/packages/core/src/utils/generators.mjs index 48430853..ea2d97df 100644 --- a/packages/core/src/utils/generators.mjs +++ b/packages/core/src/utils/generators.mjs @@ -69,55 +69,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/install.mjs b/packages/core/src/utils/install.mjs new file mode 100644 index 00000000..13d71bdb --- /dev/null +++ b/packages/core/src/utils/install.mjs @@ -0,0 +1,187 @@ +'use strict'; + +import { spawnSync } from 'node:child_process'; +import { existsSync, readFileSync } from 'node:fs'; +import { isAbsolute, join } from 'node:path'; + +import packageJson from '../../package.json' with { type: 'json' }; +import { allGenerators } from '../generators/index.mjs'; +import logger from '../logger/index.mjs'; + +const installLogger = logger.child('install'); + +/** + * Lockfiles used to detect which package manager a project uses. + */ +const LOCKFILES = { + 'bun.lock': 'bun', + 'bun.lockb': 'bun', + 'package-lock.json': 'npm', + 'pnpm-lock.yaml': 'pnpm', + 'yarn.lock': 'yarn', +}; + +/** + * Extracts the package name from an import specifier + * (`@scope/package/subpath` or `package/subpath`). + * + * @param {string} specifier - A bare import specifier + * @returns {string} The package name + */ +export const packageForSpecifier = specifier => { + const segments = specifier.split('/'); + + return specifier.startsWith('@') + ? segments.slice(0, 2).join('/') + : segments[0]; +}; + +/** + * Whether a generator target is a filesystem path (or file URL) rather than + * a package specifier. + * + * @param {string} target - A generator target + * @returns {boolean} + */ +export const isPathTarget = target => + target.startsWith('.') || isAbsolute(target) || target.startsWith('file:'); + +/** + * Resolves generator names into the npm packages providing them. + * Names already provided by this package resolve to no install. + * + * With `allowSpecifiers` (used for configuration-file targets), bare import + * specifiers of third-party generators resolve to their package, and + * filesystem paths resolve to no install; otherwise only built-in names are + * accepted, so a typo cannot install an unintended package. + * + * @param {string[]} names - Built-in generator names (e.g. `html`), or any + * generator target when `allowSpecifiers` is set + * @param {boolean} [allowSpecifiers=false] - Accept non-built-in targets + * @returns {string[]} Deduplicated package names to install + * @throws {Error} When a name is not a built-in generator + */ +export const resolveGeneratorPackages = (names, allowSpecifiers = false) => { + const packages = names.map(name => { + if (name in allGenerators) { + return packageForSpecifier(allGenerators[name]); + } + + if (!allowSpecifiers) { + throw new Error( + `"${name}" is not a built-in generator ` + + `(expected one of: ${Object.keys(allGenerators).join(', ')}). ` + + 'For a third-party generator, install its package with your ' + + 'package manager directly.' + ); + } + + return isPathTarget(name) ? null : packageForSpecifier(name); + }); + + return [...new Set(packages)].filter( + name => name && name !== packageJson.name + ); +}; + +/** + * Detects the package manager used by the project in `directory`, based on + * the lockfile present. Defaults to npm. + * + * @param {string} directory - The project directory + * @returns {string} The package manager binary name + */ +export const detectPackageManager = directory => { + for (const [lockfile, packageManager] of Object.entries(LOCKFILES)) { + if (existsSync(join(directory, lockfile))) { + return packageManager; + } + } + + return 'npm'; +}; + +/** + * Whether new packages should be installed as `devDependencies`: mirrors + * where the project put this package, defaulting to a dev install (doc + * tooling rarely ships to production). + * + * @param {string} directory - The project directory + * @returns {boolean} + */ +export const isDevInstall = directory => { + const manifestPath = join(directory, 'package.json'); + + if (!existsSync(manifestPath)) { + return true; + } + + try { + const manifest = JSON.parse(readFileSync(manifestPath, 'utf-8')); + + return !(packageJson.name in (manifest.dependencies ?? {})); + } catch { + return true; + } +}; + +/** + * Builds the argument list for installing packages with a package manager. + * + * @param {string} packageManager - The package manager binary name + * @param {string[]} packages - Package names to install + * @param {boolean} dev - Whether to install as devDependencies + * @returns {string[]} The arguments to pass to the package manager + */ +export const buildInstallArguments = (packageManager, packages, dev) => { + const subcommand = packageManager === 'npm' ? 'install' : 'add'; + // yarn and bun spell it `--dev`; npm and pnpm accept `--save-dev` + const devFlag = ['yarn', 'bun'].includes(packageManager) + ? '--dev' + : '--save-dev'; + + return [subcommand, ...(dev ? [devFlag] : []), ...packages]; +}; + +/** + * Installs the packages providing the given generators with the project's + * package manager. + * + * @param {string[]} names - Generator targets (see + * {@link resolveGeneratorPackages}) + * @param {boolean} [allowSpecifiers=false] - Accept non-built-in targets + * @returns {string[]} The installed package names (empty when everything was + * already part of this package) + * @throws {Error} When the package manager exits unsuccessfully + */ +export const installGeneratorPackages = (names, allowSpecifiers = false) => { + const packages = resolveGeneratorPackages(names, allowSpecifiers); + + if (!packages.length) { + return packages; + } + + const packageManager = detectPackageManager(process.cwd()); + const args = buildInstallArguments( + packageManager, + packages, + isDevInstall(process.cwd()) + ); + + installLogger.info(`Running \`${packageManager} ${args.join(' ')}\``); + + const { status, error } = spawnSync(packageManager, args, { + stdio: 'inherit', + shell: process.platform === 'win32', + }); + + if (error) { + throw error; + } + + if (status !== 0) { + throw new Error(`\`${packageManager}\` exited with status ${status}`); + } + + return packages; +}; diff --git a/packages/core/src/utils/remark.mjs b/packages/core/src/utils/remark.mjs index 1aacd37a..3935b7ea 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 remarkMdx from 'remark-mdx'; @@ -12,49 +7,24 @@ import remarkParse from 'remark-parse'; import remarkRehype from 'remark-rehype'; import remarkStringify from 'remark-stringify'; import { unified } from 'unified'; -import { visit } from 'unist-util-visit'; -import syntaxHighlighter, { highlighter } from './highlighter.mjs'; +import syntaxHighlighter from './highlighter.mjs'; import { lazy } from './misc.mjs'; -import { - typeAnnotationToHast, - typeAnnotationToHighlightedHast, -} from './type-annotations/hast.mjs'; +import { typeAnnotationToHast } from './type-annotations/hast.mjs'; import remarkTypeAnnotations from './type-annotations/remark.mjs'; -import { AST_NODE_TYPES } from '../generators/jsx-ast/constants.mjs'; -import transformAlerts from '../generators/jsx-ast/utils/plugins/alerts.mjs'; -import transformElements from '../generators/jsx-ast/utils/plugins/transformer.mjs'; -const passThrough = ['element', ...Object.values(AST_NODE_TYPES.MDX)]; -const codeMetaProperty = 'codeMeta'; - -/** - * Stores fenced code metadata on properties before rehypeRaw reparses the tree. - */ -const preserveCodeMeta = () => tree => { - visit(tree, 'element', node => { - const meta = node.data?.meta; - - if (node.tagName === 'code' && typeof meta === 'string') { - node.properties ||= {}; - node.properties[codeMetaProperty] = meta; - } - }); -}; - -/** - * Restores fenced code metadata so the Shiki plugin can read displayName. - */ -const restoreCodeMeta = () => tree => { - visit(tree, 'element', node => { - const meta = node.properties?.[codeMetaProperty]; - - if (node.tagName === 'code' && typeof meta === 'string') { - node.data = { ...node.data, meta }; - delete node.properties[codeMetaProperty]; - } - }); -}; +// MDX node types that may appear in trees parsed by `getRemarkMdx`; the +// rehype pipelines pass them through untouched. +const passThrough = [ + 'element', + 'mdxJsxTextElement', + 'mdxJsxFlowElement', + 'mdxJsxAttribute', + 'mdxJsxAttributeValueExpression', + 'mdxFlowExpression', + 'mdxTextExpression', + 'mdxjsEsm', +]; /** * Retrieves an instance of Remark configured to parse GFM (GitHub Flavored Markdown) @@ -125,34 +95,3 @@ export const getRemarkRehypeWithShiki = lazy(() => // 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 = lazy(() => - unified() - .use(remarkParse) - .use(transformAlerts) - // 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, - // The web pipeline gets Shiki-highlighted types with embedded links - handlers: { typeAnnotation: typeAnnotationToHighlightedHast }, - }) - .use(preserveCodeMeta) - // Any `raw` HTML in the markdown must be converted to AST in order for Recma to understand it - .use(rehypeRaw, { passThrough }) - .use(restoreCodeMeta) - .use(() => singletonShiki) - .use(transformElements) - .use(rehypeRecma) - .use(recmaJsx) - .use(recmaStringify) -); diff --git a/packages/core/src/utils/signature/parseList.mjs b/packages/core/src/utils/signature/parseList.mjs index 9839b594..a9ca2cad 100644 --- a/packages/core/src/utils/signature/parseList.mjs +++ b/packages/core/src/utils/signature/parseList.mjs @@ -85,7 +85,7 @@ export function parseListItem(child) { /** * 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('../../generators/legacy-json/types').Section} section + * @param {{ [key: string]: unknown }} section - The section object to populate with the parsed values * @param {import('@types/mdast').RootContent[]} nodes */ export function parseList(section, nodes) { diff --git a/packages/legacy/package.json b/packages/legacy/package.json new file mode 100644 index 00000000..be6f5894 --- /dev/null +++ b/packages/legacy/package.json @@ -0,0 +1,32 @@ +{ + "name": "@nodejs/doc-kit-generator-legacy", + "type": "module", + "version": "0.0.0", + "description": "Legacy-format generators for @nodejs/doc-kit: legacy-html, legacy-html-all, legacy-json, and legacy-json-all", + "repository": { + "type": "git", + "url": "git+https://github.com/nodejs/doc-kit.git", + "directory": "packages/legacy" + }, + "exports": { + "./legacy-html": "./src/legacy-html/index.mjs", + "./legacy-html-all": "./src/legacy-html-all/index.mjs", + "./legacy-json": "./src/legacy-json/index.mjs", + "./legacy-json-all": "./src/legacy-json-all/index.mjs", + "./package.json": "./package.json" + }, + "files": [ + "src", + "!src/**/*.test.mjs", + "!src/**/__tests__", + "CHANGELOG.md", + "LICENSE", + "README.md" + ], + "dependencies": { + "@nodejs/doc-kit": "^0.0.0", + "hastscript": "^9.0.1", + "unist-builder": "^4.0.0", + "unist-util-visit": "^5.1.0" + } +} diff --git a/packages/core/src/generators/legacy-html-all/README.md b/packages/legacy/src/legacy-html-all/README.md similarity index 100% rename from packages/core/src/generators/legacy-html-all/README.md rename to packages/legacy/src/legacy-html-all/README.md diff --git a/packages/core/src/generators/legacy-html-all/generate.mjs b/packages/legacy/src/legacy-html-all/generate.mjs similarity index 90% rename from packages/core/src/generators/legacy-html-all/generate.mjs rename to packages/legacy/src/legacy-html-all/generate.mjs index 1aa7bfdf..192f18e4 100644 --- a/packages/core/src/generators/legacy-html-all/generate.mjs +++ b/packages/legacy/src/legacy-html-all/generate.mjs @@ -3,9 +3,10 @@ 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 as remark } from '../../utils/remark.mjs'; +import getConfig from '@nodejs/doc-kit/utils/configuration/index.mjs'; +import { minifyHTML } from '@nodejs/doc-kit/utils/html-minifier.mjs'; +import { getRemarkRehype as remark } from '@nodejs/doc-kit/utils/remark.mjs'; + import { replaceTemplateValues } from '../legacy-html/utils/replaceTemplateValues.mjs'; import tableOfContents from '../legacy-html/utils/tableOfContents.mjs'; diff --git a/packages/core/src/generators/legacy-html-all/index.mjs b/packages/legacy/src/legacy-html-all/index.mjs similarity index 93% rename from packages/core/src/generators/legacy-html-all/index.mjs rename to packages/legacy/src/legacy-html-all/index.mjs index 4d6f1d35..27bb3b85 100644 --- a/packages/core/src/generators/legacy-html-all/index.mjs +++ b/packages/legacy/src/legacy-html-all/index.mjs @@ -18,7 +18,7 @@ export default { description: 'Generates the `all.html` file from the `legacy-html` generator, which includes all the modules in one single file', - dependsOn: '@node-core/doc-kit/legacy-html', + dependsOn: '@nodejs/doc-kit-generator-legacy/legacy-html', defaultConfiguration: { templatePath: legacyHtml.defaultConfiguration.templatePath, diff --git a/packages/core/src/generators/legacy-html-all/types.d.ts b/packages/legacy/src/legacy-html-all/types.d.ts similarity index 100% rename from packages/core/src/generators/legacy-html-all/types.d.ts rename to packages/legacy/src/legacy-html-all/types.d.ts diff --git a/packages/core/src/generators/legacy-html/README.md b/packages/legacy/src/legacy-html/README.md similarity index 100% rename from packages/core/src/generators/legacy-html/README.md rename to packages/legacy/src/legacy-html/README.md diff --git a/packages/core/src/generators/legacy-html/assets/api.js b/packages/legacy/src/legacy-html/assets/api.js similarity index 100% rename from packages/core/src/generators/legacy-html/assets/api.js rename to packages/legacy/src/legacy-html/assets/api.js diff --git a/packages/core/src/generators/legacy-html/assets/js-flavor-cjs.svg b/packages/legacy/src/legacy-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/legacy-html/assets/js-flavor-cjs.svg diff --git a/packages/core/src/generators/legacy-html/assets/js-flavor-esm.svg b/packages/legacy/src/legacy-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/legacy-html/assets/js-flavor-esm.svg diff --git a/packages/core/src/generators/legacy-html/assets/style.css b/packages/legacy/src/legacy-html/assets/style.css similarity index 100% rename from packages/core/src/generators/legacy-html/assets/style.css rename to packages/legacy/src/legacy-html/assets/style.css diff --git a/packages/core/src/generators/legacy-html/generate.mjs b/packages/legacy/src/legacy-html/generate.mjs similarity index 91% rename from packages/core/src/generators/legacy-html/generate.mjs rename to packages/legacy/src/legacy-html/generate.mjs index a4d5de62..b3f143c3 100644 --- a/packages/core/src/generators/legacy-html/generate.mjs +++ b/packages/legacy/src/legacy-html/generate.mjs @@ -3,14 +3,15 @@ import { readFile, cp } from 'node:fs/promises'; import { basename, join } from 'node:path'; +import getConfig from '@nodejs/doc-kit/utils/configuration/index.mjs'; +import { writeFile } from '@nodejs/doc-kit/utils/file.mjs'; +import { groupNodesByModule } from '@nodejs/doc-kit/utils/generators.mjs'; +import { minifyHTML } from '@nodejs/doc-kit/utils/html-minifier.mjs'; +import { getRemarkRehypeWithShiki as remark } from '@nodejs/doc-kit/utils/remark.mjs'; + 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 { writeFile } from '../../utils/file.mjs'; -import { groupNodesByModule } from '../../utils/generators.mjs'; -import { minifyHTML } from '../../utils/html-minifier.mjs'; -import { getRemarkRehypeWithShiki as remark } from '../../utils/remark.mjs'; /** * Creates a heading object with the given name. diff --git a/packages/core/src/generators/legacy-html/index.mjs b/packages/legacy/src/legacy-html/index.mjs similarity index 89% rename from packages/core/src/generators/legacy-html/index.mjs rename to packages/legacy/src/legacy-html/index.mjs index 68c4ea9f..2b6f8836 100644 --- a/packages/core/src/generators/legacy-html/index.mjs +++ b/packages/legacy/src/legacy-html/index.mjs @@ -2,8 +2,9 @@ import { join } from 'node:path'; +import { GITHUB_EDIT_URL } from '@nodejs/doc-kit/utils/configuration/templates.mjs'; + import { generate, processChunk } from './generate.mjs'; -import { GITHUB_EDIT_URL } from '../../utils/configuration/templates.mjs'; /** * @@ -21,7 +22,7 @@ export default { description: 'Generates the legacy version of the API docs in HTML, with the assets and styles included as files', - dependsOn: '@node-core/doc-kit/metadata', + dependsOn: '@nodejs/doc-kit/metadata', defaultConfiguration: { templatePath: join(import.meta.dirname, 'template.html'), diff --git a/packages/core/src/generators/legacy-html/template.html b/packages/legacy/src/legacy-html/template.html similarity index 100% rename from packages/core/src/generators/legacy-html/template.html rename to packages/legacy/src/legacy-html/template.html diff --git a/packages/core/src/generators/legacy-html/types.d.ts b/packages/legacy/src/legacy-html/types.d.ts similarity index 86% rename from packages/core/src/generators/legacy-html/types.d.ts rename to packages/legacy/src/legacy-html/types.d.ts index 19b2cf7d..9de27555 100644 --- a/packages/core/src/generators/legacy-html/types.d.ts +++ b/packages/legacy/src/legacy-html/types.d.ts @@ -1,4 +1,4 @@ -import type { MetadataEntry } from '../metadata/types'; +import type { MetadataEntry } from '@nodejs/doc-kit/generators/metadata/types'; export interface TemplateValues { api: string; diff --git a/packages/core/src/generators/legacy-html/utils/__tests__/buildContent.test.mjs b/packages/legacy/src/legacy-html/utils/__tests__/buildContent.test.mjs similarity index 93% rename from packages/core/src/generators/legacy-html/utils/__tests__/buildContent.test.mjs rename to packages/legacy/src/legacy-html/utils/__tests__/buildContent.test.mjs index ac40da98..24c99216 100644 --- a/packages/core/src/generators/legacy-html/utils/__tests__/buildContent.test.mjs +++ b/packages/legacy/src/legacy-html/utils/__tests__/buildContent.test.mjs @@ -3,7 +3,8 @@ import assert from 'node:assert/strict'; import { before, describe, it } from 'node:test'; -import { setConfig } from '../../../../utils/configuration/index.mjs'; +import { setConfig } from '@nodejs/doc-kit/utils/configuration/index.mjs'; + import buildContent from '../buildContent.mjs'; const createEntry = slug => { diff --git a/packages/core/src/generators/legacy-html/utils/__tests__/slugger.test.mjs b/packages/legacy/src/legacy-html/utils/__tests__/slugger.test.mjs similarity index 100% rename from packages/core/src/generators/legacy-html/utils/__tests__/slugger.test.mjs rename to packages/legacy/src/legacy-html/utils/__tests__/slugger.test.mjs diff --git a/packages/core/src/generators/legacy-html/utils/buildContent.mjs b/packages/legacy/src/legacy-html/utils/buildContent.mjs similarity index 88% rename from packages/core/src/generators/legacy-html/utils/buildContent.mjs rename to packages/legacy/src/legacy-html/utils/buildContent.mjs index fd643ab8..d5b0dcdd 100644 --- a/packages/core/src/generators/legacy-html/utils/buildContent.mjs +++ b/packages/legacy/src/legacy-html/utils/buildContent.mjs @@ -1,23 +1,23 @@ 'use strict'; +import getConfig from '@nodejs/doc-kit/utils/configuration/index.mjs'; +import { + GITHUB_BLOB_URL, + populate, +} from '@nodejs/doc-kit/utils/configuration/templates.mjs'; +import { UNIST } from '@nodejs/doc-kit/utils/queries/index.mjs'; +import { getRemarkRehypeWithShiki as remark } from '@nodejs/doc-kit/utils/remark.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 { UNIST } from '../../../utils/queries/index.mjs'; -import { getRemarkRehypeWithShiki as remark } from '../../../utils/remark.mjs'; /** * Builds a Markdown heading for a given node * - * @param {import('../../metadata/types').HeadingNode} node The node to build the Markdown heading for + * @param {import('@nodejs/doc-kit/generators/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 @@ -54,7 +54,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('@nodejs/doc-kit/generators/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 */ @@ -77,7 +77,7 @@ const buildStability = ({ children, data }, index, parent) => { /** * Creates a history table row. * - * @param {import('../../metadata/types').ChangeEntry} change + * @param {import('@nodejs/doc-kit/generators/metadata/types').ChangeEntry} change */ const createHistoryTableRow = ({ version: changeVersions, description }) => { const descriptionNode = remark().parse(description); @@ -94,7 +94,7 @@ const createHistoryTableRow = ({ version: changeVersions, description }) => { /** * Builds the Metadata Properties into content * - * @param {import('../../metadata/types').MetadataEntry} node The node to build the properties from + * @param {import('@nodejs/doc-kit/generators/metadata/types').MetadataEntry} node The node to build the properties from * @returns {import('unist').Parent} The HTML AST tree of the properties content */ const buildMetadataElement = node => { @@ -202,8 +202,8 @@ const buildMetadataElement = node => { /** * 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 */ export default (headNodes, metadataEntries) => { const getLegacySlug = createLegacySlugger(); diff --git a/packages/core/src/generators/legacy-html/utils/buildDropdowns.mjs b/packages/legacy/src/legacy-html/utils/buildDropdowns.mjs similarity index 91% rename from packages/core/src/generators/legacy-html/utils/buildDropdowns.mjs rename to packages/legacy/src/legacy-html/utils/buildDropdowns.mjs index 7a2c3be8..ef71c6d3 100644 --- a/packages/core/src/generators/legacy-html/utils/buildDropdowns.mjs +++ b/packages/legacy/src/legacy-html/utils/buildDropdowns.mjs @@ -1,11 +1,11 @@ 'use strict'; -import getConfig from '../../../utils/configuration/index.mjs'; -import { populate } from '../../../utils/configuration/templates.mjs'; +import getConfig from '@nodejs/doc-kit/utils/configuration/index.mjs'; +import { populate } from '@nodejs/doc-kit/utils/configuration/templates.mjs'; import { getCompatibleVersions, getVersionFromSemVer, -} from '../../../utils/generators.mjs'; +} from '@nodejs/doc-kit/utils/generators.mjs'; /** * Builds the Dropdown for the current Table of Contents @@ -50,7 +50,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'); diff --git a/packages/core/src/generators/legacy-html/utils/buildExtraContent.mjs b/packages/legacy/src/legacy-html/utils/buildExtraContent.mjs similarity index 79% rename from packages/core/src/generators/legacy-html/utils/buildExtraContent.mjs rename to packages/legacy/src/legacy-html/utils/buildExtraContent.mjs index 6f9ef45f..7086fd30 100644 --- a/packages/core/src/generators/legacy-html/utils/buildExtraContent.mjs +++ b/packages/legacy/src/legacy-html/utils/buildExtraContent.mjs @@ -6,7 +6,7 @@ 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 + * @param {Array} headMetadata The API metadata nodes to be used for the Stability Overview */ const buildStabilityOverview = headMetadata => { const headNodesWithStability = headMetadata.filter(entry => entry.stability); @@ -46,8 +46,8 @@ const buildStabilityOverview = headMetadata => { /** * 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 + * @param {Array} headNodes The API metadata nodes to be used for the Stability Overview + * @param {import('@nodejs/doc-kit/generators/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) => { diff --git a/packages/core/src/generators/legacy-html/utils/replaceTemplateValues.mjs b/packages/legacy/src/legacy-html/utils/replaceTemplateValues.mjs similarity index 89% rename from packages/core/src/generators/legacy-html/utils/replaceTemplateValues.mjs rename to packages/legacy/src/legacy-html/utils/replaceTemplateValues.mjs index baec0754..c46739d9 100644 --- a/packages/core/src/generators/legacy-html/utils/replaceTemplateValues.mjs +++ b/packages/legacy/src/legacy-html/utils/replaceTemplateValues.mjs @@ -1,5 +1,7 @@ 'use strict'; +import { populate } from '@nodejs/doc-kit/utils/configuration/templates.mjs'; + import { buildToC, buildNavigation, @@ -7,13 +9,12 @@ import { 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 {import('@nodejs/doc-kit/utils/configuration/types').GlobalConfiguration} config * @param {{ skipGitHub?: boolean; skipGtocPicker?: boolean }} [options] - Optional settings * @returns {string} The replaced template values */ diff --git a/packages/core/src/generators/legacy-html/utils/slugger.mjs b/packages/legacy/src/legacy-html/utils/slugger.mjs similarity index 91% rename from packages/core/src/generators/legacy-html/utils/slugger.mjs rename to packages/legacy/src/legacy-html/utils/slugger.mjs index 43d805cb..a18af1fe 100644 --- a/packages/core/src/generators/legacy-html/utils/slugger.mjs +++ b/packages/legacy/src/legacy-html/utils/slugger.mjs @@ -1,6 +1,6 @@ 'use strict'; -import { DEPRECATION_HEADING_REGEX } from '../../metadata/constants.mjs'; +import { DEPRECATION_HEADING_REGEX } from '@nodejs/doc-kit/generators/metadata/constants.mjs'; /** * Creates a stateful slugger for legacy anchor links. diff --git a/packages/core/src/generators/legacy-html/utils/tableOfContents.mjs b/packages/legacy/src/legacy-html/utils/tableOfContents.mjs similarity index 81% rename from packages/core/src/generators/legacy-html/utils/tableOfContents.mjs rename to packages/legacy/src/legacy-html/utils/tableOfContents.mjs index 6a8f86d9..35918f7f 100644 --- a/packages/core/src/generators/legacy-html/utils/tableOfContents.mjs +++ b/packages/legacy/src/legacy-html/utils/tableOfContents.mjs @@ -8,8 +8,8 @@ * * 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('@nodejs/doc-kit/generators/metadata/types').MetadataEntry) => string }} options The optional ToC options */ const tableOfContents = (entries, options) => { // Filter out the entries that have a name property / or that have empty content @@ -33,7 +33,7 @@ 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('@nodejs/doc-kit/generators/metadata/types').MetadataEntry} metadata The current node that is being parsed */ tableOfContents.parseNavigationNode = ({ api, heading }) => `${heading.data.name}`; @@ -41,7 +41,7 @@ tableOfContents.parseNavigationNode = ({ api, heading }) => /** * Builds the Label with extra metadata to be used in the ToC * - * @param {import('../../metadata/types').MetadataEntry} metadata + * @param {import('@nodejs/doc-kit/generators/metadata/types').MetadataEntry} metadata */ tableOfContents.parseToCNode = ({ stability, api, heading }) => { const fullSlug = `${api}.html#${heading.data.slug}`; diff --git a/packages/core/src/generators/legacy-json-all/README.md b/packages/legacy/src/legacy-json-all/README.md similarity index 100% rename from packages/core/src/generators/legacy-json-all/README.md rename to packages/legacy/src/legacy-json-all/README.md diff --git a/packages/core/src/generators/legacy-json-all/generate.mjs b/packages/legacy/src/legacy-json-all/generate.mjs similarity index 93% rename from packages/core/src/generators/legacy-json-all/generate.mjs rename to packages/legacy/src/legacy-json-all/generate.mjs index bc04f9aa..67b0be2a 100644 --- a/packages/core/src/generators/legacy-json-all/generate.mjs +++ b/packages/legacy/src/legacy-json-all/generate.mjs @@ -3,8 +3,9 @@ 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 '@nodejs/doc-kit/utils/configuration/index.mjs'; + +import { legacyToJSON } from '../utils/legacyToJSON.mjs'; /** * Generates the legacy JSON `all.json` file. diff --git a/packages/core/src/generators/legacy-json-all/index.mjs b/packages/legacy/src/legacy-json-all/index.mjs similarity index 88% rename from packages/core/src/generators/legacy-json-all/index.mjs rename to packages/legacy/src/legacy-json-all/index.mjs index 38af8498..e92066dd 100644 --- a/packages/core/src/generators/legacy-json-all/index.mjs +++ b/packages/legacy/src/legacy-json-all/index.mjs @@ -14,7 +14,7 @@ export default { description: 'Generates the `all.json` file from the `legacy-json` generator, which includes all the modules in one single file.', - dependsOn: '@node-core/doc-kit/legacy-json', + dependsOn: '@nodejs/doc-kit-generator-legacy/legacy-json', defaultConfiguration: { minify: false, diff --git a/packages/core/src/generators/legacy-json-all/types.d.ts b/packages/legacy/src/legacy-json-all/types.d.ts similarity index 100% rename from packages/core/src/generators/legacy-json-all/types.d.ts rename to packages/legacy/src/legacy-json-all/types.d.ts diff --git a/packages/core/src/generators/legacy-json/README.md b/packages/legacy/src/legacy-json/README.md similarity index 100% rename from packages/core/src/generators/legacy-json/README.md rename to packages/legacy/src/legacy-json/README.md diff --git a/packages/core/src/generators/legacy-json/constants.mjs b/packages/legacy/src/legacy-json/constants.mjs similarity index 100% rename from packages/core/src/generators/legacy-json/constants.mjs rename to packages/legacy/src/legacy-json/constants.mjs diff --git a/packages/core/src/generators/legacy-json/generate.mjs b/packages/legacy/src/legacy-json/generate.mjs similarity index 87% rename from packages/core/src/generators/legacy-json/generate.mjs rename to packages/legacy/src/legacy-json/generate.mjs index b19f2e5d..346a98a6 100644 --- a/packages/core/src/generators/legacy-json/generate.mjs +++ b/packages/legacy/src/legacy-json/generate.mjs @@ -2,10 +2,12 @@ import { join } from 'node:path'; +import getConfig from '@nodejs/doc-kit/utils/configuration/index.mjs'; +import { writeFile, withExt } from '@nodejs/doc-kit/utils/file.mjs'; +import { groupNodesByModule } from '@nodejs/doc-kit/utils/generators.mjs'; + import { createSectionBuilder } from './utils/buildSection.mjs'; -import getConfig from '../../utils/configuration/index.mjs'; -import { writeFile, withExt } from '../../utils/file.mjs'; -import { groupNodesByModule, legacyToJSON } from '../../utils/generators.mjs'; +import { legacyToJSON } from '../utils/legacyToJSON.mjs'; const buildSection = createSectionBuilder(); diff --git a/packages/core/src/generators/legacy-json/index.mjs b/packages/legacy/src/legacy-json/index.mjs similarity index 94% rename from packages/core/src/generators/legacy-json/index.mjs rename to packages/legacy/src/legacy-json/index.mjs index fdb71717..5e55f511 100644 --- a/packages/core/src/generators/legacy-json/index.mjs +++ b/packages/legacy/src/legacy-json/index.mjs @@ -18,7 +18,7 @@ export default { description: 'Generates the legacy version of the JSON API docs.', - dependsOn: '@node-core/doc-kit/metadata', + dependsOn: '@nodejs/doc-kit/metadata', defaultConfiguration: { ref: 'main', diff --git a/packages/core/src/generators/legacy-json/types.d.ts b/packages/legacy/src/legacy-json/types.d.ts similarity index 96% rename from packages/core/src/generators/legacy-json/types.d.ts rename to packages/legacy/src/legacy-json/types.d.ts index 058ffc65..e394f863 100644 --- a/packages/core/src/generators/legacy-json/types.d.ts +++ b/packages/legacy/src/legacy-json/types.d.ts @@ -1,6 +1,6 @@ import { ListItem } from '@types/mdast'; -import { MetadataEntry } from '../metadata/types'; -import { MethodSignature } from '../../utils/signature/types'; +import { MetadataEntry } from '@nodejs/doc-kit/generators/metadata/types'; +import { MethodSignature } from '@nodejs/doc-kit/utils/signature/types'; /** * A node in the entry hierarchy. diff --git a/packages/core/src/generators/legacy-json/utils/__tests__/buildHierarchy.test.mjs b/packages/legacy/src/legacy-json/utils/__tests__/buildHierarchy.test.mjs similarity index 100% rename from packages/core/src/generators/legacy-json/utils/__tests__/buildHierarchy.test.mjs rename to packages/legacy/src/legacy-json/utils/__tests__/buildHierarchy.test.mjs diff --git a/packages/core/src/generators/legacy-json/utils/__tests__/buildSection.test.mjs b/packages/legacy/src/legacy-json/utils/__tests__/buildSection.test.mjs similarity index 100% rename from packages/core/src/generators/legacy-json/utils/__tests__/buildSection.test.mjs rename to packages/legacy/src/legacy-json/utils/__tests__/buildSection.test.mjs diff --git a/packages/core/src/generators/legacy-json/utils/buildHierarchy.mjs b/packages/legacy/src/legacy-json/utils/buildHierarchy.mjs similarity index 91% rename from packages/core/src/generators/legacy-json/utils/buildHierarchy.mjs rename to packages/legacy/src/legacy-json/utils/buildHierarchy.mjs index 1b17edec..e8acfc29 100644 --- a/packages/core/src/generators/legacy-json/utils/buildHierarchy.mjs +++ b/packages/legacy/src/legacy-json/utils/buildHierarchy.mjs @@ -1,7 +1,7 @@ /** * Recursively finds the most suitable parent node for a given `entry` based on heading depth. * - * @param {import('../../metadata/types').MetadataEntry} entry + * @param {import('@nodejs/doc-kit/generators/metadata/types').MetadataEntry} entry * @param {Array} nodes * @param {number} startIdx * @returns {import('../types.d.ts').HierarchizedEntry} @@ -39,7 +39,7 @@ export function findParent(entry, nodes, startIdx) { * found by looping through entries in reverse starting at the current * index - 1. * - * @param {Array} entries + * @param {Array} entries * @returns {Array} */ export function buildHierarchy(entries) { diff --git a/packages/core/src/generators/legacy-json/utils/buildSection.mjs b/packages/legacy/src/legacy-json/utils/buildSection.mjs similarity index 83% rename from packages/core/src/generators/legacy-json/utils/buildSection.mjs rename to packages/legacy/src/legacy-json/utils/buildSection.mjs index 41b2d222..e92f623a 100644 --- a/packages/core/src/generators/legacy-json/utils/buildSection.mjs +++ b/packages/legacy/src/legacy-json/utils/buildSection.mjs @@ -1,8 +1,9 @@ +import { enforceArray } from '@nodejs/doc-kit/utils/array.mjs'; +import { getRemarkRehype as remark } from '@nodejs/doc-kit/utils/remark.mjs'; +import { parseList } from '@nodejs/doc-kit/utils/signature/parseList.mjs'; +import { transformNodesToString } from '@nodejs/doc-kit/utils/unist.mjs'; + import { buildHierarchy } from './buildHierarchy.mjs'; -import { enforceArray } from '../../../utils/array.mjs'; -import { getRemarkRehype as remark } from '../../../utils/remark.mjs'; -import { parseList } from '../../../utils/signature/parseList.mjs'; -import { transformNodesToString } from '../../../utils/unist.mjs'; import { SECTION_TYPE_PLURALS, UNPROMOTED_KEYS } from '../constants.mjs'; /** @@ -33,7 +34,7 @@ export const promoteMiscChildren = (section, parent) => { export const createSectionBuilder = () => { /** * Creates metadata from a metadata entry. - * @param {import('../../metadata/types').MetadataEntry} entry - The entry to create metadata from. + * @param {import('@nodejs/doc-kit/generators/metadata/types').MetadataEntry} 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 = ({ @@ -73,8 +74,8 @@ export const createSectionBuilder = () => { /** * Creates a section from an entry and its heading. - * @param {import('../../metadata/types').MetadataEntry} entry - The AST entry. - * @param {import('../../metadata/types').HeadingNode} head - The head node of the entry. + * @param {import('@nodejs/doc-kit/generators/metadata/types').MetadataEntry} entry - The AST entry. + * @param {import('@nodejs/doc-kit/generators/metadata/types').HeadingNode} head - The head node of the entry. * @returns {import('../types.d.ts').Section} The created section. */ const createSection = (entry, head) => { @@ -98,7 +99,7 @@ export const createSectionBuilder = () => { * 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('../../metadata/types').MetadataEntry} entry - The entry providing stability information. + * @param {import('@nodejs/doc-kit/generators/metadata/types').MetadataEntry} entry - The entry providing stability information. */ const parseStability = (section, nodes, { stability, content }) => { if (stability) { @@ -135,7 +136,7 @@ export const createSectionBuilder = () => { * 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. + * @param {import('@nodejs/doc-kit/generators/metadata/types').HeadingNode} heading - The heading node of the section. */ const addAdditionalMetadata = (section, parent, heading) => { if (!section.type || section.type === 'module') { @@ -180,8 +181,8 @@ export const createSectionBuilder = () => { /** * 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. + * @param {import('@nodejs/doc-kit/generators/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) => { diff --git a/packages/legacy/src/utils/__tests__/legacyToJSON.test.mjs b/packages/legacy/src/utils/__tests__/legacyToJSON.test.mjs new file mode 100644 index 00000000..65619717 --- /dev/null +++ b/packages/legacy/src/utils/__tests__/legacyToJSON.test.mjs @@ -0,0 +1,47 @@ +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 })); + // 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/legacy/src/utils/legacyToJSON.mjs b/packages/legacy/src/utils/legacyToJSON.mjs new file mode 100644 index 00000000..2b1a1f95 --- /dev/null +++ b/packages/legacy/src/utils/legacyToJSON.mjs @@ -0,0 +1,53 @@ +'use strict'; + +/** + * Transforms an object to JSON output consistent with the JSON version. + * @param {import('../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/node/package.json b/packages/node/package.json new file mode 100644 index 00000000..3f7ec767 --- /dev/null +++ b/packages/node/package.json @@ -0,0 +1,32 @@ +{ + "name": "@node-core/doc-kit", + "type": "module", + "version": "1.4.3", + "description": "Node.js-specific generators for @nodejs/doc-kit: api-links, addon-verify, and man-page", + "repository": { + "type": "git", + "url": "git+https://github.com/nodejs/doc-kit.git", + "directory": "packages/node" + }, + "exports": { + "./addon-verify": "./src/addon-verify/index.mjs", + "./api-links": "./src/api-links/index.mjs", + "./config": "./src/config/index.mjs", + "./man-page": "./src/man-page/index.mjs", + "./package.json": "./package.json" + }, + "files": [ + "src", + "!src/**/*.test.mjs", + "!src/**/__tests__", + "CHANGELOG.md", + "LICENSE", + "README.md" + ], + "dependencies": { + "@nodejs/doc-kit": "^0.0.0", + "dedent": "^1.7.2", + "estree-util-visit": "^2.0.0", + "unist-util-visit": "^5.1.0" + } +} diff --git a/packages/core/src/generators/addon-verify/README.md b/packages/node/src/addon-verify/README.md similarity index 100% rename from packages/core/src/generators/addon-verify/README.md rename to packages/node/src/addon-verify/README.md diff --git a/packages/core/src/generators/addon-verify/constants.mjs b/packages/node/src/addon-verify/constants.mjs similarity index 100% rename from packages/core/src/generators/addon-verify/constants.mjs rename to packages/node/src/addon-verify/constants.mjs diff --git a/packages/core/src/generators/addon-verify/generate.mjs b/packages/node/src/addon-verify/generate.mjs similarity index 93% rename from packages/core/src/generators/addon-verify/generate.mjs rename to packages/node/src/addon-verify/generate.mjs index 91f9b463..f9999318 100644 --- a/packages/core/src/generators/addon-verify/generate.mjs +++ b/packages/node/src/addon-verify/generate.mjs @@ -3,6 +3,8 @@ import { mkdir } from 'node:fs/promises'; import { join } from 'node:path'; +import getConfig from '@nodejs/doc-kit/utils/configuration/index.mjs'; +import { writeFile } from '@nodejs/doc-kit/utils/file.mjs'; import { visit } from 'unist-util-visit'; import { EXTRACT_CODE_FILENAME_COMMENT } from './constants.mjs'; @@ -12,8 +14,6 @@ import { isBuildableSection, normalizeSectionName, } from './utils/section.mjs'; -import getConfig from '../../utils/configuration/index.mjs'; -import { writeFile } from '../../utils/file.mjs'; /** * Generates a file list from code blocks. diff --git a/packages/core/src/generators/addon-verify/index.mjs b/packages/node/src/addon-verify/index.mjs similarity index 91% rename from packages/core/src/generators/addon-verify/index.mjs rename to packages/node/src/addon-verify/index.mjs index 7b5515d1..4055a8a8 100644 --- a/packages/core/src/generators/addon-verify/index.mjs +++ b/packages/node/src/addon-verify/index.mjs @@ -15,7 +15,7 @@ export default { description: 'Generates a file list from code blocks extracted from `doc/api/addons.md` to facilitate C++ compilation and JavaScript runtime validations', - dependsOn: '@node-core/doc-kit/metadata', + dependsOn: '@nodejs/doc-kit/metadata', generate, }; diff --git a/packages/core/src/generators/addon-verify/types.d.ts b/packages/node/src/addon-verify/types.d.ts similarity index 59% rename from packages/core/src/generators/addon-verify/types.d.ts rename to packages/node/src/addon-verify/types.d.ts index c893d8d0..da6600c7 100644 --- a/packages/core/src/generators/addon-verify/types.d.ts +++ b/packages/node/src/addon-verify/types.d.ts @@ -1,4 +1,4 @@ -import type { MetadataEntry } from '../metadata/types'; +import type { MetadataEntry } from '@nodejs/doc-kit/generators/metadata/types'; export type Generator = GeneratorMetadata< {}, diff --git a/packages/core/src/generators/addon-verify/utils/__tests__/generateFileList.test.mjs b/packages/node/src/addon-verify/utils/__tests__/generateFileList.test.mjs similarity index 88% rename from packages/core/src/generators/addon-verify/utils/__tests__/generateFileList.test.mjs rename to packages/node/src/addon-verify/utils/__tests__/generateFileList.test.mjs index d32f1a6f..95cbf334 100644 --- a/packages/core/src/generators/addon-verify/utils/__tests__/generateFileList.test.mjs +++ b/packages/node/src/addon-verify/utils/__tests__/generateFileList.test.mjs @@ -16,6 +16,9 @@ describe('generateFileList', () => { const testFile = result.find(file => file.name === 'test.js'); assert(testFile.content.includes("'use strict';")); + // The generated file runs inside nodejs/node's `test/addons` tree, where + // the helper is only reachable via this relative path. + assert(testFile.content.includes("require('../../common')")); assert(testFile.content.includes('`./build/${common.buildType}/addon`')); assert(!testFile.content.includes("'./build/Release/addon'")); }); diff --git a/packages/core/src/generators/addon-verify/utils/__tests__/section.test.mjs b/packages/node/src/addon-verify/utils/__tests__/section.test.mjs similarity index 100% rename from packages/core/src/generators/addon-verify/utils/__tests__/section.test.mjs rename to packages/node/src/addon-verify/utils/__tests__/section.test.mjs diff --git a/packages/core/src/generators/addon-verify/utils/generateFileList.mjs b/packages/node/src/addon-verify/utils/generateFileList.mjs similarity index 100% rename from packages/core/src/generators/addon-verify/utils/generateFileList.mjs rename to packages/node/src/addon-verify/utils/generateFileList.mjs diff --git a/packages/core/src/generators/addon-verify/utils/section.mjs b/packages/node/src/addon-verify/utils/section.mjs similarity index 100% rename from packages/core/src/generators/addon-verify/utils/section.mjs rename to packages/node/src/addon-verify/utils/section.mjs diff --git a/packages/core/src/generators/api-links/README.md b/packages/node/src/api-links/README.md similarity index 100% rename from packages/core/src/generators/api-links/README.md rename to packages/node/src/api-links/README.md diff --git a/packages/core/src/generators/api-links/__tests__/fixtures.test.mjs b/packages/node/src/api-links/__tests__/fixtures.test.mjs similarity index 80% rename from packages/core/src/generators/api-links/__tests__/fixtures.test.mjs rename to packages/node/src/api-links/__tests__/fixtures.test.mjs index f3199e6d..2b80aa66 100644 --- a/packages/core/src/generators/api-links/__tests__/fixtures.test.mjs +++ b/packages/node/src/api-links/__tests__/fixtures.test.mjs @@ -1,13 +1,13 @@ import { basename, join, relative, sep } from 'node:path'; import { after, before, describe, it } from 'node:test'; +import { generate as astJsGenerate } from '@nodejs/doc-kit/generators/ast-js/generate.mjs'; +import { loadGenerator } from '@nodejs/doc-kit/generators/loader.mjs'; +import createWorkerPool from '@nodejs/doc-kit/threading/index.mjs'; +import createParallelWorker from '@nodejs/doc-kit/threading/parallel.mjs'; +import { setConfig } from '@nodejs/doc-kit/utils/configuration/index.mjs'; import { globSync } from 'tinyglobby'; -import { loadGenerator } from '../../../generators/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/generate.mjs'; import { generate as apiLinksGenerate } from '../generate.mjs'; const relativePath = relative(process.cwd(), import.meta.dirname); diff --git a/packages/core/src/generators/api-links/__tests__/fixtures.test.mjs.snapshot b/packages/node/src/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/node/src/api-links/__tests__/fixtures.test.mjs.snapshot diff --git a/packages/core/src/generators/api-links/__tests__/fixtures/buffer.js b/packages/node/src/api-links/__tests__/fixtures/buffer.js similarity index 100% rename from packages/core/src/generators/api-links/__tests__/fixtures/buffer.js rename to packages/node/src/api-links/__tests__/fixtures/buffer.js diff --git a/packages/core/src/generators/api-links/__tests__/fixtures/class.js b/packages/node/src/api-links/__tests__/fixtures/class.js similarity index 100% rename from packages/core/src/generators/api-links/__tests__/fixtures/class.js rename to packages/node/src/api-links/__tests__/fixtures/class.js diff --git a/packages/core/src/generators/api-links/__tests__/fixtures/exports.js b/packages/node/src/api-links/__tests__/fixtures/exports.js similarity index 100% rename from packages/core/src/generators/api-links/__tests__/fixtures/exports.js rename to packages/node/src/api-links/__tests__/fixtures/exports.js diff --git a/packages/core/src/generators/api-links/__tests__/fixtures/mod.js b/packages/node/src/api-links/__tests__/fixtures/mod.js similarity index 100% rename from packages/core/src/generators/api-links/__tests__/fixtures/mod.js rename to packages/node/src/api-links/__tests__/fixtures/mod.js diff --git a/packages/core/src/generators/api-links/__tests__/fixtures/prototype.js b/packages/node/src/api-links/__tests__/fixtures/prototype.js similarity index 100% rename from packages/core/src/generators/api-links/__tests__/fixtures/prototype.js rename to packages/node/src/api-links/__tests__/fixtures/prototype.js diff --git a/packages/core/src/generators/api-links/__tests__/fixtures/reverse.js b/packages/node/src/api-links/__tests__/fixtures/reverse.js similarity index 100% rename from packages/core/src/generators/api-links/__tests__/fixtures/reverse.js rename to packages/node/src/api-links/__tests__/fixtures/reverse.js diff --git a/packages/core/src/generators/api-links/__tests__/fixtures/root.js b/packages/node/src/api-links/__tests__/fixtures/root.js similarity index 100% rename from packages/core/src/generators/api-links/__tests__/fixtures/root.js rename to packages/node/src/api-links/__tests__/fixtures/root.js diff --git a/packages/core/src/generators/api-links/constants.mjs b/packages/node/src/api-links/constants.mjs similarity index 100% rename from packages/core/src/generators/api-links/constants.mjs rename to packages/node/src/api-links/constants.mjs diff --git a/packages/core/src/generators/api-links/generate.mjs b/packages/node/src/api-links/generate.mjs similarity index 88% rename from packages/core/src/generators/api-links/generate.mjs rename to packages/node/src/api-links/generate.mjs index 3890a308..6b141d68 100644 --- a/packages/core/src/generators/api-links/generate.mjs +++ b/packages/node/src/api-links/generate.mjs @@ -2,12 +2,13 @@ import { basename, join } from 'node:path'; +import getConfig from '@nodejs/doc-kit/utils/configuration/index.mjs'; +import { populate } from '@nodejs/doc-kit/utils/configuration/templates.mjs'; +import { withExt, writeFile } from '@nodejs/doc-kit/utils/file.mjs'; + 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 { populate } from '../../utils/configuration/templates.mjs'; -import { withExt, writeFile } from '../../utils/file.mjs'; /** * Generates the `apilinks.json` file. diff --git a/packages/core/src/generators/api-links/index.mjs b/packages/node/src/api-links/index.mjs similarity index 86% rename from packages/core/src/generators/api-links/index.mjs rename to packages/node/src/api-links/index.mjs index fb988f99..b7b8d106 100644 --- a/packages/core/src/generators/api-links/index.mjs +++ b/packages/node/src/api-links/index.mjs @@ -1,7 +1,8 @@ 'use strict'; +import { GITHUB_BLOB_URL } from '@nodejs/doc-kit/utils/configuration/templates.mjs'; + import { generate } from './generate.mjs'; -import { GITHUB_BLOB_URL } from '../../utils/configuration/templates.mjs'; /** * This generator is responsible for mapping publicly accessible functions in @@ -21,7 +22,7 @@ export default { // Unlike the rest of the generators, this utilizes Javascript sources being // passed into the input field rather than Markdown. - dependsOn: '@node-core/doc-kit/ast-js', + dependsOn: '@nodejs/doc-kit/ast-js', defaultConfiguration: { sourceURL: `${GITHUB_BLOB_URL}lib/{fileName}`, diff --git a/packages/core/src/generators/api-links/types.d.ts b/packages/node/src/api-links/types.d.ts similarity index 100% rename from packages/core/src/generators/api-links/types.d.ts rename to packages/node/src/api-links/types.d.ts diff --git a/packages/core/src/generators/api-links/utils/checkIndirectReferences.mjs b/packages/node/src/api-links/utils/checkIndirectReferences.mjs similarity index 100% rename from packages/core/src/generators/api-links/utils/checkIndirectReferences.mjs rename to packages/node/src/api-links/utils/checkIndirectReferences.mjs diff --git a/packages/core/src/generators/api-links/utils/extractExports.mjs b/packages/node/src/api-links/utils/extractExports.mjs similarity index 100% rename from packages/core/src/generators/api-links/utils/extractExports.mjs rename to packages/node/src/api-links/utils/extractExports.mjs diff --git a/packages/core/src/generators/api-links/utils/findDefinitions.mjs b/packages/node/src/api-links/utils/findDefinitions.mjs similarity index 100% rename from packages/core/src/generators/api-links/utils/findDefinitions.mjs rename to packages/node/src/api-links/utils/findDefinitions.mjs diff --git a/packages/node/src/config/__tests__/index.test.mjs b/packages/node/src/config/__tests__/index.test.mjs new file mode 100644 index 00000000..8b5875fb --- /dev/null +++ b/packages/node/src/config/__tests__/index.test.mjs @@ -0,0 +1,25 @@ +import assert from 'node:assert/strict'; +import { existsSync } from 'node:fs'; +import { describe, it } from 'node:test'; + +import preset from '../index.mjs'; + +describe('Node.js preset', () => { + it('should brand the generators for nodejs.org', () => { + assert.equal(preset.global.project, 'Node.js'); + assert.equal(preset.global.repository, 'nodejs/node'); + assert.equal(preset.global.baseURL, 'https://nodejs.org/docs'); + assert.match(preset.global.changelog, /nodejs\/node/); + + assert.equal(preset.html.remoteConfigUrl, 'https://nodejs.org/site.json'); + assert.equal( + preset.html.imports['#theme/Logo'], + '@node-core/ui-components/Common/NodejsLogo' + ); + assert.ok(preset.html.editURL.includes('/doc/api{path}.md')); + }); + + it('should ship the Node.js llms.txt template', () => { + assert.ok(existsSync(preset['llms-txt'].templatePath)); + }); +}); diff --git a/packages/node/src/config/index.mjs b/packages/node/src/config/index.mjs new file mode 100644 index 00000000..faf9598d --- /dev/null +++ b/packages/node/src/config/index.mjs @@ -0,0 +1,84 @@ +'use strict'; + +import { join } from 'node:path'; + +import { + CHANGELOG_URL, + GITHUB_EDIT_URL, + populate, +} from '@nodejs/doc-kit/utils/configuration/templates.mjs'; + +const NODE_REPOSITORY = { repository: 'nodejs/node', ref: 'HEAD' }; + +/** + * The Node.js preset: configures the project-neutral doc-kit generators the + * way nodejs.org builds its API documentation — branding, URL layouts, and + * release history included. + * + * Use it from a configuration file: + * + * ```mjs + * export default { + * extends: '@node-core/doc-kit/config', + * }; + * ``` + * + * @type {Partial} + */ +export default { + global: { + project: 'Node.js', + ...NODE_REPOSITORY, + baseURL: 'https://nodejs.org/docs', + changelog: populate(CHANGELOG_URL, NODE_REPOSITORY), + }, + + html: { + editURL: `${GITHUB_EDIT_URL}/doc/api{path}.md`, + pageURL: '{baseURL}/latest-{version}/api{path}.html', + remoteConfigUrl: 'https://nodejs.org/site.json', + + head: { + meta: [ + { + name: 'description', + content: + 'Node.js® is a free, open-source, cross-platform JavaScript ' + + 'runtime environment that lets developers create servers, web ' + + 'apps, command line tools and scripts.', + }, + { + property: 'og:description', + content: + 'Node.js® is a free, open-source, cross-platform JavaScript ' + + 'runtime environment that lets developers create servers, web ' + + 'apps, command line tools and scripts.', + }, + { + property: 'og:image', + content: + 'https://nodejs.org/en/next-data/og/announcement/Node.js%20%E2%80%94%20Run%20JavaScript%20Everywhere', + }, + ], + links: [ + { + rel: 'icon', + href: 'https://nodejs.org/static/images/favicons/favicon.png', + }, + ], + }, + + imports: { + '#theme/Logo': '@node-core/ui-components/Common/NodejsLogo', + }, + }, + + 'llms-txt': { + templatePath: join(import.meta.dirname, 'llms-template.txt'), + pageURL: '{baseURL}/latest/api{path}.md', + }, + + sitemap: { + indexURL: '{baseURL}/latest/api/', + }, +}; diff --git a/packages/core/src/generators/llms-txt/template.txt b/packages/node/src/config/llms-template.txt similarity index 100% rename from packages/core/src/generators/llms-txt/template.txt rename to packages/node/src/config/llms-template.txt diff --git a/packages/core/src/generators/man-page/README.md b/packages/node/src/man-page/README.md similarity index 100% rename from packages/core/src/generators/man-page/README.md rename to packages/node/src/man-page/README.md diff --git a/packages/core/src/generators/man-page/generate.mjs b/packages/node/src/man-page/generate.mjs similarity index 83% rename from packages/core/src/generators/man-page/generate.mjs rename to packages/node/src/man-page/generate.mjs index 4f779f71..b9bd41d7 100644 --- a/packages/core/src/generators/man-page/generate.mjs +++ b/packages/node/src/man-page/generate.mjs @@ -3,18 +3,19 @@ import { readFile } from 'node:fs/promises'; import { join } from 'node:path'; +import getConfig from '@nodejs/doc-kit/utils/configuration/index.mjs'; +import { writeFile } from '@nodejs/doc-kit/utils/file.mjs'; + import { convertOptionToMandoc, convertEnvVarToMandoc, } from './utils/converter.mjs'; -import getConfig from '../../utils/configuration/index.mjs'; -import { writeFile } from '../../utils/file.mjs'; /** - * @param {Array} components + * @param {Array} components * @param {number} start * @param {number} end - * @param {(element: import('../metadata/types').MetadataEntry) => string} convert + * @param {(element: import('@nodejs/doc-kit/generators/metadata/types').MetadataEntry) => string} convert * @returns {string} */ function extractMandoc(components, start, end, convert) { diff --git a/packages/core/src/generators/man-page/index.mjs b/packages/node/src/man-page/index.mjs similarity index 92% rename from packages/core/src/generators/man-page/index.mjs rename to packages/node/src/man-page/index.mjs index 6b3d035c..771db4ee 100644 --- a/packages/core/src/generators/man-page/index.mjs +++ b/packages/node/src/man-page/index.mjs @@ -15,7 +15,7 @@ export default { description: 'Generates the Node.js man-page.', - dependsOn: '@node-core/doc-kit/metadata', + dependsOn: '@nodejs/doc-kit/metadata', defaultConfiguration: { fileName: 'node.1', diff --git a/packages/core/src/generators/man-page/template.1 b/packages/node/src/man-page/template.1 similarity index 95% rename from packages/core/src/generators/man-page/template.1 rename to packages/node/src/man-page/template.1 index 9da71f53..9d2cf658 100644 --- a/packages/core/src/generators/man-page/template.1 +++ b/packages/node/src/man-page/template.1 @@ -1,5 +1,5 @@ .\" -.\" This file was generated automatically by the @node-core/doc-kit tool. +.\" This file was generated automatically by the @nodejs/doc-kit tool. .\" Please do not edit this file manually. Make any updates to cli.md .\" and regenerate the file afterward. .\" diff --git a/packages/core/src/generators/man-page/types.d.ts b/packages/node/src/man-page/types.d.ts similarity index 74% rename from packages/core/src/generators/man-page/types.d.ts rename to packages/node/src/man-page/types.d.ts index 3bab3f6f..dfd92af9 100644 --- a/packages/core/src/generators/man-page/types.d.ts +++ b/packages/node/src/man-page/types.d.ts @@ -1,4 +1,4 @@ -import { MetadataEntry } from '../metadata/types'; +import { MetadataEntry } from '@nodejs/doc-kit/generators/metadata/types'; export type Generator = GeneratorMetadata< { diff --git a/packages/core/src/generators/man-page/utils/__tests__/converter.test.mjs b/packages/node/src/man-page/utils/__tests__/converter.test.mjs similarity index 100% rename from packages/core/src/generators/man-page/utils/__tests__/converter.test.mjs rename to packages/node/src/man-page/utils/__tests__/converter.test.mjs diff --git a/packages/core/src/generators/man-page/utils/converter.mjs b/packages/node/src/man-page/utils/converter.mjs similarity index 95% rename from packages/core/src/generators/man-page/utils/converter.mjs rename to packages/node/src/man-page/utils/converter.mjs index fe496285..9def57d4 100644 --- a/packages/core/src/generators/man-page/utils/converter.mjs +++ b/packages/node/src/man-page/utils/converter.mjs @@ -126,7 +126,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('@nodejs/doc-kit/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) { @@ -150,7 +150,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('@nodejs/doc-kit/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/react/package.json b/packages/react/package.json new file mode 100644 index 00000000..68ccf8dc --- /dev/null +++ b/packages/react/package.json @@ -0,0 +1,57 @@ +{ + "name": "@nodejs/doc-kit-generator-react", + "type": "module", + "version": "0.0.0", + "description": "React/JSX-based generators for @nodejs/doc-kit: html, jsx-ast, llms-txt, sitemap, and orama-db", + "repository": { + "type": "git", + "url": "git+https://github.com/nodejs/doc-kit.git", + "directory": "packages/react" + }, + "exports": { + "./html": "./src/html/index.mjs", + "./html/bundlers/vite": "./src/html/bundlers/vite.mjs", + "./jsx-ast": "./src/jsx-ast/index.mjs", + "./llms-txt": "./src/llms-txt/index.mjs", + "./orama-db": "./src/orama-db/index.mjs", + "./sitemap": "./src/sitemap/index.mjs", + "./package.json": "./package.json" + }, + "files": [ + "src", + "!src/**/*.test.mjs", + "!src/**/__tests__", + "CHANGELOG.md", + "LICENSE", + "README.md" + ], + "dependencies": { + "@11ty/is-land": "^5.0.1", + "@fontsource-variable/open-sans": "^5.3.0", + "@fontsource/ibm-plex-mono": "^5.3.0", + "@heroicons/react": "^2.2.0", + "@nodejs/doc-kit": "^0.0.0", + "@node-core/rehype-shiki": "^1.4.3", + "@node-core/ui-components": "^1.7.4", + "@orama/orama": "^3.1.18", + "@orama/ui": "^1.5.4", + "estree-util-to-js": "^2.0.0", + "hast-util-to-string": "^3.0.1", + "hastscript": "^9.0.1", + "mdast-util-slice-markdown": "^2.0.1", + "preact": "^10.29.7", + "preact-render-to-string": "^6.7.0", + "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", + "remark-parse": "^11.0.0", + "remark-rehype": "^11.1.2", + "semver": "^7.8.5", + "unified": "^11.0.5", + "unist-builder": "^4.0.0", + "unist-util-visit": "^5.1.0", + "vite": "~8.1.5" + } +} diff --git a/packages/core/src/generators/web/README.md b/packages/react/src/html/README.md similarity index 95% rename from packages/core/src/generators/web/README.md rename to packages/react/src/html/README.md index c86c521b..7f875f7a 100644 --- a/packages/core/src/generators/web/README.md +++ b/packages/react/src/html/README.md @@ -1,6 +1,6 @@ -# `web` Generator +# `html` Generator -The `web` generator transforms JSX AST entries into complete web bundles. Its +The `html` generator transforms JSX AST entries into complete web bundles. Its bundler adapter builds server-rendered HTML and client-side JavaScript, CSS, and imported assets, then writes the complete static site to `output`. Vite is the default adapter, but projects can supply an adapter for webpack or another @@ -64,7 +64,7 @@ for any project: ```js // doc-kit.config.mjs export default { - web: { + html: { head: { meta: [ { name: 'description', content: 'My project documentation' }, @@ -97,7 +97,7 @@ are optional; omit either one to keep that component's default. Sidebar items are `{ label, link }` and may nest through an `items` array of their own. A `label` is plain text, except that backticked spans render as `` (``'`fs`'``), matching how page headings are rendered. A `link` is a -page path without its extension (`/fs`, `/generators/web`): it is resolved +page path without its extension (`/fs`, `/generators/html`): it is resolved against the page being rendered, so it obeys `useAbsoluteURLs` and highlights while it is the current page. Links starting with `http://` or `https://` are used as authored. @@ -109,7 +109,7 @@ tab and mark them with an external-link icon. ```js // doc-kit.config.mjs export default { - web: { + html: { navigation: { sidebar: [ { @@ -149,7 +149,7 @@ for a particular build system. Both `render` and `build` receive `{ entries, virtualImports, config }`; `build` also receives `pages`. Entry maps use `${api}.jsx` keys, rendered server results use `api` keys, and page maps use output-relative HTML file names. `config` is -the resolved web configuration. +the resolved `html` configuration. The adapter must compile the generated Preact JSX and CSS imports and resolve the supplied theme aliases and virtual modules. The generated `#theme/config` @@ -181,7 +181,7 @@ export const createWebpackBundler = webpackOptions => ({ import { createWebpackBundler } from './webpack-bundler.mjs'; export default { - web: { + html: { bundler: createWebpackBundler({ // Project-owned webpack configuration. }), @@ -197,11 +197,11 @@ directly and pass Vite's `UserConfig` to it: ```js // doc-kit.config.mjs -import { createViteBundler } from '@node-core/doc-kit/src/generators/web/bundlers/vite.mjs'; +import { createViteBundler } from '@nodejs/doc-kit-generator-react/html/bundlers/vite'; import myVitePlugin from './my-vite-plugin.mjs'; export default { - web: { + html: { bundler: createViteBundler({ plugins: [myVitePlugin()], define: { @@ -237,7 +237,7 @@ name to `createViteBundler` when another tool needs one. The generated HTML already references the correct hashed scripts, stylesheets, imported assets, and module preloads. -Function-valued plugins and hooks are supported because the `web` generator +Function-valued plugins and hooks are supported because the `html` generator runs on the main thread and does not serialize the bundler to a worker. ### Default `imports` @@ -260,7 +260,7 @@ Override any alias in your config file to swap in a custom component: ```js // doc-kit.config.mjs export default { - web: { + html: { imports: { '#theme/Logo': './src/MyLogo.jsx', '#theme/Sidebar': './src/MySidebar.jsx', @@ -282,7 +282,7 @@ real module path: ```js // doc-kit.config.mjs export default { - web: { + html: { components: { // Shorthand — equivalent to { name: 'Hero', source: '#theme/Hero' } Hero: '#theme/Hero', @@ -321,7 +321,7 @@ There are {stats.length} APIs documented. ## `#theme/config` virtual module -The `web` generator provides a `#theme/config` virtual module that exposes pre-computed configuration as named exports. Any component (including custom overrides) can import the values it needs, and tree-shaking removes the rest. +The `html` generator provides a `#theme/config` virtual module that exposes pre-computed configuration as named exports. Any component (including custom overrides) can import the values it needs, and tree-shaking removes the rest. ```js import { project, repository, editURL } from '#theme/config'; @@ -397,7 +397,7 @@ The HTML template file (set via `templatePath`) uses JavaScript template literal - `themeScript` {string} Inline script that applies the saved theme before paint. - `root` {string} Relative or absolute path to the site root. - `metadata` {Object} Full page metadata (frontmatter, path, heading, etc.). -- `config` {Object} The resolved web generator configuration. +- `config` {Object} The resolved `html` generator configuration. - `head` {string} Pre-rendered ``/``/raw markup from the `head` config. diff --git a/packages/core/src/generators/web/__tests__/generate.test.mjs b/packages/react/src/html/__tests__/generate.test.mjs similarity index 93% rename from packages/core/src/generators/web/__tests__/generate.test.mjs rename to packages/react/src/html/__tests__/generate.test.mjs index bb052f99..ac036ee1 100644 --- a/packages/core/src/generators/web/__tests__/generate.test.mjs +++ b/packages/react/src/html/__tests__/generate.test.mjs @@ -4,9 +4,9 @@ import { tmpdir } from 'node:os'; import { dirname, join } from 'node:path'; import { describe, it } from 'node:test'; +import { setConfig } from '@nodejs/doc-kit/utils/configuration/index.mjs'; import { jsx, toJs } from 'estree-util-to-js'; -import { setConfig } from '../../../utils/configuration/index.mjs'; import buildContent from '../../jsx-ast/utils/buildContent.mjs'; import { buildNotFoundPage } from '../../jsx-ast/utils/synthetic/404.mjs'; import { createViteBundler } from '../bundlers/vite.mjs'; @@ -53,12 +53,12 @@ const createTestConfiguration = async context => { context.after(() => rm(output, { recursive: true, force: true })); const config = await setConfig({ - target: ['web'], + target: ['html'], output, version: 'v22.0.0', changelog: [], generators: { - web: {}, + html: {}, }, }); @@ -93,7 +93,7 @@ describe('web generate', () => { it('renders the configurable head without hardcoded defaults', async context => { const { config, output } = await createTestConfiguration(context); - config.web.head = { + config.html.head = { meta: [ { name: 'description', content: 'Custom project docs' }, { property: 'og:image', content: 'https://example.com/og.png' }, @@ -116,8 +116,8 @@ describe('web generate', () => { it('uses Vite base URLs for absolute client assets', async context => { const { config, output } = await createTestConfiguration(context); - config.web.useAbsoluteURLs = true; - config.web.baseURL = 'https://example.com/docs'; + config.html.useAbsoluteURLs = true; + config.html.baseURL = 'https://example.com/docs'; const notFoundPage = buildNotFoundPage(); const content = await buildContent(notFoundPage.entries, notFoundPage.head); @@ -130,7 +130,7 @@ describe('web generate', () => { it('applies configured Vite plugins', async context => { const { config, output } = await createTestConfiguration(context); - config.web.bundler = createViteBundler({ + config.html.bundler = createViteBundler({ plugins: [ { name: 'test-html-transform', @@ -158,7 +158,7 @@ describe('web generate', () => { const { config, output } = await createTestConfiguration(context); const calls = []; - config.web.bundler = { + config.html.bundler = { getEntryId(api) { calls.push(`entry:${api}`); return `/custom/${api}.js`; @@ -166,7 +166,7 @@ describe('web generate', () => { async render({ entries, virtualImports, config: receivedConfig }) { calls.push('server'); - assert.strictEqual(receivedConfig, config.web); + assert.strictEqual(receivedConfig, config.html); assert.ok(entries.has('fs.jsx')); assert.match(virtualImports['#theme/config'], /export const pages/); assert.match( @@ -181,7 +181,7 @@ describe('web generate', () => { async build({ entries, virtualImports, pages, config: receivedConfig }) { calls.push('client'); - assert.strictEqual(receivedConfig, config.web); + assert.strictEqual(receivedConfig, config.html); assert.ok(entries.has('fs.jsx')); assert.match( virtualImports['#theme/config'], diff --git a/packages/core/src/generators/web/bundlers/__tests__/vite.test.mjs b/packages/react/src/html/bundlers/__tests__/vite.test.mjs similarity index 94% rename from packages/core/src/generators/web/bundlers/__tests__/vite.test.mjs rename to packages/react/src/html/bundlers/__tests__/vite.test.mjs index 7ff724c6..3017f431 100644 --- a/packages/core/src/generators/web/bundlers/__tests__/vite.test.mjs +++ b/packages/react/src/html/bundlers/__tests__/vite.test.mjs @@ -7,7 +7,8 @@ import { describe, it } from 'node:test'; import { default as getConfig, setConfig, -} from '../../../../utils/configuration/index.mjs'; +} from '@nodejs/doc-kit/utils/configuration/index.mjs'; + import { createVirtualModulesPlugin, createViteConfig, @@ -17,12 +18,12 @@ import { const output = join(tmpdir(), 'doc-kit-vite-test-output'); await setConfig({ - target: ['web'], + target: ['html'], output, version: 'v22.0.0', changelog: [], generators: { - web: {}, + html: {}, }, }); @@ -66,7 +67,7 @@ describe('Vite configuration', () => { sources: new Map(), input, server: false, - config: getConfig('web'), + config: getConfig('html'), vite, }); @@ -97,7 +98,7 @@ describe('Vite configuration', () => { input, server: true, serverOutDir: serverOutput, - config: getConfig('web'), + config: getConfig('html'), vite, }); @@ -125,7 +126,7 @@ describe('Vite SSR temporary output', () => { ], ]), virtualImports: {}, - config: getConfig('web'), + config: getConfig('html'), vite: {}, createTemporaryDirectory: async () => temporaryDirectory, }), diff --git a/packages/core/src/generators/web/bundlers/index.mjs b/packages/react/src/html/bundlers/index.mjs similarity index 100% rename from packages/core/src/generators/web/bundlers/index.mjs rename to packages/react/src/html/bundlers/index.mjs diff --git a/packages/core/src/generators/web/bundlers/vite.mjs b/packages/react/src/html/bundlers/vite.mjs similarity index 99% rename from packages/core/src/generators/web/bundlers/vite.mjs rename to packages/react/src/html/bundlers/vite.mjs index f6edcf23..83906eb1 100644 --- a/packages/core/src/generators/web/bundlers/vite.mjs +++ b/packages/react/src/html/bundlers/vite.mjs @@ -3,6 +3,7 @@ import { tmpdir } from 'node:os'; import { basename, isAbsolute, join, resolve } from 'node:path'; import { pathToFileURL } from 'node:url'; +import { minifyHTML } from '@nodejs/doc-kit/utils/html-minifier.mjs'; import { build as viteBuild, defaultClientConditions, @@ -10,8 +11,6 @@ import { mergeConfig, } from 'vite'; -import { minifyHTML } from '../../../utils/html-minifier.mjs'; - const VIRTUAL_PREFIX = 'virtual:doc-kit/'; const RESOLVED_VIRTUAL_PREFIX = '\0doc-kit:'; diff --git a/packages/core/src/generators/web/constants.mjs b/packages/react/src/html/constants.mjs similarity index 100% rename from packages/core/src/generators/web/constants.mjs rename to packages/react/src/html/constants.mjs diff --git a/packages/core/src/generators/web/generate.mjs b/packages/react/src/html/generate.mjs similarity index 93% rename from packages/core/src/generators/web/generate.mjs rename to packages/react/src/html/generate.mjs index 6903f67d..4e5c7cac 100644 --- a/packages/core/src/generators/web/generate.mjs +++ b/packages/react/src/html/generate.mjs @@ -2,9 +2,10 @@ import { readFile } from 'node:fs/promises'; +import getConfig from '@nodejs/doc-kit/utils/configuration/index.mjs'; + import { copyStaticAssets } from './utils/copying.mjs'; import { createCodeConverter, processBundles } from './utils/processing.mjs'; -import getConfig from '../../utils/configuration/index.mjs'; /** * Main generation function that sends per-page JSX code to the web bundler. @@ -17,7 +18,7 @@ import getConfig from '../../utils/configuration/index.mjs'; * @type {import('./types').Generator['generate']} */ export async function generate(input) { - const config = getConfig('web'); + const config = getConfig('html'); const template = await readFile(config.templatePath, 'utf-8'); diff --git a/packages/core/src/generators/web/index.mjs b/packages/react/src/html/index.mjs similarity index 65% rename from packages/core/src/generators/web/index.mjs rename to packages/react/src/html/index.mjs index 69cfaeea..c19a8a4b 100644 --- a/packages/core/src/generators/web/index.mjs +++ b/packages/react/src/html/index.mjs @@ -3,7 +3,6 @@ import { join } from 'node:path'; import { generate } from './generate.mjs'; -import { GITHUB_EDIT_URL } from '../../utils/configuration/templates.mjs'; /** * Web generator - transforms JSX AST entries into complete web bundles. @@ -25,23 +24,20 @@ import { GITHUB_EDIT_URL } from '../../utils/configuration/templates.mjs'; * @type {import('./types').Generator} */ export default { - name: 'web', + name: 'html', description: 'Generates HTML/CSS/JS bundles from JSX AST entries', - dependsOn: '@node-core/doc-kit/jsx-ast', + dependsOn: '@nodejs/doc-kit-generator-react/jsx-ast', /** - * @param {import('../../utils/configuration/types').Configuration} config + * @param {import('@nodejs/doc-kit/utils/configuration/types').Configuration} config */ defaultConfiguration: config => ({ templatePath: join(import.meta.dirname, 'template.html'), - project: 'Node.js', title: '{project} {version} Documentation', useAbsoluteURLs: false, - editURL: `${GITHUB_EDIT_URL}/doc/api{path}.md`, - pageURL: '{baseURL}/latest-{version}/api{path}.html', - remoteConfigUrl: 'https://nodejs.org/site.json', + pageURL: '{baseURL}{path}.html', // By default, the search box is only shown when we are _also_ building // search data. `target` holds resolved import specifiers, so match on the // subpath rather than an exact name. @@ -55,38 +51,13 @@ export default { // hatch. Structural/theme tags such as `og:type` are hardcoded in the // template instead. head: { - meta: [ - { - name: 'description', - content: - 'Node.js® is a free, open-source, cross-platform JavaScript ' + - 'runtime environment that lets developers create servers, web ' + - 'apps, command line tools and scripts.', - }, - { - property: 'og:description', - content: - 'Node.js® is a free, open-source, cross-platform JavaScript ' + - 'runtime environment that lets developers create servers, web ' + - 'apps, command line tools and scripts.', - }, - { - property: 'og:image', - content: - 'https://nodejs.org/en/next-data/og/announcement/Node.js%20%E2%80%94%20Run%20JavaScript%20Everywhere', - }, - ], - links: [ - { - rel: 'icon', - href: 'https://nodejs.org/static/images/favicons/favicon.png', - }, - ], + meta: [], + links: [], html: [], }, imports: { - '#theme/Logo': '@node-core/ui-components/Common/NodejsLogo', + '#theme/Logo': join(import.meta.dirname, './ui/components/ProjectName'), '#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'), diff --git a/packages/react/src/html/template.html b/packages/react/src/html/template.html new file mode 100644 index 00000000..b702d8e5 --- /dev/null +++ b/packages/react/src/html/template.html @@ -0,0 +1,25 @@ + + + + + + ${title} + + + + ${head} + + + + + + + +
${dehydrated}
+ + + diff --git a/packages/core/src/generators/web/types.d.ts b/packages/react/src/html/types.d.ts similarity index 97% rename from packages/core/src/generators/web/types.d.ts rename to packages/react/src/html/types.d.ts index e4e53d82..b6df9d78 100644 --- a/packages/core/src/generators/web/types.d.ts +++ b/packages/react/src/html/types.d.ts @@ -1,5 +1,5 @@ import type { JSXContent } from '../jsx-ast/utils/buildContent.mjs'; -import type { GlobalConfiguration } from '../../utils/configuration/types'; +import type { GlobalConfiguration } from '@nodejs/doc-kit/utils/configuration/types'; import type SideBar from '@node-core/ui-components/Containers/Sidebar'; import type NavBar from '@node-core/ui-components/Containers/NavBar'; import type { ComponentProps } from 'preact'; diff --git a/packages/core/src/generators/web/ui/components/Banner.jsx b/packages/react/src/html/ui/components/Banner.jsx similarity index 100% rename from packages/core/src/generators/web/ui/components/Banner.jsx rename to packages/react/src/html/ui/components/Banner.jsx diff --git a/packages/core/src/generators/web/ui/components/CodeBox.jsx b/packages/react/src/html/ui/components/CodeBox.jsx similarity index 100% rename from packages/core/src/generators/web/ui/components/CodeBox.jsx rename to packages/react/src/html/ui/components/CodeBox.jsx diff --git a/packages/core/src/generators/web/ui/components/CodeTabs.jsx b/packages/react/src/html/ui/components/CodeTabs.jsx similarity index 100% rename from packages/core/src/generators/web/ui/components/CodeTabs.jsx rename to packages/react/src/html/ui/components/CodeTabs.jsx diff --git a/packages/core/src/generators/web/ui/components/Layout/index.jsx b/packages/react/src/html/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/html/ui/components/Layout/index.jsx diff --git a/packages/core/src/generators/web/ui/components/MetaBar/index.jsx b/packages/react/src/html/ui/components/MetaBar/index.jsx similarity index 95% rename from packages/core/src/generators/web/ui/components/MetaBar/index.jsx rename to packages/react/src/html/ui/components/MetaBar/index.jsx index 261498ea..5f705efd 100644 --- a/packages/core/src/generators/web/ui/components/MetaBar/index.jsx +++ b/packages/react/src/html/ui/components/MetaBar/index.jsx @@ -52,7 +52,7 @@ const HeadingValue = ({ value, stability }) => { * @param {{ metadata: import('../../types').SerializedMetadata, headings: Array, readingTime: string }} props */ export default ({ metadata, headings = [], readingTime }) => { - const editThisPage = editURL.replace('{path}', metadata.path); + const editThisPage = editURL?.replace('{path}', metadata.path); const viewAs = [ ['JSON', `${metadata.basename}.json`], @@ -88,7 +88,7 @@ export default ({ metadata, headings = [], readingTime }) => { })} ), - Contribute: !metadata.synthetic && ( + Contribute: !metadata.synthetic && editThisPage && ( <> diff --git a/packages/core/src/generators/web/ui/components/MetaBar/index.module.css b/packages/react/src/html/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/html/ui/components/MetaBar/index.module.css diff --git a/packages/core/src/generators/web/ui/components/NavBar.jsx b/packages/react/src/html/ui/components/NavBar.jsx similarity index 80% rename from packages/core/src/generators/web/ui/components/NavBar.jsx rename to packages/react/src/html/ui/components/NavBar.jsx index c190a37e..a3a77f31 100644 --- a/packages/core/src/generators/web/ui/components/NavBar.jsx +++ b/packages/react/src/html/ui/components/NavBar.jsx @@ -22,12 +22,14 @@ export default ({ metadata }) => ( > {showSearchBox && } - - - + {repository && ( + + + + )} ); diff --git a/packages/core/src/generators/web/ui/components/NoOp.jsx b/packages/react/src/html/ui/components/NoOp.jsx similarity index 100% rename from packages/core/src/generators/web/ui/components/NoOp.jsx rename to packages/react/src/html/ui/components/NoOp.jsx diff --git a/packages/react/src/html/ui/components/ProjectName.jsx b/packages/react/src/html/ui/components/ProjectName.jsx new file mode 100644 index 00000000..e91a57b2 --- /dev/null +++ b/packages/react/src/html/ui/components/ProjectName.jsx @@ -0,0 +1,7 @@ +import { project } from '#theme/config'; + +/** + * Plain-text stand-in for `#theme/Logo`, used when a project has not + * configured a logo component of its own. + */ +export default props => {project}; diff --git a/packages/core/src/generators/web/ui/components/SearchBox/index.jsx b/packages/react/src/html/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/html/ui/components/SearchBox/index.jsx diff --git a/packages/core/src/generators/web/ui/components/SearchBox/index.module.css b/packages/react/src/html/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/html/ui/components/SearchBox/index.module.css diff --git a/packages/core/src/generators/web/ui/components/SideBar/index.jsx b/packages/react/src/html/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/html/ui/components/SideBar/index.jsx diff --git a/packages/core/src/generators/web/ui/components/SideBar/index.module.css b/packages/react/src/html/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/html/ui/components/SideBar/index.module.css diff --git a/packages/core/src/generators/web/ui/components/ThemeToggle.jsx b/packages/react/src/html/ui/components/ThemeToggle.jsx similarity index 100% rename from packages/core/src/generators/web/ui/components/ThemeToggle.jsx rename to packages/react/src/html/ui/components/ThemeToggle.jsx diff --git a/packages/core/src/generators/web/ui/hooks/useBanners.mjs b/packages/react/src/html/ui/hooks/useBanners.mjs similarity index 100% rename from packages/core/src/generators/web/ui/hooks/useBanners.mjs rename to packages/react/src/html/ui/hooks/useBanners.mjs diff --git a/packages/core/src/generators/web/ui/hooks/useOrama.mjs b/packages/react/src/html/ui/hooks/useOrama.mjs similarity index 100% rename from packages/core/src/generators/web/ui/hooks/useOrama.mjs rename to packages/react/src/html/ui/hooks/useOrama.mjs diff --git a/packages/core/src/generators/web/ui/hooks/useTheme.mjs b/packages/react/src/html/ui/hooks/useTheme.mjs similarity index 100% rename from packages/core/src/generators/web/ui/hooks/useTheme.mjs rename to packages/react/src/html/ui/hooks/useTheme.mjs diff --git a/packages/core/src/generators/web/ui/index.css b/packages/react/src/html/ui/index.css similarity index 100% rename from packages/core/src/generators/web/ui/index.css rename to packages/react/src/html/ui/index.css diff --git a/packages/core/src/generators/web/ui/islands/loaders.mjs b/packages/react/src/html/ui/islands/loaders.mjs similarity index 100% rename from packages/core/src/generators/web/ui/islands/loaders.mjs rename to packages/react/src/html/ui/islands/loaders.mjs diff --git a/packages/core/src/generators/web/ui/islands/runtime.mjs b/packages/react/src/html/ui/islands/runtime.mjs similarity index 100% rename from packages/core/src/generators/web/ui/islands/runtime.mjs rename to packages/react/src/html/ui/islands/runtime.mjs diff --git a/packages/core/src/generators/web/ui/islands/withIsland.jsx b/packages/react/src/html/ui/islands/withIsland.jsx similarity index 100% rename from packages/core/src/generators/web/ui/islands/withIsland.jsx rename to packages/react/src/html/ui/islands/withIsland.jsx diff --git a/packages/core/src/generators/web/ui/package.json b/packages/react/src/html/ui/package.json similarity index 100% rename from packages/core/src/generators/web/ui/package.json rename to packages/react/src/html/ui/package.json diff --git a/packages/core/src/generators/web/ui/theme-script.mjs b/packages/react/src/html/ui/theme-script.mjs similarity index 100% rename from packages/core/src/generators/web/ui/theme-script.mjs rename to packages/react/src/html/ui/theme-script.mjs diff --git a/packages/core/src/generators/web/ui/types.d.ts b/packages/react/src/html/ui/types.d.ts similarity index 90% rename from packages/core/src/generators/web/ui/types.d.ts rename to packages/react/src/html/ui/types.d.ts index a47e6773..119a8727 100644 --- a/packages/core/src/generators/web/ui/types.d.ts +++ b/packages/react/src/html/ui/types.d.ts @@ -1,5 +1,5 @@ -import { GlobalConfiguration } from '../../../utils/configuration/types'; -import { MetadataEntry } from '../../metadata/types'; +import { GlobalConfiguration } from '@nodejs/doc-kit/utils/configuration/types'; +import { MetadataEntry } from '@nodejs/doc-kit/generators/metadata/types'; import { Configuration } from '../types'; import { SemVer } from 'semver'; diff --git a/packages/core/src/generators/web/ui/utils/relativeOrAbsolute.mjs b/packages/react/src/html/ui/utils/relativeOrAbsolute.mjs similarity index 89% rename from packages/core/src/generators/web/ui/utils/relativeOrAbsolute.mjs rename to packages/react/src/html/ui/utils/relativeOrAbsolute.mjs index e08b4876..5f8f420c 100644 --- a/packages/core/src/generators/web/ui/utils/relativeOrAbsolute.mjs +++ b/packages/react/src/html/ui/utils/relativeOrAbsolute.mjs @@ -1,4 +1,4 @@ -import { relative } from '../../../../utils/url.mjs'; +import { relative } from '@nodejs/doc-kit/utils/url.mjs'; import { useAbsoluteURLs, baseURL } from '#theme/config'; diff --git a/packages/core/src/generators/web/ui/utils/renderLabel.jsx b/packages/react/src/html/ui/utils/renderLabel.jsx similarity index 100% rename from packages/core/src/generators/web/ui/utils/renderLabel.jsx rename to packages/react/src/html/ui/utils/renderLabel.jsx diff --git a/packages/core/src/generators/web/utils/__tests__/config.test.mjs b/packages/react/src/html/utils/__tests__/config.test.mjs similarity index 98% rename from packages/core/src/generators/web/utils/__tests__/config.test.mjs rename to packages/react/src/html/utils/__tests__/config.test.mjs index 4a64149b..7def2964 100644 --- a/packages/core/src/generators/web/utils/__tests__/config.test.mjs +++ b/packages/react/src/html/utils/__tests__/config.test.mjs @@ -1,10 +1,9 @@ import assert from 'node:assert/strict'; import { describe, it, mock } from 'node:test'; +import { setConfig } from '@nodejs/doc-kit/utils/configuration/index.mjs'; import { SemVer } from 'semver'; -import { setConfig } from '../../../../utils/configuration/index.mjs'; - mock.module('@node-core/rehype-shiki', { namedExports: { LANGS: [ @@ -25,7 +24,7 @@ await setConfig({ { version: new SemVer('22.0.0'), isLts: false, isCurrent: true }, ], generators: { - web: { + html: { title: 'Node.js', repository: 'nodejs/node', ref: 'main', diff --git a/packages/core/src/generators/web/utils/__tests__/copying.test.mjs b/packages/react/src/html/utils/__tests__/copying.test.mjs similarity index 95% rename from packages/core/src/generators/web/utils/__tests__/copying.test.mjs rename to packages/react/src/html/utils/__tests__/copying.test.mjs index 385ab714..052a1559 100644 --- a/packages/core/src/generators/web/utils/__tests__/copying.test.mjs +++ b/packages/react/src/html/utils/__tests__/copying.test.mjs @@ -8,7 +8,7 @@ mock.module('node:fs/promises', { }); const mockLogError = mock.fn(); -mock.module('../../../../logger/index.mjs', { +mock.module('@nodejs/doc-kit/logger/index.mjs', { defaultExport: { error: mockLogError }, }); @@ -120,7 +120,7 @@ describe('copyStaticAssets', () => { const logMessage = mockLogError.mock.calls[0].arguments[0]; assert.match( logMessage, - /\[web-generator\] Failed to copy asset from protected-file to \/out\/protected-file: Permission denied/ + /\[html-generator\] Failed to copy asset from protected-file to \/out\/protected-file: Permission denied/ ); }); }); diff --git a/packages/core/src/generators/web/utils/__tests__/processing.test.mjs b/packages/react/src/html/utils/__tests__/processing.test.mjs similarity index 93% rename from packages/core/src/generators/web/utils/__tests__/processing.test.mjs rename to packages/react/src/html/utils/__tests__/processing.test.mjs index bd0b4c5b..7312f530 100644 --- a/packages/core/src/generators/web/utils/__tests__/processing.test.mjs +++ b/packages/react/src/html/utils/__tests__/processing.test.mjs @@ -4,7 +4,8 @@ import { describe, it } from 'node:test'; import { default as getConfig, setConfig, -} from '../../../../utils/configuration/index.mjs'; +} from '@nodejs/doc-kit/utils/configuration/index.mjs'; + import { buildHead, populateWithEvaluation, @@ -12,11 +13,11 @@ import { } from '../processing.mjs'; await setConfig({ - target: ['web'], + target: ['html'], version: 'v22.0.0', changelog: [], generators: { - web: { + html: { useAbsoluteURLs: false, baseURL: 'https://nodejs.org/docs', }, @@ -97,15 +98,16 @@ describe('resolvePageRoot', () => { }); it('uses the configured base URL for synthetic pages with absolute URLs', async () => { - getConfig('web').useAbsoluteURLs = true; + getConfig('html').useAbsoluteURLs = true; + getConfig('html').baseURL = 'https://example.com/docs'; const result = resolvePageRoot({ path: '/404', synthetic: true, }); - assert.strictEqual(result, 'https://nodejs.org/docs/'); + assert.strictEqual(result, 'https://example.com/docs/'); - getConfig('web').useAbsoluteURLs = false; + getConfig('html').useAbsoluteURLs = false; }); }); diff --git a/packages/core/src/generators/web/utils/__tests__/relativeOrAbsolute.test.mjs b/packages/react/src/html/utils/__tests__/relativeOrAbsolute.test.mjs similarity index 78% rename from packages/core/src/generators/web/utils/__tests__/relativeOrAbsolute.test.mjs rename to packages/react/src/html/utils/__tests__/relativeOrAbsolute.test.mjs index 998d22e5..af9d1e6e 100644 --- a/packages/core/src/generators/web/utils/__tests__/relativeOrAbsolute.test.mjs +++ b/packages/react/src/html/utils/__tests__/relativeOrAbsolute.test.mjs @@ -4,15 +4,16 @@ import { beforeEach, describe, it } from 'node:test'; import { setConfig, default as getConfig, -} from '../../../../utils/configuration/index.mjs'; +} from '@nodejs/doc-kit/utils/configuration/index.mjs'; + import { relativeOrAbsolute } from '../relativeOrAbsolute.mjs'; await setConfig({ - target: ['web'], + target: ['html'], version: 'v22.0.0', changelog: [], generators: { - web: { + html: { useAbsoluteURLs: false, baseURL: 'https://nodejs.org/docs', }, @@ -21,7 +22,7 @@ await setConfig({ describe('relativeOrAbsolute (relative mode)', () => { beforeEach(() => { - getConfig('web').useAbsoluteURLs = false; + getConfig('html').useAbsoluteURLs = false; }); it('returns a relative path from a nested page to root', () => { @@ -47,21 +48,22 @@ describe('relativeOrAbsolute (relative mode)', () => { describe('relativeOrAbsolute (absolute mode)', () => { beforeEach(() => { - getConfig('web').useAbsoluteURLs = true; + getConfig('html').useAbsoluteURLs = true; + getConfig('html').baseURL = 'https://example.com/docs'; }); it('returns an absolute URL to root', () => { const result = relativeOrAbsolute('/', '/api/fs'); - assert.strictEqual(result, 'https://nodejs.org/docs/'); + assert.strictEqual(result, 'https://example.com/docs/'); }); it('returns an absolute URL for a page path', () => { const result = relativeOrAbsolute('/http', '/fs'); - assert.strictEqual(result, 'https://nodejs.org/docs/http'); + assert.strictEqual(result, 'https://example.com/docs/http'); }); it('returns an absolute URL for a resource', () => { const result = relativeOrAbsolute('/orama-db.json', '/api/fs'); - assert.strictEqual(result, 'https://nodejs.org/docs/orama-db.json'); + assert.strictEqual(result, 'https://example.com/docs/orama-db.json'); }); }); diff --git a/packages/core/src/generators/web/utils/config.mjs b/packages/react/src/html/utils/config.mjs similarity index 84% rename from packages/core/src/generators/web/utils/config.mjs rename to packages/react/src/html/utils/config.mjs index 042f1fcc..e616e6d7 100644 --- a/packages/core/src/generators/web/utils/config.mjs +++ b/packages/react/src/html/utils/config.mjs @@ -1,11 +1,11 @@ 'use strict'; import { LANGS } from '@node-core/rehype-shiki'; +import getConfig from '@nodejs/doc-kit/utils/configuration/index.mjs'; +import { populate } from '@nodejs/doc-kit/utils/configuration/templates.mjs'; +import { getVersionFromSemVer } from '@nodejs/doc-kit/utils/generators.mjs'; +import { omitKeys } from '@nodejs/doc-kit/utils/misc.mjs'; -import getConfig from '../../../utils/configuration/index.mjs'; -import { populate } from '../../../utils/configuration/templates.mjs'; -import { getVersionFromSemVer } from '../../../utils/generators.mjs'; -import { omitKeys } from '../../../utils/misc.mjs'; import { getSortedHeadNodes } from '../../jsx-ast/utils/getSortedHeadNodes.mjs'; /** @@ -74,15 +74,20 @@ export function buildLanguageDisplayNameMap() { * @returns {string} JavaScript source code string with named exports */ export default function createConfigSource(input, server = false) { - const { version: configVersion, ...config } = getConfig('web'); + const { version: configVersion, ...config } = getConfig('html'); - const editURL = populate(config.editURL, { - ...config, - version: `v${configVersion.version}`, - }); + const editURL = + config.editURL && + populate(config.editURL, { + ...config, + version: `v${configVersion.version}`, + }); const pageURL = populate(config.pageURL, config); const exports = { + repository: undefined, + baseURL: undefined, + remoteConfigUrl: undefined, ...omitKeys( config, // These are large or build-time-only keys, or may contain functions, so diff --git a/packages/core/src/generators/web/utils/copying.mjs b/packages/react/src/html/utils/copying.mjs similarity index 86% rename from packages/core/src/generators/web/utils/copying.mjs rename to packages/react/src/html/utils/copying.mjs index 20bd5d12..1c43a907 100644 --- a/packages/core/src/generators/web/utils/copying.mjs +++ b/packages/react/src/html/utils/copying.mjs @@ -1,7 +1,7 @@ import { cp } from 'node:fs/promises'; import { join, basename } from 'node:path'; -import logger from '../../../logger/index.mjs'; +import logger from '@nodejs/doc-kit/logger/index.mjs'; /** * Copies static directories/files defined in `pathsToCopy` to the output directory. @@ -28,7 +28,7 @@ export async function copyStaticAssets(config) { } catch (err) { if (err.code !== 'ENOENT') { logger.error( - `[web-generator] Failed to copy asset from ${src} to ${dest}: ${err.message}` + `[html-generator] Failed to copy asset from ${src} to ${dest}: ${err.message}` ); } } diff --git a/packages/core/src/generators/web/utils/generate.mjs b/packages/react/src/html/utils/generate.mjs similarity index 96% rename from packages/core/src/generators/web/utils/generate.mjs rename to packages/react/src/html/utils/generate.mjs index e3c13c74..d5f0b51c 100644 --- a/packages/core/src/generators/web/utils/generate.mjs +++ b/packages/react/src/html/utils/generate.mjs @@ -1,6 +1,7 @@ import { resolve } from 'node:path'; -import getConfig from '../../../utils/configuration/index.mjs'; +import getConfig from '@nodejs/doc-kit/utils/configuration/index.mjs'; + import { JSX_IMPORTS, ROOT } from '../constants.mjs'; /** @@ -50,7 +51,7 @@ export const createImportDeclaration = ( */ export default () => { // User-configured components (for JSX-in-MDX), merged with the built-ins. - const { components } = getConfig('web'); + const { components } = getConfig('html'); const componentImports = [ ...Object.values(JSX_IMPORTS), diff --git a/packages/core/src/generators/web/utils/processing.mjs b/packages/react/src/html/utils/processing.mjs similarity index 85% rename from packages/core/src/generators/web/utils/processing.mjs rename to packages/react/src/html/utils/processing.mjs index 73263c02..5fe51daa 100644 --- a/packages/core/src/generators/web/utils/processing.mjs +++ b/packages/react/src/html/utils/processing.mjs @@ -1,8 +1,9 @@ +import getConfig from '@nodejs/doc-kit/utils/configuration/index.mjs'; +import { populate } from '@nodejs/doc-kit/utils/configuration/templates.mjs'; + import createConfigSource from './config.mjs'; import createProgramBuilder from './generate.mjs'; import { relativeOrAbsolute } from './relativeOrAbsolute.mjs'; -import getConfig from '../../../utils/configuration/index.mjs'; -import { populate } from '../../../utils/configuration/templates.mjs'; import { resolveBundler } from '../bundlers/index.mjs'; import { SPECULATION_RULES } from '../constants.mjs'; import { THEME_SCRIPT } from '../ui/theme-script.mjs'; @@ -10,7 +11,7 @@ import { THEME_SCRIPT } from '../ui/theme-script.mjs'; /** * Creates the virtual imports for one bundle target. * - * @param {Array<{ data: import('../../metadata/types').MetadataEntry }>} sidebarEntries + * @param {Array<{ data: import('@nodejs/doc-kit/generators/metadata/types').MetadataEntry }>} sidebarEntries * @param {Record} virtualImports * @param {boolean} server * @returns {Record} @@ -38,12 +39,12 @@ export const populateWithEvaluation = (template, config) => { }; /** - * @param {import('../../metadata/types').MetadataEntry} data + * @param {import('@nodejs/doc-kit/generators/metadata/types').MetadataEntry} data * @returns {string} */ export const resolvePageRoot = data => { if (data.synthetic === true) { - const { baseURL, useAbsoluteURLs } = getConfig('web'); + const { baseURL, useAbsoluteURLs } = getConfig('html'); return useAbsoluteURLs ? String(baseURL).replace(/\/?$/, '/') : '/'; } @@ -91,7 +92,7 @@ export const buildHead = ({ meta = [], links = [], html = [] }) => * string upstream (in the `jsx-ast` worker), so the heavy AST never reaches * the main thread — only the code string and page metadata stream in here. * - * @returns {{ add: (item: { data: import('../../metadata/types').MetadataEntry, code: string }) => void, serverCodeMap: Map, clientCodeMap: Map }} + * @returns {{ add: (item: { data: import('@nodejs/doc-kit/generators/metadata/types').MetadataEntry, code: string }) => void, serverCodeMap: Map, clientCodeMap: Map }} */ export function createCodeConverter() { const { buildServerProgram, clientProgram } = createProgramBuilder(); @@ -103,7 +104,7 @@ export function createCodeConverter() { /** * Records the server/client programs for a single page's JSX code. * - * @param {{ data: import('../../metadata/types').MetadataEntry, code: string }} item + * @param {{ data: import('@nodejs/doc-kit/generators/metadata/types').MetadataEntry, code: string }} item */ add: ({ data, code }) => { const fileName = `${data.api}.jsx`; @@ -128,8 +129,8 @@ export function createCodeConverter() { * @param {object} params * @param {Map} params.serverCodeMap - Server-side code per page. * @param {Map} params.clientCodeMap - Client-side code per page. - * @param {Array} params.datas - Per-page metadata, in render order. - * @param {Array<{ data: import('../../metadata/types').MetadataEntry }>} params.sidebarEntries - Entries used to build the sidebar page list (real module pages only). + * @param {Array} params.datas - Per-page metadata, in render order. + * @param {Array<{ data: import('@nodejs/doc-kit/generators/metadata/types').MetadataEntry }>} params.sidebarEntries - Entries used to build the sidebar page list (real module pages only). * @param {string} params.template - The HTML template string for the output pages. */ export async function processBundles({ @@ -139,7 +140,7 @@ export async function processBundles({ sidebarEntries, template, }) { - const config = getConfig('web'); + const config = getConfig('html'); const bundler = await resolveBundler(config.bundler); const serverPages = await bundler.render({ diff --git a/packages/core/src/generators/web/utils/relativeOrAbsolute.mjs b/packages/react/src/html/utils/relativeOrAbsolute.mjs similarity index 70% rename from packages/core/src/generators/web/utils/relativeOrAbsolute.mjs rename to packages/react/src/html/utils/relativeOrAbsolute.mjs index a37be0e3..d6860851 100644 --- a/packages/core/src/generators/web/utils/relativeOrAbsolute.mjs +++ b/packages/react/src/html/utils/relativeOrAbsolute.mjs @@ -1,5 +1,5 @@ -import getConfig from '../../../utils/configuration/index.mjs'; -import { relative } from '../../../utils/url.mjs'; +import getConfig from '@nodejs/doc-kit/utils/configuration/index.mjs'; +import { relative } from '@nodejs/doc-kit/utils/url.mjs'; /** * Returns an absolute URL (based on baseURL) or a relative URL, @@ -10,7 +10,7 @@ import { relative } from '../../../utils/url.mjs'; * @returns {string} */ export const relativeOrAbsolute = (to, from) => { - const { useAbsoluteURLs, baseURL } = getConfig('web'); + const { useAbsoluteURLs, baseURL } = getConfig('html'); return useAbsoluteURLs ? new URL(`.${to}`, baseURL.replace(/\/?$/, '/')).href diff --git a/packages/core/src/generators/jsx-ast/README.md b/packages/react/src/jsx-ast/README.md similarity index 100% rename from packages/core/src/generators/jsx-ast/README.md rename to packages/react/src/jsx-ast/README.md diff --git a/packages/core/src/generators/jsx-ast/__tests__/generate.test.mjs b/packages/react/src/jsx-ast/__tests__/generate.test.mjs similarity index 96% rename from packages/core/src/generators/jsx-ast/__tests__/generate.test.mjs rename to packages/react/src/jsx-ast/__tests__/generate.test.mjs index e1f52e76..028a5b95 100644 --- a/packages/core/src/generators/jsx-ast/__tests__/generate.test.mjs +++ b/packages/react/src/jsx-ast/__tests__/generate.test.mjs @@ -1,7 +1,10 @@ import assert from 'node:assert/strict'; import { describe, it } from 'node:test'; -import getConfig, { setConfig } from '../../../utils/configuration/index.mjs'; +import getConfig, { + setConfig, +} from '@nodejs/doc-kit/utils/configuration/index.mjs'; + import { generate, processChunk } from '../generate.mjs'; const createEntry = (api, name, { stabilityIndex = '2' } = {}) => { diff --git a/packages/core/src/generators/jsx-ast/constants.mjs b/packages/react/src/jsx-ast/constants.mjs similarity index 99% rename from packages/core/src/generators/jsx-ast/constants.mjs rename to packages/react/src/jsx-ast/constants.mjs index dddad667..ffeec38c 100644 --- a/packages/core/src/generators/jsx-ast/constants.mjs +++ b/packages/react/src/jsx-ast/constants.mjs @@ -1,4 +1,4 @@ -import { JSX_IMPORTS } from '../web/constants.mjs'; +import { JSX_IMPORTS } from '../html/constants.mjs'; /** * UI classes for Node.js API stability levels diff --git a/packages/core/src/generators/jsx-ast/generate.mjs b/packages/react/src/jsx-ast/generate.mjs similarity index 92% rename from packages/core/src/generators/jsx-ast/generate.mjs rename to packages/react/src/jsx-ast/generate.mjs index 055e0efb..9a21310d 100644 --- a/packages/core/src/generators/jsx-ast/generate.mjs +++ b/packages/react/src/jsx-ast/generate.mjs @@ -1,3 +1,5 @@ +import getConfig from '@nodejs/doc-kit/utils/configuration/index.mjs'; +import { groupNodesByModule } from '@nodejs/doc-kit/utils/generators.mjs'; import { jsx, toJs } from 'estree-util-to-js'; import buildContent from './utils/buildContent.mjs'; @@ -5,8 +7,6 @@ import { getSortedHeadNodes } from './utils/getSortedHeadNodes.mjs'; import { buildNotFoundPage } from './utils/synthetic/404.mjs'; import { buildAllPage } from './utils/synthetic/all.mjs'; import { buildIndexPage } from './utils/synthetic/index.mjs'; -import getConfig from '../../utils/configuration/index.mjs'; -import { groupNodesByModule } from '../../utils/generators.mjs'; /** * Builds the `{ head, entries }` page descriptors for all configured synthetic @@ -14,7 +14,7 @@ import { groupNodesByModule } from '../../utils/generators.mjs'; * runs later in a worker (via `processChunk`), so the very large synthetic * `all` page is never built on the main thread. * - * @param {Array} input + * @param {Array} input */ const buildSyntheticDescriptors = input => { const config = getConfig('jsx-ast'); diff --git a/packages/core/src/generators/jsx-ast/index.mjs b/packages/react/src/jsx-ast/index.mjs similarity index 91% rename from packages/core/src/generators/jsx-ast/index.mjs rename to packages/react/src/jsx-ast/index.mjs index 2060ed9f..9cd6a71a 100644 --- a/packages/core/src/generators/jsx-ast/index.mjs +++ b/packages/react/src/jsx-ast/index.mjs @@ -12,7 +12,7 @@ export default { description: 'Generates JSX AST from the input MDAST', - dependsOn: '@node-core/doc-kit/metadata', + dependsOn: '@nodejs/doc-kit/metadata', defaultConfiguration: { ref: 'main', diff --git a/packages/core/src/generators/jsx-ast/types.d.ts b/packages/react/src/jsx-ast/types.d.ts similarity index 82% rename from packages/core/src/generators/jsx-ast/types.d.ts rename to packages/react/src/jsx-ast/types.d.ts index 672c5a4f..e4403ff2 100644 --- a/packages/core/src/generators/jsx-ast/types.d.ts +++ b/packages/react/src/jsx-ast/types.d.ts @@ -1,4 +1,4 @@ -import type { MetadataEntry } from '../metadata/types'; +import type { MetadataEntry } from '@nodejs/doc-kit/generators/metadata/types'; import type { JSXContent } from './utils/buildContent.mjs'; export type Generator = GeneratorMetadata< diff --git a/packages/core/src/generators/jsx-ast/utils/__tests__/ast.test.mjs b/packages/react/src/jsx-ast/utils/__tests__/ast.test.mjs similarity index 100% rename from packages/core/src/generators/jsx-ast/utils/__tests__/ast.test.mjs rename to packages/react/src/jsx-ast/utils/__tests__/ast.test.mjs diff --git a/packages/core/src/generators/jsx-ast/utils/__tests__/buildBarProps.test.mjs b/packages/react/src/jsx-ast/utils/__tests__/buildBarProps.test.mjs similarity index 100% rename from packages/core/src/generators/jsx-ast/utils/__tests__/buildBarProps.test.mjs rename to packages/react/src/jsx-ast/utils/__tests__/buildBarProps.test.mjs diff --git a/packages/core/src/generators/jsx-ast/utils/__tests__/buildContent.test.mjs b/packages/react/src/jsx-ast/utils/__tests__/buildContent.test.mjs similarity index 96% rename from packages/core/src/generators/jsx-ast/utils/__tests__/buildContent.test.mjs rename to packages/react/src/jsx-ast/utils/__tests__/buildContent.test.mjs index afb86405..8903c08b 100644 --- a/packages/core/src/generators/jsx-ast/utils/__tests__/buildContent.test.mjs +++ b/packages/react/src/jsx-ast/utils/__tests__/buildContent.test.mjs @@ -1,7 +1,8 @@ import assert from 'node:assert/strict'; import { describe, it } from 'node:test'; -import { setConfig } from '../../../../utils/configuration/index.mjs'; +import { setConfig } from '@nodejs/doc-kit/utils/configuration/index.mjs'; + import { transformHeadingNode } from '../buildContent.mjs'; const heading = { diff --git a/packages/core/src/generators/jsx-ast/utils/__tests__/overloads.test.mjs b/packages/react/src/jsx-ast/utils/__tests__/overloads.test.mjs similarity index 100% rename from packages/core/src/generators/jsx-ast/utils/__tests__/overloads.test.mjs rename to packages/react/src/jsx-ast/utils/__tests__/overloads.test.mjs diff --git a/packages/core/src/utils/__tests__/remark.test.mjs b/packages/react/src/jsx-ast/utils/__tests__/remark.test.mjs similarity index 100% rename from packages/core/src/utils/__tests__/remark.test.mjs rename to packages/react/src/jsx-ast/utils/__tests__/remark.test.mjs diff --git a/packages/core/src/generators/jsx-ast/utils/__tests__/signature.test.mjs b/packages/react/src/jsx-ast/utils/__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/jsx-ast/utils/__tests__/signature.test.mjs diff --git a/packages/core/src/generators/jsx-ast/utils/__tests__/types.test.mjs b/packages/react/src/jsx-ast/utils/__tests__/types.test.mjs similarity index 99% rename from packages/core/src/generators/jsx-ast/utils/__tests__/types.test.mjs rename to packages/react/src/jsx-ast/utils/__tests__/types.test.mjs index 0c4a7c3e..de963d4f 100644 --- a/packages/core/src/generators/jsx-ast/utils/__tests__/types.test.mjs +++ b/packages/react/src/jsx-ast/utils/__tests__/types.test.mjs @@ -2,7 +2,7 @@ import assert from 'node:assert/strict'; import { describe, it, mock } from 'node:test'; // Mock remark -mock.module('../../../../utils/remark.mjs', { +mock.module('../remark.mjs', { namedExports: { getRemarkRecma: () => ({ runSync: () => ({ diff --git a/packages/core/src/generators/jsx-ast/utils/ast.mjs b/packages/react/src/jsx-ast/utils/ast.mjs similarity index 100% rename from packages/core/src/generators/jsx-ast/utils/ast.mjs rename to packages/react/src/jsx-ast/utils/ast.mjs diff --git a/packages/core/src/generators/jsx-ast/utils/buildBarProps.mjs b/packages/react/src/jsx-ast/utils/buildBarProps.mjs similarity index 84% rename from packages/core/src/generators/jsx-ast/utils/buildBarProps.mjs rename to packages/react/src/jsx-ast/utils/buildBarProps.mjs index d535c30a..cd5758fa 100644 --- a/packages/core/src/generators/jsx-ast/utils/buildBarProps.mjs +++ b/packages/react/src/jsx-ast/utils/buildBarProps.mjs @@ -12,7 +12,7 @@ const FUNCTION_HEADING_TYPES = new Set(['method', 'ctor', 'classMethod']); /** * 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) => { @@ -25,7 +25,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('@nodejs/doc-kit/generators/metadata/types').MetadataEntry} entry */ const shouldIncludeEntryInToC = ({ heading }) => // Only include headings with text, @@ -41,7 +41,7 @@ const shouldIncludeEntryInToC = ({ heading }) => * other headings keep their plain text, with CLI flags / env vars and leading * prefixes (i.e. `Class:`) stripped. * - * @param {import('../../metadata/types').HeadingData} data + * @param {import('@nodejs/doc-kit/generators/metadata/types').HeadingData} data */ const headingLabel = data => { if (FUNCTION_HEADING_TYPES.has(data.type)) { @@ -70,7 +70,7 @@ const headingLabel = data => { /** * Extracts and formats heading information from an API documentation entry. - * @param {import('../../metadata/types').MetadataEntry} entry + * @param {import('@nodejs/doc-kit/generators/metadata/types').MetadataEntry} entry */ const extractHeading = entry => { const data = entry.heading.data; @@ -88,7 +88,7 @@ const extractHeading = entry => { * Build the list of heading metadata for sidebar navigation. Overload headings * are dropped so each function contributes a single entry. * - * @param {Array} entries - All API metadata entries + * @param {Array} entries - All API metadata entries */ export const extractHeadings = entries => entries diff --git a/packages/core/src/generators/jsx-ast/utils/buildContent.mjs b/packages/react/src/jsx-ast/utils/buildContent.mjs similarity index 83% rename from packages/core/src/generators/jsx-ast/utils/buildContent.mjs rename to packages/react/src/jsx-ast/utils/buildContent.mjs index dd621eec..e21d7624 100644 --- a/packages/core/src/generators/jsx-ast/utils/buildContent.mjs +++ b/packages/react/src/jsx-ast/utils/buildContent.mjs @@ -1,5 +1,13 @@ 'use strict'; +import { enforceArray } from '@nodejs/doc-kit/utils/array.mjs'; +import getConfig from '@nodejs/doc-kit/utils/configuration/index.mjs'; +import { + GITHUB_BLOB_URL, + populate, +} from '@nodejs/doc-kit/utils/configuration/templates.mjs'; +import { omitKeys } from '@nodejs/doc-kit/utils/misc.mjs'; +import { UNIST } from '@nodejs/doc-kit/utils/queries/index.mjs'; import { h as createElement } from 'hastscript'; import { slice } from 'mdast-util-slice-markdown'; import readingTime from 'reading-time'; @@ -9,9 +17,8 @@ import { SKIP, visit } from 'unist-util-visit'; import { createJSXElement } from './ast.mjs'; import { extractHeadings, extractTextContent } from './buildBarProps.mjs'; import { annotateOverloads } from './overloads.mjs'; -import { enforceArray } from '../../../utils/array.mjs'; -import { omitKeys } from '../../../utils/misc.mjs'; -import { JSX_IMPORTS } from '../../web/constants.mjs'; +import { getRemarkRecma as remark } from './remark.mjs'; +import { JSX_IMPORTS } from '../../html/constants.mjs'; import { STABILITY_LEVELS, LIFECYCLE_LABELS, @@ -27,17 +34,10 @@ import { 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 { getRemarkRecma as remark } from '../../../utils/remark.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('@nodejs/doc-kit/generators/metadata/types').MetadataEntry} entry - The metadata entry */ export const gatherChangeEntries = entry => { // Lifecycle changes (e.g., added, deprecated) @@ -61,7 +61,7 @@ export const gatherChangeEntries = entry => { /** * Creates a JSX ChangeHistory element or returns null if no changes. - * @param {import('../../metadata/types').MetadataEntry} entry - The metadata entry + * @param {import('@nodejs/doc-kit/generators/metadata/types').MetadataEntry} entry - The metadata entry */ export const createChangeElement = entry => { const changes = gatherChangeEntries(entry); @@ -128,7 +128,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('@nodejs/doc-kit/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) => { @@ -166,7 +166,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('@nodejs/doc-kit/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 */ @@ -200,8 +200,8 @@ 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('../../metadata/types').HeadingNode} node - The heading node to transform + * @param {import('@nodejs/doc-kit/generators/metadata/types').MetadataEntry} entry - The API metadata entry + * @param {import('@nodejs/doc-kit/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 */ @@ -250,7 +250,7 @@ export const transformHeadingNode = async (entry, node, index, parent) => { /** * Processes a single API documentation entry's content - * @param {import('../../metadata/types').MetadataEntry} entry - The API metadata entry to process + * @param {import('@nodejs/doc-kit/generators/metadata/types').MetadataEntry} entry - The API metadata entry to process */ export const processEntry = entry => { // Visit and transform stability nodes @@ -276,7 +276,7 @@ export const processEntry = entry => { /** * Builds the overall document layout tree - * @param {Array} entries - API documentation metadata entries + * @param {Array} entries - API documentation metadata entries * @param {Object} metadata - Raw page metadata from the head entry */ export const createDocumentLayout = (entries, metadata) => { @@ -295,11 +295,11 @@ export const createDocumentLayout = (entries, metadata) => { }; /** - * @typedef {import('estree').Node & { data: import('../../metadata/types').MetadataEntry }} JSXContent + * @typedef {import('estree').Node & { data: import('@nodejs/doc-kit/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('@nodejs/doc-kit/generators/metadata/types').MetadataEntry} head - Main API metadata entry with version information * @returns {Promise} */ const buildContent = async (metadataEntries, head) => { diff --git a/packages/core/src/generators/jsx-ast/utils/getSortedHeadNodes.mjs b/packages/react/src/jsx-ast/utils/getSortedHeadNodes.mjs similarity index 67% rename from packages/core/src/generators/jsx-ast/utils/getSortedHeadNodes.mjs rename to packages/react/src/jsx-ast/utils/getSortedHeadNodes.mjs index c0f0c54c..f9ff18cd 100644 --- a/packages/core/src/generators/jsx-ast/utils/getSortedHeadNodes.mjs +++ b/packages/react/src/jsx-ast/utils/getSortedHeadNodes.mjs @@ -4,8 +4,8 @@ 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('@nodejs/doc-kit/generators/metadata/types').MetadataEntry} a + * @param {import('@nodejs/doc-kit/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/overloads.mjs b/packages/react/src/jsx-ast/utils/overloads.mjs similarity index 78% rename from packages/core/src/generators/jsx-ast/utils/overloads.mjs rename to packages/react/src/jsx-ast/utils/overloads.mjs index b35ad093..a0ae7413 100644 --- a/packages/core/src/generators/jsx-ast/utils/overloads.mjs +++ b/packages/react/src/jsx-ast/utils/overloads.mjs @@ -8,8 +8,8 @@ const OVERLOADABLE_TYPES = new Set(['method', 'ctor', 'classMethod']); * Two headings document the same function (i.e. are overloads of one another) * when they sit at the same depth and share the same resolved name and type. * - * @param {import('../../metadata/types').HeadingNode} a - * @param {import('../../metadata/types').HeadingNode} b + * @param {import('@nodejs/doc-kit/generators/metadata/types').HeadingNode} a + * @param {import('@nodejs/doc-kit/generators/metadata/types').HeadingNode} b */ const isSameFunction = (a, b) => a.depth === b.depth && @@ -26,8 +26,8 @@ const isSameFunction = (a, b) => * rendering in full on the page. The first (most stable) heading is left as-is, * and the ToC links to its existing anchor. * - * @param {Array} entries - Page entries, in render order. - * @returns {Array} The same entries (mutated). + * @param {Array} entries - Page entries, in render order. + * @returns {Array} The same entries (mutated). */ export const annotateOverloads = entries => { for (let i = 0; i < entries.length; i++) { diff --git a/packages/core/src/generators/jsx-ast/utils/plugins/__tests__/alerts.test.mjs b/packages/react/src/jsx-ast/utils/plugins/__tests__/alerts.test.mjs similarity index 100% rename from packages/core/src/generators/jsx-ast/utils/plugins/__tests__/alerts.test.mjs rename to packages/react/src/jsx-ast/utils/plugins/__tests__/alerts.test.mjs diff --git a/packages/core/src/generators/jsx-ast/utils/plugins/__tests__/transformer.test.mjs b/packages/react/src/jsx-ast/utils/plugins/__tests__/transformer.test.mjs similarity index 100% rename from packages/core/src/generators/jsx-ast/utils/plugins/__tests__/transformer.test.mjs rename to packages/react/src/jsx-ast/utils/plugins/__tests__/transformer.test.mjs diff --git a/packages/core/src/generators/jsx-ast/utils/plugins/alerts.mjs b/packages/react/src/jsx-ast/utils/plugins/alerts.mjs similarity index 97% rename from packages/core/src/generators/jsx-ast/utils/plugins/alerts.mjs rename to packages/react/src/jsx-ast/utils/plugins/alerts.mjs index 1abe1940..4ede03e5 100644 --- a/packages/core/src/generators/jsx-ast/utils/plugins/alerts.mjs +++ b/packages/react/src/jsx-ast/utils/plugins/alerts.mjs @@ -2,7 +2,7 @@ import { SKIP, visit } from 'unist-util-visit'; -import { JSX_IMPORTS } from '../../../web/constants.mjs'; +import { JSX_IMPORTS } from '../../../html/constants.mjs'; import { ALERT_MARKER, GITHUB_ALERT_TYPES } from '../../constants.mjs'; import { createJSXElement } from '../ast.mjs'; diff --git a/packages/core/src/generators/jsx-ast/utils/plugins/transformer.mjs b/packages/react/src/jsx-ast/utils/plugins/transformer.mjs similarity index 100% rename from packages/core/src/generators/jsx-ast/utils/plugins/transformer.mjs rename to packages/react/src/jsx-ast/utils/plugins/transformer.mjs diff --git a/packages/react/src/jsx-ast/utils/remark.mjs b/packages/react/src/jsx-ast/utils/remark.mjs new file mode 100644 index 00000000..d0b3f7ba --- /dev/null +++ b/packages/react/src/jsx-ast/utils/remark.mjs @@ -0,0 +1,80 @@ +'use strict'; + +import rehypeShikiji from '@node-core/rehype-shiki/plugin'; +import { highlighter } from '@nodejs/doc-kit/utils/highlighter.mjs'; +import { lazy } from '@nodejs/doc-kit/utils/misc.mjs'; +import { typeAnnotationToHighlightedHast } from '@nodejs/doc-kit/utils/type-annotations/hast.mjs'; +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 { visit } from 'unist-util-visit'; + +import { AST_NODE_TYPES } from '../constants.mjs'; +import transformAlerts from './plugins/alerts.mjs'; +import transformElements from './plugins/transformer.mjs'; + +const passThrough = ['element', ...Object.values(AST_NODE_TYPES.MDX)]; +const codeMetaProperty = 'codeMeta'; + +/** + * Stores fenced code metadata on properties before rehypeRaw reparses the tree. + */ +const preserveCodeMeta = () => tree => { + visit(tree, 'element', node => { + const meta = node.data?.meta; + + if (node.tagName === 'code' && typeof meta === 'string') { + node.properties ||= {}; + node.properties[codeMetaProperty] = meta; + } + }); +}; + +/** + * Restores fenced code metadata so the Shiki plugin can read displayName. + */ +const restoreCodeMeta = () => tree => { + visit(tree, 'element', node => { + const meta = node.properties?.[codeMetaProperty]; + + if (node.tagName === 'code' && typeof meta === 'string') { + node.data = { ...node.data, meta }; + delete node.properties[codeMetaProperty]; + } + }); +}; + +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 = lazy(() => + unified() + .use(remarkParse) + .use(transformAlerts) + // 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, + // The web pipeline gets Shiki-highlighted types with embedded links + handlers: { typeAnnotation: typeAnnotationToHighlightedHast }, + }) + .use(preserveCodeMeta) + // Any `raw` HTML in the markdown must be converted to AST in order for Recma to understand it + .use(rehypeRaw, { passThrough }) + .use(restoreCodeMeta) + .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/jsx-ast/utils/signature.mjs similarity index 83% rename from packages/core/src/generators/jsx-ast/utils/signature.mjs rename to packages/react/src/jsx-ast/utils/signature.mjs index f7220d02..5c5c0275 100644 --- a/packages/core/src/generators/jsx-ast/utils/signature.mjs +++ b/packages/react/src/jsx-ast/utils/signature.mjs @@ -1,18 +1,18 @@ +import { highlighter } from '@nodejs/doc-kit/utils/highlighter.mjs'; +import { UNIST } from '@nodejs/doc-kit/utils/queries/index.mjs'; +import { parseListItem } from '@nodejs/doc-kit/utils/signature/parseList.mjs'; +import parseSignature from '@nodejs/doc-kit/utils/signature/parseSignature.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 '../../../utils/signature/parseList.mjs'; -import parseSignature from '../../../utils/signature/parseSignature.mjs'; -import { JSX_IMPORTS } from '../../web/constants.mjs'; +import { JSX_IMPORTS } from '../../html/constants.mjs'; /** * Generates a string representation of a function or class signature. * * @param {string} functionName - The name of the function or class. - * @param {import('../../../utils/signature/types').MethodSignature} signature - The parsed signature object. + * @param {import('@nodejs/doc-kit/utils/signature/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('../../../utils/signature/types').MethodSignature} signature - Signature object with parameter and return type info. + * @param {import('@nodejs/doc-kit/utils/signature/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('@nodejs/doc-kit/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('@nodejs/doc-kit/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/synthetic/404.mjs b/packages/react/src/jsx-ast/utils/synthetic/404.mjs similarity index 100% rename from packages/core/src/generators/jsx-ast/utils/synthetic/404.mjs rename to packages/react/src/jsx-ast/utils/synthetic/404.mjs diff --git a/packages/core/src/generators/jsx-ast/utils/synthetic/__tests__/404.test.mjs b/packages/react/src/jsx-ast/utils/synthetic/__tests__/404.test.mjs similarity index 100% rename from packages/core/src/generators/jsx-ast/utils/synthetic/__tests__/404.test.mjs rename to packages/react/src/jsx-ast/utils/synthetic/__tests__/404.test.mjs diff --git a/packages/core/src/generators/jsx-ast/utils/synthetic/__tests__/all.test.mjs b/packages/react/src/jsx-ast/utils/synthetic/__tests__/all.test.mjs similarity index 100% rename from packages/core/src/generators/jsx-ast/utils/synthetic/__tests__/all.test.mjs rename to packages/react/src/jsx-ast/utils/synthetic/__tests__/all.test.mjs diff --git a/packages/core/src/generators/jsx-ast/utils/synthetic/__tests__/index.test.mjs b/packages/react/src/jsx-ast/utils/synthetic/__tests__/index.test.mjs similarity index 100% rename from packages/core/src/generators/jsx-ast/utils/synthetic/__tests__/index.test.mjs rename to packages/react/src/jsx-ast/utils/synthetic/__tests__/index.test.mjs diff --git a/packages/core/src/generators/jsx-ast/utils/synthetic/__tests__/synthetic.test.mjs b/packages/react/src/jsx-ast/utils/synthetic/__tests__/synthetic.test.mjs similarity index 100% rename from packages/core/src/generators/jsx-ast/utils/synthetic/__tests__/synthetic.test.mjs rename to packages/react/src/jsx-ast/utils/synthetic/__tests__/synthetic.test.mjs diff --git a/packages/core/src/generators/jsx-ast/utils/synthetic/all.mjs b/packages/react/src/jsx-ast/utils/synthetic/all.mjs similarity index 70% rename from packages/core/src/generators/jsx-ast/utils/synthetic/all.mjs rename to packages/react/src/jsx-ast/utils/synthetic/all.mjs index a25894ed..cd55015d 100644 --- a/packages/core/src/generators/jsx-ast/utils/synthetic/all.mjs +++ b/packages/react/src/jsx-ast/utils/synthetic/all.mjs @@ -5,7 +5,7 @@ import { createSyntheticHead } from './synthetic.mjs'; /** * Builds the page descriptor for `all.html` * - * @param {Array} entries + * @param {Array} entries */ export const buildAllPage = entries => ({ head: createSyntheticHead('all', 'All'), diff --git a/packages/core/src/generators/jsx-ast/utils/synthetic/index.mjs b/packages/react/src/jsx-ast/utils/synthetic/index.mjs similarity index 88% rename from packages/core/src/generators/jsx-ast/utils/synthetic/index.mjs rename to packages/react/src/jsx-ast/utils/synthetic/index.mjs index 423d11a0..4c4cf42f 100644 --- a/packages/core/src/generators/jsx-ast/utils/synthetic/index.mjs +++ b/packages/react/src/jsx-ast/utils/synthetic/index.mjs @@ -3,7 +3,7 @@ import { h as createElement } from 'hastscript'; import { createSyntheticHead, wrapAsEntry } from './synthetic.mjs'; -import { JSX_IMPORTS } from '../../../web/constants.mjs'; +import { JSX_IMPORTS } from '../../../html/constants.mjs'; import { createJSXElement } from '../ast.mjs'; import { getSortedHeadNodes } from '../getSortedHeadNodes.mjs'; @@ -28,7 +28,7 @@ const getStabilityBadgeKind = index => * Builds the Stability Overview table from module heads that declare a * top-level stability index, mirroring the `legacy-html-all` overview. * - * @param {Array} headEntries + * @param {Array} headEntries */ export const buildStabilityOverview = headEntries => createElement('table', [ @@ -64,7 +64,7 @@ export const buildStabilityOverview = headEntries => /** * Builds the page descriptor for `index.html` * - * @param {Array} entries + * @param {Array} entries */ export const buildIndexPage = entries => { const head = createSyntheticHead('index', 'Index'); diff --git a/packages/core/src/generators/jsx-ast/utils/synthetic/synthetic.mjs b/packages/react/src/jsx-ast/utils/synthetic/synthetic.mjs similarity index 100% rename from packages/core/src/generators/jsx-ast/utils/synthetic/synthetic.mjs rename to packages/react/src/jsx-ast/utils/synthetic/synthetic.mjs diff --git a/packages/core/src/generators/jsx-ast/utils/types.mjs b/packages/react/src/jsx-ast/utils/types.mjs similarity index 90% rename from packages/core/src/generators/jsx-ast/utils/types.mjs rename to packages/react/src/jsx-ast/utils/types.mjs index 706f7dd9..ef457c7c 100644 --- a/packages/core/src/generators/jsx-ast/utils/types.mjs +++ b/packages/react/src/jsx-ast/utils/types.mjs @@ -1,9 +1,9 @@ +import { QUERIES, UNIST } from '@nodejs/doc-kit/utils/queries/index.mjs'; +import { DEFAULT_EXPRESSION } from '@nodejs/doc-kit/utils/signature/constants.mjs'; +import { transformNodesToString } from '@nodejs/doc-kit/utils/unist.mjs'; import { u as createTree } from 'unist-builder'; -import { QUERIES, UNIST } from '../../../utils/queries/index.mjs'; -import { getRemarkRecma as remark } from '../../../utils/remark.mjs'; -import { DEFAULT_EXPRESSION } from '../../../utils/signature/constants.mjs'; -import { transformNodesToString } from '../../../utils/unist.mjs'; +import { getRemarkRecma as remark } from './remark.mjs'; import { TRIMMABLE_PADDING_REGEX } from '../constants.mjs'; /** diff --git a/packages/core/src/generators/llms-txt/README.md b/packages/react/src/llms-txt/README.md similarity index 100% rename from packages/core/src/generators/llms-txt/README.md rename to packages/react/src/llms-txt/README.md diff --git a/packages/core/src/generators/llms-txt/generate.mjs b/packages/react/src/llms-txt/generate.mjs similarity index 71% rename from packages/core/src/generators/llms-txt/generate.mjs rename to packages/react/src/llms-txt/generate.mjs index 0610b73c..dac8aaf7 100644 --- a/packages/core/src/generators/llms-txt/generate.mjs +++ b/packages/react/src/llms-txt/generate.mjs @@ -3,9 +3,11 @@ import { readFile } from 'node:fs/promises'; import { join } from 'node:path'; +import getConfig from '@nodejs/doc-kit/utils/configuration/index.mjs'; +import { populate } from '@nodejs/doc-kit/utils/configuration/templates.mjs'; +import { writeFile } from '@nodejs/doc-kit/utils/file.mjs'; + import { buildApiDocLink } from './utils/buildApiDocLink.mjs'; -import getConfig from '../../utils/configuration/index.mjs'; -import { writeFile } from '../../utils/file.mjs'; /** * Generates a llms.txt file @@ -22,7 +24,7 @@ export async function generate(input) { .map(entry => `- ${buildApiDocLink(entry, config)}`) .join('\n'); - const filledTemplate = `${template}${apiDocsLinks}`; + const filledTemplate = `${populate(template, config)}${apiDocsLinks}`; if (config.output) { await writeFile(join(config.output, 'llms.txt'), filledTemplate); diff --git a/packages/core/src/generators/llms-txt/index.mjs b/packages/react/src/llms-txt/index.mjs similarity index 84% rename from packages/core/src/generators/llms-txt/index.mjs rename to packages/react/src/llms-txt/index.mjs index 9547625d..cd6d5d73 100644 --- a/packages/core/src/generators/llms-txt/index.mjs +++ b/packages/react/src/llms-txt/index.mjs @@ -16,11 +16,11 @@ export default { description: 'Generates a llms.txt file to provide information to LLMs at inference time', - dependsOn: '@node-core/doc-kit/metadata', + dependsOn: '@nodejs/doc-kit/metadata', defaultConfiguration: { templatePath: join(import.meta.dirname, 'template.txt'), - pageURL: '{baseURL}/latest/api{path}.md', + pageURL: '{baseURL}{path}.md', }, generate, diff --git a/packages/react/src/llms-txt/template.txt b/packages/react/src/llms-txt/template.txt new file mode 100644 index 00000000..adeb27a8 --- /dev/null +++ b/packages/react/src/llms-txt/template.txt @@ -0,0 +1,7 @@ +# {project} Documentation + +> API documentation for {project}. + +Below are the sections of the API documentation. Look out especially towards the links that point towards guidance/introduction to the structure of this documentation. + +## API Documentations diff --git a/packages/core/src/generators/llms-txt/types.d.ts b/packages/react/src/llms-txt/types.d.ts similarity index 67% rename from packages/core/src/generators/llms-txt/types.d.ts rename to packages/react/src/llms-txt/types.d.ts index 85c720ed..caacc6d7 100644 --- a/packages/core/src/generators/llms-txt/types.d.ts +++ b/packages/react/src/llms-txt/types.d.ts @@ -1,4 +1,4 @@ -import { MetadataEntry } from '../metadata/types'; +import { MetadataEntry } from '@nodejs/doc-kit/generators/metadata/types'; export type Generator = GeneratorMetadata< { diff --git a/packages/core/src/generators/llms-txt/utils/__tests__/buildApiDocLink.test.mjs b/packages/react/src/llms-txt/utils/__tests__/buildApiDocLink.test.mjs similarity index 100% rename from packages/core/src/generators/llms-txt/utils/__tests__/buildApiDocLink.test.mjs rename to packages/react/src/llms-txt/utils/__tests__/buildApiDocLink.test.mjs diff --git a/packages/core/src/generators/llms-txt/utils/buildApiDocLink.mjs b/packages/react/src/llms-txt/utils/buildApiDocLink.mjs similarity index 71% rename from packages/core/src/generators/llms-txt/utils/buildApiDocLink.mjs rename to packages/react/src/llms-txt/utils/buildApiDocLink.mjs index 271249d0..fced9643 100644 --- a/packages/core/src/generators/llms-txt/utils/buildApiDocLink.mjs +++ b/packages/react/src/llms-txt/utils/buildApiDocLink.mjs @@ -1,12 +1,12 @@ -import { populate } from '../../../utils/configuration/templates.mjs'; -import { transformNodeToString } from '../../../utils/unist.mjs'; +import { populate } from '@nodejs/doc-kit/utils/configuration/templates.mjs'; +import { transformNodeToString } from '@nodejs/doc-kit/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('@nodejs/doc-kit/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('@nodejs/doc-kit/generators/metadata/types').MetadataEntry} entry + * @param {import('@nodejs/doc-kit/utils/configuration/types').Configuration['llms-txt']} * @returns {string} */ export const buildApiDocLink = (entry, config) => { diff --git a/packages/core/src/generators/orama-db/README.md b/packages/react/src/orama-db/README.md similarity index 100% rename from packages/core/src/generators/orama-db/README.md rename to packages/react/src/orama-db/README.md diff --git a/packages/core/src/generators/orama-db/constants.mjs b/packages/react/src/orama-db/constants.mjs similarity index 100% rename from packages/core/src/generators/orama-db/constants.mjs rename to packages/react/src/orama-db/constants.mjs diff --git a/packages/core/src/generators/orama-db/generate.mjs b/packages/react/src/orama-db/generate.mjs similarity index 83% rename from packages/core/src/generators/orama-db/generate.mjs rename to packages/react/src/orama-db/generate.mjs index e8ec639d..4d29e22b 100644 --- a/packages/core/src/generators/orama-db/generate.mjs +++ b/packages/react/src/orama-db/generate.mjs @@ -2,14 +2,14 @@ import { join } from 'node:path'; +import getConfig from '@nodejs/doc-kit/utils/configuration/index.mjs'; +import { writeFile } from '@nodejs/doc-kit/utils/file.mjs'; +import { groupNodesByModule } from '@nodejs/doc-kit/utils/generators.mjs'; +import { transformNodeToString } from '@nodejs/doc-kit/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'; /** * Generates the Orama database. diff --git a/packages/core/src/generators/orama-db/index.mjs b/packages/react/src/orama-db/index.mjs similarity index 89% rename from packages/core/src/generators/orama-db/index.mjs rename to packages/react/src/orama-db/index.mjs index 1052dcd2..a50ac620 100644 --- a/packages/core/src/generators/orama-db/index.mjs +++ b/packages/react/src/orama-db/index.mjs @@ -13,7 +13,7 @@ export default { description: 'Generates the Orama database for the API docs.', - dependsOn: '@node-core/doc-kit/metadata', + dependsOn: '@nodejs/doc-kit/metadata', generate, }; diff --git a/packages/core/src/generators/orama-db/types.d.ts b/packages/react/src/orama-db/types.d.ts similarity index 87% rename from packages/core/src/generators/orama-db/types.d.ts rename to packages/react/src/orama-db/types.d.ts index 8c8c14db..57fe220a 100644 --- a/packages/core/src/generators/orama-db/types.d.ts +++ b/packages/react/src/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 '@nodejs/doc-kit/generators/metadata/types'; /** * Schema for the Orama database entry diff --git a/packages/core/src/generators/orama-db/utils/__tests__/title.test.mjs b/packages/react/src/orama-db/utils/__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/orama-db/utils/__tests__/title.test.mjs diff --git a/packages/core/src/generators/orama-db/utils/title.mjs b/packages/react/src/orama-db/utils/title.mjs similarity index 100% rename from packages/core/src/generators/orama-db/utils/title.mjs rename to packages/react/src/orama-db/utils/title.mjs diff --git a/packages/core/src/generators/sitemap/README.md b/packages/react/src/sitemap/README.md similarity index 100% rename from packages/core/src/generators/sitemap/README.md rename to packages/react/src/sitemap/README.md diff --git a/packages/core/src/generators/sitemap/entry-template.xml b/packages/react/src/sitemap/entry-template.xml similarity index 100% rename from packages/core/src/generators/sitemap/entry-template.xml rename to packages/react/src/sitemap/entry-template.xml diff --git a/packages/core/src/generators/sitemap/generate.mjs b/packages/react/src/sitemap/generate.mjs similarity index 87% rename from packages/core/src/generators/sitemap/generate.mjs rename to packages/react/src/sitemap/generate.mjs index 9ec9786a..3fb63d12 100644 --- a/packages/core/src/generators/sitemap/generate.mjs +++ b/packages/react/src/sitemap/generate.mjs @@ -3,10 +3,11 @@ import { readFile } from 'node:fs/promises'; import { join } from 'node:path'; +import getConfig from '@nodejs/doc-kit/utils/configuration/index.mjs'; +import { populate } from '@nodejs/doc-kit/utils/configuration/templates.mjs'; +import { writeFile } from '@nodejs/doc-kit/utils/file.mjs'; + 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'; /** * Generates a sitemap.xml file diff --git a/packages/core/src/generators/sitemap/index.mjs b/packages/react/src/sitemap/index.mjs similarity index 82% rename from packages/core/src/generators/sitemap/index.mjs rename to packages/react/src/sitemap/index.mjs index 1c267699..a9d777f1 100644 --- a/packages/core/src/generators/sitemap/index.mjs +++ b/packages/react/src/sitemap/index.mjs @@ -12,10 +12,10 @@ export default { description: 'Generates a sitemap.xml file for search engine optimization', - dependsOn: '@node-core/doc-kit/metadata', + dependsOn: '@nodejs/doc-kit/metadata', defaultConfiguration: { - indexURL: '{baseURL}/latest/api/', + indexURL: '{baseURL}/', pageURL: '{indexURL}{path}.html', }, diff --git a/packages/core/src/generators/sitemap/template.xml b/packages/react/src/sitemap/template.xml similarity index 100% rename from packages/core/src/generators/sitemap/template.xml rename to packages/react/src/sitemap/template.xml diff --git a/packages/core/src/generators/sitemap/types.d.ts b/packages/react/src/sitemap/types.d.ts similarity index 81% rename from packages/core/src/generators/sitemap/types.d.ts rename to packages/react/src/sitemap/types.d.ts index abad2fd1..f1ca96f3 100644 --- a/packages/core/src/generators/sitemap/types.d.ts +++ b/packages/react/src/sitemap/types.d.ts @@ -1,4 +1,4 @@ -import type { MetadataEntry } from '../metadata/types'; +import type { MetadataEntry } from '@nodejs/doc-kit/generators/metadata/types'; export interface SitemapEntry { loc: string; diff --git a/packages/react/src/sitemap/utils/createPageSitemapEntry.mjs b/packages/react/src/sitemap/utils/createPageSitemapEntry.mjs new file mode 100644 index 00000000..0c243bd8 --- /dev/null +++ b/packages/react/src/sitemap/utils/createPageSitemapEntry.mjs @@ -0,0 +1,19 @@ +import { populate } from '@nodejs/doc-kit/utils/configuration/templates.mjs'; + +/** + * Builds an API doc sitemap url. + * + * @param {import('@nodejs/doc-kit/generators/metadata/types').MetadataEntry} entry + * @param {import('@nodejs/doc-kit/utils/configuration/types').Configuration['sitemap']} config + * @param {string} lastmod - Last-modified date as `YYYY-MM-DD` + * @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/playwright.config.js b/playwright.config.js index ddffdc95..ac12abc6 100644 --- a/playwright.config.js +++ b/playwright.config.js @@ -6,7 +6,7 @@ export default defineConfig({ baseURL: 'http://localhost:3000', }, webServer: { - command: 'npx serve out', + command: 'node packages/core/bin/cli.mjs serve --static -o out', url: 'http://localhost:3000', reuseExistingServer: !process.env.CI, }, diff --git a/scripts/build-docs-content.mjs b/scripts/build-docs-content.mjs index 07442da1..836a5e0c 100644 --- a/scripts/build-docs-content.mjs +++ b/scripts/build-docs-content.mjs @@ -6,11 +6,11 @@ // www/pages/*.md authored narrative pages, copied verbatim // docs/*.md the existing reference docs // packages/core/src/generators/*/README.md -// per-generator config reference, written to +// packages/*/src/*/README.md per-generator config reference, written to // `generators/.md` // // `www/content/` is a build artifact and is gitignored. Run this before -// invoking the `web` generator against it. +// invoking the `html` generator against it. import { glob, mkdir, readFile, rm, writeFile } from 'node:fs/promises'; import { basename, dirname, join } from 'node:path'; @@ -25,6 +25,10 @@ const SOURCES = [ pattern: 'packages/core/src/generators/*/README.md', rename: file => `generators/${basename(dirname(file))}.md`, }, + { + pattern: 'packages/*/src/*/README.md', + rename: file => `generators/${basename(dirname(file))}.md`, + }, ]; /** diff --git a/scripts/vercel-build.sh b/scripts/vercel-build.sh index a9f5fd9a..5940b71f 100755 --- a/scripts/vercel-build.sh +++ b/scripts/vercel-build.sh @@ -9,7 +9,7 @@ node packages/core/bin/cli.mjs generate \ -t orama-db \ -t legacy-json \ -t llms-txt \ - -t web \ + -t html \ -i "./node/doc/api/*.md" \ --ignore "./node/doc/api/quic.md" \ -o "./out" \ diff --git a/www/doc-kit.config.mjs b/www/doc-kit.config.mjs index 017c440a..22b4dd76 100644 --- a/www/doc-kit.config.mjs +++ b/www/doc-kit.config.mjs @@ -28,7 +28,7 @@ const DESCRIPTION = /** @type {import('../packages/core/src/utils/configuration/types').Configuration} */ export default { - target: ['orama-db', 'legacy-json', 'web'], + target: ['orama-db', 'legacy-json', 'html'], global: { // `www/content/` is assembled by `scripts/build-docs-content.mjs`. @@ -41,10 +41,8 @@ export default { baseURL: BASE_URL, minify: true, - // Both default to fetching from nodejs/node over the network. This site has - // no Node.js release matrix and no `index.md`, and an array short-circuits - // the parse step, so pass empty ones rather than paying for the request. - changelog: [], + // This site has no `index.md`; an array short-circuits the parse step. + // (An empty `changelog` is the default: no version picker.) index: [], }, @@ -55,14 +53,10 @@ export default { generateIndexPage: false, }, - web: { + html: { project: 'doc-kit', title: '{project} documentation', - // The default is `{baseURL}/latest-{version}/api{path}.html`, which encodes - // Node.js's versioned-docs layout. This site publishes a single flat tree. - pageURL: `${BASE_URL}{path}.html`, - // Pages are assembled into `www/content/` at build time, so there is no // single source file a `{path}` template could point at. Link to the repo // instead; a per-page link would need a `#theme/Metabar` override that maps diff --git a/www/pages/getting-started.md b/www/pages/getting-started.md index a687ebca..d91a6edc 100644 --- a/www/pages/getting-started.md +++ b/www/pages/getting-started.md @@ -2,14 +2,34 @@ This page takes you from an empty directory to a rendered documentation page. +## Quick start + +Two commands take an initialized npm project to a live-previewed docs site: + +```bash +npx doc-kit bootstrap +npx doc-kit serve +``` + +`bootstrap` writes a configuration file wired to your `package.json`, finds or +creates your documentation directory (with a starter page), and installs the +generator packages you pick. `serve` builds the site and rebuilds it as you +edit. The rest of this page walks the same road manually, so you can see each +moving part. + ## Install From an initialized npm project, run this in your terminal. ```bash -npm install --save-dev @node-core/doc-kit +npm install --save-dev @nodejs/doc-kit @nodejs/doc-kit-generator-legacy @nodejs/doc-kit-generator-react ``` +`@nodejs/doc-kit` provides the engine and CLI; generators ship as separate +packages, so install the ones for the output formats you need +(`@nodejs/doc-kit-generator-legacy` and `@nodejs/doc-kit-generator-react` cover +the targets used on this page). + ## Write a valid input document Create `docs/hello.md`: @@ -48,12 +68,12 @@ that's fully customizable via [a configuration file][]. ## Render the modern site -The `web` target produces the server-rendered, client-hydrated site that +The `html` target produces the server-rendered, client-hydrated site that [nodejs.org](https://nodejs.org) uses — and that this site is built with: ```bash npx doc-kit generate \ - -t web \ + -t html \ -i "docs/*.md" \ -o out ``` @@ -61,26 +81,26 @@ npx doc-kit generate \ Pair it with `orama-db` to add search: ```bash -npx doc-kit generate -t web -t orama-db -i "docs/*.md" -o out +npx doc-kit generate -t html -t orama-db -i "docs/*.md" -o out ``` ## Preview it locally -The `web` output uses import maps and client-side hydration, so it must be -served over HTTP — opening the files directly with `file://` will not work. Any -static server will do the trick; for example: +The `html` output uses import maps and client-side hydration, so it must be +served over HTTP — opening the files directly with `file://` will not work. +doc-kit ships one: ```bash -npx serve out -p 3000 +npx doc-kit serve --static -o out ``` Then open the printed URL (usually ). The `legacy-html-all` output from earlier has no such requirement — `out/all.html` opens straight from disk. -## Customize the `web` generator output +## Customize the `html` generator output -The power of the `web` generator comes from its customization hooks. Let's walk +The power of the `html` generator comes from its customization hooks. Let's walk through a couple quick changes. Create a `doc-kit.config.mjs` file at the root of the project. @@ -88,11 +108,13 @@ Create a `doc-kit.config.mjs` file at the root of the project. ```mjs import { join } from 'node:path'; -/** @type {import('@node-core/doc-kit/src/utils/configuration/types').Configuration} */ +/** @type {import('@nodejs/doc-kit/src/utils/configuration/types').Configuration} */ export default { - web: { - project: 'My Project', // Project name used in page titles and the version selector - remoteConfigUrl: '', // Suppress the Node.js default that sets the top banner based on Node.js news. + global: { + project: 'My Project', // Project name used in titles, the logo, and templates + }, + + html: { head: { html: [ // re-write the brand color for effect @@ -110,7 +132,7 @@ export default { `, ], }, - // use a custom logo instead of the Node.js logo + // use a custom logo component instead of the plain project name // our logo.jsx file like this, just for the demo // export default Logo = () => // ; @@ -130,7 +152,7 @@ experience, preserving core functionality. - Explore [Configuration](./configuration.html) — consider moving your `-t` target flags into a `doc-kit.config.mjs` file. -- [Further customize the `web` generator](./generators/web.html) — give it a +- [Further customize the `html` generator](./generators/html.html) — give it a custom sidenav or footer. - [Read the full input specification](./specification.html) — the full Markdown contract. diff --git a/www/pages/index.md b/www/pages/index.md index 9a193e54..7ee686d5 100644 --- a/www/pages/index.md +++ b/www/pages/index.md @@ -4,7 +4,7 @@ documentation. It's the documentation toolchain behind the Node.js API reference, found at https://nodejs.org/docs/latest/api/. This site is built by `doc-kit`, from its own repository. The pages you are reading were produced by -the `web` generator. +the `html` generator. > 📣 `doc-kit` is in beta. We'd like feedback within the > [issue log](https://github.com/nodejs/doc-kit/issues) or by visiting the @@ -24,7 +24,7 @@ Raw Markdown Files │ [metadata] extract structured API metadata │ - ├─► [jsx-ast] ─► [web] server-rendered site + ├─► [jsx-ast] ─► [html] server-rendered site ├─► [legacy-html] ─► …-all classic HTML ├─► [legacy-json] ─► …-all JSON ├─► [json-simple] simplified JSON @@ -39,7 +39,7 @@ Only some of these are things you ask for by name. `ast`, `metadata`, and them, and they are not valid `-t` targets. Everything in the fan-out below `metadata` is a target you can pass to `-t`, and passing several at once reuses the one shared parse rather than repeating it. The full list is in the -[generators reference](./generators/web.html). +[generators reference](./generators/html.html). ## The input contract @@ -65,7 +65,7 @@ See [the specification](./specification.html) for the full input format. A couple places `doc-kit` is already in use. Feel free to PR yours. - - `legacy-json` -- - `web` -- - `web` -- - `web` -- - `web` +- - `html` +- - `html` +- - `html` +- - `html`