@raystack/chronicle 0.6.1 → 0.7.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli/index.js +277 -27
- package/package.json +5 -1
- package/src/components/ui/search.tsx +3 -3
- package/src/lib/config.ts +5 -0
- package/src/lib/remark-resolve-images.ts +59 -0
- package/src/lib/remark-resolve-links.ts +32 -0
- package/src/lib/source.ts +20 -4
- package/src/pages/ApiLayout.tsx +0 -2
- package/src/pages/LandingPage.module.css +137 -24
- package/src/pages/LandingPage.tsx +23 -7
- 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/entry-server.tsx +4 -1
- 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 +8 -2
- package/src/themes/paper/ChapterNav.module.css +23 -12
- package/src/themes/paper/ChapterNav.tsx +1 -17
- package/src/themes/paper/Layout.module.css +61 -16
- package/src/themes/paper/Layout.tsx +73 -17
- package/src/themes/paper/Page.module.css +89 -37
- package/src/themes/paper/Page.tsx +89 -53
- package/src/themes/paper/ReaderModeContext.tsx +28 -0
- package/src/themes/paper/ReadingProgress.tsx +1 -0
- package/src/themes/paper/fonts/DepartureMono-Regular.woff2 +0 -0
- package/src/themes/registry.ts +1 -1
- package/src/types/config.ts +1 -0
- package/src/types/content.ts +1 -0
- package/src/lib/remark-strip-md-extensions.ts +0 -14
package/dist/cli/index.js
CHANGED
|
@@ -1,5 +1,35 @@
|
|
|
1
1
|
import { createRequire } from "node:module";
|
|
2
|
+
var __create = Object.create;
|
|
3
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
2
4
|
var __defProp = Object.defineProperty;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
7
|
+
function __accessProp(key) {
|
|
8
|
+
return this[key];
|
|
9
|
+
}
|
|
10
|
+
var __toESMCache_node;
|
|
11
|
+
var __toESMCache_esm;
|
|
12
|
+
var __toESM = (mod, isNodeMode, target) => {
|
|
13
|
+
var canCache = mod != null && typeof mod === "object";
|
|
14
|
+
if (canCache) {
|
|
15
|
+
var cache = isNodeMode ? __toESMCache_node ??= new WeakMap : __toESMCache_esm ??= new WeakMap;
|
|
16
|
+
var cached = cache.get(mod);
|
|
17
|
+
if (cached)
|
|
18
|
+
return cached;
|
|
19
|
+
}
|
|
20
|
+
target = mod != null ? __create(__getProtoOf(mod)) : {};
|
|
21
|
+
const to = isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target;
|
|
22
|
+
for (let key of __getOwnPropNames(mod))
|
|
23
|
+
if (!__hasOwnProp.call(to, key))
|
|
24
|
+
__defProp(to, key, {
|
|
25
|
+
get: __accessProp.bind(mod, key),
|
|
26
|
+
enumerable: true
|
|
27
|
+
});
|
|
28
|
+
if (canCache)
|
|
29
|
+
cache.set(mod, to);
|
|
30
|
+
return to;
|
|
31
|
+
};
|
|
32
|
+
var __commonJS = (cb, mod) => () => (mod || cb((mod = { exports: {} }).exports, mod), mod.exports);
|
|
3
33
|
var __returnValue = (v) => v;
|
|
4
34
|
function __exportSetter(name, newValue) {
|
|
5
35
|
this[name] = __returnValue.bind(null, newValue);
|
|
@@ -16,28 +46,239 @@ var __export = (target, all) => {
|
|
|
16
46
|
var __esm = (fn, res) => () => (fn && (res = fn(fn = 0)), res);
|
|
17
47
|
var __require = /* @__PURE__ */ createRequire(import.meta.url);
|
|
18
48
|
|
|
19
|
-
// src/lib/remark-
|
|
49
|
+
// src/lib/remark-resolve-images.ts
|
|
50
|
+
import path4 from "node:path";
|
|
20
51
|
import { visit } from "unist-util-visit";
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
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) => {
|
|
24
76
|
if (!node.url)
|
|
25
77
|
return;
|
|
26
|
-
|
|
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")
|
|
27
99
|
return;
|
|
28
|
-
node.
|
|
100
|
+
node.properties.src = resolveUrl(src, dir);
|
|
29
101
|
});
|
|
30
102
|
};
|
|
31
|
-
},
|
|
32
|
-
var
|
|
33
|
-
|
|
103
|
+
}, remark_resolve_images_default;
|
|
104
|
+
var init_remark_resolve_images = __esm(() => {
|
|
105
|
+
remark_resolve_images_default = remarkResolveImages;
|
|
34
106
|
});
|
|
35
107
|
|
|
36
|
-
// src/lib/remark-
|
|
108
|
+
// src/lib/remark-resolve-links.ts
|
|
109
|
+
import path5 from "node:path";
|
|
37
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) => {
|
|
122
|
+
if (!node.url)
|
|
123
|
+
return;
|
|
124
|
+
if (/^[a-z][a-z0-9+\-.]*:/i.test(node.url))
|
|
125
|
+
return;
|
|
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}` : ""}`;
|
|
135
|
+
});
|
|
136
|
+
};
|
|
137
|
+
}, remark_resolve_links_default;
|
|
138
|
+
var init_remark_resolve_links = __esm(() => {
|
|
139
|
+
remark_resolve_links_default = remarkResolveLinks;
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
// ../../node_modules/.bun/reading-time@1.5.0/node_modules/reading-time/lib/reading-time.js
|
|
143
|
+
var require_reading_time = __commonJS((exports, module) => {
|
|
144
|
+
/*!
|
|
145
|
+
* reading-time
|
|
146
|
+
* Copyright (c) Nicolas Gryman <ngryman@gmail.com>
|
|
147
|
+
* MIT Licensed
|
|
148
|
+
*/
|
|
149
|
+
function codeIsInRanges(number, arrayOfRanges) {
|
|
150
|
+
return arrayOfRanges.some(([lowerBound, upperBound]) => lowerBound <= number && number <= upperBound);
|
|
151
|
+
}
|
|
152
|
+
function isCJK(c) {
|
|
153
|
+
if (typeof c !== "string") {
|
|
154
|
+
return false;
|
|
155
|
+
}
|
|
156
|
+
const charCode = c.charCodeAt(0);
|
|
157
|
+
return codeIsInRanges(charCode, [
|
|
158
|
+
[12352, 12447],
|
|
159
|
+
[19968, 40959],
|
|
160
|
+
[44032, 55203],
|
|
161
|
+
[131072, 191456]
|
|
162
|
+
]);
|
|
163
|
+
}
|
|
164
|
+
function isAnsiWordBound(c) {
|
|
165
|
+
return `
|
|
166
|
+
\r `.includes(c);
|
|
167
|
+
}
|
|
168
|
+
function isPunctuation(c) {
|
|
169
|
+
if (typeof c !== "string") {
|
|
170
|
+
return false;
|
|
171
|
+
}
|
|
172
|
+
const charCode = c.charCodeAt(0);
|
|
173
|
+
return codeIsInRanges(charCode, [
|
|
174
|
+
[33, 47],
|
|
175
|
+
[58, 64],
|
|
176
|
+
[91, 96],
|
|
177
|
+
[123, 126],
|
|
178
|
+
[12288, 12351],
|
|
179
|
+
[65280, 65519]
|
|
180
|
+
]);
|
|
181
|
+
}
|
|
182
|
+
function readingTime(text, options = {}) {
|
|
183
|
+
let words = 0, start = 0, end = text.length - 1;
|
|
184
|
+
const wordsPerMinute = options.wordsPerMinute || 200;
|
|
185
|
+
const isWordBound = options.wordBound || isAnsiWordBound;
|
|
186
|
+
while (isWordBound(text[start]))
|
|
187
|
+
start++;
|
|
188
|
+
while (isWordBound(text[end]))
|
|
189
|
+
end--;
|
|
190
|
+
const normalizedText = `${text}
|
|
191
|
+
`;
|
|
192
|
+
for (let i = start;i <= end; i++) {
|
|
193
|
+
if (isCJK(normalizedText[i]) || !isWordBound(normalizedText[i]) && (isWordBound(normalizedText[i + 1]) || isCJK(normalizedText[i + 1]))) {
|
|
194
|
+
words++;
|
|
195
|
+
}
|
|
196
|
+
if (isCJK(normalizedText[i])) {
|
|
197
|
+
while (i <= end && (isPunctuation(normalizedText[i + 1]) || isWordBound(normalizedText[i + 1]))) {
|
|
198
|
+
i++;
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
const minutes = words / wordsPerMinute;
|
|
203
|
+
const time = Math.round(minutes * 60 * 1000);
|
|
204
|
+
const displayed = Math.ceil(minutes.toFixed(2));
|
|
205
|
+
return {
|
|
206
|
+
text: displayed + " min read",
|
|
207
|
+
minutes,
|
|
208
|
+
time,
|
|
209
|
+
words
|
|
210
|
+
};
|
|
211
|
+
}
|
|
212
|
+
module.exports = readingTime;
|
|
213
|
+
});
|
|
214
|
+
|
|
215
|
+
// ../../node_modules/.bun/reading-time@1.5.0/node_modules/reading-time/lib/stream.js
|
|
216
|
+
var require_stream = __commonJS((exports, module) => {
|
|
217
|
+
/*!
|
|
218
|
+
* reading-time
|
|
219
|
+
* Copyright (c) Nicolas Gryman <ngryman@gmail.com>
|
|
220
|
+
* MIT Licensed
|
|
221
|
+
*/
|
|
222
|
+
var readingTime = require_reading_time();
|
|
223
|
+
var Transform = __require("stream").Transform;
|
|
224
|
+
var util = __require("util");
|
|
225
|
+
function ReadingTimeStream(options) {
|
|
226
|
+
if (!(this instanceof ReadingTimeStream)) {
|
|
227
|
+
return new ReadingTimeStream(options);
|
|
228
|
+
}
|
|
229
|
+
Transform.call(this, { objectMode: true });
|
|
230
|
+
this.options = options || {};
|
|
231
|
+
this.stats = {
|
|
232
|
+
minutes: 0,
|
|
233
|
+
time: 0,
|
|
234
|
+
words: 0
|
|
235
|
+
};
|
|
236
|
+
}
|
|
237
|
+
util.inherits(ReadingTimeStream, Transform);
|
|
238
|
+
ReadingTimeStream.prototype._transform = function(chunk, encoding, callback) {
|
|
239
|
+
const stats = readingTime(chunk.toString(encoding), this.options);
|
|
240
|
+
this.stats.minutes += stats.minutes;
|
|
241
|
+
this.stats.time += stats.time;
|
|
242
|
+
this.stats.words += stats.words;
|
|
243
|
+
callback();
|
|
244
|
+
};
|
|
245
|
+
ReadingTimeStream.prototype._flush = function(callback) {
|
|
246
|
+
this.stats.text = Math.ceil(this.stats.minutes.toFixed(2)) + " min read";
|
|
247
|
+
this.push(this.stats);
|
|
248
|
+
callback();
|
|
249
|
+
};
|
|
250
|
+
module.exports = ReadingTimeStream;
|
|
251
|
+
});
|
|
252
|
+
|
|
253
|
+
// ../../node_modules/.bun/reading-time@1.5.0/node_modules/reading-time/index.js
|
|
254
|
+
var require_reading_time2 = __commonJS((exports, module) => {
|
|
255
|
+
exports.default = module.exports = require_reading_time();
|
|
256
|
+
module.exports.readingTimeStream = require_stream();
|
|
257
|
+
});
|
|
258
|
+
|
|
259
|
+
// ../../node_modules/.bun/remark-reading-time@2.1.0/node_modules/remark-reading-time/index.js
|
|
260
|
+
import { visit as visit3 } from "unist-util-visit";
|
|
261
|
+
function readingTime({
|
|
262
|
+
attribute = "readingTime"
|
|
263
|
+
} = {}) {
|
|
264
|
+
return function(info, file) {
|
|
265
|
+
let text = "";
|
|
266
|
+
visit3(info, ["text", "code"], (node) => {
|
|
267
|
+
text += node.value;
|
|
268
|
+
});
|
|
269
|
+
file.data[attribute] = import_reading_time.default(text);
|
|
270
|
+
};
|
|
271
|
+
}
|
|
272
|
+
var import_reading_time;
|
|
273
|
+
var init_remark_reading_time = __esm(() => {
|
|
274
|
+
import_reading_time = __toESM(require_reading_time2(), 1);
|
|
275
|
+
});
|
|
276
|
+
|
|
277
|
+
// src/lib/remark-unused-directives.ts
|
|
278
|
+
import { visit as visit4 } from "unist-util-visit";
|
|
38
279
|
var remarkUnusedDirectives = () => {
|
|
39
280
|
return (tree) => {
|
|
40
|
-
|
|
281
|
+
visit4(tree, ["textDirective"], (node) => {
|
|
41
282
|
const directive = node;
|
|
42
283
|
if (!directive.data) {
|
|
43
284
|
const hasAttributes = directive.attributes && Object.keys(directive.attributes).length > 0;
|
|
@@ -69,12 +310,12 @@ import { defineConfig as defineFumadocsConfig } from "fumadocs-mdx/config";
|
|
|
69
310
|
import mdx from "fumadocs-mdx/vite";
|
|
70
311
|
import { nitro } from "nitro/vite";
|
|
71
312
|
import fs3 from "node:fs/promises";
|
|
72
|
-
import
|
|
313
|
+
import path6 from "node:path";
|
|
73
314
|
import remarkDirective from "remark-directive";
|
|
74
315
|
function resolveOutputDir(projectRoot, preset) {
|
|
75
316
|
if (preset === "vercel" || preset === "vercel-static")
|
|
76
|
-
return
|
|
77
|
-
return
|
|
317
|
+
return path6.resolve(projectRoot, ".vercel/output");
|
|
318
|
+
return path6.resolve(projectRoot, ".output");
|
|
78
319
|
}
|
|
79
320
|
async function readChronicleConfig(projectRoot, configPath) {
|
|
80
321
|
if (configPath) {
|
|
@@ -85,7 +326,7 @@ async function readChronicleConfig(projectRoot, configPath) {
|
|
|
85
326
|
}
|
|
86
327
|
}
|
|
87
328
|
try {
|
|
88
|
-
return await fs3.readFile(
|
|
329
|
+
return await fs3.readFile(path6.join(projectRoot, "chronicle.yaml"), "utf-8");
|
|
89
330
|
} catch {
|
|
90
331
|
return null;
|
|
91
332
|
}
|
|
@@ -93,18 +334,20 @@ async function readChronicleConfig(projectRoot, configPath) {
|
|
|
93
334
|
async function createViteConfig(options) {
|
|
94
335
|
const { packageRoot, projectRoot, configPath, preset } = options;
|
|
95
336
|
const rawConfig = await readChronicleConfig(projectRoot, configPath);
|
|
96
|
-
const contentMirror =
|
|
337
|
+
const contentMirror = path6.resolve(packageRoot, ".content");
|
|
97
338
|
return {
|
|
98
339
|
root: packageRoot,
|
|
99
340
|
configFile: false,
|
|
100
341
|
plugins: [
|
|
101
342
|
nitro({
|
|
102
|
-
serverDir:
|
|
343
|
+
serverDir: path6.resolve(packageRoot, "src/server"),
|
|
103
344
|
...preset && { preset }
|
|
104
345
|
}),
|
|
105
346
|
mdx({
|
|
106
347
|
default: defineFumadocsConfig({
|
|
107
348
|
mdxOptions: {
|
|
349
|
+
remarkImageOptions: false,
|
|
350
|
+
valueToExport: ["readingTime"],
|
|
108
351
|
remarkPlugins: [
|
|
109
352
|
remarkDirective,
|
|
110
353
|
[remarkDirectiveAdmonition, {
|
|
@@ -125,8 +368,10 @@ async function createViteConfig(options) {
|
|
|
125
368
|
}
|
|
126
369
|
}],
|
|
127
370
|
remark_unused_directives_default,
|
|
128
|
-
|
|
129
|
-
|
|
371
|
+
remark_resolve_links_default,
|
|
372
|
+
remark_resolve_images_default,
|
|
373
|
+
remarkMdxMermaid,
|
|
374
|
+
readingTime
|
|
130
375
|
]
|
|
131
376
|
}
|
|
132
377
|
})
|
|
@@ -135,7 +380,7 @@ async function createViteConfig(options) {
|
|
|
135
380
|
],
|
|
136
381
|
resolve: {
|
|
137
382
|
alias: {
|
|
138
|
-
"@":
|
|
383
|
+
"@": path6.resolve(packageRoot, "src"),
|
|
139
384
|
tslib: "tslib/tslib.es6.js"
|
|
140
385
|
},
|
|
141
386
|
dedupe: [
|
|
@@ -168,7 +413,7 @@ async function createViteConfig(options) {
|
|
|
168
413
|
client: {
|
|
169
414
|
build: {
|
|
170
415
|
rollupOptions: {
|
|
171
|
-
input:
|
|
416
|
+
input: path6.resolve(packageRoot, "src/server/entry-client.tsx")
|
|
172
417
|
}
|
|
173
418
|
}
|
|
174
419
|
}
|
|
@@ -182,7 +427,9 @@ async function createViteConfig(options) {
|
|
|
182
427
|
};
|
|
183
428
|
}
|
|
184
429
|
var init_vite_config = __esm(() => {
|
|
185
|
-
|
|
430
|
+
init_remark_resolve_images();
|
|
431
|
+
init_remark_resolve_links();
|
|
432
|
+
init_remark_reading_time();
|
|
186
433
|
init_remark_unused_directives();
|
|
187
434
|
});
|
|
188
435
|
|
|
@@ -266,6 +513,7 @@ var dirNameSchema = z.string().min(1).refine((s) => DIR_NAME_PATTERN.test(s) &&
|
|
|
266
513
|
var contentEntrySchema = z.object({
|
|
267
514
|
dir: dirNameSchema,
|
|
268
515
|
label: z.string().min(1),
|
|
516
|
+
description: z.string().optional(),
|
|
269
517
|
icon: z.string().optional()
|
|
270
518
|
});
|
|
271
519
|
var badgeVariantSchema = z.enum([
|
|
@@ -428,6 +676,7 @@ function getLatestContentRoots(config2) {
|
|
|
428
676
|
versionLabel: config2.latest?.label ?? null,
|
|
429
677
|
contentDir: c.dir,
|
|
430
678
|
contentLabel: c.label,
|
|
679
|
+
contentDescription: c.description,
|
|
431
680
|
contentIcon: c.icon,
|
|
432
681
|
fsPath: `content/${c.dir}`,
|
|
433
682
|
urlPrefix: `/${c.dir}`
|
|
@@ -442,6 +691,7 @@ function getVersionContentRoots(config2, versionDir) {
|
|
|
442
691
|
versionLabel: version.label,
|
|
443
692
|
contentDir: c.dir,
|
|
444
693
|
contentLabel: c.label,
|
|
694
|
+
contentDescription: c.description,
|
|
445
695
|
contentIcon: c.icon,
|
|
446
696
|
fsPath: `versions/${version.dir}/${c.dir}`,
|
|
447
697
|
urlPrefix: `/${version.dir}/${c.dir}`
|
|
@@ -549,7 +799,7 @@ var devCommand = new Command2("dev").description("Start development server").opt
|
|
|
549
799
|
|
|
550
800
|
// src/cli/commands/init.ts
|
|
551
801
|
import fs4 from "node:fs";
|
|
552
|
-
import
|
|
802
|
+
import path7 from "node:path";
|
|
553
803
|
import chalk4 from "chalk";
|
|
554
804
|
import { Command as Command3 } from "commander";
|
|
555
805
|
import { stringify } from "yaml";
|
|
@@ -576,12 +826,12 @@ var GITIGNORE_ENTRIES = ["node_modules", "dist", ".output"];
|
|
|
576
826
|
function runInit(projectDir) {
|
|
577
827
|
const events = [];
|
|
578
828
|
const defaultDir = defaultInitConfig.content[0].dir;
|
|
579
|
-
const contentDir =
|
|
829
|
+
const contentDir = path7.join(projectDir, "content", defaultDir);
|
|
580
830
|
if (!fs4.existsSync(contentDir)) {
|
|
581
831
|
fs4.mkdirSync(contentDir, { recursive: true });
|
|
582
832
|
events.push({ type: "created", path: contentDir });
|
|
583
833
|
}
|
|
584
|
-
const configPath =
|
|
834
|
+
const configPath = path7.join(projectDir, "chronicle.yaml");
|
|
585
835
|
if (!fs4.existsSync(configPath)) {
|
|
586
836
|
fs4.writeFileSync(configPath, stringify(defaultInitConfig));
|
|
587
837
|
events.push({ type: "created", path: configPath });
|
|
@@ -590,11 +840,11 @@ function runInit(projectDir) {
|
|
|
590
840
|
}
|
|
591
841
|
const contentFiles = fs4.readdirSync(contentDir);
|
|
592
842
|
if (contentFiles.length === 0) {
|
|
593
|
-
const indexPath =
|
|
843
|
+
const indexPath = path7.join(contentDir, "index.mdx");
|
|
594
844
|
fs4.writeFileSync(indexPath, sampleMdx);
|
|
595
845
|
events.push({ type: "created", path: indexPath });
|
|
596
846
|
}
|
|
597
|
-
const gitignorePath =
|
|
847
|
+
const gitignorePath = path7.join(projectDir, ".gitignore");
|
|
598
848
|
if (fs4.existsSync(gitignorePath)) {
|
|
599
849
|
const existing = fs4.readFileSync(gitignorePath, "utf-8");
|
|
600
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.
|
|
3
|
+
"version": "0.7.1",
|
|
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>
|
package/src/lib/config.ts
CHANGED
|
@@ -35,6 +35,7 @@ export interface ContentRoot {
|
|
|
35
35
|
versionLabel: string | null
|
|
36
36
|
contentDir: string
|
|
37
37
|
contentLabel: string
|
|
38
|
+
contentDescription?: string
|
|
38
39
|
contentIcon?: string
|
|
39
40
|
fsPath: string
|
|
40
41
|
urlPrefix: string
|
|
@@ -46,6 +47,7 @@ export function getLatestContentRoots(config: ChronicleConfig): ContentRoot[] {
|
|
|
46
47
|
versionLabel: config.latest?.label ?? null,
|
|
47
48
|
contentDir: c.dir,
|
|
48
49
|
contentLabel: c.label,
|
|
50
|
+
contentDescription: c.description,
|
|
49
51
|
contentIcon: c.icon,
|
|
50
52
|
fsPath: `content/${c.dir}`,
|
|
51
53
|
urlPrefix: `/${c.dir}`,
|
|
@@ -64,6 +66,7 @@ export function getVersionContentRoots(
|
|
|
64
66
|
versionLabel: version.label,
|
|
65
67
|
contentDir: c.dir,
|
|
66
68
|
contentLabel: c.label,
|
|
69
|
+
contentDescription: c.description,
|
|
67
70
|
contentIcon: c.icon,
|
|
68
71
|
fsPath: `versions/${version.dir}/${c.dir}`,
|
|
69
72
|
urlPrefix: `/${version.dir}/${c.dir}`,
|
|
@@ -79,6 +82,7 @@ export interface VersionDescriptor {
|
|
|
79
82
|
|
|
80
83
|
export interface LandingEntry {
|
|
81
84
|
label: string
|
|
85
|
+
description?: string
|
|
82
86
|
href: string
|
|
83
87
|
contentDir: string
|
|
84
88
|
icon?: string
|
|
@@ -95,6 +99,7 @@ export function getLandingEntries(
|
|
|
95
99
|
|
|
96
100
|
return roots.map((r) => ({
|
|
97
101
|
label: r.contentLabel,
|
|
102
|
+
description: r.contentDescription,
|
|
98
103
|
href: r.urlPrefix,
|
|
99
104
|
contentDir: r.contentDir,
|
|
100
105
|
icon: r.contentIcon,
|
|
@@ -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
|
|
|
@@ -206,6 +213,7 @@ export function extractFrontmatter(page: { data: unknown }, fallbackTitle?: stri
|
|
|
206
213
|
order: d.order as number | undefined,
|
|
207
214
|
icon: d.icon as string | undefined,
|
|
208
215
|
lastModified: d.lastModified as string | undefined,
|
|
216
|
+
_readingTime: d._readingTime as number | undefined,
|
|
209
217
|
};
|
|
210
218
|
}
|
|
211
219
|
|
|
@@ -217,13 +225,20 @@ export function getOriginalPath(page: { data: unknown }): string {
|
|
|
217
225
|
return ((page.data as Record<string, unknown>)._originalPath as string) ?? '';
|
|
218
226
|
}
|
|
219
227
|
|
|
220
|
-
|
|
228
|
+
interface ReadingTime {
|
|
229
|
+
text: string;
|
|
230
|
+
minutes: number;
|
|
231
|
+
words: number;
|
|
232
|
+
time: number;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
const ssrModules = import.meta.glob<{ default?: MDXContent; toc?: TableOfContents; readingTime?: ReadingTime }>(
|
|
221
236
|
'../../.content/**/*.{mdx,md}'
|
|
222
237
|
);
|
|
223
238
|
|
|
224
239
|
export async function loadPageModule(
|
|
225
240
|
relativePath: string
|
|
226
|
-
): Promise<{ default: MDXContent | null; toc: TableOfContents }> {
|
|
241
|
+
): Promise<{ default: MDXContent | null; toc: TableOfContents; _readingTime?: number }> {
|
|
227
242
|
if (!relativePath || relativePath.includes('..')) return { default: null, toc: [] };
|
|
228
243
|
const withoutExt = relativePath.replace(/\.(mdx|md)$/, '');
|
|
229
244
|
const key = relativePath.endsWith('.md')
|
|
@@ -232,5 +247,6 @@ export async function loadPageModule(
|
|
|
232
247
|
const loader = ssrModules[key];
|
|
233
248
|
if (!loader) return { default: null, toc: [] };
|
|
234
249
|
const mod = await loader();
|
|
235
|
-
|
|
250
|
+
const minutes = mod.readingTime?.minutes;
|
|
251
|
+
return { default: mod.default ?? null, toc: mod.toc ?? [], _readingTime: minutes != null ? Math.max(1, Math.round(minutes)) : undefined };
|
|
236
252
|
}
|