Getting started

TinyCld is an npm workspace that ties together a set of independent repositories: a shared library (@tinycld/core), an Expo/PocketBase app shell (@tinycld/app), and a set of feature packages (@tinycld/mail, @tinycld/contacts, @tinycld/calendar, @tinycld/drive, and more) that the app opts into at build time. A checkout with just app + core runs as a lean shell with zero features - you clone exactly the feature packages you want to work on, and the generator wires in whichever are present.

Ecosystem layout

A workspace meta-repo (tinycld/workspace) is the root. It holds the root package.json (whose workspaces array lists every possible member), the tinycld-pkg CLI under package-scripts/, shared test stubs, tinycld.packages.ts, and the pinned .node-version / .go-version. Clone it to ~/code/tinycld/; every other member is its own repo, cloned in beside it as a sibling directory:

~/code/tinycld/            # tinycld/workspace - the npm-workspace root
    package.json           # the workspaces member list (all possible members)
    package-scripts/       # the tinycld-pkg per-member CLI (ships here)
    tests/                 # shared unit-test stubs
    tinycld.packages.ts    # member enumeration for the generator
    app/                   # @tinycld/app - the Expo/PocketBase shell (own repo)
    core/                  # @tinycld/core - shared TS + Go library (own repo)
    contacts/              # @tinycld/contacts ─┐
    mail/                  # @tinycld/mail       │
    calendar/              # @tinycld/calendar   │ feature packages, each its own repo
    drive/                 # @tinycld/drive      │
    calc/                  # @tinycld/calc       │
    text/                  # @tinycld/text       │
    google-takeout-import/ #                    ─┘

app and core are always present. Each feature is its own git repo with its own history, issues, and CI, and you only clone the ones you intend to work on. The workspace package.json lists every possible member, but npm tolerates members whose directories are absent - so a partial checkout installs and runs cleanly.

How the workspace, core, app, and features relate

@tinycld/core is its own repo (tinycld/core) and a workspace member at ~/code/tinycld/core/. It holds the runtime: React Native, Expo Router, the PocketBase client, pbtsdb, shared UI components, theming, auth. Every feature package peer-depends on those; none of them ship their own copies. Features import from @tinycld/core/lib/* and @tinycld/core/ui/*, never the other way around. The @tinycld/app shell (tinycld/app) owns the bundler config, the generator, and the Expo Router app/ tree, and consumes @tinycld/core like any other member.

Features do not depend on each other. If the takeout importer wants to know whether mail is installed, it reads the runtime package registry (usePackages()) rather than taking a compile-time import on @tinycld/mail. This keeps each feature independently releasable and keeps the lean-shell guarantee intact - a checkout with no feature packages still typechecks and runs.

Fresh-machine setup

Use the bootstrap CLI’s tooling mode to assemble the workspace — it clones the workspace root plus app + core, and adds whatever features you name with --with — then run a single npm install at the root:

mkdir ~/code/tinycld && cd ~/code/tinycld
# clone the workspace root + app + core + the package(s) you want (a subset is fine):
npx @tinycld/bootstrap@latest --tooling --with mail --with contacts
npm install            # links members + runs the generator (postinstall)
cd app && npm run dev

The root npm install creates the node_modules/@tinycld/* symlinks for every present member and runs the generator via the postinstall hook. You do not have to clone every feature - the generator scans whichever member directories are present, and the app boots as a lean shell when none are. To add another feature later, clone it as a sibling (or npx @tinycld/bootstrap --tooling --with <pkg> again) and re-run npm install at the root.

The dev loop

cd ~/code/tinycld/app
npm run dev           # runs the generator, then starts Expo + PocketBase
npm run checks        # biome lint + format + typecheck
npx tinycld-pkg test --all   # vitest across every present member
npx tinycld-pkg test:e2e --all  # playwright across every present member

cd app && npm run dev runs the package generator first, then starts the normal Expo dev server, a local PocketBase instance, and a single-port HTTP proxy that fronts both — open http://localhost:7100 (or https:// if certs are present) and the app talks to PB same-origin via /api. The generator produces re-exports under app/app/a/[orgSlug]/<slug>/..., writes app/tinycld.config.ts (the typed installed-package source of truth), and symlinks package migrations into app/server/pb_migrations/. You don’t run it manually - the dev launcher and npm install (via postinstall) both invoke it automatically.

Per-member checks

The tinycld-pkg CLI runs checks scoped to a single member, or across every present member with --all. From any member directory:

cd ~/code/tinycld/contacts
npx tinycld-pkg check     # typecheck + unit tests for this member
npx tinycld-pkg test      # unit tests only
npx tinycld-pkg test:e2e  # playwright specs for this member

From anywhere, tinycld-pkg <verb> --all runs the verb against only the members that are present. Feature unit/e2e tests are discovered automatically from each member’s tests/ directory; cloning or removing a feature changes the run with no config edit.

Where to go next