@raystack/chronicle 0.1.0-canary.30bf0df

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.
Files changed (107) hide show
  1. package/bin/chronicle.js +2 -0
  2. package/dist/cli/index.js +9988 -0
  3. package/next.config.mjs +10 -0
  4. package/package.json +63 -0
  5. package/source.config.ts +50 -0
  6. package/src/app/[[...slug]]/layout.tsx +15 -0
  7. package/src/app/[[...slug]]/page.tsx +57 -0
  8. package/src/app/api/apis-proxy/route.ts +59 -0
  9. package/src/app/api/health/route.ts +3 -0
  10. package/src/app/api/search/route.ts +90 -0
  11. package/src/app/apis/[[...slug]]/layout.module.css +22 -0
  12. package/src/app/apis/[[...slug]]/layout.tsx +26 -0
  13. package/src/app/apis/[[...slug]]/page.tsx +57 -0
  14. package/src/app/layout.tsx +26 -0
  15. package/src/app/llms-full.txt/route.ts +18 -0
  16. package/src/app/llms.txt/route.ts +15 -0
  17. package/src/app/providers.tsx +8 -0
  18. package/src/cli/commands/build.ts +32 -0
  19. package/src/cli/commands/dev.ts +33 -0
  20. package/src/cli/commands/init.ts +163 -0
  21. package/src/cli/commands/serve.ts +53 -0
  22. package/src/cli/commands/start.ts +33 -0
  23. package/src/cli/index.ts +21 -0
  24. package/src/cli/utils/config.ts +43 -0
  25. package/src/cli/utils/index.ts +3 -0
  26. package/src/cli/utils/process.ts +7 -0
  27. package/src/cli/utils/resolve.ts +4 -0
  28. package/src/cli/utils/scaffold.ts +131 -0
  29. package/src/components/api/code-snippets.module.css +7 -0
  30. package/src/components/api/code-snippets.tsx +76 -0
  31. package/src/components/api/endpoint-page.module.css +58 -0
  32. package/src/components/api/endpoint-page.tsx +283 -0
  33. package/src/components/api/field-row.module.css +126 -0
  34. package/src/components/api/field-row.tsx +204 -0
  35. package/src/components/api/field-section.module.css +24 -0
  36. package/src/components/api/field-section.tsx +100 -0
  37. package/src/components/api/index.ts +8 -0
  38. package/src/components/api/json-editor.module.css +9 -0
  39. package/src/components/api/json-editor.tsx +61 -0
  40. package/src/components/api/key-value-editor.module.css +13 -0
  41. package/src/components/api/key-value-editor.tsx +62 -0
  42. package/src/components/api/method-badge.module.css +4 -0
  43. package/src/components/api/method-badge.tsx +29 -0
  44. package/src/components/api/response-panel.module.css +8 -0
  45. package/src/components/api/response-panel.tsx +44 -0
  46. package/src/components/common/breadcrumb.tsx +3 -0
  47. package/src/components/common/button.tsx +3 -0
  48. package/src/components/common/callout.module.css +7 -0
  49. package/src/components/common/callout.tsx +27 -0
  50. package/src/components/common/code-block.tsx +3 -0
  51. package/src/components/common/dialog.tsx +3 -0
  52. package/src/components/common/index.ts +10 -0
  53. package/src/components/common/input-field.tsx +3 -0
  54. package/src/components/common/sidebar.tsx +3 -0
  55. package/src/components/common/switch.tsx +3 -0
  56. package/src/components/common/table.tsx +3 -0
  57. package/src/components/common/tabs.tsx +3 -0
  58. package/src/components/mdx/code.module.css +42 -0
  59. package/src/components/mdx/code.tsx +27 -0
  60. package/src/components/mdx/details.module.css +37 -0
  61. package/src/components/mdx/details.tsx +18 -0
  62. package/src/components/mdx/image.tsx +38 -0
  63. package/src/components/mdx/index.tsx +35 -0
  64. package/src/components/mdx/link.tsx +38 -0
  65. package/src/components/mdx/mermaid.module.css +9 -0
  66. package/src/components/mdx/mermaid.tsx +37 -0
  67. package/src/components/mdx/paragraph.module.css +8 -0
  68. package/src/components/mdx/paragraph.tsx +19 -0
  69. package/src/components/mdx/table.tsx +40 -0
  70. package/src/components/ui/breadcrumbs.tsx +72 -0
  71. package/src/components/ui/client-theme-switcher.tsx +18 -0
  72. package/src/components/ui/footer.module.css +27 -0
  73. package/src/components/ui/footer.tsx +30 -0
  74. package/src/components/ui/search.module.css +104 -0
  75. package/src/components/ui/search.tsx +202 -0
  76. package/src/lib/api-routes.ts +120 -0
  77. package/src/lib/config.ts +55 -0
  78. package/src/lib/get-llm-text.ts +10 -0
  79. package/src/lib/index.ts +2 -0
  80. package/src/lib/openapi.ts +188 -0
  81. package/src/lib/remark-unused-directives.ts +30 -0
  82. package/src/lib/schema.ts +99 -0
  83. package/src/lib/snippet-generators.ts +87 -0
  84. package/src/lib/source.ts +67 -0
  85. package/src/themes/default/Layout.module.css +81 -0
  86. package/src/themes/default/Layout.tsx +133 -0
  87. package/src/themes/default/Page.module.css +46 -0
  88. package/src/themes/default/Page.tsx +21 -0
  89. package/src/themes/default/Toc.module.css +48 -0
  90. package/src/themes/default/Toc.tsx +66 -0
  91. package/src/themes/default/font.ts +6 -0
  92. package/src/themes/default/index.ts +13 -0
  93. package/src/themes/paper/ChapterNav.module.css +71 -0
  94. package/src/themes/paper/ChapterNav.tsx +96 -0
  95. package/src/themes/paper/Layout.module.css +33 -0
  96. package/src/themes/paper/Layout.tsx +25 -0
  97. package/src/themes/paper/Page.module.css +174 -0
  98. package/src/themes/paper/Page.tsx +107 -0
  99. package/src/themes/paper/ReadingProgress.module.css +132 -0
  100. package/src/themes/paper/ReadingProgress.tsx +294 -0
  101. package/src/themes/paper/index.ts +8 -0
  102. package/src/themes/registry.ts +14 -0
  103. package/src/types/config.ts +69 -0
  104. package/src/types/content.ts +35 -0
  105. package/src/types/index.ts +3 -0
  106. package/src/types/theme.ts +22 -0
  107. package/tsconfig.json +30 -0
