Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,10 @@
## 7.0.0b7 (unreleased)


- Nothing changed yet.
- Add legacy-package migration guidance to the bundled plonecli skill,
documenting how to adapt old mr.bob/buildout/setup.py packages so plonecli
subtemplates wire in correctly.
[MrTango]


## 7.0.0b6 (2026-05-22)
Expand Down
5 changes: 3 additions & 2 deletions plonecli/skills/plonecli/SKILL.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
---
name: plonecli
description: Scaffold and develop Plone packages with plonecli (copier-template based). Use for any plonecli command (create, add, setup, serve, test, debug, update, config) and whenever working on Plone add-on features: creating a backend add-on or Zope project; adding or creating a content type, behavior, view, viewlet, portlet, vocabulary, indexer, subscriber, control panel, form, theme, REST API service, or any other subtemplate; scaffolding an upgrade step / GenericSetup migration after changing profile XML (catalog.xml, types, workflows, registry, rolemap) so it reaches already-installed sites; running/testing/reconfiguring a generated project. Triggers on "plonecli ...", "create a Plone addon", "add/create a content type / behavior / view / viewlet / portlet / vocabulary / indexer / subscriber / control panel / REST API service", "add upgrade_step / upgrade step for the last change", "bump profile version / metadata.xml", "migrate already-installed Plone sites", "plone scaffold", "zope-setup".
description: Scaffold and develop Plone packages with plonecli (copier-template based). Use for any plonecli command (create, add, setup, serve, test, debug, update, config) and whenever working on Plone add-on features: creating a backend add-on or Zope project; adding a content type, behavior, view, viewlet, portlet, vocabulary, indexer, subscriber, control panel, form, theme, or any other subtemplate; scaffolding an upgrade step / GenericSetup migration after changing profile XML (catalog, types, workflows, registry, rolemap) so it reaches already-installed sites; adapting an old/legacy package (mr.bob/bobtemplates, buildout, setup.py) to the structure plonecli's templates need; running/testing/reconfiguring a generated project. Triggers on "plonecli ...", "create a Plone addon", "add a content type / behavior / restapi service", "add upgrade_step", "migrate already-installed Plone sites", "use plonecli on an old/legacy package", "convert setup.py / buildout addon to plonecli", "zope-setup".
---

