Pipeline

The sync pipeline that transforms config into content.

Overview

The sync() function in packages/cli/src/lib/sync/index.ts runs a four-phase pipeline: setup, resolve, write, and generate. This expands the three-phase model from the Engine Overview into the individual steps that run within each phase.

Steps

#StepPhaseWhat it does
1Create dirsSetupCreate .ciderpress/content/, .ciderpress/content/.generated/, and .ciderpress/public/ directories
2Load manifestSetupLoad previous sync manifest for incremental diffing
3Generate assetsSetupGenerate branded SVG assets (conditional -- skipped when asset config hash unchanged)
4Copy public/SetupCopy public assets into .ciderpress/public/ for Rspress
5Synthesize workspacesResolveConvert apps/packages/workspaces into entry sections
6Resolve entriesResolveWalk config tree, resolve globs, derive text, merge frontmatter
7Enrich cardsResolveAttach workspace metadata (icon, scope, tags, badge)
8Inject landing pagesResolveGenerate virtual MDX for sections with children but no page
9Collect pagesResolveFlatten the resolved tree into a flat page list
10Write workspace dataResolveSerialize workspace metadata to .ciderpress/content/.generated/workspaces.json
11Generate homeResolveCreate default home page from config metadata (when no explicit index.md)
12Discover planning pagesResolveResolve pages from .planning/ directory (included in output, excluded from sidebar/nav)
13OpenAPI syncResolveDereference specs, generate .mdx per operation
14Copy pagesWriteWrite pages with injected frontmatter, rewrite links, track hashes (parallel)
15Clean stale filesWriteRemove files present in old manifest but absent in new; prune empty directories
16Write sidebar + navGenerateWrite _meta.json per directory and _nav.json at content root (Rspress's native format). Also writes debug snapshots .generated/sidebar.json and .generated/nav.json for tooling
17Save manifestGenerateRecord file hashes and incremental metadata
18Write READMEGenerateWrite bare-bones README to .ciderpress/ root

Returns: { pagesWritten, pagesSkipped, pagesRemoved, elapsed } (elapsed in milliseconds)

Page Transformation

Each page passes through copyPage() (sync/copy.ts):

  1. Mtime skip check -- If source mtime and frontmatter hash match the previous manifest, return the cached entry (see Incremental Sync)
  2. Read source file (or evaluate virtual content)
  3. Rewrite relative markdown links using source-to-output path map
  4. Copy referenced images to content/public/images/, rewrite paths to /images/<name>-<hash>.<ext> (8-char MD5 of the image path)
  5. Merge frontmatter (config defaults + source frontmatter, source wins)
  6. SHA-256 content hash -- skip write if unchanged from previous manifest
  7. Write final markdown/MDX to .ciderpress/content/

Entry Resolution

The entry resolver (sync/resolve/index.ts) recursively walks the config tree and resolves each entry:

Entry typeDescriptionExample
Single fileSource file with explicit linkinclude: 'docs/getting-started.md'
Virtual pageGenerated contentcontent: () => '# Hello'
Glob sectionPattern that discovers filesinclude: 'docs/guides/*.md'
Recursive globDirectory-driven nestinginclude: 'docs/**/*.md', recursive: true
Explicit itemsHand-written child entriesitems: [{ title: '...', include: '...' }]

Text Derivation

Text derivation is configurable via the title field's from property:

ValueSource
'auto'Frontmatter title, then first # heading, then filename (default)
'filename'Kebab-case filename to title case
'heading'First # heading in the markdown file
'frontmatter'title from YAML frontmatter, falling back to heading, then filename

Multi-Sidebar

ciderpress generates a multi-sidebar structure for Rspress. Root entries share the / namespace. Standalone scopes (workspace items, sections with standalone: true, root-level OpenAPI sidebars) get their own namespace (e.g., /apps/api/). Each scope has an independent sidebar tree.

The two relevant entry fields are:

FieldSourceEffect
standaloneSection config (standalone: true) or synthesized workspace itemSection gets its own sidebar scope rooted at the entry's link
rootSynthesized for workspaces and root-level OpenAPI mountsSection is treated as a top-level standalone scope

Runtime contract: scopes.json

The list of standalone scope paths is collected at sync time (packages/cli/src/lib/sync/index.ts:378-380 filters on e.standalone || e.root) and written to .ciderpress/content/.generated/scopes.json. The UI loads it at runtime (packages/ui/src/config.ts:89-98) so the custom Sidebar component can isolate each standalone scope into its own pane.

References