create-syncular-app
v0.15.45
Published
Scaffold a new Syncular app: `create-syncular-app`
Maintainers
Readme
create-syncular-app
The create-syncular-app scaffolder — the create-app experience for syncular.
bun create syncular-app my-app # prompts for the template
bunx create-syncular-app my-app --template webTemplates
| Template | Shape |
|---|---|
| minimal | Server + a terminal two-client convergence demo (no browser) — migrations + manifest + generate wiring. Copy-evolved from examples/quickstart. The smallest honest starting point. |
| web | Hono server + WebSocket realtime + a single-pane browser todo app whose whole client core runs in a Web Worker on OPFS. Derived from apps/demo, slimmed to one pane (no conflict simulator, no blob attachments) — the minimal browser app a real user starts from. |
| tauri | One React codebase, web + desktop: the web template's server plus a shared React tree behind the __TAURI_INTERNALS__ engine seam (src/frontend/engine.ts) — worker core on OPFS in the browser, native Rust core in a src-tauri/ host (tauri-plugin-syncular from crates.io, native-transport). Derived from bindings/tauri/example + the web+desktop guide. |
Each template ships its own README.md (run steps, what to edit first),
.gitignore (as gitignore — see below), a working tsconfig.json, and a
smoke test.
The local-vs-published dependency mechanism
Template package.json files use workspace:* ranges for every
@syncular/* dependency. At scaffold time the scaffolder rewrites those
ranges:
--local(or the in-tree test path): keepworkspace:*verbatim. These are the only ranges that resolve when the scaffolded app sits inside this repo's workspace.- default (a normal
bunx create-…run): rewrite toPUBLISHED_DEPENDENCY_RANGE(src/constants.ts), derived from the scaffolder's own version because all Syncular packages release in lockstep.
.gitignore ships as gitignore (no dot) because npm strips real dotfiles
from published tarballs; the scaffolder renames it on copy.
Placeholder substitution is deliberately dumb and greppable: the only token is
__PROJECT_NAME__ (in each template's package.json and README.md),
replaced with the derived package name.
How the templates are tested (and how the tier splits)
test/scaffold.test.ts exercises the TEMPLATES THEMSELVES, not just the
scaffolder logic. For each template it:
- scaffolds into a temp dir (
--local), - asserts the tree shape + placeholder substitution + package.json rewrite,
- runs
syncular generate --check— proving immutable migration history and the committedsyncular.generated.tsare byte-fresh, - links a
node_modulesinto the temp dir offline (seetest/link-workspace.ts:@syncular/*→ the real package dirs, external deps → the workspace.bunhoist store), typechecks the template's own files, and runs the app's ownbun testsmoke.
Steps 1–4 are the always-run tier — offline and fast (~1.5s total), so
they ride the normal bun run check. A full-fidelity tier behind
SYNCULAR_TEMPLATE_INSTALL=1 additionally does a real bun install per
template before running the smoke; it is opt-in because it needs the network.
The in-tree template *.test.ts files are excluded from the root bun test
sweep (--path-ignore-patterns '**/create-app/template/**' in the root test
script) — they can only resolve their deps inside a scaffolded, linked copy,
which the tier test above provides.
The tauri template's desktop half (src-tauri/) is deliberately outside
both tiers: compiling it needs the Rust toolchain, the Tauri system
libraries, and the crates.io registry. Its compile proof is a local
cargo check in a scaffolded app (documented in the template README) plus
the tauri-bindings CI job, which builds the same plugin wiring from the
in-tree path dep — the honest scoping the tauri/RN binding gates already
use. The webview side (engine seam, React tree, build-frontend.ts) rides
the always-run tier like any other template file.