@@ -0,0 +1,163 @@
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
+ type: 'module',
25
+ scripts: {
26
+ dev: 'chronicle dev',
27
+ build: 'chronicle build',
28
+ start: 'chronicle start',
29
+ },
30
+ dependencies: {
31
+ '@raystack/chronicle': 'latest',
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
+
43
+ const sampleMdx = `---
44
+ title: Welcome
45
+ description: Getting started with your documentation
46
+ order: 1
47
+ ---
48
+
49
+ # Welcome
50
+
51
+ This is your documentation home page.
52
+ `
53
+
54
+ export const initCommand = new Command('init')
55
+ .description('Initialize a new Chronicle project')
56
+ .option('-c, --content <path>', 'Content directory name', 'content')
57
+ .action((options) => {
58
+ const projectDir = process.cwd()
59
+ const dirName = path.basename(projectDir) || 'docs'
60
+ const contentDir = path.join(projectDir, options.content)
61
+
62
+ // Create content directory if it doesn't exist
63
+ if (!fs.existsSync(contentDir)) {
64
+ fs.mkdirSync(contentDir, { recursive: true })
65
+ console.log(chalk.green('✓'), 'Created', contentDir)
66
+ }
67
+
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')
121
+ if (!fs.existsSync(configPath)) {
122
+ fs.writeFileSync(configPath, stringify(createConfig()))
123
+ console.log(chalk.green('✓'), 'Created', configPath)
124
+ } else {
125
+ console.log(chalk.yellow('⚠'), configPath, 'already exists')
126
+ }
127
+
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')
132
+ fs.writeFileSync(indexPath, sampleMdx)
133
+ console.log(chalk.green('✓'), 'Created', indexPath)
134
+ }
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`
161
+ console.log(chalk.green('\n✓ Chronicle initialized!'))
162
+ console.log('\nRun', chalk.cyan(`${runCmd} chronicle dev`), 'to start development server')
163
+ })
@@ -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
+ })
@@ -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,3 @@
1
+ export * from './config'
2
+ export * from './process'
3
+ export * from './scaffold'
@@ -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,4 @@
1
+ import path from 'path'
2
+ import { fileURLToPath } from 'url'
3
+
4
+ export const PACKAGE_ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..', '..')
@@ -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,7 @@
1
+ .snippets {
2
+ width: 100%;
3
+ }
4
+
5
+ .snippets :global([class*="code-block-module_header"]) {
6
+ justify-content: space-between;
7
+ }
@@ -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
+ }