@raystack/chronicle 0.7.0 → 0.7.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/dist/cli/index.js +107 -28
- package/package.json +5 -1
- package/src/components/ui/search.tsx +3 -3
- package/src/lib/remark-resolve-images.ts +59 -0
- package/src/lib/remark-resolve-links.ts +32 -0
- package/src/lib/source.ts +8 -1
- package/src/pages/ApiLayout.tsx +0 -2
- package/src/server/App.tsx +1 -1
- package/src/server/api/apis-proxy.ts +2 -2
- package/src/server/api/health.ts +1 -1
- package/src/server/api/page.ts +2 -2
- package/src/server/api/search.ts +4 -4
- package/src/server/api/specs.ts +2 -2
- package/src/server/routes/[...slug].md.ts +1 -2
- package/src/server/routes/[version]/llms.txt.ts +1 -2
- package/src/server/routes/_content/[...path].ts +40 -0
- package/src/server/routes/llms.txt.ts +2 -3
- package/src/server/routes/og.tsx +1 -3
- package/src/server/routes/robots.txt.ts +2 -3
- package/src/server/routes/sitemap.xml.ts +3 -5
- package/src/server/vite-config.ts +5 -2
- package/src/themes/paper/Layout.module.css +4 -0
- package/src/themes/paper/Layout.tsx +2 -0
- package/src/lib/remark-strip-md-extensions.ts +0 -14
package/dist/cli/index.js
CHANGED
|
@@ -46,21 +46,97 @@ var __export = (target, all) => {
|
|
|
46
46
|
var __esm = (fn, res) => () => (fn && (res = fn(fn = 0)), res);
|
|
47
47
|
var __require = /* @__PURE__ */ createRequire(import.meta.url);
|
|
48
48
|
|
|
49
|
-
// src/lib/remark-
|
|
49
|
+
// src/lib/remark-resolve-images.ts
|
|
50
|
+
import path4 from "node:path";
|
|
50
51
|
import { visit } from "unist-util-visit";
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
52
|
+
function resolveUrl(src, dir) {
|
|
53
|
+
if (/^[a-z][a-z0-9+\-.]*:/i.test(src))
|
|
54
|
+
return src;
|
|
55
|
+
if (src.startsWith("//"))
|
|
56
|
+
return src;
|
|
57
|
+
if (src.startsWith("#"))
|
|
58
|
+
return src;
|
|
59
|
+
if (src.startsWith("/_content/"))
|
|
60
|
+
return src;
|
|
61
|
+
if (src.startsWith("/"))
|
|
62
|
+
return `/_content${src}`;
|
|
63
|
+
return `/_content/${path4.posix.normalize(path4.posix.join(dir, src))}`;
|
|
64
|
+
}
|
|
65
|
+
var remarkResolveImages = () => {
|
|
66
|
+
return (tree, file) => {
|
|
67
|
+
const filePath = file.path;
|
|
68
|
+
if (!filePath)
|
|
69
|
+
return;
|
|
70
|
+
const contentIdx = filePath.lastIndexOf("/content/");
|
|
71
|
+
if (contentIdx === -1)
|
|
72
|
+
return;
|
|
73
|
+
const relative = filePath.slice(contentIdx + "/content/".length);
|
|
74
|
+
const dir = path4.posix.dirname(relative);
|
|
75
|
+
visit(tree, "image", (node) => {
|
|
76
|
+
if (!node.url)
|
|
77
|
+
return;
|
|
78
|
+
node.url = resolveUrl(node.url, dir);
|
|
79
|
+
});
|
|
80
|
+
visit(tree, "html", (node) => {
|
|
81
|
+
node.value = node.value.replace(/(<img\b[^>]*\bsrc=["'])([^"']+)(["'])/gi, (_, before, src, after) => `${before}${resolveUrl(src, dir)}${after}`);
|
|
82
|
+
});
|
|
83
|
+
visit(tree, (node) => {
|
|
84
|
+
if (node.type !== "mdxJsxFlowElement" && node.type !== "mdxJsxTextElement")
|
|
85
|
+
return;
|
|
86
|
+
const jsx = node;
|
|
87
|
+
if (jsx.name !== "img")
|
|
88
|
+
return;
|
|
89
|
+
const srcAttr = jsx.attributes.find((a) => a.type === "mdxJsxAttribute" && a.name === "src");
|
|
90
|
+
if (!srcAttr?.value || typeof srcAttr.value !== "string")
|
|
91
|
+
return;
|
|
92
|
+
srcAttr.value = resolveUrl(srcAttr.value, dir);
|
|
93
|
+
});
|
|
94
|
+
visit(tree, "element", (node) => {
|
|
95
|
+
if (node.tagName !== "img")
|
|
96
|
+
return;
|
|
97
|
+
const src = node.properties?.src;
|
|
98
|
+
if (typeof src !== "string")
|
|
99
|
+
return;
|
|
100
|
+
node.properties.src = resolveUrl(src, dir);
|
|
101
|
+
});
|
|
102
|
+
};
|
|
103
|
+
}, remark_resolve_images_default;
|
|
104
|
+
var init_remark_resolve_images = __esm(() => {
|
|
105
|
+
remark_resolve_images_default = remarkResolveImages;
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
// src/lib/remark-resolve-links.ts
|
|
109
|
+
import path5 from "node:path";
|
|
110
|
+
import { visit as visit2 } from "unist-util-visit";
|
|
111
|
+
var remarkResolveLinks = () => {
|
|
112
|
+
return (tree, file) => {
|
|
113
|
+
const filePath = file.path;
|
|
114
|
+
if (!filePath)
|
|
115
|
+
return;
|
|
116
|
+
const contentIdx = filePath.lastIndexOf("/content/");
|
|
117
|
+
if (contentIdx === -1)
|
|
118
|
+
return;
|
|
119
|
+
const relative = filePath.slice(contentIdx + "/content/".length);
|
|
120
|
+
const dir = path5.posix.dirname(relative);
|
|
121
|
+
visit2(tree, "link", (node) => {
|
|
54
122
|
if (!node.url)
|
|
55
123
|
return;
|
|
56
|
-
if (
|
|
124
|
+
if (/^[a-z][a-z0-9+\-.]*:/i.test(node.url))
|
|
57
125
|
return;
|
|
58
|
-
|
|
126
|
+
if (node.url.startsWith("#"))
|
|
127
|
+
return;
|
|
128
|
+
if (node.url.startsWith("/"))
|
|
129
|
+
return;
|
|
130
|
+
const [rawPath, hash] = node.url.split("#");
|
|
131
|
+
const stripped = rawPath.replace(/\.mdx?$/, "");
|
|
132
|
+
let resolved = path5.posix.normalize(path5.posix.join(dir, stripped));
|
|
133
|
+
resolved = resolved.replace(/\/(index|readme)$/i, "") || ".";
|
|
134
|
+
node.url = `/${resolved}${hash ? `#${hash}` : ""}`;
|
|
59
135
|
});
|
|
60
136
|
};
|
|
61
|
-
},
|
|
62
|
-
var
|
|
63
|
-
|
|
137
|
+
}, remark_resolve_links_default;
|
|
138
|
+
var init_remark_resolve_links = __esm(() => {
|
|
139
|
+
remark_resolve_links_default = remarkResolveLinks;
|
|
64
140
|
});
|
|
65
141
|
|
|
66
142
|
// ../../node_modules/.bun/reading-time@1.5.0/node_modules/reading-time/lib/reading-time.js
|
|
@@ -181,13 +257,13 @@ var require_reading_time2 = __commonJS((exports, module) => {
|
|
|
181
257
|
});
|
|
182
258
|
|
|
183
259
|
// ../../node_modules/.bun/remark-reading-time@2.1.0/node_modules/remark-reading-time/index.js
|
|
184
|
-
import { visit as
|
|
260
|
+
import { visit as visit3 } from "unist-util-visit";
|
|
185
261
|
function readingTime({
|
|
186
262
|
attribute = "readingTime"
|
|
187
263
|
} = {}) {
|
|
188
264
|
return function(info, file) {
|
|
189
265
|
let text = "";
|
|
190
|
-
|
|
266
|
+
visit3(info, ["text", "code"], (node) => {
|
|
191
267
|
text += node.value;
|
|
192
268
|
});
|
|
193
269
|
file.data[attribute] = import_reading_time.default(text);
|
|
@@ -199,10 +275,10 @@ var init_remark_reading_time = __esm(() => {
|
|
|
199
275
|
});
|
|
200
276
|
|
|
201
277
|
// src/lib/remark-unused-directives.ts
|
|
202
|
-
import { visit as
|
|
278
|
+
import { visit as visit4 } from "unist-util-visit";
|
|
203
279
|
var remarkUnusedDirectives = () => {
|
|
204
280
|
return (tree) => {
|
|
205
|
-
|
|
281
|
+
visit4(tree, ["textDirective"], (node) => {
|
|
206
282
|
const directive = node;
|
|
207
283
|
if (!directive.data) {
|
|
208
284
|
const hasAttributes = directive.attributes && Object.keys(directive.attributes).length > 0;
|
|
@@ -234,12 +310,12 @@ import { defineConfig as defineFumadocsConfig } from "fumadocs-mdx/config";
|
|
|
234
310
|
import mdx from "fumadocs-mdx/vite";
|
|
235
311
|
import { nitro } from "nitro/vite";
|
|
236
312
|
import fs3 from "node:fs/promises";
|
|
237
|
-
import
|
|
313
|
+
import path6 from "node:path";
|
|
238
314
|
import remarkDirective from "remark-directive";
|
|
239
315
|
function resolveOutputDir(projectRoot, preset) {
|
|
240
316
|
if (preset === "vercel" || preset === "vercel-static")
|
|
241
|
-
return
|
|
242
|
-
return
|
|
317
|
+
return path6.resolve(projectRoot, ".vercel/output");
|
|
318
|
+
return path6.resolve(projectRoot, ".output");
|
|
243
319
|
}
|
|
244
320
|
async function readChronicleConfig(projectRoot, configPath) {
|
|
245
321
|
if (configPath) {
|
|
@@ -250,7 +326,7 @@ async function readChronicleConfig(projectRoot, configPath) {
|
|
|
250
326
|
}
|
|
251
327
|
}
|
|
252
328
|
try {
|
|
253
|
-
return await fs3.readFile(
|
|
329
|
+
return await fs3.readFile(path6.join(projectRoot, "chronicle.yaml"), "utf-8");
|
|
254
330
|
} catch {
|
|
255
331
|
return null;
|
|
256
332
|
}
|
|
@@ -258,18 +334,19 @@ async function readChronicleConfig(projectRoot, configPath) {
|
|
|
258
334
|
async function createViteConfig(options) {
|
|
259
335
|
const { packageRoot, projectRoot, configPath, preset } = options;
|
|
260
336
|
const rawConfig = await readChronicleConfig(projectRoot, configPath);
|
|
261
|
-
const contentMirror =
|
|
337
|
+
const contentMirror = path6.resolve(packageRoot, ".content");
|
|
262
338
|
return {
|
|
263
339
|
root: packageRoot,
|
|
264
340
|
configFile: false,
|
|
265
341
|
plugins: [
|
|
266
342
|
nitro({
|
|
267
|
-
serverDir:
|
|
343
|
+
serverDir: path6.resolve(packageRoot, "src/server"),
|
|
268
344
|
...preset && { preset }
|
|
269
345
|
}),
|
|
270
346
|
mdx({
|
|
271
347
|
default: defineFumadocsConfig({
|
|
272
348
|
mdxOptions: {
|
|
349
|
+
remarkImageOptions: false,
|
|
273
350
|
valueToExport: ["readingTime"],
|
|
274
351
|
remarkPlugins: [
|
|
275
352
|
remarkDirective,
|
|
@@ -291,7 +368,8 @@ async function createViteConfig(options) {
|
|
|
291
368
|
}
|
|
292
369
|
}],
|
|
293
370
|
remark_unused_directives_default,
|
|
294
|
-
|
|
371
|
+
remark_resolve_links_default,
|
|
372
|
+
remark_resolve_images_default,
|
|
295
373
|
remarkMdxMermaid,
|
|
296
374
|
readingTime
|
|
297
375
|
]
|
|
@@ -302,7 +380,7 @@ async function createViteConfig(options) {
|
|
|
302
380
|
],
|
|
303
381
|
resolve: {
|
|
304
382
|
alias: {
|
|
305
|
-
"@":
|
|
383
|
+
"@": path6.resolve(packageRoot, "src"),
|
|
306
384
|
tslib: "tslib/tslib.es6.js"
|
|
307
385
|
},
|
|
308
386
|
dedupe: [
|
|
@@ -335,7 +413,7 @@ async function createViteConfig(options) {
|
|
|
335
413
|
client: {
|
|
336
414
|
build: {
|
|
337
415
|
rollupOptions: {
|
|
338
|
-
input:
|
|
416
|
+
input: path6.resolve(packageRoot, "src/server/entry-client.tsx")
|
|
339
417
|
}
|
|
340
418
|
}
|
|
341
419
|
}
|
|
@@ -349,7 +427,8 @@ async function createViteConfig(options) {
|
|
|
349
427
|
};
|
|
350
428
|
}
|
|
351
429
|
var init_vite_config = __esm(() => {
|
|
352
|
-
|
|
430
|
+
init_remark_resolve_images();
|
|
431
|
+
init_remark_resolve_links();
|
|
353
432
|
init_remark_reading_time();
|
|
354
433
|
init_remark_unused_directives();
|
|
355
434
|
});
|
|
@@ -720,7 +799,7 @@ var devCommand = new Command2("dev").description("Start development server").opt
|
|
|
720
799
|
|
|
721
800
|
// src/cli/commands/init.ts
|
|
722
801
|
import fs4 from "node:fs";
|
|
723
|
-
import
|
|
802
|
+
import path7 from "node:path";
|
|
724
803
|
import chalk4 from "chalk";
|
|
725
804
|
import { Command as Command3 } from "commander";
|
|
726
805
|
import { stringify } from "yaml";
|
|
@@ -747,12 +826,12 @@ var GITIGNORE_ENTRIES = ["node_modules", "dist", ".output"];
|
|
|
747
826
|
function runInit(projectDir) {
|
|
748
827
|
const events = [];
|
|
749
828
|
const defaultDir = defaultInitConfig.content[0].dir;
|
|
750
|
-
const contentDir =
|
|
829
|
+
const contentDir = path7.join(projectDir, "content", defaultDir);
|
|
751
830
|
if (!fs4.existsSync(contentDir)) {
|
|
752
831
|
fs4.mkdirSync(contentDir, { recursive: true });
|
|
753
832
|
events.push({ type: "created", path: contentDir });
|
|
754
833
|
}
|
|
755
|
-
const configPath =
|
|
834
|
+
const configPath = path7.join(projectDir, "chronicle.yaml");
|
|
756
835
|
if (!fs4.existsSync(configPath)) {
|
|
757
836
|
fs4.writeFileSync(configPath, stringify(defaultInitConfig));
|
|
758
837
|
events.push({ type: "created", path: configPath });
|
|
@@ -761,11 +840,11 @@ function runInit(projectDir) {
|
|
|
761
840
|
}
|
|
762
841
|
const contentFiles = fs4.readdirSync(contentDir);
|
|
763
842
|
if (contentFiles.length === 0) {
|
|
764
|
-
const indexPath =
|
|
843
|
+
const indexPath = path7.join(contentDir, "index.mdx");
|
|
765
844
|
fs4.writeFileSync(indexPath, sampleMdx);
|
|
766
845
|
events.push({ type: "created", path: indexPath });
|
|
767
846
|
}
|
|
768
|
-
const gitignorePath =
|
|
847
|
+
const gitignorePath = path7.join(projectDir, ".gitignore");
|
|
769
848
|
if (fs4.existsSync(gitignorePath)) {
|
|
770
849
|
const existing = fs4.readFileSync(gitignorePath, "utf-8");
|
|
771
850
|
const existingLines = new Set(existing.split(/\r?\n/).map((l) => l.trim()).filter(Boolean));
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@raystack/chronicle",
|
|
3
|
-
"version": "0.7.
|
|
3
|
+
"version": "0.7.2",
|
|
4
4
|
"description": "Config-driven documentation framework",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"type": "module",
|
|
@@ -22,12 +22,16 @@
|
|
|
22
22
|
"devDependencies": {
|
|
23
23
|
"@biomejs/biome": "^2.3.13",
|
|
24
24
|
"@raystack/tools-config": "0.56.0",
|
|
25
|
+
"@types/hast": "^3.0.4",
|
|
25
26
|
"@types/lodash": "^4.17.23",
|
|
27
|
+
"@types/mdast": "^4.0.4",
|
|
26
28
|
"@types/mdx": "^2.0.13",
|
|
27
29
|
"@types/node": "^25.1.0",
|
|
28
30
|
"@types/react": "^19.2.10",
|
|
29
31
|
"@types/react-dom": "^19.2.3",
|
|
30
32
|
"@types/semver": "^7.7.1",
|
|
33
|
+
"@types/unist": "^3.0.3",
|
|
34
|
+
"mdast-util-mdx-jsx": "^3.2.0",
|
|
31
35
|
"semver": "^7.7.4",
|
|
32
36
|
"typescript": "5.9.3"
|
|
33
37
|
},
|
|
@@ -13,10 +13,10 @@ import { usePageContext } from '@/lib/page-context';
|
|
|
13
13
|
import styles from './search.module.css';
|
|
14
14
|
|
|
15
15
|
interface SearchProps {
|
|
16
|
-
|
|
16
|
+
classNames?: { trigger?: string };
|
|
17
17
|
}
|
|
18
18
|
|
|
19
|
-
export function Search({
|
|
19
|
+
export function Search({ classNames }: SearchProps) {
|
|
20
20
|
const [open, setOpen] = useState(false);
|
|
21
21
|
const navigate = useNavigate();
|
|
22
22
|
const { version } = usePageContext();
|
|
@@ -60,7 +60,7 @@ export function Search({ className }: SearchProps) {
|
|
|
60
60
|
aria-label='Search'
|
|
61
61
|
title='Search (Ctrl/⌘K)'
|
|
62
62
|
onClick={() => setOpen(true)}
|
|
63
|
-
className={
|
|
63
|
+
className={classNames?.trigger}
|
|
64
64
|
>
|
|
65
65
|
<MagnifyingGlassIcon width={16} height={16} />
|
|
66
66
|
</IconButton>
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import path from 'node:path'
|
|
2
|
+
import { visit } from 'unist-util-visit'
|
|
3
|
+
import type { Plugin } from 'unified'
|
|
4
|
+
import type { Image, Html } from 'mdast'
|
|
5
|
+
import type { Element } from 'hast'
|
|
6
|
+
import type { MdxJsxFlowElement, MdxJsxTextElement, MdxJsxAttribute } from 'mdast-util-mdx-jsx'
|
|
7
|
+
|
|
8
|
+
function resolveUrl(src: string, dir: string): string {
|
|
9
|
+
if (/^[a-z][a-z0-9+\-.]*:/i.test(src)) return src
|
|
10
|
+
if (src.startsWith('//')) return src
|
|
11
|
+
if (src.startsWith('#')) return src
|
|
12
|
+
if (src.startsWith('/_content/')) return src
|
|
13
|
+
|
|
14
|
+
if (src.startsWith('/')) return `/_content${src}`
|
|
15
|
+
return `/_content/${path.posix.normalize(path.posix.join(dir, src))}`
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const remarkResolveImages: Plugin = () => {
|
|
19
|
+
return (tree, file) => {
|
|
20
|
+
const filePath = file.path
|
|
21
|
+
if (!filePath) return
|
|
22
|
+
|
|
23
|
+
const contentIdx = filePath.lastIndexOf('/content/')
|
|
24
|
+
if (contentIdx === -1) return
|
|
25
|
+
|
|
26
|
+
const relative = filePath.slice(contentIdx + '/content/'.length)
|
|
27
|
+
const dir = path.posix.dirname(relative)
|
|
28
|
+
|
|
29
|
+
visit(tree, 'image', (node: Image) => {
|
|
30
|
+
if (!node.url) return
|
|
31
|
+
node.url = resolveUrl(node.url, dir)
|
|
32
|
+
})
|
|
33
|
+
|
|
34
|
+
visit(tree, 'html', (node: Html) => {
|
|
35
|
+
node.value = node.value.replace(
|
|
36
|
+
/(<img\b[^>]*\bsrc=["'])([^"']+)(["'])/gi,
|
|
37
|
+
(_, before, src, after) => `${before}${resolveUrl(src, dir)}${after}`
|
|
38
|
+
)
|
|
39
|
+
})
|
|
40
|
+
|
|
41
|
+
visit(tree, (node) => {
|
|
42
|
+
if (node.type !== 'mdxJsxFlowElement' && node.type !== 'mdxJsxTextElement') return
|
|
43
|
+
const jsx = node as MdxJsxFlowElement | MdxJsxTextElement
|
|
44
|
+
if (jsx.name !== 'img') return
|
|
45
|
+
const srcAttr = jsx.attributes.find((a): a is MdxJsxAttribute => a.type === 'mdxJsxAttribute' && a.name === 'src')
|
|
46
|
+
if (!srcAttr?.value || typeof srcAttr.value !== 'string') return
|
|
47
|
+
srcAttr.value = resolveUrl(srcAttr.value, dir)
|
|
48
|
+
})
|
|
49
|
+
|
|
50
|
+
visit(tree, 'element', (node: Element) => {
|
|
51
|
+
if (node.tagName !== 'img') return
|
|
52
|
+
const src = node.properties?.src
|
|
53
|
+
if (typeof src !== 'string') return
|
|
54
|
+
node.properties.src = resolveUrl(src, dir)
|
|
55
|
+
})
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export default remarkResolveImages
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import path from 'node:path'
|
|
2
|
+
import { visit } from 'unist-util-visit'
|
|
3
|
+
import type { Plugin } from 'unified'
|
|
4
|
+
import type { Link } from 'mdast'
|
|
5
|
+
|
|
6
|
+
const remarkResolveLinks: Plugin = () => {
|
|
7
|
+
return (tree, file) => {
|
|
8
|
+
const filePath = file.path
|
|
9
|
+
if (!filePath) return
|
|
10
|
+
|
|
11
|
+
const contentIdx = filePath.lastIndexOf('/content/')
|
|
12
|
+
if (contentIdx === -1) return
|
|
13
|
+
|
|
14
|
+
const relative = filePath.slice(contentIdx + '/content/'.length)
|
|
15
|
+
const dir = path.posix.dirname(relative)
|
|
16
|
+
|
|
17
|
+
visit(tree, 'link', (node: Link) => {
|
|
18
|
+
if (!node.url) return
|
|
19
|
+
if (/^[a-z][a-z0-9+\-.]*:/i.test(node.url)) return
|
|
20
|
+
if (node.url.startsWith('#')) return
|
|
21
|
+
if (node.url.startsWith('/')) return
|
|
22
|
+
|
|
23
|
+
const [rawPath, hash] = node.url.split('#')
|
|
24
|
+
const stripped = rawPath.replace(/\.mdx?$/, '')
|
|
25
|
+
let resolved = path.posix.normalize(path.posix.join(dir, stripped))
|
|
26
|
+
resolved = resolved.replace(/\/(index|readme)$/i, '') || '.'
|
|
27
|
+
node.url = `/${resolved}${hash ? `#${hash}` : ''}`
|
|
28
|
+
})
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export default remarkResolveLinks
|
package/src/lib/source.ts
CHANGED
|
@@ -23,6 +23,11 @@ const frontmatterGlob: Record<string, Record<string, unknown>> = import.meta.glo
|
|
|
23
23
|
{ eager: true, import: 'frontmatter' }
|
|
24
24
|
);
|
|
25
25
|
|
|
26
|
+
const readingTimeGlob: Record<string, { text: string; minutes: number; words: number; time: number } | undefined> = import.meta.glob(
|
|
27
|
+
'../../.content/**/*.{mdx,md}',
|
|
28
|
+
{ eager: true, import: 'readingTime' }
|
|
29
|
+
);
|
|
30
|
+
|
|
26
31
|
const metaGlob: Record<string, Record<string, unknown>> = import.meta.glob(
|
|
27
32
|
'../../.content/**/meta.json',
|
|
28
33
|
{ eager: true }
|
|
@@ -38,10 +43,12 @@ function buildFiles() {
|
|
|
38
43
|
for (const [key, data] of Object.entries(frontmatterGlob)) {
|
|
39
44
|
const originalPath = key.slice(CONTENT_PREFIX.length);
|
|
40
45
|
const relativePath = originalPath.replace(/readme\.(mdx?)$/i, 'index.$1');
|
|
46
|
+
const rt = readingTimeGlob[key];
|
|
47
|
+
const _readingTime = rt?.minutes != null ? Math.max(1, Math.round(rt.minutes)) : undefined;
|
|
41
48
|
files.push({
|
|
42
49
|
type: 'page',
|
|
43
50
|
path: relativePath,
|
|
44
|
-
data: { ...data, _relativePath: relativePath, _originalPath: originalPath }
|
|
51
|
+
data: { ...data, _readingTime, _relativePath: relativePath, _originalPath: originalPath }
|
|
45
52
|
});
|
|
46
53
|
}
|
|
47
54
|
|
package/src/pages/ApiLayout.tsx
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { cx } from 'class-variance-authority';
|
|
2
2
|
import type { ReactNode } from 'react';
|
|
3
|
-
import { Search } from '@/components/ui/search';
|
|
4
3
|
import { buildApiPageTree } from '@/lib/api-routes';
|
|
5
4
|
import { usePageContext } from '@/lib/page-context';
|
|
6
5
|
import { getTheme } from '@/themes/registry';
|
|
@@ -26,7 +25,6 @@ export function ApiLayout({ children }: ApiLayoutProps) {
|
|
|
26
25
|
content: styles.content
|
|
27
26
|
}}
|
|
28
27
|
>
|
|
29
|
-
<Search className={styles.hiddenSearch} />
|
|
30
28
|
{children}
|
|
31
29
|
</Layout>
|
|
32
30
|
);
|
package/src/server/App.tsx
CHANGED
|
@@ -51,11 +51,11 @@ export default defineHandler(async event => {
|
|
|
51
51
|
? await response.json()
|
|
52
52
|
: await response.text();
|
|
53
53
|
|
|
54
|
-
return {
|
|
54
|
+
return Response.json({
|
|
55
55
|
status: response.status,
|
|
56
56
|
statusText: response.statusText,
|
|
57
57
|
body: responseBody
|
|
58
|
-
};
|
|
58
|
+
});
|
|
59
59
|
} catch (error) {
|
|
60
60
|
const message =
|
|
61
61
|
error instanceof Error
|
package/src/server/api/health.ts
CHANGED
package/src/server/api/page.ts
CHANGED
|
@@ -12,11 +12,11 @@ export default defineHandler(async event => {
|
|
|
12
12
|
|
|
13
13
|
const nav = await getPageNav(slug);
|
|
14
14
|
|
|
15
|
-
return {
|
|
15
|
+
return Response.json({
|
|
16
16
|
frontmatter: extractFrontmatter(page, slug[slug.length - 1]),
|
|
17
17
|
relativePath: getRelativePath(page),
|
|
18
18
|
originalPath: getOriginalPath(page),
|
|
19
19
|
prev: nav.prev,
|
|
20
20
|
next: nav.next,
|
|
21
|
-
};
|
|
21
|
+
});
|
|
22
22
|
});
|
package/src/server/api/search.ts
CHANGED
|
@@ -125,7 +125,7 @@ export default defineHandler(async event => {
|
|
|
125
125
|
|
|
126
126
|
if (!query) {
|
|
127
127
|
const docs = await getDocs(ctx);
|
|
128
|
-
return docs
|
|
128
|
+
return Response.json(docs
|
|
129
129
|
.filter(d => d.type === 'page')
|
|
130
130
|
.slice(0, 8)
|
|
131
131
|
.map(d => ({
|
|
@@ -133,13 +133,13 @@ export default defineHandler(async event => {
|
|
|
133
133
|
url: d.url,
|
|
134
134
|
type: d.type,
|
|
135
135
|
content: d.title
|
|
136
|
-
}));
|
|
136
|
+
})));
|
|
137
137
|
}
|
|
138
138
|
|
|
139
|
-
return index.search(query).map(r => ({
|
|
139
|
+
return Response.json(index.search(query).map(r => ({
|
|
140
140
|
id: r.id,
|
|
141
141
|
url: r.url,
|
|
142
142
|
type: r.type,
|
|
143
143
|
content: r.title
|
|
144
|
-
}));
|
|
144
|
+
})));
|
|
145
145
|
});
|
package/src/server/api/specs.ts
CHANGED
|
@@ -15,7 +15,7 @@ export default defineHandler(async event => {
|
|
|
15
15
|
}
|
|
16
16
|
|
|
17
17
|
const apiConfigs = getApiConfigsForVersion(config, versionDir);
|
|
18
|
-
if (!apiConfigs.length) return [];
|
|
18
|
+
if (!apiConfigs.length) return Response.json([]);
|
|
19
19
|
|
|
20
|
-
return loadApiSpecs(apiConfigs);
|
|
20
|
+
return Response.json(await loadApiSpecs(apiConfigs));
|
|
21
21
|
});
|
|
@@ -34,6 +34,5 @@ export default defineHandler(async event => {
|
|
|
34
34
|
throw new HTTPError({ status: 404, message: 'Not Found' });
|
|
35
35
|
}
|
|
36
36
|
|
|
37
|
-
|
|
38
|
-
return matter(raw).content;
|
|
37
|
+
return new Response(matter(raw).content, { headers: { 'Content-Type': 'text/markdown; charset=utf-8' } });
|
|
39
38
|
});
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import fs from 'node:fs/promises';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { defineHandler, HTTPError } from 'nitro';
|
|
4
|
+
import { safePath } from '@/server/utils/safe-path';
|
|
5
|
+
|
|
6
|
+
const MIME: Record<string, string> = {
|
|
7
|
+
'.png': 'image/png',
|
|
8
|
+
'.jpg': 'image/jpeg',
|
|
9
|
+
'.jpeg': 'image/jpeg',
|
|
10
|
+
'.gif': 'image/gif',
|
|
11
|
+
'.svg': 'image/svg+xml',
|
|
12
|
+
'.webp': 'image/webp',
|
|
13
|
+
'.ico': 'image/x-icon',
|
|
14
|
+
'.pdf': 'application/pdf',
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
export default defineHandler(async event => {
|
|
18
|
+
const pathname = event.path?.replace(/^\/_content/, '') || '';
|
|
19
|
+
if (!pathname || pathname.endsWith('.md') || pathname.endsWith('.mdx')) {
|
|
20
|
+
throw new HTTPError({ status: 404, message: 'Not Found' });
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const contentDir = __CHRONICLE_CONTENT_DIR__;
|
|
24
|
+
let filePath: string | null = null;
|
|
25
|
+
try { filePath = safePath(contentDir, pathname); } catch { /* malformed URL encoding */ }
|
|
26
|
+
if (!filePath) throw new HTTPError({ status: 404, message: 'Not Found' });
|
|
27
|
+
|
|
28
|
+
const data = await fs.readFile(filePath).catch(() => null);
|
|
29
|
+
if (!data) throw new HTTPError({ status: 404, message: 'Not Found' });
|
|
30
|
+
|
|
31
|
+
const ext = path.extname(filePath).toLowerCase();
|
|
32
|
+
const contentType = MIME[ext] ?? 'application/octet-stream';
|
|
33
|
+
|
|
34
|
+
return new Response(data, {
|
|
35
|
+
headers: {
|
|
36
|
+
'Content-Type': contentType,
|
|
37
|
+
'Cache-Control': 'public, max-age=86400',
|
|
38
|
+
},
|
|
39
|
+
});
|
|
40
|
+
});
|
|
@@ -4,7 +4,7 @@ import { buildLlmsTxt } from '@/lib/llms';
|
|
|
4
4
|
import { extractFrontmatter, getPagesForVersion } from '@/lib/source';
|
|
5
5
|
import { LATEST_CONTEXT } from '@/lib/version-source';
|
|
6
6
|
|
|
7
|
-
export default defineHandler(async
|
|
7
|
+
export default defineHandler(async () => {
|
|
8
8
|
const config = loadConfig();
|
|
9
9
|
|
|
10
10
|
const pages = await getPagesForVersion(LATEST_CONTEXT);
|
|
@@ -14,6 +14,5 @@ export default defineHandler(async event => {
|
|
|
14
14
|
LATEST_CONTEXT,
|
|
15
15
|
);
|
|
16
16
|
|
|
17
|
-
|
|
18
|
-
return body;
|
|
17
|
+
return new Response(body, { headers: { 'Content-Type': 'text/plain' } });
|
|
19
18
|
});
|
package/src/server/routes/og.tsx
CHANGED
|
@@ -69,7 +69,5 @@ export default defineHandler(async event => {
|
|
|
69
69
|
},
|
|
70
70
|
);
|
|
71
71
|
|
|
72
|
-
|
|
73
|
-
event.res.headers.set('Cache-Control', 'public, max-age=86400');
|
|
74
|
-
return svg;
|
|
72
|
+
return new Response(svg, { headers: { 'Content-Type': 'image/svg+xml', 'Cache-Control': 'public, max-age=86400' } });
|
|
75
73
|
});
|
|
@@ -1,11 +1,10 @@
|
|
|
1
1
|
import { defineHandler } from 'nitro';
|
|
2
2
|
import { loadConfig } from '@/lib/config';
|
|
3
3
|
|
|
4
|
-
export default defineHandler(
|
|
4
|
+
export default defineHandler(() => {
|
|
5
5
|
const config = loadConfig();
|
|
6
6
|
const sitemap = config.url ? `\nSitemap: ${config.url}/sitemap.xml` : '';
|
|
7
7
|
const body = `User-agent: *\nAllow: /${sitemap}`;
|
|
8
8
|
|
|
9
|
-
|
|
10
|
-
return body;
|
|
9
|
+
return new Response(body, { headers: { 'Content-Type': 'text/plain' } });
|
|
11
10
|
});
|
|
@@ -4,12 +4,11 @@ import { getAllVersions, getApiConfigsForVersion, loadConfig } from '@/lib/confi
|
|
|
4
4
|
import { loadApiSpecs } from '@/lib/openapi';
|
|
5
5
|
import { getPages } from '@/lib/source';
|
|
6
6
|
|
|
7
|
-
export default defineHandler(async
|
|
7
|
+
export default defineHandler(async () => {
|
|
8
8
|
const config = loadConfig();
|
|
9
9
|
|
|
10
10
|
if (!config.url) {
|
|
11
|
-
|
|
12
|
-
return '<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9"/>';
|
|
11
|
+
return new Response('<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9"/>', { headers: { 'Content-Type': 'application/xml' } });
|
|
13
12
|
}
|
|
14
13
|
|
|
15
14
|
const baseUrl = config.url.replace(/\/$/, '');
|
|
@@ -43,6 +42,5 @@ export default defineHandler(async event => {
|
|
|
43
42
|
${[...docPages, ...apiPages].join('\n')}
|
|
44
43
|
</urlset>`;
|
|
45
44
|
|
|
46
|
-
|
|
47
|
-
return xml;
|
|
45
|
+
return new Response(xml, { headers: { 'Content-Type': 'application/xml' } });
|
|
48
46
|
});
|
|
@@ -7,7 +7,8 @@ import fs from 'node:fs/promises';
|
|
|
7
7
|
import path from 'node:path';
|
|
8
8
|
import remarkDirective from 'remark-directive';
|
|
9
9
|
import { type InlineConfig } from 'vite';
|
|
10
|
-
import
|
|
10
|
+
import remarkResolveImages from '../lib/remark-resolve-images';
|
|
11
|
+
import remarkResolveLinks from '../lib/remark-resolve-links';
|
|
11
12
|
import remarkReadingTime from 'remark-reading-time';
|
|
12
13
|
import remarkUnusedDirectives from '../lib/remark-unused-directives';
|
|
13
14
|
|
|
@@ -56,6 +57,7 @@ export async function createViteConfig(
|
|
|
56
57
|
mdx({
|
|
57
58
|
default: defineFumadocsConfig({
|
|
58
59
|
mdxOptions: {
|
|
60
|
+
remarkImageOptions: false,
|
|
59
61
|
valueToExport: ['readingTime'],
|
|
60
62
|
remarkPlugins: [
|
|
61
63
|
remarkDirective,
|
|
@@ -77,7 +79,8 @@ export async function createViteConfig(
|
|
|
77
79
|
},
|
|
78
80
|
}],
|
|
79
81
|
remarkUnusedDirectives,
|
|
80
|
-
|
|
82
|
+
remarkResolveLinks,
|
|
83
|
+
remarkResolveImages,
|
|
81
84
|
remarkMdxMermaid,
|
|
82
85
|
remarkReadingTime,
|
|
83
86
|
],
|
|
@@ -6,6 +6,7 @@ import { useLocation, useNavigate } from 'react-router';
|
|
|
6
6
|
import { getLandingEntries } from '@/lib/config';
|
|
7
7
|
import { getActiveContentDir } from '@/lib/navigation';
|
|
8
8
|
import { usePageContext } from '@/lib/page-context';
|
|
9
|
+
import { Search } from '@/components/ui/search';
|
|
9
10
|
import type { ThemeLayoutProps } from '@/types';
|
|
10
11
|
import { ChapterNav } from './ChapterNav';
|
|
11
12
|
import styles from './Layout.module.css';
|
|
@@ -82,6 +83,7 @@ function LayoutInner({
|
|
|
82
83
|
</aside>
|
|
83
84
|
) : null}
|
|
84
85
|
<div className={cx(styles.content, classNames?.content)}>
|
|
86
|
+
{config.search?.enabled && <Search classNames={{ trigger: styles.hiddenTrigger }} />}
|
|
85
87
|
{children}
|
|
86
88
|
</div>
|
|
87
89
|
</Flex>
|
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
import { visit } from 'unist-util-visit'
|
|
2
|
-
import type { Plugin } from 'unified'
|
|
3
|
-
|
|
4
|
-
const remarkStripMdExtensions: Plugin = () => {
|
|
5
|
-
return (tree) => {
|
|
6
|
-
visit(tree, 'link', (node: any) => {
|
|
7
|
-
if (!node.url) return
|
|
8
|
-
if (node.url.startsWith('http://') || node.url.startsWith('https://')) return
|
|
9
|
-
node.url = node.url.replace(/\.mdx?(#|$)/, '$1')
|
|
10
|
-
})
|
|
11
|
-
}
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
export default remarkStripMdExtensions
|