Contributing
How to work on Aphex itself — the monorepo, dev workflow, releases, and the studio → template → CLI sync chain.
The canonical contributor guide is CONTRIBUTING.md in the repo. This page mirrors the highlights so you can browse them alongside the rest of the docs.
Aphex is open source and contributions are welcome — bug fixes, new field types, database or storage adapters, UI improvements, and docs all help. Browse the open issues for ideas.
Prerequisites
- Node.js 20+
- pnpm 10+ (
npm install -g pnpm) - Docker + Docker Compose
- Git with SSH keys configured
Repository layout
aphex/
├── apps/
│ └── studio/ # Reference SvelteKit app — features land here first
├── packages/
│ ├── cms-core/ # Database-agnostic engine + admin UI
│ ├── postgresql-adapter/
│ ├── storage-s3/
│ ├── nodemailer-adapter/
│ ├── resend-adapter/
│ ├── ui/ # Shared shadcn-svelte components
│ ├── create-aphex/ # `pnpm create aphex` scaffolder
│ └── cli/ # `aphx` thin wrapper around create-aphex
├── templates/
│ └── base/ # Starter template (mirrored to aphex-base)
├── docs/
│ └── aphex-docs/ # This site (mirrored to aphex-docs)
└── .github/workflows/ # release.yml, sync-template.yml, sync-docs.ymlLocal setup
git clone [email protected]:IcelandicIcecream/aphex.git
cd aphex
pnpm install
cp apps/studio/.env.example apps/studio/.env
pnpm db:start # Postgres + Mailpit via Docker
pnpm db:push # apply schema (dev only)
pnpm dev # studio + cms-core via TurborepoAdmin UI lands at http://localhost:5173/admin. The first user to sign up at /login becomes super admin and gets a default org.
Hot reload behavior
| Change | Behavior |
|---|---|
| Schema files | Picked up on the next request. |
| Component changes | Instant via Vite HMR. |
cms-core source | Live — consumed from source via the workspace protocol. |
postgresql-adapter source | Requires a rebuild + dev server restart (consumed from dist). |
storage-s3 source | Requires a rebuild + dev server restart (consumed from dist). |
| Drizzle schema changes | pnpm db:push (dev) or generate + migrate cycle. |
Common commands
pnpm dev # studio + cms-core
pnpm build # build everything (Turborepo)
pnpm check # type-check all packages
pnpm lint # Prettier + ESLint
pnpm format # write Prettier formatting
pnpm test:package # build + type-check cms-core
pnpm shadcn <name> # add shadcn-svelte components to @aphexcms/uiTests live in apps/studio/tests/:
pnpm -F @aphexcms/studio test # all
pnpm -F @aphexcms/studio test:local # Local API only
pnpm -F @aphexcms/studio test:http # HTTP API only
pnpm -F @aphexcms/studio test:graphql # GraphQL onlyCommits & PRs
Conventional Commits — feat:, fix:, docs:, refactor:, chore:, test:. One feature or fix per PR; aim for under 500 lines of diff.
If your PR touches a published package, add a changeset:
pnpm changesetPick the affected packages, the bump type (patch / minor / major), and write a one-liner. Commit the generated .changeset/*.md file with the rest of your PR. Skip the changeset for docs-only or studio-only changes.
Releases & publishing
Releases run through Changesets and .github/workflows/release.yml. The flow on every push to main:
Pending changesets exist → workflow opens (or updates) a chore: version packages PR that bumps versions in every affected package.json and regenerates each package's CHANGELOG.md.
Version PR merges → workflow runs pnpm release which is turbo build --filter='./packages/*' && changeset publish --provenance. Packages ship to npm with a signed SLSA provenance statement.
Tags and GitHub Releases are created automatically by changesets/action.
Published packages: @aphexcms/cms-core, @aphexcms/postgresql-adapter, @aphexcms/storage-s3, @aphexcms/nodemailer-adapter, @aphexcms/resend-adapter, @aphexcms/ui, create-aphex, and @aphexcms/cli (the aphx bin). The studio, base, and aphex-docs packages are explicitly ignored in .changeset/config.json — they ship via mirror repos instead of npm.
Required repo secrets:
| Secret | Purpose |
|---|---|
GITHUB_TOKEN | Auto-provided. Used to open the version PR. |
NPM_TOKEN | Required unless npm trusted publishing is configured for the @aphexcms scope. |
Studio → template sync
apps/studio is the working reference. templates/base/ is the starter shipped to end users via pnpm create aphex (or npm create aphex@latest). To flow studio changes into the template:
./scripts/sync-template.sh # dry run — preview changes
./scripts/sync-template.sh --apply # actually copy filesThe script is template-driven: it walks every file tracked in templates/base/ and copies the matching file from apps/studio/ if it exists. Studio-only files (tests, seed routes) don't get copied because the template has no matching path. Three special cases:
src/lib/schemaTypes/**— skipped. Template keeps itspost.tsexample, not studio's fixtures.src/app.css— skipped. Template usesnode_modules/@aphexcms/*/distpaths for Tailwind@source.package.json— merged. Studio content wins; template'snameandversionare preserved.
If studio adds a brand-new file or directory, create a placeholder in templates/base/ first so the next sync picks it up.
After applying, update templates/base/CHANGELOG.md under ## Unreleased so downstream users know what changed — the template is meant to be customized, so changes don't auto-apply to anyone's existing project.
To refresh the scaffolder so pnpm create aphex ships the new template:
pnpm -F create-aphex buildThis runs scripts/copy-templates.js which copies templates/ into packages/create-aphex/templates/ and rewrites workspace:* deps to concrete versions, then tsc builds the CLI bundle. The bundled templates/ is what npm publishes — the root templates/ is for the monorepo's working state and the standalone mirror.
node packages/create-aphex/dist/index.js my-test-app # smoke-testMirror workflows
Two GitHub Actions push subdirectories of the monorepo to standalone public repos:
| Workflow | What | Destination | Secret |
|---|---|---|---|
.github/workflows/sync-template.yml | templates/base/ (with workspace:* resolved) | IcelandicIcecream/aphex-base | TEMPLATE_REPO_DEPLOY_KEY |
.github/workflows/sync-docs.yml | docs/aphex-docs/ | IcelandicIcecream/aphex-docs | DOCS_REPO_DEPLOY_KEY |
Both run on pushes to main that touch the relevant directory and use s0/git-publish-subdir-action under the hood.
Setting up a deploy key
ssh-keygen -t ed25519 -f my_repo_deploy -N ""Public key (my_repo_deploy.pub) → destination repo → Settings → Deploy keys → Add deploy key. Tick "Allow write access".
Private key (full file content, including the -----BEGIN/-----END markers and trailing newline) → source repo → Settings → Secrets and variables → Actions → New repository secret. Name it to match the workflow (TEMPLATE_REPO_DEPLOY_KEY or DOCS_REPO_DEPLOY_KEY).
Push a change touching the relevant directory and watch the Actions tab — first run prints the SSH handshake.
Adding new things
| What you're adding | Where it goes |
|---|---|
| New field type | packages/cms-core/src/lib/types/schemas.ts + an editor in components/admin/fields/ |
| New database adapter | New package implementing DatabaseAdapter and its sub-interfaces |
| New storage adapter | New package implementing StorageAdapter |
| New email adapter | New package implementing EmailAdapter |
| Custom HTTP route or middleware | api(app) hook in aphex.config.ts (no plugin system) |
See ARCHITECTURE.md for the deep dive on adapter interfaces and the request lifecycle.
See also
Last updated on