Contributing

Workspace

How the Pluto repositories are organized, and the rules that apply to all of them.

Pluto is a set of repositories under the plutocms GitHub organization. Each project is its own git repository. A shared workspace folder holds them side by side.

Repositories

RepositoryPackagePublished
pluto@plutocms/core (packages/core), @plutocms/docs (packages/layers/docs), @plutocms/3d (packages/layers/addons/3d)Yes
supabase@plutocms/supabase, @plutocms/supabase-shop, @plutocms/supabase-blog (packages/<name>)Yes
create-plutocreate-plutoYes
pluto-supabase-shop-templateNoneNo. A giget template.
pluto-admin-templateNoneNo. A giget template (Dashboard only, both backends).
pluto-blog-templateNoneNo. A giget template (the blog, both backends).
plutocmsNoneNo. The documentation site.

The pluto repository is a Bun workspace with many packages: core in packages/core, the feature layers in packages/layers/ (docs), and the add-on layers in packages/layers/addons/. Each package has its own version and its own releases. The pluto repository has no backend: it holds the packages that every backend uses.

The supabase repository is a Bun workspace too. It holds the Supabase integration: @plutocms/supabase and its feature layers @plutocms/supabase-shop and @plutocms/supabase-blog, in packages/<name>. A change to the integration is one pull request in this repository. A future backend gets its own repository in the same way.

Stay inside the project that you work on. Do not change another project unless the task needs it.

Commands

Every layer repository uses Bun. These scripts exist in most of them:

  • bun install installs the dependencies. Then the postinstall script of the root runs the dev:prepare script (nuxt prepare) of each package. An app that installs a layer does not run it.
  • bun run dev runs the layer as an app.
  • bun run lint, bun run typecheck, bun run test, and bun run build. A repository defines only the scripts that it needs.

In the pluto repository, run the scripts at the root. They run the scripts of each package. There, the postinstall script of the root runs dev:prepare in packages/core and packages/layers/docs. In the supabase repository, it runs dev:prepare in each of the three packages.

The playground

The pluto repository has a playground: playground/, a small app that extends '@plutocms/core' and '@plutocms/3d' by name. The Bun workspace links these names to the folders in packages/, so a change in a layer shows in the playground at once. bun run dev at the root starts it.

The playground uses the Git CMS. In development, a save in the admin is a commit in your local repository, which is the pluto repository. Do not save content or upload files in the playground.

Lint

The layers do not register the @nuxt/eslint module. A module of a layer runs in every app that extends the layer. Each repository has an eslint.config.mjs that uses createConfigForNuxt from @nuxt/eslint-config.

No trustedDependencies

The repositories and the templates have no trustedDependencies field. The field replaces the default list of Bun. The packages that must run their install scripts, such as esbuild, are on that list.

CI runs lint, typecheck, test, and build. Run them before you open a pull request. Do not run typecheck or build while the dev server runs, because both write to .nuxt/.

Local layers

A layer reads an environment variable that points at a local checkout of an upstream layer. Use it to test a change before the upstream layer is released. When the variable is not set, the layer uses the published package.

VariableLayer
PLUTO_CORE_PATH@plutocms/core
PLUTO_SUPABASE_PATH@plutocms/supabase
PLUTO_SUPABASE_BLOG_PATH@plutocms/supabase-blog
PLUTO_SUPABASE_SHOP_PATH@plutocms/supabase-shop
PLUTO_DOCS_PATH@plutocms/docs
PLUTO_3D_PATH@plutocms/3d

For example, core is in a folder of the pluto repository: PLUTO_CORE_PATH=../pluto/packages/core.

The plutoLayer helper

The helper is for apps that you use to develop Pluto, such as the docs site. The templates and a normal project extend the layers by name (extends: ['@plutocms/core']), with no helper.

An app should not set these variables by hand. Use the plutoLayer helper of @plutocms/core in nuxt.config.ts:

