nimbus-docs 0.1.9 → 0.1.11

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1,4 +1,4 @@
1
- import { o as validateLintOptions, r as suggest, t as IMPLEMENTED_CODES } from "./rules-DDDvKkyJ.js";
1
+ import { o as validateLintOptions, r as suggest, t as IMPLEMENTED_CODES } from "./rules-CzB-afEb.js";
2
2
  import { i as toRouteKey, n as isAbsoluteUrl, r as toBrowserHref, t as withStrictKeys } from "./strict-keys-fbKKxxKL.js";
3
3
  import { createRequire } from "node:module";
4
4
  import { execFile } from "node:child_process";
@@ -118,28 +118,16 @@ function slug(value, maintainCase) {
118
118
  * Mirror of Astro's content-layer slug normalization, used by every
119
119
  * framework URL builder that derives a URL from an `entry.id`.
120
120
  *
121
- * Astro's `glob` content loader (used by Nimbus's `docsCollection` /
122
- * `partialsCollection` factories) runs each path segment through
121
+ * Astro's `glob` content loader runs each path segment through
123
122
  * `github-slugger.slug()` and strips a trailing `/index`. That output is
124
- * what `entry.id` becomes inside `getCollection`, and it's what `params.slug`
125
- * substitutes into `[...slug].astro` routes. Anything that constructs a
126
- * URL from the raw filesystem path *must* apply the same normalization or
127
- * the URL won't match what Astro actually serves.
128
- *
129
- * Why mirror instead of asking Astro: framework URL builders run at page
130
- * render time (sidebar hrefs) and during the integration's
131
- * `astro:config:setup` (sitemap, llms.txt). At neither point do we have a
132
- * clean way to read Astro's resolved routes. The honest architectural fix
133
- * is to refactor those builders to consume the route manifest at
134
- * `astro:routes:resolved` (sitemap/llms) or via a build-emitted lookup
135
- * table (sidebar). Until that work lands, this helper keeps the URLs
136
- * correct.
137
- *
138
- * Mirror caveat: this matches `github-slugger.slug()` and the trailing-
139
- * /index strip — the documented public-library behaviors Astro inherits
140
- * — not Astro's private routing internals. If a user supplies a custom
141
- * `generateId` to the content loader, or a `data.slug` override in
142
- * frontmatter, this helper doesn't see it. Both are uncommon.
123
+ * what `entry.id` becomes and what `params.slug` substitutes into
124
+ * `[...slug].astro` routes, so anything building a URL from the raw
125
+ * filesystem path must apply the same normalization to match what Astro
126
+ * serves.
127
+ *
128
+ * Caveat: this matches `github-slugger.slug()` and the trailing-/index
129
+ * strip, not a custom loader `generateId` or a `data.slug` frontmatter
130
+ * override neither of which this helper sees.
143
131
  */
144
132
  /** Canonicalize one entry id the way Astro's content layer does. */
