Skip to content

Commit 0b67350

Browse files
rsbhclaude
andauthored
fix: scaffold .chronicle/ directory for Turbopack compatibility (#21)
* fix: resolve next binary via createRequire instead of hardcoded .bin path When installed via npx/bunx or as a dependency, next gets hoisted and node_modules/.bin/next doesn't exist inside the chronicle package dir. Use createRequire + require.resolve to find next/dist/bin/next via Node's module resolution which handles hoisting automatically. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix: resolve chronicle.yaml from cwd first, then content dir Allows placing chronicle.yaml in project root when using chronicle as a library dependency, while still supporting it inside content dir. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * ci: add canary release workflow for PR builds Publishes canary npm release on every PR commit with version format 0.1.0-canary.<short-sha> using --tag canary. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * feat: scaffold .chronicle/ directory instead of running Next.js from node_modules Turbopack refuses to compile .ts/.tsx from node_modules. Instead of running Next.js with cwd: PACKAGE_ROOT (inside node_modules), the CLI now scaffolds a .chronicle/ directory in the user's project with copied source files and a content symlink. Next.js runs from .chronicle/ where Turbopack compiles normally. - Add scaffold utility that copies src/, source.config.ts, tsconfig.json and generates next.config.mjs - Update dev/build/start/serve commands to use scaffold cwd - Update init command to create package.json with deps, content/ subdirectory, and auto-install via detected package manager - Default content dir changed from cwd to ./content - Add .chronicle to .gitignore in init Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix: move scaffolding to init, resolve next from cwd, detect package manager via lockfiles - Move scaffoldDir call from dev/build/start/serve into init command - Commands now require .chronicle/ to exist (fail with helpful message) - Remove --content and -d flags from commands and init - Add resolveNextCli() using createRequire to avoid duplicate Next.js instances - Detect package manager via npm_config_user_agent then lockfile fallback - Update Dockerfile to use bun add + chronicle init flow - Add resolve.ts for PACKAGE_ROOT derivation Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix: use .npmrc file for npm auth in canary workflow Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix: write .npmrc to home dir for bun publish auth Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix: revert canary publish to bun with NPM_CONFIG_TOKEN Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * debug: add npm whoami step to verify token Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix: standalone tsconfig, openapi-types dep, content flag, update docs - Remove extends @raystack/tools-config from tsconfig.json (standalone) - Move openapi-types from devDependencies to dependencies - Add -c/--content flag to init for custom content dir name - Skip sample index.mdx if content dir already has files - Update CLI, Docker, and Getting Started docs for new scaffold flow Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix: resolve config from project root and fix .source import path Pass CHRONICLE_PROJECT_ROOT env var to Next.js child process so runtime config loader finds chronicle.yaml at the actual project root instead of .chronicle/ scaffold dir. Use tsconfig path alias for .source/server import to resolve correctly in scaffold context. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix: merge missing scripts and deps into existing package.json on init Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * chore: remove debug npm whoami step from canary workflow Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix: set type module and add node_modules/.next to gitignore in init Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix: render search highlights using dangerouslySetInnerHTML for mark tags Fumadocs returns search results with <mark> HTML tags for highlighting. Render them properly instead of showing raw HTML text. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * chore: remove canary release workflow Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix: address CodeRabbit PR review comments - Use template literal for chalk error message in config.ts - Resolve Next.js CLI from Chronicle's PACKAGE_ROOT instead of cwd - Derive chronicle version from own package.json instead of hardcoding 'latest' - Add try-catch with user-friendly error for resolveNextCli in all commands - Add comment explaining PACKAGE_ROOT resolution path Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
1 parent ca271b9 commit 0b67350

20 files changed

Lines changed: 463 additions & 124 deletions

File tree

Dockerfile

Lines changed: 11 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -13,22 +13,23 @@ WORKDIR /app/packages/chronicle
1313
COPY --from=deps /app /app
1414
COPY packages/chronicle ./
1515
RUN bun build-cli.ts
16+
RUN chmod +x bin/chronicle.js
17+
RUN ln -s /app/packages/chronicle/bin/chronicle.js /usr/local/bin/chronicle
18+
19+
# --- init project ---
20+
WORKDIR /docs
21+
RUN bun add /app/packages/chronicle
22+
RUN chronicle init
1623

1724
# --- runner ---
1825
FROM base AS runner
19-
WORKDIR /app/packages/chronicle
20-
21-
COPY --from=builder /app /app
26+
WORKDIR /docs
2227

23-
RUN chmod +x bin/chronicle.js
28+
COPY --from=builder /docs /docs
29+
COPY --from=builder /app/packages/chronicle /app/packages/chronicle
2430
RUN ln -s /app/packages/chronicle/bin/chronicle.js /usr/local/bin/chronicle
2531

26-
RUN mkdir -p /app/content && ln -s /app/content /app/packages/chronicle/content
27-
28-
VOLUME /app/content
29-
30-
ENV CHRONICLE_CONTENT_DIR=./content
31-
WORKDIR /app/packages/chronicle
32+
VOLUME /docs/content
3233

3334
EXPOSE 3000
3435

docs/cli.mdx

Lines changed: 18 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -10,21 +10,28 @@ Chronicle provides a CLI to initialize, develop, build, and serve your documenta
1010

1111
## init
1212

13-
Scaffold a new Chronicle project.
13+
Initialize a new Chronicle project. Must be run before other commands.
1414

1515
```bash
1616
chronicle init [options]
1717
```
1818

1919
| Flag | Description | Default |
2020
|------|-------------|---------|
21-
| `-d, --dir <path>` | Target directory | `.` (current directory) |
21+
| `-c, --content <path>` | Content directory name | `content` |
2222

23-
Creates a `chronicle.yaml` config file and a sample `index.mdx` in the specified directory.
23+
This creates:
24+
- `chronicle.yaml` — site configuration
25+
- `content/` (or custom name) — content directory with a sample `index.mdx`
26+
- `package.json` — with `@raystack/chronicle` dependency (if not exists)
27+
- `.chronicle/` — scaffolded build directory
28+
- `.gitignore` — with `.chronicle` entry
29+
30+
If the content directory already exists and has files, the sample `index.mdx` is skipped.
2431

2532
## dev
2633

27-
Start the development server with hot reload.
34+
Start the development server with hot reload. Requires `chronicle init` first.
2835

2936
```bash
3037
chronicle dev [options]
@@ -33,20 +40,15 @@ chronicle dev [options]
3340
| Flag | Description | Default |
3441
|------|-------------|---------|
3542
| `-p, --port <port>` | Port number | `3000` |
36-
| `-c, --content <path>` | Content directory | Current directory |
3743

3844
## build
3945

40-
Build the site for production.
46+
Build the site for production. Requires `chronicle init` first.
4147

4248
```bash
43-
chronicle build [options]
49+
chronicle build
4450
```
4551

46-
| Flag | Description | Default |
47-
|------|-------------|---------|
48-
| `-c, --content <path>` | Content directory | Current directory |
49-
5052
## start
5153

5254
Start the production server. Requires a prior `chronicle build`.
@@ -58,11 +60,10 @@ chronicle start [options]
5860
| Flag | Description | Default |
5961
|------|-------------|---------|
6062
| `-p, --port <port>` | Port number | `3000` |
61-
| `-c, --content <path>` | Content directory | Current directory |
6263

6364
## serve
6465

65-
Build and start the production server in one step.
66+
Build and start the production server in one step. Requires `chronicle init` first.
6667

6768
```bash
6869
chronicle serve [options]
@@ -71,14 +72,10 @@ chronicle serve [options]
7172
| Flag | Description | Default |
7273
|------|-------------|---------|
7374
| `-p, --port <port>` | Port number | `3000` |
74-
| `-c, --content <path>` | Content directory | Current directory |
75-
76-
## Content Directory Resolution
7775

78-
The content directory is resolved in this order:
76+
## Config Resolution
7977

80-
1. `--content` CLI flag (highest priority)
81-
2. `CHRONICLE_CONTENT_DIR` environment variable
82-
3. Current working directory (default)
78+
`chronicle.yaml` is resolved in this order:
8379

84-
The content directory must contain a `chronicle.yaml` file.
80+
1. Current working directory
81+
2. Content directory

docs/docker.mdx

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -19,23 +19,23 @@ docker pull raystack/chronicle
1919
The default command builds and starts a production server on port 3000.
2020

2121
```bash
22-
docker run -p 3000:3000 -v ./content:/app/content raystack/chronicle
22+
docker run -p 3000:3000 -v ./content:/docs/content raystack/chronicle
2323
```
2424

25-
Mount your content directory (containing `chronicle.yaml` and MDX files) to `/app/content`.
25+
Mount your content directory (containing MDX files) to `/docs/content`. Place `chronicle.yaml` in the content directory or it will use defaults.
2626

2727
## Development Server
2828

2929
Override the default command with `dev` for hot reload:
3030

3131
```bash
32-
docker run -p 3000:3000 -v ./content:/app/content raystack/chronicle dev --port 3000
32+
docker run -p 3000:3000 -v ./content:/docs/content raystack/chronicle dev --port 3000
3333
```
3434

3535
## Custom Port
3636

3737
```bash
38-
docker run -p 8080:8080 -v ./content:/app/content raystack/chronicle serve --port 8080
38+
docker run -p 8080:8080 -v ./content:/docs/content raystack/chronicle serve --port 8080
3939
```
4040

4141
## Docker Compose
@@ -47,7 +47,7 @@ services:
4747
ports:
4848
- "3000:3000"
4949
volumes:
50-
- ./content:/app/content
50+
- ./content:/docs/content
5151
```
5252
5353
## Available Commands
@@ -56,11 +56,11 @@ The entrypoint is `chronicle`, so any CLI command can be passed:
5656

5757
```bash
5858
# Build only
59-
docker run -v ./content:/app/content raystack/chronicle build
59+
docker run -v ./content:/docs/content raystack/chronicle build
6060
6161
# Start pre-built server
62-
docker run -p 3000:3000 -v ./content:/app/content raystack/chronicle start --port 3000
62+
docker run -p 3000:3000 -v ./content:/docs/content raystack/chronicle start --port 3000
6363
6464
# Build and start (default)
65-
docker run -p 3000:3000 -v ./content:/app/content raystack/chronicle serve --port 3000
65+
docker run -p 3000:3000 -v ./content:/docs/content raystack/chronicle serve --port 3000
6666
```

docs/index.mdx

Lines changed: 22 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,15 @@ chronicle init
2424

2525
This creates:
2626
- `chronicle.yaml` — your site configuration
27-
- `index.mdx` — a sample documentation page
27+
- `content/` — content directory with a sample `index.mdx`
28+
- `package.json` — with `@raystack/chronicle` dependency
29+
- `.chronicle/` — scaffolded build directory (gitignored)
30+
31+
To use an existing directory as content (e.g. `docs/`):
32+
33+
```bash
34+
chronicle init -c docs
35+
```
2836

2937
### 2. Start the development server
3038

@@ -39,13 +47,15 @@ Your docs site is now running at [http://localhost:3000](http://localhost:3000).
3947
Create `.mdx` files in your content directory. Each file becomes a page. Use folders to create nested navigation.
4048

4149
```
42-
content/
50+
my-docs/
4351
├── chronicle.yaml
44-
├── index.mdx
45-
├── getting-started.mdx
46-
└── guides/
47-
├── installation.mdx
48-
└── configuration.mdx
52+
├── content/
53+
│ ├── index.mdx
54+
│ ├── getting-started.mdx
55+
│ └── guides/
56+
│ ├── installation.mdx
57+
│ └── configuration.mdx
58+
└── .chronicle/ # generated, gitignored
4959
```
5060

5161
### 4. Build for production
@@ -68,9 +78,11 @@ A minimal Chronicle project looks like:
6878
```
6979
my-docs/
7080
├── chronicle.yaml # Site configuration
71-
├── index.mdx # Home page
72-
└── guides/
73-
└── setup.mdx # Nested page at /guides/setup
81+
├── content/
82+
│ ├── index.mdx # Home page
83+
│ └── guides/
84+
│ └── setup.mdx # Nested page at /guides/setup
85+
└── .chronicle/ # Generated by init, gitignored
7486
```
7587

7688
All configuration is done through `chronicle.yaml`. No additional config files needed.

packages/chronicle/bin/chronicle.js

100644100755
File mode changed.

packages/chronicle/package.json

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,8 @@
1010
"src",
1111
"templates",
1212
"next.config.mjs",
13-
"source.config.ts"
13+
"source.config.ts",
14+
"tsconfig.json"
1415
],
1516
"bin": {
1617
"chronicle": "./bin/chronicle.js"
@@ -27,7 +28,6 @@
2728
"@types/react": "^19.2.10",
2829
"@types/react-dom": "^19.2.3",
2930
"@types/semver": "^7.7.1",
30-
"openapi-types": "^12.1.3",
3131
"semver": "^7.7.4",
3232
"typescript": "5.9.3"
3333
},
@@ -56,6 +56,7 @@
5656
"slugify": "^1.6.6",
5757
"unified": "^11.0.5",
5858
"unist-util-visit": "^5.1.0",
59+
"openapi-types": "^12.1.3",
5960
"yaml": "^2.8.2",
6061
"zod": "^4.3.6"
6162
}

packages/chronicle/src/cli/commands/build.ts

Lines changed: 20 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,29 +1,36 @@
11
import { Command } from 'commander'
22
import { spawn } from 'child_process'
33
import path from 'path'
4-
import { fileURLToPath } from 'url'
4+
import fs from 'fs'
55
import chalk from 'chalk'
6-
import { resolveContentDir, loadCLIConfig, attachLifecycleHandlers } from '@/cli/utils'
7-
8-
const PACKAGE_ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..', '..')
9-
const nextBin = path.join(PACKAGE_ROOT, 'node_modules', '.bin', process.platform === 'win32' ? 'next.cmd' : 'next')
6+
import { attachLifecycleHandlers, resolveNextCli } from '@/cli/utils'
107

118
export const buildCommand = new Command('build')
129
.description('Build for production')
13-
.option('-c, --content <path>', 'Content directory')
14-
.action((options) => {
15-
const contentDir = resolveContentDir(options.content)
16-
loadCLIConfig(contentDir)
10+
.action(() => {
11+
const scaffoldPath = path.join(process.cwd(), '.chronicle')
12+
if (!fs.existsSync(scaffoldPath)) {
13+
console.log(chalk.red('Error: .chronicle/ not found. Run'), chalk.cyan('chronicle init'), chalk.red('first.'))
14+
process.exit(1)
15+
}
16+
17+
let nextCli: string
18+
try {
19+
nextCli = resolveNextCli()
20+
} catch {
21+
console.log(chalk.red('Error: Next.js CLI not found. Run'), chalk.cyan('chronicle init'), chalk.red('first.'))
22+
process.exit(1)
23+
}
1724

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

21-
const child = spawn(nextBin, ['build'], {
27+
const child = spawn(process.execPath, [nextCli, 'build'], {
2228
stdio: 'inherit',
23-
cwd: PACKAGE_ROOT,
29+
cwd: scaffoldPath,
2430
env: {
2531
...process.env,
26-
CHRONICLE_CONTENT_DIR: contentDir,
32+
CHRONICLE_PROJECT_ROOT: process.cwd(),
33+
CHRONICLE_CONTENT_DIR: './content',
2734
},
2835
})
2936

packages/chronicle/src/cli/commands/dev.ts

Lines changed: 19 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,30 +1,37 @@
11
import { Command } from 'commander'
22
import { spawn } from 'child_process'
33
import path from 'path'
4-
import { fileURLToPath } from 'url'
4+
import fs from 'fs'
55
import chalk from 'chalk'
6-
import { resolveContentDir, loadCLIConfig, attachLifecycleHandlers } from '@/cli/utils'
7-
8-
const PACKAGE_ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..', '..')
9-
const nextBin = path.join(PACKAGE_ROOT, 'node_modules', '.bin', process.platform === 'win32' ? 'next.cmd' : 'next')
6+
import { attachLifecycleHandlers, resolveNextCli } from '@/cli/utils'
107

118
export const devCommand = new Command('dev')
129
.description('Start development server')
1310
.option('-p, --port <port>', 'Port number', '3000')
14-
.option('-c, --content <path>', 'Content directory')
1511
.action((options) => {
16-
const contentDir = resolveContentDir(options.content)
17-
loadCLIConfig(contentDir)
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+
let nextCli: string
19+
try {
20+
nextCli = resolveNextCli()
21+
} catch {
22+
console.log(chalk.red('Error: Next.js CLI not found. Run'), chalk.cyan('chronicle init'), chalk.red('first.'))
23+
process.exit(1)
24+
}
1825

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

22-
const child = spawn(nextBin, ['dev', '-p', options.port], {
28+
const child = spawn(process.execPath, [nextCli, 'dev', '-p', options.port], {
2329
stdio: 'inherit',
24-
cwd: PACKAGE_ROOT,
30+
cwd: scaffoldPath,
2531
env: {
2632
...process.env,
27-
CHRONICLE_CONTENT_DIR: contentDir,
33+
CHRONICLE_PROJECT_ROOT: process.cwd(),
34+
CHRONICLE_CONTENT_DIR: './content',
2835
},
2936
})
3037

0 commit comments

Comments
 (0)