@grove-dev/starlight 0.3.1 → 0.3.4
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/package.json +16 -1
- package/tests/plugin.test.ts +0 -191
- package/tsconfig.json +0 -9
package/package.json
CHANGED
|
@@ -1,10 +1,13 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@grove-dev/starlight",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.4",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Grove's theme for Starlight (the Astro native documentation site generator)",
|
|
6
6
|
"author": "grove-dev",
|
|
7
7
|
"license": "MIT",
|
|
8
|
+
"engines": {
|
|
9
|
+
"node": ">=22.12.0"
|
|
10
|
+
},
|
|
8
11
|
"readme": "README.md",
|
|
9
12
|
"homepage": "https://grove.dev.mn/",
|
|
10
13
|
"repository": {
|
|
@@ -18,6 +21,18 @@
|
|
|
18
21
|
"publishConfig": {
|
|
19
22
|
"access": "public"
|
|
20
23
|
},
|
|
24
|
+
"files": [
|
|
25
|
+
"components",
|
|
26
|
+
"core",
|
|
27
|
+
"styles",
|
|
28
|
+
"global.d.ts",
|
|
29
|
+
"index.ts",
|
|
30
|
+
"schema.ts",
|
|
31
|
+
"user-components.ts",
|
|
32
|
+
"virtual.d.ts",
|
|
33
|
+
"README.md",
|
|
34
|
+
"THIRD_PARTY_LICENSES.md"
|
|
35
|
+
],
|
|
21
36
|
"exports": {
|
|
22
37
|
".": "./index.ts",
|
|
23
38
|
"./schema": "./schema.ts",
|
package/tests/plugin.test.ts
DELETED
|
@@ -1,191 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @grove-dev/starlight — config / override / virtual module tests.
|
|
3
|
-
*
|
|
4
|
-
* Coverage (per the brief):
|
|
5
|
-
* - plugin.ts:parseConfig: accepts empty config (all defaults),
|
|
6
|
-
* honours user-supplied fields, throws on invalid input
|
|
7
|
-
* - virtual module resolution: the vite plugin's `load` and
|
|
8
|
-
* `resolveId` hooks return the canonical module id when
|
|
9
|
-
* called with the expected arguments, and pass through for
|
|
10
|
-
* unrelated ids
|
|
11
|
-
* - override composition: the override() function fills in
|
|
12
|
-
* every component override slot, warns the user on a clash,
|
|
13
|
-
* and preserves a pre-set user override (does NOT clobber it
|
|
14
|
-
* with the default)
|
|
15
|
-
*/
|
|
16
|
-
import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
|
|
17
|
-
import { plugin } from "../core/plugin.js";
|
|
18
|
-
import { override, COMPONENT_OVERRIDES } from "../core/config/override.js";
|
|
19
|
-
import { vitePlugin } from "../core/config/vite.js";
|
|
20
|
-
import { LucodeStarlightConfigSchema } from "../core/config/schemas.js";
|
|
21
|
-
|
|
22
|
-
describe("parseConfig (exercised through plugin())", () => {
|
|
23
|
-
it("accepts an empty config and fills in all defaults", () => {
|
|
24
|
-
// We can't import parseConfig directly (it's local to
|
|
25
|
-
// plugin.ts), so we exercise it through `plugin(undefined)`,
|
|
26
|
-
// which goes through the same Zod parse path. The hook
|
|
27
|
-
// callback is called with the parsed config; we read it
|
|
28
|
-
// back via the test.
|
|
29
|
-
let captured: unknown;
|
|
30
|
-
const p = plugin();
|
|
31
|
-
// The plugin's `config:setup` hook is the only place
|
|
32
|
-
// parseConfig's output is consumed. Invoke it with a
|
|
33
|
-
// minimal stub of the hook parameters and capture the
|
|
34
|
-
// addIntegration's vite config.
|
|
35
|
-
const addIntegration = vi.fn();
|
|
36
|
-
const updateConfig = vi.fn();
|
|
37
|
-
const calls: Array<{ vite?: { plugins?: Array<{ load?: unknown; resolveId?: unknown }> } }> = [];
|
|
38
|
-
addIntegration.mockImplementation((integration: { hooks: { "astro:config:setup": (args: { updateConfig: (cfg: { vite?: { plugins?: Array<{ load?: unknown; resolveId?: unknown }> } }) => void }) => void } }) => {
|
|
39
|
-
integration.hooks["astro:config:setup"]({
|
|
40
|
-
updateConfig: (cfg) => calls.push(cfg),
|
|
41
|
-
});
|
|
42
|
-
});
|
|
43
|
-
p.hooks["config:setup"]({
|
|
44
|
-
config: { customCss: [] },
|
|
45
|
-
logger: { warn: () => {}, info: () => {}, error: () => {}, debug: () => {} } as never,
|
|
46
|
-
updateConfig: updateConfig as never,
|
|
47
|
-
addIntegration: addIntegration as never,
|
|
48
|
-
command: "build" as never,
|
|
49
|
-
isRestart: false,
|
|
50
|
-
});
|
|
51
|
-
expect(calls).toHaveLength(1);
|
|
52
|
-
const call = calls[0];
|
|
53
|
-
const vite = call?.vite;
|
|
54
|
-
expect(vite?.plugins).toBeDefined();
|
|
55
|
-
captured = vite;
|
|
56
|
-
// Sanity: parseConfig didn't throw on the empty input.
|
|
57
|
-
expect(captured).toBeDefined();
|
|
58
|
-
});
|
|
59
|
-
|
|
60
|
-
it("honours user-supplied docs.includeAiUtilities", () => {
|
|
61
|
-
// Capture the vite plugin's resolved config to assert
|
|
62
|
-
// that the user's boolean is preserved.
|
|
63
|
-
let capturedPlugin: ReturnType<typeof vitePlugin> | undefined;
|
|
64
|
-
const addIntegration = vi.fn((integration: { hooks: { "astro:config:setup": (args: { updateConfig: (cfg: { vite: { plugins: Array<ReturnType<typeof vitePlugin>> } }) => void }) => void } }) => {
|
|
65
|
-
integration.hooks["astro:config:setup"]({
|
|
66
|
-
updateConfig: (cfg) => {
|
|
67
|
-
capturedPlugin = cfg.vite.plugins[0];
|
|
68
|
-
},
|
|
69
|
-
});
|
|
70
|
-
});
|
|
71
|
-
const p = plugin({ docs: { includeAiUtilities: true } });
|
|
72
|
-
p.hooks["config:setup"]({
|
|
73
|
-
config: { customCss: [] },
|
|
74
|
-
logger: { warn: () => {}, info: () => {}, error: () => {}, debug: () => {} } as never,
|
|
75
|
-
updateConfig: vi.fn() as never,
|
|
76
|
-
addIntegration: addIntegration as never,
|
|
77
|
-
command: "build" as never,
|
|
78
|
-
isRestart: false,
|
|
79
|
-
});
|
|
80
|
-
expect(capturedPlugin).toBeDefined();
|
|
81
|
-
// The vite plugin's moduleContent embeds the JSON-serialized
|
|
82
|
-
// config. Load it to confirm `includeAiUtilities: true`
|
|
83
|
-
// survived the parse.
|
|
84
|
-
const loaded = capturedPlugin!.load("\0virtual:lucode-starlight-config") as string;
|
|
85
|
-
expect(loaded).toContain('"includeAiUtilities":true');
|
|
86
|
-
});
|
|
87
|
-
|
|
88
|
-
it("falls back to the schema default for an empty docs object", () => {
|
|
89
|
-
// The schema has `.default({ includeAiUtilities: false })`
|
|
90
|
-
// for the docs sub-object. Pin it.
|
|
91
|
-
const parsed = LucodeStarlightConfigSchema.parse({});
|
|
92
|
-
expect(parsed.docs?.includeAiUtilities).toBe(false);
|
|
93
|
-
});
|
|
94
|
-
|
|
95
|
-
it("throws on invalid input (zod validation failure)", () => {
|
|
96
|
-
// A non-string label fails the linkSchema validation.
|
|
97
|
-
expect(() => LucodeStarlightConfigSchema.parse({ navLinks: "not-an-array" })).toThrow();
|
|
98
|
-
});
|
|
99
|
-
});
|
|
100
|
-
|
|
101
|
-
describe("vitePlugin — virtual module resolution", () => {
|
|
102
|
-
it("resolveId returns the resolved id for the canonical module id", () => {
|
|
103
|
-
const p = vitePlugin(LucodeStarlightConfigSchema.parse({}));
|
|
104
|
-
// The virtual module prefix is 'virtual:'. The plugin
|
|
105
|
-
// matches on the un-prefixed name and returns the
|
|
106
|
-
// '\0'-prefixed resolved id (Vite convention for virtual
|
|
107
|
-
// modules — the null byte prevents the resolved id from
|
|
108
|
-
// being treated as a real file path).
|
|
109
|
-
const result = p.resolveId?.("virtual:lucode-starlight-config");
|
|
110
|
-
expect(result).toBe("\0virtual:lucode-starlight-config");
|
|
111
|
-
});
|
|
112
|
-
|
|
113
|
-
it("resolveId returns undefined for unrelated ids (passthrough)", () => {
|
|
114
|
-
const p = vitePlugin(LucodeStarlightConfigSchema.parse({}));
|
|
115
|
-
expect(p.resolveId?.("virtual:some-other-module")).toBeUndefined();
|
|
116
|
-
expect(p.resolveId?.("./relative-import")).toBeUndefined();
|
|
117
|
-
});
|
|
118
|
-
|
|
119
|
-
it("load returns the JSON-serialized config when given the resolved id", () => {
|
|
120
|
-
const config = LucodeStarlightConfigSchema.parse({ docs: { includeAiUtilities: true } });
|
|
121
|
-
const p = vitePlugin(config);
|
|
122
|
-
const module = p.load?.("\0virtual:lucode-starlight-config") as string;
|
|
123
|
-
expect(module).toContain("export default");
|
|
124
|
-
expect(module).toContain('"includeAiUtilities":true');
|
|
125
|
-
});
|
|
126
|
-
|
|
127
|
-
it("load returns undefined for unrelated ids", () => {
|
|
128
|
-
const p = vitePlugin(LucodeStarlightConfigSchema.parse({}));
|
|
129
|
-
expect(p.load?.("not-the-virtual-id")).toBeUndefined();
|
|
130
|
-
});
|
|
131
|
-
});
|
|
132
|
-
|
|
133
|
-
describe("override — component override composition", () => {
|
|
134
|
-
let warnSpy: ReturnType<typeof vi.spyOn>;
|
|
135
|
-
|
|
136
|
-
beforeEach(() => {
|
|
137
|
-
warnSpy = vi.spyOn(console, "warn").mockImplementation(() => {});
|
|
138
|
-
});
|
|
139
|
-
|
|
140
|
-
afterEach(() => {
|
|
141
|
-
warnSpy.mockRestore();
|
|
142
|
-
});
|
|
143
|
-
|
|
144
|
-
it("fills every component override slot when the user has none", () => {
|
|
145
|
-
// Starlight starts with no components overridden (empty
|
|
146
|
-
// config). The override() function should set every key in
|
|
147
|
-
// COMPONENT_OVERRIDES to point at the @grove-dev/starlight
|
|
148
|
-
// path.
|
|
149
|
-
const components = override(
|
|
150
|
-
{ components: {} } as never,
|
|
151
|
-
COMPONENT_OVERRIDES,
|
|
152
|
-
{ warn: warnSpy, info: () => {}, error: () => {}, debug: () => {} } as never,
|
|
153
|
-
);
|
|
154
|
-
for (const key of COMPONENT_OVERRIDES) {
|
|
155
|
-
expect(components?.[key]).toBe(`@grove-dev/starlight/components/overrides/${key}.astro`);
|
|
156
|
-
}
|
|
157
|
-
});
|
|
158
|
-
|
|
159
|
-
it("preserves a user-supplied component override (does not clobber)", () => {
|
|
160
|
-
// If the user already set components.Footer, we should
|
|
161
|
-
// NOT overwrite it with the default — we warn and skip.
|
|
162
|
-
// Pin: a previous "always overwrite" implementation would
|
|
163
|
-
// silently clobber the user's custom footer.
|
|
164
|
-
const userFooter = "./my-custom/Footer.astro";
|
|
165
|
-
const components = override(
|
|
166
|
-
{ components: { Footer: userFooter } } as never,
|
|
167
|
-
COMPONENT_OVERRIDES,
|
|
168
|
-
{ warn: warnSpy, info: () => {}, error: () => {}, debug: () => {} } as never,
|
|
169
|
-
);
|
|
170
|
-
expect(components?.Footer).toBe(userFooter);
|
|
171
|
-
// The warn call names the slot and points at the override path.
|
|
172
|
-
const warnings = warnSpy.mock.calls.map((c: unknown[]) => String(c[0]));
|
|
173
|
-
expect(warnings.some((w: string) => w.includes("Footer"))).toBe(true);
|
|
174
|
-
// All OTHER slots are still filled in.
|
|
175
|
-
expect(components?.Header).toBe(`@grove-dev/starlight/components/overrides/Header.astro`);
|
|
176
|
-
});
|
|
177
|
-
|
|
178
|
-
it("handles a config with no `components` field at all (undefined)", () => {
|
|
179
|
-
// Defensive: Starlight's config can omit `components`
|
|
180
|
-
// entirely. override() should still produce a full set
|
|
181
|
-
// without throwing.
|
|
182
|
-
const components = override(
|
|
183
|
-
{} as never,
|
|
184
|
-
COMPONENT_OVERRIDES,
|
|
185
|
-
{ warn: warnSpy, info: () => {}, error: () => {}, debug: () => {} } as never,
|
|
186
|
-
);
|
|
187
|
-
for (const key of COMPONENT_OVERRIDES) {
|
|
188
|
-
expect(components?.[key]).toBeDefined();
|
|
189
|
-
}
|
|
190
|
-
});
|
|
191
|
-
});
|
package/tsconfig.json
DELETED
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"extends": "../../tsconfig.base.json",
|
|
3
|
-
"compilerOptions": {
|
|
4
|
-
"rootDir": ".",
|
|
5
|
-
"noEmit": true,
|
|
6
|
-
"types": ["node", "vitest/globals"]
|
|
7
|
-
},
|
|
8
|
-
"include": ["core/**/*.ts", "tests/**/*.ts", "global.d.ts", "virtual.d.ts", "index.ts", "schema.ts", "user-components.ts"]
|
|
9
|
-
}
|