Skip to content

feat: add Docker support with CLI improvements#10

Merged
rsbh merged 7 commits intomainfrom
feat_add_docker
Feb 11, 2026
Merged

feat: add Docker support with CLI improvements#10
rsbh merged 7 commits intomainfrom
feat_add_docker

Conversation

@rsbh
Copy link
Copy Markdown
Member

@rsbh rsbh commented Feb 11, 2026

Summary

  • Add multi-stage Dockerfile for running Chronicle in Docker with dev and prod modes
  • Refactor CLI to use --content flag, PACKAGE_ROOT build-time constant, and direct next binary invocation
  • Add tsup for CLI bundling with build:cli script

Changes

Docker

  • Dockerfile: Multi-stage build (deps → builder → runner) with pnpm monorepo support
  • .dockerignore: Excludes node_modules, .next, .source, .git
  • Content mounted at /app/content, symlinked into project root to stay within Turbopack's filesystem boundary
  • CHRONICLE_CONTENT_DIR=./content env var resolves through symlink

CLI

  • --content flag on dev, build, start commands (replaces config-based content dir resolution)
  • PACKAGE_ROOT injected at build time via tsup define — eliminates brittle __dirname + ../../.. relative path
  • Direct next binary (node_modules/.bin/next) instead of npx with shell: true — fixes signal handling (Ctrl+C)
  • Signal forwarding: SIGINT/SIGTERM properly forwarded to child Next.js process
  • init command: Creates flat content dir structure (chronicle.yaml + index.mdx in same dir)

Build

  • tsup.config.ts: target: node22, define: { PACKAGE_ROOT }, ESM output
  • build:cli script: Added to package.json
  • tsup added as devDependency

Cleanup

  • Removed contentDir from ChronicleConfig type and chronicle.yaml
  • Removed unused getConfigPath utility
  • Updated pnpm@10, node >=22

Usage

# Docker dev (hot-reload)
docker run -it -v $(pwd):/app/content -p 3000:3000 chronicle

# Docker prod
docker run -it -v $(pwd):/app/content chronicle build
docker run -it -v $(pwd):/app/content -p 3000:3000 chronicle start

# Local
cd examples/basic && chronicle dev
chronicle dev --content ./examples/basic

Test plan

  • Local dev server starts and returns HTTP 200
  • Docker dev mode with mounted content
  • Ctrl+C properly stops the server
  • Docker prod build + start
  • chronicle init creates correct structure

🤖 Generated with Claude Code

@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Feb 11, 2026

📝 Walkthrough

Summary by CodeRabbit

  • New Features

    • Added -c/--content flag to dev, build, and start commands for flexible content directory
    • Added Docker support (multi-stage image) and a .dockerignore for containerized runs
  • Improvements

    • Lowered Node.js minimum from 24 to 22
    • Updated pnpm to 10.6.2
    • Added a CLI build script for producing a standalone CLI bundle
    • Simplified init workflow for content directory setup

Walkthrough

Adds Docker containerization and a multi-stage Dockerfile; refactors Chronicle CLI to accept explicit content directory flags, resolves content dir via new helper, invokes Next binary directly, forwards lifecycle signals, and introduces a tsup-based CLI build configuration. Also lowers Node engine requirement to >=22 and updates pnpm.

Changes

