@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.
@@ -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 dir = process.env.CHRONICLE_CONTENT_DIR ?? process.cwd()
16
- const configPath = path.join(dir, CONFIG_FILE)
34
+ const configPath = resolveConfigPath()
17
35
 
18
- if (!fs.existsSync(configPath)) {
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
@@ -1,4 +1,4 @@
1
- import { docs } from '../../.source/server'
1
+ import { docs } from '@/.source/server'
2
2
  import { loader } from 'fumadocs-core/source'
3
3
  import type { PageTree, PageTreeItem, Frontmatter } from '@/types'
4
4
 
@@ -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
@@ -5,6 +5,7 @@ export interface Frontmatter {
5
5
  description?: string
6
6
  order?: number
7
7
  icon?: string
8
+ lastModified?: string
8
9
  }
9
10
 
10
11
  export interface Page {
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
+ }