Skip to content

fix: scaffold .chronicle/ directory for Turbopack compatibility#21

Open
rsbh wants to merge 16 commits intomainfrom
fix_nextjs_path
Open

fix: scaffold .chronicle/ directory for Turbopack compatibility#21
rsbh wants to merge 16 commits intomainfrom
fix_nextjs_path

Conversation

@rsbh
Copy link
Member

@rsbh rsbh commented Mar 12, 2026

Summary

  • Scaffold approach: chronicle init creates .chronicle/ directory with copied source files, so Turbopack compiles TypeScript without node_modules restrictions
  • CLI commands (dev/build/start/serve) run Next.js from .chronicle/ with CHRONICLE_PROJECT_ROOT env var for config resolution
  • Init improvements: merges missing scripts/deps into existing package.json, sets "type": "module", adds node_modules/.next/.chronicle to .gitignore
  • Fix .source/server import: use tsconfig path alias @/.source/* instead of relative path
  • Fix search highlights: render fumadocs <mark> tags via dangerouslySetInnerHTML
  • Standalone tsconfig: no longer extends @raystack/tools-config (unavailable to consumers)
  • Moved openapi-types from devDependencies to dependencies

Test plan

  • npm pack and install tarball in a fresh project
  • chronicle init creates correct structure
  • chronicle init on existing project merges missing scripts/deps
  • chronicle dev starts dev server, hot reload works
  • chronicle build && chronicle start works
  • Search highlights render correctly (no raw <mark> tags)
  • chronicle.yaml config is read from project root
  • Docker build and run works

🤖 Generated with Claude Code

rsbh and others added 2 commits March 12, 2026 13:29
…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>
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>
@coderabbitai
Copy link

coderabbitai bot commented Mar 12, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Replaces content-dir driven flows with a scaffold-first workflow: CLI commands now require a local .chronicle scaffold, resolve the Next.js CLI at runtime (resolveNextCli), spawn Next via Node (process.execPath) in the scaffold directory, set CHRONICLE_CONTENT_DIR='./content', and add scaffold creation plus package-manager installs.

Changes

Cohort / File(s) Summary
CLI commands (spawn & env changes)
packages/chronicle/src/cli/commands/build.ts, packages/chronicle/src/cli/commands/dev.ts, packages/chronicle/src/cli/commands/serve.ts, packages/chronicle/src/cli/commands/start.ts
Removed -c/--content option and contentDir resolution; added runtime check for .chronicle; use resolveNextCli and spawn Next via process.execPath with cwd set to scaffoldPath; set CHRONICLE_CONTENT_DIR='./content'; preserved lifecycle handlers.
Scaffold utilities & resolution
packages/chronicle/src/cli/utils/scaffold.ts, packages/chronicle/src/cli/utils/index.ts, packages/chronicle/src/cli/utils/resolve.ts
New scaffold implementation: scaffoldDir, resolveNextCli, detectPackageManager, and PACKAGE_ROOT; copies templates into .chronicle, creates content symlink, installs deps via detected package manager; re-exported from utils index.
Config resolution (CLI & lib)
packages/chronicle/src/cli/utils/config.ts, packages/chronicle/src/lib/config.ts
Added resolveConfigPath() used by CLI and lib loaders to prefer chronicle.yaml in cwd then content dir; load functions now gate reads and give clearer not-found messaging.
Init flow changes
packages/chronicle/src/cli/commands/init.ts
Reworked init to bootstrap a project: create root package.json, .gitignore, content/index.mdx; detect package manager, install deps, scaffold .chronicle, and print run instructions tailored to package manager.
Dockerfile and CI canary
Dockerfile, .github/workflows/canary.yml
Dockerfile: adjust build/runner to use /docs, add CLI symlink, run chronicle init during build stage, and adjust content paths. CI: add canary workflow to build and publish a canary npm release from packages/chronicle.
Misc public exports
packages/chronicle/src/cli/utils/index.ts
Re-export scaffold utilities (export * from './scaffold') to expose new helpers from the public utils surface.

Sequence Diagram(s)

sequenceDiagram
  participant User
  participant CLI as Chronicle CLI
  participant FS as Filesystem
  participant PM as PackageManager
  participant NodeExec as Node (process.execPath)
  participant NextCLI as Next CLI (resolved)

  User->>CLI: run init / dev / build / start
  CLI->>FS: check for `.chronicle` (scaffold)
  alt scaffold missing and command is init
    CLI->>FS: create project files (package.json, chronicle.yaml, content/index.mdx, .gitignore)
  else scaffold missing for dev/build/start
    CLI-->>User: error ("scaffold not found")
  end
  CLI->>CLI: scaffoldDir -> copy templates into `.chronicle`
  CLI->>PM: detectPackageManager -> install dependencies
  PM-->>CLI: install complete
  CLI->>FS: ensure `.chronicle/content` -> symlink to `./content`
  CLI->>NodeExec: spawn process with resolved Next CLI path
  NodeExec->>NextCLI: execute (dev/build/start) in cwd = `.chronicle`
  NextCLI->>FS: read `.chronicle` config & content
  NextCLI-->>User: serve or build output
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

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 describes the main architectural change (scaffolding .chronicle/ directory for Turbopack compatibility), which aligns with the core changes across all command files and the new scaffold utility.
Description check ✅ Passed The description is related to the changeset, explaining the scaffold approach, CLI command updates, init improvements, and configuration resolution changes that match the actual file modifications.

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

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch fix_nextjs_path
📝 Coding Plan
  • Generate coding plan for human review comments

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

Tip

You can get early access to new features in CodeRabbit.

Enable the early_access setting to enable early access features such as new models, tools, and more.

Copy link

@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: 1

🧹 Nitpick comments (2)
packages/chronicle/src/cli/utils/config.ts (1)

19-25: Code duplication with lib/config.ts.

This resolveConfigPath implementation duplicates the logic in packages/chronicle/src/lib/config.ts:14-23, with slight semantic differences: the CLI version takes contentDir as a parameter while the lib version reads from process.env.CHRONICLE_CONTENT_DIR directly. Consider extracting a shared utility to avoid drift between these two implementations.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/chronicle/src/cli/utils/config.ts` around lines 19 - 25, The
resolveConfigPath implementation in CLI (resolveConfigPath) duplicates logic
from lib/config.ts; extract a shared utility (e.g., findChronicleConfig) into a
common module that accepts an optional contentDir parameter (and falls back to
process.env.CHRONICLE_CONTENT_DIR when undefined) and replace both
resolveConfigPath and the code in packages/chronicle/src/lib/config.ts to call
this shared function so behavior is centralized and avoids drift.
packages/chronicle/src/cli/commands/dev.ts (1)

5-11: Consider extracting shared Next.js CLI resolution.

The createRequire + require.resolve('next/dist/bin/next') pattern is duplicated across dev.ts, build.ts, start.ts, and serve.ts. Consider extracting this to a shared utility (e.g., in @/cli/utils) to reduce duplication and centralize the resolution logic.

♻️ Example shared utility
// In `@/cli/utils/next.ts`
import { createRequire } from 'module'

const require = createRequire(import.meta.url)
export const nextCli = require.resolve('next/dist/bin/next')
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/chronicle/src/cli/commands/dev.ts` around lines 5 - 11, The Next.js
CLI resolution (createRequire + require.resolve('next/dist/bin/next')) is
duplicated in dev.ts (and also in build.ts, start.ts, serve.ts); extract it into
a shared utility (e.g., add a new export nextCli in "@/cli/utils/next" or
"@/cli/utils") that performs createRequire(import.meta.url) and resolves
'next/dist/bin/next', then import and use that exported nextCli symbol in
dev.ts, build.ts, start.ts and serve.ts to remove duplication and centralize
resolution logic.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@packages/chronicle/src/cli/utils/config.ts`:
- Around line 30-31: The error message built when the configPath check fails
currently uses comma-separated console.log arguments which yields awkward
spacing; update the code inside the configPath falsy branch (the if
(!configPath) block) to build a single formatted string — e.g., use a template
literal combining process.cwd() and contentDir — and pass that single string
into chalk.red so the entire message is colored and properly formatted (for
example: console.log(chalk.red(`Error: chronicle.yaml not found in
${process.cwd()} or ${contentDir}`))).

---

Nitpick comments:
In `@packages/chronicle/src/cli/commands/dev.ts`:
- Around line 5-11: The Next.js CLI resolution (createRequire +
require.resolve('next/dist/bin/next')) is duplicated in dev.ts (and also in
build.ts, start.ts, serve.ts); extract it into a shared utility (e.g., add a new
export nextCli in "@/cli/utils/next" or "@/cli/utils") that performs
createRequire(import.meta.url) and resolves 'next/dist/bin/next', then import
and use that exported nextCli symbol in dev.ts, build.ts, start.ts and serve.ts
to remove duplication and centralize resolution logic.

In `@packages/chronicle/src/cli/utils/config.ts`:
- Around line 19-25: The resolveConfigPath implementation in CLI
(resolveConfigPath) duplicates logic from lib/config.ts; extract a shared
utility (e.g., findChronicleConfig) into a common module that accepts an
optional contentDir parameter (and falls back to
process.env.CHRONICLE_CONTENT_DIR when undefined) and replace both
resolveConfigPath and the code in packages/chronicle/src/lib/config.ts to call
this shared function so behavior is centralized and avoids drift.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 69d15e4f-0518-4c4a-9966-8782bed0715d

📥 Commits

Reviewing files that changed from the base of the PR and between ca271b9 and c47312f.

📒 Files selected for processing (7)
  • packages/chronicle/bin/chronicle.js
  • packages/chronicle/src/cli/commands/build.ts
  • packages/chronicle/src/cli/commands/dev.ts
  • packages/chronicle/src/cli/commands/serve.ts
  • packages/chronicle/src/cli/commands/start.ts
  • packages/chronicle/src/cli/utils/config.ts
  • packages/chronicle/src/lib/config.ts

Comment on lines +30 to +31
if (!configPath) {
console.log(chalk.red('Error: chronicle.yaml not found in'), process.cwd(), 'or', contentDir)
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Error message formatting produces unintended output.

Using comma-separated arguments in console.log will print values separated by spaces without proper formatting. The message will read awkwardly (e.g., Error: chronicle.yaml not found in /path/cwd or /path/content).

🛠️ Proposed fix using template literal
   if (!configPath) {
-    console.log(chalk.red('Error: chronicle.yaml not found in'), process.cwd(), 'or', contentDir)
+    console.log(chalk.red(`Error: chronicle.yaml not found in '${process.cwd()}' or '${contentDir}'`))
     console.log(chalk.gray(`Run 'chronicle init' to create one`))
     process.exit(1)
   }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if (!configPath) {
console.log(chalk.red('Error: chronicle.yaml not found in'), process.cwd(), 'or', contentDir)
if (!configPath) {
console.log(chalk.red(`Error: chronicle.yaml not found in '${process.cwd()}' or '${contentDir}'`))
console.log(chalk.gray(`Run 'chronicle init' to create one`))
process.exit(1)
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/chronicle/src/cli/utils/config.ts` around lines 30 - 31, The error
message built when the configPath check fails currently uses comma-separated
console.log arguments which yields awkward spacing; update the code inside the
configPath falsy branch (the if (!configPath) block) to build a single formatted
string — e.g., use a template literal combining process.cwd() and contentDir —
and pass that single string into chalk.red so the entire message is colored and
properly formatted (for example: console.log(chalk.red(`Error: chronicle.yaml
not found in ${process.cwd()} or ${contentDir}`))).

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>
Copy link

@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: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In @.github/workflows/canary.yml:
- Around line 3-5: The workflow is triggered on pull_request which exposes repo
secrets only for same-repo PRs, so guard the publish path: modify the publish
job or the specific publish step to run only for same-repo PRs by adding an if
condition that compares the PR head repo to the base/repository (e.g. if:
github.event.pull_request.head.repo.full_name ==
github.event.pull_request.base.repo.full_name) or gate the whole job similarly;
alternatively, keep pull_request but change only the publish step/job to use
that same-repo conditional so forked PRs won’t attempt publishing.
- Around line 29-38: The current canary flow uses SHORT_SHA and sets VERSION
then always runs bun publish, which will fail on reruns for the same commit;
modify the job so that before running bun publish it checks the registry for the
computed VERSION (e.g., with npm view <package>@$VERSION or npm info
<package>@$VERSION) and if that version already exists skip the publish step
(echo/skipped) instead of attempting bun publish, keeping the existing VERSION
and SHORT_SHA logic and only invoking bun publish when the registry check
indicates the version is not present.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 905e6000-e347-4126-8bd8-894e3b928992

📥 Commits

Reviewing files that changed from the base of the PR and between c47312f and e11f924.

📒 Files selected for processing (1)
  • .github/workflows/canary.yml

Comment on lines +3 to +5
on:
pull_request:
types: [opened, synchronize]
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🌐 Web query:

In GitHub Actions, are repository secrets available to workflows triggered by the pull_request event from forked repositories?

💡 Result:

No—by default, repository secrets are not exposed to workflows triggered by the pull_request event when the PR comes from a fork (the workflow runs with minimal privileges and a read-only token). [1]

For private/internal repositories, an admin can explicitly enable a setting to “Send secrets to workflows from pull requests”, which would make secrets available to forked-PR workflows. [2]


Guard the publish path for forked PRs.

On pull_request, repository secrets are not exposed to workflows coming from forks, so the publish step will fail for external contributors and make the PR look broken. Gate this job or at least the publish step to same-repo PRs only.

Suggested change
 jobs:
   canary-release:
     name: Publish canary to npm
+    if: github.event.pull_request.head.repo.full_name == github.repository
     runs-on: ubuntu-latest
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.github/workflows/canary.yml around lines 3 - 5, The workflow is triggered
on pull_request which exposes repo secrets only for same-repo PRs, so guard the
publish path: modify the publish job or the specific publish step to run only
for same-repo PRs by adding an if condition that compares the PR head repo to
the base/repository (e.g. if: github.event.pull_request.head.repo.full_name ==
github.event.pull_request.base.repo.full_name) or gate the whole job similarly;
alternatively, keep pull_request but change only the publish step/job to use
that same-repo conditional so forked PRs won’t attempt publishing.

Comment on lines +29 to +38
- name: Set canary version
run: |
SHORT_SHA=$(echo "${{ github.event.pull_request.head.sha }}" | cut -c1-7)
VERSION=$(jq -r .version package.json)-canary.${SHORT_SHA}
jq --arg v "$VERSION" '.version = $v' package.json > package.tmp.json
mv package.tmp.json package.json
echo "Published version: $VERSION"

- name: Publish
run: bun publish --tag canary --access public
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🌐 Web query:

Can npm publish publish the same package version more than once, and what happens if a CI workflow retries publishing an already-existing version?

💡 Result:

No. The npm registry treats the (package name, version) pair as immutable:

  • npm publish fails if that name+version already exists in the target registry. Once a version is published, that exact name+version can never be used again, even if it was later removed via npm unpublish. [1]
  • Attempting to “publish over” an existing version is rejected by the registry (historically shown as HTTP 403 Forbidden). [2]

If a CI workflow retries npm publish for a version that already got published, the retry will not overwrite anything; it will simply error and exit non‑zero (commonly surfaced as “cannot publish over previously published version” / publish conflict). The already-published tarball/version remains unchanged. [1] [2]

Sources: [1] npm-publish manual page (npm publish fails if name+version exists; cannot ever reuse) • [2] npm blog (“No More npm publish -f”, republishing rejected/403)


Make reruns idempotent for the same commit.

The canary version is derived only from the PR head SHA, so any rerun for the same commit will attempt to republish the exact same npm version. npm rejects republishing any previously published version—the retry will fail immediately. This prevents recovery from transient publish failures without pushing a new commit. Either skip publish when the version already exists or include a rerun-safe suffix.

Suggested change
       - name: Set canary version
         run: |
+          PACKAGE_NAME=$(jq -r .name package.json)
           SHORT_SHA=$(echo "${{ github.event.pull_request.head.sha }}" | cut -c1-7)
           VERSION=$(jq -r .version package.json)-canary.${SHORT_SHA}
           jq --arg v "$VERSION" '.version = $v' package.json > package.tmp.json
           mv package.tmp.json package.json
+          echo "PACKAGE_NAME=$PACKAGE_NAME" >> "$GITHUB_ENV"
+          echo "VERSION=$VERSION" >> "$GITHUB_ENV"
           echo "Published version: $VERSION"

       - name: Publish
-        run: bun publish --tag canary --access public
+        run: |
+          if npm view "${PACKAGE_NAME}@${VERSION}" version >/dev/null 2>&1; then
+            echo "Canary ${VERSION} already exists; skipping publish."
+            exit 0
+          fi
+          bun publish --tag canary --access public
         env:
           NPM_CONFIG_TOKEN: ${{ secrets.NPM_TOKEN }}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.github/workflows/canary.yml around lines 29 - 38, The current canary flow
uses SHORT_SHA and sets VERSION then always runs bun publish, which will fail on
reruns for the same commit; modify the job so that before running bun publish it
checks the registry for the computed VERSION (e.g., with npm view
<package>@$VERSION or npm info <package>@$VERSION) and if that version already
exists skip the publish step (echo/skipped) instead of attempting bun publish,
keeping the existing VERSION and SHORT_SHA logic and only invoking bun publish
when the registry check indicates the version is not present.

rsbh and others added 2 commits March 12, 2026 20:15
…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>
…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>
Copy link

@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: 5

🧹 Nitpick comments (1)
packages/chronicle/src/cli/commands/serve.ts (1)

12-16: Consider extracting scaffold validation to a shared utility.

This scaffold validation logic is duplicated in build.ts (and likely other command files). Extracting it to a helper like ensureScaffold() in @/cli/utils would reduce duplication and ensure consistent error messaging across commands.

♻️ Example extraction
// In `@/cli/utils/scaffold.ts`
export function ensureScaffold(): string {
  const scaffoldPath = path.join(process.cwd(), '.chronicle')
  if (!fs.existsSync(scaffoldPath)) {
    console.log(chalk.red('Error: .chronicle/ not found. Run'), chalk.cyan('chronicle init'), chalk.red('first.'))
    process.exit(1)
  }
  return scaffoldPath
}

Then in command files:

-    const scaffoldPath = path.join(process.cwd(), '.chronicle')
-    if (!fs.existsSync(scaffoldPath)) {
-      console.log(chalk.red('Error: .chronicle/ not found. Run'), chalk.cyan('chronicle init'), chalk.red('first.'))
-      process.exit(1)
-    }
+    const scaffoldPath = ensureScaffold()
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/chronicle/src/cli/commands/serve.ts` around lines 12 - 16, Extract
the duplicated scaffold validation into a shared helper: create a new function
ensureScaffold() in a utils module (e.g., `@/cli/utils/scaffold.ts`) that computes
scaffoldPath = path.join(process.cwd(), '.chronicle'), checks
fs.existsSync(scaffoldPath), logs the same error messages with chalk and calls
process.exit(1) on failure, and returns scaffoldPath on success; then replace
the inline logic in serve.ts (and the similar block in build.ts) with calls to
ensureScaffold() to remove duplication and unify error messaging.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@packages/chronicle/src/cli/commands/init.ts`:
- Around line 72-79: The init command currently skips updating an existing
package.json (packageJsonPath) and only warns, causing required Chronicle fields
to be missing; modify the branch that handles an existing package.json to read
and parse the existing file, merge in the result of createPackageJson(dirName)
(only adding missing scripts, devDependencies, and other Chronicle-specific
fields without overwriting user values), then write the merged JSON back to
packageJsonPath before proceeding to scaffoldDir() and installation so
Chronicle's manifest entries are always present even when package.json already
exists.
- Around line 20-40: The createPackageJson function currently pins
'@raystack/chronicle' to 'latest' which breaks reproducibility; change
createPackageJson to read the running CLI/package version at runtime and use
that version string instead of 'latest' (e.g., derive from the package.json of
the running package or export the version from the CLI entry and consume it
here), update the dependency entry for '@raystack/chronicle' accordingly, and
also apply the same approach in packages/chronicle/src/cli/index.ts where the
CLI version is hardcoded so both uses come from the same package.json-derived
source.

In `@packages/chronicle/src/cli/commands/serve.ts`:
- Line 18: Wrap the call to resolveNextCli() in a try-catch and handle
MODULE_NOT_FOUND by logging a clear, user-friendly error (similar to the
.chronicle/ directory check) and exiting gracefully; specifically update
serve.ts (and mirror the same pattern in build.ts and start.ts) to catch errors
from resolveNextCli(), check err.code === 'MODULE_NOT_FOUND' and call the same
logger/message flow used for the .chronicle/ check before process.exit(1),
otherwise rethrow or log the unexpected error.

In `@packages/chronicle/src/cli/utils/resolve.ts`:
- Line 4: PACKAGE_ROOT currently resolves to packages/chronicle/src (one
directory too deep), causing scaffoldDir's COPY_FILES (which live at
packages/chronicle) to fail; update PACKAGE_ROOT in resolve.ts to climb three
levels from the current file (so it points at packages/chronicle) instead of
two. Locate the PACKAGE_ROOT constant and change the path.resolve call so it
goes up one more '..' (ensuring scaffoldDir and COPY_FILES reference the correct
package root).

In `@packages/chronicle/src/cli/utils/scaffold.ts`:
- Around line 96-99: The resolveNextCli function currently uses
createRequire(path.join(process.cwd(), 'package.json')) which resolves from the
project root and fails if the project doesn't list next; change it to resolve
next from Chronicle's package context by creating the require rooted at
Chronicle's package.json or the Chronicle module dirname (i.e., use
createRequire(path.join(__dirname, '..', 'package.json')) or
createRequire(require.resolve('@raystack/chronicle/package.json'))), then call
.resolve('next/dist/bin/next') so resolveNextCli reliably finds Next in
Chronicle's dependency scope.

---

Nitpick comments:
In `@packages/chronicle/src/cli/commands/serve.ts`:
- Around line 12-16: Extract the duplicated scaffold validation into a shared
helper: create a new function ensureScaffold() in a utils module (e.g.,
`@/cli/utils/scaffold.ts`) that computes scaffoldPath = path.join(process.cwd(),
'.chronicle'), checks fs.existsSync(scaffoldPath), logs the same error messages
with chalk and calls process.exit(1) on failure, and returns scaffoldPath on
success; then replace the inline logic in serve.ts (and the similar block in
build.ts) with calls to ensureScaffold() to remove duplication and unify error
messaging.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 201c9108-e194-4a8b-91c9-a81670215dab

📥 Commits

Reviewing files that changed from the base of the PR and between e11f924 and 3274160.

📒 Files selected for processing (10)
  • Dockerfile
  • packages/chronicle/src/cli/commands/build.ts
  • packages/chronicle/src/cli/commands/dev.ts
  • packages/chronicle/src/cli/commands/init.ts
  • packages/chronicle/src/cli/commands/serve.ts
  • packages/chronicle/src/cli/commands/start.ts
  • packages/chronicle/src/cli/utils/config.ts
  • packages/chronicle/src/cli/utils/index.ts
  • packages/chronicle/src/cli/utils/resolve.ts
  • packages/chronicle/src/cli/utils/scaffold.ts

Comment on lines +20 to +40
function createPackageJson(name: string): Record<string, unknown> {
return {
name,
private: true,
scripts: {
dev: 'chronicle dev',
build: 'chronicle build',
start: 'chronicle start',
},
dependencies: {
'@raystack/chronicle': 'latest',
},
devDependencies: {
'@raystack/tools-config': '0.56.0',
'openapi-types': '^12.1.3',
typescript: '5.9.3',
'@types/react': '^19.2.10',
'@types/node': '^25.1.0',
},
}
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== generated dependency spec =="
sed -n '20,40p' packages/chronicle/src/cli/commands/init.ts | rg -n '@raystack/chronicle|dependencies'

echo
echo "== current Chronicle package metadata =="
sed -n '1,80p' packages/chronicle/package.json | rg -n '"name"|"version"'

Repository: raystack/chronicle

Length of output: 259


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== Checking how init command is invoked =="
fd -e ts -e js | xargs grep -l "createPackageJson" | head -5

echo
echo "== Looking for version imports/access patterns =="
rg -A 5 "require.*package\.json|import.*version|readFileSync.*package" packages/chronicle/src/cli --type ts | head -30

echo
echo "== Check if Chronicle package.json is accessible at runtime =="
fd "package\.json" packages/chronicle

Repository: raystack/chronicle

Length of output: 244


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== Full init.ts file =="
wc -l packages/chronicle/src/cli/commands/init.ts

echo
echo "== Reading init.ts =="
cat -n packages/chronicle/src/cli/commands/init.ts | head -150

echo
echo "== Checking if package.json is in CLI context =="
ls -la packages/chronicle/ | grep -E "package\.json|tsconfig"

echo
echo "== Looking for any version or package metadata access =="
rg "version|packageVersion|__version__" packages/chronicle/src/cli --type ts -A 2

Repository: raystack/chronicle

Length of output: 5404


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== CLI version management in index.ts =="
cat -n packages/chronicle/src/cli/index.ts | head -40

echo
echo "== How could the version be accessed in init command =="
rg -B 5 -A 10 "\.version\(" packages/chronicle/src/cli/index.ts

Repository: raystack/chronicle

Length of output: 1209


Don't bootstrap projects against @raystack/chronicle@latest.

This makes chronicle init non-reproducible: a tarball, canary, or locally linked install can immediately replace itself with whatever the registry's latest tag points to at line 30. Derive the dependency version from the currently running Chronicle package instead of hardcoding latest.

Note: The CLI version itself is also hardcoded in packages/chronicle/src/cli/index.ts line 13; consider reading both from package.json at runtime to ensure consistency.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/chronicle/src/cli/commands/init.ts` around lines 20 - 40, The
createPackageJson function currently pins '@raystack/chronicle' to 'latest'
which breaks reproducibility; change createPackageJson to read the running
CLI/package version at runtime and use that version string instead of 'latest'
(e.g., derive from the package.json of the running package or export the version
from the CLI entry and consume it here), update the dependency entry for
'@raystack/chronicle' accordingly, and also apply the same approach in
packages/chronicle/src/cli/index.ts where the CLI version is hardcoded so both
uses come from the same package.json-derived source.

process.exit(1)
}

const nextCli = resolveNextCli()
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Check how resolveNextCli handles resolution failures
ast-grep --pattern $'function resolveNextCli() {
  $$$
}'

# Also check if there's any try-catch wrapper
rg -nA 10 'resolveNextCli' packages/chronicle/src/cli/utils/

Repository: raystack/chronicle

Length of output: 1018


🏁 Script executed:

# Check serve.ts context around line 18
cat -n packages/chronicle/src/cli/commands/serve.ts | head -30

# Search for try-catch blocks in serve.ts
rg -n 'try|catch' packages/chronicle/src/cli/commands/serve.ts

# Also check if there's any error handling at module load time
rg -nB 5 -A 5 'resolveNextCli' packages/chronicle/src/cli/commands/serve.ts

Repository: raystack/chronicle

Length of output: 1998


🏁 Script executed:

# Check the main CLI entry point for error handlers
fd 'index\.ts|main\.ts|cli\.ts' packages/chronicle/src/cli/ | head -5 | xargs -I {} sh -c "echo '=== {} ===' && head -50 {}"

# Check other commands for error handling patterns
rg -n 'try|catch' packages/chronicle/src/cli/commands/ | head -20

# Check if there's a global error handler
rg -rn 'process.on.*error|uncaughtException' packages/chronicle/src/

Repository: raystack/chronicle

Length of output: 1226


🏁 Script executed:

# Check build command for comparison
cat -n packages/chronicle/src/cli/commands/build.ts | head -40

# Check start command for comparison
cat -n packages/chronicle/src/cli/commands/start.ts | head -40

Repository: raystack/chronicle

Length of output: 2427


Add error handling for Next.js resolution failure in resolveNextCli() call.

resolveNextCli() uses require.resolve() which throws MODULE_NOT_FOUND if Next.js is not installed. Currently, this error is unhandled and will crash with a stack trace. Add a try-catch block to provide a user-friendly error message consistent with the .chronicle/ directory check above it.

This issue also affects build.ts and start.ts, which call resolveNextCli() in the same way.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/chronicle/src/cli/commands/serve.ts` at line 18, Wrap the call to
resolveNextCli() in a try-catch and handle MODULE_NOT_FOUND by logging a clear,
user-friendly error (similar to the .chronicle/ directory check) and exiting
gracefully; specifically update serve.ts (and mirror the same pattern in
build.ts and start.ts) to catch errors from resolveNextCli(), check err.code ===
'MODULE_NOT_FOUND' and call the same logger/message flow used for the
.chronicle/ check before process.exit(1), otherwise rethrow or log the
unexpected error.

import path from 'path'
import { fileURLToPath } from 'url'

export const PACKAGE_ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..', '..')
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

python - <<'PY'
import os
f = os.path.abspath("packages/chronicle/src/cli/utils/resolve.ts")
d = os.path.dirname(f)
two = os.path.normpath(os.path.join(d, "..", ".."))
three = os.path.normpath(os.path.join(d, "..", "..", ".."))

print("two-up   :", two)
print("three-up :", three)
for rel in ("src", "source.config.ts", "tsconfig.json"):
    print(f"{rel}: two-up={os.path.exists(os.path.join(two, rel))} three-up={os.path.exists(os.path.join(three, rel))}")
PY

Repository: raystack/chronicle

Length of output: 282


🏁 Script executed:

# Search for all usages of PACKAGE_ROOT in the codebase
rg "PACKAGE_ROOT" -n

Repository: raystack/chronicle

Length of output: 389


🏁 Script executed:

# Read the scaffold.ts file to understand the PACKAGE_ROOT usage
sed -n '100,120p' packages/chronicle/src/cli/utils/scaffold.ts

Repository: raystack/chronicle

Length of output: 617


🏁 Script executed:

# Find the COPY_FILES definition in scaffold.ts
rg "COPY_FILES" packages/chronicle/src/cli/utils/scaffold.ts -B 5 -A 5

Repository: raystack/chronicle

Length of output: 701


PACKAGE_ROOT is one directory too shallow.

From packages/chronicle/src/cli/utils/resolve.ts, .. / .. resolves to packages/chronicle/src. However, scaffoldDir() copies files from COPY_FILES = ['src', 'source.config.ts', 'tsconfig.json'] by joining each with PACKAGE_ROOT. These files exist at packages/chronicle, not packages/chronicle/src, so the copy operation will fail during init.

Suggested fix
-export const PACKAGE_ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..', '..')
+export const PACKAGE_ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..', '..', '..')
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/chronicle/src/cli/utils/resolve.ts` at line 4, PACKAGE_ROOT
currently resolves to packages/chronicle/src (one directory too deep), causing
scaffoldDir's COPY_FILES (which live at packages/chronicle) to fail; update
PACKAGE_ROOT in resolve.ts to climb three levels from the current file (so it
points at packages/chronicle) instead of two. Locate the PACKAGE_ROOT constant
and change the path.resolve call so it goes up one more '..' (ensuring
scaffoldDir and COPY_FILES reference the correct package root).

Comment on lines +96 to +99
export function resolveNextCli(): string {
const cwdRequire = createRequire(path.join(process.cwd(), 'package.json'))
return cwdRequire.resolve('next/dist/bin/next')
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== resolveNextCli implementation =="
sed -n '96,99p' packages/chronicle/src/cli/utils/scaffold.ts

echo
echo "== generated project manifest =="
sed -n '20,40p' packages/chronicle/src/cli/commands/init.ts

echo
echo "== Chronicle package manifest =="
sed -n '1,200p' packages/chronicle/package.json | rg -n '"(name|version|next|react|react-dom)"'

Repository: raystack/chronicle

Length of output: 972


Resolve next from Chronicle's package context, not the project root.

createRequire(path.join(process.cwd(), 'package.json')).resolve('next/dist/bin/next') fails when the project root doesn't declare next as a dependency. The generated manifest (lines 20–40 of packages/chronicle/src/cli/commands/init.ts) only includes @raystack/chronicle, while next exists solely in Chronicle's own package.json. Under non-hoisting package managers (pnpm, Yarn PnP), this resolution will fail because next is not accessible from the project root's dependency tree.

Resolve from the Chronicle package context instead (e.g., using createRequire() pointing to Chronicle's package.json or dirname) to ensure next/dist/bin/next is located within the correct dependency scope.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/chronicle/src/cli/utils/scaffold.ts` around lines 96 - 99, The
resolveNextCli function currently uses createRequire(path.join(process.cwd(),
'package.json')) which resolves from the project root and fails if the project
doesn't list next; change it to resolve next from Chronicle's package context by
creating the require rooted at Chronicle's package.json or the Chronicle module
dirname (i.e., use createRequire(path.join(__dirname, '..', 'package.json')) or
createRequire(require.resolve('@raystack/chronicle/package.json'))), then call
.resolve('next/dist/bin/next') so resolveNextCli reliably finds Next in
Chronicle's dependency scope.

rsbh and others added 3 commits March 12, 2026 23:56
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@rsbh rsbh force-pushed the fix_nextjs_path branch from b2934ee to 3197f84 Compare March 13, 2026 03:52
rsbh and others added 8 commits March 13, 2026 09:27
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- 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>
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>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…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>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@rsbh rsbh changed the title fix: resolve next binary via createRequire and config from cwd fix: scaffold .chronicle/ directory for Turbopack compatibility Mar 14, 2026
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.

1 participant