@raystack/chronicle 0.1.3 → 0.3.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/source.config.ts +1 -0
- package/src/app/[[...slug]]/page.tsx +61 -12
- package/src/app/apis/[[...slug]]/page.tsx +60 -0
- package/src/app/layout.tsx +32 -1
- package/src/app/og/route.tsx +62 -0
- package/src/app/robots.ts +10 -0
- package/src/app/sitemap.ts +29 -0
- 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 +22 -3
- package/src/lib/source.ts +1 -1
- package/src/types/config.ts +11 -0
- package/src/types/content.ts +1 -0
- package/tsconfig.json +30 -0
|
@@ -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
|
|
|
@@ -33,5 +51,6 @@ export function loadConfig(): ChronicleConfig {
|
|
|
33
51
|
footer: userConfig.footer,
|
|
34
52
|
api: userConfig.api,
|
|
35
53
|
llms: { enabled: false, ...userConfig.llms },
|
|
54
|
+
analytics: { enabled: false, ...userConfig.analytics },
|
|
36
55
|
}
|
|
37
56
|
}
|
package/src/lib/source.ts
CHANGED
package/src/types/config.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
export interface ChronicleConfig {
|
|
2
2
|
title: string
|
|
3
3
|
description?: string
|
|
4
|
+
url?: string
|
|
4
5
|
logo?: LogoConfig
|
|
5
6
|
theme?: ThemeConfig
|
|
6
7
|
navigation?: NavigationConfig
|
|
@@ -8,12 +9,22 @@ export interface ChronicleConfig {
|
|
|
8
9
|
footer?: FooterConfig
|
|
9
10
|
api?: ApiConfig[]
|
|
10
11
|
llms?: LlmsConfig
|
|
12
|
+
analytics?: AnalyticsConfig
|
|
11
13
|
}
|
|
12
14
|
|
|
13
15
|
export interface LlmsConfig {
|
|
14
16
|
enabled?: boolean
|
|
15
17
|
}
|
|
16
18
|
|
|
19
|
+
export interface AnalyticsConfig {
|
|
20
|
+
enabled?: boolean
|
|
21
|
+
googleAnalytics?: GoogleAnalyticsConfig
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export interface GoogleAnalyticsConfig {
|
|
25
|
+
measurementId: string
|
|
26
|
+
}
|
|
27
|
+
|
|
17
28
|
export interface ApiConfig {
|
|
18
29
|
name: string
|
|
19
30
|
spec: string
|
package/src/types/content.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
|
+
}
|