# plonecli
Expand Down Expand Up @@ -34,7 +34,7 @@ On first run, plonecli clones the copier-templates to `~/.copier-templates/plone
## Decision flow

1. **New project?** → `create`. Pure backend add-on: `plonecli create backend_addon my.addon`. Add-on **with** a runnable instance in one step: `plonecli create addon my.addon` (composite = `backend_addon` + `zope-setup`). Zope project: `plonecli create zope-setup my-project`. `addon` is **not** an alias of `backend_addon` — they are different templates. Details and template list: [reference/create.md](reference/create.md).
2. **Add a feature to an existing addon?** → `cd` into the project, then `plonecli add content_type` / `behavior` / `restapi_service`. Field/wiring specifics: [reference/add.md](reference/add.md).
2. **Add a feature to an existing addon?** → `cd` into the project, then `plonecli add content_type` / `behavior` / `restapi_service`. Field/wiring specifics: [reference/add.md](reference/add.md). **Old/legacy package that doesn't fit the templates?** (no `[tool.plone.backend_addon.settings]`, `setup.py`/`bobtemplate.cfg`/buildout layout, `plonecli -l` lists nothing, or `add` lands files/registrations wrong) → don't hand-write subtemplate files, and don't re-run the `backend_addon` template (it overwrites `__init__.py` and real code). Inspect the structure and make the minimal edits the subtemplate hooks need. See the legacy rule below and [reference/migrate.md](reference/migrate.md).
3. **Changed GenericSetup profile XML (`profiles/default/*`) that must reach already-installed sites?** → `plonecli add upgrade_step` to scaffold the migration. See the upgrade-step rule below and [reference/add.md](reference/add.md).
4. **Need a runnable Plone instance around an addon?** → `plonecli setup` (inside the addon).
5. **Run / test it?** → `plonecli test` to test. For serving, follow the server rule below.
Expand All @@ -49,6 +49,7 @@ On first run, plonecli clones the copier-templates to `~/.copier-templates/plone
- **Tests must pass — never skip them.** After scaffolding or adding a feature, run `plonecli test` and report real results.
- **Profile XML changes need an upgrade step — scaffold it automatically.** Whenever you edit GenericSetup profile XML under `profiles/default/` (e.g. `catalog.xml`, `types/*.xml`, `types.xml`, `workflows.xml`, `registry.xml`, `rolemap.xml`) in a way that must propagate to already-installed sites, run `plonecli add upgrade_step --defaults -d upgrade_step_title="<what changed>"` as part of the same change — don't leave it to the user to remember. It bumps `profiles/default/metadata.xml` and registers a GS upgrade handler; then fill that handler so existing sites actually get the change (reapply the relevant import step or migrate data). Never hand-edit `metadata.xml`'s version to "do an upgrade" — that bumps the number without a registered step. Details and what does/doesn't need a step: [reference/add.md](reference/add.md).
- **Don't recreate to change settings.** Re-running `create` over an existing project is wrong; use the reconfigure flow ([reference/maintain.md](reference/maintain.md)).
- **Old/legacy package: adapt the structure minimally, never hand-roll old-style files.** If `plonecli add` won't wire features into an old package (mr.bob/`bobtemplates.plone`, buildout, `setup.py` — typically missing `[tool.plone.backend_addon.settings]` or a `src/<package_folder>/configure.zcml`), don't fall back to writing the subtemplate's files by hand, and don't re-run the `backend_addon` template over it (it overwrites `__init__.py` and other real code). Inspect what's there, then make only the minimal edits the subtemplate hooks need to function — chiefly the `[tool.plone.backend_addon.settings]` block (so plonecli detects the addon and can register subtemplates) and a stub `src/<package_folder>/configure.zcml` if absent (hooks append `<include>`s before `</configure>` and silently skip it when the file is missing). Preserve existing code; recommend but don't force broader modernization. Then run `plonecli add` normally. Details: [reference/migrate.md](reference/migrate.md).
- After `create`/`add`/reconfigure, generated files change — review `git status`/diff and preserve intentional local edits.

## Quick start
Expand Down
2 changes: 2 additions & 0 deletions plonecli/skills/plonecli/reference/add.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

Run from **inside** a plonecli-generated project. If you are not in one, the command fails with `NotInPackageError` — `cd` into the project root first.

If you *are* inside an addon but it's an **old/legacy package** (mr.bob/`bobtemplates.plone`, buildout, `setup.py`) where `add` lands files in the wrong place or doesn't register them, the structure doesn't yet fit the templates. Don't hand-write the files and don't re-run the `backend_addon` template — make the minimal structural edits first. See [migrate.md](migrate.md).

```shell
cd collective.todo
plonecli add content_type
Expand Down
122 changes: 122 additions & 0 deletions plonecli/skills/plonecli/reference/migrate.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
# Adapting an old/legacy package to fit plonecli

Older Plone packages (mr.bob / `bobtemplates.plone`, buildout, `setup.py`) often
have a structure that plonecli's copier subtemplates can't fully wire into. When
`plonecli add ...` would land files in the wrong place or fail to register them,
**do not hand-write the subtemplate output in the old style, and do not re-run
the `backend_addon` template over the package** — that template overwrites files
like `src/<package>/__init__.py` (it is *not* in `_skip_if_exists`) and would
destroy real code there.

Instead: **inspect the existing structure, compare it against what recent
plonecli + copier-templates actually need to function, and make only the minimal,
needed changes by hand** — preserving all existing code. Real package layouts
vary too much for a one-shot re-scaffold; a targeted edit is safer and an agent
can do it better.

## How the subtemplates wire in (so you know what to provide)

`plonecli add <subtemplate>` runs `copier copy` into the project root, then a
post-copy hook edits a few existing files. Those hooks (in
`shared/hooks/addon_context.py`, `shared/utils/xml_updater.py`,
`shared/utils/pyproject_updater.py`):

- **Detect the parent addon** from `pyproject.toml`'s
`[tool.plone.backend_addon.settings]` (preferred) or fall back to
`bobtemplate.cfg` / `setup.py` (package name inferred). On the legacy fallback
they still run but print "Consider running the backend_addon template to
modernize" and registration is less reliable.
- **Register the new feature** into
`[tool.plone.backend_addon.settings.subtemplates]` (`content_types`,
`behaviors`, `services`, …) — this needs a `pyproject.toml` to write to.
- **Extend ZCML by appending before the closing `</configure>` tag**, and add
`<include package=".behaviors" />`-style lines to the package's
`configure.zcml` — **but only if that file already exists** (`if
parent_zcml.exists()`); otherwise the include is *silently skipped* and the
feature never loads. There are **no special comment markers** — the anchor is
the `</configure>` / `</object>` closing tag, and missing leaf files
(`behaviors/configure.zcml`, `types.xml`) are auto-created. xmlns prefixes are
auto-added as needed; edits are idempotent.

## What recent plonecli/copier-templates need to function

Check the package against this list and fix only what's missing:

1. **`pyproject.toml` with `[tool.plone.backend_addon.settings]`** — at minimum
`package_name` and `package_folder` (folder = package name with `.`→`/`, e.g.
`collective.todo` → `collective/todo`). Also add the empty subtemplates table
so registration has somewhere to write:

```toml
[tool.plone.backend_addon.settings]
package_name = "collective.todo"
package_folder = "collective/todo"

[tool.plone.backend_addon.settings.subtemplates]
content_types = []
behaviors = []
services = []
```

This single block is what makes `plonecli` detect the project as a
`backend_addon` (see `project.py`) and what subtemplate hooks read/write. If
the package has only `setup.py`/`bobtemplate.cfg` and no `pyproject.toml`,
adding this block is usually the highest-value minimal change. Leave the rest
of an existing working `pyproject.toml` (deps, build-system) alone unless it's
actually broken.

2. **`src/<package_folder>/` source layout.** If the package uses a flat or
different layout, the subtemplates still target `src/<package_folder>/…`.
Confirm this path exists and matches `package_folder`.

3. **`src/<package_folder>/configure.zcml` must exist** and contain a
`<configure …> … </configure>` block. Without it, every `<include package=…/>`
the hooks try to add is dropped silently. If it's missing, create a minimal
one (don't overwrite an existing one):

```xml
<configure
xmlns="http://namespaces.zope.org/zope"
xmlns:genericsetup="http://namespaces.zope.org/genericsetup"
xmlns:plone="http://namespaces.plone.org/plone"
i18n_domain="collective.todo">

</configure>
```

The hooks add missing xmlns prefixes themselves, so a basic root is enough.

4. **`profiles/default/metadata.xml` with a `<version>`** — required for
`plonecli add upgrade_step` (the hook reads and bumps `<version>`). `types.xml`
is auto-created for content types if absent.

5. **Preserve real code.** If `src/<package>/__init__.py` contains a message
factory, namespace declaration, or imports, keep them. If `configure.zcml` /
`setuphandlers.py` already exist, keep them — only extend. This is exactly why
we migrate by hand rather than re-running the template.

## Workflow

1. **Start from a clean git state** so every change is a reviewable diff.
2. **Inspect:** list `src/`, read `pyproject.toml` (or `setup.py`/`setup.cfg`/
`bobtemplate.cfg`), and check for the four items above. Identify the gap.
3. **Apply the minimal fix(es)** by hand — typically just the
`[tool.plone.backend_addon.settings]` block, and a stub `configure.zcml` only
if absent. Do not touch working config you don't need to.
4. **Verify detection:** `plonecli -l` inside the package should now list the
`backend_addon` subtemplates.
5. **Add the feature the normal way** ([add.md](add.md)), e.g.
`plonecli add behavior --defaults -d behavior_name="IFeatured"`, then review
the diff to confirm files landed under `src/<package_folder>/…` and the
include/registration were added.
6. **Run `plonecli test`** and report real results (add the zope-setup layer via
`plonecli setup` first if there's no `tasks.py`; see [maintain.md](maintain.md)).
Never skip tests.

## What to recommend but not force

You may *recommend* modernizing outdated config (buildout/tox → uv, `setup.py` →
hatchling `pyproject.toml`, `*.rst` → `*.md`) and explain the benefit, but if it
still works with current plonecli, **don't change it on your own** — leave it and
let the user decide. The migration's goal is to make `plonecli add` work with the
least disruption, not to rewrite the package.
Loading