Aphex

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.yml

Local 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 Turborepo

Admin 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

ChangeBehavior
Schema filesPicked up on the next request.
Component changesInstant via Vite HMR.
cms-core sourceLive — consumed from source via the workspace protocol.
postgresql-adapter sourceRequires a rebuild + dev server restart (consumed from dist).
storage-s3 sourceRequires a rebuild + dev server restart (consumed from dist).
Drizzle schema changespnpm 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/ui

Tests 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 only

Commits & 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 changeset

Pick 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:

SecretPurpose
GITHUB_TOKENAuto-provided. Used to open the version PR.
NPM_TOKENRequired 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 files

The 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 its post.ts example, not studio's fixtures.
  • src/app.css — skipped. Template uses node_modules/@aphexcms/*/dist paths for Tailwind @source.
  • package.json — merged. Studio content wins; template's name and version are 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 build

This 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-test

Mirror workflows

Two GitHub Actions push subdirectories of the monorepo to standalone public repos:

WorkflowWhatDestinationSecret
.github/workflows/sync-template.ymltemplates/base/ (with workspace:* resolved)IcelandicIcecream/aphex-baseTEMPLATE_REPO_DEPLOY_KEY
.github/workflows/sync-docs.ymldocs/aphex-docs/IcelandicIcecream/aphex-docsDOCS_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 addingWhere it goes
New field typepackages/cms-core/src/lib/types/schemas.ts + an editor in components/admin/fields/
New database adapterNew package implementing DatabaseAdapter and its sub-interfaces
New storage adapterNew package implementing StorageAdapter
New email adapterNew package implementing EmailAdapter
Custom HTTP route or middlewareapi(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

Edit on GitHub

Last updated on