@nu-appdev/northwestern-starlight-theme 1.6.0 → 1.6.1

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/CHANGELOG.md CHANGED
@@ -7,6 +7,18 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [1.6.1] - 2026-08-20
11
+
12
+ ### Fixed
13
+
14
+ - **Starlight plugins that extend the Markdown processor no longer stop working.** Astro 7 renders Markdown with the Sätteri processor and hands every integration the same live processor object. Plugins extend the pipeline by changing that object. The theme replaced it with a `unified()` processor from two `astro:config:setup` hooks, which run after Starlight has already set up its plugins, so everything those plugins had registered was thrown away.
15
+
16
+ The visible effect was `starlight-links-validator` 0.25 or newer: it validated zero links on every build and still printed "All internal links are valid", so broken internal links passed unnoticed. Any plugin that configures itself against the Sätteri processor was affected the same way.
17
+
18
+ `defineNorthwesternConfig()` now sets `markdown.processor` to `unified()` while `astro.config.*` is evaluated, before `starlight()` is created. No plugin ever sees Sätteri, and the theme only extends the processor it is given instead of replacing it. A processor you configure yourself is left alone.
19
+
20
+ - Mermaid no longer swaps the Markdown processor either. `astro-mermaid` 2.1 supports Sätteri on its own.
21
+
10
22
  ## [1.6.0] - 2026-07-28
11
23
 
12
24
  ### Added
@@ -191,7 +203,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
191
203
  - OpenAPI plugin compatibility with method badge preservation
192
204
  - Reduced motion support for transitions
193
205
 
194
- [Unreleased]: https://github.com/NIT-Administrative-Systems/northwestern-starlight-theme/compare/v1.6.0...HEAD
206
+ [Unreleased]: https://github.com/NIT-Administrative-Systems/northwestern-starlight-theme/compare/v1.6.1...HEAD
207
+ [1.6.1]: https://github.com/NIT-Administrative-Systems/northwestern-starlight-theme/compare/v1.6.0...v1.6.1
195
208
  [1.6.0]: https://github.com/NIT-Administrative-Systems/northwestern-starlight-theme/compare/v1.5.1...v1.6.0
196
209
  [1.5.1]: https://github.com/NIT-Administrative-Systems/northwestern-starlight-theme/compare/v1.5.0...v1.5.1
197
210
  [1.5.0]: https://github.com/NIT-Administrative-Systems/northwestern-starlight-theme/compare/v1.4.0...v1.5.0
package/config.ts CHANGED
@@ -15,6 +15,7 @@ import {
15
15
  } from "./legacy-html-redirects";
16
16
  import { type NorthwesternMermaidOptions, northwesternMermaid } from "./mermaid";
17
17
  import { northwesternConfigOptionsSchema, validateSchema } from "./src/config-schema";
18
+ import { type MarkdownConfigLike, withUnifiedProcessor } from "./src/markdown-processor";
18
19
 
