@minidocs/astro 0.0.0-stage → 0.1.0

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.
Files changed (66) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +26 -2
  3. package/dist/behaviors.d.mts +114 -0
  4. package/dist/behaviors.mjs +635 -0
  5. package/dist/index.d.mts +25 -0
  6. package/dist/index.mjs +262 -0
  7. package/dist/links-UTLyoo_o.mjs +177 -0
  8. package/dist/loaders.d.mts +20 -0
  9. package/dist/loaders.mjs +76 -0
  10. package/dist/routing-TMqRCY2Q.mjs +48 -0
  11. package/dist/runtime.d.mts +160 -0
  12. package/dist/runtime.mjs +310 -0
  13. package/package.json +80 -3
  14. package/src/components/Prose.astro +9 -0
  15. package/src/components/callout/Root.astro +14 -0
  16. package/src/components/callout/Title.astro +9 -0
  17. package/src/components/callout/index.ts +2 -0
  18. package/src/components/index.ts +7 -0
  19. package/src/components/layout/Aside.astro +29 -0
  20. package/src/components/layout/Header.astro +18 -0
  21. package/src/components/layout/Main.astro +9 -0
  22. package/src/components/layout/Root.astro +15 -0
  23. package/src/components/layout/Sidebar.astro +9 -0
  24. package/src/components/layout/Title.astro +10 -0
  25. package/src/components/layout/index.ts +6 -0
  26. package/src/components/pager/Label.astro +9 -0
  27. package/src/components/pager/Link.astro +12 -0
  28. package/src/components/pager/Root.astro +12 -0
  29. package/src/components/pager/Title.astro +9 -0
  30. package/src/components/pager/index.ts +4 -0
  31. package/src/components/search/Count.astro +10 -0
  32. package/src/components/search/Dialog.astro +14 -0
  33. package/src/components/search/Empty.astro +10 -0
  34. package/src/components/search/Error.astro +10 -0
  35. package/src/components/search/Found.astro +10 -0
  36. package/src/components/search/Idle.astro +10 -0
  37. package/src/components/search/Input.astro +26 -0
  38. package/src/components/search/Loading.astro +10 -0
  39. package/src/components/search/Query.astro +10 -0
  40. package/src/components/search/Results.astro +20 -0
  41. package/src/components/search/Root.astro +15 -0
  42. package/src/components/search/Shortcut.astro +14 -0
  43. package/src/components/search/Trigger.astro +28 -0
  44. package/src/components/search/index.ts +13 -0
  45. package/src/components/sidebar/Group.astro +45 -0
  46. package/src/components/sidebar/Item.astro +9 -0
  47. package/src/components/sidebar/Label.astro +9 -0
  48. package/src/components/sidebar/Link.astro +23 -0
  49. package/src/components/sidebar/List.astro +11 -0
  50. package/src/components/sidebar/Root.astro +24 -0
  51. package/src/components/sidebar/Trigger.astro +29 -0
  52. package/src/components/sidebar/index.ts +7 -0
  53. package/src/components/toc/Item.astro +9 -0
  54. package/src/components/toc/Link.astro +12 -0
  55. package/src/components/toc/List.astro +11 -0
  56. package/src/components/toc/Root.astro +20 -0
  57. package/src/components/toc/index.ts +4 -0
  58. package/src/styles/base.css +41 -0
  59. package/src/styles/callout.css +58 -0
  60. package/src/styles/layout.css +406 -0
  61. package/src/styles/pager.css +51 -0
  62. package/src/styles/prose.css +218 -0
  63. package/src/styles/search.css +202 -0
  64. package/src/styles/sidebar.css +162 -0
  65. package/src/styles/toc.css +79 -0
  66. package/src/styles.css +9 -0
