Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions .dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
node_modules
**/node_modules
.next
**/.next
.git
*.log
**/.source
41 changes: 41 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
FROM node:24-slim AS base
ENV PNPM_HOME="/pnpm"
ENV PATH="$PNPM_HOME:$PATH"
RUN corepack enable

# --- deps ---
FROM base AS deps
WORKDIR /app
COPY package.json pnpm-workspace.yaml pnpm-lock.yaml ./
COPY packages/chronicle/package.json ./packages/chronicle/
RUN pnpm install --frozen-lockfile --filter @raystack/chronicle

# --- build CLI ---
FROM base AS builder
WORKDIR /app/packages/chronicle
COPY --from=deps /app /app
COPY packages/chronicle ./
RUN pnpm build:cli

# --- runner ---
FROM base AS runner
WORKDIR /app/packages/chronicle

COPY --from=builder /app /app

RUN chmod +x bin/chronicle.js
RUN ln -s /app/packages/chronicle/bin/chronicle.js /usr/local/bin/chronicle

RUN mkdir -p /app/content && ln -s /app/content /app/packages/chronicle/content
RUN chown -R node:node /app

VOLUME /app/content
USER node

ENV CHRONICLE_CONTENT_DIR=./content
WORKDIR /app/packages/chronicle

EXPOSE 3000

ENTRYPOINT ["chronicle"]
CMD ["dev", "--port", "3000"]
Comment thread
coderabbitai[bot] marked this conversation as resolved.
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
{
"name": "chronicle",
"private": true,
"packageManager": "pnpm@9.15.0",
"packageManager": "pnpm@10.6.2",
"engines": {
"node": ">=24"
"node": ">=22"
}
}
6 changes: 5 additions & 1 deletion packages/chronicle/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,11 @@
"bin": {
"chronicle": "./bin/chronicle.js"
},
"scripts": {
"build:cli": "tsup"
},
"engines": {
"node": ">=24"
"node": ">=22"
},
"devDependencies": {
"@biomejs/biome": "^2.3.13",
Expand All @@ -18,6 +21,7 @@
"@types/node": "^25.1.0",
"@types/react": "^19.2.10",
"@types/react-dom": "^19.2.3",
"tsup": "^8.5.1",
"tsx": "^4.21.0",
"typescript": "5.9.3"
},
Expand Down
21 changes: 12 additions & 9 deletions packages/chronicle/src/cli/commands/build.ts
Original file line number Diff line number Diff line change
@@ -1,28 +1,31 @@
import { Command } from 'commander'
import { spawn } from 'child_process'
import path from 'path'
import { fileURLToPath } from 'url'
import chalk from 'chalk'
import { loadCLIConfig } from '../utils'
import { resolveContentDir, loadCLIConfig, attachLifecycleHandlers } from '../utils'

const __dirname = path.dirname(fileURLToPath(import.meta.url))
const packageRoot = path.resolve(__dirname, '../../..')
declare const PACKAGE_ROOT: string

const nextBin = path.join(PACKAGE_ROOT, 'node_modules', '.bin', process.platform === 'win32' ? 'next.cmd' : 'next')

