nimbus-docs 0.0.2
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/LICENSE +21 -0
- package/README.md +25 -0
- package/dist/cli/index.d.ts +1 -0
- package/dist/cli/index.js +692 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/client.d.ts +167 -0
- package/dist/client.d.ts.map +1 -0
- package/dist/client.js +367 -0
- package/dist/client.js.map +1 -0
- package/dist/content.d.ts +126 -0
- package/dist/content.d.ts.map +1 -0
- package/dist/content.js +57 -0
- package/dist/content.js.map +1 -0
- package/dist/index.d.ts +255 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +1478 -0
- package/dist/index.js.map +1 -0
- package/dist/lib/pkgm.d.ts +41 -0
- package/dist/lib/pkgm.d.ts.map +1 -0
- package/dist/lib/pkgm.js +76 -0
- package/dist/lib/pkgm.js.map +1 -0
- package/dist/schemas.d.ts +164 -0
- package/dist/schemas.d.ts.map +1 -0
- package/dist/schemas.js +110 -0
- package/dist/schemas.js.map +1 -0
- package/dist/types.d.ts +274 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +1 -0
- package/package.json +81 -0
- package/src/components/NimbusHead.astro +161 -0
package/dist/schemas.js
ADDED
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import { z } from "astro/zod";
|
|
2
|
+
|
|
3
|
+
//#region src/schemas.ts
|
|
4
|
+
/**
|
|
5
|
+
* Content schemas for Nimbus.
|
|
6
|
+
*
|
|
7
|
+
* `docsSchema` is the default frontmatter contract for the `docs` collection.
|
|
8
|
+
* `partialsSchema` is the contract for `<Render file="..." />` partials.
|
|
9
|
+
* `defineDocSchema(config)` returns a customizable schema for advanced users
|
|
10
|
+
* composing schemas outside the `docsCollection()` factory.
|
|
11
|
+
*
|
|
12
|
+
* Error messages target content authors, not framework developers.
|
|
13
|
+
* Astro 6 ships Zod v4 via `astro/zod`. The v4 API uses a single `error`
|
|
14
|
+
* field on every schema constructor — NOT v3's `required_error` /
|
|
15
|
+
* `invalid_type_error` / `errorMap`.
|
|
16
|
+
*/
|
|
17
|
+
const sidebarBadgeSchema = z.union([z.string(), z.object({
|
|
18
|
+
text: z.string({ error: "sidebar badge needs a \"text\" field" }),
|
|
19
|
+
variant: z.enum([
|
|
20
|
+
"default",
|
|
21
|
+
"info",
|
|
22
|
+
"note",
|
|
23
|
+
"success",
|
|
24
|
+
"tip",
|
|
25
|
+
"warning",
|
|
26
|
+
"caution",
|
|
27
|
+
"danger"
|
|
28
|
+
], { error: "\"variant\" must be one of: default, info, note, success, tip, warning, caution, danger" }).default("default")
|
|
29
|
+
})]);
|
|
30
|
+
const sidebarFrontmatterSchema = z.object({
|
|
31
|
+
order: z.number({ error: "\"sidebar.order\" must be a number" }).optional(),
|
|
32
|
+
label: z.string({ error: "\"sidebar.label\" must be a string" }).optional(),
|
|
33
|
+
badge: sidebarBadgeSchema.optional(),
|
|
34
|
+
hidden: z.boolean({ error: "\"sidebar.hidden\" must be true or false" }).optional(),
|
|
35
|
+
hideChildren: z.boolean({ error: "\"sidebar.hideChildren\" must be true or false" }).optional()
|
|
36
|
+
}).optional();
|
|
37
|
+
const heroActionSchema = z.object({
|
|
38
|
+
text: z.string({ error: "hero action needs a \"text\" field" }),
|
|
39
|
+
link: z.string({ error: "hero action needs a \"link\" field" }),
|
|
40
|
+
variant: z.enum([
|
|
41
|
+
"primary",
|
|
42
|
+
"secondary",
|
|
43
|
+
"outline"
|
|
44
|
+
], { error: "hero action \"variant\" must be \"primary\", \"secondary\", or \"outline\"" }).default("primary"),
|
|
45
|
+
icon: z.string().optional()
|
|
46
|
+
});
|
|
47
|
+
const heroSchema = z.object({
|
|
48
|
+
title: z.string().optional(),
|
|
49
|
+
tagline: z.string().optional(),
|
|
50
|
+
actions: z.array(heroActionSchema).optional()
|
|
51
|
+
}).optional();
|
|
52
|
+
const prevNextFrontmatterSchema = z.union([
|
|
53
|
+
z.string(),
|
|
54
|
+
z.object({
|
|
55
|
+
link: z.string().optional(),
|
|
56
|
+
label: z.string().optional()
|
|
57
|
+
}),
|
|
58
|
+
z.literal(false)
|
|
59
|
+
]).optional();
|
|
60
|
+
const headElementSchema = z.object({
|
|
61
|
+
tag: z.enum([
|
|
62
|
+
"meta",
|
|
63
|
+
"link",
|
|
64
|
+
"script",
|
|
65
|
+
"style"
|
|
66
|
+
], { error: "head element \"tag\" must be \"meta\", \"link\", \"script\", or \"style\"" }),
|
|
67
|
+
attrs: z.record(z.string(), z.string()).default({}),
|
|
68
|
+
content: z.string().optional()
|
|
69
|
+
});
|
|
70
|
+
function baseDocSchema() {
|
|
71
|
+
return z.object({
|
|
72
|
+
title: z.string({ error: (iss) => iss.input === void 0 ? "Missing required \"title\" in frontmatter. Every doc needs:\n\n ---\n title: \"Your Page Title\"\n ---" : `"title" must be a string, received ${typeof iss.input}` }),
|
|
73
|
+
description: z.string({ error: "\"description\" must be a string" }).optional(),
|
|
74
|
+
template: z.enum(["doc", "splash"], { error: "\"template\" must be \"doc\" or \"splash\"" }).default("doc"),
|
|
75
|
+
sidebar: sidebarFrontmatterSchema,
|
|
76
|
+
hero: heroSchema,
|
|
77
|
+
head: z.array(headElementSchema).default([]),
|
|
78
|
+
draft: z.boolean({ error: "\"draft\" must be true or false" }).default(false),
|
|
79
|
+
noindex: z.boolean({ error: "\"noindex\" must be true or false" }).default(false),
|
|
80
|
+
llms: z.boolean({ error: "\"llms\" must be true or false" }).default(true),
|
|
81
|
+
aiDeprioritize: z.boolean({ error: "\"aiDeprioritize\" must be true or false" }).default(false),
|
|
82
|
+
pagefind: z.boolean({ error: "\"pagefind\" must be true or false" }).default(true),
|
|
83
|
+
tableOfContents: z.object({
|
|
84
|
+
minHeadingLevel: z.number({ error: "\"minHeadingLevel\" must be a number (1-6)" }).int().min(1).max(6).default(2),
|
|
85
|
+
maxHeadingLevel: z.number({ error: "\"maxHeadingLevel\" must be a number (1-6)" }).int().min(1).max(6).default(3)
|
|
86
|
+
}).refine((v) => v.minHeadingLevel <= v.maxHeadingLevel, { message: "minHeadingLevel must be <= maxHeadingLevel" }).optional(),
|
|
87
|
+
lastUpdated: z.coerce.date({ error: "\"lastUpdated\" must be a valid date (e.g. 2024-01-15)" }).optional(),
|
|
88
|
+
socialImage: z.string({ error: "\"socialImage\" must be a string (path or URL)" }).optional(),
|
|
89
|
+
prev: prevNextFrontmatterSchema,
|
|
90
|
+
next: prevNextFrontmatterSchema
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* Build a customizable docs schema. Use this when composing schemas outside
|
|
95
|
+
* the `docsCollection()` factory (e.g. multiple docs collections with
|
|
96
|
+
* different shapes).
|
|
97
|
+
*/
|
|
98
|
+
function defineDocSchema(config = {}) {
|
|
99
|
+
const base = baseDocSchema();
|
|
100
|
+
if (config.fields) return base.extend(config.fields);
|
|
101
|
+
return base;
|
|
102
|
+
}
|
|
103
|
+
/** Default docs schema. Equivalent to `defineDocSchema()`. */
|
|
104
|
+
const docsSchema = defineDocSchema();
|
|
105
|
+
/** Schema for partials (`<Render file="..." />` snippets). */
|
|
106
|
+
const partialsSchema = z.object({ params: z.array(z.string()).optional() }).default({});
|
|
107
|
+
|
|
108
|
+
//#endregion
|
|
109
|
+
export { defineDocSchema, docsSchema, partialsSchema };
|
|
110
|
+
//# sourceMappingURL=schemas.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"schemas.js","names":[],"sources":["../src/schemas.ts"],"sourcesContent":["/**\n * Content schemas for Nimbus.\n *\n * `docsSchema` is the default frontmatter contract for the `docs` collection.\n * `partialsSchema` is the contract for `<Render file=\"...\" />` partials.\n * `defineDocSchema(config)` returns a customizable schema for advanced users\n * composing schemas outside the `docsCollection()` factory.\n *\n * Error messages target content authors, not framework developers.\n * Astro 6 ships Zod v4 via `astro/zod`. The v4 API uses a single `error`\n * field on every schema constructor — NOT v3's `required_error` /\n * `invalid_type_error` / `errorMap`.\n */\n\nimport { z } from \"astro/zod\";\n\nexport interface DocSchemaConfig {\n /** Additional frontmatter fields merged into the default schema. */\n fields?: Record<string, z.ZodTypeAny>;\n}\n\n// ---------------------------------------------------------------------------\n// Building blocks\n// ---------------------------------------------------------------------------\n\nconst sidebarBadgeSchema = z.union([\n z.string(),\n z.object({\n text: z.string({ error: 'sidebar badge needs a \"text\" field' }),\n variant: z\n .enum(\n [\"default\", \"info\", \"note\", \"success\", \"tip\", \"warning\", \"caution\", \"danger\"],\n {\n error:\n '\"variant\" must be one of: default, info, note, success, tip, warning, caution, danger',\n },\n )\n .default(\"default\"),\n }),\n]);\n\nconst sidebarFrontmatterSchema = z\n .object({\n order: z.number({ error: '\"sidebar.order\" must be a number' }).optional(),\n label: z.string({ error: '\"sidebar.label\" must be a string' }).optional(),\n badge: sidebarBadgeSchema.optional(),\n hidden: z.boolean({ error: '\"sidebar.hidden\" must be true or false' }).optional(),\n hideChildren: z\n .boolean({ error: '\"sidebar.hideChildren\" must be true or false' })\n .optional(),\n })\n .optional();\n\nconst heroActionSchema = z.object({\n text: z.string({ error: 'hero action needs a \"text\" field' }),\n link: z.string({ error: 'hero action needs a \"link\" field' }),\n variant: z\n .enum([\"primary\", \"secondary\", \"outline\"], {\n error: 'hero action \"variant\" must be \"primary\", \"secondary\", or \"outline\"',\n })\n .default(\"primary\"),\n icon: z.string().optional(),\n});\n\nconst heroSchema = z\n .object({\n title: z.string().optional(),\n tagline: z.string().optional(),\n actions: z.array(heroActionSchema).optional(),\n })\n .optional();\n\nconst prevNextFrontmatterSchema = z\n .union([\n z.string(),\n z.object({ link: z.string().optional(), label: z.string().optional() }),\n z.literal(false),\n ])\n .optional();\n\nconst headElementSchema = z.object({\n tag: z.enum([\"meta\", \"link\", \"script\", \"style\"], {\n error: 'head element \"tag\" must be \"meta\", \"link\", \"script\", or \"style\"',\n }),\n attrs: z.record(z.string(), z.string()).default({}),\n content: z.string().optional(),\n});\n\n// ---------------------------------------------------------------------------\n// Base docs schema\n// ---------------------------------------------------------------------------\n\nfunction baseDocSchema() {\n return z.object({\n title: z.string({\n error: (iss) =>\n iss.input === undefined\n ? 'Missing required \"title\" in frontmatter. Every doc needs:\\n\\n ---\\n title: \"Your Page Title\"\\n ---'\n : `\"title\" must be a string, received ${typeof iss.input}`,\n }),\n description: z.string({ error: '\"description\" must be a string' }).optional(),\n template: z\n .enum([\"doc\", \"splash\"], {\n error: '\"template\" must be \"doc\" or \"splash\"',\n })\n .default(\"doc\"),\n sidebar: sidebarFrontmatterSchema,\n hero: heroSchema,\n head: z.array(headElementSchema).default([]),\n draft: z.boolean({ error: '\"draft\" must be true or false' }).default(false),\n noindex: z.boolean({ error: '\"noindex\" must be true or false' }).default(false),\n /** Include this page in llms.txt and AI consumption indexes */\n llms: z.boolean({ error: '\"llms\" must be true or false' }).default(true),\n /** Signal AI crawlers to deprioritize this page */\n aiDeprioritize: z\n .boolean({ error: '\"aiDeprioritize\" must be true or false' })\n .default(false),\n pagefind: z.boolean({ error: '\"pagefind\" must be true or false' }).default(true),\n tableOfContents: z\n .object({\n minHeadingLevel: z\n .number({ error: '\"minHeadingLevel\" must be a number (1-6)' })\n .int()\n .min(1)\n .max(6)\n .default(2),\n maxHeadingLevel: z\n .number({ error: '\"maxHeadingLevel\" must be a number (1-6)' })\n .int()\n .min(1)\n .max(6)\n .default(3),\n })\n .refine((v) => v.minHeadingLevel <= v.maxHeadingLevel, {\n message: \"minHeadingLevel must be <= maxHeadingLevel\",\n })\n .optional(),\n lastUpdated: z.coerce\n .date({ error: '\"lastUpdated\" must be a valid date (e.g. 2024-01-15)' })\n .optional(),\n /**\n * Explicit per-page social/OG image override (path or absolute URL).\n * When omitted, the page route is expected to fall back to a\n * programmatically-generated card or the site-wide `config.socialImage`.\n */\n socialImage: z\n .string({ error: '\"socialImage\" must be a string (path or URL)' })\n .optional(),\n prev: prevNextFrontmatterSchema,\n next: prevNextFrontmatterSchema,\n });\n}\n\n// ---------------------------------------------------------------------------\n// Public API\n// ---------------------------------------------------------------------------\n\n/**\n * Build a customizable docs schema. Use this when composing schemas outside\n * the `docsCollection()` factory (e.g. multiple docs collections with\n * different shapes).\n */\nexport function defineDocSchema(config: DocSchemaConfig = {}) {\n const base = baseDocSchema();\n if (config.fields) {\n return base.extend(config.fields);\n }\n return base;\n}\n\n/** Default docs schema. Equivalent to `defineDocSchema()`. */\nexport const docsSchema = defineDocSchema();\n\n/** Schema for partials (`<Render file=\"...\" />` snippets). */\nexport const partialsSchema = z\n .object({\n /**\n * Declared parameters this partial accepts.\n * Suffix with `?` for optional params: `[\"name\", \"deprecated?\"]`\n */\n params: z.array(z.string()).optional(),\n })\n .default({});\n"],"mappings":"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"}
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,274 @@
|
|
|
1
|
+
//#region src/types.d.ts
|
|
2
|
+
/**
|
|
3
|
+
* Public type surface for `nimbus-docs/types`.
|
|
4
|
+
*/
|
|
5
|
+
interface NimbusConfig {
|
|
6
|
+
/** Canonical site URL, e.g. `https://docs.example.com`. */
|
|
7
|
+
site: string;
|
|
8
|
+
title: string;
|
|
9
|
+
description?: string;
|
|
10
|
+
/** Short text or symbol (max ~2 chars) used in the header logo. */
|
|
11
|
+
logo: string;
|
|
12
|
+
locale?: string;
|
|
13
|
+
/** Label for the "Home" breadcrumb / root link. */
|
|
14
|
+
homeLabel?: string;
|
|
15
|
+
/** Repo URL for header link. `null` to hide. */
|
|
16
|
+
github?: string | null;
|
|
17
|
+
/** Edit-link URL pattern. `{path}` is replaced with the doc's repo path. */
|
|
18
|
+
editPattern?: string | null;
|
|
19
|
+
/** Footer text. */
|
|
20
|
+
footer?: string;
|
|
21
|
+
/**
|
|
22
|
+
* Site-wide social/OG fallback image. Site-relative (e.g. `/og.png`) or
|
|
23
|
+
* absolute URL. Used when a page doesn't supply its own `socialImage`
|
|
24
|
+
* (via frontmatter or a build-time-generated card).
|
|
25
|
+
*/
|
|
26
|
+
socialImage?: string;
|
|
27
|
+
/** Alt text for `socialImage`. Applied to og:image:alt + twitter:image:alt. */
|
|
28
|
+
socialImageAlt?: string;
|
|
29
|
+
/** Site-wide head elements (meta, link, script, style). */
|
|
30
|
+
head?: HeadElement[];
|
|
31
|
+
sidebar?: SidebarConfig;
|
|
32
|
+
features?: FeaturesConfig;
|
|
33
|
+
/**
|
|
34
|
+
* Search backend. Absent means Pagefind. `false` disables framework search
|
|
35
|
+
* plumbing. `{ provider: "custom" }` lets user code render a search UI
|
|
36
|
+
* without running Pagefind at build time.
|
|
37
|
+
*/
|
|
38
|
+
search?: SearchConfig | false;
|
|
39
|
+
}
|
|
40
|
+
interface FeaturesConfig {
|
|
41
|
+
search?: boolean;
|
|
42
|
+
editLinks?: boolean;
|
|
43
|
+
pagination?: boolean;
|
|
44
|
+
toc?: boolean;
|
|
45
|
+
}
|
|
46
|
+
interface SearchConfig {
|
|
47
|
+
provider?: "pagefind" | "custom";
|
|
48
|
+
}
|
|
49
|
+
interface SearchResult {
|
|
50
|
+
/** Page title — shown as the primary result text. */
|
|
51
|
+
title: string;
|
|
52
|
+
/** Destination URL. */
|
|
53
|
+
url: string;
|
|
54
|
+
/** Excerpt with optional highlight markup. Providers should sanitize HTML. */
|
|
55
|
+
snippet?: string;
|
|
56
|
+
/** Heading-level matches within the page. */
|
|
57
|
+
subResults?: {
|
|
58
|
+
title: string;
|
|
59
|
+
url: string;
|
|
60
|
+
}[];
|
|
61
|
+
}
|
|
62
|
+
interface SearchProvider {
|
|
63
|
+
/** Optional lazy setup hook, called before the first search. */
|
|
64
|
+
init?(): Promise<void>;
|
|
65
|
+
search(query: string, opts?: {
|
|
66
|
+
signal?: AbortSignal;
|
|
67
|
+
}): Promise<SearchResult[]>;
|
|
68
|
+
}
|
|
69
|
+
interface HeadElement {
|
|
70
|
+
tag: "meta" | "link" | "script" | "style";
|
|
71
|
+
attrs?: Record<string, string>;
|
|
72
|
+
content?: string;
|
|
73
|
+
}
|
|
74
|
+
interface SidebarConfig {
|
|
75
|
+
items?: SidebarConfigItem[];
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* A top-level section derived from the sidebar tree — used to render the
|
|
79
|
+
* header tab strip (or any other cross-section navigation). One per
|
|
80
|
+
* top-level group; non-group items (links, externals) are skipped.
|
|
81
|
+
*/
|
|
82
|
+
interface SidebarSection {
|
|
83
|
+
/** Group label from `sidebar.items` (or the autogenerated directory name). */
|
|
84
|
+
label: string;
|
|
85
|
+
/** First link descendant's href — where the section "lives". */
|
|
86
|
+
href: string;
|
|
87
|
+
/** True when the current page is inside this section's tree. */
|
|
88
|
+
isActive: boolean;
|
|
89
|
+
}
|
|
90
|
+
type SidebarConfigItem = string | {
|
|
91
|
+
label: string;
|
|
92
|
+
link: string;
|
|
93
|
+
badge?: SidebarBadge;
|
|
94
|
+
} | {
|
|
95
|
+
label?: string;
|
|
96
|
+
autogenerate: {
|
|
97
|
+
directory: string;
|
|
98
|
+
};
|
|
99
|
+
collapsed?: boolean;
|
|
100
|
+
badge?: SidebarBadge;
|
|
101
|
+
} | {
|
|
102
|
+
label?: string;
|
|
103
|
+
/**
|
|
104
|
+
* Autogenerate from a named content collection. `getSidebar()` loads
|
|
105
|
+
* collections referenced by sidebar items automatically. `prefix`
|
|
106
|
+
* defaults to `/{collection}` (e.g. `/api` for `collection: "api"`)
|
|
107
|
+
* unless the collection is the primary `docs` collection, which mounts
|
|
108
|
+
* at root.
|
|
109
|
+
*/
|
|
110
|
+
autogenerate: {
|
|
111
|
+
collection: string;
|
|
112
|
+
prefix?: string;
|
|
113
|
+
};
|
|
114
|
+
collapsed?: boolean;
|
|
115
|
+
badge?: SidebarBadge;
|
|
116
|
+
} | {
|
|
117
|
+
label: string;
|
|
118
|
+
items: SidebarConfigItem[];
|
|
119
|
+
collapsed?: boolean;
|
|
120
|
+
badge?: SidebarBadge;
|
|
121
|
+
};
|
|
122
|
+
type BadgeVariant = "default" | "info" | "note" | "success" | "tip" | "warning" | "caution" | "danger";
|
|
123
|
+
type SidebarBadge = string | {
|
|
124
|
+
text: string;
|
|
125
|
+
variant: BadgeVariant;
|
|
126
|
+
};
|
|
127
|
+
interface SidebarLinkItem {
|
|
128
|
+
type: "link";
|
|
129
|
+
label: string;
|
|
130
|
+
href: string;
|
|
131
|
+
isCurrent?: boolean;
|
|
132
|
+
badge?: SidebarBadge;
|
|
133
|
+
attrs?: Record<string, string>;
|
|
134
|
+
order: number;
|
|
135
|
+
}
|
|
136
|
+
interface SidebarExternalLinkItem {
|
|
137
|
+
type: "external";
|
|
138
|
+
label: string;
|
|
139
|
+
href: string;
|
|
140
|
+
badge?: SidebarBadge;
|
|
141
|
+
order: number;
|
|
142
|
+
}
|
|
143
|
+
interface SidebarGroupItem {
|
|
144
|
+
type: "group";
|
|
145
|
+
label: string;
|
|
146
|
+
order: number;
|
|
147
|
+
collapsed?: boolean;
|
|
148
|
+
badge?: SidebarBadge;
|
|
149
|
+
children: SidebarItem[];
|
|
150
|
+
_indexId?: string;
|
|
151
|
+
}
|
|
152
|
+
type SidebarItem = SidebarLinkItem | SidebarExternalLinkItem | SidebarGroupItem;
|
|
153
|
+
interface TOCItem {
|
|
154
|
+
depth: number;
|
|
155
|
+
text: string;
|
|
156
|
+
slug: string;
|
|
157
|
+
}
|
|
158
|
+
interface Breadcrumb {
|
|
159
|
+
label: string;
|
|
160
|
+
href: string;
|
|
161
|
+
}
|
|
162
|
+
interface PrevNextLink {
|
|
163
|
+
label: string;
|
|
164
|
+
href: string;
|
|
165
|
+
}
|
|
166
|
+
interface PrevNext {
|
|
167
|
+
prev?: PrevNextLink;
|
|
168
|
+
next?: PrevNextLink;
|
|
169
|
+
}
|
|
170
|
+
interface PrevNextOverrides {
|
|
171
|
+
prev?: string | {
|
|
172
|
+
link?: string;
|
|
173
|
+
label?: string;
|
|
174
|
+
} | false;
|
|
175
|
+
next?: string | {
|
|
176
|
+
link?: string;
|
|
177
|
+
label?: string;
|
|
178
|
+
} | false;
|
|
179
|
+
}
|
|
180
|
+
/**
|
|
181
|
+
* Optional banner shown at the top of a doc page.
|
|
182
|
+
*
|
|
183
|
+
* <Banner content="Heads up!" type="warning" />
|
|
184
|
+
* <Banner content="..." dismissible={{ id: "v2-release", days: 7 }} />
|
|
185
|
+
*/
|
|
186
|
+
interface BannerProps {
|
|
187
|
+
content: string;
|
|
188
|
+
type?: "note" | "tip" | "caution" | "danger";
|
|
189
|
+
/** When set, users can dismiss the banner; their preference is remembered. */
|
|
190
|
+
dismissible?: {
|
|
191
|
+
/** Stable identifier — change this when the banner content meaningfully changes. */id: string; /** How long the dismissal sticks (default: forever). */
|
|
192
|
+
days?: number;
|
|
193
|
+
};
|
|
194
|
+
}
|
|
195
|
+
/**
|
|
196
|
+
* Metadata any Nimbus page hands its outer layout chrome.
|
|
197
|
+
*
|
|
198
|
+
* `BaseLayout` (the topmost shell — `<html>`, `<head>`, theme bootstrap,
|
|
199
|
+
* font preloads) renders the same handful of fields regardless of what
|
|
200
|
+
* kind of page it's wrapping. Specific page-shape layouts like
|
|
201
|
+
* `DocsLayout` extend this with their own additional contract (sidebar,
|
|
202
|
+
* TOC, breadcrumbs, etc. — see `DocsPageProps`).
|
|
203
|
+
*
|
|
204
|
+
* Every field is something the Nimbus framework knows how to handle:
|
|
205
|
+
* - `head` entries get concatenated with `config.head` in the layout.
|
|
206
|
+
* - `noindex` emits `<meta name="robots" content="noindex">`.
|
|
207
|
+
* - `aiDeprioritize` signals AI crawlers via the conventional meta.
|
|
208
|
+
* - `title` / `description` populate `<title>` / `<meta name="description">`.
|
|
209
|
+
*/
|
|
210
|
+
interface BasePageProps {
|
|
211
|
+
title: string;
|
|
212
|
+
description?: string;
|
|
213
|
+
/** Page-level head additions, merged with `config.head`. */
|
|
214
|
+
head?: HeadElement[];
|
|
215
|
+
/** Emit `<meta name="robots" content="noindex">`. */
|
|
216
|
+
noindex?: boolean;
|
|
217
|
+
/** Signal AI crawlers to deprioritize this page. */
|
|
218
|
+
aiDeprioritize?: boolean;
|
|
219
|
+
/** Absolute or site-relative URL for this page's markdown variant. */
|
|
220
|
+
markdownUrl?: string;
|
|
221
|
+
/**
|
|
222
|
+
* Page-level OG/Twitter image. Site-relative (e.g. `/og/welcome.png`) or
|
|
223
|
+
* absolute. Resolution lives in the user's page route — by the time it
|
|
224
|
+
* reaches the layout, this is either an explicit frontmatter override or
|
|
225
|
+
* a programmatically-generated card path. Falls back to
|
|
226
|
+
* `config.socialImage` when absent.
|
|
227
|
+
*/
|
|
228
|
+
socialImage?: string;
|
|
229
|
+
/**
|
|
230
|
+
* ISO date for `article:modified_time`. Today this is sourced from
|
|
231
|
+
* frontmatter `lastUpdated`; a future git-based source can populate the
|
|
232
|
+
* same prop without touching the layout.
|
|
233
|
+
*/
|
|
234
|
+
lastUpdated?: Date;
|
|
235
|
+
}
|
|
236
|
+
/**
|
|
237
|
+
* Props passed to a Nimbus docs layout (e.g. `DocsLayout.astro`).
|
|
238
|
+
*
|
|
239
|
+
* Extends `BasePageProps` with the additional fields a docs-style page
|
|
240
|
+
* needs: a sidebar tree, on-page TOC, breadcrumbs, prev/next links, plus
|
|
241
|
+
* docs-specific frontmatter like `template`, `banner`, `lastUpdated`.
|
|
242
|
+
*
|
|
243
|
+
* Every field is produced by either:
|
|
244
|
+
* - the validated frontmatter schema (`docsSchema`), or
|
|
245
|
+
* - a framework data helper (`getSidebar`, `getTOC`, `getBreadcrumbs`,
|
|
246
|
+
* `getPrevNext`), or
|
|
247
|
+
* - the validated config.
|
|
248
|
+
*
|
|
249
|
+
* Custom layouts can `Pick<>` the subset they need or `extends` to add
|
|
250
|
+
* project-specific extras.
|
|
251
|
+
*/
|
|
252
|
+
interface DocsPageProps extends BasePageProps {
|
|
253
|
+
/** Layout variant. `"splash"` skips the sidebar / TOC chrome. */
|
|
254
|
+
template?: "doc" | "splash";
|
|
255
|
+
/** Hide the search index entry for this page. */
|
|
256
|
+
pagefind?: boolean;
|
|
257
|
+
/** Don't render in production; treat as `noindex` in dev. */
|
|
258
|
+
draft?: boolean;
|
|
259
|
+
/** URL pointing at this page's source on the repo host (computed from `config.editPattern`). */
|
|
260
|
+
editUrl?: string;
|
|
261
|
+
/** Optional top-of-page banner. */
|
|
262
|
+
banner?: BannerProps;
|
|
263
|
+
/** From `getSidebar()`. */
|
|
264
|
+
sidebar: SidebarItem[];
|
|
265
|
+
/** From `getTOC()`. */
|
|
266
|
+
headings: TOCItem[];
|
|
267
|
+
/** From `getBreadcrumbs()`. */
|
|
268
|
+
breadcrumbs: Breadcrumb[];
|
|
269
|
+
/** From `getPrevNext()`. */
|
|
270
|
+
prevNext: PrevNext;
|
|
271
|
+
}
|
|
272
|
+
//#endregion
|
|
273
|
+
export { BadgeVariant, BannerProps, BasePageProps, Breadcrumb, DocsPageProps, FeaturesConfig, HeadElement, NimbusConfig, PrevNext, PrevNextLink, PrevNextOverrides, SearchConfig, SearchProvider, SearchResult, SidebarBadge, SidebarConfig, SidebarConfigItem, SidebarExternalLinkItem, SidebarGroupItem, SidebarItem, SidebarLinkItem, SidebarSection, TOCItem };
|
|
274
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","names":[],"sources":["../src/types.ts"],"mappings":"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"}
|
package/dist/types.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { };
|
package/package.json
ADDED
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "nimbus-docs",
|
|
3
|
+
"version": "0.0.2",
|
|
4
|
+
"private": false,
|
|
5
|
+
"type": "module",
|
|
6
|
+
"description": "Docs, for humans and agents",
|
|
7
|
+
"keywords": [
|
|
8
|
+
"astro",
|
|
9
|
+
"documentation",
|
|
10
|
+
"docs",
|
|
11
|
+
"mdx",
|
|
12
|
+
"static-site"
|
|
13
|
+
],
|
|
14
|
+
"homepage": "https://nimbus-docs.com",
|
|
15
|
+
"repository": {
|
|
16
|
+
"type": "git",
|
|
17
|
+
"url": "git+https://github.com/MohamedH1998/nimbus-docs.git",
|
|
18
|
+
"directory": "packages/nimbus-docs"
|
|
19
|
+
},
|
|
20
|
+
"bugs": {
|
|
21
|
+
"url": "https://github.com/MohamedH1998/nimbus-docs/issues"
|
|
22
|
+
},
|
|
23
|
+
"license": "MIT",
|
|
24
|
+
"author": "Mohamed Hassan",
|
|
25
|
+
"bin": {
|
|
26
|
+
"nimbus": "./dist/cli/index.js"
|
|
27
|
+
},
|
|
28
|
+
"exports": {
|
|
29
|
+
".": {
|
|
30
|
+
"types": "./dist/index.d.ts",
|
|
31
|
+
"import": "./dist/index.js"
|
|
32
|
+
},
|
|
33
|
+
"./content": {
|
|
34
|
+
"types": "./dist/content.d.ts",
|
|
35
|
+
"import": "./dist/content.js"
|
|
36
|
+
},
|
|
37
|
+
"./schemas": {
|
|
38
|
+
"types": "./dist/schemas.d.ts",
|
|
39
|
+
"import": "./dist/schemas.js"
|
|
40
|
+
},
|
|
41
|
+
"./types": {
|
|
42
|
+
"types": "./dist/types.d.ts",
|
|
43
|
+
"import": "./dist/types.js"
|
|
44
|
+
},
|
|
45
|
+
"./client": {
|
|
46
|
+
"types": "./dist/client.d.ts",
|
|
47
|
+
"import": "./dist/client.js"
|
|
48
|
+
},
|
|
49
|
+
"./lib/pkgm": {
|
|
50
|
+
"types": "./dist/lib/pkgm.d.ts",
|
|
51
|
+
"import": "./dist/lib/pkgm.js"
|
|
52
|
+
},
|
|
53
|
+
"./components/NimbusHead.astro": "./src/components/NimbusHead.astro"
|
|
54
|
+
},
|
|
55
|
+
"files": [
|
|
56
|
+
"dist",
|
|
57
|
+
"src/components"
|
|
58
|
+
],
|
|
59
|
+
"scripts": {
|
|
60
|
+
"build": "tsdown",
|
|
61
|
+
"dev": "tsdown --watch",
|
|
62
|
+
"typecheck": "tsc --noEmit"
|
|
63
|
+
},
|
|
64
|
+
"peerDependencies": {
|
|
65
|
+
"astro": ">=6.4.0"
|
|
66
|
+
},
|
|
67
|
+
"dependencies": {
|
|
68
|
+
"@astrojs/markdown-satteri": "^0.2.1",
|
|
69
|
+
"@astrojs/mdx": "^6.0.0",
|
|
70
|
+
"@astrojs/sitemap": "^3.7.2",
|
|
71
|
+
"@clack/prompts": "^0.9.1",
|
|
72
|
+
"@shikijs/transformers": "^4.1.0",
|
|
73
|
+
"@vercel/detect-agent": "^1.2.3",
|
|
74
|
+
"mri": "^1.2.0"
|
|
75
|
+
},
|
|
76
|
+
"devDependencies": {
|
|
77
|
+
"astro": "^6.4.0",
|
|
78
|
+
"tsdown": "^0.20.3",
|
|
79
|
+
"typescript": "^5.8.3"
|
|
80
|
+
}
|
|
81
|
+
}
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
---
|
|
2
|
+
/**
|
|
3
|
+
* NimbusHead — the framework's SEO/head plumbing as a single component.
|
|
4
|
+
*
|
|
5
|
+
* Absorbs the ~50 lines of `<meta>`, `<link>`, OG/Twitter tags, JSON-LD,
|
|
6
|
+
* favicon, canonical, sitemap, and alternate-markdown wiring that every
|
|
7
|
+
* docs site needs but no content author edits. Reads from the validated
|
|
8
|
+
* nimbus config (via `virtual:nimbus/config`) and Astro page context.
|
|
9
|
+
*
|
|
10
|
+
* Render once inside a `<head>` in your layout, after the
|
|
11
|
+
* `<meta charset>` / `<meta viewport>` basics and any theme-FOUC
|
|
12
|
+
* inline script:
|
|
13
|
+
*
|
|
14
|
+
* ---
|
|
15
|
+
* import NimbusHead from "nimbus-docs/components/NimbusHead.astro";
|
|
16
|
+
* const { title, description, ...rest } = Astro.props;
|
|
17
|
+
* ---
|
|
18
|
+
* <head>
|
|
19
|
+
* <meta charset="utf-8" />
|
|
20
|
+
* <meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
21
|
+
* <script is:inline>// theme FOUC inline script</script>
|
|
22
|
+
* <NimbusHead {title} {description} {...rest} />
|
|
23
|
+
* </head>
|
|
24
|
+
*
|
|
25
|
+
* SEO/OG fixes ship via framework version bump, not by every user
|
|
26
|
+
* re-doing their head tags. Roadmap row 7(a).
|
|
27
|
+
*/
|
|
28
|
+
|
|
29
|
+
import { existsSync } from "node:fs";
|
|
30
|
+
import { join } from "node:path";
|
|
31
|
+
import { config } from "virtual:nimbus/config";
|
|
32
|
+
import type { HeadElement } from "../types.js";
|
|
33
|
+
|
|
34
|
+
export interface Props {
|
|
35
|
+
/** Page title — used for `<title>` and OG/Twitter title. */
|
|
36
|
+
title: string;
|
|
37
|
+
/** Falls back to `config.description` when absent. */
|
|
38
|
+
description?: string;
|
|
39
|
+
/** Emit `<meta name="robots" content="noindex">`. */
|
|
40
|
+
noindex?: boolean;
|
|
41
|
+
/** Emit `<meta name="ai-deprioritize" content="true">`. */
|
|
42
|
+
aiDeprioritize?: boolean;
|
|
43
|
+
/** Absolute or site-relative URL for the markdown variant of this page. */
|
|
44
|
+
markdownUrl?: string;
|
|
45
|
+
/**
|
|
46
|
+
* Per-page OG/Twitter image override. Site-relative (`/og/foo.png`) or
|
|
47
|
+
* absolute. Falls back to `config.socialImage`, then to `/opengraph.png`
|
|
48
|
+
* if it exists in `public/`, otherwise `/og.png`.
|
|
49
|
+
*/
|
|
50
|
+
socialImage?: string;
|
|
51
|
+
/** Drives `<meta property="article:modified_time">` on non-home pages. */
|
|
52
|
+
lastUpdated?: Date;
|
|
53
|
+
/** Per-page head extras, appended after `config.head`. */
|
|
54
|
+
head?: HeadElement[];
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const {
|
|
58
|
+
title,
|
|
59
|
+
description,
|
|
60
|
+
noindex,
|
|
61
|
+
aiDeprioritize,
|
|
62
|
+
markdownUrl,
|
|
63
|
+
socialImage,
|
|
64
|
+
lastUpdated,
|
|
65
|
+
head: pageHead = [],
|
|
66
|
+
} = Astro.props;
|
|
67
|
+
|
|
68
|
+
// ---- Derived values -------------------------------------------------------
|
|
69
|
+
|
|
70
|
+
const hasSite = !!Astro.site;
|
|
71
|
+
const canonical = Astro.site
|
|
72
|
+
? new URL(Astro.url.pathname, Astro.site).href
|
|
73
|
+
: null;
|
|
74
|
+
|
|
75
|
+
const fullTitle = title !== config.title ? `${title} | ${config.title}` : title;
|
|
76
|
+
const resolvedDescription = description ?? config.description;
|
|
77
|
+
const faviconHref = `${import.meta.env.BASE_URL}favicon.svg`;
|
|
78
|
+
const lang = config.locale ?? "en";
|
|
79
|
+
|
|
80
|
+
const isHomePage = Astro.url.pathname === "/";
|
|
81
|
+
const ogType = isHomePage ? "website" : "article";
|
|
82
|
+
|
|
83
|
+
// `process.cwd()` resolves to the consumer's project root at build time.
|
|
84
|
+
// The check lets users drop a `public/opengraph.png` and have it picked
|
|
85
|
+
// up as the default social image without touching config.
|
|
86
|
+
const userOpenGraphImage = join(process.cwd(), "public", "opengraph.png");
|
|
87
|
+
const defaultSocialImage = existsSync(userOpenGraphImage) ? "/opengraph.png" : "/og.png";
|
|
88
|
+
const socialImagePath = socialImage ?? config.socialImage ?? defaultSocialImage;
|
|
89
|
+
const ogImage = socialImagePath
|
|
90
|
+
? Astro.site
|
|
91
|
+
? new URL(socialImagePath, Astro.site).href
|
|
92
|
+
: socialImagePath
|
|
93
|
+
: null;
|
|
94
|
+
const ogImageAlt = config.socialImageAlt;
|
|
95
|
+
|
|
96
|
+
const structuredData = canonical
|
|
97
|
+
? JSON.stringify({
|
|
98
|
+
"@context": "https://schema.org",
|
|
99
|
+
"@type": isHomePage ? "WebSite" : "WebPage",
|
|
100
|
+
name: fullTitle,
|
|
101
|
+
...(resolvedDescription ? { description: resolvedDescription } : {}),
|
|
102
|
+
url: canonical,
|
|
103
|
+
inLanguage: lang,
|
|
104
|
+
...(ogImage ? { image: ogImage } : {}),
|
|
105
|
+
...(isHomePage
|
|
106
|
+
? {}
|
|
107
|
+
: {
|
|
108
|
+
isPartOf: {
|
|
109
|
+
"@type": "WebSite",
|
|
110
|
+
name: config.title,
|
|
111
|
+
url: Astro.site ? new URL("/", Astro.site).href : config.site,
|
|
112
|
+
},
|
|
113
|
+
...(lastUpdated ? { dateModified: lastUpdated.toISOString() } : {}),
|
|
114
|
+
}),
|
|
115
|
+
})
|
|
116
|
+
: null;
|
|
117
|
+
|
|
118
|
+
// Per-HTML last-element-wins semantics: page extras come after config
|
|
119
|
+
// extras so per-page overrides win for any duplicated single-value tag.
|
|
120
|
+
const mergedHead = [...(config.head ?? []), ...pageHead];
|
|
121
|
+
---
|
|
122
|
+
|
|
123
|
+
<title>{fullTitle}</title>
|
|
124
|
+
|
|
125
|
+
<meta name="generator" content={Astro.generator} />
|
|
126
|
+
{resolvedDescription && <meta name="description" content={resolvedDescription} />}
|
|
127
|
+
{noindex && <meta name="robots" content="noindex" />}
|
|
128
|
+
{aiDeprioritize && <meta name="ai-deprioritize" content="true" />}
|
|
129
|
+
|
|
130
|
+
<link rel="icon" type="image/svg+xml" href={faviconHref} />
|
|
131
|
+
{hasSite && canonical && <link rel="canonical" href={canonical} />}
|
|
132
|
+
{hasSite && <link rel="sitemap" href="/sitemap-index.xml" />}
|
|
133
|
+
{markdownUrl && <link rel="alternate" type="text/markdown" href={markdownUrl} />}
|
|
134
|
+
|
|
135
|
+
<meta property="og:title" content={fullTitle} />
|
|
136
|
+
<meta property="og:type" content={ogType} />
|
|
137
|
+
<meta property="og:site_name" content={config.title} />
|
|
138
|
+
<meta property="og:locale" content={lang} />
|
|
139
|
+
{resolvedDescription && <meta property="og:description" content={resolvedDescription} />}
|
|
140
|
+
{hasSite && canonical && <meta property="og:url" content={canonical} />}
|
|
141
|
+
{ogImage && <meta property="og:image" content={ogImage} />}
|
|
142
|
+
{ogImage && ogImageAlt && <meta property="og:image:alt" content={ogImageAlt} />}
|
|
143
|
+
{ogImage && <meta property="og:image:width" content="1200" />}
|
|
144
|
+
{ogImage && <meta property="og:image:height" content="630" />}
|
|
145
|
+
{!isHomePage && lastUpdated && (
|
|
146
|
+
<meta property="article:modified_time" content={lastUpdated.toISOString()} />
|
|
147
|
+
)}
|
|
148
|
+
|
|
149
|
+
<meta name="twitter:card" content={ogImage ? "summary_large_image" : "summary"} />
|
|
150
|
+
<meta name="twitter:title" content={fullTitle} />
|
|
151
|
+
{resolvedDescription && <meta name="twitter:description" content={resolvedDescription} />}
|
|
152
|
+
{ogImage && <meta name="twitter:image" content={ogImage} />}
|
|
153
|
+
{ogImage && ogImageAlt && <meta name="twitter:image:alt" content={ogImageAlt} />}
|
|
154
|
+
|
|
155
|
+
{structuredData && <script type="application/ld+json" set:html={structuredData} />}
|
|
156
|
+
|
|
157
|
+
{mergedHead.map(({ tag: Tag, attrs = {}, content }) =>
|
|
158
|
+
content
|
|
159
|
+
? <Tag {...attrs} set:html={content} />
|
|
160
|
+
: <Tag {...attrs} />
|
|
161
|
+
)}
|