package/dist/index.mjs ADDED
@@ -0,0 +1,262 @@
1
+ import { t as contentOptions } from "./routing-TMqRCY2Q.mjs";
2
+ import { a as renderConfigTypes, i as registry, n as remarkContentLinks, r as satteriContentLinks } from "./links-UTLyoo_o.mjs";
3
+ import { existsSync, mkdirSync, writeFileSync } from "node:fs";
4
+ import { dirname } from "node:path";
5
+ import { fileURLToPath } from "node:url";
6
+ import { parseFilePath } from "@minidocs/core";
7
+ import remarkDirective from "remark-directive";
8
+ //#region src/callouts.ts
9
+ /** Callout types, shared by `:::type` directives, `> [!TYPE]` alerts and the `Callout` component. */
10
+ const CALLOUT_TYPES = [
11
+ "note",
12
+ "tip",
13
+ "important",
14
+ "warning",
15
+ "caution"
16
+ ];
17
+ const isType = (name) => CALLOUT_TYPES.includes(name);
18
+ /** Same markup as the Callout component renders, as mdast data for the HTML conversion. */
19
+ const calloutData = (type) => ({
20
+ hName: "div",
21
+ hProperties: {
22
+ className: ["md-callout"],
23
+ dataType: type,
24
+ role: "note"
25
+ }
26
+ });
27
+ const titleData = {
28
+ hName: "p",
29
+ hProperties: { className: ["md-callout-title"] }
30
+ };
31
+ const ALERT = /^\[!(\w+)\][ \t]*(?:\n|$)/;
32
+ function unknownDirective(name) {
33
+ return /* @__PURE__ */ new Error(`[minidocs] Unknown callout type ":::${name}". Use one of ${CALLOUT_TYPES.join(", ")}, or handle it with your own plugin.`);
34
+ }
35
+ /** Reads the type of a `> [!TYPE]` alert and what is left of its first line, or `undefined` for plain blockquotes. */
36
+ function parseAlert(blockquote) {
37
+ const paragraph = blockquote.children?.[0];
38
+ const text = paragraph?.type === "paragraph" ? paragraph.children?.[0] : void 0;
39
+ const match = text?.type === "text" ? ALERT.exec(text.value ?? "") : null;
40
+ const type = match?.[1]?.toLowerCase();
41
+ if (!match || !type || !isType(type)) return void 0;
42
+ const rest = text.value.slice(match[0].length);
43
+ return {
44
+ type,
45
+ paragraph,
46
+ text,
47
+ rest,
48
+ empty: !rest && paragraph.children.length === 1
49
+ };
50
+ }
51
+ /**
52
+ * Turning on directives also parses `:name` in text and `::name` lines, which would silently drop `10:30` or `a:hover`.
53
+ * Only `:::` blocks are callouts, so the others go back to their source text.
54
+ */
55
+ function restoreSatteri(block) {
56
+ return (node, context) => {
57
+ const text = {
58
+ type: "text",
59
+ value: context.source.slice(node.position.start.offset, node.position.end.offset)
60
+ };
61
+ context.replaceNode(node, block ? {
62
+ type: "paragraph",
63
+ children: [text]
64
+ } : text);
65
+ };
66
+ }
67
+ /** Plugin for Sätteri, Astro's default Markdown processor. */
68
+ function satteriCallouts(options) {
69
+ return {
70
+ name: "minidocs-callouts",
71
+ options: { position: true },
72
+ ...options.directives && {
73
+ containerDirective(node, context) {
74
+ if (node.data?.hName) return;
75
+ const name = node.name ?? "";
76
+ if (!isType(name)) throw unknownDirective(name);
77
+ const label = node.children?.[0];
78
+ if (label?.type === "paragraph" && label.data?.directiveLabel) context.setProperty(label, "data", titleData);
79
+ context.setProperty(node, "data", calloutData(name));
80
+ },
81
+ leafDirective: restoreSatteri(true),
82
+ textDirective: restoreSatteri(false)
83
+ },
84
+ ...options.alerts && { blockquote(node, context) {
85
+ const alert = parseAlert(node);
86
+ if (!alert) return;
87
+ if (alert.empty) context.removeNode(alert.paragraph);
88
+ else context.setProperty(alert.text, "value", alert.rest);
89
+ context.setProperty(node, "data", calloutData(alert.type));
90
+ } }
91
+ };
92
+ }
93
+ /** Remark plugin for the unified processor. Directive parsing comes from `remark-directive`, added before it. */
94
+ function remarkCallouts(options) {
95
+ return () => (tree, file) => {
96
+ const source = String(file.value ?? "");
97
+ const visit = (node) => {
98
+ const children = node.children ?? [];
99
+ for (let index = 0; index < children.length; index++) {
100
+ const child = children[index];
101
+ if (options.directives && (child.type === "textDirective" || child.type === "leafDirective")) {
102
+ const text = {
103
+ type: "text",
104
+ value: source.slice(child.position?.start.offset, child.position?.end.offset)
105
+ };
106
+ children[index] = child.type === "leafDirective" ? {
107
+ type: "paragraph",
108
+ children: [text]
109
+ } : text;
110
+ continue;
111
+ }
112
+ if (options.directives && child.type === "containerDirective" && !child.data?.hName) {
113
+ const name = child.name ?? "";
114
+ if (!isType(name)) throw unknownDirective(name);
115
+ const label = child.children?.[0];
116
+ if (label?.type === "paragraph" && label.data?.directiveLabel) label.data = {
117
+ ...label.data,
118
+ ...titleData
119
+ };
120
+ child.data = {
121
+ ...child.data,
122
+ ...calloutData(name)
123
+ };
124
+ }
125
+ if (options.alerts && child.type === "blockquote") {
126
+ const alert = parseAlert(child);
127
+ if (alert) {
128
+ if (alert.empty) child.children.shift();
129
+ else alert.text.value = alert.rest;
130
+ child.data = {
131
+ ...child.data,
132
+ ...calloutData(alert.type)
133
+ };
134
+ }
135
+ }
136
+ visit(child);
137
+ }
138
+ };
139
+ visit(tree);
140
+ };
141
+ }
142
+ //#endregion
143
+ //#region src/markdown.ts
144
+ /**
145
+ * Adds minidocs' Markdown features to the configured processor, which MDX uses too unless given its own.
146
+ * Processors are meant to be extended in place. Returns `false` for processors minidocs does not know.
147
+ */
148
+ function extendProcessor(processor, callouts) {
149
+ const options = processor.options;
150
+ if (processor.name === "satteri") {
151
+ if (callouts.directives) options.features = {
152
+ ...options.features,
153
+ directive: true
154
+ };
155
+ options.mdastPlugins = [
156
+ ...options.mdastPlugins ?? [],
157
+ satteriContentLinks,
158
+ satteriCallouts(callouts)
159
+ ];
160
+ return true;
161
+ }
162
+ if (processor.name === "unified") {
163
+ options.remarkPlugins = [
164
+ ...options.remarkPlugins ?? [],
165
+ ...callouts.directives ? [remarkDirective] : [],
166
+ remarkContentLinks,
167
+ remarkCallouts(callouts)
168
+ ];
169
+ return true;
170
+ }
171
+ return false;
172
+ }
173
+ //#endregion
174
+ //#region src/settings.ts
175
+ const trim = (path) => path.replace(/^\/+|\/+$/g, "");
176
+ function resolveSettings(config, options) {
177
+ return {
178
+ prefix: trim(options.prefix ?? ""),
179
+ base: trim(config.base),
180
+ trailingSlash: config.trailingSlash === "always" || config.trailingSlash === "ignore" && config.build.format === "directory",
181
+ i18n: resolveI18n(config.i18n, options),
182
+ site: config.site ? config.site.replace(/\/+$/, "") : null
183
+ };
184
+ }
185
+ function resolveI18n(i18n, options) {
186
+ if (!i18n) {
187
+ if (options.fallback) throw new Error("[minidocs] `fallback` requires `i18n` in your Astro config.");
188
+ return null;
189
+ }
190
+ if (i18n.fallback && Object.keys(i18n.fallback).length > 0) throw new Error("[minidocs] Astro's `i18n.fallback` works per route file and produces wrong pages for content-driven docs routes in static builds. Remove it and declare fallback with `minidocs({ fallback })` instead.");
191
+ if (i18n.routing === "manual") throw new Error("[minidocs] `i18n.routing: \"manual\"` is not supported yet.");
192
+ const locales = i18n.locales.map((locale) => {
193
+ if (typeof locale !== "string") throw new Error("[minidocs] Locales with custom paths are not supported yet. Use plain locale strings.");
194
+ return locale;
195
+ });
196
+ const fallback = options.fallback ?? {};
197
+ for (const [locale, targets] of Object.entries(fallback)) for (const target of [locale, ...[targets].flat()]) if (!locales.includes(target)) throw new Error(`[minidocs] Fallback refers to "${target}", which is not one of i18n.locales (${locales.join(", ")}).`);
198
+ return {
199
+ locales,
200
+ defaultLocale: i18n.defaultLocale,
201
+ prefixDefaultLocale: i18n.routing?.prefixDefaultLocale ?? false,
202
+ fallback
203
+ };
204
+ }
205
+ //#endregion
206
+ //#region src/index.ts
207
+ const CONFIG_ID = "virtual:minidocs/config";
208
+ const RENDERERS_ID = "virtual:minidocs/renderers";
209
+ function minidocs(options = {}) {
210
+ let hasMdx = false;
211
+ return {
212
+ name: "minidocs",
213
+ hooks: {
214
+ "astro:config:setup"({ config, updateConfig, logger }) {
215
+ const callouts = {
216
+ directives: options.markdown?.directives ?? true,
217
+ alerts: options.markdown?.alerts ?? true
218
+ };
219
+ if (!extendProcessor(config.markdown.processor, callouts)) logger.warn(`Content links and callouts are not supported with the "${config.markdown.processor.name}" Markdown processor.`);
220
+ updateConfig({ vite: {
221
+ plugins: [{
222
+ name: "minidocs:config",
223
+ resolveId: (id) => id === CONFIG_ID ? `\0${CONFIG_ID}` : void 0,
224
+ load: (id) => id === `\0${CONFIG_ID}` ? `export default ${JSON.stringify({
225
+ ...registry.settings,
226
+ bases: Object.fromEntries(Object.entries(registry.collections).map(([name, { base }]) => [name, base])),
227
+ pageIds: Object.fromEntries(Object.entries(registry.collections).map(([name, collection]) => [name, pageIds(collection)]))
228
+ })};` : void 0
229
+ }, {
230
+ name: "minidocs:renderers",
231
+ resolveId: (id) => id === RENDERERS_ID ? `\0${RENDERERS_ID}` : void 0,
232
+ load: (id) => id === `\0${RENDERERS_ID}` ? hasMdx ? `import { getContainerRenderer } from '@astrojs/mdx';\nexport default [getContainerRenderer()];` : "export default [];" : void 0
233
+ }],
234
+ ssr: { noExternal: ["@minidocs/astro", "@minidocs/core"] }
235
+ } });
236
+ },
237
+ "astro:config:done"({ config, injectTypes }) {
238
+ registry.settings = resolveSettings(config, options);
239
+ registry.root = fileURLToPath(config.root);
240
+ hasMdx = config.integrations.some(({ name }) => name === "@astrojs/mdx");
241
+ const injected = injectTypes({
242
+ filename: "types.d.ts",
243
+ content: renderConfigTypes(registry.settings)
244
+ });
245
+ registry.typesFile = new URL("./collections.d.ts", injected);
246
+ const path = fileURLToPath(registry.typesFile);
247
+ if (!existsSync(path)) {
248
+ mkdirSync(dirname(path), { recursive: true });
249
+ writeFileSync(path, "export {};\n");
250
+ }
251
+ }
252
+ }
253
+ };
254
+ }
255
+ /** Ids of every page in a collection, in any locale. */
256
+ function pageIds({ base, filePaths }) {
257
+ if (!registry.settings) return [];
258
+ const { locales, localeDirs } = contentOptions(registry.settings, base);
259
+ return [...new Set(filePaths.map((filePath) => parseFilePath(filePath, base, locales, localeDirs).id))];
260
+ }
261
+ //#endregion
262
+ export { minidocs as default };
@@ -0,0 +1,177 @@
1
+ import { n as href, t as contentOptions } from "./routing-TMqRCY2Q.mjs";
2
+ import { existsSync, readFileSync } from "node:fs";
3
+ import { dirname, posix, relative, resolve, sep } from "node:path";
4
+ import { fileURLToPath } from "node:url";
5
+ import { listPages, parseFilePath } from "@minidocs/core";
6
+ //#region src/codegen.ts
7
+ const union = (values) => {
8
+ const list = [...new Set(values)].sort().map((value) => JSON.stringify(value));
9
+ return list.length > 0 ? list.join(" | ") : "never";
10
+ };
11
+ /** Renders the site-wide types, known as soon as the config is resolved. */
12
+ function renderConfigTypes(settings) {
13
+ return [
14
+ "// Generated by minidocs. Do not edit.",
15
+ "/// <reference path=\"./collections.d.ts\" />",
16
+ "declare module '@minidocs/astro/runtime' {",
17
+ `\tinterface Config { ${settings.i18n ? `i18n: true; locale: ${union(settings.i18n.locales)};` : "i18n: false;"} }`,
18
+ "}",
19
+ "export {};",
20
+ ""
21
+ ].join("\n");
22
+ }
23
+ /** Renders the declaration file that types page ids and directories per collection, and per locale for multilingual sites. */
24
+ function renderTypes(settings, collections) {
25
+ const lines = [
26
+ "// Generated by minidocs. Do not edit.",
27
+ "declare module '@minidocs/astro/runtime' {",
28
+ " interface Collections {"
29
+ ];
30
+ for (const [name, { base, filePaths }] of Object.entries(collections).sort(([a], [b]) => a < b ? -1 : 1)) {
31
+ const options = contentOptions(settings, base);
32
+ const pages = listPages(filePaths.map((filePath) => ({ filePath })), options);
33
+ const idsAndDirs = (locale) => {
34
+ const inLocale = pages.filter((page) => page.locale === locale);
35
+ const dirs = /* @__PURE__ */ new Set([""]);
36
+ for (const page of inLocale) {
37
+ const { dir } = parseFilePath(page.entry.filePath, base, options.locales, options.localeDirs);
38
+ const segments = dir.split("/").filter(Boolean);
39
+ for (let i = 1; i <= segments.length; i++) dirs.add(segments.slice(0, i).join("/"));
40
+ }
41
+ return {
42
+ ids: union(inLocale.map((page) => page.id)),
43
+ dirs: union(dirs)
44
+ };
45
+ };
46
+ lines.push(`\t\t${JSON.stringify(name)}: {`);
47
+ if (settings.i18n) {
48
+ const perLocale = settings.i18n.locales.map((locale) => [JSON.stringify(locale), idsAndDirs(locale)]);
49
+ lines.push(" pages: {", ...perLocale.map(([locale, { ids }]) => `\t\t\t\t${locale}: ${ids};`), " };");
50
+ lines.push(" dirs: {", ...perLocale.map(([locale, { dirs }]) => `\t\t\t\t${locale}: ${dirs};`), " };");
51
+ } else {
52
+ const { ids, dirs } = idsAndDirs(options.locales[0]);
53
+ lines.push(`\t\t\tpages: ${ids};`, `\t\t\tdirs: ${dirs};`);
54
+ }
55
+ lines.push(" };");
56
+ }
57
+ lines.push(" }", "}", "export {};", "");
58
+ return lines.join("\n");
59
+ }
60
+ //#endregion
61
+ //#region src/registry.ts
62
+ const key = Symbol.for("minidocs.registry");
63
+ const scope = globalThis;
64
+ scope[key] ??= { collections: {} };
65
+ const registry = scope[key];
66
+ //#endregion
67
+ //#region src/links.ts
68
+ /** Link to another page from a page in the same locale, relative so it stays in whatever locale the page is shown in. */
69
+ function relativeHref(settings, fromId, toId, locale) {
70
+ const from = href(settings, fromId, locale);
71
+ const to = href(settings, toId, locale);
72
+ const fromDir = from.slice(0, from.lastIndexOf("/") + 1);
73
+ if (to.endsWith("/")) return `${posix.relative(fromDir, to) || "."}/`;
74
+ return posix.join(posix.relative(fromDir, posix.dirname(to)), posix.basename(to));
75
+ }
76
+ const EXCLUDED = "points to a page that the collection's filter excludes.";
77
+ /**
78
+ * Rewrites a relative link to a content file into the URL of its page.
79
+ * Links within one locale become relative URLs, so pages shown through fallback keep linking within the shown locale.
80
+ * Returns `undefined` for links that are not to `.md` or `.mdx` files. Throws when the target does not exist.
81
+ */
82
+ function resolveContentLink(settings, source, url, root) {
83
+ const match = /^([^#?]+\.mdx?)(#.*)?$/i.exec(url);
84
+ if (!match || /^([a-z][a-z\d+.-]*:|\/)/i.test(url)) return void 0;
85
+ const [, path, hash = ""] = match;
86
+ const absolute = resolve(root, dirname(source.file), decodeURI(path));
87
+ const target = relative(root, absolute).split(sep).join("/");
88
+ if (!target.startsWith(`${source.base}/`)) throw new Error(`"${url}" points outside the content collection, so it has no page.`);
89
+ if (!existsSync(absolute)) throw new Error(`"${url}" points to a file that does not exist.`);
90
+ if (source.isExcluded?.(target)) throw new Error(`"${url}" ${EXCLUDED}`);
91
+ const { locales, localeDirs } = contentOptions(settings, source.base);
92
+ const from = parseFilePath(source.file, source.base, locales, localeDirs);
93
+ const to = parseFilePath(target, source.base, locales, localeDirs);
94
+ return (from.locale === to.locale ? relativeHref(settings, from.id, to.id, settings.i18n ? from.locale : void 0) : href(settings, to.id, to.locale)) + hash;
95
+ }
96
+ /**
97
+ * Resolves a link found in a Markdown file, or returns `undefined` to leave it alone.
98
+ * Only files inside a collection loaded with minidocs' glob are rewritten.
99
+ */
100
+ function rewrite(filePath, url) {
101
+ const { settings, root } = registry;
102
+ if (!settings || !root) return void 0;
103
+ const file = relative(root, filePath).split(sep).join("/");
104
+ const collection = Object.values(registry.collections).find(({ base }) => file.startsWith(`${base}/`));
105
+ if (!collection) return void 0;
106
+ const isExcluded = (target) => {
107
+ if (collection.loading) {
108
+ collection.pendingLinks.push({
109
+ file,
110
+ url,
111
+ target
112
+ });
113
+ return false;
114
+ }
115
+ return collection.excluded.has(target);
116
+ };
117
+ return resolveContentLink(settings, {
118
+ file,
119
+ base: collection.base,
120
+ isExcluded
121
+ }, url, root);
122
+ }
123
+ /** Checks links that Markdown rendered during a load, now that every entry has been filtered. */
124
+ function checkPendingLinks(collection) {
125
+ const pending = collection.pendingLinks;
126
+ collection.pendingLinks = [];
127
+ const broken = pending.filter(({ file, target }) => !collection.excluded.has(file) && collection.excluded.has(target));
128
+ if (broken.length === 0) return;
129
+ const root = registry.root ?? "";
130
+ throw new Error(broken.map(({ file, url }) => failure(resolve(root, file), url, /* @__PURE__ */ new Error(`"${url}" ${EXCLUDED}`)).message).join("\n"));
131
+ }
132
+ /** Points at the line of the source file with the link. Processors may count lines from after the frontmatter. */
133
+ function failure(filePath, url, error) {
134
+ let line;
135
+ try {
136
+ const index = readFileSync(filePath, "utf8").split("\n").findIndex((text) => text.includes(url));
137
+ if (index !== -1) line = index + 1;
138
+ } catch {}
139
+ const location = `${relative(registry.root ?? "", filePath)}${line ? `:${line}` : ""}`;
140
+ return /* @__PURE__ */ new Error(`[minidocs] ${location}: ${error.message}`);
141
+ }
142
+ /** Plugin for Sätteri, Astro's default Markdown processor. */
143
+ const satteriContentLinks = (() => {
144
+ const visit = (node, context) => {
145
+ if (!context.fileURL) return;
146
+ const filePath = fileURLToPath(context.fileURL);
147
+ try {
148
+ const url = rewrite(filePath, node.url);
149
+ if (url !== void 0) context.setProperty(node, "url", url);
150
+ } catch (error) {
151
+ throw failure(filePath, node.url, error);
152
+ }
153
+ };
154
+ return {
155
+ name: "minidocs-content-links",
156
+ link: visit,
157
+ definition: visit
158
+ };
159
+ })();
160
+ /** Remark plugin for the unified processor from `@astrojs/markdown-remark`. */
161
+ function remarkContentLinks() {
162
+ return (tree, file) => {
163
+ const filePath = file.path;
164
+ if (!filePath) return;
165
+ const visit = (node) => {
166
+ if ((node.type === "link" || node.type === "definition") && node.url) try {
167
+ node.url = rewrite(filePath, node.url) ?? node.url;
168
+ } catch (error) {
169
+ throw failure(filePath, node.url, error);
170
+ }
171
+ for (const child of node.children ?? []) visit(child);
172
+ };
173
+ visit(tree);
174
+ };
175
+ }
176
+ //#endregion
177
+ export { renderConfigTypes as a, registry as i, remarkContentLinks as n, renderTypes as o, satteriContentLinks as r, checkPendingLinks as t };
@@ -0,0 +1,20 @@
1
+ import { Loader, glob as glob$1 } from "astro/loaders";
2
+ //#region src/loaders.d.ts
3
+ type GlobOptions = Parameters<typeof glob$1>[0];
4
+ /** An entry as the filter sees it, after the collection schema has parsed its data. */
5
+ export interface FilterEntry {
6
+ id: string;
7
+ data: Record<string, unknown>;
8
+ filePath?: string | undefined;
9
+ }
10
+ /**
11
+ * Astro's `glob()` loader, plus recording of the collection root so minidocs can
12
+ * derive locales and page ids, and generate types for them.
13
+ * Entries rejected by `filter` never reach the collection, so types, links and pages all agree on what exists.
14
+ */
15
+ export declare function glob({ filter, ...options }: GlobOptions & {
16
+ base: string | URL;
17
+ filter?: (entry: FilterEntry) => boolean;
18
+ }): Loader;
19
+ //#endregion
20
+ export {};
@@ -0,0 +1,76 @@
1
+ import { i as registry, o as renderTypes, t as checkPendingLinks } from "./links-UTLyoo_o.mjs";
2
+ import { mkdirSync, writeFileSync } from "node:fs";
3
+ import { dirname, relative } from "node:path";
4
+ import { fileURLToPath } from "node:url";
5
+ import { glob as glob$1 } from "astro/loaders";
6
+ //#region src/loaders.ts
7
+ /**
8
+ * Astro's `glob()` loader, plus recording of the collection root so minidocs can
9
+ * derive locales and page ids, and generate types for them.
10
+ * Entries rejected by `filter` never reach the collection, so types, links and pages all agree on what exists.
11
+ */
12
+ function glob({ filter, ...options }) {
13
+ const inner = glob$1(options);
14
+ return {
15
+ ...inner,
16
+ name: "minidocs-glob",
17
+ async load(context) {
18
+ const baseUrl = options.base instanceof URL ? options.base : new URL(options.base, context.config.root);
19
+ const base = relative(fileURLToPath(context.config.root), fileURLToPath(baseUrl)).replaceAll("\\", "/");
20
+ let scheduled;
21
+ const update = () => {
22
+ clearTimeout(scheduled);
23
+ scheduled = setTimeout(() => writeTypes(context), 50);
24
+ };
25
+ registry.collections[context.collection] ??= {
26
+ base,
27
+ filePaths: [],
28
+ excluded: /* @__PURE__ */ new Set(),
29
+ loading: false,
30
+ pendingLinks: []
31
+ };
32
+ const state = registry.collections[context.collection];
33
+ state.base = base;
34
+ const store = new Proxy(context.store, { get(target, property, receiver) {
35
+ const value = Reflect.get(target, property, receiver);
36
+ if (property !== "set" && property !== "delete" && property !== "clear") return value;
37
+ return (...args) => {
38
+ update();
39
+ if (property === "set" && filter) {
40
+ const entry = args[0];
41
+ if (!filter(entry)) {
42
+ if (entry.filePath) state.excluded.add(entry.filePath);
43
+ if (target.has(entry.id)) target.delete(entry.id);
44
+ return false;
45
+ }
46
+ if (entry.filePath) state.excluded.delete(entry.filePath);
47
+ }
48
+ return value.apply(target, args);
49
+ };
50
+ } });
51
+ state.loading = true;
52
+ try {
53
+ await inner.load({
54
+ ...context,
55
+ store
56
+ });
57
+ } finally {
58
+ state.loading = false;
59
+ }
60
+ clearTimeout(scheduled);
61
+ writeTypes(context);
62
+ checkPendingLinks(state);
63
+ }
64
+ };
65
+ }
66
+ function writeTypes(context) {
67
+ const { settings, typesFile } = registry;
68
+ if (!settings || !typesFile) throw new Error("[minidocs] Add the minidocs() integration to your Astro config to use glob from '@minidocs/astro/loaders'.");
69
+ const filePaths = context.store.values().map((entry) => entry.filePath).filter((filePath) => Boolean(filePath));
70
+ registry.collections[context.collection].filePaths = filePaths;
71
+ const path = fileURLToPath(typesFile);
72
+ mkdirSync(dirname(path), { recursive: true });
73
+ writeFileSync(path, renderTypes(settings, registry.collections));
74
+ }
75
+ //#endregion
76
+ export { glob };
@@ -0,0 +1,48 @@
1
+ //#region src/routing.ts
2
+ /** The locale every page of a single-language site belongs to inside core. Never exposed to users. */
3
+ const SINGLE_LOCALE = "default";
4
+ /** Options for core: locale directories for multilingual sites, one implicit locale otherwise. */
5
+ function contentOptions(settings, base) {
6
+ return settings.i18n ? {
7
+ base,
8
+ locales: settings.i18n.locales,
9
+ fallback: settings.i18n.fallback
10
+ } : {
11
+ base,
12
+ locales: [SINGLE_LOCALE],
13
+ localeDirs: false
14
+ };
15
+ }
16
+ /** The URL segment of a page id: the root index page has none. */
17
+ function slugOf(id) {
18
+ return id === "index" ? "" : id;
19
+ }
20
+ const normalizeLocale = (locale) => locale.replaceAll("_", "-").toLowerCase();
21
+ /**
22
+ * Route parameter for a page, e.g. `en/docs/getting-started`, without `base` or slashes around it.
23
+ * `undefined` stands for the site root, as Astro expects for rest parameters.
24
+ */
25
+ function routePath(settings, id, locale) {
26
+ const { i18n } = settings;
27
+ return [
28
+ !i18n || locale === void 0 || locale === i18n.defaultLocale && !i18n.prefixDefaultLocale ? "" : normalizeLocale(locale),
29
+ settings.prefix,
30
+ slugOf(id)
31
+ ].filter(Boolean).join("/") || void 0;
32
+ }
33
+ /** Link to a page, including `base` and the trailing slash Astro would use. */
34
+ function href(settings, id, locale) {
35
+ const path = [settings.base, routePath(settings, id, locale)].filter(Boolean).join("/");
36
+ if (!path) return "/";
37
+ return `/${path}${settings.trailingSlash ? "/" : ""}`;
38
+ }
39
+ /** Route parameter for a page in `src/pages/[...path].md.ts`. The site root has no segment of its own, so it becomes `index`. */
40
+ function markdownPath(settings, id, locale) {
41
+ return routePath(settings, id, locale) ?? "index";
42
+ }
43
+ /** Link to the Markdown version of a page, including `base`. */
44
+ function markdownHref(settings, id, locale) {
45
+ return `/${[settings.base, markdownPath(settings, id, locale)].filter(Boolean).join("/")}.md`;
46
+ }
47
+ //#endregion
48
+ export { routePath as a, markdownPath as i, href as n, markdownHref as r, contentOptions as t };