Contributing#

Everything in this page is run from a checkout. If you only want to use the package, How to install the backend is the page you want instead.

If you are a coding agent, read AGENTS.md in the repository root. It covers the same ground more tersely, and adds the traps that are only discoverable by hitting them.

The repository holds both halves#

Directory

Holds

backend/

The Plone add-on pas.plugins.identity, its test suite, and the demo stack's own package.

frontend/

The Volto add-on @plone-collective/volto-identity, and its Storybook stories.

docs/

These pages, and the screenshot harness that illustrates them.

They are one repository on purpose. A change to a REST API payload and the change to the component that reads it are one commit, and a reference page cannot drift from the source it documents because both are in the same checkout.

The two packages are released separately, under different licences: pas.plugins.identity is GPL-2.0-only on PyPI, @plone-collective/volto-identity is MIT on npm.

Set up a checkout#

What you need#

Install#

git clone git@github.com:collective/pas-plugins-identity.git
cd pas-plugins-identity
make install

That installs both halves. make backend-install and make frontend-install do one each.

Run it#

make backend-create-site   # first run only
make backend-start         # http://localhost:8080/

Then, in a second shell:

make frontend-start        # http://localhost:3000/

To see the whole package working at once instead, run the two-site federation in Docker: Federate two Plone sites.

What a change has to satisfy#

Run these before opening a pull request. CI runs the same ones.

Command

Checks

make check

make format then make lint, across both halves

make test

make backend-test and make frontend-test

make check-imports, from backend/

That core never imports the [server] layer

make docs-build

The documentation, with warnings as errors

make -C docs vale

Prose style. Errors must be zero; warnings are advisory

Formatting and linting#

Half

Tool

Does

Configured in

backend

Ruff

Formats Python, sorts imports

backend/pyproject.toml

backend

zpretty

Formats XML and ZCML

backend

Pyroma

Checks package metadata

backend

check-python-versions

Checks the declared Python versions

frontend

ESLint

Lints JavaScript and TypeScript

frontend/.eslintrc.js

frontend

prettier

Formats JavaScript and TypeScript

frontend/.prettierrc

frontend

Stylelint

Formats CSS, Less and Sass

frontend/.stylelintrc

Each runs from backend/ or frontend/ on its own as well.

Important

Run make format before staging. A formatter that rewrites a file after you have staged it leaves the staged and working copies disagreeing, and the commit carries whichever half you were not looking at.

The layer boundary is a contract#

make check-imports runs import-linter contracts asserting that core never imports the optional server layer. It is not part of make lint, so run it yourself when you touch either layer.

A soft import counts: import-linter reads function bodies, so moving an import inside a function does not get past it. That is deliberate—see About the two layers.

Conventions#

Changelog#

Every change carries a towncrier news fragment. There are three scopes, and a change adds one to each it touches:

Scope

Folder

Repository and documentation

news/

Backend

backend/news/

Frontend

frontend/packages/volto-identity/news/

Name it <issue>.<type> when an issue exists, +<slug>.<type> when none does. The types are breaking, feature, bugfix, documentation, internal and tests. Write it in the past tense, for somebody reading the changelog rather than the diff, and end with your GitHub handle.

Tests#

A bugfix carries a test that fails without it. Verify that by removing the fix and watching the test go red—a regression test nobody has seen fail is a regression test that may be asserting nothing.

The backend suite uses pytest-plone. Tests needing Docker are marked docker and skipped without it.

Documentation#

docs/STYLE.md is the house style, and it is short. The parts that catch people out:

  • The code is the source of truth. Cite the file a fact came from in an HTML comment under the heading it supports.

  • No "should" language. Either it does, or you have not run it—and then say that.

  • Reference pages are tables. Rationale belongs in concepts/.

  • Diagrams are Mermaid, never images. Screenshots are captured by the harness in docs/screenshots/, never by hand.

  • Every page ends with Related or Next steps.

Translations#

make i18n

Generates the translation files for both halves.

Releasing#

Both packages are released by one command, from a clean checkout of main with every gate above green.

uvx repoplone release a

The argument is the next version, either spelled out or as a segment—a, b, rc, major, minor, patch, or release for the final one. uvx repoplone versions next prints what each of them resolves to for all three components, and --dry-run walks the pipeline without publishing.

repoplone reads repository.toml and runs eight steps: it shows the changelog it is about to write, settles the version, updates version.txt and the root changelog, releases the backend, releases the frontend, commits, tags, and opens a GitHub release. It asks before it starts.

Half

What the release step does

Backend

Compiles the .po files, writes the version, builds the changelog with towncrier, then uv build and uv publish.

Frontend

Drives release-it, which builds the changelog, copies frontend/README.md in as the package README, and publishes with pnpm publish.

Two things follow from that, and both bite if forgotten.

  • You have to be authenticated to PyPI and to npm before you start. There is no step that stops halfway and asks.

  • The frontend must be published by pnpm, never npm. Its peer dependencies are written as workspace:* and catalog:, which only pnpm rewrites into real version ranges. An npm publish uploads those literally, and the result cannot be installed.

towncrier removes each fragment it has consumed, so the news folders come back empty and the changelogs carry the entries.

Reporting a security vulnerability#

Not through the issue tracker. Follow SECURITY.md.