@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.
Files changed (107) hide show
  1. package/bin/chronicle.js +2 -0
  2. package/dist/cli/index.js +9980 -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 +155 -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,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
+ })
@@ -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
+ }