chore(docs): document module extensions (#3898)

These are the primary API under bzlmod

Also pull out the docgen snippet so we can call it from a different
pipeline, like I did in rules_ts and rules_js
diff --git a/.github/workflows/release_docs.sh b/.github/workflows/release_docs.sh
new file mode 100755
index 0000000..65588bf
--- /dev/null
+++ b/.github/workflows/release_docs.sh
@@ -0,0 +1,13 @@
+#!/usr/bin/env bash
+# Add generated API docs to the release
+# see https://github.com/bazelbuild/bazel-central-registry/blob/main/docs/stardoc.md
+set -o errexit -o nounset -o pipefail
+
+docs="$(mktemp -d)"
+targets="$(mktemp)"
+out=$1
+bazel --output_base="$docs" query --output=label --output_file="$targets" 'kind("starlark_doc_extract rule", //...)'
+bazel --output_base="$docs" build --target_pattern_file="$targets"
+tar --create --auto-compress \
+	--directory "$(bazel --output_base="$docs" info bazel-bin)" \
+	--file "${out}" .
diff --git a/.github/workflows/release_prep.sh b/.github/workflows/release_prep.sh
index be73595..c756bf7 100755
--- a/.github/workflows/release_prep.sh
+++ b/.github/workflows/release_prep.sh
@@ -10,14 +10,7 @@
 ARCHIVE="rules_nodejs-$TAG.tar.gz"
 git archive --format=tar --prefix="${PREFIX}/" "${TAG}" | gzip > "$ARCHIVE"
 SHA=$(shasum -a 256 "$ARCHIVE" | awk '{print $1}')
-
-# Add generated API docs to the release, see https://github.com/bazelbuild/bazel-central-registry/issues/5593
-docs="$(mktemp -d)"; targets="$(mktemp)"
-bazel --output_base="$docs" query --output=label --output_file="$targets" 'kind("starlark_doc_extract rule", //...)'
-bazel --output_base="$docs" build --target_pattern_file="$targets" --remote_download_regex='.*doc_extract\.binaryproto'
-tar --create --auto-compress \
-    --directory "$(bazel --output_base="$docs" info bazel-bin)" \
-    --file "$GITHUB_WORKSPACE/${ARCHIVE%.tar.gz}.docs.tar.gz" .
+./.github/workflows/release_docs.sh "$GITHUB_WORKSPACE/${ARCHIVE%.tar.gz}.docs.tar.gz"
 
 cat << EOF
 ## Using Bzlmod with Bazel 6 or greater
diff --git a/WORKSPACE b/WORKSPACE
index 5617d5d..c1e783a 100644
--- a/WORKSPACE
+++ b/WORKSPACE
@@ -16,17 +16,6 @@
 
 load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive")
 
-http_archive(
-    name = "bazel_features",
-    sha256 = "5ab1a90d09fd74555e0df22809ad589627ddff263cff82535815aa80ca3e3562",
-    strip_prefix = "bazel_features-1.39.0",
-    url = "https://github.com/bazel-contrib/bazel_features/releases/download/v1.39.0/bazel_features-v1.39.0.tar.gz",
-)
-
-load("@bazel_features//:deps.bzl", "bazel_features_deps")
-
-bazel_features_deps()
-
 #
 # Install rules_nodejs dev dependencies
 #
@@ -35,6 +24,10 @@
 
 rules_nodejs_dev_dependencies()
 
+load("@bazel_features//:deps.bzl", "bazel_features_deps")
+
+bazel_features_deps()
+
 #
 # Setup rules_nodejs npm dependencies
 #
@@ -84,6 +77,10 @@
 
 bazel_skylib_workspace()
 
+load("@rules_cc//cc:extensions.bzl", "compatibility_proxy_repo")
+
+compatibility_proxy_repo()
+
 # Buildifier
 load("@buildifier_prebuilt//:deps.bzl", "buildifier_prebuilt_deps")
 
diff --git a/nodejs/BUILD.bazel b/nodejs/BUILD.bazel
index e6cae44..622b1f7 100644
--- a/nodejs/BUILD.bazel
+++ b/nodejs/BUILD.bazel
@@ -7,6 +7,12 @@
 package(default_visibility = ["//visibility:public"])
 
 bzl_library(
+    name = "extensions",
+    srcs = ["extensions.bzl"],
+    deps = [":repositories"],
+)
+
+bzl_library(
     name = "providers",
     srcs = ["providers.bzl"],
     deps = [
@@ -26,6 +32,16 @@
     ],
 )
 
+bzl_library(
+    name = "toolchain",
+    srcs = ["toolchain.bzl"],
+    deps = [
+        "//nodejs/private:current_node_cc_headers",
+        "//nodejs/private:os_name",
+        "@rules_cc//cc:defs_bzl",
+    ],
+)
+
 # This is the target rule authors should put in their "toolchains"
 # attribute in order to get a node interpreter for the correct
 # platform.
diff --git a/nodejs/extensions.bzl b/nodejs/extensions.bzl
index 48589af..3b4b53f 100644
--- a/nodejs/extensions.bzl
+++ b/nodejs/extensions.bzl
@@ -1,4 +1,13 @@
-"extensions for bzlmod"
+"""Module extensions for nodejs toolchain registration in MODULE.bazel
+
+Example usage, assuming a `.nvmrc` file is present in the same directory as the `MODULE.bazel` file:
+
+```starlark
+node = use_extension("@rules_nodejs//nodejs:extensions.bzl", "node")
+node.toolchain(node_version_from_nvmrc = "//:.nvmrc")
+use_repo(node, "nodejs_toolchains")
+```
+"""
 
 load(
     ":repositories.bzl",
@@ -67,11 +76,13 @@
         allow_single_file = True,
         doc = """The .nvmrc file containing the version of Node.js to use.
 
+This is recommended to ensure Bazel uses the same Node.js version as non-Bazel tooling.
 If set then the version found in the .nvmrc file is used instead of the one specified by node_version.""",
     ),
     "include_headers": attr.bool(
         doc = """Set headers field in NodeInfo provided by this toolchain.
 
+Required to compile native code into a Node.js binary.
 This setting creates a dependency on a c++ toolchain.
 """,
     ),
diff --git a/repositories.bzl b/repositories.bzl
index 35fe4fc..63e4fcc 100644
--- a/repositories.bzl
+++ b/repositories.bzl
@@ -30,6 +30,12 @@
     These are in this file to keep version information in one place, and make the WORKSPACE
     shorter.
     """
+    http_archive(
+        name = "bazel_features",
+        sha256 = "5ab1a90d09fd74555e0df22809ad589627ddff263cff82535815aa80ca3e3562",
+        strip_prefix = "bazel_features-1.39.0",
+        url = "https://github.com/bazel-contrib/bazel_features/releases/download/v1.39.0/bazel_features-v1.39.0.tar.gz",
+    )
 
     http_archive(
         name = "bazel_skylib",
@@ -50,3 +56,10 @@
         strip_prefix = "buildifier-prebuilt-8.0.0",
         urls = ["http://github.com/keith/buildifier-prebuilt/archive/8.0.0.tar.gz"],
     )
+
+    http_archive(
+        name = "rules_cc",
+        sha256 = "458b658277ba51b4730ea7a2020efdf1c6dcadf7d30de72e37f4308277fa8c01",
+        strip_prefix = "rules_cc-0.2.16",
+        url = "https://github.com/bazelbuild/rules_cc/releases/download/0.2.16/rules_cc-0.2.16.tar.gz",
+    )