nuxt.config.ts
import { plutoLayer } from '@plutocms/core/layers'

export default defineNuxtConfig({
  extends: [plutoLayer('@plutocms/supabase-shop')]
})

Without any setting, plutoLayer(name) returns name. The app uses the npm package.

For Pluto development, set PLUTO_LOCAL_ROOT in .env. It is the folder that holds your checkouts. The path is absolute, or relative to the folder where you start Nuxt.

.env
PLUTO_LOCAL_ROOT=..

With PLUTO_LOCAL_ROOT set, the helper does this:

  1. It looks for the checkout of each layer under the root. Core is pluto/packages/core, @plutocms/docs is pluto/packages/layers/docs, and @plutocms/3d is pluto/packages/layers/addons/3d. The Supabase layers are supabase/packages/supabase, supabase/packages/supabase-shop and supabase/packages/supabase-blog. A checkout counts when it has a nuxt.config.ts.
  2. It sets PLUTO_<NAME>_PATH for each folder that exists, so the layers below the one you extend use the local copies too. A variable that is already set is kept.
  3. It returns the local path of the layer that you asked for. When that folder is missing, it returns the package name.

Rules:

  • An explicit PLUTO_<NAME>_PATH always wins.
  • PLUTO_USE_LOCAL=false turns the local mode off, and the app uses the npm packages.
  • The second argument sets root: plutoLayer(name, { root: '../checkouts' }). An explicit root wins over PLUTO_LOCAL_ROOT.
  • A production host has no .env file, so it builds with the npm packages.

The helper has no Nuxt runtime import, so nuxt.config.ts can load it. It sets environment variables, and the nuxt.config.ts of each layer reads them.

The app imports @plutocms/core/layers, so @plutocms/core must be reachable from the app. With npm and Bun, the copy that a layer brings is hoisted, and the import works. With a strict package manager such as pnpm, add @plutocms/core to the dependencies of the app.

The docs site uses the helper. Its .env.example has PLUTO_LOCAL_ROOT=.. as a comment.

The layer chain

Each layer extends the layers below it. An app extends only the top layer that it uses.

Top layerAlso brings
@plutocms/coreNothing
@plutocms/docs@plutocms/core
@plutocms/supabase@plutocms/core
@plutocms/supabase-shop@plutocms/supabase
@plutocms/supabase-blog@plutocms/supabase

See Layers.

Shared rules

Feature documentation

Each package keeps a FEATURES.md in its package folder. It lists every implemented feature in one line each. Each feature has its full detail in a skill file at .claude/skills/<feature-slug>/SKILL.md. FEATURES.md links to each skill file.

When you add a feature:

  1. Add one line to FEATURES.md.
  2. Write the full detail in a new skill file.

Writing style

All documentation, code comments, and commit messages use ASD-STE100 Simplified Technical English. See Writing docs.

Git

  • Use a semantic commit message for every commit. The format is type(scope): summary. Example: feat(foo): bar baz.
  • Do not add a co-author line to a commit.
  • Do not name a client site in the title or the body of a pull request. Describe the problem in general terms.

Implementation plans

Every new implementation has a plan file. A session can end at any time, so the plan file keeps the context and the progress on disk.

  • Put the file at <project>/.claude/plan/<slug>.md, in the project where the work happens. Work that changes more than one project goes in the workspace .claude/plan/.
  • Write the file before you write code. A bug fix of one or two lines needs no plan file.
  • Mark each checklist item done when it is done.
  • Before you stop, write in the log where you stopped and what comes next.
  • When the work is done, set Status: done and keep the file.

A plan file has these parts: a status line, the context (goal, reason, decisions, key files, open questions), a checklist, and a log.

One Tailwind build

An app has exactly one Tailwind build. A layer never registers a css entry that imports tailwindcss or @nuxt/ui. A layer puts its tokens in app/assets/css/pluto-layer.css. See Styles.

Release order

The layers depend on each other. Release from the bottom up. See Releasing.