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.
Files changed (76) hide show
  1. plonecli-7.0.0b4/.agents/skills/plonecli/SKILL.md +74 -0
  2. plonecli-7.0.0b4/.agents/skills/plonecli/reference/add.md +83 -0
  3. plonecli-7.0.0b4/.agents/skills/plonecli/reference/create.md +46 -0
  4. plonecli-7.0.0b4/.agents/skills/plonecli/reference/maintain.md +67 -0
  5. plonecli-7.0.0b4/.copier-answers.yml +12 -0
  6. {plonecli-7.0.0b2 → plonecli-7.0.0b4}/.devcontainer/Dockerfile +24 -5
  7. {plonecli-7.0.0b2 → plonecli-7.0.0b4}/.devcontainer/devcontainer.json +9 -2
  8. {plonecli-7.0.0b2 → plonecli-7.0.0b4}/.devcontainer/init-firewall.sh +92 -14
  9. plonecli-7.0.0b4/.devcontainer/setup-claude.sh +162 -0
  10. {plonecli-7.0.0b2 → plonecli-7.0.0b4}/CHANGES.md +41 -0
  11. plonecli-7.0.0b4/CLAUDE.md +6 -0
  12. {plonecli-7.0.0b2 → plonecli-7.0.0b4}/PKG-INFO +60 -1
  13. {plonecli-7.0.0b2 → plonecli-7.0.0b4}/README.md +59 -0
  14. {plonecli-7.0.0b2 → plonecli-7.0.0b4}/devcontainer.sh +3 -3
  15. plonecli-7.0.0b4/docs/demo-backend.tape +398 -0
  16. plonecli-7.0.0b4/docs/demo-backend.webm +0 -0
  17. plonecli-7.0.0b4/docs/demo-classicui.tape +330 -0
  18. plonecli-7.0.0b4/docs/demo-recording-prompt.md +232 -0
  19. plonecli-7.0.0b4/docs/demo-restapi.tape +212 -0
  20. {plonecli-7.0.0b2 → plonecli-7.0.0b4}/plonecli/cli.py +92 -2
  21. plonecli-7.0.0b4/plonecli/skill_installer.py +122 -0
  22. {plonecli-7.0.0b2 → plonecli-7.0.0b4}/plonecli/templates.py +34 -9
  23. {plonecli-7.0.0b2 → plonecli-7.0.0b4}/pyproject.toml +1 -1
  24. {plonecli-7.0.0b2 → plonecli-7.0.0b4}/tests/test_plonecli.py +20 -0
  25. plonecli-7.0.0b4/tests/test_skill.py +90 -0
  26. {plonecli-7.0.0b2 → plonecli-7.0.0b4}/tests/test_templates.py +34 -7
  27. {plonecli-7.0.0b2 → plonecli-7.0.0b4}/tests/test_theme_barceloneta_integration.py +3 -2
  28. {plonecli-7.0.0b2 → plonecli-7.0.0b4}/uv.lock +10 -10
  29. plonecli-7.0.0b2/.devcontainer/setup-claude.sh +0 -33
  30. plonecli-7.0.0b2/CLAUDE.md +0 -2
  31. {plonecli-7.0.0b2 → plonecli-7.0.0b4}/.claude/statusline-command.sh +0 -0
  32. {plonecli-7.0.0b2 → plonecli-7.0.0b4}/.editorconfig +0 -0
  33. {plonecli-7.0.0b2 → plonecli-7.0.0b4}/.github/ISSUE_TEMPLATE.md +0 -0
  34. {plonecli-7.0.0b2 → plonecli-7.0.0b4}/.github/workflows/python-package.yml +0 -0
  35. {plonecli-7.0.0b2 → plonecli-7.0.0b4}/.gitignore +0 -0
  36. {plonecli-7.0.0b2 → plonecli-7.0.0b4}/.readthedocs.yaml +0 -0
  37. {plonecli-7.0.0b2 → plonecli-7.0.0b4}/AUTHORS.md +0 -0
  38. {plonecli-7.0.0b2 → plonecli-7.0.0b4}/CONTRIBUTING.md +0 -0
  39. {plonecli-7.0.0b2 → plonecli-7.0.0b4}/LICENSE +0 -0
  40. {plonecli-7.0.0b2 → plonecli-7.0.0b4}/MANIFEST.in +0 -0
  41. {plonecli-7.0.0b2 → plonecli-7.0.0b4}/ROADMAP.md +0 -0
  42. {plonecli-7.0.0b2 → plonecli-7.0.0b4}/docs/.gitignore +0 -0
  43. {plonecli-7.0.0b2 → plonecli-7.0.0b4}/docs/Makefile +0 -0
  44. {plonecli-7.0.0b2 → plonecli-7.0.0b4}/docs/authors.md +0 -0
  45. {plonecli-7.0.0b2 → plonecli-7.0.0b4}/docs/conf.py +0 -0
  46. {plonecli-7.0.0b2 → plonecli-7.0.0b4}/docs/contributing.md +0 -0
  47. {plonecli-7.0.0b2 → plonecli-7.0.0b4}/docs/history.md +0 -0
  48. {plonecli-7.0.0b2 → plonecli-7.0.0b4}/docs/index.md +0 -0
  49. {plonecli-7.0.0b2 → plonecli-7.0.0b4}/docs/installation.md +0 -0
  50. {plonecli-7.0.0b2 → plonecli-7.0.0b4}/docs/make.bat +0 -0
  51. {plonecli-7.0.0b2 → plonecli-7.0.0b4}/docs/plone_cli_logo.png +0 -0
  52. {plonecli-7.0.0b2 → plonecli-7.0.0b4}/docs/plone_cli_logo.svg +0 -0
  53. {plonecli-7.0.0b2 → plonecli-7.0.0b4}/docs/plonecli_add_content_type_optimized.gif +0 -0
  54. {plonecli-7.0.0b2 → plonecli-7.0.0b4}/docs/plonecli_add_theme_optimized.gif +0 -0
  55. {plonecli-7.0.0b2 → plonecli-7.0.0b4}/docs/plonecli_add_vocabulary_optimized.gif +0 -0
  56. {plonecli-7.0.0b2 → plonecli-7.0.0b4}/docs/plonecli_build_optimized.gif +0 -0
  57. {plonecli-7.0.0b2 → plonecli-7.0.0b4}/docs/plonecli_create_addon_optimized.gif +0 -0
  58. {plonecli-7.0.0b2 → plonecli-7.0.0b4}/docs/plonecli_serve_optimized.gif +0 -0
  59. {plonecli-7.0.0b2 → plonecli-7.0.0b4}/docs/readme.md +0 -0
  60. {plonecli-7.0.0b2 → plonecli-7.0.0b4}/jsconfig.json +0 -0
  61. {plonecli-7.0.0b2 → plonecli-7.0.0b4}/plonecli/__init__.py +0 -0
  62. {plonecli-7.0.0b2 → plonecli-7.0.0b4}/plonecli/config.py +0 -0
  63. {plonecli-7.0.0b2 → plonecli-7.0.0b4}/plonecli/exceptions.py +0 -0
  64. {plonecli-7.0.0b2 → plonecli-7.0.0b4}/plonecli/plone_versions.py +0 -0
  65. {plonecli-7.0.0b2 → plonecli-7.0.0b4}/plonecli/project.py +0 -0
  66. {plonecli-7.0.0b2 → plonecli-7.0.0b4}/plonecli/registry.py +0 -0
  67. {plonecli-7.0.0b2 → plonecli-7.0.0b4}/plonecli/updater.py +0 -0
  68. {plonecli-7.0.0b2 → plonecli-7.0.0b4}/plonecli.md +0 -0
  69. {plonecli-7.0.0b2 → plonecli-7.0.0b4}/requirements.txt +0 -0
  70. {plonecli-7.0.0b2 → plonecli-7.0.0b4}/tests/__init__.py +0 -0
  71. {plonecli-7.0.0b2 → plonecli-7.0.0b4}/tests/conftest.py +0 -0
  72. {plonecli-7.0.0b2 → plonecli-7.0.0b4}/tests/test_config.py +0 -0
  73. {plonecli-7.0.0b2 → plonecli-7.0.0b4}/tests/test_project.py +0 -0
  74. {plonecli-7.0.0b2 → plonecli-7.0.0b4}/tests/test_registry.py +0 -0
  75. {plonecli-7.0.0b2 → plonecli-7.0.0b4}/tests/test_updater.py +0 -0
  76. {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}/develop/plone/src/copier-templates,target=/home/node/develop/plone/src/copier-templates,type=bind,consistency=delegated",
46
- "source=${localEnv:HOME}/.copier-templates/plone-copier-templates,target=/home/node/.copier-templates/plone-copier-templates,type=bind,readonly"
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
- # Extract Docker DNS info BEFORE any flushing
5
- DOCKER_DNS_RULES=$(iptables-save -t nat | grep "127\.0\.0\.11" || true)
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
- # Selectively restore ONLY internal Docker DNS resolution
17
- if [ -n "$DOCKER_DNS_RULES" ]; then
18
- echo "Restoring Docker DNS rules..."
19
- iptables -t nat -N DOCKER_OUTPUT 2>/dev/null || true
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 DNS rules to restore"
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 DROP
210
+ iptables -P FORWARD ACCEPT
133
211
  iptables -P OUTPUT DROP
134
212
 
135
213
  # First allow established connections for already approved traffic