fix: scaffold .chronicle/ directory for Turbopack compatibility#21
fix: scaffold .chronicle/ directory for Turbopack compatibility#21
Conversation
…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>
|
Note Reviews pausedIt 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 Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
📝 WalkthroughWalkthroughReplaces content-dir driven flows with a scaffold-first workflow: CLI commands now require a local Changes
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
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Possibly related PRs
Suggested reviewers
🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches
🧪 Generate unit tests (beta)
📝 Coding Plan
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. Comment Tip You can get early access to new features in CodeRabbit.Enable the |
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (2)
packages/chronicle/src/cli/utils/config.ts (1)
19-25: Code duplication withlib/config.ts.This
resolveConfigPathimplementation duplicates the logic inpackages/chronicle/src/lib/config.ts:14-23, with slight semantic differences: the CLI version takescontentDiras a parameter while the lib version reads fromprocess.env.CHRONICLE_CONTENT_DIRdirectly. 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 acrossdev.ts,build.ts,start.ts, andserve.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
📒 Files selected for processing (7)
packages/chronicle/bin/chronicle.jspackages/chronicle/src/cli/commands/build.tspackages/chronicle/src/cli/commands/dev.tspackages/chronicle/src/cli/commands/serve.tspackages/chronicle/src/cli/commands/start.tspackages/chronicle/src/cli/utils/config.tspackages/chronicle/src/lib/config.ts
| if (!configPath) { | ||
| console.log(chalk.red('Error: chronicle.yaml not found in'), process.cwd(), 'or', contentDir) |
There was a problem hiding this comment.
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.
| 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>
There was a problem hiding this comment.
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
📒 Files selected for processing (1)
.github/workflows/canary.yml
.github/workflows/canary.yml
Outdated
| on: | ||
| pull_request: | ||
| types: [opened, synchronize] |
There was a problem hiding this comment.
🧩 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.
.github/workflows/canary.yml
Outdated
| - 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 |
There was a problem hiding this comment.
🧩 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 publishfails 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 vianpm 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.
…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>
There was a problem hiding this comment.
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 likeensureScaffold()in@/cli/utilswould 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
📒 Files selected for processing (10)
Dockerfilepackages/chronicle/src/cli/commands/build.tspackages/chronicle/src/cli/commands/dev.tspackages/chronicle/src/cli/commands/init.tspackages/chronicle/src/cli/commands/serve.tspackages/chronicle/src/cli/commands/start.tspackages/chronicle/src/cli/utils/config.tspackages/chronicle/src/cli/utils/index.tspackages/chronicle/src/cli/utils/resolve.tspackages/chronicle/src/cli/utils/scaffold.ts
| 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', | ||
| }, | ||
| } | ||
| } |
There was a problem hiding this comment.
🧩 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/chronicleRepository: 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 2Repository: 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.tsRepository: 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() |
There was a problem hiding this comment.
🧩 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.tsRepository: 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 -40Repository: 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)), '..', '..') |
There was a problem hiding this comment.
🧩 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))}")
PYRepository: raystack/chronicle
Length of output: 282
🏁 Script executed:
# Search for all usages of PACKAGE_ROOT in the codebase
rg "PACKAGE_ROOT" -nRepository: 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.tsRepository: 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 5Repository: 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).
| export function resolveNextCli(): string { | ||
| const cwdRequire = createRequire(path.join(process.cwd(), 'package.json')) | ||
| return cwdRequire.resolve('next/dist/bin/next') | ||
| } |
There was a problem hiding this comment.
🧩 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.
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>
- 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>
Summary
chronicle initcreates.chronicle/directory with copied source files, so Turbopack compiles TypeScript withoutnode_modulesrestrictions.chronicle/withCHRONICLE_PROJECT_ROOTenv var for config resolutionpackage.json, sets"type": "module", addsnode_modules/.next/.chronicleto.gitignore.source/serverimport: use tsconfig path alias@/.source/*instead of relative path<mark>tags viadangerouslySetInnerHTML@raystack/tools-config(unavailable to consumers)openapi-typesfrom devDependencies to dependenciesTest plan
npm packand install tarball in a fresh projectchronicle initcreates correct structurechronicle initon existing project merges missing scripts/depschronicle devstarts dev server, hot reload workschronicle build && chronicle startworks<mark>tags)chronicle.yamlconfig is read from project root🤖 Generated with Claude Code