@raystack/chronicle 0.1.0-canary.5a2be79
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/bin/chronicle.js +2 -0
- package/dist/cli/index.js +9980 -0
- package/next.config.mjs +10 -0
- package/package.json +63 -0
- package/source.config.ts +50 -0
- package/src/app/[[...slug]]/layout.tsx +15 -0
- package/src/app/[[...slug]]/page.tsx +57 -0
- package/src/app/api/apis-proxy/route.ts +59 -0
- package/src/app/api/health/route.ts +3 -0
- package/src/app/api/search/route.ts +90 -0
- package/src/app/apis/[[...slug]]/layout.module.css +22 -0
- package/src/app/apis/[[...slug]]/layout.tsx +26 -0
- package/src/app/apis/[[...slug]]/page.tsx +57 -0
- package/src/app/layout.tsx +26 -0
- package/src/app/llms-full.txt/route.ts +18 -0
- package/src/app/llms.txt/route.ts +15 -0
- package/src/app/providers.tsx +8 -0
- package/src/cli/commands/build.ts +32 -0
- package/src/cli/commands/dev.ts +33 -0
- package/src/cli/commands/init.ts +155 -0
- package/src/cli/commands/serve.ts +53 -0
- package/src/cli/commands/start.ts +33 -0
- package/src/cli/index.ts +21 -0
- package/src/cli/utils/config.ts +43 -0
- package/src/cli/utils/index.ts +3 -0
- package/src/cli/utils/process.ts +7 -0
- package/src/cli/utils/resolve.ts +4 -0
- package/src/cli/utils/scaffold.ts +131 -0
- package/src/components/api/code-snippets.module.css +7 -0
- package/src/components/api/code-snippets.tsx +76 -0
- package/src/components/api/endpoint-page.module.css +58 -0
- package/src/components/api/endpoint-page.tsx +283 -0
- package/src/components/api/field-row.module.css +126 -0
- package/src/components/api/field-row.tsx +204 -0
- package/src/components/api/field-section.module.css +24 -0
- package/src/components/api/field-section.tsx +100 -0
- package/src/components/api/index.ts +8 -0
- package/src/components/api/json-editor.module.css +9 -0
- package/src/components/api/json-editor.tsx +61 -0
- package/src/components/api/key-value-editor.module.css +13 -0
- package/src/components/api/key-value-editor.tsx +62 -0
- package/src/components/api/method-badge.module.css +4 -0
- package/src/components/api/method-badge.tsx +29 -0
- package/src/components/api/response-panel.module.css +8 -0
- package/src/components/api/response-panel.tsx +44 -0
- package/src/components/common/breadcrumb.tsx +3 -0
- package/src/components/common/button.tsx +3 -0
- package/src/components/common/callout.module.css +7 -0
- package/src/components/common/callout.tsx +27 -0
- package/src/components/common/code-block.tsx +3 -0
- package/src/components/common/dialog.tsx +3 -0
- package/src/components/common/index.ts +10 -0
- package/src/components/common/input-field.tsx +3 -0
- package/src/components/common/sidebar.tsx +3 -0
- package/src/components/common/switch.tsx +3 -0
- package/src/components/common/table.tsx +3 -0
- package/src/components/common/tabs.tsx +3 -0
- package/src/components/mdx/code.module.css +42 -0
- package/src/components/mdx/code.tsx +27 -0
- package/src/components/mdx/details.module.css +37 -0
- package/src/components/mdx/details.tsx +18 -0
- package/src/components/mdx/image.tsx +38 -0
- package/src/components/mdx/index.tsx +35 -0
- package/src/components/mdx/link.tsx +38 -0
- package/src/components/mdx/mermaid.module.css +9 -0
- package/src/components/mdx/mermaid.tsx +37 -0
- package/src/components/mdx/paragraph.module.css +8 -0
- package/src/components/mdx/paragraph.tsx +19 -0
- package/src/components/mdx/table.tsx +40 -0
- package/src/components/ui/breadcrumbs.tsx +72 -0
- package/src/components/ui/client-theme-switcher.tsx +18 -0
- package/src/components/ui/footer.module.css +27 -0
- package/src/components/ui/footer.tsx +30 -0
- package/src/components/ui/search.module.css +104 -0
- package/src/components/ui/search.tsx +202 -0
- package/src/lib/api-routes.ts +120 -0
- package/src/lib/config.ts +55 -0
- package/src/lib/get-llm-text.ts +10 -0
- package/src/lib/index.ts +2 -0
- package/src/lib/openapi.ts +188 -0
- package/src/lib/remark-unused-directives.ts +30 -0
- package/src/lib/schema.ts +99 -0
- package/src/lib/snippet-generators.ts +87 -0
- package/src/lib/source.ts +67 -0
- package/src/themes/default/Layout.module.css +81 -0
- package/src/themes/default/Layout.tsx +133 -0
- package/src/themes/default/Page.module.css +46 -0
- package/src/themes/default/Page.tsx +21 -0
- package/src/themes/default/Toc.module.css +48 -0
- package/src/themes/default/Toc.tsx +66 -0
- package/src/themes/default/font.ts +6 -0
- package/src/themes/default/index.ts +13 -0
- package/src/themes/paper/ChapterNav.module.css +71 -0
- package/src/themes/paper/ChapterNav.tsx +96 -0
- package/src/themes/paper/Layout.module.css +33 -0
- package/src/themes/paper/Layout.tsx +25 -0
- package/src/themes/paper/Page.module.css +174 -0
- package/src/themes/paper/Page.tsx +107 -0
- package/src/themes/paper/ReadingProgress.module.css +132 -0
- package/src/themes/paper/ReadingProgress.tsx +294 -0
- package/src/themes/paper/index.ts +8 -0
- package/src/themes/registry.ts +14 -0
- package/src/types/config.ts +69 -0
- package/src/types/content.ts +35 -0
- package/src/types/index.ts +3 -0
- package/src/types/theme.ts +22 -0
- package/tsconfig.json +30 -0
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
import { Command } from 'commander'
|
|
2
|
+
import { execSync } from 'child_process'
|
|
3
|
+
import fs from 'fs'
|
|
4
|
+
import path from 'path'
|
|
5
|
+
import chalk from 'chalk'
|
|
6
|
+
import { stringify } from 'yaml'
|
|
7
|
+
import type { ChronicleConfig } from '@/types'
|
|
8
|
+
import { loadCLIConfig, scaffoldDir, detectPackageManager } from '@/cli/utils'
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
function createConfig(): ChronicleConfig {
|
|
12
|
+
return {
|
|
13
|
+
title: 'My Documentation',
|
|
14
|
+
description: 'Documentation powered by Chronicle',
|
|
15
|
+
theme: { name: 'default' },
|
|
16
|
+
search: { enabled: true, placeholder: 'Search documentation...' },
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function createPackageJson(name: string): Record<string, unknown> {
|
|
21
|
+
return {
|
|
22
|
+
name,
|
|
23
|
+
private: true,
|
|
24
|
+
scripts: {
|
|
25
|
+
dev: 'chronicle dev',
|
|
26
|
+
build: 'chronicle build',
|
|
27
|
+
start: 'chronicle start',
|
|
28
|
+
},
|
|
29
|
+
dependencies: {
|
|
30
|
+
'@raystack/chronicle': 'latest',
|
|
31
|
+
},
|
|
32
|
+
devDependencies: {
|
|
33
|
+
'@raystack/tools-config': '0.56.0',
|
|
34
|
+
'openapi-types': '^12.1.3',
|
|
35
|
+
typescript: '5.9.3',
|
|
36
|
+
'@types/react': '^19.2.10',
|
|
37
|
+
'@types/node': '^25.1.0',
|
|
38
|
+
},
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const sampleMdx = `---
|
|
43
|
+
title: Welcome
|
|
44
|
+
description: Getting started with your documentation
|
|
45
|
+
order: 1
|
|
46
|
+
---
|
|
47
|
+
|
|
48
|
+
# Welcome
|
|
49
|
+
|
|
50
|
+
This is your documentation home page.
|
|
51
|
+
`
|
|
52
|
+
|
|
53
|
+
export const initCommand = new Command('init')
|
|
54
|
+
.description('Initialize a new Chronicle project')
|
|
55
|
+
.option('-c, --content <path>', 'Content directory name', 'content')
|
|
56
|
+
.action((options) => {
|
|
57
|
+
const projectDir = process.cwd()
|
|
58
|
+
const dirName = path.basename(projectDir) || 'docs'
|
|
59
|
+
const contentDir = path.join(projectDir, options.content)
|
|
60
|
+
|
|
61
|
+
// Create content directory if it doesn't exist
|
|
62
|
+
if (!fs.existsSync(contentDir)) {
|
|
63
|
+
fs.mkdirSync(contentDir, { recursive: true })
|
|
64
|
+
console.log(chalk.green('✓'), 'Created', contentDir)
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// Create or update package.json in project root
|
|
68
|
+
const packageJsonPath = path.join(projectDir, 'package.json')
|
|
69
|
+
if (!fs.existsSync(packageJsonPath)) {
|
|
70
|
+
fs.writeFileSync(packageJsonPath, JSON.stringify(createPackageJson(dirName), null, 2) + '\n')
|
|
71
|
+
console.log(chalk.green('✓'), 'Created', packageJsonPath)
|
|
72
|
+
} else {
|
|
73
|
+
const existing = JSON.parse(fs.readFileSync(packageJsonPath, 'utf-8'))
|
|
74
|
+
const template = createPackageJson(dirName)
|
|
75
|
+
let updated = false
|
|
76
|
+
|
|
77
|
+
// Merge missing scripts
|
|
78
|
+
if (!existing.scripts) existing.scripts = {}
|
|
79
|
+
for (const [key, value] of Object.entries(template.scripts as Record<string, string>)) {
|
|
80
|
+
if (!existing.scripts[key]) {
|
|
81
|
+
existing.scripts[key] = value
|
|
82
|
+
updated = true
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// Merge missing dependencies
|
|
87
|
+
if (!existing.dependencies) existing.dependencies = {}
|
|
88
|
+
for (const [key, value] of Object.entries(template.dependencies as Record<string, string>)) {
|
|
89
|
+
if (!existing.dependencies[key]) {
|
|
90
|
+
existing.dependencies[key] = value
|
|
91
|
+
updated = true
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// Merge missing devDependencies
|
|
96
|
+
if (!existing.devDependencies) existing.devDependencies = {}
|
|
97
|
+
for (const [key, value] of Object.entries(template.devDependencies as Record<string, string>)) {
|
|
98
|
+
if (!existing.devDependencies[key]) {
|
|
99
|
+
existing.devDependencies[key] = value
|
|
100
|
+
updated = true
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
if (updated) {
|
|
105
|
+
fs.writeFileSync(packageJsonPath, JSON.stringify(existing, null, 2) + '\n')
|
|
106
|
+
console.log(chalk.green('✓'), 'Updated', packageJsonPath, 'with missing scripts/deps')
|
|
107
|
+
} else {
|
|
108
|
+
console.log(chalk.yellow('⚠'), packageJsonPath, 'already has all required entries')
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// Create chronicle.yaml in project root
|
|
113
|
+
const configPath = path.join(projectDir, 'chronicle.yaml')
|
|
114
|
+
if (!fs.existsSync(configPath)) {
|
|
115
|
+
fs.writeFileSync(configPath, stringify(createConfig()))
|
|
116
|
+
console.log(chalk.green('✓'), 'Created', configPath)
|
|
117
|
+
} else {
|
|
118
|
+
console.log(chalk.yellow('⚠'), configPath, 'already exists')
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// Create sample index.mdx only if content dir is empty
|
|
122
|
+
const contentFiles = fs.readdirSync(contentDir)
|
|
123
|
+
if (contentFiles.length === 0) {
|
|
124
|
+
const indexPath = path.join(contentDir, 'index.mdx')
|
|
125
|
+
fs.writeFileSync(indexPath, sampleMdx)
|
|
126
|
+
console.log(chalk.green('✓'), 'Created', indexPath)
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// Add .chronicle to .gitignore
|
|
130
|
+
const gitignorePath = path.join(projectDir, '.gitignore')
|
|
131
|
+
const chronicleEntry = '.chronicle'
|
|
132
|
+
if (fs.existsSync(gitignorePath)) {
|
|
133
|
+
const existing = fs.readFileSync(gitignorePath, 'utf-8')
|
|
134
|
+
if (!existing.includes(chronicleEntry)) {
|
|
135
|
+
fs.appendFileSync(gitignorePath, `\n${chronicleEntry}\n`)
|
|
136
|
+
console.log(chalk.green('✓'), 'Added .chronicle to .gitignore')
|
|
137
|
+
}
|
|
138
|
+
} else {
|
|
139
|
+
fs.writeFileSync(gitignorePath, `${chronicleEntry}\n`)
|
|
140
|
+
console.log(chalk.green('✓'), 'Created .gitignore with .chronicle')
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// Install dependencies
|
|
144
|
+
const pm = detectPackageManager()
|
|
145
|
+
console.log(chalk.cyan(`\nInstalling dependencies with ${pm}...`))
|
|
146
|
+
execSync(`${pm} install`, { cwd: projectDir, stdio: 'inherit' })
|
|
147
|
+
|
|
148
|
+
// Scaffold .chronicle/ directory
|
|
149
|
+
loadCLIConfig(contentDir)
|
|
150
|
+
scaffoldDir(contentDir)
|
|
151
|
+
|
|
152
|
+
const runCmd = pm === 'npm' ? 'npx' : pm === 'bun' ? 'bunx' : `${pm} dlx`
|
|
153
|
+
console.log(chalk.green('\n✓ Chronicle initialized!'))
|
|
154
|
+
console.log('\nRun', chalk.cyan(`${runCmd} chronicle dev`), 'to start development server')
|
|
155
|
+
})
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { Command } from 'commander'
|
|
2
|
+
import { spawn } from 'child_process'
|
|
3
|
+
import path from 'path'
|
|
4
|
+
import fs from 'fs'
|
|
5
|
+
import chalk from 'chalk'
|
|
6
|
+
import { attachLifecycleHandlers, resolveNextCli } from '@/cli/utils'
|
|
7
|
+
|
|
8
|
+
export const serveCommand = new Command('serve')
|
|
9
|
+
.description('Build and start production server')
|
|
10
|
+
.option('-p, --port <port>', 'Port number', '3000')
|
|
11
|
+
.action((options) => {
|
|
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
|
+
const nextCli = resolveNextCli()
|
|
19
|
+
|
|
20
|
+
const env = {
|
|
21
|
+
...process.env,
|
|
22
|
+
CHRONICLE_PROJECT_ROOT: process.cwd(),
|
|
23
|
+
CHRONICLE_CONTENT_DIR: './content',
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
console.log(chalk.cyan('Building for production...'))
|
|
27
|
+
|
|
28
|
+
const buildChild = spawn(process.execPath, [nextCli, 'build'], {
|
|
29
|
+
stdio: 'inherit',
|
|
30
|
+
cwd: scaffoldPath,
|
|
31
|
+
env,
|
|
32
|
+
})
|
|
33
|
+
|
|
34
|
+
process.once('SIGINT', () => buildChild.kill('SIGINT'))
|
|
35
|
+
process.once('SIGTERM', () => buildChild.kill('SIGTERM'))
|
|
36
|
+
|
|
37
|
+
buildChild.on('close', (code) => {
|
|
38
|
+
if (code !== 0) {
|
|
39
|
+
console.log(chalk.red('Build failed'))
|
|
40
|
+
process.exit(code ?? 1)
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
console.log(chalk.cyan('Starting production server...'))
|
|
44
|
+
|
|
45
|
+
const startChild = spawn(process.execPath, [nextCli, 'start', '-p', options.port], {
|
|
46
|
+
stdio: 'inherit',
|
|
47
|
+
cwd: scaffoldPath,
|
|
48
|
+
env,
|
|
49
|
+
})
|
|
50
|
+
|
|
51
|
+
attachLifecycleHandlers(startChild)
|
|
52
|
+
})
|
|
53
|
+
})
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { Command } from 'commander'
|
|
2
|
+
import { spawn } from 'child_process'
|
|
3
|
+
import path from 'path'
|
|
4
|
+
import fs from 'fs'
|
|
5
|
+
import chalk from 'chalk'
|
|
6
|
+
import { attachLifecycleHandlers, resolveNextCli } from '@/cli/utils'
|
|
7
|
+
|
|
8
|
+
export const startCommand = new Command('start')
|
|
9
|
+
.description('Start production server')
|
|
10
|
+
.option('-p, --port <port>', 'Port number', '3000')
|
|
11
|
+
.action((options) => {
|
|
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
|
+
const nextCli = resolveNextCli()
|
|
19
|
+
|
|
20
|
+
console.log(chalk.cyan('Starting production server...'))
|
|
21
|
+
|
|
22
|
+
const child = spawn(process.execPath, [nextCli, 'start', '-p', options.port], {
|
|
23
|
+
stdio: 'inherit',
|
|
24
|
+
cwd: scaffoldPath,
|
|
25
|
+
env: {
|
|
26
|
+
...process.env,
|
|
27
|
+
CHRONICLE_PROJECT_ROOT: process.cwd(),
|
|
28
|
+
CHRONICLE_CONTENT_DIR: './content',
|
|
29
|
+
},
|
|
30
|
+
})
|
|
31
|
+
|
|
32
|
+
attachLifecycleHandlers(child)
|
|
33
|
+
})
|
package/src/cli/index.ts
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { Command } from 'commander'
|
|
2
|
+
import { initCommand } from './commands/init'
|
|
3
|
+
import { devCommand } from './commands/dev'
|
|
4
|
+
import { buildCommand } from './commands/build'
|
|
5
|
+
import { startCommand } from './commands/start'
|
|
6
|
+
import { serveCommand } from './commands/serve'
|
|
7
|
+
|
|
8
|
+
const program = new Command()
|
|
9
|
+
|
|
10
|
+
program
|
|
11
|
+
.name('chronicle')
|
|
12
|
+
.description('Config-driven documentation framework')
|
|
13
|
+
.version('0.1.0')
|
|
14
|
+
|
|
15
|
+
program.addCommand(initCommand)
|
|
16
|
+
program.addCommand(devCommand)
|
|
17
|
+
program.addCommand(buildCommand)
|
|
18
|
+
program.addCommand(startCommand)
|
|
19
|
+
program.addCommand(serveCommand)
|
|
20
|
+
|
|
21
|
+
program.parse()
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import fs from 'fs'
|
|
2
|
+
import path from 'path'
|
|
3
|
+
import { parse } from 'yaml'
|
|
4
|
+
import chalk from 'chalk'
|
|
5
|
+
import type { ChronicleConfig } from '@/types'
|
|
6
|
+
|
|
7
|
+
export interface CLIConfig {
|
|
8
|
+
config: ChronicleConfig
|
|
9
|
+
configPath: string
|
|
10
|
+
contentDir: string
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export function resolveContentDir(contentFlag?: string): string {
|
|
14
|
+
if (contentFlag) return path.resolve(contentFlag)
|
|
15
|
+
if (process.env.CHRONICLE_CONTENT_DIR) return path.resolve(process.env.CHRONICLE_CONTENT_DIR)
|
|
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
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export function loadCLIConfig(contentDir: string): CLIConfig {
|
|
28
|
+
const configPath = resolveConfigPath(contentDir)
|
|
29
|
+
|
|
30
|
+
if (!configPath) {
|
|
31
|
+
console.log(chalk.red('Error: chronicle.yaml not found in'), process.cwd(), 'or', contentDir)
|
|
32
|
+
console.log(chalk.gray(`Run 'chronicle init' to create one`))
|
|
33
|
+
process.exit(1)
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const config = parse(fs.readFileSync(configPath, 'utf-8')) as ChronicleConfig
|
|
37
|
+
|
|
38
|
+
return {
|
|
39
|
+
config,
|
|
40
|
+
configPath,
|
|
41
|
+
contentDir,
|
|
42
|
+
}
|
|
43
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { ChildProcess } from 'child_process'
|
|
2
|
+
|
|
3
|
+
export function attachLifecycleHandlers(child: ChildProcess) {
|
|
4
|
+
child.on('close', (code) => process.exit(code ?? 0))
|
|
5
|
+
process.on('SIGINT', () => child.kill('SIGINT'))
|
|
6
|
+
process.on('SIGTERM', () => child.kill('SIGTERM'))
|
|
7
|
+
}
|
|
@@ -0,0 +1,131 @@
|
|
|
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': 'latest',
|
|
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 resolveNextCli(): string {
|
|
97
|
+
const cwdRequire = createRequire(path.join(process.cwd(), 'package.json'))
|
|
98
|
+
return cwdRequire.resolve('next/dist/bin/next')
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
export function scaffoldDir(contentDir: string): string {
|
|
102
|
+
const scaffoldPath = path.join(process.cwd(), '.chronicle')
|
|
103
|
+
|
|
104
|
+
// Create .chronicle/ if not exists
|
|
105
|
+
if (!fs.existsSync(scaffoldPath)) {
|
|
106
|
+
fs.mkdirSync(scaffoldPath, { recursive: true })
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// Copy package files
|
|
110
|
+
for (const name of COPY_FILES) {
|
|
111
|
+
const src = path.join(PACKAGE_ROOT, name)
|
|
112
|
+
const dest = path.join(scaffoldPath, name)
|
|
113
|
+
ensureRemoved(dest)
|
|
114
|
+
copyRecursive(src, dest)
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// Generate next.config.mjs
|
|
118
|
+
generateNextConfig(scaffoldPath)
|
|
119
|
+
|
|
120
|
+
// Symlink content dir
|
|
121
|
+
const contentLink = path.join(scaffoldPath, 'content')
|
|
122
|
+
ensureRemoved(contentLink)
|
|
123
|
+
fs.symlinkSync(path.resolve(contentDir), contentLink)
|
|
124
|
+
|
|
125
|
+
// Ensure dependencies are available
|
|
126
|
+
ensureDeps()
|
|
127
|
+
|
|
128
|
+
console.log(chalk.gray(`Scaffold: ${scaffoldPath}`))
|
|
129
|
+
|
|
130
|
+
return scaffoldPath
|
|
131
|
+
}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { useMemo, useState } from "react";
|
|
4
|
+
import { CodeBlock } from "@raystack/apsara";
|
|
5
|
+
import {
|
|
6
|
+
generateCurl,
|
|
7
|
+
generatePython,
|
|
8
|
+
generateGo,
|
|
9
|
+
generateTypeScript,
|
|
10
|
+
} from "@/lib/snippet-generators";
|
|
11
|
+
import styles from "./code-snippets.module.css";
|
|
12
|
+
|
|
13
|
+
interface CodeSnippetsProps {
|
|
14
|
+
method: string;
|
|
15
|
+
url: string;
|
|
16
|
+
headers: Record<string, string>;
|
|
17
|
+
body?: string;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const languages = [
|
|
21
|
+
{ value: "curl", label: "cURL", lang: "curl", generate: generateCurl },
|
|
22
|
+
{
|
|
23
|
+
value: "python",
|
|
24
|
+
label: "Python",
|
|
25
|
+
lang: "python",
|
|
26
|
+
generate: generatePython,
|
|
27
|
+
},
|
|
28
|
+
{ value: "go", label: "Go", lang: "go", generate: generateGo },
|
|
29
|
+
{
|
|
30
|
+
value: "typescript",
|
|
31
|
+
label: "TypeScript",
|
|
32
|
+
lang: "typescript",
|
|
33
|
+
generate: generateTypeScript,
|
|
34
|
+
},
|
|
35
|
+
];
|
|
36
|
+
|
|
37
|
+
export function CodeSnippets({
|
|
38
|
+
method,
|
|
39
|
+
url,
|
|
40
|
+
headers,
|
|
41
|
+
body,
|
|
42
|
+
}: CodeSnippetsProps) {
|
|
43
|
+
const opts = { method, url, headers, body };
|
|
44
|
+
const [selected, setSelected] = useState("curl");
|
|
45
|
+
const current = languages.find((l) => l.value === selected) ?? languages[0];
|
|
46
|
+
|
|
47
|
+
const code = useMemo(
|
|
48
|
+
() => current.generate(opts),
|
|
49
|
+
[selected, method, url, headers, body],
|
|
50
|
+
);
|
|
51
|
+
|
|
52
|
+
return (
|
|
53
|
+
<CodeBlock
|
|
54
|
+
value={selected}
|
|
55
|
+
onValueChange={setSelected}
|
|
56
|
+
className={styles.snippets}
|
|
57
|
+
>
|
|
58
|
+
<CodeBlock.Header>
|
|
59
|
+
<CodeBlock.LanguageSelect>
|
|
60
|
+
<CodeBlock.LanguageSelectTrigger />
|
|
61
|
+
<CodeBlock.LanguageSelectContent>
|
|
62
|
+
{languages.map((l) => (
|
|
63
|
+
<CodeBlock.LanguageSelectItem key={l.value} value={l.value}>
|
|
64
|
+
{l.label}
|
|
65
|
+
</CodeBlock.LanguageSelectItem>
|
|
66
|
+
))}
|
|
67
|
+
</CodeBlock.LanguageSelectContent>
|
|
68
|
+
</CodeBlock.LanguageSelect>
|
|
69
|
+
<CodeBlock.CopyButton />
|
|
70
|
+
</CodeBlock.Header>
|
|
71
|
+
<CodeBlock.Content>
|
|
72
|
+
<CodeBlock.Code language={current.lang}>{code}</CodeBlock.Code>
|
|
73
|
+
</CodeBlock.Content>
|
|
74
|
+
</CodeBlock>
|
|
75
|
+
);
|
|
76
|
+
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
.layout {
|
|
2
|
+
display: grid;
|
|
3
|
+
grid-template-columns: 1fr 1fr;
|
|
4
|
+
gap: var(--rs-space-9);
|
|
5
|
+
padding: var(--rs-space-7);
|
|
6
|
+
max-width: 1400px;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
.left {
|
|
10
|
+
gap: var(--rs-space-7);
|
|
11
|
+
min-width: 0;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
.right {
|
|
15
|
+
gap: var(--rs-space-5);
|
|
16
|
+
min-width: 0;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
.title {
|
|
20
|
+
margin: 0;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
.description {
|
|
24
|
+
color: var(--rs-color-foreground-base-secondary);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
.methodPath {
|
|
28
|
+
gap: var(--rs-space-3);
|
|
29
|
+
padding: var(--rs-space-4) var(--rs-space-5);
|
|
30
|
+
border: 1px solid var(--rs-color-border-base-primary);
|
|
31
|
+
border-radius: 8px;
|
|
32
|
+
background: var(--rs-color-background-base-secondary);
|
|
33
|
+
overflow: hidden;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
.path {
|
|
37
|
+
font-family: monospace;
|
|
38
|
+
flex: 1;
|
|
39
|
+
min-width: 0;
|
|
40
|
+
overflow: hidden;
|
|
41
|
+
text-overflow: ellipsis;
|
|
42
|
+
white-space: nowrap;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
.tryButton {
|
|
46
|
+
margin-left: auto;
|
|
47
|
+
flex-shrink: 0;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
@media (max-width: 900px) {
|
|
51
|
+
.layout {
|
|
52
|
+
grid-template-columns: 1fr;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
.right {
|
|
56
|
+
position: static;
|
|
57
|
+
}
|
|
58
|
+
}
|