@raystack/chronicle 0.5.3 → 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 +260 -81
- 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 +79 -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 +7 -7
- package/src/pages/LandingPage.module.css +56 -0
- package/src/pages/LandingPage.tsx +39 -0
- package/src/pages/NotFound.module.css +3 -0
- package/src/pages/NotFound.tsx +9 -12
- package/src/server/App.tsx +21 -23
- package/src/server/api/{page/[...slug].ts → page.ts} +7 -3
- 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 +35 -13
- package/src/server/plugins/telemetry.ts +47 -7
- 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 +146 -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/src/server/api/metrics.ts +0 -23
- package/src/server/api/page/index.ts +0 -1
- package/src/server/telemetry.ts +0 -49
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
|
});
|
|
@@ -256,23 +252,115 @@ var analyticsSchema = z.object({
|
|
|
256
252
|
});
|
|
257
253
|
var telemetrySchema = z.object({
|
|
258
254
|
enabled: z.boolean().optional(),
|
|
259
|
-
serviceName: z.string().optional()
|
|
255
|
+
serviceName: z.string().optional(),
|
|
256
|
+
port: z.number().int().min(1).max(65535).default(9090)
|
|
260
257
|
});
|
|
261
|
-
var
|
|
258
|
+
var siteSchema = z.object({
|
|
262
259
|
title: z.string(),
|
|
263
|
-
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,
|
|
264
306
|
url: z.string().optional(),
|
|
265
|
-
content: z.
|
|
307
|
+
content: z.array(contentEntrySchema).min(1),
|
|
308
|
+
latest: latestSchema.optional(),
|
|
309
|
+
versions: z.array(versionSchema).optional(),
|
|
266
310
|
preset: z.string().optional(),
|
|
267
311
|
logo: logoSchema.optional(),
|
|
268
312
|
theme: themeSchema.optional(),
|
|
269
313
|
navigation: navigationSchema.optional(),
|
|
270
314
|
search: searchSchema.optional(),
|
|
271
|
-
footer: footerSchema.optional(),
|
|
272
315
|
api: z.array(apiSchema).optional(),
|
|
273
|
-
llms: llmsSchema.optional(),
|
|
274
316
|
analytics: analyticsSchema.optional(),
|
|
275
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
|
+
});
|
|
276
364
|
});
|
|
277
365
|
// src/cli/utils/config.ts
|
|
278
366
|
function resolveConfigPath(configPath) {
|
|
@@ -298,20 +386,13 @@ function validateConfig(raw, configPath) {
|
|
|
298
386
|
if (!result.success) {
|
|
299
387
|
console.log(chalk.red(`Error: Invalid chronicle.yaml at '${configPath}'`));
|
|
300
388
|
for (const issue of result.error.issues) {
|
|
301
|
-
const
|
|
302
|
-
console.log(chalk.gray(` ${
|
|
389
|
+
const issuePath = issue.path.join(".");
|
|
390
|
+
console.log(chalk.gray(` ${issuePath ? `${issuePath}: ` : ""}${issue.message}`));
|
|
303
391
|
}
|
|
304
392
|
process.exit(1);
|
|
305
393
|
}
|
|
306
394
|
return result.data;
|
|
307
395
|
}
|
|
308
|
-
function resolveContentDir(config2, configPath, contentFlag) {
|
|
309
|
-
if (contentFlag)
|
|
310
|
-
return path.resolve(contentFlag);
|
|
311
|
-
if (config2.content)
|
|
312
|
-
return path.resolve(path.dirname(configPath), config2.content);
|
|
313
|
-
return path.resolve("content");
|
|
314
|
-
}
|
|
315
396
|
function resolvePreset(config2, presetFlag) {
|
|
316
397
|
return presetFlag ?? config2.preset;
|
|
317
398
|
}
|
|
@@ -319,9 +400,9 @@ async function loadCLIConfig(configPath, options) {
|
|
|
319
400
|
const resolvedConfigPath = resolveConfigPath(configPath) ?? path.join(process.cwd(), "chronicle.yaml");
|
|
320
401
|
const raw = await readConfig(resolvedConfigPath);
|
|
321
402
|
const config2 = validateConfig(raw, resolvedConfigPath);
|
|
322
|
-
const
|
|
403
|
+
const projectRoot = path.dirname(resolvedConfigPath);
|
|
323
404
|
const preset = resolvePreset(config2, options?.preset);
|
|
324
|
-
return { config: config2, configPath: resolvedConfigPath,
|
|
405
|
+
return { config: config2, configPath: resolvedConfigPath, projectRoot, preset };
|
|
325
406
|
}
|
|
326
407
|
|
|
327
408
|
// src/cli/utils/resolve.ts
|
|
@@ -332,36 +413,116 @@ var PACKAGE_ROOT = path2.resolve(path2.dirname(fileURLToPath(import.meta.url)),
|
|
|
332
413
|
// src/cli/utils/scaffold.ts
|
|
333
414
|
import fs2 from "node:fs/promises";
|
|
334
415
|
import path3 from "node:path";
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
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;
|
|
338
475
|
try {
|
|
339
|
-
|
|
340
|
-
|
|
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")
|
|
341
505
|
return;
|
|
342
|
-
|
|
343
|
-
}
|
|
344
|
-
await fs2.symlink(target, linkPath);
|
|
506
|
+
throw error;
|
|
507
|
+
}
|
|
345
508
|
}
|
|
346
509
|
|
|
347
510
|
// src/cli/commands/build.ts
|
|
348
|
-
var buildCommand = new Command("build").description("Build for production").option("--
|
|
349
|
-
const {
|
|
350
|
-
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, {
|
|
351
513
|
preset: options.preset
|
|
352
514
|
});
|
|
353
|
-
await linkContent(
|
|
515
|
+
await linkContent(projectRoot, config2);
|
|
354
516
|
console.log(chalk2.cyan("Building for production..."));
|
|
355
517
|
const { createBuilder } = await import("vite");
|
|
356
518
|
const { createViteConfig: createViteConfig2 } = await Promise.resolve().then(() => (init_vite_config(), exports_vite_config));
|
|
357
|
-
const
|
|
519
|
+
const viteConfig = await createViteConfig2({
|
|
358
520
|
packageRoot: PACKAGE_ROOT,
|
|
359
|
-
projectRoot
|
|
360
|
-
contentDir,
|
|
521
|
+
projectRoot,
|
|
361
522
|
configPath,
|
|
362
523
|
preset
|
|
363
524
|
});
|
|
364
|
-
const builder = await createBuilder({ ...
|
|
525
|
+
const builder = await createBuilder({ ...viteConfig, builder: {} });
|
|
365
526
|
await builder.buildApp();
|
|
366
527
|
console.log(chalk2.green("Build complete"));
|
|
367
528
|
console.log(chalk2.cyan("Run `chronicle start` to start the server"));
|
|
@@ -370,17 +531,17 @@ var buildCommand = new Command("build").description("Build for production").opti
|
|
|
370
531
|
// src/cli/commands/dev.ts
|
|
371
532
|
import chalk3 from "chalk";
|
|
372
533
|
import { Command as Command2 } from "commander";
|
|
373
|
-
var devCommand = new Command2("dev").description("Start development server").option("-p, --port <port>", "Port number", "3000").option("--
|
|
374
|
-
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);
|
|
375
536
|
const port = parseInt(options.port, 10);
|
|
376
|
-
await linkContent(
|
|
537
|
+
await linkContent(projectRoot, config2);
|
|
377
538
|
console.log(chalk3.cyan("Starting dev server..."));
|
|
378
539
|
const { createServer } = await import("vite");
|
|
379
540
|
const { createViteConfig: createViteConfig2 } = await Promise.resolve().then(() => (init_vite_config(), exports_vite_config));
|
|
380
|
-
const
|
|
541
|
+
const viteConfig = await createViteConfig2({ packageRoot: PACKAGE_ROOT, projectRoot, configPath });
|
|
381
542
|
const server = await createServer({
|
|
382
|
-
...
|
|
383
|
-
server: { ...
|
|
543
|
+
...viteConfig,
|
|
544
|
+
server: { ...viteConfig.server, port, host: options.host }
|
|
384
545
|
});
|
|
385
546
|
await server.listen();
|
|
386
547
|
server.printUrls();
|
|
@@ -392,9 +553,12 @@ import path5 from "node:path";
|
|
|
392
553
|
import chalk4 from "chalk";
|
|
393
554
|
import { Command as Command3 } from "commander";
|
|
394
555
|
import { stringify } from "yaml";
|
|
395
|
-
var
|
|
396
|
-
|
|
397
|
-
|
|
556
|
+
var defaultInitConfig = {
|
|
557
|
+
site: {
|
|
558
|
+
title: "My Documentation",
|
|
559
|
+
description: "Documentation powered by Chronicle"
|
|
560
|
+
},
|
|
561
|
+
content: [{ dir: "docs", label: "Docs" }],
|
|
398
562
|
theme: { name: "default" },
|
|
399
563
|
search: { enabled: true, placeholder: "Search documentation..." }
|
|
400
564
|
};
|
|
@@ -408,44 +572,61 @@ order: 1
|
|
|
408
572
|
|
|
409
573
|
This is your documentation home page.
|
|
410
574
|
`;
|
|
411
|
-
var
|
|
412
|
-
|
|
413
|
-
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);
|
|
414
580
|
if (!fs4.existsSync(contentDir)) {
|
|
415
581
|
fs4.mkdirSync(contentDir, { recursive: true });
|
|
416
|
-
|
|
582
|
+
events.push({ type: "created", path: contentDir });
|
|
417
583
|
}
|
|
418
584
|
const configPath = path5.join(projectDir, "chronicle.yaml");
|
|
419
585
|
if (!fs4.existsSync(configPath)) {
|
|
420
|
-
fs4.writeFileSync(configPath, stringify(
|
|
421
|
-
|
|
586
|
+
fs4.writeFileSync(configPath, stringify(defaultInitConfig));
|
|
587
|
+
events.push({ type: "created", path: configPath });
|
|
422
588
|
} else {
|
|
423
|
-
|
|
589
|
+
events.push({ type: "skipped", path: configPath, detail: "already exists" });
|
|
424
590
|
}
|
|
425
591
|
const contentFiles = fs4.readdirSync(contentDir);
|
|
426
592
|
if (contentFiles.length === 0) {
|
|
427
593
|
const indexPath = path5.join(contentDir, "index.mdx");
|
|
428
594
|
fs4.writeFileSync(indexPath, sampleMdx);
|
|
429
|
-
|
|
595
|
+
events.push({ type: "created", path: indexPath });
|
|
430
596
|
}
|
|
431
597
|
const gitignorePath = path5.join(projectDir, ".gitignore");
|
|
432
|
-
const gitignoreEntries = ["node_modules", "dist", ".output"];
|
|
433
598
|
if (fs4.existsSync(gitignorePath)) {
|
|
434
599
|
const existing = fs4.readFileSync(gitignorePath, "utf-8");
|
|
435
|
-
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));
|
|
436
602
|
if (missing.length > 0) {
|
|
437
603
|
fs4.appendFileSync(gitignorePath, `
|
|
438
604
|
${missing.join(`
|
|
439
605
|
`)}
|
|
440
606
|
`);
|
|
441
|
-
|
|
607
|
+
events.push({ type: "updated", path: gitignorePath, detail: missing.join(", ") });
|
|
442
608
|
}
|
|
443
609
|
} else {
|
|
444
|
-
fs4.writeFileSync(gitignorePath, `${
|
|
610
|
+
fs4.writeFileSync(gitignorePath, `${GITIGNORE_ENTRIES.join(`
|
|
445
611
|
`)}
|
|
446
612
|
`);
|
|
447
|
-
|
|
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}` : ""}`;
|
|
448
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));
|
|
449
630
|
console.log(chalk4.green(`
|
|
450
631
|
✓ Chronicle initialized!`));
|
|
451
632
|
console.log(`
|
|
@@ -455,27 +636,25 @@ Run`, chalk4.cyan("chronicle dev"), "to start development server");
|
|
|
455
636
|
// src/cli/commands/serve.ts
|
|
456
637
|
import chalk5 from "chalk";
|
|
457
638
|
import { Command as Command4 } from "commander";
|
|
458
|
-
var serveCommand = new Command4("serve").description("Build and start production server").option("-p, --port <port>", "Port number", "3000").option("--
|
|
459
|
-
const {
|
|
460
|
-
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, {
|
|
461
641
|
preset: options.preset
|
|
462
642
|
});
|
|
463
643
|
const port = parseInt(options.port, 10);
|
|
464
|
-
await linkContent(
|
|
644
|
+
await linkContent(projectRoot, config2);
|
|
465
645
|
const { build, preview } = await import("vite");
|
|
466
646
|
const { createViteConfig: createViteConfig2 } = await Promise.resolve().then(() => (init_vite_config(), exports_vite_config));
|
|
467
|
-
const
|
|
647
|
+
const viteConfig = await createViteConfig2({
|
|
468
648
|
packageRoot: PACKAGE_ROOT,
|
|
469
|
-
projectRoot
|
|
470
|
-
contentDir,
|
|
649
|
+
projectRoot,
|
|
471
650
|
configPath,
|
|
472
651
|
preset
|
|
473
652
|
});
|
|
474
653
|
console.log(chalk5.cyan("Building for production..."));
|
|
475
|
-
await build(
|
|
654
|
+
await build(viteConfig);
|
|
476
655
|
console.log(chalk5.cyan("Starting production server..."));
|
|
477
656
|
const server = await preview({
|
|
478
|
-
...
|
|
657
|
+
...viteConfig,
|
|
479
658
|
preview: { port, host: options.host }
|
|
480
659
|
});
|
|
481
660
|
server.printUrls();
|
|
@@ -484,16 +663,16 @@ var serveCommand = new Command4("serve").description("Build and start production
|
|
|
484
663
|
// src/cli/commands/start.ts
|
|
485
664
|
import chalk6 from "chalk";
|
|
486
665
|
import { Command as Command5 } from "commander";
|
|
487
|
-
var startCommand = new Command5("start").description("Start production server").option("-p, --port <port>", "Port number", "3000").option("--
|
|
488
|
-
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);
|
|
489
668
|
const port = parseInt(options.port, 10);
|
|
490
|
-
await linkContent(
|
|
669
|
+
await linkContent(projectRoot, config2);
|
|
491
670
|
console.log(chalk6.cyan("Starting production server..."));
|
|
492
671
|
const { preview } = await import("vite");
|
|
493
672
|
const { createViteConfig: createViteConfig2 } = await Promise.resolve().then(() => (init_vite_config(), exports_vite_config));
|
|
494
|
-
const
|
|
673
|
+
const viteConfig = await createViteConfig2({ packageRoot: PACKAGE_ROOT, projectRoot, configPath });
|
|
495
674
|
const server = await preview({
|
|
496
|
-
...
|
|
675
|
+
...viteConfig,
|
|
497
676
|
preview: { port, host: options.host }
|
|
498
677
|
});
|
|
499
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": "
|
|
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();
|