19
20
  /**
20
21
  * Vite plugin that enables `pluginLineNumbers` for both markdown code blocks
@@ -333,6 +334,14 @@ export function defineNorthwesternConfig(options: NorthwesternConfigOptions): As
333
334
  return {
334
335
  ...astroConfig,
335
336
  integrations,
337
+ // Pin the Markdown processor to `unified()` here, at `astro.config.*`
338
+ // evaluation time, so `starlight()` and every Starlight plugin observes it
339
+ // from the start. Doing this later from an `astro:config:setup` hook means
340
+ // replacing a processor plugins have already registered against — which
341
+ // silently drops those registrations (see `src/markdown-processor.ts`).
342
+ markdown: withUnifiedProcessor(
343
+ astroConfig.markdown as MarkdownConfigLike | undefined,
344
+ ) as AstroUserConfig["markdown"],
336
345
  ...(mergedRedirects ? { redirects: mergedRedirects } : {}),
337
346
  vite: {
338
347
  ...existingViteConfig,
@@ -1 +1 @@
1
- {"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../config.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,eAAe,EAAE,mBAAmB,EAAE,MAAM,0BAA0B,CAAC;AAErF,OAAO,KAAK,EAAE,gBAAgB,EAAE,eAAe,EAAE,MAAM,OAAO,CAAC;AAC/D,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,SAAS,CAAC;AAEvD,OAAO,EAEH,KAAK,0BAA0B,EAElC,MAAM,yBAAyB,CAAC;AACjC,OAAO,EAAE,KAAK,0BAA0B,EAAuB,MAAM,WAAW,CAAC;AA2EjF;;;;;;;;GAQG;AACH,wBAAgB,qBAAqB,CACjC,WAAW,EAAE,eAAe,EAC5B,gBAAgB,EAAE,eAAe,EAAE,EACnC,aAAa,EAAE,eAAe,EAAE,GACjC,eAAe,EAAE,CAmBnB;AAED;;;;;;;GAOG;AACH,MAAM,MAAM,yBAAyB,GAAG,IAAI,CAAC,eAAe,EAAE,cAAc,CAAC,GAAG;IAC5E,wFAAwF;IACxF,SAAS,EAAE,mBAAmB,CAAC;IAE/B;;;;OAIG;IACH,KAAK,CAAC,EAAE,uBAAuB,CAAC;IAEhC;;;;;;;;;OASG;IACH,OAAO,CAAC,EAAE,OAAO,GAAG,0BAA0B,CAAC;IAE/C;;;;OAIG;IACH,OAAO,CAAC,EAAE,eAAe,EAAE,CAAC;IAE5B;;;;;;;;;;;;;;OAcG;IACH,mBAAmB,CAAC,EAAE,OAAO,GAAG,0BAA0B,CAAC;IAE3D;;;;;OAKG;IACH,YAAY,CAAC,EAAE;QACX,MAAM,CAAC,EAAE,gBAAgB,EAAE,CAAC;QAC5B,KAAK,CAAC,EAAE,gBAAgB,EAAE,CAAC;KAC9B,CAAC;CACL,CAAC;AAEF;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,wBAAgB,wBAAwB,CAAC,OAAO,EAAE,yBAAyB,GAAG,eAAe,CAiI5F"}
1
+ {"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../config.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,eAAe,EAAE,mBAAmB,EAAE,MAAM,0BAA0B,CAAC;AAErF,OAAO,KAAK,EAAE,gBAAgB,EAAE,eAAe,EAAE,MAAM,OAAO,CAAC;AAC/D,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,SAAS,CAAC;AAEvD,OAAO,EAEH,KAAK,0BAA0B,EAElC,MAAM,yBAAyB,CAAC;AACjC,OAAO,EAAE,KAAK,0BAA0B,EAAuB,MAAM,WAAW,CAAC;AA4EjF;;;;;;;;GAQG;AACH,wBAAgB,qBAAqB,CACjC,WAAW,EAAE,eAAe,EAC5B,gBAAgB,EAAE,eAAe,EAAE,EACnC,aAAa,EAAE,eAAe,EAAE,GACjC,eAAe,EAAE,CAmBnB;AAED;;;;;;;GAOG;AACH,MAAM,MAAM,yBAAyB,GAAG,IAAI,CAAC,eAAe,EAAE,cAAc,CAAC,GAAG;IAC5E,wFAAwF;IACxF,SAAS,EAAE,mBAAmB,CAAC;IAE/B;;;;OAIG;IACH,KAAK,CAAC,EAAE,uBAAuB,CAAC;IAEhC;;;;;;;;;OASG;IACH,OAAO,CAAC,EAAE,OAAO,GAAG,0BAA0B,CAAC;IAE/C;;;;OAIG;IACH,OAAO,CAAC,EAAE,eAAe,EAAE,CAAC;IAE5B;;;;;;;;;;;;;;OAcG;IACH,mBAAmB,CAAC,EAAE,OAAO,GAAG,0BAA0B,CAAC;IAE3D;;;;;OAKG;IACH,YAAY,CAAC,EAAE;QACX,MAAM,CAAC,EAAE,gBAAgB,EAAE,CAAC;QAC5B,KAAK,CAAC,EAAE,gBAAgB,EAAE,CAAC;KAC9B,CAAC;CACL,CAAC;AAEF;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,wBAAgB,wBAAwB,CAAC,OAAO,EAAE,yBAAyB,GAAG,eAAe,CAyI5F"}
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../index.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,0BAA0B,CAAC;AAIhE;;;;;;;;;;;;;;;;GAgBG;AACH,MAAM,WAAW,0BAA0B;IACvC;;;;;OAKG;IACH,MAAM,CAAC,EAAE,UAAU,GAAG,OAAO,CAAC;IAE9B;;;;;;OAMG;IACH,SAAS,CAAC,EAAE,OAAO,CAAC;IAEpB;;;;;;OAMG;IACH,UAAU,CAAC,EAAE,MAAM,CAAC;CACvB;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,MAAM,WAAW,uBAAuB;IACpC;;;;OAIG;IACH,QAAQ,CAAC,EAAE,0BAA0B,CAAC;IAEtC;;;;;;;;;OASG;IACH,OAAO,CAAC,EAAE,OAAO,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAE5C;;;;;;;;;OASG;IACH,OAAO,CAAC,EAAE,OAAO,CAAC;CACrB;AA6BD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AACH,MAAM,CAAC,OAAO,UAAU,iBAAiB,CAAC,MAAM,GAAE,uBAA4B,GAAG,eAAe,CAgQ/F"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../index.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,0BAA0B,CAAC;AAKhE;;;;;;;;;;;;;;;;GAgBG;AACH,MAAM,WAAW,0BAA0B;IACvC;;;;;OAKG;IACH,MAAM,CAAC,EAAE,UAAU,GAAG,OAAO,CAAC;IAE9B;;;;;;OAMG;IACH,SAAS,CAAC,EAAE,OAAO,CAAC;IAEpB;;;;;;OAMG;IACH,UAAU,CAAC,EAAE,MAAM,CAAC;CACvB;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,MAAM,WAAW,uBAAuB;IACpC;;;;OAIG;IACH,QAAQ,CAAC,EAAE,0BAA0B,CAAC;IAEtC;;;;;;;;;OASG;IACH,OAAO,CAAC,EAAE,OAAO,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAE5C;;;;;;;;;OASG;IACH,OAAO,CAAC,EAAE,OAAO,CAAC;CACrB;AA6BD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AACH,MAAM,CAAC,OAAO,UAAU,iBAAiB,CAAC,MAAM,GAAE,uBAA4B,GAAG,eAAe,CAyP/F"}
package/dist/mermaid.d.ts CHANGED
@@ -69,8 +69,8 @@ export declare const darkMermaidConfig: AstroMermaidOptions;
69
69
  * This function is only needed for manual setups.
70
70
  *
71
71
  * **Must be added after `starlight()` in the `integrations` array.** This lets
72
- * `astro-mermaid` detect and extend Starlight's active Markdown processor,
73
- * including Astro 7's Sätteri pipeline.
72
+ * `astro-mermaid` extend the active Markdown processor after Starlight and its
73
+ * plugins have configured it.
74
74
  *
75
75
  * @param options - Merged with Northwestern defaults. Set `toolbar: false` to
76
76
  * disable the hover toolbar.
@@ -1 +1 @@
1
- {"version":3,"file":"mermaid.d.ts","sourceRoot":"","sources":["../mermaid.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,OAAO,CAAC;AAC9C,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,eAAe,CAAC;AAIzD;;;;;;;GAOG;AACH,MAAM,WAAW,0BAA2B,SAAQ,mBAAmB;IACnE;;;;OAIG;IACH,OAAO,CAAC,EAAE,OAAO,CAAC;CACrB;AAED;;;;;;GAMG;AACH,MAAM,MAAM,uBAAuB,GAAG,OAAO,GAAG,MAAM,CAAC;AAwUvD;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,+BAA+B,CAAC,IAAI,EAAE,uBAAuB,GAAG,mBAAmB,CAmBlG;AAED;;;;;;GAMG;AACH,eAAO,MAAM,oBAAoB,EAAE,mBAA8D,CAAC;AAElG;;;;;GAKG;AACH,eAAO,MAAM,iBAAiB,EAAE,mBAA6D,CAAC;AAE9F;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgCG;AACH,wBAAgB,mBAAmB,CAAC,OAAO,GAAE,0BAA+B,GAAG,gBAAgB,CA+E9F"}
1
+ {"version":3,"file":"mermaid.d.ts","sourceRoot":"","sources":["../mermaid.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,OAAO,CAAC;AAC9C,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,eAAe,CAAC;AAIzD;;;;;;;GAOG;AACH,MAAM,WAAW,0BAA2B,SAAQ,mBAAmB;IACnE;;;;OAIG;IACH,OAAO,CAAC,EAAE,OAAO,CAAC;CACrB;AAED;;;;;;GAMG;AACH,MAAM,MAAM,uBAAuB,GAAG,OAAO,GAAG,MAAM,CAAC;AAwUvD;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,+BAA+B,CAAC,IAAI,EAAE,uBAAuB,GAAG,mBAAmB,CAmBlG;AAED;;;;;;GAMG;AACH,eAAO,MAAM,oBAAoB,EAAE,mBAA8D,CAAC;AAElG;;;;;GAKG;AACH,eAAO,MAAM,iBAAiB,EAAE,mBAA6D,CAAC;AAE9F;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgCG;AACH,wBAAgB,mBAAmB,CAAC,OAAO,GAAE,0BAA+B,GAAG,gBAAgB,CAsD9F"}
@@ -0,0 +1,86 @@
1
+ /**
2
+ * Helpers for Astro's `markdown.processor` API.
3
+ *
4
+ * Astro 6.4 introduced `markdown.processor`, and Astro 7 changed its default
5
+ * from `unified()` (`@astrojs/markdown-remark`) to `satteri()`
6
+ * (`@astrojs/markdown-satteri`). Both are *live objects*: integrations and
7
+ * Starlight plugins extend the Markdown pipeline by mutating the processor
8
+ * instance they are handed — pushing into `processor.options.*Plugins`, or
9
+ * wrapping `processor.createRenderer`.
10
+ *
11
+ * That makes processor **replacement** destructive. Anything registered on the
12
+ * outgoing object is dropped, and because most registrations are optional
13
+ * behaviour, the failure is silent: `starlight-links-validator` swapped for a
14
+ * fresh processor simply validates zero links and reports success.
15
+ *
16
+ * The theme therefore installs a `unified()` processor in
17
+ * `defineNorthwesternConfig()` — at `astro.config.*` evaluation time, before
18
+ * `starlight()` is constructed and before any plugin can observe the default —
19
+ * and from then on only ever *extends* the processor it is given.
20
+ */
21
+ /**
22
+ * Structural shape of `markdown.processor`.
23
+ *
24
+ * Declared locally because `AstroUserConfig["markdown"]` only gained the
25
+ * `processor` key in Astro 6.4, and the theme supports Astro 5 and 6 as well.
26
+ */
27
+ export interface MarkdownProcessorLike {
28
+ name: string;
29
+ options?: {
30
+ remarkPlugins?: unknown[];
31
+ rehypePlugins?: unknown[];
32
+ mdastPlugins?: unknown[];
33
+ hastPlugins?: unknown[];
34
+ [key: string]: unknown;
35
+ };
36
+ createRenderer?: unknown;
37
+ }
38
+ /**
39
+ * Structural shape of `markdown` in an Astro config, including the keys the
40
+ * installed Astro version may not know about yet.
41
+ */
42
+ export interface MarkdownConfigLike {
43
+ processor?: MarkdownProcessorLike;
44
+ remarkPlugins?: unknown[];
45
+ rehypePlugins?: unknown[];
46
+ remarkRehype?: Record<string, unknown>;
47
+ gfm?: boolean;
48
+ smartypants?: unknown;
49
+ [key: string]: unknown;
50
+ }
51
+ /** A `unified()` processor from `@astrojs/markdown-remark`. */
52
+ export declare function isUnifiedProcessor(processor: MarkdownProcessorLike | undefined): processor is MarkdownProcessorLike & {
53
+ options: {
54
+ rehypePlugins: unknown[];
55
+ };
56
+ };
57
+ /**
58
+ * Return a `markdown` config that is guaranteed to use a `unified()` processor.
59
+ *
60
+ * Call this while building the Astro config — before `starlight()` runs — so no
61
+ * plugin ever observes the Sätteri default. A processor the consumer supplied
62
+ * themselves is respected as-is (a non-`unified` one earns a warning, because
63
+ * several Starlight plugins the theme ships with only support `unified`).
64
+ *
65
+ * User-supplied `remarkPlugins` / `rehypePlugins` are deliberately *not* copied
66
+ * into the processor: Astro migrates those deprecated top-level arrays onto a
67
+ * `unified` processor itself (exactly once, tracked internally), and on Astro
68
+ * versions without `markdown.processor` they are the whole pipeline. Baking
69
+ * copies in here would run them twice.
70
+ */
71
+ export declare function withUnifiedProcessor(markdown: MarkdownConfigLike | undefined, warn?: (message: string) => void): MarkdownConfigLike;
72
+ /**
73
+ * Register a rehype plugin on the active Markdown pipeline.
74
+ *
75
+ * Returns a `markdown` config patch to hand to `updateConfig()`, or `undefined`
76
+ * when the plugin was added to the live processor and no config update is
77
+ * needed. Mutating `processor.options` is how Astro itself extends a processor
78
+ * after config resolution — the object identity survives config validation
79
+ * precisely so that late registrations still apply.
80
+ *
81
+ * A non-`unified` processor is left alone. Replacing it here is what used to
82
+ * discard Sätteri-registered plugins from unrelated Starlight plugins, so the
83
+ * theme warns instead of swapping behind their back.
84
+ */
85
+ export declare function addRehypePlugin(markdown: MarkdownConfigLike | undefined, plugin: unknown, warn?: (message: string) => void): MarkdownConfigLike | undefined;
86
+ //# sourceMappingURL=markdown-processor.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"markdown-processor.d.ts","sourceRoot":"","sources":["../../src/markdown-processor.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAIH;;;;;GAKG;AACH,MAAM,WAAW,qBAAqB;IAClC,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,CAAC,EAAE;QACN,aAAa,CAAC,EAAE,OAAO,EAAE,CAAC;QAC1B,aAAa,CAAC,EAAE,OAAO,EAAE,CAAC;QAC1B,YAAY,CAAC,EAAE,OAAO,EAAE,CAAC;QACzB,WAAW,CAAC,EAAE,OAAO,EAAE,CAAC;QACxB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;KAC1B,CAAC;IACF,cAAc,CAAC,EAAE,OAAO,CAAC;CAC5B;AAED;;;GAGG;AACH,MAAM,WAAW,kBAAkB;IAC/B,SAAS,CAAC,EAAE,qBAAqB,CAAC;IAClC,aAAa,CAAC,EAAE,OAAO,EAAE,CAAC;IAC1B,aAAa,CAAC,EAAE,OAAO,EAAE,CAAC;IAC1B,YAAY,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACvC,GAAG,CAAC,EAAE,OAAO,CAAC;IACd,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;CAC1B;AAED,+DAA+D;AAC/D,wBAAgB,kBAAkB,CAC9B,SAAS,EAAE,qBAAqB,GAAG,SAAS,GAC7C,SAAS,IAAI,qBAAqB,GAAG;IAAE,OAAO,EAAE;QAAE,aAAa,EAAE,OAAO,EAAE,CAAA;KAAE,CAAA;CAAE,CAEhF;AAkBD;;;;;;;;;;;;;GAaG;AACH,wBAAgB,oBAAoB,CAChC,QAAQ,EAAE,kBAAkB,GAAG,SAAS,EACxC,IAAI,GAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAmB,GAC/C,kBAAkB,CAwBpB;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,eAAe,CAC3B,QAAQ,EAAE,kBAAkB,GAAG,SAAS,EACxC,MAAM,EAAE,OAAO,EACf,IAAI,GAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAmB,GAC/C,kBAAkB,GAAG,SAAS,CA6BhC"}
package/index.ts CHANGED
@@ -3,9 +3,9 @@ import type { IncomingMessage, ServerResponse } from "node:http";
3
3
  import { createRequire } from "node:module";
