Workspace
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
| Repository | Package | Published |
|---|---|---|
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-pluto | create-pluto | Yes |
pluto-supabase-shop-template | None | No. A giget template. |
pluto-admin-template | None | No. A giget template (Dashboard only, both backends). |
pluto-blog-template | None | No. A giget template (the blog, both backends). |
plutocms | None | No. 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 installinstalls the dependencies. Then thepostinstallscript of the root runs thedev:preparescript (nuxt prepare) of each package. An app that installs a layer does not run it.bun run devruns the layer as an app.bun run lint,bun run typecheck,bun run test, andbun 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.
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.
| Variable | Layer |
|---|---|
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:
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.
PLUTO_LOCAL_ROOT=..
With PLUTO_LOCAL_ROOT set, the helper does this:
- It looks for the checkout of each layer under the root. Core is
pluto/packages/core,@plutocms/docsispluto/packages/layers/docs, and@plutocms/3displuto/packages/layers/addons/3d. The Supabase layers aresupabase/packages/supabase,supabase/packages/supabase-shopandsupabase/packages/supabase-blog. A checkout counts when it has anuxt.config.ts. - It sets
PLUTO_<NAME>_PATHfor 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. - 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>_PATHalways wins. PLUTO_USE_LOCAL=falseturns the local mode off, and the app uses the npm packages.- The second argument sets
root:plutoLayer(name, { root: '../checkouts' }). An explicitrootwins overPLUTO_LOCAL_ROOT. - A production host has no
.envfile, 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.
@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 layer | Also brings |
|---|---|
@plutocms/core | Nothing |
@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:
- Add one line to
FEATURES.md. - 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: doneand 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.