Cohort / File(s) Summary
Docker Configuration
.dockerignore, Dockerfile
Adds .dockerignore excluding node_modules, .next, .git, logs, and source noise. Adds multi-stage Dockerfile (base/deps/builder/runner), runtime volume /app/content, CHRONICLE_CONTENT_DIR env, and entrypoint to run the CLI.
Root & Package Manifests
package.json, packages/chronicle/package.json
Bumps packageManager to pnpm@10.6.2, lowers Node engine requirement to >=22. Adds build:cli script and tsup devDependency in packages/chronicle.
CLI Build Config
packages/chronicle/tsup.config.ts
Adds tsup config for CLI: entry src/cli/index.ts, ESM output to dist/cli, target node22, cleans build, and defines PACKAGE_ROOT global.
CLI Commands
packages/chronicle/src/cli/commands/...
packages/chronicle/src/cli/commands/build.ts, dev.ts, start.ts, init.ts
Adds -c, --content <path> to build/dev/start; build/dev/start now resolve content dir, set CHRONICLE_CONTENT_DIR, compute nextBin from PACKAGE_ROOT, spawn Next binary directly with cwd PACKAGE_ROOT, and attach lifecycle handlers to forward signals. init simplified to use a single -d/--dir as content directory and removed separate project/content dir handling.
CLI Utilities
packages/chronicle/src/cli/utils/*
config.ts, process.ts, index.ts
Adds resolveContentDir(contentFlag?: string) and changes loadCLIConfig signature to require contentDir. Adds attachLifecycleHandlers(child: ChildProcess) to forward SIGINT/SIGTERM and exit on child close. Re-exports new utilities from index.ts.
Config Types & Lib
packages/chronicle/src/lib/config.ts, packages/chronicle/src/types/config.ts
Removes exported getConfigPath helper. Removes contentDir?: string from ChronicleConfig type; load behavior preserved but API surface trimmed.

Sequence Diagram(s)

mermaid
sequenceDiagram
participant CLI as CLI (chronicle)
participant OS as OS / Signals
participant Next as Next Binary (next)
CLI->>CLI: resolveContentDir(flag | env | cwd)
CLI->>CLI: loadCLIConfig(contentDir)
CLI->>Next: spawn nextBin (cwd: PACKAGE_ROOT, env includes CHRONICLE_CONTENT_DIR)
CLI->>OS: attach lifecycle handlers (SIGINT, SIGTERM)
OS->>CLI: SIGINT / SIGTERM
CLI->>Next: forward signal (child.kill)
Next-->>CLI: close (exitCode)
CLI->>CLI: process.exit(exitCode)

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

  • feat: add search #8: touches packages/chronicle/src/lib/config.ts and config-loading APIs, closely related to the config refactor in this PR.

Suggested reviewers

  • rohilsurana
🚥 Pre-merge checks | ✅ 2 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately summarizes the main changes: adding Docker support and improving the CLI architecture.
Description check ✅ Passed The description clearly relates to the changeset, providing comprehensive details about Docker setup, CLI refactoring, build configuration, and cleanup work.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat_add_docker

No actionable comments were generated in the recent review. 🎉


Comment @coderabbitai help to get the list of available commands and usage tips.

@rsbh rsbh requested a review from rohilsurana February 11, 2026 07:06
Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

🤖 Fix all issues with AI agents
In `@Dockerfile`:
- Around line 21-38: The Dockerfile's runner stage is running as root; update
the runtime stage to switch to the non-root node user (use USER node) after
creating and setting ownership/permissions for the app and mounted content
directories so they are writable by that user; ensure the symbolic link creation
(ln -s /app/packages/chronicle/bin/chronicle.js /usr/local/bin/chronicle),
VOLUME /app/content, and ENTRYPOINT/CMD still work under USER node by chown -R
node:node /app and chown the mounted content path (/app/content) before
switching users and then set appropriate permissions (e.g., chmod) so the node
user can execute bin/chronicle.js and write to /app and /app/content.

In `@packages/chronicle/src/cli/commands/dev.ts`:
- Around line 7-24: The Windows shim in node_modules/.bin isn't executable as-is
on Windows; update the binary resolution used by nextBin (and equivalents in
build.ts and start.ts) to choose the platform-specific file: compute the base
path using PACKAGE_ROOT and then if process.platform === 'win32' append '.cmd'
(or test for the existence of the .cmd file) before calling spawn in
devCommand.action (the variable nextBin and the spawn(...) invocation), so spawn
receives the correct executable on Windows.

In `@packages/chronicle/src/cli/utils/config.ts`:
- Around line 13-17: The function resolveContentDir inconsistently resolves
paths: it calls path.resolve(contentFlag) for contentFlag but returns
process.env.CHRONICLE_CONTENT_DIR unchanged; update resolveContentDir to
normalize the env var as well by passing process.env.CHRONICLE_CONTENT_DIR
through path.resolve (or path.resolve(process.env.CHRONICLE_CONTENT_DIR ||
process.cwd())) so the returned path is always absolute whether coming from
contentFlag, CHRONICLE_CONTENT_DIR, or falling back to process.cwd(); adjust
only resolveContentDir and reference contentFlag and CHRONICLE_CONTENT_DIR
accordingly.

In `@packages/chronicle/tsup.config.ts`:
- Around line 5-16: The build currently injects PACKAGE_ROOT via tsup's define
(in tsup.config.ts using defineConfig and PACKAGE_ROOT), embedding the build
machine's absolute path; instead remove PACKAGE_ROOT from tsup define and change
the runtime code that relies on PACKAGE_ROOT (e.g., code in src/cli that
references PACKAGE_ROOT) to compute the package root at runtime using
import.meta.url and fileURLToPath (or an ESM-compatible __dirname fallback) so
lookups like node_modules/.bin/next resolve correctly when the package is
installed; update tsup.config.ts to stop defining PACKAGE_ROOT and implement the
runtime resolution where PACKAGE_ROOT was used.
🧹 Nitpick comments (3)
packages/chronicle/src/cli/commands/start.ts (1)

16-17: Unused config variable.

The config is destructured from loadCLIConfig but never used. If the intent is solely to validate that chronicle.yaml exists (since loadCLIConfig exits on missing config), consider adding a comment or using loadCLIConfig(contentDir) without destructuring.

🔧 Suggested change
-    const { config } = loadCLIConfig(contentDir)
+    loadCLIConfig(contentDir) // validates chronicle.yaml exists
packages/chronicle/src/cli/commands/build.ts (2)

15-16: Same issue: unused config variable.

Same as in start.ts - the config is destructured but not used. If this is intentional validation, consider documenting it.

🔧 Suggested change
-    const { config } = loadCLIConfig(contentDir)
+    loadCLIConfig(contentDir) // validates chronicle.yaml exists

29-32: Consider extracting shared lifecycle handling.

The signal forwarding and exit code handling pattern is duplicated across build.ts, start.ts, and likely dev.ts. While acceptable for CLI commands, you could optionally extract this into a shared utility.

// In utils/process.ts
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'))
}

- Add non-root user (node) in Dockerfile runner stage
- Use platform-specific next binary (.cmd on Windows)
- Normalize env var path in resolveContentDir
- Remove unused config destructuring in command files
- Extract shared signal handling to attachLifecycleHandlers util

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@rsbh rsbh merged commit 73eca49 into main Feb 11, 2026
1 check passed
This was referenced Feb 18, 2026
@rsbh rsbh deleted the feat_add_docker branch March 16, 2026 04:47
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants