@raystack/chronicle 0.4.0 → 0.5.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 +175 -45
- package/package.json +5 -4
- package/src/cli/commands/build.ts +9 -4
- package/src/cli/commands/dev.ts +5 -6
- package/src/cli/commands/serve.ts +9 -4
- package/src/cli/commands/start.ts +4 -4
- package/src/cli/utils/config.ts +51 -22
- package/src/server/vite-config.ts +13 -9
- package/src/types/config.ts +86 -67
package/dist/cli/index.js
CHANGED
|
@@ -68,7 +68,7 @@ import { remarkDirectiveAdmonition, remarkMdxMermaid } from "fumadocs-core/mdx-p
|
|
|
68
68
|
import { defineConfig as defineFumadocsConfig } from "fumadocs-mdx/config";
|
|
69
69
|
import mdx from "fumadocs-mdx/vite";
|
|
70
70
|
import { nitro } from "nitro/vite";
|
|
71
|
-
import
|
|
71
|
+
import fs3 from "node:fs/promises";
|
|
72
72
|
import path4 from "node:path";
|
|
73
73
|
import remarkDirective from "remark-directive";
|
|
74
74
|
function resolveOutputDir(projectRoot, preset) {
|
|
@@ -76,18 +76,23 @@ function resolveOutputDir(projectRoot, preset) {
|
|
|
76
76
|
return path4.resolve(projectRoot, ".vercel/output");
|
|
77
77
|
return path4.resolve(projectRoot, ".output");
|
|
78
78
|
}
|
|
79
|
-
async function readChronicleConfig(projectRoot,
|
|
80
|
-
|
|
81
|
-
const filePath = path4.join(dir, "chronicle.yaml");
|
|
79
|
+
async function readChronicleConfig(projectRoot, configPath) {
|
|
80
|
+
if (configPath) {
|
|
82
81
|
try {
|
|
83
|
-
return await
|
|
84
|
-
} catch {
|
|
82
|
+
return await fs3.readFile(configPath, "utf-8");
|
|
83
|
+
} catch (error) {
|
|
84
|
+
throw new Error(`Failed to read config file '${configPath}': ${error.message}`);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
try {
|
|
88
|
+
return await fs3.readFile(path4.join(projectRoot, "chronicle.yaml"), "utf-8");
|
|
89
|
+
} catch {
|
|
90
|
+
return null;
|
|
85
91
|
}
|
|
86
|
-
return null;
|
|
87
92
|
}
|
|
88
93
|
async function createViteConfig(options) {
|
|
89
|
-
const { packageRoot, projectRoot, contentDir, preset } = options;
|
|
90
|
-
const rawConfig = await readChronicleConfig(projectRoot,
|
|
94
|
+
const { packageRoot, projectRoot, contentDir, configPath, preset } = options;
|
|
95
|
+
const rawConfig = await readChronicleConfig(projectRoot, configPath);
|
|
91
96
|
return {
|
|
92
97
|
root: packageRoot,
|
|
93
98
|
configFile: false,
|
|
@@ -188,14 +193,131 @@ import chalk2 from "chalk";
|
|
|
188
193
|
import { Command } from "commander";
|
|
189
194
|
|
|
190
195
|
// src/cli/utils/config.ts
|
|
196
|
+
import fs from "node:fs/promises";
|
|
191
197
|
import path from "node:path";
|
|
192
198
|
import chalk from "chalk";
|
|
193
199
|
import { parse } from "yaml";
|
|
194
|
-
|
|
200
|
+
|
|
201
|
+
// src/types/config.ts
|
|
202
|
+
import { z } from "zod";
|
|
203
|
+
var logoSchema = z.object({
|
|
204
|
+
light: z.string().optional(),
|
|
205
|
+
dark: z.string().optional()
|
|
206
|
+
});
|
|
207
|
+
var themeSchema = z.object({
|
|
208
|
+
name: z.enum(["default", "paper"]),
|
|
209
|
+
colors: z.record(z.string(), z.string()).optional()
|
|
210
|
+
});
|
|
211
|
+
var navLinkSchema = z.object({
|
|
212
|
+
label: z.string(),
|
|
213
|
+
href: z.string()
|
|
214
|
+
});
|
|
215
|
+
var socialLinkSchema = z.object({
|
|
216
|
+
type: z.string(),
|
|
217
|
+
href: z.string()
|
|
218
|
+
});
|
|
219
|
+
var navigationSchema = z.object({
|
|
220
|
+
links: z.array(navLinkSchema).optional(),
|
|
221
|
+
social: z.array(socialLinkSchema).optional()
|
|
222
|
+
});
|
|
223
|
+
var searchSchema = z.object({
|
|
224
|
+
enabled: z.boolean().optional(),
|
|
225
|
+
placeholder: z.string().optional()
|
|
226
|
+
});
|
|
227
|
+
var apiServerSchema = z.object({
|
|
228
|
+
url: z.string(),
|
|
229
|
+
description: z.string().optional()
|
|
230
|
+
});
|
|
231
|
+
var apiAuthSchema = z.object({
|
|
232
|
+
type: z.string(),
|
|
233
|
+
header: z.string(),
|
|
234
|
+
placeholder: z.string().optional()
|
|
235
|
+
});
|
|
236
|
+
var apiSchema = z.object({
|
|
237
|
+
name: z.string(),
|
|
238
|
+
spec: z.string(),
|
|
239
|
+
basePath: z.string(),
|
|
240
|
+
server: apiServerSchema,
|
|
241
|
+
auth: apiAuthSchema.optional()
|
|
242
|
+
});
|
|
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
|
+
var googleAnalyticsSchema = z.object({
|
|
251
|
+
measurementId: z.string()
|
|
252
|
+
});
|
|
253
|
+
var analyticsSchema = z.object({
|
|
254
|
+
enabled: z.boolean().optional(),
|
|
255
|
+
googleAnalytics: googleAnalyticsSchema.optional()
|
|
256
|
+
});
|
|
257
|
+
var chronicleConfigSchema = z.object({
|
|
258
|
+
title: z.string(),
|
|
259
|
+
description: z.string().optional(),
|
|
260
|
+
url: z.string().optional(),
|
|
261
|
+
content: z.string().optional(),
|
|
262
|
+
preset: z.string().optional(),
|
|
263
|
+
logo: logoSchema.optional(),
|
|
264
|
+
theme: themeSchema.optional(),
|
|
265
|
+
navigation: navigationSchema.optional(),
|
|
266
|
+
search: searchSchema.optional(),
|
|
267
|
+
footer: footerSchema.optional(),
|
|
268
|
+
api: z.array(apiSchema).optional(),
|
|
269
|
+
llms: llmsSchema.optional(),
|
|
270
|
+
analytics: analyticsSchema.optional()
|
|
271
|
+
});
|
|
272
|
+
// src/cli/utils/config.ts
|
|
273
|
+
function resolveConfigPath(configPath) {
|
|
274
|
+
if (configPath)
|
|
275
|
+
return path.resolve(configPath);
|
|
276
|
+
return;
|
|
277
|
+
}
|
|
278
|
+
async function readConfig(configPath) {
|
|
279
|
+
return fs.readFile(configPath, "utf-8").catch((error) => {
|
|
280
|
+
if (error.code === "ENOENT") {
|
|
281
|
+
console.log(chalk.red(`Error: chronicle.yaml not found at '${configPath}'`));
|
|
282
|
+
console.log(chalk.gray("Run 'chronicle init' to create one"));
|
|
283
|
+
} else {
|
|
284
|
+
console.log(chalk.red(`Error: Failed to read '${configPath}'`));
|
|
285
|
+
console.log(chalk.gray(error.message));
|
|
286
|
+
}
|
|
287
|
+
process.exit(1);
|
|
288
|
+
});
|
|
289
|
+
}
|
|
290
|
+
function validateConfig(raw, configPath) {
|
|
291
|
+
const parsed = parse(raw);
|
|
292
|
+
const result = chronicleConfigSchema.safeParse(parsed);
|
|
293
|
+
if (!result.success) {
|
|
294
|
+
console.log(chalk.red(`Error: Invalid chronicle.yaml at '${configPath}'`));
|
|
295
|
+
for (const issue of result.error.issues) {
|
|
296
|
+
const path2 = issue.path.join(".");
|
|
297
|
+
console.log(chalk.gray(` ${path2 ? `${path2}: ` : ""}${issue.message}`));
|
|
298
|
+
}
|
|
299
|
+
process.exit(1);
|
|
300
|
+
}
|
|
301
|
+
return result.data;
|
|
302
|
+
}
|
|
303
|
+
function resolveContentDir(config2, configPath, contentFlag) {
|
|
195
304
|
if (contentFlag)
|
|
196
305
|
return path.resolve(contentFlag);
|
|
306
|
+
if (config2.content)
|
|
307
|
+
return path.resolve(path.dirname(configPath), config2.content);
|
|
197
308
|
return path.resolve("content");
|
|
198
309
|
}
|
|
310
|
+
function resolvePreset(config2, presetFlag) {
|
|
311
|
+
return presetFlag ?? config2.preset;
|
|
312
|
+
}
|
|
313
|
+
async function loadCLIConfig(configPath, options) {
|
|
314
|
+
const resolvedConfigPath = resolveConfigPath(configPath) ?? path.join(process.cwd(), "chronicle.yaml");
|
|
315
|
+
const raw = await readConfig(resolvedConfigPath);
|
|
316
|
+
const config2 = validateConfig(raw, resolvedConfigPath);
|
|
317
|
+
const contentDir = resolveContentDir(config2, resolvedConfigPath, options?.content);
|
|
318
|
+
const preset = resolvePreset(config2, options?.preset);
|
|
319
|
+
return { config: config2, configPath: resolvedConfigPath, contentDir, preset };
|
|
320
|
+
}
|
|
199
321
|
|
|
200
322
|
// src/cli/utils/resolve.ts
|
|
201
323
|
import path2 from "path";
|
|
@@ -203,34 +325,38 @@ import { fileURLToPath } from "url";
|
|
|
203
325
|
var PACKAGE_ROOT = path2.resolve(path2.dirname(fileURLToPath(import.meta.url)), "..", "..");
|
|
204
326
|
|
|
205
327
|
// src/cli/utils/scaffold.ts
|
|
206
|
-
import
|
|
328
|
+
import fs2 from "node:fs/promises";
|
|
207
329
|
import path3 from "node:path";
|
|
208
330
|
async function linkContent(contentDir) {
|
|
209
331
|
const linkPath = path3.join(PACKAGE_ROOT, ".content");
|
|
210
332
|
const target = path3.resolve(contentDir);
|
|
211
333
|
try {
|
|
212
|
-
const existing = await
|
|
334
|
+
const existing = await fs2.readlink(linkPath);
|
|
213
335
|
if (existing === target)
|
|
214
336
|
return;
|
|
215
|
-
await
|
|
337
|
+
await fs2.unlink(linkPath);
|
|
216
338
|
} catch {}
|
|
217
|
-
await
|
|
339
|
+
await fs2.symlink(target, linkPath);
|
|
218
340
|
}
|
|
219
341
|
|
|
220
342
|
// src/cli/commands/build.ts
|
|
221
|
-
var buildCommand = new Command("build").description("Build for production").option("
|
|
222
|
-
const contentDir =
|
|
343
|
+
var buildCommand = new Command("build").description("Build for production").option("--content <path>", "Content directory").option("--config <path>", "Path to chronicle.yaml").option("--preset <preset>", "Deploy preset (vercel, cloudflare, node-server)").action(async (options) => {
|
|
344
|
+
const { contentDir, configPath, preset } = await loadCLIConfig(options.config, {
|
|
345
|
+
content: options.content,
|
|
346
|
+
preset: options.preset
|
|
347
|
+
});
|
|
223
348
|
await linkContent(contentDir);
|
|
224
349
|
console.log(chalk2.cyan("Building for production..."));
|
|
225
350
|
const { createBuilder } = await import("vite");
|
|
226
351
|
const { createViteConfig: createViteConfig2 } = await Promise.resolve().then(() => (init_vite_config(), exports_vite_config));
|
|
227
|
-
const
|
|
352
|
+
const config2 = await createViteConfig2({
|
|
228
353
|
packageRoot: PACKAGE_ROOT,
|
|
229
354
|
projectRoot: process.cwd(),
|
|
230
355
|
contentDir,
|
|
231
|
-
|
|
356
|
+
configPath,
|
|
357
|
+
preset
|
|
232
358
|
});
|
|
233
|
-
const builder = await createBuilder({ ...
|
|
359
|
+
const builder = await createBuilder({ ...config2, builder: {} });
|
|
234
360
|
await builder.buildApp();
|
|
235
361
|
console.log(chalk2.green("Build complete"));
|
|
236
362
|
console.log(chalk2.cyan("Run `chronicle start` to start the server"));
|
|
@@ -239,24 +365,24 @@ var buildCommand = new Command("build").description("Build for production").opti
|
|
|
239
365
|
// src/cli/commands/dev.ts
|
|
240
366
|
import chalk3 from "chalk";
|
|
241
367
|
import { Command as Command2 } from "commander";
|
|
242
|
-
var devCommand = new Command2("dev").description("Start development server").option("-p, --port <port>", "Port number", "3000").option("
|
|
243
|
-
const contentDir =
|
|
368
|
+
var devCommand = new Command2("dev").description("Start development server").option("-p, --port <port>", "Port number", "3000").option("--content <path>", "Content directory").option("--config <path>", "Path to chronicle.yaml").action(async (options) => {
|
|
369
|
+
const { contentDir, configPath } = await loadCLIConfig(options.config, { content: options.content });
|
|
244
370
|
const port = parseInt(options.port, 10);
|
|
245
371
|
await linkContent(contentDir);
|
|
246
372
|
console.log(chalk3.cyan("Starting dev server..."));
|
|
247
373
|
const { createServer } = await import("vite");
|
|
248
374
|
const { createViteConfig: createViteConfig2 } = await Promise.resolve().then(() => (init_vite_config(), exports_vite_config));
|
|
249
|
-
const
|
|
375
|
+
const config2 = await createViteConfig2({ packageRoot: PACKAGE_ROOT, projectRoot: process.cwd(), contentDir, configPath });
|
|
250
376
|
const server = await createServer({
|
|
251
|
-
...
|
|
252
|
-
server: { ...
|
|
377
|
+
...config2,
|
|
378
|
+
server: { ...config2.server, port }
|
|
253
379
|
});
|
|
254
380
|
await server.listen();
|
|
255
381
|
server.printUrls();
|
|
256
382
|
});
|
|
257
383
|
|
|
258
384
|
// src/cli/commands/init.ts
|
|
259
|
-
import
|
|
385
|
+
import fs4 from "node:fs";
|
|
260
386
|
import path5 from "node:path";
|
|
261
387
|
import chalk4 from "chalk";
|
|
262
388
|
import { Command as Command3 } from "commander";
|
|
@@ -280,37 +406,37 @@ This is your documentation home page.
|
|
|
280
406
|
var initCommand = new Command3("init").description("Initialize a new Chronicle project").option("-c, --content <path>", "Content directory name", "content").action((options) => {
|
|
281
407
|
const projectDir = process.cwd();
|
|
282
408
|
const contentDir = path5.join(projectDir, options.content);
|
|
283
|
-
if (!
|
|
284
|
-
|
|
409
|
+
if (!fs4.existsSync(contentDir)) {
|
|
410
|
+
fs4.mkdirSync(contentDir, { recursive: true });
|
|
285
411
|
console.log(chalk4.green("✓"), "Created", contentDir);
|
|
286
412
|
}
|
|
287
413
|
const configPath = path5.join(projectDir, "chronicle.yaml");
|
|
288
|
-
if (!
|
|
289
|
-
|
|
414
|
+
if (!fs4.existsSync(configPath)) {
|
|
415
|
+
fs4.writeFileSync(configPath, stringify(defaultConfig));
|
|
290
416
|
console.log(chalk4.green("✓"), "Created", configPath);
|
|
291
417
|
} else {
|
|
292
418
|
console.log(chalk4.yellow("⚠"), configPath, "already exists");
|
|
293
419
|
}
|
|
294
|
-
const contentFiles =
|
|
420
|
+
const contentFiles = fs4.readdirSync(contentDir);
|
|
295
421
|
if (contentFiles.length === 0) {
|
|
296
422
|
const indexPath = path5.join(contentDir, "index.mdx");
|
|
297
|
-
|
|
423
|
+
fs4.writeFileSync(indexPath, sampleMdx);
|
|
298
424
|
console.log(chalk4.green("✓"), "Created", indexPath);
|
|
299
425
|
}
|
|
300
426
|
const gitignorePath = path5.join(projectDir, ".gitignore");
|
|
301
427
|
const gitignoreEntries = ["node_modules", "dist", ".output"];
|
|
302
|
-
if (
|
|
303
|
-
const existing =
|
|
428
|
+
if (fs4.existsSync(gitignorePath)) {
|
|
429
|
+
const existing = fs4.readFileSync(gitignorePath, "utf-8");
|
|
304
430
|
const missing = gitignoreEntries.filter((e) => !existing.includes(e));
|
|
305
431
|
if (missing.length > 0) {
|
|
306
|
-
|
|
432
|
+
fs4.appendFileSync(gitignorePath, `
|
|
307
433
|
${missing.join(`
|
|
308
434
|
`)}
|
|
309
435
|
`);
|
|
310
436
|
console.log(chalk4.green("✓"), "Added", missing.join(", "), "to .gitignore");
|
|
311
437
|
}
|
|
312
438
|
} else {
|
|
313
|
-
|
|
439
|
+
fs4.writeFileSync(gitignorePath, `${gitignoreEntries.join(`
|
|
314
440
|
`)}
|
|
315
441
|
`);
|
|
316
442
|
console.log(chalk4.green("✓"), "Created .gitignore");
|
|
@@ -324,23 +450,27 @@ Run`, chalk4.cyan("chronicle dev"), "to start development server");
|
|
|
324
450
|
// src/cli/commands/serve.ts
|
|
325
451
|
import chalk5 from "chalk";
|
|
326
452
|
import { Command as Command4 } from "commander";
|
|
327
|
-
var serveCommand = new Command4("serve").description("Build and start production server").option("-p, --port <port>", "Port number", "3000").option("
|
|
328
|
-
const contentDir =
|
|
453
|
+
var serveCommand = new Command4("serve").description("Build and start production server").option("-p, --port <port>", "Port number", "3000").option("--content <path>", "Content directory").option("--config <path>", "Path to chronicle.yaml").option("--preset <preset>", "Deploy preset (vercel, cloudflare, node-server)").action(async (options) => {
|
|
454
|
+
const { contentDir, configPath, preset } = await loadCLIConfig(options.config, {
|
|
455
|
+
content: options.content,
|
|
456
|
+
preset: options.preset
|
|
457
|
+
});
|
|
329
458
|
const port = parseInt(options.port, 10);
|
|
330
459
|
await linkContent(contentDir);
|
|
331
460
|
const { build, preview } = await import("vite");
|
|
332
461
|
const { createViteConfig: createViteConfig2 } = await Promise.resolve().then(() => (init_vite_config(), exports_vite_config));
|
|
333
|
-
const
|
|
462
|
+
const config2 = await createViteConfig2({
|
|
334
463
|
packageRoot: PACKAGE_ROOT,
|
|
335
464
|
projectRoot: process.cwd(),
|
|
336
465
|
contentDir,
|
|
337
|
-
|
|
466
|
+
configPath,
|
|
467
|
+
preset
|
|
338
468
|
});
|
|
339
469
|
console.log(chalk5.cyan("Building for production..."));
|
|
340
|
-
await build(
|
|
470
|
+
await build(config2);
|
|
341
471
|
console.log(chalk5.cyan("Starting production server..."));
|
|
342
472
|
const server = await preview({
|
|
343
|
-
...
|
|
473
|
+
...config2,
|
|
344
474
|
preview: { port }
|
|
345
475
|
});
|
|
346
476
|
server.printUrls();
|
|
@@ -349,16 +479,16 @@ var serveCommand = new Command4("serve").description("Build and start production
|
|
|
349
479
|
// src/cli/commands/start.ts
|
|
350
480
|
import chalk6 from "chalk";
|
|
351
481
|
import { Command as Command5 } from "commander";
|
|
352
|
-
var startCommand = new Command5("start").description("Start production server").option("-p, --port <port>", "Port number", "3000").option("
|
|
353
|
-
const contentDir =
|
|
482
|
+
var startCommand = new Command5("start").description("Start production server").option("-p, --port <port>", "Port number", "3000").option("--content <path>", "Content directory").action(async (options) => {
|
|
483
|
+
const { contentDir, configPath } = await loadCLIConfig(undefined, { content: options.content });
|
|
354
484
|
const port = parseInt(options.port, 10);
|
|
355
485
|
await linkContent(contentDir);
|
|
356
486
|
console.log(chalk6.cyan("Starting production server..."));
|
|
357
487
|
const { preview } = await import("vite");
|
|
358
488
|
const { createViteConfig: createViteConfig2 } = await Promise.resolve().then(() => (init_vite_config(), exports_vite_config));
|
|
359
|
-
const
|
|
489
|
+
const config2 = await createViteConfig2({ packageRoot: PACKAGE_ROOT, projectRoot: process.cwd(), contentDir, configPath });
|
|
360
490
|
const server = await preview({
|
|
361
|
-
...
|
|
491
|
+
...config2,
|
|
362
492
|
preview: { port }
|
|
363
493
|
});
|
|
364
494
|
server.printUrls();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@raystack/chronicle",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.0",
|
|
4
4
|
"description": "Config-driven documentation framework",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"type": "module",
|
|
@@ -51,21 +51,22 @@
|
|
|
51
51
|
"lodash": "^4.17.23",
|
|
52
52
|
"mermaid": "^11.13.0",
|
|
53
53
|
"minisearch": "^7.2.0",
|
|
54
|
-
"nitro": "
|
|
54
|
+
"nitro": "3.0.260311-beta",
|
|
55
55
|
"openapi-types": "^12.1.3",
|
|
56
56
|
"react": "^19.0.0",
|
|
57
57
|
"react-dom": "^19.0.0",
|
|
58
58
|
"react-router": "^7.13.1",
|
|
59
59
|
"remark-directive": "^4.0.0",
|
|
60
|
-
"remark-parse": "^11.0.0",
|
|
61
60
|
"remark-frontmatter": "^5.0.0",
|
|
62
61
|
"remark-gfm": "^4.0.1",
|
|
63
62
|
"remark-mdx-frontmatter": "^5.2.0",
|
|
63
|
+
"remark-parse": "^11.0.0",
|
|
64
64
|
"satori": "^0.25.0",
|
|
65
65
|
"slugify": "^1.6.6",
|
|
66
66
|
"unified": "^11.0.5",
|
|
67
67
|
"unist-util-visit": "^5.1.0",
|
|
68
68
|
"vite": "^8.0.0",
|
|
69
|
-
"yaml": "^2.8.2"
|
|
69
|
+
"yaml": "^2.8.2",
|
|
70
|
+
"zod": "^4.3.6"
|
|
70
71
|
}
|
|
71
72
|
}
|
|
@@ -1,18 +1,22 @@
|
|
|
1
1
|
import chalk from 'chalk';
|
|
2
2
|
import { Command } from 'commander';
|
|
3
|
-
import {
|
|
3
|
+
import { loadCLIConfig } from '@/cli/utils/config';
|
|
4
4
|
import { PACKAGE_ROOT } from '@/cli/utils/resolve';
|
|
5
5
|
import { linkContent } from '@/cli/utils/scaffold';
|
|
6
6
|
|
|
7
7
|
export const buildCommand = new Command('build')
|
|
8
8
|
.description('Build for production')
|
|
9
|
-
.option('
|
|
9
|
+
.option('--content <path>', 'Content directory')
|
|
10
|
+
.option('--config <path>', 'Path to chronicle.yaml')
|
|
10
11
|
.option(
|
|
11
12
|
'--preset <preset>',
|
|
12
13
|
'Deploy preset (vercel, cloudflare, node-server)'
|
|
13
14
|
)
|
|
14
15
|
.action(async options => {
|
|
15
|
-
const contentDir =
|
|
16
|
+
const { contentDir, configPath, preset } = await loadCLIConfig(options.config, {
|
|
17
|
+
content: options.content,
|
|
18
|
+
preset: options.preset,
|
|
19
|
+
});
|
|
16
20
|
await linkContent(contentDir);
|
|
17
21
|
|
|
18
22
|
console.log(chalk.cyan('Building for production...'));
|
|
@@ -24,7 +28,8 @@ export const buildCommand = new Command('build')
|
|
|
24
28
|
packageRoot: PACKAGE_ROOT,
|
|
25
29
|
projectRoot: process.cwd(),
|
|
26
30
|
contentDir,
|
|
27
|
-
|
|
31
|
+
configPath,
|
|
32
|
+
preset
|
|
28
33
|
});
|
|
29
34
|
|
|
30
35
|
const builder = await createBuilder({ ...config, builder: {} });
|
package/src/cli/commands/dev.ts
CHANGED
|
@@ -1,17 +1,16 @@
|
|
|
1
|
-
import fs from 'node:fs';
|
|
2
|
-
import path from 'node:path';
|
|
3
1
|
import chalk from 'chalk';
|
|
4
2
|
import { Command } from 'commander';
|
|
5
|
-
import {
|
|
3
|
+
import { loadCLIConfig } from '@/cli/utils/config';
|
|
6
4
|
import { PACKAGE_ROOT } from '@/cli/utils/resolve';
|
|
7
5
|
import { linkContent } from '@/cli/utils/scaffold';
|
|
8
6
|
|
|
9
7
|
export const devCommand = new Command('dev')
|
|
10
8
|
.description('Start development server')
|
|
11
9
|
.option('-p, --port <port>', 'Port number', '3000')
|
|
12
|
-
.option('
|
|
10
|
+
.option('--content <path>', 'Content directory')
|
|
11
|
+
.option('--config <path>', 'Path to chronicle.yaml')
|
|
13
12
|
.action(async options => {
|
|
14
|
-
const contentDir =
|
|
13
|
+
const { contentDir, configPath } = await loadCLIConfig(options.config, { content: options.content });
|
|
15
14
|
const port = parseInt(options.port, 10);
|
|
16
15
|
|
|
17
16
|
await linkContent(contentDir);
|
|
@@ -21,7 +20,7 @@ export const devCommand = new Command('dev')
|
|
|
21
20
|
const { createServer } = await import('vite');
|
|
22
21
|
const { createViteConfig } = await import('@/server/vite-config');
|
|
23
22
|
|
|
24
|
-
const config = await createViteConfig({ packageRoot: PACKAGE_ROOT, projectRoot: process.cwd(), contentDir });
|
|
23
|
+
const config = await createViteConfig({ packageRoot: PACKAGE_ROOT, projectRoot: process.cwd(), contentDir, configPath });
|
|
25
24
|
const server = await createServer({
|
|
26
25
|
...config,
|
|
27
26
|
server: { ...config.server, port }
|
|
@@ -1,19 +1,23 @@
|
|
|
1
1
|
import chalk from 'chalk';
|
|
2
2
|
import { Command } from 'commander';
|
|
3
|
-
import {
|
|
3
|
+
import { loadCLIConfig } from '@/cli/utils/config';
|
|
4
4
|
import { PACKAGE_ROOT } from '@/cli/utils/resolve';
|
|
5
5
|
import { linkContent } from '@/cli/utils/scaffold';
|
|
6
6
|
|
|
7
7
|
export const serveCommand = new Command('serve')
|
|
8
8
|
.description('Build and start production server')
|
|
9
9
|
.option('-p, --port <port>', 'Port number', '3000')
|
|
10
|
-
.option('
|
|
10
|
+
.option('--content <path>', 'Content directory')
|
|
11
|
+
.option('--config <path>', 'Path to chronicle.yaml')
|
|
11
12
|
.option(
|
|
12
13
|
'--preset <preset>',
|
|
13
14
|
'Deploy preset (vercel, cloudflare, node-server)'
|
|
14
15
|
)
|
|
15
16
|
.action(async options => {
|
|
16
|
-
const contentDir =
|
|
17
|
+
const { contentDir, configPath, preset } = await loadCLIConfig(options.config, {
|
|
18
|
+
content: options.content,
|
|
19
|
+
preset: options.preset,
|
|
20
|
+
});
|
|
17
21
|
const port = parseInt(options.port, 10);
|
|
18
22
|
await linkContent(contentDir);
|
|
19
23
|
|
|
@@ -24,7 +28,8 @@ export const serveCommand = new Command('serve')
|
|
|
24
28
|
packageRoot: PACKAGE_ROOT,
|
|
25
29
|
projectRoot: process.cwd(),
|
|
26
30
|
contentDir,
|
|
27
|
-
|
|
31
|
+
configPath,
|
|
32
|
+
preset
|
|
28
33
|
});
|
|
29
34
|
|
|
30
35
|
console.log(chalk.cyan('Building for production...'));
|
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
import chalk from 'chalk';
|
|
2
2
|
import { Command } from 'commander';
|
|
3
|
-
import {
|
|
3
|
+
import { loadCLIConfig } from '@/cli/utils/config';
|
|
4
4
|
import { PACKAGE_ROOT } from '@/cli/utils/resolve';
|
|
5
5
|
import { linkContent } from '@/cli/utils/scaffold';
|
|
6
6
|
|
|
7
7
|
export const startCommand = new Command('start')
|
|
8
8
|
.description('Start production server')
|
|
9
9
|
.option('-p, --port <port>', 'Port number', '3000')
|
|
10
|
-
.option('
|
|
10
|
+
.option('--content <path>', 'Content directory')
|
|
11
11
|
.action(async options => {
|
|
12
|
-
const contentDir =
|
|
12
|
+
const { contentDir, configPath } = await loadCLIConfig(undefined, { content: options.content });
|
|
13
13
|
const port = parseInt(options.port, 10);
|
|
14
14
|
await linkContent(contentDir);
|
|
15
15
|
|
|
@@ -18,7 +18,7 @@ export const startCommand = new Command('start')
|
|
|
18
18
|
const { preview } = await import('vite');
|
|
19
19
|
const { createViteConfig } = await import('@/server/vite-config');
|
|
20
20
|
|
|
21
|
-
const config = await createViteConfig({ packageRoot: PACKAGE_ROOT, projectRoot: process.cwd(), contentDir });
|
|
21
|
+
const config = await createViteConfig({ packageRoot: PACKAGE_ROOT, projectRoot: process.cwd(), contentDir, configPath });
|
|
22
22
|
const server = await preview({
|
|
23
23
|
...config,
|
|
24
24
|
preview: { port }
|
package/src/cli/utils/config.ts
CHANGED
|
@@ -1,42 +1,71 @@
|
|
|
1
|
-
import fs from 'node:fs';
|
|
1
|
+
import fs from 'node:fs/promises';
|
|
2
2
|
import path from 'node:path';
|
|
3
3
|
import chalk from 'chalk';
|
|
4
4
|
import { parse } from 'yaml';
|
|
5
|
-
import type
|
|
5
|
+
import { chronicleConfigSchema, type ChronicleConfig } from '@/types';
|
|
6
6
|
|
|
7
7
|
export interface CLIConfig {
|
|
8
8
|
config: ChronicleConfig;
|
|
9
9
|
configPath: string;
|
|
10
10
|
contentDir: string;
|
|
11
|
+
preset?: string;
|
|
11
12
|
}
|
|
12
13
|
|
|
13
|
-
export function
|
|
14
|
-
if (
|
|
15
|
-
return
|
|
14
|
+
export function resolveConfigPath(configPath?: string): string | undefined {
|
|
15
|
+
if (configPath) return path.resolve(configPath);
|
|
16
|
+
return undefined;
|
|
16
17
|
}
|
|
17
18
|
|
|
18
|
-
function
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
19
|
+
async function readConfig(configPath: string): Promise<string> {
|
|
20
|
+
return fs.readFile(configPath, 'utf-8').catch((error: NodeJS.ErrnoException) => {
|
|
21
|
+
if (error.code === 'ENOENT') {
|
|
22
|
+
console.log(chalk.red(`Error: chronicle.yaml not found at '${configPath}'`));
|
|
23
|
+
console.log(chalk.gray("Run 'chronicle init' to create one"));
|
|
24
|
+
} else {
|
|
25
|
+
console.log(chalk.red(`Error: Failed to read '${configPath}'`));
|
|
26
|
+
console.log(chalk.gray(error.message));
|
|
27
|
+
}
|
|
28
|
+
process.exit(1);
|
|
29
|
+
});
|
|
24
30
|
}
|
|
25
31
|
|
|
26
|
-
|
|
27
|
-
const
|
|
32
|
+
function validateConfig(raw: string, configPath: string): ChronicleConfig {
|
|
33
|
+
const parsed = parse(raw);
|
|
34
|
+
const result = chronicleConfigSchema.safeParse(parsed);
|
|
28
35
|
|
|
29
|
-
if (!
|
|
30
|
-
console.log(
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
)
|
|
34
|
-
|
|
35
|
-
console.log(chalk.gray("Run 'chronicle init' to create one"));
|
|
36
|
+
if (!result.success) {
|
|
37
|
+
console.log(chalk.red(`Error: Invalid chronicle.yaml at '${configPath}'`));
|
|
38
|
+
for (const issue of result.error.issues) {
|
|
39
|
+
const path = issue.path.join('.');
|
|
40
|
+
console.log(chalk.gray(` ${path ? `${path}: ` : ''}${issue.message}`));
|
|
41
|
+
}
|
|
36
42
|
process.exit(1);
|
|
37
43
|
}
|
|
38
44
|
|
|
39
|
-
|
|
45
|
+
return result.data;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export function resolveContentDir(config: ChronicleConfig, configPath: string, contentFlag?: string): string {
|
|
49
|
+
if (contentFlag) return path.resolve(contentFlag);
|
|
50
|
+
if (config.content) return path.resolve(path.dirname(configPath), config.content);
|
|
51
|
+
return path.resolve('content');
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export function resolvePreset(config: ChronicleConfig, presetFlag?: string): string | undefined {
|
|
55
|
+
return presetFlag ?? config.preset;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export async function loadCLIConfig(
|
|
59
|
+
configPath?: string,
|
|
60
|
+
options?: { content?: string; preset?: string }
|
|
61
|
+
): Promise<CLIConfig> {
|
|
62
|
+
const resolvedConfigPath = resolveConfigPath(configPath)
|
|
63
|
+
?? path.join(process.cwd(), 'chronicle.yaml');
|
|
64
|
+
|
|
65
|
+
const raw = await readConfig(resolvedConfigPath);
|
|
66
|
+
const config = validateConfig(raw, resolvedConfigPath);
|
|
67
|
+
const contentDir = resolveContentDir(config, resolvedConfigPath, options?.content);
|
|
68
|
+
const preset = resolvePreset(config, options?.preset);
|
|
40
69
|
|
|
41
|
-
return { config, configPath, contentDir };
|
|
70
|
+
return { config, configPath: resolvedConfigPath, contentDir, preset };
|
|
42
71
|
}
|
|
@@ -19,26 +19,30 @@ export interface ViteConfigOptions {
|
|
|
19
19
|
packageRoot: string;
|
|
20
20
|
projectRoot: string;
|
|
21
21
|
contentDir: string;
|
|
22
|
+
configPath?: string;
|
|
22
23
|
preset?: string;
|
|
23
24
|
}
|
|
24
25
|
|
|
25
|
-
async function readChronicleConfig(projectRoot: string,
|
|
26
|
-
|
|
27
|
-
const filePath = path.join(dir, 'chronicle.yaml');
|
|
26
|
+
async function readChronicleConfig(projectRoot: string, configPath?: string): Promise<string | null> {
|
|
27
|
+
if (configPath) {
|
|
28
28
|
try {
|
|
29
|
-
return await fs.readFile(
|
|
30
|
-
} catch {
|
|
31
|
-
|
|
29
|
+
return await fs.readFile(configPath, 'utf-8');
|
|
30
|
+
} catch (error) {
|
|
31
|
+
throw new Error(`Failed to read config file '${configPath}': ${(error as Error).message}`);
|
|
32
32
|
}
|
|
33
33
|
}
|
|
34
|
-
|
|
34
|
+
try {
|
|
35
|
+
return await fs.readFile(path.join(projectRoot, 'chronicle.yaml'), 'utf-8');
|
|
36
|
+
} catch {
|
|
37
|
+
return null;
|
|
38
|
+
}
|
|
35
39
|
}
|
|
36
40
|
|
|
37
41
|
export async function createViteConfig(
|
|
38
42
|
options: ViteConfigOptions
|
|
39
43
|
): Promise<InlineConfig> {
|
|
40
|
-
const { packageRoot, projectRoot, contentDir, preset } = options;
|
|
41
|
-
const rawConfig = await readChronicleConfig(projectRoot,
|
|
44
|
+
const { packageRoot, projectRoot, contentDir, configPath, preset } = options;
|
|
45
|
+
const rawConfig = await readChronicleConfig(projectRoot, configPath);
|
|
42
46
|
|
|
43
47
|
return {
|
|
44
48
|
root: packageRoot,
|
package/src/types/config.ts
CHANGED
|
@@ -1,80 +1,99 @@
|
|
|
1
|
-
|
|
2
|
-
title: string
|
|
3
|
-
description?: string
|
|
4
|
-
url?: string
|
|
5
|
-
logo?: LogoConfig
|
|
6
|
-
theme?: ThemeConfig
|
|
7
|
-
navigation?: NavigationConfig
|
|
8
|
-
search?: SearchConfig
|
|
9
|
-
footer?: FooterConfig
|
|
10
|
-
api?: ApiConfig[]
|
|
11
|
-
llms?: LlmsConfig
|
|
12
|
-
analytics?: AnalyticsConfig
|
|
13
|
-
}
|
|
1
|
+
import { z } from 'zod'
|
|
14
2
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
3
|
+
const logoSchema = z.object({
|
|
4
|
+
light: z.string().optional(),
|
|
5
|
+
dark: z.string().optional(),
|
|
6
|
+
})
|
|
18
7
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
}
|
|
8
|
+
const themeSchema = z.object({
|
|
9
|
+
name: z.enum(['default', 'paper']),
|
|
10
|
+
colors: z.record(z.string(), z.string()).optional(),
|
|
11
|
+
})
|
|
23
12
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
13
|
+
const navLinkSchema = z.object({
|
|
14
|
+
label: z.string(),
|
|
15
|
+
href: z.string(),
|
|
16
|
+
})
|
|
27
17
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
server: ApiServerConfig
|
|
33
|
-
auth?: ApiAuthConfig
|
|
34
|
-
}
|
|
18
|
+
const socialLinkSchema = z.object({
|
|
19
|
+
type: z.string(),
|
|
20
|
+
href: z.string(),
|
|
21
|
+
})
|
|
35
22
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
}
|
|
23
|
+
const navigationSchema = z.object({
|
|
24
|
+
links: z.array(navLinkSchema).optional(),
|
|
25
|
+
social: z.array(socialLinkSchema).optional(),
|
|
26
|
+
})
|
|
40
27
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
}
|
|
28
|
+
const searchSchema = z.object({
|
|
29
|
+
enabled: z.boolean().optional(),
|
|
30
|
+
placeholder: z.string().optional(),
|
|
31
|
+
})
|
|
46
32
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
}
|
|
33
|
+
const apiServerSchema = z.object({
|
|
34
|
+
url: z.string(),
|
|
35
|
+
description: z.string().optional(),
|
|
36
|
+
})
|
|
51
37
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
38
|
+
const apiAuthSchema = z.object({
|
|
39
|
+
type: z.string(),
|
|
40
|
+
header: z.string(),
|
|
41
|
+
placeholder: z.string().optional(),
|
|
42
|
+
})
|
|
56
43
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
44
|
+
const apiSchema = z.object({
|
|
45
|
+
name: z.string(),
|
|
46
|
+
spec: z.string(),
|
|
47
|
+
basePath: z.string(),
|
|
48
|
+
server: apiServerSchema,
|
|
49
|
+
auth: apiAuthSchema.optional(),
|
|
50
|
+
})
|
|
61
51
|
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
}
|
|
52
|
+
const footerSchema = z.object({
|
|
53
|
+
copyright: z.string().optional(),
|
|
54
|
+
links: z.array(navLinkSchema).optional(),
|
|
55
|
+
})
|
|
66
56
|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
}
|
|
57
|
+
const llmsSchema = z.object({
|
|
58
|
+
enabled: z.boolean().optional(),
|
|
59
|
+
})
|
|
71
60
|
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
}
|
|
61
|
+
const googleAnalyticsSchema = z.object({
|
|
62
|
+
measurementId: z.string(),
|
|
63
|
+
})
|
|
76
64
|
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
}
|
|
65
|
+
const analyticsSchema = z.object({
|
|
66
|
+
enabled: z.boolean().optional(),
|
|
67
|
+
googleAnalytics: googleAnalyticsSchema.optional(),
|
|
68
|
+
})
|
|
69
|
+
|
|
70
|
+
export const chronicleConfigSchema = z.object({
|
|
71
|
+
title: z.string(),
|
|
72
|
+
description: z.string().optional(),
|
|
73
|
+
url: z.string().optional(),
|
|
74
|
+
content: z.string().optional(),
|
|
75
|
+
preset: z.string().optional(),
|
|
76
|
+
logo: logoSchema.optional(),
|
|
77
|
+
theme: themeSchema.optional(),
|
|
78
|
+
navigation: navigationSchema.optional(),
|
|
79
|
+
search: searchSchema.optional(),
|
|
80
|
+
footer: footerSchema.optional(),
|
|
81
|
+
api: z.array(apiSchema).optional(),
|
|
82
|
+
llms: llmsSchema.optional(),
|
|
83
|
+
analytics: analyticsSchema.optional(),
|
|
84
|
+
})
|
|
85
|
+
|
|
86
|
+
export type ChronicleConfig = z.infer<typeof chronicleConfigSchema>
|
|
87
|
+
export type LogoConfig = z.infer<typeof logoSchema>
|
|
88
|
+
export type ThemeConfig = z.infer<typeof themeSchema>
|
|
89
|
+
export type NavigationConfig = z.infer<typeof navigationSchema>
|
|
90
|
+
export type NavLink = z.infer<typeof navLinkSchema>
|
|
91
|
+
export type SocialLink = z.infer<typeof socialLinkSchema>
|
|
92
|
+
export type SearchConfig = z.infer<typeof searchSchema>
|
|
93
|
+
export type ApiConfig = z.infer<typeof apiSchema>
|
|
94
|
+
export type ApiServerConfig = z.infer<typeof apiServerSchema>
|
|
95
|
+
export type ApiAuthConfig = z.infer<typeof apiAuthSchema>
|
|
96
|
+
export type FooterConfig = z.infer<typeof footerSchema>
|
|
97
|
+
export type LlmsConfig = z.infer<typeof llmsSchema>
|
|
98
|
+
export type AnalyticsConfig = z.infer<typeof analyticsSchema>
|
|
99
|
+
export type GoogleAnalyticsConfig = z.infer<typeof googleAnalyticsSchema>
|