plonecli 7.0.0b2__tar.gz → 7.0.0b4__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- plonecli-7.0.0b4/.agents/skills/plonecli/SKILL.md +74 -0
- plonecli-7.0.0b4/.agents/skills/plonecli/reference/add.md +83 -0
- plonecli-7.0.0b4/.agents/skills/plonecli/reference/create.md +46 -0
- plonecli-7.0.0b4/.agents/skills/plonecli/reference/maintain.md +67 -0
- plonecli-7.0.0b4/.copier-answers.yml +12 -0
- {plonecli-7.0.0b2 → plonecli-7.0.0b4}/.devcontainer/Dockerfile +24 -5
- {plonecli-7.0.0b2 → plonecli-7.0.0b4}/.devcontainer/devcontainer.json +9 -2
- {plonecli-7.0.0b2 → plonecli-7.0.0b4}/.devcontainer/init-firewall.sh +92 -14
- plonecli-7.0.0b4/.devcontainer/setup-claude.sh +162 -0
- {plonecli-7.0.0b2 → plonecli-7.0.0b4}/CHANGES.md +41 -0
- plonecli-7.0.0b4/CLAUDE.md +6 -0
- {plonecli-7.0.0b2 → plonecli-7.0.0b4}/PKG-INFO +60 -1
- {plonecli-7.0.0b2 → plonecli-7.0.0b4}/README.md +59 -0
- {plonecli-7.0.0b2 → plonecli-7.0.0b4}/devcontainer.sh +3 -3
- plonecli-7.0.0b4/docs/demo-backend.tape +398 -0
- plonecli-7.0.0b4/docs/demo-backend.webm +0 -0
- plonecli-7.0.0b4/docs/demo-classicui.tape +330 -0
- plonecli-7.0.0b4/docs/demo-recording-prompt.md +232 -0
- plonecli-7.0.0b4/docs/demo-restapi.tape +212 -0
- {plonecli-7.0.0b2 → plonecli-7.0.0b4}/plonecli/cli.py +92 -2
- plonecli-7.0.0b4/plonecli/skill_installer.py +122 -0
- {plonecli-7.0.0b2 → plonecli-7.0.0b4}/plonecli/templates.py +34 -9
- {plonecli-7.0.0b2 → plonecli-7.0.0b4}/pyproject.toml +1 -1
- {plonecli-7.0.0b2 → plonecli-7.0.0b4}/tests/test_plonecli.py +20 -0
- plonecli-7.0.0b4/tests/test_skill.py +90 -0
- {plonecli-7.0.0b2 → plonecli-7.0.0b4}/tests/test_templates.py +34 -7
- {plonecli-7.0.0b2 → plonecli-7.0.0b4}/tests/test_theme_barceloneta_integration.py +3 -2
- {plonecli-7.0.0b2 → plonecli-7.0.0b4}/uv.lock +10 -10
- plonecli-7.0.0b2/.devcontainer/setup-claude.sh +0 -33
- plonecli-7.0.0b2/CLAUDE.md +0 -2
- {plonecli-7.0.0b2 → plonecli-7.0.0b4}/.claude/statusline-command.sh +0 -0
- {plonecli-7.0.0b2 → plonecli-7.0.0b4}/.editorconfig +0 -0
- {plonecli-7.0.0b2 → plonecli-7.0.0b4}/.github/ISSUE_TEMPLATE.md +0 -0
- {plonecli-7.0.0b2 → plonecli-7.0.0b4}/.github/workflows/python-package.yml +0 -0
- {plonecli-7.0.0b2 → plonecli-7.0.0b4}/.gitignore +0 -0
- {plonecli-7.0.0b2 → plonecli-7.0.0b4}/.readthedocs.yaml +0 -0
- {plonecli-7.0.0b2 → plonecli-7.0.0b4}/AUTHORS.md +0 -0
- {plonecli-7.0.0b2 → plonecli-7.0.0b4}/CONTRIBUTING.md +0 -0
- {plonecli-7.0.0b2 → plonecli-7.0.0b4}/LICENSE +0 -0
- {plonecli-7.0.0b2 → plonecli-7.0.0b4}/MANIFEST.in +0 -0
- {plonecli-7.0.0b2 → plonecli-7.0.0b4}/ROADMAP.md +0 -0
- {plonecli-7.0.0b2 → plonecli-7.0.0b4}/docs/.gitignore +0 -0
- {plonecli-7.0.0b2 → plonecli-7.0.0b4}/docs/Makefile +0 -0
- {plonecli-7.0.0b2 → plonecli-7.0.0b4}/docs/authors.md +0 -0
- {plonecli-7.0.0b2 → plonecli-7.0.0b4}/docs/conf.py +0 -0
- {plonecli-7.0.0b2 → plonecli-7.0.0b4}/docs/contributing.md +0 -0
- {plonecli-7.0.0b2 → plonecli-7.0.0b4}/docs/history.md +0 -0
- {plonecli-7.0.0b2 → plonecli-7.0.0b4}/docs/index.md +0 -0
- {plonecli-7.0.0b2 → plonecli-7.0.0b4}/docs/installation.md +0 -0
- {plonecli-7.0.0b2 → plonecli-7.0.0b4}/docs/make.bat +0 -0
- {plonecli-7.0.0b2 → plonecli-7.0.0b4}/docs/plone_cli_logo.png +0 -0
- {plonecli-7.0.0b2 → plonecli-7.0.0b4}/docs/plone_cli_logo.svg +0 -0
- {plonecli-7.0.0b2 → plonecli-7.0.0b4}/docs/plonecli_add_content_type_optimized.gif +0 -0
- {plonecli-7.0.0b2 → plonecli-7.0.0b4}/docs/plonecli_add_theme_optimized.gif +0 -0
- {plonecli-7.0.0b2 → plonecli-7.0.0b4}/docs/plonecli_add_vocabulary_optimized.gif +0 -0
- {plonecli-7.0.0b2 → plonecli-7.0.0b4}/docs/plonecli_build_optimized.gif +0 -0
- {plonecli-7.0.0b2 → plonecli-7.0.0b4}/docs/plonecli_create_addon_optimized.gif +0 -0
- {plonecli-7.0.0b2 → plonecli-7.0.0b4}/docs/plonecli_serve_optimized.gif +0 -0
- {plonecli-7.0.0b2 → plonecli-7.0.0b4}/docs/readme.md +0 -0
- {plonecli-7.0.0b2 → plonecli-7.0.0b4}/jsconfig.json +0 -0
- {plonecli-7.0.0b2 → plonecli-7.0.0b4}/plonecli/__init__.py +0 -0
- {plonecli-7.0.0b2 → plonecli-7.0.0b4}/plonecli/config.py +0 -0
- {plonecli-7.0.0b2 → plonecli-7.0.0b4}/plonecli/exceptions.py +0 -0
- {plonecli-7.0.0b2 → plonecli-7.0.0b4}/plonecli/plone_versions.py +0 -0
- {plonecli-7.0.0b2 → plonecli-7.0.0b4}/plonecli/project.py +0 -0
- {plonecli-7.0.0b2 → plonecli-7.0.0b4}/plonecli/registry.py +0 -0
- {plonecli-7.0.0b2 → plonecli-7.0.0b4}/plonecli/updater.py +0 -0
- {plonecli-7.0.0b2 → plonecli-7.0.0b4}/plonecli.md +0 -0
- {plonecli-7.0.0b2 → plonecli-7.0.0b4}/requirements.txt +0 -0
- {plonecli-7.0.0b2 → plonecli-7.0.0b4}/tests/__init__.py +0 -0
- {plonecli-7.0.0b2 → plonecli-7.0.0b4}/tests/conftest.py +0 -0
- {plonecli-7.0.0b2 → plonecli-7.0.0b4}/tests/test_config.py +0 -0
- {plonecli-7.0.0b2 → plonecli-7.0.0b4}/tests/test_project.py +0 -0
- {plonecli-7.0.0b2 → plonecli-7.0.0b4}/tests/test_registry.py +0 -0
- {plonecli-7.0.0b2 → plonecli-7.0.0b4}/tests/test_updater.py +0 -0
- {plonecli-7.0.0b2 → plonecli-7.0.0b4}/tox.ini +0 -0
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: plonecli
|
|
3
|
+
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".
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# plonecli
|
|
7
|
+
|
|
8
|
+
`plonecli` scaffolds and develops Plone packages using [copier](https://copier.readthedocs.io/) templates. It creates backend add-ons and Zope project setups, adds features (content types, behaviors, REST API services) via subtemplates, and wraps the project's `invoke` tasks for serving and testing.
|
|
9
|
+
|
|
10
|
+
## How to invoke it
|
|
11
|
+
|
|
12
|
+
- **End users:** `uvx plonecli <command>` (no install needed).
|
|
13
|
+
- **Inside this repo (plonecli's own source):** it is not on `PATH` — use `uv run plonecli <command>`.
|
|
14
|
+
|
|
15
|
+
On first run, plonecli clones the copier-templates to `~/.copier-templates/plone-copier-templates`. If a command complains about missing templates, run `plonecli update` first.
|
|
16
|
+
|
|
17
|
+
## Command map
|
|
18
|
+
|
|
19
|
+
| Command | Scope | What it does |
|
|
20
|
+
|---|---|---|
|
|
21
|
+
| `create <template> <name>` | anywhere | Scaffold a new project (`backend_addon`, `addon` = backend_addon+zope-setup, or `zope-setup`). See [reference/create.md](reference/create.md). |
|
|
22
|
+
| `add <subtemplate>` | inside a project | Add a feature. Gated by project type. See [reference/add.md](reference/add.md). |
|
|
23
|
+
| `setup` | inside a `backend_addon` | Apply `zope-setup` in place (run a Plone instance around the addon). |
|
|
24
|
+
| `serve` | inside a project | `uv run invoke start` → http://localhost:8080. **See server rule below.** |
|
|
25
|
+
| `test [-v]` | inside a project | `uv run invoke test`. |
|
|
26
|
+
| `debug` | inside a project | `uv run invoke debug`. |
|
|
27
|
+
| `update` | anywhere | Pull latest copier-templates + check PyPI for plonecli updates. |
|
|
28
|
+
| `config` | anywhere | Interactive global settings → `~/.plonecli/config.toml`. |
|
|
29
|
+
|
|
30
|
+
`add`, `setup`, `serve`, `test`, `debug` require being inside a plonecli-generated project (detected from `pyproject.toml`); otherwise they fail with `NotInPackageError`. Subtemplates are filtered by the project's type, so `plonecli -l` shows different options depending on where you are.
|
|
31
|
+
|
|
32
|
+
`serve`, `test`, `debug` additionally need the `invoke` harness (`tasks.py`), which only the **`zope-setup`** layer provides. A project made with `create backend_addon` alone has no `tasks.py` — run `plonecli setup` first (or scaffold with the `addon` composite / `zope-setup`) before these commands work.
|
|
33
|
+
|
|
34
|
+
## Decision flow
|
|
35
|
+
|
|
36
|
+
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).
|
|
37
|
+
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).
|
|
38
|
+
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).
|
|
39
|
+
4. **Need a runnable Plone instance around an addon?** → `plonecli setup` (inside the addon).
|
|
40
|
+
5. **Run / test it?** → `plonecli test` to test. For serving, follow the server rule below.
|
|
41
|
+
6. **Change settings of an already-generated project?** → don't recreate it; use reconfigure. See [reference/maintain.md](reference/maintain.md).
|
|
42
|
+
7. **Templates outdated, or want a different template repo/branch?** → `plonecli update`, or env overrides in [reference/maintain.md](reference/maintain.md).
|
|
43
|
+
|
|
44
|
+
## Critical rules
|
|
45
|
+
|
|
46
|
+
- **Never start the dev server yourself.** Do not run `plonecli serve` / `plonecli debug` / `invoke start`. Assume the instance is already running; if it is not, ask the user to start it. (`plonecli test` is fine to run.)
|
|
47
|
+
- **Use native `uv`.** Run things as `uv run <command>`; never `uv pip` or `pip` unless explicitly told.
|
|
48
|
+
- **Tests must pass — never skip them.** After scaffolding or adding a feature, run `plonecli test` and report real results.
|
|
49
|
+
- **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` 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).
|
|
50
|
+
- **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)).
|
|
51
|
+
- After `create`/`add`/reconfigure, generated files change — review `git status`/diff and preserve intentional local edits.
|
|
52
|
+
|
|
53
|
+
## Quick start
|
|
54
|
+
|
|
55
|
+
```shell
|
|
56
|
+
# new backend add-on (package only — no runnable instance yet)
|
|
57
|
+
plonecli create backend_addon collective.todo
|
|
58
|
+
cd collective.todo
|
|
59
|
+
|
|
60
|
+
# add features
|
|
61
|
+
plonecli add content_type
|
|
62
|
+
plonecli add behavior
|
|
63
|
+
plonecli add restapi_service
|
|
64
|
+
|
|
65
|
+
# wrap it in a runnable Plone instance (adds the zope-setup / invoke harness)
|
|
66
|
+
plonecli setup
|
|
67
|
+
|
|
68
|
+
# verify (needs the zope-setup layer added above)
|
|
69
|
+
plonecli test
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
Shortcut: `plonecli create addon collective.todo` scaffolds `backend_addon` **and** `zope-setup` in one step — use it instead of `create backend_addon` + `setup`. Do not run both; that applies zope-setup twice.
|
|
73
|
+
|
|
74
|
+
For anything beyond this happy path, read the matching file in `reference/`.
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
# Adding features — `plonecli add <subtemplate>`
|
|
2
|
+
|
|
3
|
+
Run from **inside** a plonecli-generated project. If you are not in one, the command fails with `NotInPackageError` — `cd` into the project root first.
|
|
4
|
+
|
|
5
|
+
```shell
|
|
6
|
+
cd collective.todo
|
|
7
|
+
plonecli add content_type
|
|
8
|
+
plonecli add behavior
|
|
9
|
+
plonecli add restapi_service
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
## Subtemplates are gated by project type
|
|
13
|
+
|
|
14
|
+
plonecli detects the project type from `pyproject.toml` (e.g. `backend_addon`, `project`). Only subtemplates whose `parent` matches that type are offered. So:
|
|
15
|
+
|
|
16
|
+
- Inside a **`backend_addon`**: `content_type`, `behavior`, `restapi_service`, `upgrade_step`, and more (`indexer`, `subscriber`, `vocabulary`, `view`, `viewlet`, `portlet`, `controlpanel`, `form`, `theme*`, `site_initialization`, …).
|
|
17
|
+
- Inside a **`zope-setup`** project: `zope_instance`.
|
|
18
|
+
|
|
19
|
+
Always confirm what is actually available here with `plonecli -l` (run inside the project) — it lists only the applicable subtemplates and reflects the configured template repo/branch.
|
|
20
|
+
|
|
21
|
+
## The three common backend_addon subtemplates
|
|
22
|
+
|
|
23
|
+
| Subtemplate | Adds | Typical follow-up |
|
|
24
|
+
|---|---|---|
|
|
25
|
+
| `content_type` | A Dexterity content type (schema, FTI, registration). | Restart/reinstall the addon so the new type is registered; add tests for the type. |
|
|
26
|
+
| `behavior` | A behavior (reusable schema/marker applied to content types). | Wire the behavior onto a content type; add tests. |
|
|
27
|
+
| `restapi_service` | A `plone.restapi` service (endpoint, adapter, registration). | Add tests exercising the endpoint. |
|
|
28
|
+
|
|
29
|
+
copier will prompt interactively for the specifics (names, fields, options) — answer per the user's requirements. Do not invent answers; if the user hasn't specified e.g. field names, ask.
|
|
30
|
+
|
|
31
|
+
## After adding
|
|
32
|
+
|
|
33
|
+
1. Review `git status`/diff — `add` writes new files and may touch existing ones (e.g. `configure.zcml`, `profiles`). Preserve intentional local edits.
|
|
34
|
+
2. Run `plonecli test` and report real results. Never skip tests.
|
|
35
|
+
3. If a running instance is needed to see the change, ask the user to (re)start it — do not start the server yourself.
|
|
36
|
+
|
|
37
|
+
## upgrade_step — required after profile XML changes
|
|
38
|
+
|
|
39
|
+
When you change GenericSetup profile XML under `profiles/default/` in a way that must reach **already-installed** sites, add an upgrade step in the same change. Reinstalling the profile is not an option on real sites, so the migration has to be an upgrade step.
|
|
40
|
+
|
|
41
|
+
```shell
|
|
42
|
+
cd collective.todo
|
|
43
|
+
plonecli add upgrade_step
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
It prompts for (defaults injected from the addon):
|
|
47
|
+
|
|
48
|
+
| Question | Default | Meaning |
|
|
49
|
+
|---|---|---|
|
|
50
|
+
| `upgrade_step_title` | (required) | Human-readable title, e.g. "Add catalog index". |
|
|
51
|
+
| `upgrade_step_description` | "A custom upgrade step" | What the step does. |
|
|
52
|
+
| `source_version` | current `metadata.xml` version | Version being upgraded **from**. |
|
|
53
|
+
| `destination_version` | `source + 1` | Version being upgraded **to**. |
|
|
54
|
+
|
|
55
|
+
What it does (so you don't do it by hand):
|
|
56
|
+
|
|
57
|
+
- Bumps `profiles/default/metadata.xml` to `destination_version`.
|
|
58
|
+
- Creates `src/<package>/upgrades/` with a handler stub + ZCML registration, and includes `.upgrades` from `configure.zcml`.
|
|
59
|
+
- Registers the step in `pyproject.toml` addon settings.
|
|
60
|
+
|
|
61
|
+
After scaffolding, **fill the generated handler** so existing sites actually get the change — bumping the version alone does nothing. Typically the handler reapplies the relevant GenericSetup import step (e.g. reimport `catalog`, `typeinfo`, `workflow`, `plone.app.registry`) and/or migrates existing data, then add a test under `tests/test_upgrade_<destination_version>.py`.
|
|
62
|
+
|
|
63
|
+
### Which profile changes need an upgrade step
|
|
64
|
+
|
|
65
|
+
Need one (change must propagate to live sites):
|
|
66
|
+
|
|
67
|
+
- `catalog.xml` — new/changed indexes or metadata columns (add index + reindex).
|
|
68
|
+
- `types/*.xml`, `types.xml` — FTI changes, new content types, behaviors added to a type.
|
|
69
|
+
- `workflows.xml`, `workflows/*.xml` — workflow definition or state changes.
|
|
70
|
+
- `registry.xml` — new/changed `plone.registry` records.
|
|
71
|
+
- `rolemap.xml` — new roles or permission mappings.
|
|
72
|
+
|
|
73
|
+
Usually don't:
|
|
74
|
+
|
|
75
|
+
- Brand-new addon whose profile has never been installed anywhere (initial install covers it).
|
|
76
|
+
- Changes that only affect fresh installs and have no existing-site impact.
|
|
77
|
+
- `metadata.xml` itself — that's the version marker the upgrade step bumps, not a thing you migrate.
|
|
78
|
+
|
|
79
|
+
If unsure whether a given profile edit needs migrating to existing sites, add the upgrade step — it's cheap and safe; a missing one silently leaves installed sites stale.
|
|
80
|
+
|
|
81
|
+
## zope_instance
|
|
82
|
+
|
|
83
|
+
Inside a `zope-setup` project, `plonecli add zope_instance` adds an additional named Zope instance. Each instance has its own `.copier-answers.zope-instance-<name>.yml` and can later be reconfigured by name ([maintain.md](maintain.md)).
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
# Creating projects — `plonecli create <template> <name>`
|
|
2
|
+
|
|
3
|
+
```shell
|
|
4
|
+
plonecli create backend_addon collective.todo # backend add-on package only
|
|
5
|
+
plonecli create addon collective.todo # backend add-on + zope-setup (composite)
|
|
6
|
+
plonecli create zope-setup my-project # Zope project setup
|
|
7
|
+
```
|
|
8
|
+
|
|
9
|
+
## Project templates
|
|
10
|
+
|
|
11
|
+
Discover them live with `plonecli -l` (this is authoritative — the registry scans `copier.yml` files in the templates clone, so available templates depend on the configured template repo/branch).
|
|
12
|
+
|
|
13
|
+
| Template | Alias | Purpose |
|
|
14
|
+
|---|---|---|
|
|
15
|
+
| `backend_addon` | — | A Plone backend add-on package (the thing you develop). No alias. |
|
|
16
|
+
| `addon` | `add-on` | **Composite** — applies `backend_addon` then `zope-setup` in one go. |
|
|
17
|
+
| `zope-setup` | `project` | A Zope/Plone project setup that can run an instance. |
|
|
18
|
+
|
|
19
|
+
`create` accepts either a template name or an alias (e.g. `add-on` → `addon`, `project` → `zope-setup`). Note `addon` and `backend_addon` are **different** templates: `addon` is the composite that also lays down the runnable zope-setup layer, while `backend_addon` is just the add-on package.
|
|
20
|
+
|
|
21
|
+
## Composite templates
|
|
22
|
+
|
|
23
|
+
A template may define **composite steps** — `create` then applies several sub-templates in sequence, echoing each step. `addon` is exactly this: it walks `backend_addon` then `zope-setup`. You do not need to do anything special; just run `create` once and let it walk the steps. Because `addon` already includes `zope-setup`, do **not** also run `plonecli setup` afterward — that would apply zope-setup twice.
|
|
24
|
+
|
|
25
|
+
## Naming
|
|
26
|
+
|
|
27
|
+
- Backend add-on names use dotted package notation: `collective.todo`, `my.addon`. This becomes the Python package and the project directory.
|
|
28
|
+
- Zope-setup names are plain project directory names: `my-project`.
|
|
29
|
+
|
|
30
|
+
## What gets generated
|
|
31
|
+
|
|
32
|
+
A scaffolded project includes `pyproject.toml` (plonecli detects the project type from this) and a `.copier-answers*.yml` recording the answers — used later by reconfigure. Do not hand-edit the answers files; change settings via reconfigure ([maintain.md](maintain.md)).
|
|
33
|
+
|
|
34
|
+
The `tasks.py` for `invoke` (which drives `serve`/`test`/`debug`/`reconfigure`) is provided by the **`zope-setup`** layer — not by a bare `backend_addon`. So a project made with `create backend_addon` alone has no `tasks.py`, and `plonecli test`/`serve`/`debug` won't work in it until you add zope-setup via `plonecli setup`. Projects made with `create addon` (composite) or `create zope-setup` already include `tasks.py`.
|
|
35
|
+
|
|
36
|
+
## After creating
|
|
37
|
+
|
|
38
|
+
1. `cd` into the new project directory.
|
|
39
|
+
2. Add features with `plonecli add ...` ([add.md](add.md)).
|
|
40
|
+
3. Run `plonecli test` and report results — do not skip tests.
|
|
41
|
+
4. Review `git status` to see everything that was generated.
|
|
42
|
+
5. Do **not** auto-start the server; if a running instance is needed, ask the user.
|
|
43
|
+
|
|
44
|
+
## Adding a Zope instance to an existing addon
|
|
45
|
+
|
|
46
|
+
If you already have a `backend_addon` and need a runnable Plone instance around it, do **not** create a separate project — run `plonecli setup` inside the addon. It applies `zope-setup` in place. `setup` only works inside a `backend_addon`; elsewhere it errors.
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
# Running, testing, reconfiguring, updating
|
|
2
|
+
|
|
3
|
+
## Running & testing
|
|
4
|
+
|
|
5
|
+
These wrap the project's `invoke` tasks and must run inside the project:
|
|
6
|
+
|
|
7
|
+
| Command | Delegates to | Notes |
|
|
8
|
+
|---|---|---|
|
|
9
|
+
| `plonecli serve` | `uv run invoke start` | Serves at http://localhost:8080. **Do not run this yourself** — assume the instance is already running; if not, ask the user to start it. |
|
|
10
|
+
| `plonecli debug` | `uv run invoke debug` | Debug instance. **Do not run this yourself** either. |
|
|
11
|
+
| `plonecli test` | `uv run invoke test` | Safe to run. `-v`/`--verbose` for verbose output. Run after scaffolding/adding features; report real results; never skip. |
|
|
12
|
+
|
|
13
|
+
You may also call the underlying tasks directly with `uv run invoke <task>` if `plonecli` itself is unavailable.
|
|
14
|
+
|
|
15
|
+
## Reconfiguring an existing project
|
|
16
|
+
|
|
17
|
+
To change settings of an already-generated project, **do not re-run `create`** — re-run the template's questions via the `reconfigure` invoke task (wraps `copier recopy --trust --overwrite` against the right answers file):
|
|
18
|
+
|
|
19
|
+
```shell
|
|
20
|
+
uv run invoke reconfigure --target=addon # backend addon package metadata
|
|
21
|
+
uv run invoke reconfigure --target=zope-setup # project-level Plone/Zope settings
|
|
22
|
+
uv run invoke reconfigure --target=instance # a Zope instance (port, DB, creds)
|
|
23
|
+
uv run invoke reconfigure --target=instance --name=instance2 # a specific named instance
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
| Target | Reconfigures | Answers file |
|
|
27
|
+
|---|---|---|
|
|
28
|
+
| `addon` | Backend addon package settings | `.copier-answers.yml` |
|
|
29
|
+
| `zope-setup` | Project-level Plone/Zope settings | `.copier-answers.zope-setup.yml` |
|
|
30
|
+
| `instance` | Zope instance (port, DB, credentials) | `.copier-answers.zope-instance-<name>.yml` |
|
|
31
|
+
|
|
32
|
+
Reconfigure **overwrites** generated config files with the new answers. Afterwards, review `git status`/diff and preserve any local edits worth keeping.
|
|
33
|
+
|
|
34
|
+
## Updating templates & plonecli
|
|
35
|
+
|
|
36
|
+
```shell
|
|
37
|
+
plonecli update
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
Pulls the latest copier-templates clone and checks PyPI for a newer plonecli. Run this if `create`/`add` reports missing or stale templates. The clone lives at `~/.copier-templates/plone-copier-templates`.
|
|
41
|
+
|
|
42
|
+
## Pointing at a different template repo/branch
|
|
43
|
+
|
|
44
|
+
Environment variables override `~/.plonecli/config.toml` and take precedence:
|
|
45
|
+
|
|
46
|
+
- `PLONECLI_TEMPLATES_REPO_URL` — template repository URL.
|
|
47
|
+
- `PLONECLI_TEMPLATES_BRANCH` — branch to track (default `main`).
|
|
48
|
+
- `PLONECLI_TEMPLATES_DIR` — local directory for the clone.
|
|
49
|
+
|
|
50
|
+
```shell
|
|
51
|
+
export PLONECLI_TEMPLATES_REPO_URL=https://github.com/myorg/my-templates
|
|
52
|
+
export PLONECLI_TEMPLATES_BRANCH=develop
|
|
53
|
+
plonecli create addon my.addon
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
Useful for testing custom template forks, CI with pre-cloned templates, or org-maintained template sets.
|
|
57
|
+
|
|
58
|
+
## Global config
|
|
59
|
+
|
|
60
|
+
`plonecli config` interactively sets author name/email, GitHub user, default Plone version, and templates repo/branch, saved to `~/.plonecli/config.toml`. On first run it offers to import legacy `~/.mrbob` settings.
|
|
61
|
+
|
|
62
|
+
## Troubleshooting
|
|
63
|
+
|
|
64
|
+
- **`NotInPackageError`** — you ran an in-project command (`add`/`setup`/`serve`/`test`/`debug`) outside a project. `cd` into the project root.
|
|
65
|
+
- **Template not found / empty `plonecli -l`** — templates clone missing or stale: run `plonecli update`. If `update` itself fails (e.g. `~/.plonecli` is read-only, or `[templates] local_path` in `config.toml` points at a non-existent path such as another user's home) but a templates clone already exists elsewhere, bypass the config by pointing `PLONECLI_TEMPLATES_DIR` at the existing clone, e.g. `export PLONECLI_TEMPLATES_DIR=~/.copier-templates/plone-copier-templates`.
|
|
66
|
+
- **Subtemplate not listed** — it does not match the current project type; run `plonecli -l` inside the correct project type.
|
|
67
|
+
- **`setup` rejected** — `setup` only runs inside a `backend_addon`.
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
# Changes here will be overwritten by Copier; NEVER EDIT MANUALLY
|
|
2
|
+
_commit: 978185b
|
|
3
|
+
_src_path: /home/maik/develop/src/copier-claude-code-devcontainer/
|
|
4
|
+
enable_docker_in_docker: true
|
|
5
|
+
enable_plone: true
|
|
6
|
+
enable_pnpm: false
|
|
7
|
+
enable_python_uv: true
|
|
8
|
+
enable_terminal_recording: true
|
|
9
|
+
extra_mounts:
|
|
10
|
+
- source=${localEnv:HOME}/develop/plone/src/copier-templates,target=/home/node/develop/plone/src/copier-templates,type=bind,consistency=delegated
|
|
11
|
+
node_version: '20'
|
|
12
|
+
|
|
@@ -26,6 +26,8 @@ RUN apt-get update && apt-get install -y --no-install-recommends \
|
|
|
26
26
|
chromium \
|
|
27
27
|
python3 \
|
|
28
28
|
python3-venv \
|
|
29
|
+
asciinema \
|
|
30
|
+
ffmpeg \
|
|
29
31
|
&& apt-get clean && rm -rf /var/lib/apt/lists/*
|
|
30
32
|
|
|
31
33
|
# Ensure default node user has access to /usr/local/share
|
|
@@ -55,6 +57,26 @@ RUN ARCH=$(dpkg --print-architecture) && \
|
|
|
55
57
|
sudo dpkg -i "git-delta_${GIT_DELTA_VERSION}_${ARCH}.deb" && \
|
|
56
58
|
rm "git-delta_${GIT_DELTA_VERSION}_${ARCH}.deb"
|
|
57
59
|
|
|
60
|
+
# Terminal recording tools: vhs (scripted demos), agg (cast -> GIF), ttyd (vhs dep)
|
|
61
|
+
ARG VHS_VERSION=0.8.0
|
|
62
|
+
ARG AGG_VERSION=1.5.0
|
|
63
|
+
ARG TTYD_VERSION=1.7.7
|
|
64
|
+
RUN ARCH=$(dpkg --print-architecture) && \
|
|
65
|
+
case "$ARCH" in \
|
|
66
|
+
amd64) RUST_ARCH=x86_64-unknown-linux-gnu; TTYD_ARCH=x86_64 ;; \
|
|
67
|
+
arm64) RUST_ARCH=aarch64-unknown-linux-gnu; TTYD_ARCH=aarch64 ;; \
|
|
68
|
+
*) echo "Unsupported arch: $ARCH" && exit 1 ;; \
|
|
69
|
+
esac && \
|
|
70
|
+
wget "https://github.com/charmbracelet/vhs/releases/download/v${VHS_VERSION}/vhs_${VHS_VERSION}_${ARCH}.deb" && \
|
|
71
|
+
sudo dpkg -i "vhs_${VHS_VERSION}_${ARCH}.deb" && \
|
|
72
|
+
rm "vhs_${VHS_VERSION}_${ARCH}.deb" && \
|
|
73
|
+
wget -O /tmp/agg "https://github.com/asciinema/agg/releases/download/v${AGG_VERSION}/agg-${RUST_ARCH}" && \
|
|
74
|
+
sudo install -m 0755 /tmp/agg /usr/local/bin/agg && \
|
|
75
|
+
rm /tmp/agg && \
|
|
76
|
+
wget -O /tmp/ttyd "https://github.com/tsl0922/ttyd/releases/download/${TTYD_VERSION}/ttyd.${TTYD_ARCH}" && \
|
|
77
|
+
sudo install -m 0755 /tmp/ttyd /usr/local/bin/ttyd && \
|
|
78
|
+
rm /tmp/ttyd
|
|
79
|
+
|
|
58
80
|
# Set up non-root user
|
|
59
81
|
USER node
|
|
60
82
|
|
|
@@ -74,6 +96,8 @@ ARG ZSH_IN_DOCKER_VERSION=1.2.0
|
|
|
74
96
|
RUN sh -c "$(wget -O- https://github.com/deluan/zsh-in-docker/releases/download/v${ZSH_IN_DOCKER_VERSION}/zsh-in-docker.sh)" -- \
|
|
75
97
|
-p git \
|
|
76
98
|
-p fzf \
|
|
99
|
+
-p docker \
|
|
100
|
+
-p docker-compose \
|
|
77
101
|
-a "source /usr/share/doc/fzf/examples/key-bindings.zsh" \
|
|
78
102
|
-a "source /usr/share/doc/fzf/examples/completion.zsh" \
|
|
79
103
|
-a "export PROMPT_COMMAND='history -a' && export HISTFILE=/commandhistory/.bash_history" \
|
|
@@ -95,8 +119,3 @@ USER node
|
|
|
95
119
|
|
|
96
120
|
# Install UV package manager
|
|
97
121
|
RUN curl -LsSf https://astral.sh/uv/install.sh | sh
|
|
98
|
-
|
|
99
|
-
# Install Claude Code (native) and OpenCode into ~/.local/bin (persisted in image)
|
|
100
|
-
RUN mkdir -p "$HOME/.local/bin" && \
|
|
101
|
-
curl -fsSL https://claude.ai/install.sh | bash && \
|
|
102
|
-
curl -fsSL https://opencode.ai/install | bash
|
|
@@ -9,6 +9,11 @@
|
|
|
9
9
|
}
|
|
10
10
|
},
|
|
11
11
|
|
|
12
|
+
"features": {
|
|
13
|
+
"ghcr.io/devcontainers/features/docker-in-docker:2": {}
|
|
14
|
+
},
|
|
15
|
+
|
|
16
|
+
"initializeCommand": "mkdir -p \"$HOME/.claude\" \"$HOME/.copier-templates\" \"$HOME/.plonecli\"",
|
|
12
17
|
"runArgs": ["--cap-add=NET_ADMIN", "--cap-add=NET_RAW"],
|
|
13
18
|
"customizations": {
|
|
14
19
|
"vscode": {
|
|
@@ -42,8 +47,10 @@
|
|
|
42
47
|
"mounts": [
|
|
43
48
|
"source=claude-code-bashhistory-${devcontainerId},target=/commandhistory,type=volume",
|
|
44
49
|
"source=claude-code-config-${devcontainerId},target=/home/node/.claude,type=volume",
|
|
45
|
-
"source=${localEnv:HOME}
|
|
46
|
-
"source=${localEnv:HOME}/.copier-templates
|
|
50
|
+
"source=${localEnv:HOME}/.claude,target=/host-claude-config,type=bind,readonly",
|
|
51
|
+
"source=${localEnv:HOME}/.copier-templates,target=/home/node/.copier-templates,type=bind,readonly",
|
|
52
|
+
"source=${localEnv:HOME}/.plonecli,target=/home/node/.plonecli,type=bind,readonly",
|
|
53
|
+
"source=${localEnv:HOME}/develop/plone/src/copier-templates,target=/home/node/develop/plone/src/copier-templates,type=bind,consistency=delegated"
|
|
47
54
|
],
|
|
48
55
|
"containerEnv": {
|
|
49
56
|
"CLAUDE_CONFIG_DIR": "/home/node/.claude",
|
|
@@ -1,8 +1,22 @@
|
|
|
1
1
|
#!/bin/bash
|
|
2
2
|
set -euo pipefail # Exit on error, undefined vars, and pipeline failures
|
|
3
3
|
IFS=$'\n\t' # Stricter word splitting
|
|
4
|
-
|
|
5
|
-
|
|
4
|
+
|
|
5
|
+
# Wait for Docker daemon to be ready (docker-in-docker feature starts it async)
|
|
6
|
+
echo "Waiting for Docker daemon to be ready..."
|
|
7
|
+
for i in $(seq 1 30); do
|
|
8
|
+
if docker info &>/dev/null; then
|
|
9
|
+
echo "Docker daemon is ready"
|
|
10
|
+
break
|
|
11
|
+
fi
|
|
12
|
+
if [ "$i" -eq 30 ]; then
|
|
13
|
+
echo "WARNING: Docker daemon not ready after 30s, proceeding anyway"
|
|
14
|
+
fi
|
|
15
|
+
sleep 1
|
|
16
|
+
done
|
|
17
|
+
|
|
18
|
+
# Save entire nat table BEFORE flushing (Docker needs it for DinD networking)
|
|
19
|
+
SAVED_NAT=$(iptables-save -t nat)
|
|
6
20
|
|
|
7
21
|
# Flush existing rules and delete existing ipsets
|
|
8
22
|
iptables -F
|
|
@@ -13,21 +27,21 @@ iptables -t mangle -F
|
|
|
13
27
|
iptables -t mangle -X
|
|
14
28
|
ipset destroy allowed-domains 2>/dev/null || true
|
|
15
29
|
|
|
16
|
-
#
|
|
17
|
-
if [ -n "$
|
|
18
|
-
echo "Restoring Docker DNS
|
|
19
|
-
|
|
20
|
-
iptables -t nat -N DOCKER_POSTROUTING 2>/dev/null || true
|
|
21
|
-
echo "$DOCKER_DNS_RULES" | xargs -L 1 iptables -t nat
|
|
30
|
+
# Restore full nat table (DNS + MASQUERADE + DOCKER chains for DinD)
|
|
31
|
+
if [ -n "$SAVED_NAT" ]; then
|
|
32
|
+
echo "Restoring Docker nat rules (DNS, MASQUERADE, DOCKER chains)..."
|
|
33
|
+
echo "$SAVED_NAT" | iptables-restore --noflush
|
|
22
34
|
else
|
|
23
|
-
echo "No Docker
|
|
35
|
+
echo "No Docker nat rules to restore"
|
|
24
36
|
fi
|
|
25
37
|
|
|
26
38
|
# First allow DNS and localhost before any restrictions
|
|
27
39
|
# Allow outbound DNS
|
|
28
40
|
iptables -A OUTPUT -p udp --dport 53 -j ACCEPT
|
|
41
|
+
iptables -A OUTPUT -p tcp --dport 53 -j ACCEPT
|
|
29
42
|
# Allow inbound DNS responses
|
|
30
43
|
iptables -A INPUT -p udp --sport 53 -j ACCEPT
|
|
44
|
+
iptables -A INPUT -p tcp --sport 53 -j ACCEPT
|
|
31
45
|
# Allow outbound SSH
|
|
32
46
|
iptables -A OUTPUT -p tcp --dport 22 -j ACCEPT
|
|
33
47
|
# Allow inbound SSH responses
|
|
@@ -36,6 +50,10 @@ iptables -A INPUT -p tcp --sport 22 -m state --state ESTABLISHED -j ACCEPT
|
|
|
36
50
|
iptables -A INPUT -i lo -j ACCEPT
|
|
37
51
|
iptables -A OUTPUT -o lo -j ACCEPT
|
|
38
52
|
|
|
53
|
+
# Allow Docker bridge networks (DinD container communication)
|
|
54
|
+
iptables -A INPUT -s 172.16.0.0/12 -j ACCEPT
|
|
55
|
+
iptables -A OUTPUT -d 172.16.0.0/12 -j ACCEPT
|
|
56
|
+
|
|
39
57
|
# Create ipset with CIDR support
|
|
40
58
|
ipset create allowed-domains hash:net
|
|
41
59
|
|
|
@@ -68,12 +86,10 @@ for domain in \
|
|
|
68
86
|
"api.anthropic.com" \
|
|
69
87
|
"claude.ai" \
|
|
70
88
|
"sentry.io" \
|
|
71
|
-
"statsig.anthropic.com" \
|
|
72
89
|
"statsig.com" \
|
|
73
90
|
"marketplace.visualstudio.com" \
|
|
74
91
|
"vscode.blob.core.windows.net" \
|
|
75
|
-
"update.code.visualstudio.com"
|
|
76
|
-
"opencode.ai"; do
|
|
92
|
+
"update.code.visualstudio.com"; do
|
|
77
93
|
echo "Resolving $domain..."
|
|
78
94
|
ips=$(dig +noall +answer A "$domain" | awk '$4 == "A" {print $5}')
|
|
79
95
|
if [ -z "$ips" ]; then
|
|
@@ -94,7 +110,26 @@ done
|
|
|
94
110
|
# PyPI (for UV package installation)
|
|
95
111
|
for domain in \
|
|
96
112
|
"pypi.org" \
|
|
97
|
-
"files.pythonhosted.org"
|
|
113
|
+
"files.pythonhosted.org"; do
|
|
114
|
+
echo "Resolving $domain..."
|
|
115
|
+
ips=$(dig +noall +answer A "$domain" | awk '$4 == "A" {print $5}')
|
|
116
|
+
if [ -z "$ips" ]; then
|
|
117
|
+
echo "ERROR: Failed to resolve $domain"
|
|
118
|
+
exit 1
|
|
119
|
+
fi
|
|
120
|
+
|
|
121
|
+
while read -r ip; do
|
|
122
|
+
if [[ ! "$ip" =~ ^[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}$ ]]; then
|
|
123
|
+
echo "ERROR: Invalid IP from DNS for $domain: $ip"
|
|
124
|
+
exit 1
|
|
125
|
+
fi
|
|
126
|
+
echo "Adding $ip for $domain"
|
|
127
|
+
ipset add allowed-domains "$ip" 2>/dev/null || true
|
|
128
|
+
done < <(echo "$ips")
|
|
129
|
+
done
|
|
130
|
+
|
|
131
|
+
# Plone (for version constraints)
|
|
132
|
+
for domain in \
|
|
98
133
|
"dist.plone.org"; do
|
|
99
134
|
echo "Resolving $domain..."
|
|
100
135
|
ips=$(dig +noall +answer A "$domain" | awk '$4 == "A" {print $5}')
|
|
@@ -113,6 +148,49 @@ for domain in \
|
|
|
113
148
|
done < <(echo "$ips")
|
|
114
149
|
done
|
|
115
150
|
|
|
151
|
+
# Docker Hub (for DinD image pulls - CDN with rotating IPs, need /16)
|
|
152
|
+
for domain in \
|
|
153
|
+
"registry-1.docker.io" \
|
|
154
|
+
"auth.docker.io" \
|
|
155
|
+
"production.cloudflare.docker.com"; do
|
|
156
|
+
echo "Resolving $domain..."
|
|
157
|
+
ips=$(dig +noall +answer A "$domain" | awk '$4 == "A" {print $5}')
|
|
158
|
+
if [ -z "$ips" ]; then
|
|
159
|
+
echo "WARNING: Failed to resolve $domain, skipping"
|
|
160
|
+
continue
|
|
161
|
+
fi
|
|
162
|
+
while read -r ip; do
|
|
163
|
+
if [[ ! "$ip" =~ ^[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}$ ]]; then
|
|
164
|
+
echo "WARNING: Invalid IP from DNS for $domain: $ip, skipping"
|
|
165
|
+
continue
|
|
166
|
+
fi
|
|
167
|
+
cidr=$(echo "$ip" | sed 's/\.[0-9]*\.[0-9]*$/.0.0\/16/')
|
|
168
|
+
echo "Adding $cidr for $domain (CDN /16)"
|
|
169
|
+
ipset add allowed-domains "$cidr" 2>/dev/null || true
|
|
170
|
+
done < <(echo "$ips")
|
|
171
|
+
done
|
|
172
|
+
|
|
173
|
+
# GHCR (for container images)
|
|
174
|
+
for domain in \
|
|
175
|
+
"ghcr.io" \
|
|
176
|
+
"pkg-containers.githubusercontent.com"; do
|
|
177
|
+
echo "Resolving $domain..."
|
|
178
|
+
ips=$(dig +noall +answer A "$domain" | awk '$4 == "A" {print $5}')
|
|
179
|
+
if [ -z "$ips" ]; then
|
|
180
|
+
echo "WARNING: Failed to resolve $domain, skipping"
|
|
181
|
+
continue
|
|
182
|
+
fi
|
|
183
|
+
while read -r ip; do
|
|
184
|
+
if [[ ! "$ip" =~ ^[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}$ ]]; then
|
|
185
|
+
echo "WARNING: Invalid IP from DNS for $domain: $ip, skipping"
|
|
186
|
+
continue
|
|
187
|
+
fi
|
|
188
|
+
cidr=$(echo "$ip" | sed 's/\.[0-9]*\.[0-9]*$/.0.0\/16/')
|
|
189
|
+
echo "Adding $cidr for $domain (CDN /16)"
|
|
190
|
+
ipset add allowed-domains "$cidr" 2>/dev/null || true
|
|
191
|
+
done < <(echo "$ips")
|
|
192
|
+
done
|
|
193
|
+
|
|
116
194
|
# Get host IP from default route
|
|
117
195
|
HOST_IP=$(ip route | grep default | cut -d" " -f3)
|
|
118
196
|
if [ -z "$HOST_IP" ]; then
|
|
@@ -129,7 +207,7 @@ iptables -A OUTPUT -d "$HOST_NETWORK" -j ACCEPT
|
|
|
129
207
|
|
|
130
208
|
# Set default policies to DROP first
|
|
131
209
|
iptables -P INPUT DROP
|
|
132
|
-
iptables -P FORWARD
|
|
210
|
+
iptables -P FORWARD ACCEPT
|
|
133
211
|
iptables -P OUTPUT DROP
|
|
134
212
|
|
|
135
213
|
# First allow established connections for already approved traffic
|