@raystack/chronicle 0.1.3 → 0.2.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 +335 -89
- package/package.json +4 -3
- package/src/cli/commands/build.ts +20 -13
- package/src/cli/commands/dev.ts +19 -12
- package/src/cli/commands/init.ts +114 -9
- package/src/cli/commands/serve.ts +21 -14
- package/src/cli/commands/start.ts +19 -12
- package/src/cli/utils/config.ts +12 -4
- package/src/cli/utils/index.ts +1 -0
- package/src/cli/utils/resolve.ts +6 -0
- package/src/cli/utils/scaffold.ts +137 -0
- package/src/components/ui/search.module.css +7 -0
- package/src/components/ui/search.tsx +7 -3
- package/src/lib/config.ts +21 -3
- package/src/lib/source.ts +1 -1
- package/tsconfig.json +30 -0
package/dist/cli/index.js
CHANGED
|
@@ -9044,8 +9044,9 @@ var {
|
|
|
9044
9044
|
} = import__.default;
|
|
9045
9045
|
|
|
9046
9046
|
// src/cli/commands/init.ts
|
|
9047
|
-
import
|
|
9048
|
-
import
|
|
9047
|
+
import { execSync as execSync2 } from "child_process";
|
|
9048
|
+
import fs3 from "fs";
|
|
9049
|
+
import path4 from "path";
|
|
9049
9050
|
|
|
9050
9051
|
// ../../node_modules/.bun/chalk@5.6.2/node_modules/chalk/source/vendor/ansi-styles/index.js
|
|
9051
9052
|
var ANSI_BACKGROUND_OFFSET = 10;
|
|
@@ -9582,6 +9583,156 @@ var $stringify = publicApi.stringify;
|
|
|
9582
9583
|
var $visit = visit.visit;
|
|
9583
9584
|
var $visitAsync = visit.visitAsync;
|
|
9584
9585
|
|
|
9586
|
+
// src/cli/utils/config.ts
|
|
9587
|
+
import fs from "fs";
|
|
9588
|
+
import path from "path";
|
|
9589
|
+
function resolveConfigPath(contentDir) {
|
|
9590
|
+
const cwdPath = path.join(process.cwd(), "chronicle.yaml");
|
|
9591
|
+
if (fs.existsSync(cwdPath))
|
|
9592
|
+
return cwdPath;
|
|
9593
|
+
const contentPath = path.join(contentDir, "chronicle.yaml");
|
|
9594
|
+
if (fs.existsSync(contentPath))
|
|
9595
|
+
return contentPath;
|
|
9596
|
+
return null;
|
|
9597
|
+
}
|
|
9598
|
+
function loadCLIConfig(contentDir) {
|
|
9599
|
+
const configPath = resolveConfigPath(contentDir);
|
|
9600
|
+
if (!configPath) {
|
|
9601
|
+
console.log(source_default.red(`Error: chronicle.yaml not found in '${process.cwd()}' or '${contentDir}'`));
|
|
9602
|
+
console.log(source_default.gray(`Run 'chronicle init' to create one`));
|
|
9603
|
+
process.exit(1);
|
|
9604
|
+
}
|
|
9605
|
+
const config = $parse(fs.readFileSync(configPath, "utf-8"));
|
|
9606
|
+
return {
|
|
9607
|
+
config,
|
|
9608
|
+
configPath,
|
|
9609
|
+
contentDir
|
|
9610
|
+
};
|
|
9611
|
+
}
|
|
9612
|
+
// src/cli/utils/process.ts
|
|
9613
|
+
function attachLifecycleHandlers(child) {
|
|
9614
|
+
child.on("close", (code) => process.exit(code ?? 0));
|
|
9615
|
+
process.on("SIGINT", () => child.kill("SIGINT"));
|
|
9616
|
+
process.on("SIGTERM", () => child.kill("SIGTERM"));
|
|
9617
|
+
}
|
|
9618
|
+
// src/cli/utils/scaffold.ts
|
|
9619
|
+
import { execSync } from "child_process";
|
|
9620
|
+
import { createRequire as createRequire2 } from "module";
|
|
9621
|
+
import fs2 from "fs";
|
|
9622
|
+
import path3 from "path";
|
|
9623
|
+
|
|
9624
|
+
// src/cli/utils/resolve.ts
|
|
9625
|
+
import path2 from "path";
|
|
9626
|
+
import { fileURLToPath } from "url";
|
|
9627
|
+
var PACKAGE_ROOT = path2.resolve(path2.dirname(fileURLToPath(import.meta.url)), "..", "..");
|
|
9628
|
+
|
|
9629
|
+
// src/cli/utils/scaffold.ts
|
|
9630
|
+
var COPY_FILES = ["src", "source.config.ts", "tsconfig.json"];
|
|
9631
|
+
function copyRecursive(src, dest) {
|
|
9632
|
+
const stat = fs2.statSync(src);
|
|
9633
|
+
if (stat.isDirectory()) {
|
|
9634
|
+
fs2.mkdirSync(dest, { recursive: true });
|
|
9635
|
+
for (const entry of fs2.readdirSync(src)) {
|
|
9636
|
+
copyRecursive(path3.join(src, entry), path3.join(dest, entry));
|
|
9637
|
+
}
|
|
9638
|
+
} else {
|
|
9639
|
+
fs2.copyFileSync(src, dest);
|
|
9640
|
+
}
|
|
9641
|
+
}
|
|
9642
|
+
function ensureRemoved(targetPath) {
|
|
9643
|
+
try {
|
|
9644
|
+
fs2.lstatSync(targetPath);
|
|
9645
|
+
fs2.rmSync(targetPath, { recursive: true, force: true });
|
|
9646
|
+
} catch {}
|
|
9647
|
+
}
|
|
9648
|
+
function detectPackageManager() {
|
|
9649
|
+
if (process.env.npm_config_user_agent) {
|
|
9650
|
+
return process.env.npm_config_user_agent.split("/")[0];
|
|
9651
|
+
}
|
|
9652
|
+
const cwd = process.cwd();
|
|
9653
|
+
if (fs2.existsSync(path3.join(cwd, "bun.lock")) || fs2.existsSync(path3.join(cwd, "bun.lockb")))
|
|
9654
|
+
return "bun";
|
|
9655
|
+
if (fs2.existsSync(path3.join(cwd, "pnpm-lock.yaml")))
|
|
9656
|
+
return "pnpm";
|
|
9657
|
+
if (fs2.existsSync(path3.join(cwd, "yarn.lock")))
|
|
9658
|
+
return "yarn";
|
|
9659
|
+
return "npm";
|
|
9660
|
+
}
|
|
9661
|
+
function generateNextConfig(scaffoldPath) {
|
|
9662
|
+
const config = `import { createMDX } from 'fumadocs-mdx/next'
|
|
9663
|
+
|
|
9664
|
+
const withMDX = createMDX()
|
|
9665
|
+
|
|
9666
|
+
/** @type {import('next').NextConfig} */
|
|
9667
|
+
const nextConfig = {
|
|
9668
|
+
reactStrictMode: true,
|
|
9669
|
+
}
|
|
9670
|
+
|
|
9671
|
+
export default withMDX(nextConfig)
|
|
9672
|
+
`;
|
|
9673
|
+
fs2.writeFileSync(path3.join(scaffoldPath, "next.config.mjs"), config);
|
|
9674
|
+
}
|
|
9675
|
+
function createPackageJson() {
|
|
9676
|
+
return {
|
|
9677
|
+
name: "chronicle-docs",
|
|
9678
|
+
private: true,
|
|
9679
|
+
dependencies: {
|
|
9680
|
+
"@raystack/chronicle": `^${getChronicleVersion()}`
|
|
9681
|
+
},
|
|
9682
|
+
devDependencies: {
|
|
9683
|
+
"@raystack/tools-config": "0.56.0",
|
|
9684
|
+
"openapi-types": "^12.1.3",
|
|
9685
|
+
typescript: "5.9.3",
|
|
9686
|
+
"@types/react": "^19.2.10",
|
|
9687
|
+
"@types/node": "^25.1.0"
|
|
9688
|
+
}
|
|
9689
|
+
};
|
|
9690
|
+
}
|
|
9691
|
+
function ensureDeps() {
|
|
9692
|
+
const cwd = process.cwd();
|
|
9693
|
+
const cwdPkgJson = path3.join(cwd, "package.json");
|
|
9694
|
+
const cwdNodeModules = path3.join(cwd, "node_modules");
|
|
9695
|
+
if (fs2.existsSync(cwdPkgJson) && fs2.existsSync(cwdNodeModules)) {
|
|
9696
|
+
return;
|
|
9697
|
+
}
|
|
9698
|
+
if (!fs2.existsSync(cwdPkgJson)) {
|
|
9699
|
+
fs2.writeFileSync(cwdPkgJson, JSON.stringify(createPackageJson(), null, 2) + `
|
|
9700
|
+
`);
|
|
9701
|
+
}
|
|
9702
|
+
if (!fs2.existsSync(cwdNodeModules)) {
|
|
9703
|
+
const pm = detectPackageManager();
|
|
9704
|
+
console.log(source_default.cyan(`Installing dependencies with ${pm}...`));
|
|
9705
|
+
execSync(`${pm} install`, { cwd, stdio: "inherit" });
|
|
9706
|
+
}
|
|
9707
|
+
}
|
|
9708
|
+
function getChronicleVersion() {
|
|
9709
|
+
const pkgPath = path3.join(PACKAGE_ROOT, "package.json");
|
|
9710
|
+
const pkg = JSON.parse(fs2.readFileSync(pkgPath, "utf-8"));
|
|
9711
|
+
return pkg.version;
|
|
9712
|
+
}
|
|
9713
|
+
function resolveNextCli() {
|
|
9714
|
+
const chronicleRequire = createRequire2(path3.join(PACKAGE_ROOT, "package.json"));
|
|
9715
|
+
return chronicleRequire.resolve("next/dist/bin/next");
|
|
9716
|
+
}
|
|
9717
|
+
function scaffoldDir(contentDir) {
|
|
9718
|
+
const scaffoldPath = path3.join(process.cwd(), ".chronicle");
|
|
9719
|
+
if (!fs2.existsSync(scaffoldPath)) {
|
|
9720
|
+
fs2.mkdirSync(scaffoldPath, { recursive: true });
|
|
9721
|
+
}
|
|
9722
|
+
for (const name of COPY_FILES) {
|
|
9723
|
+
const src = path3.join(PACKAGE_ROOT, name);
|
|
9724
|
+
const dest = path3.join(scaffoldPath, name);
|
|
9725
|
+
ensureRemoved(dest);
|
|
9726
|
+
copyRecursive(src, dest);
|
|
9727
|
+
}
|
|
9728
|
+
generateNextConfig(scaffoldPath);
|
|
9729
|
+
const contentLink = path3.join(scaffoldPath, "content");
|
|
9730
|
+
ensureRemoved(contentLink);
|
|
9731
|
+
fs2.symlinkSync(path3.resolve(contentDir), contentLink);
|
|
9732
|
+
ensureDeps();
|
|
9733
|
+
console.log(source_default.gray(`Scaffold: ${scaffoldPath}`));
|
|
9734
|
+
return scaffoldPath;
|
|
9735
|
+
}
|
|
9585
9736
|
// src/cli/commands/init.ts
|
|
9586
9737
|
function createConfig() {
|
|
9587
9738
|
return {
|
|
@@ -9591,6 +9742,28 @@ function createConfig() {
|
|
|
9591
9742
|
search: { enabled: true, placeholder: "Search documentation..." }
|
|
9592
9743
|
};
|
|
9593
9744
|
}
|
|
9745
|
+
function createPackageJson2(name) {
|
|
9746
|
+
return {
|
|
9747
|
+
name,
|
|
9748
|
+
private: true,
|
|
9749
|
+
type: "module",
|
|
9750
|
+
scripts: {
|
|
9751
|
+
dev: "chronicle dev",
|
|
9752
|
+
build: "chronicle build",
|
|
9753
|
+
start: "chronicle start"
|
|
9754
|
+
},
|
|
9755
|
+
dependencies: {
|
|
9756
|
+
"@raystack/chronicle": `^${getChronicleVersion()}`
|
|
9757
|
+
},
|
|
9758
|
+
devDependencies: {
|
|
9759
|
+
"@raystack/tools-config": "0.56.0",
|
|
9760
|
+
"openapi-types": "^12.1.3",
|
|
9761
|
+
typescript: "5.9.3",
|
|
9762
|
+
"@types/react": "^19.2.10",
|
|
9763
|
+
"@types/node": "^25.1.0"
|
|
9764
|
+
}
|
|
9765
|
+
};
|
|
9766
|
+
}
|
|
9594
9767
|
var sampleMdx = `---
|
|
9595
9768
|
title: Welcome
|
|
9596
9769
|
description: Getting started with your documentation
|
|
@@ -9601,79 +9774,128 @@ order: 1
|
|
|
9601
9774
|
|
|
9602
9775
|
This is your documentation home page.
|
|
9603
9776
|
`;
|
|
9604
|
-
var initCommand = new Command("init").description("Initialize a new Chronicle project").option("-
|
|
9605
|
-
const
|
|
9606
|
-
|
|
9607
|
-
|
|
9777
|
+
var initCommand = new Command("init").description("Initialize a new Chronicle project").option("-c, --content <path>", "Content directory name", "content").action((options) => {
|
|
9778
|
+
const projectDir = process.cwd();
|
|
9779
|
+
const dirName = path4.basename(projectDir) || "docs";
|
|
9780
|
+
const contentDir = path4.join(projectDir, options.content);
|
|
9781
|
+
if (!fs3.existsSync(contentDir)) {
|
|
9782
|
+
fs3.mkdirSync(contentDir, { recursive: true });
|
|
9608
9783
|
console.log(source_default.green("✓"), "Created", contentDir);
|
|
9609
9784
|
}
|
|
9610
|
-
const
|
|
9611
|
-
if (!
|
|
9612
|
-
|
|
9785
|
+
const packageJsonPath = path4.join(projectDir, "package.json");
|
|
9786
|
+
if (!fs3.existsSync(packageJsonPath)) {
|
|
9787
|
+
fs3.writeFileSync(packageJsonPath, JSON.stringify(createPackageJson2(dirName), null, 2) + `
|
|
9788
|
+
`);
|
|
9789
|
+
console.log(source_default.green("✓"), "Created", packageJsonPath);
|
|
9790
|
+
} else {
|
|
9791
|
+
const existing = JSON.parse(fs3.readFileSync(packageJsonPath, "utf-8"));
|
|
9792
|
+
const template = createPackageJson2(dirName);
|
|
9793
|
+
let updated = false;
|
|
9794
|
+
if (existing.type !== "module") {
|
|
9795
|
+
existing.type = "module";
|
|
9796
|
+
updated = true;
|
|
9797
|
+
}
|
|
9798
|
+
if (!existing.scripts)
|
|
9799
|
+
existing.scripts = {};
|
|
9800
|
+
for (const [key, value] of Object.entries(template.scripts)) {
|
|
9801
|
+
if (!existing.scripts[key]) {
|
|
9802
|
+
existing.scripts[key] = value;
|
|
9803
|
+
updated = true;
|
|
9804
|
+
}
|
|
9805
|
+
}
|
|
9806
|
+
if (!existing.dependencies)
|
|
9807
|
+
existing.dependencies = {};
|
|
9808
|
+
for (const [key, value] of Object.entries(template.dependencies)) {
|
|
9809
|
+
if (!existing.dependencies[key]) {
|
|
9810
|
+
existing.dependencies[key] = value;
|
|
9811
|
+
updated = true;
|
|
9812
|
+
}
|
|
9813
|
+
}
|
|
9814
|
+
if (!existing.devDependencies)
|
|
9815
|
+
existing.devDependencies = {};
|
|
9816
|
+
for (const [key, value] of Object.entries(template.devDependencies)) {
|
|
9817
|
+
if (!existing.devDependencies[key]) {
|
|
9818
|
+
existing.devDependencies[key] = value;
|
|
9819
|
+
updated = true;
|
|
9820
|
+
}
|
|
9821
|
+
}
|
|
9822
|
+
if (updated) {
|
|
9823
|
+
fs3.writeFileSync(packageJsonPath, JSON.stringify(existing, null, 2) + `
|
|
9824
|
+
`);
|
|
9825
|
+
console.log(source_default.green("✓"), "Updated", packageJsonPath, "with missing scripts/deps");
|
|
9826
|
+
} else {
|
|
9827
|
+
console.log(source_default.yellow("⚠"), packageJsonPath, "already has all required entries");
|
|
9828
|
+
}
|
|
9829
|
+
}
|
|
9830
|
+
const configPath = path4.join(projectDir, "chronicle.yaml");
|
|
9831
|
+
if (!fs3.existsSync(configPath)) {
|
|
9832
|
+
fs3.writeFileSync(configPath, $stringify(createConfig()));
|
|
9613
9833
|
console.log(source_default.green("✓"), "Created", configPath);
|
|
9614
9834
|
} else {
|
|
9615
9835
|
console.log(source_default.yellow("⚠"), configPath, "already exists");
|
|
9616
9836
|
}
|
|
9617
|
-
const
|
|
9618
|
-
if (
|
|
9619
|
-
|
|
9837
|
+
const contentFiles = fs3.readdirSync(contentDir);
|
|
9838
|
+
if (contentFiles.length === 0) {
|
|
9839
|
+
const indexPath = path4.join(contentDir, "index.mdx");
|
|
9840
|
+
fs3.writeFileSync(indexPath, sampleMdx);
|
|
9620
9841
|
console.log(source_default.green("✓"), "Created", indexPath);
|
|
9621
9842
|
}
|
|
9843
|
+
const gitignorePath = path4.join(projectDir, ".gitignore");
|
|
9844
|
+
const gitignoreEntries = [".chronicle", "node_modules", ".next"];
|
|
9845
|
+
if (fs3.existsSync(gitignorePath)) {
|
|
9846
|
+
const existing = fs3.readFileSync(gitignorePath, "utf-8");
|
|
9847
|
+
const missing = gitignoreEntries.filter((e) => !existing.includes(e));
|
|
9848
|
+
if (missing.length > 0) {
|
|
9849
|
+
fs3.appendFileSync(gitignorePath, `
|
|
9850
|
+
${missing.join(`
|
|
9851
|
+
`)}
|
|
9852
|
+
`);
|
|
9853
|
+
console.log(source_default.green("✓"), "Added", missing.join(", "), "to .gitignore");
|
|
9854
|
+
}
|
|
9855
|
+
} else {
|
|
9856
|
+
fs3.writeFileSync(gitignorePath, `${gitignoreEntries.join(`
|
|
9857
|
+
`)}
|
|
9858
|
+
`);
|
|
9859
|
+
console.log(source_default.green("✓"), "Created .gitignore");
|
|
9860
|
+
}
|
|
9861
|
+
const pm = detectPackageManager();
|
|
9862
|
+
console.log(source_default.cyan(`
|
|
9863
|
+
Installing dependencies with ${pm}...`));
|
|
9864
|
+
execSync2(`${pm} install`, { cwd: projectDir, stdio: "inherit" });
|
|
9865
|
+
loadCLIConfig(contentDir);
|
|
9866
|
+
scaffoldDir(contentDir);
|
|
9867
|
+
const runCmd = pm === "npm" ? "npx" : pm === "bun" ? "bunx" : `${pm} dlx`;
|
|
9622
9868
|
console.log(source_default.green(`
|
|
9623
9869
|
✓ Chronicle initialized!`));
|
|
9624
9870
|
console.log(`
|
|
9625
|
-
Run`, source_default.cyan(
|
|
9871
|
+
Run`, source_default.cyan(`${runCmd} chronicle dev`), "to start development server");
|
|
9626
9872
|
});
|
|
9627
9873
|
|
|
9628
9874
|
// src/cli/commands/dev.ts
|
|
9629
9875
|
import { spawn } from "child_process";
|
|
9630
|
-
import
|
|
9631
|
-
import
|
|
9632
|
-
|
|
9633
|
-
|
|
9634
|
-
|
|
9635
|
-
|
|
9636
|
-
|
|
9637
|
-
|
|
9638
|
-
|
|
9639
|
-
|
|
9640
|
-
|
|
9641
|
-
|
|
9642
|
-
|
|
9643
|
-
function loadCLIConfig(contentDir) {
|
|
9644
|
-
const configPath = path2.join(contentDir, "chronicle.yaml");
|
|
9645
|
-
if (!fs2.existsSync(configPath)) {
|
|
9646
|
-
console.log(source_default.red("Error: chronicle.yaml not found in"), contentDir);
|
|
9647
|
-
console.log(source_default.gray(`Run 'chronicle init' to create one`));
|
|
9876
|
+
import path5 from "path";
|
|
9877
|
+
import fs4 from "fs";
|
|
9878
|
+
var devCommand = new Command("dev").description("Start development server").option("-p, --port <port>", "Port number", "3000").action((options) => {
|
|
9879
|
+
const scaffoldPath = path5.join(process.cwd(), ".chronicle");
|
|
9880
|
+
if (!fs4.existsSync(scaffoldPath)) {
|
|
9881
|
+
console.log(source_default.red("Error: .chronicle/ not found. Run"), source_default.cyan("chronicle init"), source_default.red("first."));
|
|
9882
|
+
process.exit(1);
|
|
9883
|
+
}
|
|
9884
|
+
let nextCli;
|
|
9885
|
+
try {
|
|
9886
|
+
nextCli = resolveNextCli();
|
|
9887
|
+
} catch {
|
|
9888
|
+
console.log(source_default.red("Error: Next.js CLI not found. Run"), source_default.cyan("chronicle init"), source_default.red("first."));
|
|
9648
9889
|
process.exit(1);
|
|
9649
9890
|
}
|
|
9650
|
-
const config = $parse(fs2.readFileSync(configPath, "utf-8"));
|
|
9651
|
-
return {
|
|
9652
|
-
config,
|
|
9653
|
-
configPath,
|
|
9654
|
-
contentDir
|
|
9655
|
-
};
|
|
9656
|
-
}
|
|
9657
|
-
// src/cli/utils/process.ts
|
|
9658
|
-
function attachLifecycleHandlers(child) {
|
|
9659
|
-
child.on("close", (code) => process.exit(code ?? 0));
|
|
9660
|
-
process.on("SIGINT", () => child.kill("SIGINT"));
|
|
9661
|
-
process.on("SIGTERM", () => child.kill("SIGTERM"));
|
|
9662
|
-
}
|
|
9663
|
-
// src/cli/commands/dev.ts
|
|
9664
|
-
var PACKAGE_ROOT = path3.resolve(path3.dirname(fileURLToPath(import.meta.url)), "..", "..");
|
|
9665
|
-
var nextBin = path3.join(PACKAGE_ROOT, "node_modules", ".bin", process.platform === "win32" ? "next.cmd" : "next");
|
|
9666
|
-
var devCommand = new Command("dev").description("Start development server").option("-p, --port <port>", "Port number", "3000").option("-c, --content <path>", "Content directory").action((options) => {
|
|
9667
|
-
const contentDir = resolveContentDir(options.content);
|
|
9668
|
-
loadCLIConfig(contentDir);
|
|
9669
9891
|
console.log(source_default.cyan("Starting dev server..."));
|
|
9670
|
-
|
|
9671
|
-
const child = spawn(nextBin, ["dev", "-p", options.port], {
|
|
9892
|
+
const child = spawn(process.execPath, [nextCli, "dev", "-p", options.port], {
|
|
9672
9893
|
stdio: "inherit",
|
|
9673
|
-
cwd:
|
|
9894
|
+
cwd: scaffoldPath,
|
|
9674
9895
|
env: {
|
|
9675
9896
|
...process.env,
|
|
9676
|
-
|
|
9897
|
+
CHRONICLE_PROJECT_ROOT: process.cwd(),
|
|
9898
|
+
CHRONICLE_CONTENT_DIR: "./content"
|
|
9677
9899
|
}
|
|
9678
9900
|
});
|
|
9679
9901
|
attachLifecycleHandlers(child);
|
|
@@ -9681,21 +9903,29 @@ var devCommand = new Command("dev").description("Start development server").opti
|
|
|
9681
9903
|
|
|
9682
9904
|
// src/cli/commands/build.ts
|
|
9683
9905
|
import { spawn as spawn2 } from "child_process";
|
|
9684
|
-
import
|
|
9685
|
-
import
|
|
9686
|
-
var
|
|
9687
|
-
|
|
9688
|
-
|
|
9689
|
-
|
|
9690
|
-
|
|
9906
|
+
import path6 from "path";
|
|
9907
|
+
import fs5 from "fs";
|
|
9908
|
+
var buildCommand = new Command("build").description("Build for production").action(() => {
|
|
9909
|
+
const scaffoldPath = path6.join(process.cwd(), ".chronicle");
|
|
9910
|
+
if (!fs5.existsSync(scaffoldPath)) {
|
|
9911
|
+
console.log(source_default.red("Error: .chronicle/ not found. Run"), source_default.cyan("chronicle init"), source_default.red("first."));
|
|
9912
|
+
process.exit(1);
|
|
9913
|
+
}
|
|
9914
|
+
let nextCli;
|
|
9915
|
+
try {
|
|
9916
|
+
nextCli = resolveNextCli();
|
|
9917
|
+
} catch {
|
|
9918
|
+
console.log(source_default.red("Error: Next.js CLI not found. Run"), source_default.cyan("chronicle init"), source_default.red("first."));
|
|
9919
|
+
process.exit(1);
|
|
9920
|
+
}
|
|
9691
9921
|
console.log(source_default.cyan("Building for production..."));
|
|
9692
|
-
|
|
9693
|
-
const child = spawn2(nextBin2, ["build"], {
|
|
9922
|
+
const child = spawn2(process.execPath, [nextCli, "build"], {
|
|
9694
9923
|
stdio: "inherit",
|
|
9695
|
-
cwd:
|
|
9924
|
+
cwd: scaffoldPath,
|
|
9696
9925
|
env: {
|
|
9697
9926
|
...process.env,
|
|
9698
|
-
|
|
9927
|
+
CHRONICLE_PROJECT_ROOT: process.cwd(),
|
|
9928
|
+
CHRONICLE_CONTENT_DIR: "./content"
|
|
9699
9929
|
}
|
|
9700
9930
|
});
|
|
9701
9931
|
attachLifecycleHandlers(child);
|
|
@@ -9703,21 +9933,29 @@ var buildCommand = new Command("build").description("Build for production").opti
|
|
|
9703
9933
|
|
|
9704
9934
|
// src/cli/commands/start.ts
|
|
9705
9935
|
import { spawn as spawn3 } from "child_process";
|
|
9706
|
-
import
|
|
9707
|
-
import
|
|
9708
|
-
var
|
|
9709
|
-
|
|
9710
|
-
|
|
9711
|
-
|
|
9712
|
-
|
|
9936
|
+
import path7 from "path";
|
|
9937
|
+
import fs6 from "fs";
|
|
9938
|
+
var startCommand = new Command("start").description("Start production server").option("-p, --port <port>", "Port number", "3000").action((options) => {
|
|
9939
|
+
const scaffoldPath = path7.join(process.cwd(), ".chronicle");
|
|
9940
|
+
if (!fs6.existsSync(scaffoldPath)) {
|
|
9941
|
+
console.log(source_default.red("Error: .chronicle/ not found. Run"), source_default.cyan("chronicle init"), source_default.red("first."));
|
|
9942
|
+
process.exit(1);
|
|
9943
|
+
}
|
|
9944
|
+
let nextCli;
|
|
9945
|
+
try {
|
|
9946
|
+
nextCli = resolveNextCli();
|
|
9947
|
+
} catch {
|
|
9948
|
+
console.log(source_default.red("Error: Next.js CLI not found. Run"), source_default.cyan("chronicle init"), source_default.red("first."));
|
|
9949
|
+
process.exit(1);
|
|
9950
|
+
}
|
|
9713
9951
|
console.log(source_default.cyan("Starting production server..."));
|
|
9714
|
-
|
|
9715
|
-
const child = spawn3(nextBin3, ["start", "-p", options.port], {
|
|
9952
|
+
const child = spawn3(process.execPath, [nextCli, "start", "-p", options.port], {
|
|
9716
9953
|
stdio: "inherit",
|
|
9717
|
-
cwd:
|
|
9954
|
+
cwd: scaffoldPath,
|
|
9718
9955
|
env: {
|
|
9719
9956
|
...process.env,
|
|
9720
|
-
|
|
9957
|
+
CHRONICLE_PROJECT_ROOT: process.cwd(),
|
|
9958
|
+
CHRONICLE_CONTENT_DIR: "./content"
|
|
9721
9959
|
}
|
|
9722
9960
|
});
|
|
9723
9961
|
attachLifecycleHandlers(child);
|
|
@@ -9725,22 +9963,30 @@ var startCommand = new Command("start").description("Start production server").o
|
|
|
9725
9963
|
|
|
9726
9964
|
// src/cli/commands/serve.ts
|
|
9727
9965
|
import { spawn as spawn4 } from "child_process";
|
|
9728
|
-
import
|
|
9729
|
-
import
|
|
9730
|
-
var
|
|
9731
|
-
|
|
9732
|
-
|
|
9733
|
-
|
|
9734
|
-
|
|
9966
|
+
import path8 from "path";
|
|
9967
|
+
import fs7 from "fs";
|
|
9968
|
+
var serveCommand = new Command("serve").description("Build and start production server").option("-p, --port <port>", "Port number", "3000").action((options) => {
|
|
9969
|
+
const scaffoldPath = path8.join(process.cwd(), ".chronicle");
|
|
9970
|
+
if (!fs7.existsSync(scaffoldPath)) {
|
|
9971
|
+
console.log(source_default.red("Error: .chronicle/ not found. Run"), source_default.cyan("chronicle init"), source_default.red("first."));
|
|
9972
|
+
process.exit(1);
|
|
9973
|
+
}
|
|
9974
|
+
let nextCli;
|
|
9975
|
+
try {
|
|
9976
|
+
nextCli = resolveNextCli();
|
|
9977
|
+
} catch {
|
|
9978
|
+
console.log(source_default.red("Error: Next.js CLI not found. Run"), source_default.cyan("chronicle init"), source_default.red("first."));
|
|
9979
|
+
process.exit(1);
|
|
9980
|
+
}
|
|
9735
9981
|
const env2 = {
|
|
9736
9982
|
...process.env,
|
|
9737
|
-
|
|
9983
|
+
CHRONICLE_PROJECT_ROOT: process.cwd(),
|
|
9984
|
+
CHRONICLE_CONTENT_DIR: "./content"
|
|
9738
9985
|
};
|
|
9739
9986
|
console.log(source_default.cyan("Building for production..."));
|
|
9740
|
-
|
|
9741
|
-
const buildChild = spawn4(nextBin4, ["build"], {
|
|
9987
|
+
const buildChild = spawn4(process.execPath, [nextCli, "build"], {
|
|
9742
9988
|
stdio: "inherit",
|
|
9743
|
-
cwd:
|
|
9989
|
+
cwd: scaffoldPath,
|
|
9744
9990
|
env: env2
|
|
9745
9991
|
});
|
|
9746
9992
|
process.once("SIGINT", () => buildChild.kill("SIGINT"));
|
|
@@ -9751,9 +9997,9 @@ var serveCommand = new Command("serve").description("Build and start production
|
|
|
9751
9997
|
process.exit(code ?? 1);
|
|
9752
9998
|
}
|
|
9753
9999
|
console.log(source_default.cyan("Starting production server..."));
|
|
9754
|
-
const startChild = spawn4(
|
|
10000
|
+
const startChild = spawn4(process.execPath, [nextCli, "start", "-p", options.port], {
|
|
9755
10001
|
stdio: "inherit",
|
|
9756
|
-
cwd:
|
|
10002
|
+
cwd: scaffoldPath,
|
|
9757
10003
|
env: env2
|
|
9758
10004
|
});
|
|
9759
10005
|
attachLifecycleHandlers(startChild);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@raystack/chronicle",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "Config-driven documentation framework",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"type": "module",
|
|
@@ -10,7 +10,8 @@
|
|
|
10
10
|
"src",
|
|
11
11
|
"templates",
|
|
12
12
|
"next.config.mjs",
|
|
13
|
-
"source.config.ts"
|
|
13
|
+
"source.config.ts",
|
|
14
|
+
"tsconfig.json"
|
|
14
15
|
],
|
|
15
16
|
"bin": {
|
|
16
17
|
"chronicle": "./bin/chronicle.js"
|
|
@@ -27,7 +28,6 @@
|
|
|
27
28
|
"@types/react": "^19.2.10",
|
|
28
29
|
"@types/react-dom": "^19.2.3",
|
|
29
30
|
"@types/semver": "^7.7.1",
|
|
30
|
-
"openapi-types": "^12.1.3",
|
|
31
31
|
"semver": "^7.7.4",
|
|
32
32
|
"typescript": "5.9.3"
|
|
33
33
|
},
|
|
@@ -56,6 +56,7 @@
|
|
|
56
56
|
"slugify": "^1.6.6",
|
|
57
57
|
"unified": "^11.0.5",
|
|
58
58
|
"unist-util-visit": "^5.1.0",
|
|
59
|
+
"openapi-types": "^12.1.3",
|
|
59
60
|
"yaml": "^2.8.2",
|
|
60
61
|
"zod": "^4.3.6"
|
|
61
62
|
}
|
|
@@ -1,29 +1,36 @@
|
|
|
1
1
|
import { Command } from 'commander'
|
|
2
2
|
import { spawn } from 'child_process'
|
|
3
3
|
import path from 'path'
|
|
4
|
-
import
|
|
4
|
+
import fs from 'fs'
|
|
5
5
|
import chalk from 'chalk'
|
|
6
|
-
import {
|
|
7
|
-
|
|
8
|
-
const PACKAGE_ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..', '..')
|
|
9
|
-
const nextBin = path.join(PACKAGE_ROOT, 'node_modules', '.bin', process.platform === 'win32' ? 'next.cmd' : 'next')
|
|
6
|
+
import { attachLifecycleHandlers, resolveNextCli } from '@/cli/utils'
|
|
10
7
|
|
|
11
8
|
export const buildCommand = new Command('build')
|
|
12
9
|
.description('Build for production')
|
|
13
|
-
.
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
10
|
+
.action(() => {
|
|
11
|
+
const scaffoldPath = path.join(process.cwd(), '.chronicle')
|
|
12
|
+
if (!fs.existsSync(scaffoldPath)) {
|
|
13
|
+
console.log(chalk.red('Error: .chronicle/ not found. Run'), chalk.cyan('chronicle init'), chalk.red('first.'))
|
|
14
|
+
process.exit(1)
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
let nextCli: string
|
|
18
|
+
try {
|
|
19
|
+
nextCli = resolveNextCli()
|
|
20
|
+
} catch {
|
|
21
|
+
console.log(chalk.red('Error: Next.js CLI not found. Run'), chalk.cyan('chronicle init'), chalk.red('first.'))
|
|
22
|
+
process.exit(1)
|
|
23
|
+
}
|
|
17
24
|
|
|
18
25
|
console.log(chalk.cyan('Building for production...'))
|
|
19
|
-
console.log(chalk.gray(`Content: ${contentDir}`))
|
|
20
26
|
|
|
21
|
-
const child = spawn(
|
|
27
|
+
const child = spawn(process.execPath, [nextCli, 'build'], {
|
|
22
28
|
stdio: 'inherit',
|
|
23
|
-
cwd:
|
|
29
|
+
cwd: scaffoldPath,
|
|
24
30
|
env: {
|
|
25
31
|
...process.env,
|
|
26
|
-
|
|
32
|
+
CHRONICLE_PROJECT_ROOT: process.cwd(),
|
|
33
|
+
CHRONICLE_CONTENT_DIR: './content',
|
|
27
34
|
},
|
|
28
35
|
})
|
|
29
36
|
|
package/src/cli/commands/dev.ts
CHANGED
|
@@ -1,30 +1,37 @@
|
|
|
1
1
|
import { Command } from 'commander'
|
|
2
2
|
import { spawn } from 'child_process'
|
|
3
3
|
import path from 'path'
|
|
4
|
-
import
|
|
4
|
+
import fs from 'fs'
|
|
5
5
|
import chalk from 'chalk'
|
|
6
|
-
import {
|
|
7
|
-
|
|
8
|
-
const PACKAGE_ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..', '..')
|
|
9
|
-
const nextBin = path.join(PACKAGE_ROOT, 'node_modules', '.bin', process.platform === 'win32' ? 'next.cmd' : 'next')
|
|
6
|
+
import { attachLifecycleHandlers, resolveNextCli } from '@/cli/utils'
|
|
10
7
|
|
|
11
8
|
export const devCommand = new Command('dev')
|
|
12
9
|
.description('Start development server')
|
|
13
10
|
.option('-p, --port <port>', 'Port number', '3000')
|
|
14
|
-
.option('-c, --content <path>', 'Content directory')
|
|
15
11
|
.action((options) => {
|
|
16
|
-
const
|
|
17
|
-
|
|
12
|
+
const scaffoldPath = path.join(process.cwd(), '.chronicle')
|
|
13
|
+
if (!fs.existsSync(scaffoldPath)) {
|
|
14
|
+
console.log(chalk.red('Error: .chronicle/ not found. Run'), chalk.cyan('chronicle init'), chalk.red('first.'))
|
|
15
|
+
process.exit(1)
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
let nextCli: string
|
|
19
|
+
try {
|
|
20
|
+
nextCli = resolveNextCli()
|
|
21
|
+
} catch {
|
|
22
|
+
console.log(chalk.red('Error: Next.js CLI not found. Run'), chalk.cyan('chronicle init'), chalk.red('first.'))
|
|
23
|
+
process.exit(1)
|
|
24
|
+
}
|
|
18
25
|
|
|
19
26
|
console.log(chalk.cyan('Starting dev server...'))
|
|
20
|
-
console.log(chalk.gray(`Content: ${contentDir}`))
|
|
21
27
|
|
|
22
|
-
const child = spawn(
|
|
28
|
+
const child = spawn(process.execPath, [nextCli, 'dev', '-p', options.port], {
|
|
23
29
|
stdio: 'inherit',
|
|
24
|
-
cwd:
|
|
30
|
+
cwd: scaffoldPath,
|
|
25
31
|
env: {
|
|
26
32
|
...process.env,
|
|
27
|
-
|
|
33
|
+
CHRONICLE_PROJECT_ROOT: process.cwd(),
|
|
34
|
+
CHRONICLE_CONTENT_DIR: './content',
|
|
28
35
|
},
|
|
29
36
|
})
|
|
30
37
|
|
package/src/cli/commands/init.ts
CHANGED
|
@@ -1,9 +1,12 @@
|
|
|
1
1
|
import { Command } from 'commander'
|
|
2
|
+
import { execSync } from 'child_process'
|
|
2
3
|
import fs from 'fs'
|
|
3
4
|
import path from 'path'
|
|
4
5
|
import chalk from 'chalk'
|
|
5
6
|
import { stringify } from 'yaml'
|
|
6
7
|
import type { ChronicleConfig } from '@/types'
|
|
8
|
+
import { loadCLIConfig, scaffoldDir, detectPackageManager, getChronicleVersion } from '@/cli/utils'
|
|
9
|
+
|
|
7
10
|
|
|
8
11
|
function createConfig(): ChronicleConfig {
|
|
9
12
|
return {
|
|
@@ -14,6 +17,29 @@ function createConfig(): ChronicleConfig {
|
|
|
14
17
|
}
|
|
15
18
|
}
|
|
16
19
|
|
|
20
|
+
function createPackageJson(name: string): Record<string, unknown> {
|
|
21
|
+
return {
|
|
22
|
+
name,
|
|
23
|
+
private: true,
|
|
24
|
+
type: 'module',
|
|
25
|
+
scripts: {
|
|
26
|
+
dev: 'chronicle dev',
|
|
27
|
+
build: 'chronicle build',
|
|
28
|
+
start: 'chronicle start',
|
|
29
|
+
},
|
|
30
|
+
dependencies: {
|
|
31
|
+
'@raystack/chronicle': `^${getChronicleVersion()}`,
|
|
32
|
+
},
|
|
33
|
+
devDependencies: {
|
|
34
|
+
'@raystack/tools-config': '0.56.0',
|
|
35
|
+
'openapi-types': '^12.1.3',
|
|
36
|
+
typescript: '5.9.3',
|
|
37
|
+
'@types/react': '^19.2.10',
|
|
38
|
+
'@types/node': '^25.1.0',
|
|
39
|
+
},
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
17
43
|
const sampleMdx = `---
|
|
18
44
|
title: Welcome
|
|
19
45
|
description: Getting started with your documentation
|
|
@@ -27,18 +53,71 @@ This is your documentation home page.
|
|
|
27
53
|
|
|
28
54
|
export const initCommand = new Command('init')
|
|
29
55
|
.description('Initialize a new Chronicle project')
|
|
30
|
-
.option('-
|
|
56
|
+
.option('-c, --content <path>', 'Content directory name', 'content')
|
|
31
57
|
.action((options) => {
|
|
32
|
-
const
|
|
58
|
+
const projectDir = process.cwd()
|
|
59
|
+
const dirName = path.basename(projectDir) || 'docs'
|
|
60
|
+
const contentDir = path.join(projectDir, options.content)
|
|
33
61
|
|
|
34
|
-
// Create content directory
|
|
62
|
+
// Create content directory if it doesn't exist
|
|
35
63
|
if (!fs.existsSync(contentDir)) {
|
|
36
64
|
fs.mkdirSync(contentDir, { recursive: true })
|
|
37
65
|
console.log(chalk.green('✓'), 'Created', contentDir)
|
|
38
66
|
}
|
|
39
67
|
|
|
40
|
-
// Create
|
|
41
|
-
const
|
|
68
|
+
// Create or update package.json in project root
|
|
69
|
+
const packageJsonPath = path.join(projectDir, 'package.json')
|
|
70
|
+
if (!fs.existsSync(packageJsonPath)) {
|
|
71
|
+
fs.writeFileSync(packageJsonPath, JSON.stringify(createPackageJson(dirName), null, 2) + '\n')
|
|
72
|
+
console.log(chalk.green('✓'), 'Created', packageJsonPath)
|
|
73
|
+
} else {
|
|
74
|
+
const existing = JSON.parse(fs.readFileSync(packageJsonPath, 'utf-8'))
|
|
75
|
+
const template = createPackageJson(dirName)
|
|
76
|
+
let updated = false
|
|
77
|
+
|
|
78
|
+
// Set type to module
|
|
79
|
+
if (existing.type !== 'module') {
|
|
80
|
+
existing.type = 'module'
|
|
81
|
+
updated = true
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// Merge missing scripts
|
|
85
|
+
if (!existing.scripts) existing.scripts = {}
|
|
86
|
+
for (const [key, value] of Object.entries(template.scripts as Record<string, string>)) {
|
|
87
|
+
if (!existing.scripts[key]) {
|
|
88
|
+
existing.scripts[key] = value
|
|
89
|
+
updated = true
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// Merge missing dependencies
|
|
94
|
+
if (!existing.dependencies) existing.dependencies = {}
|
|
95
|
+
for (const [key, value] of Object.entries(template.dependencies as Record<string, string>)) {
|
|
96
|
+
if (!existing.dependencies[key]) {
|
|
97
|
+
existing.dependencies[key] = value
|
|
98
|
+
updated = true
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// Merge missing devDependencies
|
|
103
|
+
if (!existing.devDependencies) existing.devDependencies = {}
|
|
104
|
+
for (const [key, value] of Object.entries(template.devDependencies as Record<string, string>)) {
|
|
105
|
+
if (!existing.devDependencies[key]) {
|
|
106
|
+
existing.devDependencies[key] = value
|
|
107
|
+
updated = true
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
if (updated) {
|
|
112
|
+
fs.writeFileSync(packageJsonPath, JSON.stringify(existing, null, 2) + '\n')
|
|
113
|
+
console.log(chalk.green('✓'), 'Updated', packageJsonPath, 'with missing scripts/deps')
|
|
114
|
+
} else {
|
|
115
|
+
console.log(chalk.yellow('⚠'), packageJsonPath, 'already has all required entries')
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// Create chronicle.yaml in project root
|
|
120
|
+
const configPath = path.join(projectDir, 'chronicle.yaml')
|
|
42
121
|
if (!fs.existsSync(configPath)) {
|
|
43
122
|
fs.writeFileSync(configPath, stringify(createConfig()))
|
|
44
123
|
console.log(chalk.green('✓'), 'Created', configPath)
|
|
@@ -46,13 +125,39 @@ export const initCommand = new Command('init')
|
|
|
46
125
|
console.log(chalk.yellow('⚠'), configPath, 'already exists')
|
|
47
126
|
}
|
|
48
127
|
|
|
49
|
-
// Create sample index.mdx
|
|
50
|
-
const
|
|
51
|
-
if (
|
|
128
|
+
// Create sample index.mdx only if content dir is empty
|
|
129
|
+
const contentFiles = fs.readdirSync(contentDir)
|
|
130
|
+
if (contentFiles.length === 0) {
|
|
131
|
+
const indexPath = path.join(contentDir, 'index.mdx')
|
|
52
132
|
fs.writeFileSync(indexPath, sampleMdx)
|
|
53
133
|
console.log(chalk.green('✓'), 'Created', indexPath)
|
|
54
134
|
}
|
|
55
135
|
|
|
136
|
+
// Add entries to .gitignore
|
|
137
|
+
const gitignorePath = path.join(projectDir, '.gitignore')
|
|
138
|
+
const gitignoreEntries = ['.chronicle', 'node_modules', '.next']
|
|
139
|
+
if (fs.existsSync(gitignorePath)) {
|
|
140
|
+
const existing = fs.readFileSync(gitignorePath, 'utf-8')
|
|
141
|
+
const missing = gitignoreEntries.filter(e => !existing.includes(e))
|
|
142
|
+
if (missing.length > 0) {
|
|
143
|
+
fs.appendFileSync(gitignorePath, `\n${missing.join('\n')}\n`)
|
|
144
|
+
console.log(chalk.green('✓'), 'Added', missing.join(', '), 'to .gitignore')
|
|
145
|
+
}
|
|
146
|
+
} else {
|
|
147
|
+
fs.writeFileSync(gitignorePath, `${gitignoreEntries.join('\n')}\n`)
|
|
148
|
+
console.log(chalk.green('✓'), 'Created .gitignore')
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// Install dependencies
|
|
152
|
+
const pm = detectPackageManager()
|
|
153
|
+
console.log(chalk.cyan(`\nInstalling dependencies with ${pm}...`))
|
|
154
|
+
execSync(`${pm} install`, { cwd: projectDir, stdio: 'inherit' })
|
|
155
|
+
|
|
156
|
+
// Scaffold .chronicle/ directory
|
|
157
|
+
loadCLIConfig(contentDir)
|
|
158
|
+
scaffoldDir(contentDir)
|
|
159
|
+
|
|
160
|
+
const runCmd = pm === 'npm' ? 'npx' : pm === 'bun' ? 'bunx' : `${pm} dlx`
|
|
56
161
|
console.log(chalk.green('\n✓ Chronicle initialized!'))
|
|
57
|
-
console.log('\nRun', chalk.cyan(
|
|
162
|
+
console.log('\nRun', chalk.cyan(`${runCmd} chronicle dev`), 'to start development server')
|
|
58
163
|
})
|
|
@@ -1,32 +1,39 @@
|
|
|
1
1
|
import { Command } from 'commander'
|
|
2
2
|
import { spawn } from 'child_process'
|
|
3
3
|
import path from 'path'
|
|
4
|
-
import
|
|
4
|
+
import fs from 'fs'
|
|
5
5
|
import chalk from 'chalk'
|
|
6
|
-
import {
|
|
7
|
-
|
|
8
|
-
const PACKAGE_ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..', '..')
|
|
9
|
-
const nextBin = path.join(PACKAGE_ROOT, 'node_modules', '.bin', process.platform === 'win32' ? 'next.cmd' : 'next')
|
|
6
|
+
import { attachLifecycleHandlers, resolveNextCli } from '@/cli/utils'
|
|
10
7
|
|
|
11
8
|
export const serveCommand = new Command('serve')
|
|
12
9
|
.description('Build and start production server')
|
|
13
10
|
.option('-p, --port <port>', 'Port number', '3000')
|
|
14
|
-
.option('-c, --content <path>', 'Content directory')
|
|
15
11
|
.action((options) => {
|
|
16
|
-
const
|
|
17
|
-
|
|
12
|
+
const scaffoldPath = path.join(process.cwd(), '.chronicle')
|
|
13
|
+
if (!fs.existsSync(scaffoldPath)) {
|
|
14
|
+
console.log(chalk.red('Error: .chronicle/ not found. Run'), chalk.cyan('chronicle init'), chalk.red('first.'))
|
|
15
|
+
process.exit(1)
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
let nextCli: string
|
|
19
|
+
try {
|
|
20
|
+
nextCli = resolveNextCli()
|
|
21
|
+
} catch {
|
|
22
|
+
console.log(chalk.red('Error: Next.js CLI not found. Run'), chalk.cyan('chronicle init'), chalk.red('first.'))
|
|
23
|
+
process.exit(1)
|
|
24
|
+
}
|
|
18
25
|
|
|
19
26
|
const env = {
|
|
20
27
|
...process.env,
|
|
21
|
-
|
|
28
|
+
CHRONICLE_PROJECT_ROOT: process.cwd(),
|
|
29
|
+
CHRONICLE_CONTENT_DIR: './content',
|
|
22
30
|
}
|
|
23
31
|
|
|
24
32
|
console.log(chalk.cyan('Building for production...'))
|
|
25
|
-
console.log(chalk.gray(`Content: ${contentDir}`))
|
|
26
33
|
|
|
27
|
-
const buildChild = spawn(
|
|
34
|
+
const buildChild = spawn(process.execPath, [nextCli, 'build'], {
|
|
28
35
|
stdio: 'inherit',
|
|
29
|
-
cwd:
|
|
36
|
+
cwd: scaffoldPath,
|
|
30
37
|
env,
|
|
31
38
|
})
|
|
32
39
|
|
|
@@ -41,9 +48,9 @@ export const serveCommand = new Command('serve')
|
|
|
41
48
|
|
|
42
49
|
console.log(chalk.cyan('Starting production server...'))
|
|
43
50
|
|
|
44
|
-
const startChild = spawn(
|
|
51
|
+
const startChild = spawn(process.execPath, [nextCli, 'start', '-p', options.port], {
|
|
45
52
|
stdio: 'inherit',
|
|
46
|
-
cwd:
|
|
53
|
+
cwd: scaffoldPath,
|
|
47
54
|
env,
|
|
48
55
|
})
|
|
49
56
|
|
|
@@ -1,30 +1,37 @@
|
|
|
1
1
|
import { Command } from 'commander'
|
|
2
2
|
import { spawn } from 'child_process'
|
|
3
3
|
import path from 'path'
|
|
4
|
-
import
|
|
4
|
+
import fs from 'fs'
|
|
5
5
|
import chalk from 'chalk'
|
|
6
|
-
import {
|
|
7
|
-
|
|
8
|
-
const PACKAGE_ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..', '..')
|
|
9
|
-
const nextBin = path.join(PACKAGE_ROOT, 'node_modules', '.bin', process.platform === 'win32' ? 'next.cmd' : 'next')
|
|
6
|
+
import { attachLifecycleHandlers, resolveNextCli } from '@/cli/utils'
|
|
10
7
|
|
|
11
8
|
export const startCommand = new Command('start')
|
|
12
9
|
.description('Start production server')
|
|
13
10
|
.option('-p, --port <port>', 'Port number', '3000')
|
|
14
|
-
.option('-c, --content <path>', 'Content directory')
|
|
15
11
|
.action((options) => {
|
|
16
|
-
const
|
|
17
|
-
|
|
12
|
+
const scaffoldPath = path.join(process.cwd(), '.chronicle')
|
|
13
|
+
if (!fs.existsSync(scaffoldPath)) {
|
|
14
|
+
console.log(chalk.red('Error: .chronicle/ not found. Run'), chalk.cyan('chronicle init'), chalk.red('first.'))
|
|
15
|
+
process.exit(1)
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
let nextCli: string
|
|
19
|
+
try {
|
|
20
|
+
nextCli = resolveNextCli()
|
|
21
|
+
} catch {
|
|
22
|
+
console.log(chalk.red('Error: Next.js CLI not found. Run'), chalk.cyan('chronicle init'), chalk.red('first.'))
|
|
23
|
+
process.exit(1)
|
|
24
|
+
}
|
|
18
25
|
|
|
19
26
|
console.log(chalk.cyan('Starting production server...'))
|
|
20
|
-
console.log(chalk.gray(`Content: ${contentDir}`))
|
|
21
27
|
|
|
22
|
-
const child = spawn(
|
|
28
|
+
const child = spawn(process.execPath, [nextCli, 'start', '-p', options.port], {
|
|
23
29
|
stdio: 'inherit',
|
|
24
|
-
cwd:
|
|
30
|
+
cwd: scaffoldPath,
|
|
25
31
|
env: {
|
|
26
32
|
...process.env,
|
|
27
|
-
|
|
33
|
+
CHRONICLE_PROJECT_ROOT: process.cwd(),
|
|
34
|
+
CHRONICLE_CONTENT_DIR: './content',
|
|
28
35
|
},
|
|
29
36
|
})
|
|
30
37
|
|
package/src/cli/utils/config.ts
CHANGED
|
@@ -13,14 +13,22 @@ export interface CLIConfig {
|
|
|
13
13
|
export function resolveContentDir(contentFlag?: string): string {
|
|
14
14
|
if (contentFlag) return path.resolve(contentFlag)
|
|
15
15
|
if (process.env.CHRONICLE_CONTENT_DIR) return path.resolve(process.env.CHRONICLE_CONTENT_DIR)
|
|
16
|
-
return
|
|
16
|
+
return path.resolve('content')
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function resolveConfigPath(contentDir: string): string | null {
|
|
20
|
+
const cwdPath = path.join(process.cwd(), 'chronicle.yaml')
|
|
21
|
+
if (fs.existsSync(cwdPath)) return cwdPath
|
|
22
|
+
const contentPath = path.join(contentDir, 'chronicle.yaml')
|
|
23
|
+
if (fs.existsSync(contentPath)) return contentPath
|
|
24
|
+
return null
|
|
17
25
|
}
|
|
18
26
|
|
|
19
27
|
export function loadCLIConfig(contentDir: string): CLIConfig {
|
|
20
|
-
const configPath =
|
|
28
|
+
const configPath = resolveConfigPath(contentDir)
|
|
21
29
|
|
|
22
|
-
if (!
|
|
23
|
-
console.log(chalk.red(
|
|
30
|
+
if (!configPath) {
|
|
31
|
+
console.log(chalk.red(`Error: chronicle.yaml not found in '${process.cwd()}' or '${contentDir}'`))
|
|
24
32
|
console.log(chalk.gray(`Run 'chronicle init' to create one`))
|
|
25
33
|
process.exit(1)
|
|
26
34
|
}
|
package/src/cli/utils/index.ts
CHANGED
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import path from 'path'
|
|
2
|
+
import { fileURLToPath } from 'url'
|
|
3
|
+
|
|
4
|
+
// After bundling: dist/cli/index.js → ../.. = package root
|
|
5
|
+
// After install: node_modules/@raystack/chronicle/dist/cli/index.js → ../.. = package root
|
|
6
|
+
export const PACKAGE_ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..', '..')
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
import { execSync } from 'child_process'
|
|
2
|
+
import { createRequire } from 'module'
|
|
3
|
+
import fs from 'fs'
|
|
4
|
+
import path from 'path'
|
|
5
|
+
import chalk from 'chalk'
|
|
6
|
+
import { PACKAGE_ROOT } from './resolve'
|
|
7
|
+
|
|
8
|
+
const COPY_FILES = ['src', 'source.config.ts', 'tsconfig.json']
|
|
9
|
+
|
|
10
|
+
function copyRecursive(src: string, dest: string) {
|
|
11
|
+
const stat = fs.statSync(src)
|
|
12
|
+
if (stat.isDirectory()) {
|
|
13
|
+
fs.mkdirSync(dest, { recursive: true })
|
|
14
|
+
for (const entry of fs.readdirSync(src)) {
|
|
15
|
+
copyRecursive(path.join(src, entry), path.join(dest, entry))
|
|
16
|
+
}
|
|
17
|
+
} else {
|
|
18
|
+
fs.copyFileSync(src, dest)
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function ensureRemoved(targetPath: string) {
|
|
23
|
+
try {
|
|
24
|
+
fs.lstatSync(targetPath)
|
|
25
|
+
fs.rmSync(targetPath, { recursive: true, force: true })
|
|
26
|
+
} catch {
|
|
27
|
+
// nothing exists, proceed
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export function detectPackageManager(): string {
|
|
32
|
+
if (process.env.npm_config_user_agent) {
|
|
33
|
+
return process.env.npm_config_user_agent.split('/')[0]
|
|
34
|
+
}
|
|
35
|
+
const cwd = process.cwd()
|
|
36
|
+
if (fs.existsSync(path.join(cwd, 'bun.lock')) || fs.existsSync(path.join(cwd, 'bun.lockb'))) return 'bun'
|
|
37
|
+
if (fs.existsSync(path.join(cwd, 'pnpm-lock.yaml'))) return 'pnpm'
|
|
38
|
+
if (fs.existsSync(path.join(cwd, 'yarn.lock'))) return 'yarn'
|
|
39
|
+
return 'npm'
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function generateNextConfig(scaffoldPath: string) {
|
|
43
|
+
const config = `import { createMDX } from 'fumadocs-mdx/next'
|
|
44
|
+
|
|
45
|
+
const withMDX = createMDX()
|
|
46
|
+
|
|
47
|
+
/** @type {import('next').NextConfig} */
|
|
48
|
+
const nextConfig = {
|
|
49
|
+
reactStrictMode: true,
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export default withMDX(nextConfig)
|
|
53
|
+
`
|
|
54
|
+
fs.writeFileSync(path.join(scaffoldPath, 'next.config.mjs'), config)
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function createPackageJson(): Record<string, unknown> {
|
|
58
|
+
return {
|
|
59
|
+
name: 'chronicle-docs',
|
|
60
|
+
private: true,
|
|
61
|
+
dependencies: {
|
|
62
|
+
'@raystack/chronicle': `^${getChronicleVersion()}`,
|
|
63
|
+
},
|
|
64
|
+
devDependencies: {
|
|
65
|
+
'@raystack/tools-config': '0.56.0',
|
|
66
|
+
'openapi-types': '^12.1.3',
|
|
67
|
+
typescript: '5.9.3',
|
|
68
|
+
'@types/react': '^19.2.10',
|
|
69
|
+
'@types/node': '^25.1.0',
|
|
70
|
+
},
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function ensureDeps() {
|
|
75
|
+
const cwd = process.cwd()
|
|
76
|
+
const cwdPkgJson = path.join(cwd, 'package.json')
|
|
77
|
+
const cwdNodeModules = path.join(cwd, 'node_modules')
|
|
78
|
+
|
|
79
|
+
if (fs.existsSync(cwdPkgJson) && fs.existsSync(cwdNodeModules)) {
|
|
80
|
+
// Case 1: existing project with deps installed
|
|
81
|
+
return
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// Case 2: no package.json — create in cwd and install
|
|
85
|
+
if (!fs.existsSync(cwdPkgJson)) {
|
|
86
|
+
fs.writeFileSync(cwdPkgJson, JSON.stringify(createPackageJson(), null, 2) + '\n')
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
if (!fs.existsSync(cwdNodeModules)) {
|
|
90
|
+
const pm = detectPackageManager()
|
|
91
|
+
console.log(chalk.cyan(`Installing dependencies with ${pm}...`))
|
|
92
|
+
execSync(`${pm} install`, { cwd, stdio: 'inherit' })
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
export function getChronicleVersion(): string {
|
|
97
|
+
const pkgPath = path.join(PACKAGE_ROOT, 'package.json')
|
|
98
|
+
const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf-8'))
|
|
99
|
+
return pkg.version
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
export function resolveNextCli(): string {
|
|
103
|
+
const chronicleRequire = createRequire(path.join(PACKAGE_ROOT, 'package.json'))
|
|
104
|
+
return chronicleRequire.resolve('next/dist/bin/next')
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
export function scaffoldDir(contentDir: string): string {
|
|
108
|
+
const scaffoldPath = path.join(process.cwd(), '.chronicle')
|
|
109
|
+
|
|
110
|
+
// Create .chronicle/ if not exists
|
|
111
|
+
if (!fs.existsSync(scaffoldPath)) {
|
|
112
|
+
fs.mkdirSync(scaffoldPath, { recursive: true })
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// Copy package files
|
|
116
|
+
for (const name of COPY_FILES) {
|
|
117
|
+
const src = path.join(PACKAGE_ROOT, name)
|
|
118
|
+
const dest = path.join(scaffoldPath, name)
|
|
119
|
+
ensureRemoved(dest)
|
|
120
|
+
copyRecursive(src, dest)
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// Generate next.config.mjs
|
|
124
|
+
generateNextConfig(scaffoldPath)
|
|
125
|
+
|
|
126
|
+
// Symlink content dir
|
|
127
|
+
const contentLink = path.join(scaffoldPath, 'content')
|
|
128
|
+
ensureRemoved(contentLink)
|
|
129
|
+
fs.symlinkSync(path.resolve(contentDir), contentLink)
|
|
130
|
+
|
|
131
|
+
// Ensure dependencies are available
|
|
132
|
+
ensureDeps()
|
|
133
|
+
|
|
134
|
+
console.log(chalk.gray(`Scaffold: ${scaffoldPath}`))
|
|
135
|
+
|
|
136
|
+
return scaffoldPath
|
|
137
|
+
}
|
|
@@ -102,3 +102,10 @@
|
|
|
102
102
|
.item[data-selected="true"] .icon {
|
|
103
103
|
color: var(--rs-color-foreground-accent-primary-hover);
|
|
104
104
|
}
|
|
105
|
+
|
|
106
|
+
.pageText :global(mark),
|
|
107
|
+
.headingText :global(mark) {
|
|
108
|
+
background: transparent;
|
|
109
|
+
color: var(--rs-color-foreground-accent-primary);
|
|
110
|
+
font-weight: 600;
|
|
111
|
+
}
|
|
@@ -111,7 +111,7 @@ export function Search({ className }: SearchProps) {
|
|
|
111
111
|
<div className={styles.itemContent}>
|
|
112
112
|
{getResultIcon(result)}
|
|
113
113
|
<Text className={styles.pageText}>
|
|
114
|
-
{stripMethod(result.content)}
|
|
114
|
+
<HighlightedText html={stripMethod(result.content)} />
|
|
115
115
|
</Text>
|
|
116
116
|
</div>
|
|
117
117
|
</Command.Item>
|
|
@@ -132,7 +132,7 @@ export function Search({ className }: SearchProps) {
|
|
|
132
132
|
{result.type === "heading" ? (
|
|
133
133
|
<>
|
|
134
134
|
<Text className={styles.headingText}>
|
|
135
|
-
{stripMethod(result.content)}
|
|
135
|
+
<HighlightedText html={stripMethod(result.content)} />
|
|
136
136
|
</Text>
|
|
137
137
|
<Text className={styles.separator}>-</Text>
|
|
138
138
|
<Text className={styles.pageText}>
|
|
@@ -141,7 +141,7 @@ export function Search({ className }: SearchProps) {
|
|
|
141
141
|
</>
|
|
142
142
|
) : (
|
|
143
143
|
<Text className={styles.pageText}>
|
|
144
|
-
{stripMethod(result.content)}
|
|
144
|
+
<HighlightedText html={stripMethod(result.content)} />
|
|
145
145
|
</Text>
|
|
146
146
|
)}
|
|
147
147
|
</div>
|
|
@@ -178,6 +178,10 @@ function stripMethod(content: string): string {
|
|
|
178
178
|
return API_METHODS.has(first) ? content.slice(first.length + 1) : content;
|
|
179
179
|
}
|
|
180
180
|
|
|
181
|
+
function HighlightedText({ html, className }: { html: string; className?: string }) {
|
|
182
|
+
return <span className={className} dangerouslySetInnerHTML={{ __html: html }} />;
|
|
183
|
+
}
|
|
184
|
+
|
|
181
185
|
function getResultIcon(result: SortedResult): React.ReactNode {
|
|
182
186
|
if (!result.url.startsWith("/apis/")) {
|
|
183
187
|
return result.type === "page" ? (
|
package/src/lib/config.ts
CHANGED
|
@@ -11,11 +11,29 @@ const defaultConfig: ChronicleConfig = {
|
|
|
11
11
|
search: { enabled: true, placeholder: 'Search...' },
|
|
12
12
|
}
|
|
13
13
|
|
|
14
|
+
function resolveConfigPath(): string | null {
|
|
15
|
+
// Check project root via env var
|
|
16
|
+
const projectRoot = process.env.CHRONICLE_PROJECT_ROOT
|
|
17
|
+
if (projectRoot) {
|
|
18
|
+
const rootPath = path.join(projectRoot, CONFIG_FILE)
|
|
19
|
+
if (fs.existsSync(rootPath)) return rootPath
|
|
20
|
+
}
|
|
21
|
+
// Check cwd
|
|
22
|
+
const cwdPath = path.join(process.cwd(), CONFIG_FILE)
|
|
23
|
+
if (fs.existsSync(cwdPath)) return cwdPath
|
|
24
|
+
// Check content dir
|
|
25
|
+
const contentDir = process.env.CHRONICLE_CONTENT_DIR
|
|
26
|
+
if (contentDir) {
|
|
27
|
+
const contentPath = path.join(contentDir, CONFIG_FILE)
|
|
28
|
+
if (fs.existsSync(contentPath)) return contentPath
|
|
29
|
+
}
|
|
30
|
+
return null
|
|
31
|
+
}
|
|
32
|
+
|
|
14
33
|
export function loadConfig(): ChronicleConfig {
|
|
15
|
-
const
|
|
16
|
-
const configPath = path.join(dir, CONFIG_FILE)
|
|
34
|
+
const configPath = resolveConfigPath()
|
|
17
35
|
|
|
18
|
-
if (!
|
|
36
|
+
if (!configPath) {
|
|
19
37
|
return defaultConfig
|
|
20
38
|
}
|
|
21
39
|
|
package/src/lib/source.ts
CHANGED
package/tsconfig.json
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"composite": false,
|
|
4
|
+
"declaration": true,
|
|
5
|
+
"declarationMap": true,
|
|
6
|
+
"esModuleInterop": true,
|
|
7
|
+
"forceConsistentCasingInFileNames": true,
|
|
8
|
+
"inlineSources": false,
|
|
9
|
+
"isolatedModules": true,
|
|
10
|
+
"noUnusedLocals": false,
|
|
11
|
+
"noUnusedParameters": false,
|
|
12
|
+
"preserveWatchOutput": true,
|
|
13
|
+
"skipLibCheck": true,
|
|
14
|
+
"strict": true,
|
|
15
|
+
"jsx": "react-jsx",
|
|
16
|
+
"module": "ESNext",
|
|
17
|
+
"target": "es6",
|
|
18
|
+
"outDir": "dist",
|
|
19
|
+
"rootDir": ".",
|
|
20
|
+
"baseUrl": ".",
|
|
21
|
+
"lib": ["ES2022", "DOM", "DOM.Iterable"],
|
|
22
|
+
"moduleResolution": "bundler",
|
|
23
|
+
"paths": {
|
|
24
|
+
"@/*": ["./src/*"],
|
|
25
|
+
"@/.source/*": ["./.source/*"]
|
|
26
|
+
}
|
|
27
|
+
},
|
|
28
|
+
"include": ["src", ".source", "source.config.ts"],
|
|
29
|
+
"exclude": ["node_modules", "dist"]
|
|
30
|
+
}
|