@raystack/chronicle 0.5.4 → 0.6.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.
- package/dist/cli/index.js +258 -80
- package/package.json +8 -6
- package/src/cli/commands/build.ts +5 -8
- package/src/cli/commands/dev.ts +5 -6
- package/src/cli/commands/init.test.ts +77 -0
- package/src/cli/commands/init.ts +73 -40
- package/src/cli/commands/serve.ts +6 -9
- package/src/cli/commands/start.ts +5 -5
- package/src/cli/utils/config.ts +6 -12
- package/src/cli/utils/scaffold.test.ts +179 -0
- package/src/cli/utils/scaffold.ts +70 -9
- package/src/components/api/field-row.tsx +1 -1
- package/src/components/api/field-section.tsx +2 -2
- package/src/components/mdx/index.tsx +1 -1
- package/src/components/mdx/mermaid.tsx +24 -21
- package/src/components/ui/breadcrumbs.tsx +4 -2
- package/src/components/ui/client-theme-switcher.tsx +21 -4
- package/src/components/ui/search.module.css +16 -41
- package/src/components/ui/search.tsx +30 -41
- package/src/lib/config.test.ts +493 -0
- package/src/lib/config.ts +123 -22
- package/src/lib/head.tsx +23 -5
- package/src/lib/llms.test.ts +94 -0
- package/src/lib/llms.ts +41 -0
- package/src/lib/navigation.test.ts +94 -0
- package/src/lib/navigation.ts +51 -0
- package/src/lib/page-context.tsx +51 -32
- package/src/lib/route-resolver.test.ts +173 -0
- package/src/lib/route-resolver.ts +73 -0
- package/src/lib/source.ts +94 -1
- package/src/lib/version-source.test.ts +163 -0
- package/src/lib/version-source.ts +101 -0
- package/src/pages/ApiPage.tsx +1 -1
- package/src/pages/DocsLayout.tsx +24 -3
- package/src/pages/DocsPage.tsx +3 -6
- package/src/pages/LandingPage.module.css +56 -0
- package/src/pages/LandingPage.tsx +39 -0
- package/src/pages/NotFound.tsx +2 -0
- package/src/server/App.tsx +21 -23
- package/src/server/api/page.ts +5 -1
- package/src/server/api/search.ts +51 -24
- package/src/server/api/specs.ts +17 -5
- package/src/server/entry-client.tsx +42 -14
- package/src/server/entry-server.tsx +33 -11
- package/src/server/routes/[...slug].md.ts +0 -6
- package/src/server/routes/[version]/llms.txt.ts +26 -0
- package/src/server/routes/llms.txt.ts +10 -13
- package/src/server/routes/og.tsx +2 -2
- package/src/server/routes/sitemap.xml.ts +14 -6
- package/src/server/vite-config.ts +5 -5
- package/src/themes/default/ContentDirButtons.tsx +66 -0
- package/src/themes/default/Layout.module.css +187 -40
- package/src/themes/default/Layout.tsx +166 -65
- package/src/themes/default/OpenInAI.tsx +112 -0
- package/src/themes/default/Page.module.css +30 -0
- package/src/themes/default/Page.tsx +1 -3
- package/src/themes/default/SidebarLogo.tsx +26 -0
- package/src/themes/default/Toc.module.css +102 -25
- package/src/themes/default/Toc.tsx +56 -10
- package/src/themes/default/VersionSwitcher.tsx +59 -0
- package/src/themes/paper/ContentDirDropdown.tsx +47 -0
- package/src/themes/paper/Layout.module.css +7 -0
- package/src/themes/paper/Layout.tsx +20 -13
- package/src/themes/paper/VersionSwitcher.tsx +60 -0
- package/src/types/config.ts +145 -23
- package/src/types/content.ts +11 -1
- package/src/types/theme.ts +1 -0
- package/src/components/ui/footer.module.css +0 -27
- package/src/components/ui/footer.tsx +0 -30
package/dist/cli/index.js
CHANGED
|
@@ -91,8 +91,9 @@ async function readChronicleConfig(projectRoot, configPath) {
|
|
|
91
91
|
}
|
|
92
92
|
}
|
|
93
93
|
async function createViteConfig(options) {
|
|
94
|
-
const { packageRoot, projectRoot,
|
|
94
|
+
const { packageRoot, projectRoot, configPath, preset } = options;
|
|
95
95
|
const rawConfig = await readChronicleConfig(projectRoot, configPath);
|
|
96
|
+
const contentMirror = path4.resolve(packageRoot, ".content");
|
|
96
97
|
return {
|
|
97
98
|
root: packageRoot,
|
|
98
99
|
configFile: false,
|
|
@@ -134,9 +135,9 @@ async function createViteConfig(options) {
|
|
|
134
135
|
],
|
|
135
136
|
resolve: {
|
|
136
137
|
alias: {
|
|
137
|
-
"@": path4.resolve(packageRoot, "src")
|
|
138
|
+
"@": path4.resolve(packageRoot, "src"),
|
|
139
|
+
tslib: "tslib/tslib.es6.js"
|
|
138
140
|
},
|
|
139
|
-
conditions: ["module-sync", "import", "node"],
|
|
140
141
|
dedupe: [
|
|
141
142
|
"react",
|
|
142
143
|
"react-dom",
|
|
@@ -147,11 +148,11 @@ async function createViteConfig(options) {
|
|
|
147
148
|
},
|
|
148
149
|
server: {
|
|
149
150
|
fs: {
|
|
150
|
-
allow: [packageRoot, projectRoot,
|
|
151
|
+
allow: [packageRoot, projectRoot, contentMirror]
|
|
151
152
|
}
|
|
152
153
|
},
|
|
153
154
|
define: {
|
|
154
|
-
__CHRONICLE_CONTENT_DIR__: JSON.stringify(
|
|
155
|
+
__CHRONICLE_CONTENT_DIR__: JSON.stringify(contentMirror),
|
|
155
156
|
__CHRONICLE_PROJECT_ROOT__: JSON.stringify(projectRoot),
|
|
156
157
|
__CHRONICLE_CONFIG_RAW__: JSON.stringify(rawConfig)
|
|
157
158
|
},
|
|
@@ -199,6 +200,7 @@ import chalk from "chalk";
|
|
|
199
200
|
import { parse } from "yaml";
|
|
200
201
|
|
|
201
202
|
// src/types/config.ts
|
|
203
|
+
import uniqBy from "lodash/uniqBy.js";
|
|
202
204
|
import { z } from "zod";
|
|
203
205
|
var logoSchema = z.object({
|
|
204
206
|
light: z.string().optional(),
|
|
@@ -237,16 +239,10 @@ var apiSchema = z.object({
|
|
|
237
239
|
name: z.string(),
|
|
238
240
|
spec: z.string(),
|
|
239
241
|
basePath: z.string(),
|
|
242
|
+
icon: z.string().optional(),
|
|
240
243
|
server: apiServerSchema,
|
|
241
244
|
auth: apiAuthSchema.optional()
|
|
242
245
|
});
|
|
243
|
-
var footerSchema = z.object({
|
|
244
|
-
copyright: z.string().optional(),
|
|
245
|
-
links: z.array(navLinkSchema).optional()
|
|
246
|
-
});
|
|
247
|
-
var llmsSchema = z.object({
|
|
248
|
-
enabled: z.boolean().optional()
|
|
249
|
-
});
|
|
250
246
|
var googleAnalyticsSchema = z.object({
|
|
251
247
|
measurementId: z.string()
|
|
252
248
|
});
|
|
@@ -259,21 +255,112 @@ var telemetrySchema = z.object({
|
|
|
259
255
|
serviceName: z.string().optional(),
|
|
260
256
|
port: z.number().int().min(1).max(65535).default(9090)
|
|
261
257
|
});
|
|
262
|
-
var
|
|
258
|
+
var siteSchema = z.object({
|
|
263
259
|
title: z.string(),
|
|
264
|
-
description: z.string().optional()
|
|
260
|
+
description: z.string().optional()
|
|
261
|
+
});
|
|
262
|
+
var DIR_NAME_PATTERN = /^[a-zA-Z0-9][a-zA-Z0-9._-]*$/;
|
|
263
|
+
var dirNameSchema = z.string().min(1).refine((s) => DIR_NAME_PATTERN.test(s) && s !== "." && s !== "..", {
|
|
264
|
+
message: 'dir must start with a letter or digit and contain only letters, digits, ".", "_", or "-"'
|
|
265
|
+
});
|
|
266
|
+
var contentEntrySchema = z.object({
|
|
267
|
+
dir: dirNameSchema,
|
|
268
|
+
label: z.string().min(1),
|
|
269
|
+
icon: z.string().optional()
|
|
270
|
+
});
|
|
271
|
+
var badgeVariantSchema = z.enum([
|
|
272
|
+
"accent",
|
|
273
|
+
"warning",
|
|
274
|
+
"danger",
|
|
275
|
+
"success",
|
|
276
|
+
"neutral",
|
|
277
|
+
"gradient"
|
|
278
|
+
]);
|
|
279
|
+
var badgeSchema = z.object({
|
|
280
|
+
label: z.string().min(1),
|
|
281
|
+
variant: badgeVariantSchema.default("accent")
|
|
282
|
+
});
|
|
283
|
+
var latestSchema = z.object({
|
|
284
|
+
label: z.string().min(1),
|
|
285
|
+
landing: z.boolean().optional()
|
|
286
|
+
});
|
|
287
|
+
var versionSchema = z.object({
|
|
288
|
+
dir: dirNameSchema,
|
|
289
|
+
label: z.string().min(1),
|
|
290
|
+
badge: badgeSchema.optional(),
|
|
291
|
+
landing: z.boolean().optional(),
|
|
292
|
+
content: z.array(contentEntrySchema).min(1),
|
|
293
|
+
api: z.array(apiSchema).optional()
|
|
294
|
+
});
|
|
295
|
+
var allUnique = (items, key) => uniqBy(items, key).length === items.length;
|
|
296
|
+
var RESERVED_ROUTE_SEGMENTS = [
|
|
297
|
+
"api",
|
|
298
|
+
"apis",
|
|
299
|
+
"og",
|
|
300
|
+
"llms.txt",
|
|
301
|
+
"robots.txt",
|
|
302
|
+
"sitemap.xml"
|
|
303
|
+
];
|
|
304
|
+
var chronicleConfigSchema = z.object({
|
|
305
|
+
site: siteSchema,
|
|
265
306
|
url: z.string().optional(),
|
|
266
|
-
content: z.
|
|
307
|
+
content: z.array(contentEntrySchema).min(1),
|
|
308
|
+
latest: latestSchema.optional(),
|
|
309
|
+
versions: z.array(versionSchema).optional(),
|
|
267
310
|
preset: z.string().optional(),
|
|
268
311
|
logo: logoSchema.optional(),
|
|
269
312
|
theme: themeSchema.optional(),
|
|
270
313
|
navigation: navigationSchema.optional(),
|
|
271
314
|
search: searchSchema.optional(),
|
|
272
|
-
footer: footerSchema.optional(),
|
|
273
315
|
api: z.array(apiSchema).optional(),
|
|
274
|
-
llms: llmsSchema.optional(),
|
|
275
316
|
analytics: analyticsSchema.optional(),
|
|
276
317
|
telemetry: telemetrySchema.optional()
|
|
318
|
+
}).strict().refine((cfg) => allUnique(cfg.content, (c) => c.dir), {
|
|
319
|
+
message: "content[].dir must be unique",
|
|
320
|
+
path: ["content"]
|
|
321
|
+
}).refine((cfg) => !cfg.versions || allUnique(cfg.versions, (v) => v.dir), {
|
|
322
|
+
message: "versions[].dir must be unique",
|
|
323
|
+
path: ["versions"]
|
|
324
|
+
}).refine((cfg) => !cfg.versions || cfg.versions.every((v) => allUnique(v.content, (c) => c.dir)), {
|
|
325
|
+
message: "versions[].content[].dir must be unique within each version",
|
|
326
|
+
path: ["versions"]
|
|
327
|
+
}).refine((cfg) => !cfg.versions || cfg.versions.length === 0 || !!cfg.latest, {
|
|
328
|
+
message: "latest is required when versions are declared",
|
|
329
|
+
path: ["latest"]
|
|
330
|
+
}).refine((cfg) => {
|
|
331
|
+
if (!cfg.versions)
|
|
332
|
+
return true;
|
|
333
|
+
const contentDirs = new Set(cfg.content.map((c) => c.dir));
|
|
334
|
+
return !cfg.versions.some((v) => contentDirs.has(v.dir));
|
|
335
|
+
}, {
|
|
336
|
+
message: "versions[].dir must not overlap with content[].dir — the URL segment would be shadowed",
|
|
337
|
+
path: ["versions"]
|
|
338
|
+
}).superRefine((cfg, ctx) => {
|
|
339
|
+
const reserved = new Set(RESERVED_ROUTE_SEGMENTS);
|
|
340
|
+
const message = `dir must not be a reserved route segment: ${RESERVED_ROUTE_SEGMENTS.join(", ")}`;
|
|
341
|
+
cfg.content.forEach((c, i) => {
|
|
342
|
+
if (reserved.has(c.dir)) {
|
|
343
|
+
ctx.addIssue({ code: "custom", message, path: ["content", i, "dir"] });
|
|
344
|
+
}
|
|
345
|
+
});
|
|
346
|
+
cfg.versions?.forEach((v, vi) => {
|
|
347
|
+
if (reserved.has(v.dir)) {
|
|
348
|
+
ctx.addIssue({
|
|
349
|
+
code: "custom",
|
|
350
|
+
message,
|
|
351
|
+
path: ["versions", vi, "dir"]
|
|
352
|
+
});
|
|
353
|
+
}
|
|
354
|
+
v.content.forEach((c, ci) => {
|
|
355
|
+
if (reserved.has(c.dir)) {
|
|
356
|
+
ctx.addIssue({
|
|
357
|
+
code: "custom",
|
|
358
|
+
message,
|
|
359
|
+
path: ["versions", vi, "content", ci, "dir"]
|
|
360
|
+
});
|
|
361
|
+
}
|
|
362
|
+
});
|
|
363
|
+
});
|
|
277
364
|
});
|
|
278
365
|
// src/cli/utils/config.ts
|
|
279
366
|
function resolveConfigPath(configPath) {
|
|
@@ -299,20 +386,13 @@ function validateConfig(raw, configPath) {
|
|
|
299
386
|
if (!result.success) {
|
|
300
387
|
console.log(chalk.red(`Error: Invalid chronicle.yaml at '${configPath}'`));
|
|
301
388
|
for (const issue of result.error.issues) {
|
|
302
|
-
const
|
|
303
|
-
console.log(chalk.gray(` ${
|
|
389
|
+
const issuePath = issue.path.join(".");
|
|
390
|
+
console.log(chalk.gray(` ${issuePath ? `${issuePath}: ` : ""}${issue.message}`));
|
|
304
391
|
}
|
|
305
392
|
process.exit(1);
|
|
306
393
|
}
|
|
307
394
|
return result.data;
|
|
308
395
|
}
|
|
309
|
-
function resolveContentDir(config2, configPath, contentFlag) {
|
|
310
|
-
if (contentFlag)
|
|
311
|
-
return path.resolve(contentFlag);
|
|
312
|
-
if (config2.content)
|
|
313
|
-
return path.resolve(path.dirname(configPath), config2.content);
|
|
314
|
-
return path.resolve("content");
|
|
315
|
-
}
|
|
316
396
|
function resolvePreset(config2, presetFlag) {
|
|
317
397
|
return presetFlag ?? config2.preset;
|
|
318
398
|
}
|
|
@@ -320,9 +400,9 @@ async function loadCLIConfig(configPath, options) {
|
|
|
320
400
|
const resolvedConfigPath = resolveConfigPath(configPath) ?? path.join(process.cwd(), "chronicle.yaml");
|
|
321
401
|
const raw = await readConfig(resolvedConfigPath);
|
|
322
402
|
const config2 = validateConfig(raw, resolvedConfigPath);
|
|
323
|
-
const
|
|
403
|
+
const projectRoot = path.dirname(resolvedConfigPath);
|
|
324
404
|
const preset = resolvePreset(config2, options?.preset);
|
|
325
|
-
return { config: config2, configPath: resolvedConfigPath,
|
|
405
|
+
return { config: config2, configPath: resolvedConfigPath, projectRoot, preset };
|
|
326
406
|
}
|
|
327
407
|
|
|
328
408
|
// src/cli/utils/resolve.ts
|
|
@@ -333,36 +413,116 @@ var PACKAGE_ROOT = path2.resolve(path2.dirname(fileURLToPath(import.meta.url)),
|
|
|
333
413
|
// src/cli/utils/scaffold.ts
|
|
334
414
|
import fs2 from "node:fs/promises";
|
|
335
415
|
import path3 from "node:path";
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
416
|
+
|
|
417
|
+
// src/lib/config.ts
|
|
418
|
+
import { parse as parse2 } from "yaml";
|
|
419
|
+
var defaultConfig = chronicleConfigSchema.parse({
|
|
420
|
+
site: { title: "Documentation" },
|
|
421
|
+
content: [{ dir: "docs", label: "Docs" }],
|
|
422
|
+
theme: { name: "default" },
|
|
423
|
+
search: { enabled: true, placeholder: "Search..." }
|
|
424
|
+
});
|
|
425
|
+
function getLatestContentRoots(config2) {
|
|
426
|
+
return config2.content.map((c) => ({
|
|
427
|
+
versionDir: null,
|
|
428
|
+
versionLabel: config2.latest?.label ?? null,
|
|
429
|
+
contentDir: c.dir,
|
|
430
|
+
contentLabel: c.label,
|
|
431
|
+
contentIcon: c.icon,
|
|
432
|
+
fsPath: `content/${c.dir}`,
|
|
433
|
+
urlPrefix: `/${c.dir}`
|
|
434
|
+
}));
|
|
435
|
+
}
|
|
436
|
+
function getVersionContentRoots(config2, versionDir) {
|
|
437
|
+
const version = config2.versions?.find((v) => v.dir === versionDir);
|
|
438
|
+
if (!version)
|
|
439
|
+
return [];
|
|
440
|
+
return version.content.map((c) => ({
|
|
441
|
+
versionDir: version.dir,
|
|
442
|
+
versionLabel: version.label,
|
|
443
|
+
contentDir: c.dir,
|
|
444
|
+
contentLabel: c.label,
|
|
445
|
+
contentIcon: c.icon,
|
|
446
|
+
fsPath: `versions/${version.dir}/${c.dir}`,
|
|
447
|
+
urlPrefix: `/${version.dir}/${c.dir}`
|
|
448
|
+
}));
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
// src/cli/utils/scaffold.ts
|
|
452
|
+
async function buildContentMirror(mirrorRoot, projectRoot, config2) {
|
|
453
|
+
await removeMirror(mirrorRoot);
|
|
454
|
+
await fs2.mkdir(mirrorRoot, { recursive: true });
|
|
455
|
+
for (const root of getLatestContentRoots(config2)) {
|
|
456
|
+
const source = path3.resolve(projectRoot, root.fsPath);
|
|
457
|
+
const dest = path3.join(mirrorRoot, root.contentDir);
|
|
458
|
+
await mirrorTree(source, dest);
|
|
459
|
+
}
|
|
460
|
+
for (const version of config2.versions ?? []) {
|
|
461
|
+
const versionMirror = path3.join(mirrorRoot, version.dir);
|
|
462
|
+
await fs2.mkdir(versionMirror, { recursive: true });
|
|
463
|
+
for (const root of getVersionContentRoots(config2, version.dir)) {
|
|
464
|
+
const source = path3.resolve(projectRoot, root.fsPath);
|
|
465
|
+
const dest = path3.join(versionMirror, root.contentDir);
|
|
466
|
+
await mirrorTree(source, dest);
|
|
467
|
+
}
|
|
468
|
+
}
|
|
469
|
+
}
|
|
470
|
+
function linkContent(projectRoot, config2) {
|
|
471
|
+
return buildContentMirror(path3.join(PACKAGE_ROOT, ".content"), projectRoot, config2);
|
|
472
|
+
}
|
|
473
|
+
async function mirrorTree(source, dest) {
|
|
474
|
+
let entries;
|
|
339
475
|
try {
|
|
340
|
-
|
|
341
|
-
|
|
476
|
+
entries = await fs2.readdir(source, { withFileTypes: true });
|
|
477
|
+
} catch (error) {
|
|
478
|
+
const err = error;
|
|
479
|
+
if (err.code === "ENOENT") {
|
|
480
|
+
throw new Error(`Content directory not found: ${source}`);
|
|
481
|
+
}
|
|
482
|
+
throw error;
|
|
483
|
+
}
|
|
484
|
+
await fs2.mkdir(dest, { recursive: true });
|
|
485
|
+
for (const entry of entries) {
|
|
486
|
+
const sourcePath = path3.join(source, entry.name);
|
|
487
|
+
const destPath = path3.join(dest, entry.name);
|
|
488
|
+
if (entry.isDirectory()) {
|
|
489
|
+
await mirrorTree(sourcePath, destPath);
|
|
490
|
+
} else if (entry.isFile() || entry.isSymbolicLink()) {
|
|
491
|
+
await fs2.symlink(sourcePath, destPath);
|
|
492
|
+
}
|
|
493
|
+
}
|
|
494
|
+
}
|
|
495
|
+
async function removeMirror(mirrorRoot) {
|
|
496
|
+
try {
|
|
497
|
+
const stat = await fs2.lstat(mirrorRoot);
|
|
498
|
+
if (stat.isSymbolicLink() || stat.isFile()) {
|
|
499
|
+
await fs2.unlink(mirrorRoot);
|
|
500
|
+
} else if (stat.isDirectory()) {
|
|
501
|
+
await fs2.rm(mirrorRoot, { recursive: true, force: true });
|
|
502
|
+
}
|
|
503
|
+
} catch (error) {
|
|
504
|
+
if (error.code === "ENOENT")
|
|
342
505
|
return;
|
|
343
|
-
|
|
344
|
-
}
|
|
345
|
-
await fs2.symlink(target, linkPath);
|
|
506
|
+
throw error;
|
|
507
|
+
}
|
|
346
508
|
}
|
|
347
509
|
|
|
348
510
|
// src/cli/commands/build.ts
|
|
349
|
-
var buildCommand = new Command("build").description("Build for production").option("--
|
|
350
|
-
const {
|
|
351
|
-
content: options.content,
|
|
511
|
+
var buildCommand = new Command("build").description("Build for production").option("--config <path>", "Path to chronicle.yaml").option("--preset <preset>", "Deploy preset (vercel, cloudflare, node-server)").action(async (options) => {
|
|
512
|
+
const { config: config2, projectRoot, configPath, preset } = await loadCLIConfig(options.config, {
|
|
352
513
|
preset: options.preset
|
|
353
514
|
});
|
|
354
|
-
await linkContent(
|
|
515
|
+
await linkContent(projectRoot, config2);
|
|
355
516
|
console.log(chalk2.cyan("Building for production..."));
|
|
356
517
|
const { createBuilder } = await import("vite");
|
|
357
518
|
const { createViteConfig: createViteConfig2 } = await Promise.resolve().then(() => (init_vite_config(), exports_vite_config));
|
|
358
|
-
const
|
|
519
|
+
const viteConfig = await createViteConfig2({
|
|
359
520
|
packageRoot: PACKAGE_ROOT,
|
|
360
|
-
projectRoot
|
|
361
|
-
contentDir,
|
|
521
|
+
projectRoot,
|
|
362
522
|
configPath,
|
|
363
523
|
preset
|
|
364
524
|
});
|
|
365
|
-
const builder = await createBuilder({ ...
|
|
525
|
+
const builder = await createBuilder({ ...viteConfig, builder: {} });
|
|
366
526
|
await builder.buildApp();
|
|
367
527
|
console.log(chalk2.green("Build complete"));
|
|
368
528
|
console.log(chalk2.cyan("Run `chronicle start` to start the server"));
|
|
@@ -371,17 +531,17 @@ var buildCommand = new Command("build").description("Build for production").opti
|
|
|
371
531
|
// src/cli/commands/dev.ts
|
|
372
532
|
import chalk3 from "chalk";
|
|
373
533
|
import { Command as Command2 } from "commander";
|
|
374
|
-
var devCommand = new Command2("dev").description("Start development server").option("-p, --port <port>", "Port number", "3000").option("--
|
|
375
|
-
const {
|
|
534
|
+
var devCommand = new Command2("dev").description("Start development server").option("-p, --port <port>", "Port number", "3000").option("--config <path>", "Path to chronicle.yaml").option("--host <host>", "Host address", "localhost").action(async (options) => {
|
|
535
|
+
const { config: config2, projectRoot, configPath } = await loadCLIConfig(options.config);
|
|
376
536
|
const port = parseInt(options.port, 10);
|
|
377
|
-
await linkContent(
|
|
537
|
+
await linkContent(projectRoot, config2);
|
|
378
538
|
console.log(chalk3.cyan("Starting dev server..."));
|
|
379
539
|
const { createServer } = await import("vite");
|
|
380
540
|
const { createViteConfig: createViteConfig2 } = await Promise.resolve().then(() => (init_vite_config(), exports_vite_config));
|
|
381
|
-
const
|
|
541
|
+
const viteConfig = await createViteConfig2({ packageRoot: PACKAGE_ROOT, projectRoot, configPath });
|
|
382
542
|
const server = await createServer({
|
|
383
|
-
...
|
|
384
|
-
server: { ...
|
|
543
|
+
...viteConfig,
|
|
544
|
+
server: { ...viteConfig.server, port, host: options.host }
|
|
385
545
|
});
|
|
386
546
|
await server.listen();
|
|
387
547
|
server.printUrls();
|
|
@@ -393,9 +553,12 @@ import path5 from "node:path";
|
|
|
393
553
|
import chalk4 from "chalk";
|
|
394
554
|
import { Command as Command3 } from "commander";
|
|
395
555
|
import { stringify } from "yaml";
|
|
396
|
-
var
|
|
397
|
-
|
|
398
|
-
|
|
556
|
+
var defaultInitConfig = {
|
|
557
|
+
site: {
|
|
558
|
+
title: "My Documentation",
|
|
559
|
+
description: "Documentation powered by Chronicle"
|
|
560
|
+
},
|
|
561
|
+
content: [{ dir: "docs", label: "Docs" }],
|
|
399
562
|
theme: { name: "default" },
|
|
400
563
|
search: { enabled: true, placeholder: "Search documentation..." }
|
|
401
564
|
};
|
|
@@ -409,44 +572,61 @@ order: 1
|
|
|
409
572
|
|
|
410
573
|
This is your documentation home page.
|
|
411
574
|
`;
|
|
412
|
-
var
|
|
413
|
-
|
|
414
|
-
const
|
|
575
|
+
var GITIGNORE_ENTRIES = ["node_modules", "dist", ".output"];
|
|
576
|
+
function runInit(projectDir) {
|
|
577
|
+
const events = [];
|
|
578
|
+
const defaultDir = defaultInitConfig.content[0].dir;
|
|
579
|
+
const contentDir = path5.join(projectDir, "content", defaultDir);
|
|
415
580
|
if (!fs4.existsSync(contentDir)) {
|
|
416
581
|
fs4.mkdirSync(contentDir, { recursive: true });
|
|
417
|
-
|
|
582
|
+
events.push({ type: "created", path: contentDir });
|
|
418
583
|
}
|
|
419
584
|
const configPath = path5.join(projectDir, "chronicle.yaml");
|
|
420
585
|
if (!fs4.existsSync(configPath)) {
|
|
421
|
-
fs4.writeFileSync(configPath, stringify(
|
|
422
|
-
|
|
586
|
+
fs4.writeFileSync(configPath, stringify(defaultInitConfig));
|
|
587
|
+
events.push({ type: "created", path: configPath });
|
|
423
588
|
} else {
|
|
424
|
-
|
|
589
|
+
events.push({ type: "skipped", path: configPath, detail: "already exists" });
|
|
425
590
|
}
|
|
426
591
|
const contentFiles = fs4.readdirSync(contentDir);
|
|
427
592
|
if (contentFiles.length === 0) {
|
|
428
593
|
const indexPath = path5.join(contentDir, "index.mdx");
|
|
429
594
|
fs4.writeFileSync(indexPath, sampleMdx);
|
|
430
|
-
|
|
595
|
+
events.push({ type: "created", path: indexPath });
|
|
431
596
|
}
|
|
432
597
|
const gitignorePath = path5.join(projectDir, ".gitignore");
|
|
433
|
-
const gitignoreEntries = ["node_modules", "dist", ".output"];
|
|
434
598
|
if (fs4.existsSync(gitignorePath)) {
|
|
435
599
|
const existing = fs4.readFileSync(gitignorePath, "utf-8");
|
|
436
|
-
const
|
|
600
|
+
const existingLines = new Set(existing.split(/\r?\n/).map((l) => l.trim()).filter(Boolean));
|
|
601
|
+
const missing = GITIGNORE_ENTRIES.filter((e) => !existingLines.has(e));
|
|
437
602
|
if (missing.length > 0) {
|
|
438
603
|
fs4.appendFileSync(gitignorePath, `
|
|
439
604
|
${missing.join(`
|
|
440
605
|
`)}
|
|
441
606
|
`);
|
|
442
|
-
|
|
607
|
+
events.push({ type: "updated", path: gitignorePath, detail: missing.join(", ") });
|
|
443
608
|
}
|
|
444
609
|
} else {
|
|
445
|
-
fs4.writeFileSync(gitignorePath, `${
|
|
610
|
+
fs4.writeFileSync(gitignorePath, `${GITIGNORE_ENTRIES.join(`
|
|
446
611
|
`)}
|
|
447
612
|
`);
|
|
448
|
-
|
|
613
|
+
events.push({ type: "created", path: gitignorePath });
|
|
614
|
+
}
|
|
615
|
+
return events;
|
|
616
|
+
}
|
|
617
|
+
function formatEvent(e) {
|
|
618
|
+
if (e.type === "skipped") {
|
|
619
|
+
return `${chalk4.yellow("⚠")} ${e.path}${e.detail ? ` ${e.detail}` : ""}`;
|
|
449
620
|
}
|
|
621
|
+
if (e.type === "updated") {
|
|
622
|
+
return `${chalk4.green("✓")} Updated ${e.path}${e.detail ? ` (+${e.detail})` : ""}`;
|
|
623
|
+
}
|
|
624
|
+
return `${chalk4.green("✓")} Created ${e.path}`;
|
|
625
|
+
}
|
|
626
|
+
var initCommand = new Command3("init").description("Initialize a new Chronicle project").action(() => {
|
|
627
|
+
const events = runInit(process.cwd());
|
|
628
|
+
for (const e of events)
|
|
629
|
+
console.log(formatEvent(e));
|
|
450
630
|
console.log(chalk4.green(`
|
|
451
631
|
✓ Chronicle initialized!`));
|
|
452
632
|
console.log(`
|
|
@@ -456,27 +636,25 @@ Run`, chalk4.cyan("chronicle dev"), "to start development server");
|
|
|
456
636
|
// src/cli/commands/serve.ts
|
|
457
637
|
import chalk5 from "chalk";
|
|
458
638
|
import { Command as Command4 } from "commander";
|
|
459
|
-
var serveCommand = new Command4("serve").description("Build and start production server").option("-p, --port <port>", "Port number", "3000").option("--
|
|
460
|
-
const {
|
|
461
|
-
content: options.content,
|
|
639
|
+
var serveCommand = new Command4("serve").description("Build and start production server").option("-p, --port <port>", "Port number", "3000").option("--config <path>", "Path to chronicle.yaml").option("--host <host>", "Host address", "localhost").option("--preset <preset>", "Deploy preset (vercel, cloudflare, node-server)").action(async (options) => {
|
|
640
|
+
const { config: config2, projectRoot, configPath, preset } = await loadCLIConfig(options.config, {
|
|
462
641
|
preset: options.preset
|
|
463
642
|
});
|
|
464
643
|
const port = parseInt(options.port, 10);
|
|
465
|
-
await linkContent(
|
|
644
|
+
await linkContent(projectRoot, config2);
|
|
466
645
|
const { build, preview } = await import("vite");
|
|
467
646
|
const { createViteConfig: createViteConfig2 } = await Promise.resolve().then(() => (init_vite_config(), exports_vite_config));
|
|
468
|
-
const
|
|
647
|
+
const viteConfig = await createViteConfig2({
|
|
469
648
|
packageRoot: PACKAGE_ROOT,
|
|
470
|
-
projectRoot
|
|
471
|
-
contentDir,
|
|
649
|
+
projectRoot,
|
|
472
650
|
configPath,
|
|
473
651
|
preset
|
|
474
652
|
});
|
|
475
653
|
console.log(chalk5.cyan("Building for production..."));
|
|
476
|
-
await build(
|
|
654
|
+
await build(viteConfig);
|
|
477
655
|
console.log(chalk5.cyan("Starting production server..."));
|
|
478
656
|
const server = await preview({
|
|
479
|
-
...
|
|
657
|
+
...viteConfig,
|
|
480
658
|
preview: { port, host: options.host }
|
|
481
659
|
});
|
|
482
660
|
server.printUrls();
|
|
@@ -485,16 +663,16 @@ var serveCommand = new Command4("serve").description("Build and start production
|
|
|
485
663
|
// src/cli/commands/start.ts
|
|
486
664
|
import chalk6 from "chalk";
|
|
487
665
|
import { Command as Command5 } from "commander";
|
|
488
|
-
var startCommand = new Command5("start").description("Start production server").option("-p, --port <port>", "Port number", "3000").option("--
|
|
489
|
-
const {
|
|
666
|
+
var startCommand = new Command5("start").description("Start production server").option("-p, --port <port>", "Port number", "3000").option("--config <path>", "Path to chronicle.yaml").option("--host <host>", "Host address", "localhost").action(async (options) => {
|
|
667
|
+
const { config: config2, projectRoot, configPath } = await loadCLIConfig(options.config);
|
|
490
668
|
const port = parseInt(options.port, 10);
|
|
491
|
-
await linkContent(
|
|
669
|
+
await linkContent(projectRoot, config2);
|
|
492
670
|
console.log(chalk6.cyan("Starting production server..."));
|
|
493
671
|
const { preview } = await import("vite");
|
|
494
672
|
const { createViteConfig: createViteConfig2 } = await Promise.resolve().then(() => (init_vite_config(), exports_vite_config));
|
|
495
|
-
const
|
|
673
|
+
const viteConfig = await createViteConfig2({ packageRoot: PACKAGE_ROOT, projectRoot, configPath });
|
|
496
674
|
const server = await preview({
|
|
497
|
-
...
|
|
675
|
+
...viteConfig,
|
|
498
676
|
preview: { port, host: options.host }
|
|
499
677
|
});
|
|
500
678
|
server.printUrls();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@raystack/chronicle",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.6.0",
|
|
4
4
|
"description": "Config-driven documentation framework",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"type": "module",
|
|
@@ -16,7 +16,8 @@
|
|
|
16
16
|
},
|
|
17
17
|
"scripts": {
|
|
18
18
|
"build:cli": "bun build-cli.ts",
|
|
19
|
-
"lint": "biome lint src/"
|
|
19
|
+
"lint": "biome lint src/",
|
|
20
|
+
"test": "bun test"
|
|
20
21
|
},
|
|
21
22
|
"devDependencies": {
|
|
22
23
|
"@biomejs/biome": "^2.3.13",
|
|
@@ -41,20 +42,20 @@
|
|
|
41
42
|
"@opentelemetry/resources": "^2.6.1",
|
|
42
43
|
"@opentelemetry/sdk-metrics": "^2.6.1",
|
|
43
44
|
"@opentelemetry/semantic-conventions": "^1.40.0",
|
|
44
|
-
"@raystack/apsara": "0.
|
|
45
|
+
"@raystack/apsara": "1.0.0-rc.4",
|
|
45
46
|
"@shikijs/rehype": "^4.0.2",
|
|
46
47
|
"@vitejs/plugin-react": "^6.0.1",
|
|
48
|
+
"beautiful-mermaid": "^1.1.3",
|
|
47
49
|
"chalk": "^5.6.2",
|
|
48
50
|
"class-variance-authority": "^0.7.1",
|
|
49
51
|
"codemirror": "^6.0.2",
|
|
50
52
|
"commander": "^14.0.2",
|
|
51
|
-
"fumadocs-core": "16.
|
|
52
|
-
"fumadocs-mdx": "14.
|
|
53
|
+
"fumadocs-core": "16.8.1",
|
|
54
|
+
"fumadocs-mdx": "14.3.1",
|
|
53
55
|
"glob": "^11.0.0",
|
|
54
56
|
"gray-matter": "^4.0.3",
|
|
55
57
|
"h3": "^2.0.1-rc.16",
|
|
56
58
|
"lodash": "^4.17.23",
|
|
57
|
-
"mermaid": "^11.13.0",
|
|
58
59
|
"minisearch": "^7.2.0",
|
|
59
60
|
"nitro": "3.0.260311-beta",
|
|
60
61
|
"openapi-types": "^12.1.3",
|
|
@@ -68,6 +69,7 @@
|
|
|
68
69
|
"remark-parse": "^11.0.0",
|
|
69
70
|
"satori": "^0.25.0",
|
|
70
71
|
"slugify": "^1.6.6",
|
|
72
|
+
"std-env": "^4.1.0",
|
|
71
73
|
"unified": "^11.0.5",
|
|
72
74
|
"unist-util-visit": "^5.1.0",
|
|
73
75
|
"vite": "8.0.3",
|
|
@@ -6,33 +6,30 @@ import { linkContent } from '@/cli/utils/scaffold';
|
|
|
6
6
|
|
|
7
7
|
export const buildCommand = new Command('build')
|
|
8
8
|
.description('Build for production')
|
|
9
|
-
.option('--content <path>', 'Content directory')
|
|
10
9
|
.option('--config <path>', 'Path to chronicle.yaml')
|
|
11
10
|
.option(
|
|
12
11
|
'--preset <preset>',
|
|
13
12
|
'Deploy preset (vercel, cloudflare, node-server)'
|
|
14
13
|
)
|
|
15
14
|
.action(async options => {
|
|
16
|
-
const {
|
|
17
|
-
content: options.content,
|
|
15
|
+
const { config, projectRoot, configPath, preset } = await loadCLIConfig(options.config, {
|
|
18
16
|
preset: options.preset,
|
|
19
17
|
});
|
|
20
|
-
await linkContent(
|
|
18
|
+
await linkContent(projectRoot, config);
|
|
21
19
|
|
|
22
20
|
console.log(chalk.cyan('Building for production...'));
|
|
23
21
|
|
|
24
22
|
const { createBuilder } = await import('vite');
|
|
25
23
|
const { createViteConfig } = await import('@/server/vite-config');
|
|
26
24
|
|
|
27
|
-
const
|
|
25
|
+
const viteConfig = await createViteConfig({
|
|
28
26
|
packageRoot: PACKAGE_ROOT,
|
|
29
|
-
projectRoot
|
|
30
|
-
contentDir,
|
|
27
|
+
projectRoot,
|
|
31
28
|
configPath,
|
|
32
29
|
preset
|
|
33
30
|
});
|
|
34
31
|
|
|
35
|
-
const builder = await createBuilder({ ...
|
|
32
|
+
const builder = await createBuilder({ ...viteConfig, builder: {} });
|
|
36
33
|
await builder.buildApp();
|
|
37
34
|
|
|
38
35
|
console.log(chalk.green('Build complete'));
|
package/src/cli/commands/dev.ts
CHANGED
|
@@ -7,24 +7,23 @@ import { linkContent } from '@/cli/utils/scaffold';
|
|
|
7
7
|
export const devCommand = new Command('dev')
|
|
8
8
|
.description('Start development server')
|
|
9
9
|
.option('-p, --port <port>', 'Port number', '3000')
|
|
10
|
-
.option('--content <path>', 'Content directory')
|
|
11
10
|
.option('--config <path>', 'Path to chronicle.yaml')
|
|
12
11
|
.option('--host <host>', 'Host address', 'localhost')
|
|
13
12
|
.action(async options => {
|
|
14
|
-
const {
|
|
13
|
+
const { config, projectRoot, configPath } = await loadCLIConfig(options.config);
|
|
15
14
|
const port = parseInt(options.port, 10);
|
|
16
15
|
|
|
17
|
-
await linkContent(
|
|
16
|
+
await linkContent(projectRoot, config);
|
|
18
17
|
|
|
19
18
|
console.log(chalk.cyan('Starting dev server...'));
|
|
20
19
|
|
|
21
20
|
const { createServer } = await import('vite');
|
|
22
21
|
const { createViteConfig } = await import('@/server/vite-config');
|
|
23
22
|
|
|
24
|
-
const
|
|
23
|
+
const viteConfig = await createViteConfig({ packageRoot: PACKAGE_ROOT, projectRoot, configPath });
|
|
25
24
|
const server = await createServer({
|
|
26
|
-
...
|
|
27
|
-
server: { ...
|
|
25
|
+
...viteConfig,
|
|
26
|
+
server: { ...viteConfig.server, port, host: options.host }
|
|
28
27
|
});
|
|
29
28
|
|
|
30
29
|
await server.listen();
|