4
4
  import { dirname, join } from "node:path";
5
5
  import { fileURLToPath } from "node:url";
6
- import { unified } from "@astrojs/markdown-remark";
7
6
  import type { StarlightPlugin } from "@astrojs/starlight/types";
8
7
  import { nonEmptyStringSchema, northwesternThemeConfigSchema, validateSchema } from "./src/config-schema";
8
+ import { addRehypePlugin, type MarkdownConfigLike } from "./src/markdown-processor";
9
9
  import rehypeTableScroll from "./src/rehype-table-scroll";
10
10
 
11
11
  /**
@@ -204,24 +204,17 @@ export default function northwesternTheme(config: NorthwesternThemeConfig = {}):
204
204
  name: "northwestern-theme-config",
205
205
  hooks: {
206
206
  "astro:config:setup": ({ config: astroConfig, updateConfig: updateAstroConfig }) => {
207
- const markdown = astroConfig.markdown ?? {};
208
- const markdownConfig =
209
- markdown.processor?.name === "satteri"
210
- ? {
211
- processor: unified({
212
- remarkPlugins: markdown.remarkPlugins,
213
- rehypePlugins: [...(markdown.rehypePlugins ?? []), rehypeTableScroll],
214
- remarkRehype: markdown.remarkRehype,
215
- gfm: markdown.gfm,
216
- smartypants: markdown.smartypants,
217
- }),
218
- }
219
- : {
220
- rehypePlugins: [...(markdown.rehypePlugins ?? []), rehypeTableScroll],
221
- };
207
+ // Extend whichever Markdown pipeline is active — never replace it.
208
+ // `defineNorthwesternConfig()` has already pinned a `unified()`
209
+ // processor before any plugin could register against it.
210
+ const markdownConfig = addRehypePlugin(
211
+ astroConfig.markdown as MarkdownConfigLike | undefined,
212
+ rehypeTableScroll,
213
+ (message) => logger.warn(message),
214
+ );
222
215
 
223
216
  updateAstroConfig({
224
- markdown: markdownConfig,
217
+ ...(markdownConfig ? { markdown: markdownConfig } : {}),
225
218
  vite: {
226
219
  plugins: [
227
220
  {
package/mermaid.ts CHANGED
@@ -1,4 +1,3 @@
1
- import { unified } from "@astrojs/markdown-remark";
2
1
  import type { AstroIntegration } from "astro";
3
2
  import type { AstroMermaidOptions } from "astro-mermaid";
4
3
  import { darken, isDark, lighten, mix, transparentize } from "khroma";
@@ -423,8 +422,8 @@ export const darkMermaidConfig: AstroMermaidOptions = createNorthwesternMermaidC
423
422
  * This function is only needed for manual setups.
424
423
  *
425
424
  * **Must be added after `starlight()` in the `integrations` array.** This lets
426
- * `astro-mermaid` detect and extend Starlight's active Markdown processor,
427
- * including Astro 7's Sätteri pipeline.
425
+ * `astro-mermaid` extend the active Markdown processor after Starlight and its
426
+ * plugins have configured it.
428
427
  *
429
428
  * @param options - Merged with Northwestern defaults. Set `toolbar: false` to
430
429
  * disable the hover toolbar.
@@ -478,31 +477,6 @@ export function northwesternMermaid(options: NorthwesternMermaidOptions = {}): A
478
477
  hooks: {
479
478
  async "astro:config:setup"(params) {
480
479
  const { default: mermaid } = await mermaidModulePromise;
481
- let mermaidHookParams = params;
482
- const markdown = params.config.markdown ?? {};
483
-
484
- if (markdown.processor?.name === "satteri") {
485
- const processor = unified({
486
- remarkPlugins: markdown.remarkPlugins,
487
- rehypePlugins: markdown.rehypePlugins,
488
- remarkRehype: markdown.remarkRehype,
489
- gfm: markdown.gfm,
490
- smartypants: markdown.smartypants,
491
- });
492
- const unifiedMarkdown = {
493
- ...markdown,
494
- processor,
495
- };
496
-
497
- params.updateConfig({ markdown: { processor } });
498
- mermaidHookParams = {
499
- ...params,
500
- config: {
501
- ...params.config,
502
- markdown: unifiedMarkdown,
503
- },
504
- };
505
- }
506
480
 
507
481
  const mermaidIntegration = mermaid({
508
482
  ...lightConfig,
@@ -511,7 +485,7 @@ export function northwesternMermaid(options: NorthwesternMermaidOptions = {}): A
511
485
  mermaidConfig: mergedLightMermaidConfig,
512
486
  });
513
487
 
514
- await mermaidIntegration.hooks["astro:config:setup"]?.(mermaidHookParams);
488
+ await mermaidIntegration.hooks["astro:config:setup"]?.(params);
515
489
 
516
490
  params.injectScript(
517
491
  "page",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nu-appdev/northwestern-starlight-theme",
3
- "version": "1.6.0",
3
+ "version": "1.6.1",
4
4
  "description": "A Northwestern-branded theme for Astro Starlight",
5
5
  "license": "MIT",
6
6
  "author": "Danny Foster <danny@northwestern.edu>",
@@ -0,0 +1,168 @@
1
+ /**
2
+ * Helpers for Astro's `markdown.processor` API.
3
+ *
4
+ * Astro 6.4 introduced `markdown.processor`, and Astro 7 changed its default
5
+ * from `unified()` (`@astrojs/markdown-remark`) to `satteri()`
6
+ * (`@astrojs/markdown-satteri`). Both are *live objects*: integrations and
7
+ * Starlight plugins extend the Markdown pipeline by mutating the processor
8
+ * instance they are handed — pushing into `processor.options.*Plugins`, or
9
+ * wrapping `processor.createRenderer`.
10
+ *
11
+ * That makes processor **replacement** destructive. Anything registered on the
12
+ * outgoing object is dropped, and because most registrations are optional
13
+ * behaviour, the failure is silent: `starlight-links-validator` swapped for a
14
+ * fresh processor simply validates zero links and reports success.
15
+ *
16
+ * The theme therefore installs a `unified()` processor in
17
+ * `defineNorthwesternConfig()` — at `astro.config.*` evaluation time, before
18
+ * `starlight()` is constructed and before any plugin can observe the default —
19
+ * and from then on only ever *extends* the processor it is given.
20
+ */
21
+
22
+ import { unified } from "@astrojs/markdown-remark";
23
+
24
+ /**
25
+ * Structural shape of `markdown.processor`.
26
+ *
27
+ * Declared locally because `AstroUserConfig["markdown"]` only gained the
28
+ * `processor` key in Astro 6.4, and the theme supports Astro 5 and 6 as well.
29
+ */
30
+ export interface MarkdownProcessorLike {
31
+ name: string;
32
+ options?: {
33
+ remarkPlugins?: unknown[];
34
+ rehypePlugins?: unknown[];
35
+ mdastPlugins?: unknown[];
36
+ hastPlugins?: unknown[];
37
+ [key: string]: unknown;
38
+ };
39
+ createRenderer?: unknown;
40
+ }
41
+
42
+ /**
43
+ * Structural shape of `markdown` in an Astro config, including the keys the
44
+ * installed Astro version may not know about yet.
45
+ */
46
+ export interface MarkdownConfigLike {
47
+ processor?: MarkdownProcessorLike;
48
+ remarkPlugins?: unknown[];
49
+ rehypePlugins?: unknown[];
50
+ remarkRehype?: Record<string, unknown>;
51
+ gfm?: boolean;
52
+ smartypants?: unknown;
53
+ [key: string]: unknown;
54
+ }
55
+
56
+ /** A `unified()` processor from `@astrojs/markdown-remark`. */
57
+ export function isUnifiedProcessor(
58
+ processor: MarkdownProcessorLike | undefined,
59
+ ): processor is MarkdownProcessorLike & { options: { rehypePlugins: unknown[] } } {
60
+ return processor?.name === "unified" && Array.isArray(processor.options?.rehypePlugins);
61
+ }
62
+
63
+ /**
64
+ * Plugins registered against a non-`unified` processor (Sätteri's `mdastPlugins`
65
+ * / `hastPlugins`).
66
+ *
67
+ * These use a different plugin system than remark/rehype — a visitor object from
68
+ * `defineHastPlugin()`, not a unified transformer — so they cannot be carried
69
+ * across when a processor is replaced. Counting them is how the theme detects
70
+ * that a replacement would silently drop somebody else's work.
71
+ */
72
+ function countProcessorPlugins(processor: MarkdownProcessorLike): number {
73
+ const { mdastPlugins, hastPlugins } = processor.options ?? {};
74
+ return (
75
+ (Array.isArray(mdastPlugins) ? mdastPlugins.length : 0) + (Array.isArray(hastPlugins) ? hastPlugins.length : 0)
76
+ );
77
+ }
78
+
79
+ /**
80
+ * Return a `markdown` config that is guaranteed to use a `unified()` processor.
81
+ *
82
+ * Call this while building the Astro config — before `starlight()` runs — so no
83
+ * plugin ever observes the Sätteri default. A processor the consumer supplied
84
+ * themselves is respected as-is (a non-`unified` one earns a warning, because
85
+ * several Starlight plugins the theme ships with only support `unified`).
86
+ *
87
+ * User-supplied `remarkPlugins` / `rehypePlugins` are deliberately *not* copied
88
+ * into the processor: Astro migrates those deprecated top-level arrays onto a
89
+ * `unified` processor itself (exactly once, tracked internally), and on Astro
90
+ * versions without `markdown.processor` they are the whole pipeline. Baking
91
+ * copies in here would run them twice.
92
+ */
93
+ export function withUnifiedProcessor(
94
+ markdown: MarkdownConfigLike | undefined,
95
+ warn: (message: string) => void = console.warn,
96
+ ): MarkdownConfigLike {
97
+ const config = markdown ?? {};
98
+
99
+ if (config.processor) {
100
+ if (!isUnifiedProcessor(config.processor)) {
101
+ warn(
102
+ `[northwestern-starlight-theme] \`markdown.processor\` is set to \`${config.processor.name}()\`. ` +
103
+ "The theme's scrollable tables and Starlight plugins such as `starlight-image-zoom` require the " +
104
+ "`unified()` processor from `@astrojs/markdown-remark`. Remove `markdown.processor` to let the " +
105
+ "theme configure it, or set it to `unified({ ... })`.",
106
+ );
107
+ }
108
+
109
+ return config;
110
+ }
111
+
112
+ return {
113
+ ...config,
114
+ processor: unified({
115
+ remarkRehype: config.remarkRehype,
116
+ gfm: config.gfm,
117
+ smartypants: config.smartypants,
118
+ } as Parameters<typeof unified>[0]) as MarkdownProcessorLike,
119
+ };
120
+ }
121
+
122
+ /**
123
+ * Register a rehype plugin on the active Markdown pipeline.
124
+ *
125
+ * Returns a `markdown` config patch to hand to `updateConfig()`, or `undefined`
126
+ * when the plugin was added to the live processor and no config update is
127
+ * needed. Mutating `processor.options` is how Astro itself extends a processor
128
+ * after config resolution — the object identity survives config validation
129
+ * precisely so that late registrations still apply.
130
+ *
131
+ * A non-`unified` processor is left alone. Replacing it here is what used to
132
+ * discard Sätteri-registered plugins from unrelated Starlight plugins, so the
133
+ * theme warns instead of swapping behind their back.
134
+ */
135
+ export function addRehypePlugin(
136
+ markdown: MarkdownConfigLike | undefined,
137
+ plugin: unknown,
138
+ warn: (message: string) => void = console.warn,
139
+ ): MarkdownConfigLike | undefined {
140
+ const config = markdown ?? {};
141
+ const processor = config.processor;
142
+
143
+ // Astro < 6.4: the top-level plugin arrays are the entire pipeline.
144
+ if (!processor) {
145
+ return { rehypePlugins: [...(config.rehypePlugins ?? []), plugin] };
146
+ }
147
+
148
+ if (isUnifiedProcessor(processor)) {
149
+ if (!processor.options.rehypePlugins.includes(plugin)) {
150
+ processor.options.rehypePlugins.push(plugin);
151
+ }
152
+
153
+ return undefined;
154
+ }
155
+
156
+ const registered = countProcessorPlugins(processor);
157
+ warn(
158
+ `[northwestern-starlight-theme] Markdown is rendered by the \`${processor.name}()\` processor, which the theme ` +
159
+ "does not extend. Scrollable table wrappers are disabled for this build" +
160
+ (registered > 0
161
+ ? `, and converting the processor would drop ${registered} plugin(s) other integrations registered on it. `
162
+ : ". ") +
163
+ "Use `defineNorthwesternConfig()` (it installs a `unified()` processor for you) or set " +
164
+ "`markdown.processor: unified({ ... })` from `@astrojs/markdown-remark`.",
165
+ );
166
+
167
+ return undefined;
168
+ }