export const buildCommand = new Command('build')
.description('Build for production')
.action(() => {
const { contentDir } = loadCLIConfig()
.option('-c, --content <path>', 'Content directory')
.action((options) => {
const contentDir = resolveContentDir(options.content)
loadCLIConfig(contentDir)

console.log(chalk.cyan('Building for production...'))
console.log(chalk.gray(`Content: ${contentDir}`))

spawn('npx', ['next', 'build'], {
const child = spawn(nextBin, ['build'], {
stdio: 'inherit',
shell: true,
cwd: packageRoot,
cwd: PACKAGE_ROOT,
env: {
...process.env,
CHRONICLE_CONTENT_DIR: contentDir,
},
})

attachLifecycleHandlers(child)
})
19 changes: 11 additions & 8 deletions packages/chronicle/src/cli/commands/dev.ts
Original file line number Diff line number Diff line change
@@ -1,29 +1,32 @@
import { Command } from 'commander'
import { spawn } from 'child_process'
import path from 'path'
import { fileURLToPath } from 'url'
import chalk from 'chalk'
import { loadCLIConfig } from '../utils'
import { resolveContentDir, loadCLIConfig, attachLifecycleHandlers } from '../utils'

const __dirname = path.dirname(fileURLToPath(import.meta.url))
const packageRoot = path.resolve(__dirname, '../../..')
declare const PACKAGE_ROOT: string

const nextBin = path.join(PACKAGE_ROOT, 'node_modules', '.bin', process.platform === 'win32' ? 'next.cmd' : 'next')

export const devCommand = new Command('dev')
.description('Start development server')
.option('-p, --port <port>', 'Port number', '3000')
.option('-c, --content <path>', 'Content directory')
.action((options) => {
const { contentDir } = loadCLIConfig()
const contentDir = resolveContentDir(options.content)
loadCLIConfig(contentDir)

console.log(chalk.cyan('Starting dev server...'))
console.log(chalk.gray(`Content: ${contentDir}`))

spawn('npx', ['next', 'dev', '-p', options.port], {
const child = spawn(nextBin, ['dev', '-p', options.port], {
stdio: 'inherit',
shell: true,
cwd: packageRoot,
cwd: PACKAGE_ROOT,
Comment thread
coderabbitai[bot] marked this conversation as resolved.
env: {
...process.env,
CHRONICLE_CONTENT_DIR: contentDir,
},
})

attachLifecycleHandlers(child)
})
30 changes: 10 additions & 20 deletions packages/chronicle/src/cli/commands/init.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,10 @@ import chalk from 'chalk'
import { stringify } from 'yaml'
import type { ChronicleConfig } from '../../types'

function createConfig(contentDir: string): ChronicleConfig {
function createConfig(): ChronicleConfig {
return {
title: 'My Documentation',
description: 'Documentation powered by Chronicle',
contentDir,
theme: { name: 'default' },
search: { enabled: true, placeholder: 'Search documentation...' },
}
Expand All @@ -28,36 +27,27 @@ This is your documentation home page.

export const initCommand = new Command('init')
.description('Initialize a new Chronicle project')
.option('-d, --dir <path>', 'Project directory', '.')
.option('-c, --content-dir <path>', 'Content directory', './content')
.option('-d, --dir <path>', 'Content directory', '.')
.action((options) => {
const projectDir = options.dir
const contentDir = options.contentDir
const contentDir = path.resolve(options.dir)

// Create project directory
if (!fs.existsSync(projectDir)) {
fs.mkdirSync(projectDir, { recursive: true })
console.log(chalk.green('✓'), 'Created', projectDir)
// Create content directory
if (!fs.existsSync(contentDir)) {
fs.mkdirSync(contentDir, { recursive: true })
console.log(chalk.green('✓'), 'Created', contentDir)
}

// Create chronicle.yaml
const configPath = path.join(projectDir, 'chronicle.yaml')
const configPath = path.join(contentDir, 'chronicle.yaml')
if (!fs.existsSync(configPath)) {
fs.writeFileSync(configPath, stringify(createConfig(contentDir)))
fs.writeFileSync(configPath, stringify(createConfig()))
console.log(chalk.green('✓'), 'Created', configPath)
} else {
console.log(chalk.yellow('⚠'), configPath, 'already exists')
}

// Create content directory
const contentPath = path.join(projectDir, contentDir)
if (!fs.existsSync(contentPath)) {
fs.mkdirSync(contentPath, { recursive: true })
console.log(chalk.green('✓'), 'Created', contentPath)
}

// Create sample index.mdx
const indexPath = path.join(contentPath, 'index.mdx')
const indexPath = path.join(contentDir, 'index.mdx')
if (!fs.existsSync(indexPath)) {
fs.writeFileSync(indexPath, sampleMdx)
console.log(chalk.green('✓'), 'Created', indexPath)
Expand Down
19 changes: 11 additions & 8 deletions packages/chronicle/src/cli/commands/start.ts
Original file line number Diff line number Diff line change
@@ -1,29 +1,32 @@
import { Command } from 'commander'
import { spawn } from 'child_process'
import path from 'path'
import { fileURLToPath } from 'url'
import chalk from 'chalk'
import { loadCLIConfig } from '../utils'
import { resolveContentDir, loadCLIConfig, attachLifecycleHandlers } from '../utils'

const __dirname = path.dirname(fileURLToPath(import.meta.url))
const packageRoot = path.resolve(__dirname, '../../..')
declare const PACKAGE_ROOT: string

const nextBin = path.join(PACKAGE_ROOT, 'node_modules', '.bin', process.platform === 'win32' ? 'next.cmd' : 'next')

export const startCommand = new Command('start')
.description('Start production server')
.option('-p, --port <port>', 'Port number', '3000')
.option('-c, --content <path>', 'Content directory')
.action((options) => {
const { contentDir } = loadCLIConfig()
const contentDir = resolveContentDir(options.content)
loadCLIConfig(contentDir)

console.log(chalk.cyan('Starting production server...'))
console.log(chalk.gray(`Content: ${contentDir}`))

spawn('npx', ['next', 'start', '-p', options.port], {
const child = spawn(nextBin, ['start', '-p', options.port], {
stdio: 'inherit',
shell: true,
cwd: packageRoot,
cwd: PACKAGE_ROOT,
env: {
...process.env,
CHRONICLE_CONTENT_DIR: contentDir,
},
})

attachLifecycleHandlers(child)
})
13 changes: 9 additions & 4 deletions packages/chronicle/src/cli/utils/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,17 +10,22 @@ export interface CLIConfig {
contentDir: string
}

export function loadCLIConfig(cwd: string = process.cwd()): CLIConfig {
const configPath = path.join(cwd, 'chronicle.yaml')
export function resolveContentDir(contentFlag?: string): string {
if (contentFlag) return path.resolve(contentFlag)
if (process.env.CHRONICLE_CONTENT_DIR) return path.resolve(process.env.CHRONICLE_CONTENT_DIR)
return process.cwd()
}
Comment thread
rsbh marked this conversation as resolved.

export function loadCLIConfig(contentDir: string): CLIConfig {
const configPath = path.join(contentDir, 'chronicle.yaml')

if (!fs.existsSync(configPath)) {
console.log(chalk.red('Error: chronicle.yaml not found'))
console.log(chalk.red('Error: chronicle.yaml not found in'), contentDir)
console.log(chalk.gray(`Run 'chronicle init' to create one`))
process.exit(1)
}

const config = parse(fs.readFileSync(configPath, 'utf-8')) as ChronicleConfig
const contentDir = path.resolve(cwd, config.contentDir || '.')

return {
config,
Expand Down
1 change: 1 addition & 0 deletions packages/chronicle/src/cli/utils/index.ts
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
export * from './config'
export * from './process'
7 changes: 7 additions & 0 deletions packages/chronicle/src/cli/utils/process.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import type { ChildProcess } from 'child_process'

export function attachLifecycleHandlers(child: ChildProcess) {
child.on('close', (code) => process.exit(code ?? 0))
process.on('SIGINT', () => child.kill('SIGINT'))
process.on('SIGTERM', () => child.kill('SIGTERM'))
}
6 changes: 1 addition & 5 deletions packages/chronicle/src/lib/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,4 @@ export function loadConfig(): ChronicleConfig {
search: { ...defaultConfig.search, ...userConfig.search },
footer: userConfig.footer,
}
}

export function getConfigPath(contentDir: string = './content'): string {
return path.join(contentDir, CONFIG_FILE)
}
}
1 change: 0 additions & 1 deletion packages/chronicle/src/types/config.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
export interface ChronicleConfig {
title: string
description?: string
contentDir?: string
logo?: LogoConfig
theme?: ThemeConfig
navigation?: NavigationConfig
Expand Down
17 changes: 17 additions & 0 deletions packages/chronicle/tsup.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { defineConfig } from 'tsup'
import path from 'path'
import { fileURLToPath } from 'url'

const __dirname = path.dirname(fileURLToPath(import.meta.url))

export default defineConfig({
entry: ['src/cli/index.ts'],
format: ['esm'],
outDir: 'dist/cli',
target: 'node22',
clean: true,
sourcemap: false,
define: {
PACKAGE_ROOT: JSON.stringify(path.resolve(__dirname)),
},
Comment thread
rsbh marked this conversation as resolved.
})
Loading