145
133
  function canonicalSlug(entryId) {
@@ -489,12 +477,9 @@ function hasActivePage(item, currentPath) {
489
477
  * URL prefix* — e.g. `Components` mounted at `/components/` —
490
478
  * rather than a sub-directory of the primary docs collection.
491
479
  *
492
- * Why this matters: under the previous unconditional behavior, every
493
- * top-level group in the sidebar (including `wip/`, `lab/`, and other
494
- * docs-collection subdirectories) was promoted to a header tab. The
495
- * header rail is meant for "other collections" navigation, not for
496
- * sub-sections of the default collection — those belong in the
497
- * sidebar's own collapsible tree.
480
+ * Sub-directories of the primary collection (`wip/`, `lab/`, etc.) are
481
+ * deliberately excluded the header rail is for cross-collection
482
+ * navigation; sub-sections belong in the sidebar's own tree.
498
483
  *
499
484
  * Caller must pass the *un-scoped* tree (the result of
500
485
  * `buildSidebarTree`, not `getSidebar`); otherwise only the current
@@ -557,16 +542,13 @@ function applyDefaultCollapsed(items) {
557
542
  }
558
543
  }
559
544
  /**
560
- * @deprecated Effective no-op under structural separation. Pre-2026
561
- * Nimbus rendered the group's index as the first child link and used
562
- * this to rename that link to "Overview". The index is now exposed via
563
- * `SidebarGroupItem.indexHref` (the group label IS the link), so there's
564
- * no first-child index to rename. The function is kept so older configs
565
- * that set `sidebar.overviewLabel` don't blow up; future major can drop it.
566
- *
567
- * Renamed only when the first link IS the group's index (matched via the
568
- * `sortKeyByItem` WeakMap) — under structural separation that condition
569
- * never holds, so this silently returns the input unchanged.
545
+ * Relabel a section's landing link to the `overviewLabel` string (default
546
+ * "Overview"). Applies to a `directory:` autogenerate's leading landing
547
+ * link wherever it surfaces (tracked via `directoryIndexLinks`), and to a
548
+ * group whose first child link IS the group's own index (matched via the
549
+ * `sortKeyByItem` WeakMap). Config groups expose their index as the group
550
+ * label itself (`SidebarGroupItem.indexHref`), so those aren't relabelled
551
+ * here — there's no separate child link to rename.
570
552
  */
571
553
  function applyOverviewLabel(items, label) {
572
554
  for (const item of items) if (item.type === "link" && directoryIndexLinks.has(item)) item.label = label;
@@ -1025,7 +1007,7 @@ async function getLastUpdatedFromGit(filePath) {
1025
1007
 
1026
1008
  //#endregion
1027
1009
  //#region src/_internal/admonition-transform.ts
1028
- /** Built-in MyST / Docusaurus / CF admonition types and their Aside mapping. */
1010
+ /** Built-in MyST / Docusaurus admonition types and their Aside mapping. */
1029
1011
  const BUILTIN_TYPES = {
1030
1012
  note: "note",
1031
1013
  info: "note",
@@ -1856,9 +1838,9 @@ async function validateMdxContent(options) {
1856
1838
  * Format a list of failures into a single multi-line error message
1857
1839
  * suitable for `throw new Error(...)`.
1858
1840
  */
1859
- function formatFailures(failures, globalsCount) {
1841
+ function formatFailures(failures) {
1860
1842
  const lines = failures.map((f) => {
1861
- const fix = f.hint ? `Did you mean <${f.hint} />?` : globalsCount === 0 ? `Register it in src/components.ts, or add an explicit \`import\` at the top of this file.` : `Register it in src/components.ts, or add an explicit \`import\` at the top of this file.`;
1843
+ const fix = f.hint ? `Did you mean <${f.hint} />?` : `Register it in src/components.ts, or add an explicit \`import\` at the top of this file.`;
1862
1844
  return ` ${f.filePath}:${f.line}:${f.column} <${f.tag} /> → ${fix}`;
1863
1845
  });
1864
1846
  return `[nimbus-docs] Unknown MDX component ${failures.length === 1 ? "tag" : "tags"}:\n` + lines.join("\n") + "\n\nA PascalCase tag in MDX must either be registered in src/components.ts (the global registry) or imported at the top of the file. Without either, MDX renders the tag as literal text on the page — a silent failure this validator turns into a build error.";
@@ -2086,7 +2068,7 @@ function validateNimbusConfig(input) {
2086
2068
  const tail = received === null ? "" : `\n received: ${received}`;
2087
2069
  return ` - ${display}: ${issue.message}${tail}`;
2088
2070
  }).join("\n");
2089
- throw new Error(`Invalid nimbus.config — fix these issues:\n${issues}\n\nSee https://nimbus-docs.dev/config for the full config schema.`);
2071
+ throw new Error(`Invalid nimbus.config — fix these issues:\n${issues}\n\nSee https://nimbus-docs.com/config for the full config schema.`);
2090
2072
  }
2091
2073
  /**
2092
2074
  * Resolve the value at `path` inside the raw input and format it for an
@@ -2134,18 +2116,11 @@ function virtualConfigPlugin(config, extras) {
2134
2116
  * inside `.md` / `.mdx` files. Output feeds `shikiConfig.langs` so Shiki
2135
2117
  * eager-loads every grammar at startup instead of lazy-loading on first use.
2136
2118
  *
2137
- * Why this matters: Shiki's lazy load assumes every MDX file gets processed
2138
- * during a build. Layer 2 (incremental builds' Vite MDX-skip plugin) breaks
2139
- * that assumption cached MDX files never enter the markdown pipeline, so
2140
- * languages that only appear in cached files would never trigger a grammar
2141
- * load, and any non-cached file using those languages would silently render
2142
- * without highlighting.
2143
- *
2144
- * Eager loading also gives non-incremental users a small predictability win:
2145
- * the highlighter behaves the same regardless of which file is processed
2146
- * first.
2147
- *
2148
- * Cost: ~1s on a 7k-file bench. Acceptable.
2119
+ * Needed because incremental builds skip cached MDX files: those never
2120
+ * enter the markdown pipeline, so a language appearing only in cached
2121
+ * files would never trigger Shiki's lazy grammar load, and a non-cached
2122
+ * file using it would render without highlighting. Eager loading also
2123
+ * keeps highlighting independent of which file is processed first.
2149
2124
  */
2150
2125
  const FENCE_RE = /^[ \t]*```([a-zA-Z][a-zA-Z0-9_+\-]*)/gm;
2151
2126
  async function* walkMdx(dir) {
@@ -2200,16 +2175,23 @@ async function scanCodeBlockLanguages(projectRoot, langAlias = {}) {
2200
2175
  * pages/<aa>/<full-hash>.html — cached HTML body for a page, sharded
2201
2176
  * by the first 2 hex chars of the hash
2202
2177
  *
2203
- * Phase 2 MVP — atomic per-file writes. v2 adds a manifest-level
2204
- * `namespace` field for PR-vs-main isolation; resolution lives in
2205
- * `namespace.ts`. Framework/Node version is folded into `globalHash` via
2206
- * `computeGlobalHash` already, so it doesn't need a separate field.
2178
+ * Atomic per-file writes. A manifest-level `namespace` field provides
2179
+ * PR-vs-main isolation; resolution lives in `namespace.ts`. Framework/Node
2180
+ * version is folded into `globalHash` via `computeGlobalHash` already, so
2181
+ * it doesn't need a separate field.
2207
2182
  */
2208
2183
  const SCHEMA_VERSION = 2;
2209
2184
  var Cache = class {
2210
2185
  root;
2211
- constructor(projectRoot) {
2212
- this.root = resolve(projectRoot, ".nimbus/cache");
2186
+ /**
2187
+ * @param root Absolute path to the cache directory. Callers resolve this —
2188
+ * by default the incremental layer roots it under Astro's own `cacheDir`
2189
+ * (`node_modules/.astro/nimbus`) so it travels with the framework cache
2190
+ * every host already persists between builds (Cloudflare, Vercel,
2191
+ * Netlify, GitHub Actions). Falls back to `<projectRoot>/.nimbus/cache`.
2192
+ */
2193
+ constructor(root) {
2194
+ this.root = root;
2213
2195
  }
2214
2196
  pagePath(hash) {
2215
2197
  return resolve(this.root, "pages", hash.slice(0, 2), `${hash}.html`);
@@ -2265,14 +2247,13 @@ var Cache = class {
2265
2247
  /**
2266
2248
  * Snapshot a *bounded subset* of `dist/_astro/` into the cache.
2267
2249
  *
2268
- * Naive snapshot was unbounded: every warm build accumulated new
2269
- * bundle hashes (vite produces different hashes when the module graph
2270
- * differs between builds) and we kept everything forever. Caller passes
2271
- * the set of asset rel-paths that some cached HTML actually references —
2250
+ * Bounded so the cache doesn't grow forever: Vite emits new bundle
2251
+ * hashes whenever the module graph differs between builds. The caller
2252
+ * passes the asset rel-paths that some cached HTML actually references;
2272
2253
  * anything outside that set gets dropped.
2273
2254
  *
2274
2255
  * `referencedRelPaths` should be the union of every `/_astro/...` URL
2275
- * extracted from cached HTML — see `parseReferencedAssets` in index.ts.
2256
+ * extracted from cached HTML — see `collectReferencedAssets` in index.ts.
2276
2257
  */
2277
2258
  async snapshotAssets(distAstroDir, referencedRelPaths) {
2278
2259
  const target = resolve(this.root, "assets");
@@ -2441,10 +2422,9 @@ async function writeAtomic(path, data) {
2441
2422
  * - pageHash: sha256(page bytes + globalHash). Determines whether a
2442
2423
  * given page's cached HTML is still valid.
2443
2424
  *
2444
- * Phase 2 MVP — deliberately no partial tracking, no data-collection tracking,
2445
- * no component-graph tracking. Phase 3 wires the partial registry into the
2446
- * page hash; that work depends on `validate-mdx-content.ts` being extended
2447
- * to capture `<Render file="…">` references.
2425
+ * Current scope deliberately omits data-collection tracking and
2426
+ * component-graph tracking. Partial-dependency tracking folds the partial
2427
+ * registry into the page hash (see `partial-refs.ts`).
2448
2428
  */
2449
2429
  const TRACKED_DIRS = ["src", "public"];
2450
2430
  const TRACKED_FILES = [
@@ -2507,7 +2487,7 @@ async function walk$1(dir, root) {
2507
2487
  * - Node major version (minor diffs occasionally affect bundling)
2508
2488
  * - Platform + arch (some asset emission is platform-sensitive)
2509
2489
  *
2510
- * Including provenance closes BUG-002 / BUG-003: a framework upgrade
2490
+ * Including provenance closes a class of staleness bug: a framework upgrade
2511
2491
  * (or Node bump, or OS change) silently changed rendered output but the
2512
2492
  * old global hash matched, so warm builds served stale entries from a
2513
2493
  * different version of the world.
@@ -2576,7 +2556,7 @@ async function readDepVersion(projectRoot, dep) {
2576
2556
  }
2577
2557
  }
2578
2558
  /**
2579
- * Phase 3 — per-page hash with transitive partial dependencies folded in.
2559
+ * Per-page hash with transitive partial dependencies folded in.
2580
2560
  *
2581
2561
  * Same shape as `computePageHash` but additionally absorbs the bytes of
2582
2562
  * every partial the page transitively embeds. Sorted by path so two
@@ -2660,14 +2640,14 @@ async function resolveCacheNamespace(projectRoot) {
2660
2640
  //#endregion
2661
2641
  //#region src/_internal/incremental/partial-refs.ts
2662
2642
  /**
2663
- * Phase 3 — partial dependency tracking.
2643
+ * Partial dependency tracking.
2664
2644
  *
2665
2645
  * Walks MDX content to find `<Render file="…" />` and `<Render slug="…" />`
2666
2646
  * references, then builds a per-page transitive closure: "pathname X
2667
2647
  * embeds partials A, B, C — where A in turn embeds D, and B in turn
2668
2648
  * embeds E and F." Folding all of those partials' bytes into the page's
2669
- * hash gives us the property the spec promises: edit one partial, exactly
2670
- * the pages that transitively embed it re-render.
2649
+ * hash gives us the property we want: edit one partial, exactly the pages
2650
+ * that transitively embed it re-render.
2671
2651
  *
2672
2652
  * Scope (v1):
2673
2653
  * - Only string-literal `file` / `slug` props get captured. Dynamic
@@ -2676,16 +2656,17 @@ async function resolveCacheNamespace(projectRoot) {
2676
2656
  * v1 limitation; the `partialResolver` hook (deferred) gives sites
2677
2657
  * an escape valve.
2678
2658
  * - Default resolver: `<Render file="topic/slug" />` resolves to
2679
- * `src/content/partials/topic/slug.mdx`. Matches the bench, apps/www,
2680
- * and mvvmm's PR shape for cloudflare-docs (their resolver also
2681
- * prepends a `product` prop — that needs a custom resolver).
2659
+ * `src/content/partials/topic/slug.mdx`. Sites with a multi-prop
2660
+ * convention (e.g. a resolver that prepends a `product` prop) need a
2661
+ * custom resolver.
2682
2662
  * - Cycles in the partial graph are handled (visited set).
2683
2663
  */
2684
2664
  /**
2685
2665
  * Check `candidate` is a normalised path under `rootWithSep`. Cheap
2686
2666
  * defense against `../` traversal escaping the partials root. We use a
2687
2667
  * trailing-sep marker on root to avoid false-matching `partialsRoot` with
2688
- * `partialsRoot-evil/` style siblings.
2668
+ * sibling directories that share its name as a prefix (e.g.
2669
+ * `partialsRoot-shared/`).
2689
2670
  */
2690
2671
  function isInside(candidate, rootWithSep) {
2691
2672
  return candidate.startsWith(rootWithSep) || candidate === rootWithSep.slice(0, -1);
@@ -2708,7 +2689,7 @@ const ATTR_RE = /([a-zA-Z][a-zA-Z0-9_]*)\s*=\s*["']([^"']*)["']/g;
2708
2689
  * extensions or use plain Markdown for partials.
2709
2690
  *
2710
2691
  * `partialsBase` lets callers point the resolver at a non-default partials
2711
- * collection base (closes BUG-040). Default: `src/content/partials`.
2692
+ * collection base. Default: `src/content/partials`.
2712
2693
  */
2713
2694
  function makeDefaultPartialResolver(projectRoot, partialsBase = "src/content/partials") {
2714
2695
  const partialsRoot = resolve(projectRoot, partialsBase);
@@ -2877,7 +2858,7 @@ async function partialsDirExists(projectRoot, partialsBase = "src/content/partia
2877
2858
  //#endregion
2878
2859
  //#region src/_internal/incremental/index.ts
2879
2860
  /**
2880
- * Incremental builds — Phase 2 MVP.
2861
+ * Incremental builds.
2881
2862
  *
2882
2863
  * Wires the cache layer into Astro's prerenderer. On warm build, pages whose
2883
2864
  * source bytes (and the global hash) haven't changed since the last build
@@ -2886,16 +2867,14 @@ async function partialsDirExists(projectRoot, partialsBase = "src/content/partia
2886
2867
  *
2887
2868
  * Astro sees every route in `getStaticPaths` either way — cache hits flow
2888
2869
  * through `astro:build:generated`, adapter writers, route-headers accounting
2889
- * exactly like fresh renders. This is the spec's design, *not* mvvmm's
2890
- * `getStaticPaths`-filtering approach, because the latter hides cached
2891
- * routes from downstream hooks.
2870
+ * exactly like fresh renders. This is by design rather than filtering
2871
+ * cached routes out of `getStaticPaths`, which would hide them from
2872
+ * downstream hooks.
2892
2873
  *
2893
- * Anti-goals for this MVP (deferred to Phase 3+):
2894
- * - Partial-dependency tracking. Edit a partial → still full rebuild today.
2874
+ * Out of scope for now:
2895
2875
  * - Data-collection scoping.
2896
2876
  * - Component-graph tracking. Any tracked-file change → full rebuild.
2897
- * - Provenance / namespacing / trust boundary. Hardening track.
2898
- * - `nimbus build --explain` and structured build reports. Console log only.
2877
+ * - `nimbus build --explain` and structured build reports.
2899
2878
  */
2900
2879
  /**
2901
2880
  * Normalise a request URL to its canonical pathname (no trailing slash,
@@ -2914,7 +2893,7 @@ function canonicalisePathname(input) {
2914
2893
  }
2915
2894
  /**
2916
2895
  * Build a map from pathname → MDX file bytes by walking the docs collection
2917
- * directory. Phase 2 MVP only handles the primary `docs` collection.
2896
+ * directory. Only the primary `docs` collection is handled.
2918
2897
  *
2919
2898
  * Pathname derivation: `src/content/docs/<entry.id>.mdx` → `/<entry.id>`,
2920
2899
  * mirroring `getDocsStaticPaths` which uses `entry.id` verbatim as slug.
@@ -3012,12 +2991,12 @@ async function collectDocsPages(projectRoot, docsBase = "src/content/docs") {
3012
2991
  * Computes per-page hashes, reads prior manifest, determines which pages
3013
2992
  * are cache-hits.
3014
2993
  *
3015
- * Phase 3 — the page hash includes the bytes of every partial the page
3016
- * transitively embeds, so editing a partial invalidates exactly the pages
3017
- * that reference it (directly or transitively) and nothing else.
2994
+ * The page hash includes the bytes of every partial the page transitively
2995
+ * embeds, so editing a partial invalidates exactly the pages that reference
2996
+ * it (directly or transitively) and nothing else.
3018
2997
  */
3019
- async function setupIncrementalContext(projectRoot, logger, partialResolver) {
3020
- const cache = new Cache(projectRoot);
2998
+ async function setupIncrementalContext(projectRoot, cacheDir, logger, partialResolver) {
2999
+ const cache = new Cache(cacheDir ? resolve(cacheDir, "nimbus") : resolve(projectRoot, ".nimbus/cache"));
3021
3000
  const globalHash = await computeGlobalHash(projectRoot);
3022
3001
  const namespace = await resolveCacheNamespace(projectRoot);
3023
3002
  const priorManifest = await cache.readManifest();
@@ -3074,7 +3053,7 @@ async function setupIncrementalContext(projectRoot, logger, partialResolver) {
3074
3053
  /**
3075
3054
  * Wrap an Astro prerenderer with the cache.
3076
3055
  *
3077
- * Strategy (mvvmm-style — chosen empirically over the "wrap Response" approach
3056
+ * Strategy (chosen empirically over the "wrap Response" approach
3078
3057
  * because Astro's per-route work outside `render` is the actual dominant cost,
3079
3058
  * not MDX→HTML conversion):
3080
3059
  *
@@ -3087,12 +3066,12 @@ async function setupIncrementalContext(projectRoot, logger, partialResolver) {
3087
3066
  * `dist/<pathname>/index.html` for the filtered cached routes — Astro
3088
3067
  * never wrote them, so we do.
3089
3068
  *
3090
- * Trade-off vs. the spec's "wrap Response in render" design: downstream
3069
+ * Trade-off vs. the "wrap Response in render" design: downstream
3091
3070
  * Astro hooks (`astro:build:generated`, adapter writers, route accounting)
3092
3071
  * don't see cached routes. For Cloudflare adapter sites or anything that
3093
3072
  * depends on every route being visible to those hooks, this matters.
3094
3073
  * For static SSG sites where the rendered HTML *is* the output, it's fine.
3095
- * Documented as a limitation in Phase 5 user-facing notes.
3074
+ * Documented as a known limitation.
3096
3075
  */
3097
3076
  function wrapPrerenderer(defaultPrerenderer, ctx) {
3098
3077
  return {
@@ -3169,7 +3148,7 @@ async function restoreCachedPagesToDist(ctx, outDir) {
3169
3148
  * (so the snapshot is the union of fresh + previously-cached assets the
3170
3149
  * cached HTML still references).
3171
3150
  *
3172
- * BUG-007 fix: bounded to assets actually referenced by cached HTML. We
3151
+ * Bounded to assets actually referenced by cached HTML. We
3173
3152
  * walk every cached page's bytes, regex-extract `/_astro/...` URLs,
3174
3153
  * dedupe — and only persist those. Without this the snapshot grew
3175
3154
  * unboundedly because vite produces new bundle hashes on every warm
@@ -3186,7 +3165,7 @@ const ASSET_REF_RE = /\/_astro\/([^"')\s>]+)/g;
3186
3165
  * Strip query string and hash from an extracted asset path. Without
3187
3166
  * this, `/_astro/foo.js?v=1` would record `foo.js?v=1` as the file
3188
3167
  * name — the snapshot would skip it because no such file exists in
3189
- * `_astro/`, leaving the warm build with a broken reference (BUG-108).
3168
+ * `_astro/`, leaving the warm build with a broken reference.
3190
3169
  */
3191
3170
  function normaliseAssetRef(raw) {
3192
3171
  if (!raw) return null;
@@ -3206,10 +3185,9 @@ function normaliseAssetRef(raw) {
3206
3185
  *
3207
3186
  * The single regex matches `/_astro/...` anywhere in the HTML —
3208
3187
  * straightforward for `href="..."`, `src="..."`, `url(...)` in inline
3209
- * styles, and individual `srcset` URLs alike. (BUG-107 was about the
3210
- * earlier regex anchoring on a quote/paren prefix and missing the
3211
- * second+nth URL inside a `srcset` value; the unanchored form here
3212
- * catches them all.)
3188
+ * styles, and individual `srcset` URLs alike. (An earlier regex
3189
+ * anchored on a quote/paren prefix and missed the second+nth URL
3190
+ * inside a `srcset` value; the unanchored form here catches them all.)
3213
3191
  *
3214
3192
  * We scan the dist output rather than the in-memory cache because dist
3215
3193
  * is the source of truth for what's currently referenced — after the
@@ -3328,8 +3306,8 @@ function mdxSkipPlugin(ctx) {
3328
3306
  * - sitemap-0.xml carries all entries (we don't split until >45k urls)
3329
3307
  * - sitemap-index.xml lists sitemap-0.xml only
3330
3308
  *
3331
- * v1 scope no custom `serialize` yet (deferred; cloudflare-docs case),
3332
- * no `lastmod`, `changefreq`, `priority`, no image/video sitemaps. Matches
3309
+ * Scope: an optional `serialize` hook per URL, but no `lastmod`,
3310
+ * `changefreq`, `priority`, and no image/video sitemaps. Matches
3333
3311
  * `@astrojs/sitemap` *default* output for sites that don't override.
3334
3312
  */
3335
3313
  const URLSET_XMLNS = "xmlns=\"http://www.sitemaps.org/schemas/sitemap/0.9\" xmlns:news=\"http://www.google.com/schemas/sitemap-news/0.9\" xmlns:xhtml=\"http://www.w3.org/1999/xhtml\" xmlns:image=\"http://www.google.com/schemas/sitemap-image/1.1\" xmlns:video=\"http://www.google.com/schemas/sitemap-video/1.1\"";
@@ -3480,8 +3458,7 @@ function extractFrontmatter(source) {
3480
3458
  const rest = source.slice(afterFirstMarker + 1);
3481
3459
  const closingMatch = rest.match(/(^|\n)---\s*(\n|$)/);
3482
3460
  if (!closingMatch || closingMatch.index === void 0) return null;
3483
- const endIndex = closingMatch[1] === "\n" ? closingMatch.index : closingMatch.index;
3484
- return rest.slice(0, endIndex);
3461
+ return rest.slice(0, closingMatch.index);
3485
3462
  }
3486
3463
  /**
3487
3464
  * Find a top-level boolean field in YAML frontmatter. Returns the
@@ -3512,9 +3489,8 @@ function parseBoolField(yaml, field) {
3512
3489
  * - `string[]` for either array form
3513
3490
  * - `undefined` if absent
3514
3491
  *
3515
- * The multiline form is the canonical YAML list syntax; the scanner
3516
- * previously accepted only scalar and inline-array, which silently
3517
- * dropped valid block lists at build time. The schema validates the
3492
+ * All three forms are accepted: scalar, inline array, and the multiline
3493
+ * block list (canonical YAML list syntax). The schema validates the
3518
3494
  * post-parse shape; the scanner has to match it.
3519
3495
  */
3520
3496
  function parsePreviousSlugField(yaml) {
@@ -3792,6 +3768,7 @@ function nimbus(rawConfig, options = {}) {
3792
3768
  collections: options.collections
3793
3769
  }, IMPLEMENTED_CODES);
3794
3770
  let projectRootForBuild = "";
3771
+ let cacheDirForBuild = "";
3795
3772
  let astroBaseForBuild = "";
3796
3773
  let incrementalCtx = null;
3797
3774
  const mdxSkipCtx = createMdxSkipContext();
@@ -3816,12 +3793,13 @@ function nimbus(rawConfig, options = {}) {
3816
3793
  skip: validateOpts.skip,
3817
3794
  projectRoot
3818
3795
  });
3819
- if (failures.length > 0) throw new Error(formatFailures(failures, globals.length));
3796
+ if (failures.length > 0) throw new Error(formatFailures(failures));
3820
3797
  logger.info(`MDX validation passed — ${globals.length} global component${globals.length === 1 ? "" : "s"} registered, ${contentDirs.length} content dir${contentDirs.length === 1 ? "" : "s"} scanned.`);
3821
3798
  }
3822
3799
  }
3823
3800
  const projectRoot = fileURLToPath(astroConfig.root);
3824
3801
  projectRootForBuild = projectRoot;
3802
+ cacheDirForBuild = fileURLToPath(astroConfig.cacheDir);
3825
3803
  astroBaseForBuild = astroConfig.base ?? "";
3826
3804
  const codeBlockLangs = await scanCodeBlockLanguages(projectRoot, SHIKI_LANG_ALIAS);
3827
3805
  const contentConfigPath = path.join(projectRoot, "src/content.config.ts");
@@ -3936,7 +3914,7 @@ function nimbus(rawConfig, options = {}) {
3936
3914
  logger.warn("[incremental] project root unknown at build:start; cache disabled this run");
3937
3915
  return;
3938
3916
  }
3939
- incrementalCtx = await setupIncrementalContext(projectRootForBuild, logger, options.partialResolver);
3917
+ incrementalCtx = await setupIncrementalContext(projectRootForBuild, cacheDirForBuild || void 0, logger, options.partialResolver);
3940
3918
  mdxSkipCtx.cachedAbsolutePaths.clear();
3941
3919
  for (const pathname of incrementalCtx.cacheableHits) {
3942
3920
  const filePath = incrementalCtx.filePathByPathname.get(pathname);
@@ -4066,9 +4044,9 @@ function runPagefind(siteDir) {
4066
4044
  /**
4067
4045
  * Main entry for `nimbus-docs`.
4068
4046
  *
4069
- * Exports the Astro integration (default), config helper, and the four
4070
- * data helpers (sidebar, prev/next, breadcrumbs, TOC). Phase 6 will
4071
- * add page composition helpers (`getDocsStaticPaths`, `getDocsPageProps`).
4047
+ * Exports the Astro integration (default), config helper, the data helpers
4048
+ * (sidebar, prev/next, breadcrumbs, TOC), and the page composition helpers
4049
+ * (`getDocsStaticPaths`, `getDocsPageProps`).
4072
4050
  *
4073
4051
  * Helpers read the user's config from `virtual:nimbus/config` (provided
4074
4052
  * by our Vite plugin) and content entries from `astro:content`. Both
@@ -4317,7 +4295,7 @@ async function getPrevNext(currentSlug, options) {
4317
4295
  /**
4318
4296
  * Build breadcrumb trail from "/" to the current page.
4319
4297
  *
4320
- * Phase 5: simple URL-segment derivation. Later phases may enrich with
4298
+ * Simple URL-segment derivation. A later iteration may enrich this with
4321
4299
  * sidebar-aware labels.
4322
4300
  */
4323
4301
  async function getBreadcrumbs(currentSlug, options) {