diff --git a/.claude/statusline-command.sh b/.claude/statusline-command.sh new file mode 100644 index 0000000..ec0636b --- /dev/null +++ b/.claude/statusline-command.sh @@ -0,0 +1,36 @@ +#!/usr/bin/env bash +input=$(cat) + +model=$(echo "$input" | jq -r '.model.display_name // "unknown model"') +output_style=$(echo "$input" | jq -r '.output_style.name // empty') +used_pct=$(echo "$input" | jq -r '.context_window.used_percentage // empty') +remaining_pct=$(echo "$input" | jq -r '.context_window.remaining_percentage // empty') + +# Build status parts +parts=() + +# Model +parts+=("$model") + +# Context window +if [ -n "$used_pct" ] && [ -n "$remaining_pct" ]; then + ctx=$(printf "ctx: %.0f%% used / %.0f%% left" "$used_pct" "$remaining_pct") + parts+=("$ctx") +fi + +# Output style / thinking +if [ -n "$output_style" ] && [ "$output_style" != "default" ]; then + parts+=("style: $output_style") +fi + +# Join with separator +result="" +for part in "${parts[@]}"; do + if [ -z "$result" ]; then + result="$part" + else + result="$result | $part" + fi +done + +echo "$result" diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile new file mode 100644 index 0000000..682b0d1 --- /dev/null +++ b/.devcontainer/Dockerfile @@ -0,0 +1,102 @@ +FROM node:20 + +ARG TZ +ENV TZ="$TZ" + +# Install basic development tools, iptables/ipset, and chromium +RUN apt-get update && apt-get install -y --no-install-recommends \ + less \ + git \ + procps \ + sudo \ + fzf \ + zsh \ + man-db \ + unzip \ + gnupg2 \ + gh \ + iptables \ + ipset \ + iproute2 \ + dnsutils \ + aggregate \ + jq \ + nano \ + vim \ + chromium \ + python3 \ + python3-venv \ + && apt-get clean && rm -rf /var/lib/apt/lists/* + +# Ensure default node user has access to /usr/local/share +RUN mkdir -p /usr/local/share/npm-global && \ + chown -R node:node /usr/local/share + +ARG USERNAME=node + +# Persist bash history. +RUN SNIPPET="export PROMPT_COMMAND='history -a' && export HISTFILE=/commandhistory/.bash_history" \ + && mkdir /commandhistory \ + && touch /commandhistory/.bash_history \ + && chown -R $USERNAME /commandhistory + +# Set `DEVCONTAINER` environment variable to help with orientation +ENV DEVCONTAINER=true + +# Create workspace and config directories and set permissions +RUN mkdir -p /workspace /home/node/.claude && \ + chown -R node:node /workspace /home/node/.claude + +WORKDIR /workspace + +ARG GIT_DELTA_VERSION=0.18.2 +RUN ARCH=$(dpkg --print-architecture) && \ + wget "https://github.com/dandavison/delta/releases/download/${GIT_DELTA_VERSION}/git-delta_${GIT_DELTA_VERSION}_${ARCH}.deb" && \ + sudo dpkg -i "git-delta_${GIT_DELTA_VERSION}_${ARCH}.deb" && \ + rm "git-delta_${GIT_DELTA_VERSION}_${ARCH}.deb" + +# Set up non-root user +USER node + +# Install global packages +ENV NPM_CONFIG_PREFIX=/usr/local/share/npm-global +ENV PATH=$HOME/.local/bin:$PATH:/usr/local/share/npm-global/bin + +# Set the default shell to zsh rather than sh +ENV SHELL=/bin/zsh + +# Set the default editor and visual +ENV EDITOR=nano +ENV VISUAL=nano + +# Default powerline10k theme +ARG ZSH_IN_DOCKER_VERSION=1.2.0 +RUN sh -c "$(wget -O- https://github.com/deluan/zsh-in-docker/releases/download/v${ZSH_IN_DOCKER_VERSION}/zsh-in-docker.sh)" -- \ + -p git \ + -p fzf \ + -a "source /usr/share/doc/fzf/examples/key-bindings.zsh" \ + -a "source /usr/share/doc/fzf/examples/completion.zsh" \ + -a "export PROMPT_COMMAND='history -a' && export HISTFILE=/commandhistory/.bash_history" \ + -x + +# Copy and set up firewall and Claude setup scripts +COPY init-firewall.sh /usr/local/bin/ +COPY setup-claude.sh /usr/local/bin/ +USER root +RUN chmod +x /usr/local/bin/init-firewall.sh /usr/local/bin/setup-claude.sh && \ + echo "node ALL=(root) NOPASSWD: /usr/local/bin/init-firewall.sh" > /etc/sudoers.d/node-firewall && \ + chmod 0440 /etc/sudoers.d/node-firewall + +# Chromium flags for running in container +ENV CHROME_PATH=/usr/bin/chromium +ENV CHROMIUM_FLAGS="--no-sandbox --disable-gpu --disable-dev-shm-usage" + +USER node + +# Install UV package manager +RUN curl -LsSf https://astral.sh/uv/install.sh | sh + +# Install Claude Code (native) and OpenCode into ~/.local/bin (persisted in image) +RUN mkdir -p "$HOME/.local/bin" && \ + curl -fsSL https://claude.ai/install.sh | bash && \ + curl -fsSL https://opencode.ai/install | bash diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json new file mode 100644 index 0000000..c075031 --- /dev/null +++ b/.devcontainer/devcontainer.json @@ -0,0 +1,59 @@ +{ + "name": "Claude Code Sandbox", + "build": { + "dockerfile": "Dockerfile", + "args": { + "TZ": "${localEnv:TZ:America/Los_Angeles}", + "GIT_DELTA_VERSION": "0.18.2", + "ZSH_IN_DOCKER_VERSION": "1.2.0" + } + }, + + "runArgs": ["--cap-add=NET_ADMIN", "--cap-add=NET_RAW"], + "customizations": { + "vscode": { + "extensions": [ + "anthropic.claude-code", + "dbaeumer.vscode-eslint", + "esbenp.prettier-vscode", + "eamodio.gitlens", + "ms-python.python" + ], + "settings": { + "editor.formatOnSave": true, + "editor.defaultFormatter": "esbenp.prettier-vscode", + "editor.codeActionsOnSave": { + "source.fixAll.eslint": "explicit" + }, + "terminal.integrated.defaultProfile.linux": "zsh", + "terminal.integrated.profiles.linux": { + "bash": { + "path": "bash", + "icon": "terminal-bash" + }, + "zsh": { + "path": "zsh" + } + } + } + } + }, + "remoteUser": "node", + "mounts": [ + "source=claude-code-bashhistory-${devcontainerId},target=/commandhistory,type=volume", + "source=claude-code-config-${devcontainerId},target=/home/node/.claude,type=volume", + "source=${localEnv:HOME}/develop/plone/src/copier-templates,target=/home/node/develop/plone/src/copier-templates,type=bind,consistency=delegated", + "source=${localEnv:HOME}/.copier-templates/plone-copier-templates,target=/home/node/.copier-templates/plone-copier-templates,type=bind,readonly" + ], + "containerEnv": { + "CLAUDE_CONFIG_DIR": "/home/node/.claude", + "POWERLEVEL9K_DISABLE_GITSTATUS": "true", + "CHROME_PATH": "/usr/bin/chromium", + "PUPPETEER_EXECUTABLE_PATH": "/usr/bin/chromium" + }, + "workspaceMount": "source=${localWorkspaceFolder},target=/workspace,type=bind,consistency=delegated", + "workspaceFolder": "/workspace", + "postCreateCommand": "/usr/local/bin/setup-claude.sh", + "postStartCommand": "sudo /usr/local/bin/init-firewall.sh", + "waitFor": "postStartCommand" +} diff --git a/.devcontainer/init-firewall.sh b/.devcontainer/init-firewall.sh new file mode 100644 index 0000000..56755c6 --- /dev/null +++ b/.devcontainer/init-firewall.sh @@ -0,0 +1,163 @@ +#!/bin/bash +set -euo pipefail # Exit on error, undefined vars, and pipeline failures +IFS=$'\n\t' # Stricter word splitting +# Extract Docker DNS info BEFORE any flushing +DOCKER_DNS_RULES=$(iptables-save -t nat | grep "127\.0\.0\.11" || true) + +# Flush existing rules and delete existing ipsets +iptables -F +iptables -X +iptables -t nat -F +iptables -t nat -X +iptables -t mangle -F +iptables -t mangle -X +ipset destroy allowed-domains 2>/dev/null || true + +# Selectively restore ONLY internal Docker DNS resolution +if [ -n "$DOCKER_DNS_RULES" ]; then + echo "Restoring Docker DNS rules..." + iptables -t nat -N DOCKER_OUTPUT 2>/dev/null || true + iptables -t nat -N DOCKER_POSTROUTING 2>/dev/null || true + echo "$DOCKER_DNS_RULES" | xargs -L 1 iptables -t nat +else + echo "No Docker DNS rules to restore" +fi + +# First allow DNS and localhost before any restrictions +# Allow outbound DNS +iptables -A OUTPUT -p udp --dport 53 -j ACCEPT +# Allow inbound DNS responses +iptables -A INPUT -p udp --sport 53 -j ACCEPT +# Allow outbound SSH +iptables -A OUTPUT -p tcp --dport 22 -j ACCEPT +# Allow inbound SSH responses +iptables -A INPUT -p tcp --sport 22 -m state --state ESTABLISHED -j ACCEPT +# Allow localhost +iptables -A INPUT -i lo -j ACCEPT +iptables -A OUTPUT -o lo -j ACCEPT + +# Create ipset with CIDR support +ipset create allowed-domains hash:net + +# Fetch GitHub meta information and aggregate + add their IP ranges +echo "Fetching GitHub IP ranges..." +gh_ranges=$(curl -s https://api.github.com/meta) +if [ -z "$gh_ranges" ]; then + echo "ERROR: Failed to fetch GitHub IP ranges" + exit 1 +fi + +if ! echo "$gh_ranges" | jq -e '.web and .api and .git' >/dev/null; then + echo "ERROR: GitHub API response missing required fields" + exit 1 +fi + +echo "Processing GitHub IPs..." +while read -r cidr; do + if [[ ! "$cidr" =~ ^[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}/[0-9]{1,2}$ ]]; then + echo "ERROR: Invalid CIDR range from GitHub meta: $cidr" + exit 1 + fi + echo "Adding GitHub range $cidr" + ipset add allowed-domains "$cidr" 2>/dev/null || true +done < <(echo "$gh_ranges" | jq -r '(.web + .api + .git)[]' | aggregate -q) + +# Resolve and add other allowed domains +for domain in \ + "registry.npmjs.org" \ + "api.anthropic.com" \ + "claude.ai" \ + "sentry.io" \ + "statsig.anthropic.com" \ + "statsig.com" \ + "marketplace.visualstudio.com" \ + "vscode.blob.core.windows.net" \ + "update.code.visualstudio.com" \ + "opencode.ai"; do + echo "Resolving $domain..." + ips=$(dig +noall +answer A "$domain" | awk '$4 == "A" {print $5}') + if [ -z "$ips" ]; then + echo "ERROR: Failed to resolve $domain" + exit 1 + fi + + while read -r ip; do + if [[ ! "$ip" =~ ^[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}$ ]]; then + echo "ERROR: Invalid IP from DNS for $domain: $ip" + exit 1 + fi + echo "Adding $ip for $domain" + ipset add allowed-domains "$ip" 2>/dev/null || true + done < <(echo "$ips") +done + +# PyPI (for UV package installation) +for domain in \ + "pypi.org" \ + "files.pythonhosted.org" \ + "dist.plone.org"; do + echo "Resolving $domain..." + ips=$(dig +noall +answer A "$domain" | awk '$4 == "A" {print $5}') + if [ -z "$ips" ]; then + echo "ERROR: Failed to resolve $domain" + exit 1 + fi + + while read -r ip; do + if [[ ! "$ip" =~ ^[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}$ ]]; then + echo "ERROR: Invalid IP from DNS for $domain: $ip" + exit 1 + fi + echo "Adding $ip for $domain" + ipset add allowed-domains "$ip" 2>/dev/null || true + done < <(echo "$ips") +done + +# Get host IP from default route +HOST_IP=$(ip route | grep default | cut -d" " -f3) +if [ -z "$HOST_IP" ]; then + echo "ERROR: Failed to detect host IP" + exit 1 +fi + +HOST_NETWORK=$(echo "$HOST_IP" | sed "s/\.[0-9]*$/.0\/24/") +echo "Host network detected as: $HOST_NETWORK" + +# Set up remaining iptables rules +iptables -A INPUT -s "$HOST_NETWORK" -j ACCEPT +iptables -A OUTPUT -d "$HOST_NETWORK" -j ACCEPT + +# Set default policies to DROP first +iptables -P INPUT DROP +iptables -P FORWARD DROP +iptables -P OUTPUT DROP + +# First allow established connections for already approved traffic +iptables -A INPUT -m state --state ESTABLISHED,RELATED -j ACCEPT +iptables -A OUTPUT -m state --state ESTABLISHED,RELATED -j ACCEPT + +# Then allow only specific outbound traffic to allowed domains +iptables -A OUTPUT -m set --match-set allowed-domains dst -j ACCEPT + +# Explicitly REJECT all other outbound traffic for immediate feedback +iptables -A OUTPUT -j REJECT --reject-with icmp-admin-prohibited + +echo "Firewall configuration complete" +echo "Verifying firewall rules..." + +# Verify blocked traffic using a direct IP (1.1.1.1) to avoid DNS issues +# (container DNS may resolve blocked domains to 127.0.0.1, bypassing the firewall) +if curl --connect-timeout 5 -s -o /dev/null https://1.1.1.1 2>/dev/null; then + echo "ERROR: Firewall verification failed - was able to reach https://1.1.1.1" + exit 1 +else + echo "Firewall verification passed - unable to reach https://1.1.1.1 as expected" +fi + +# Verify GitHub API access +if ! curl --connect-timeout 5 https://api.github.com/zen >/dev/null 2>&1; then + echo "ERROR: Firewall verification failed - unable to reach https://api.github.com" + exit 1 +else + echo "Firewall verification passed - able to reach https://api.github.com as expected" +fi diff --git a/.devcontainer/setup-claude.sh b/.devcontainer/setup-claude.sh new file mode 100644 index 0000000..2919ba5 --- /dev/null +++ b/.devcontainer/setup-claude.sh @@ -0,0 +1,33 @@ +#!/bin/bash +set -euo pipefail + +# Claude Code and OpenCode are installed into the image via the Dockerfile. +# This script only handles per-container configuration that depends on the +# mounted ~/.claude volume. + +export PATH="$HOME/.local/bin:$PATH" + +if command -v claude &>/dev/null; then + echo "Configuring Chrome DevTools MCP server..." + claude mcp remove chrome-devtools 2>/dev/null || true + claude mcp add chrome-devtools -- npx -y chrome-devtools-mcp@latest +else + echo "WARNING: claude CLI not found, skipping MCP configuration" +fi + +echo "Enabling remote control for all sessions..." +mkdir -p ~/.claude +SETTINGS_FILE="$HOME/.claude/settings.json" +if [ -f "$SETTINGS_FILE" ]; then + # Merge preferRemoteControl into existing settings using node (available in the container) + node -e " + const fs = require('fs'); + const s = JSON.parse(fs.readFileSync('$SETTINGS_FILE', 'utf8')); + s.preferRemoteControl = true; + fs.writeFileSync('$SETTINGS_FILE', JSON.stringify(s, null, 2)); + " +else + echo '{"preferRemoteControl": true}' > "$SETTINGS_FILE" +fi + +echo "Claude Code and OpenCode setup complete." diff --git a/AUTHORS.md b/AUTHORS.md new file mode 100644 index 0000000..835f8b8 --- /dev/null +++ b/AUTHORS.md @@ -0,0 +1,9 @@ +# Credits + +## Development Lead + +* Maik Derstappen + +## Contributors + +* Thomas Massmann diff --git a/AUTHORS.rst b/AUTHORS.rst deleted file mode 100644 index 8711043..0000000 --- a/AUTHORS.rst +++ /dev/null @@ -1,13 +0,0 @@ -======= -Credits -======= - -Development Lead ----------------- - -* Maik Derstappen - -Contributors ------------- - -* Thomas Massmann diff --git a/CHANGES.rst b/CHANGES.md similarity index 61% rename from CHANGES.rst rename to CHANGES.md index 120310b..799ec4c 100644 --- a/CHANGES.rst +++ b/CHANGES.md @@ -1,59 +1,89 @@ -History -======= +# History -3.0 (unreleased) ----------------- +## 7.0 (unreleased) -- Nothing changed yet. +- Major rewrite: replaced mr.bob/bobtemplates.plone with copier-templates. + Templates are now managed as a local git clone at + `~/.copier-templates/plone-copier-templates`. + [MrTango] +- Removed buildout-related commands: `build`, `buildout`, + `requirements`, `venv`/`virtualenv`. + [MrTango] -3.0b1 (2026-04-14) ------------------- +- Replaced `~/.mrbob` config with `~/.plonecli/config.toml`. + Migration from old config is offered on first run. + [MrTango] -- Support only python 3.11 - 3.14 - [erral] +New features: -- Require setuptools < 82.0.0 - [erral] +- New `update` command to update copier-templates and check PyPI for + plonecli updates. + [MrTango] + +- New `setup` command to run zope-setup inside an existing backend_addon. + [MrTango] + +- `serve`, `test`, `debug` now delegate to invoke tasks generated by + templates (`uv run invoke start/test/debug`). + [MrTango] + +- Default Plone version is fetched from `dist.plone.org/release/` and + cached for 24 hours. + [MrTango] +- Environment variable overrides for template repo configuration: + `PLONECLI_TEMPLATES_REPO_URL`, `PLONECLI_TEMPLATES_BRANCH`, + `PLONECLI_TEMPLATES_DIR`. + [MrTango] + +- Periodic PyPI update check (every 24 hours) with non-intrusive notification. + [MrTango] + +- Primary installation method is now `uv tool install plonecli`. Also + supports uvx, venv, and pipx. + [MrTango] + +- Requires Python >= 3.11. + [MrTango] + + +# 3.0b1 (2026-04-14) + +- Support only python 3.11 - 3.14 + [erral] -2.5 (2022-11-03) ----------------- +## 2.5 (2022-11-03) - Provide a way to pass a mrbob configuration file [erral] -2.4 (2022-10-10) ----------------- +## 2.4 (2022-10-10) - Depend on bobtemplates.plone >=6.0b15 ;) -2.3 (2022-10-08) ----------------- +## 2.3 (2022-10-08) - Depend on bobtemplates.plone >=6.0b16 -2.2 (2022-05-04) ----------------- +## 2.2 (2022-05-04) -- Remove dependency on ``virtualenv``. +- Remove dependency on `virtualenv`. [wesleybl] -- Rename deprecated ``autocompletion`` parameter to ``shell_complete`` +- Rename deprecated `autocompletion` parameter to `shell_complete` [zshashz] -2.1.2 (2021-05-06) ------------------- +## 2.1.2 (2021-05-06) - Fix broken releases 2.1/2.1.1 [MrTango] -2.1.1 (2021-05-05) ------------------- +## 2.1.1 (2021-05-05) - Fix setup.cfg - v2.1 is a brown bag release. [jensens] @@ -62,8 +92,7 @@ History [svx] -2.1 (2021-05-05) ----------------- +## 2.1 (2021-05-05) - Call mr.bob using its API, no longer as subprocess. This ease the usage in a virtualenv. @@ -76,14 +105,12 @@ History [jensens] -2.0 (2020-12-10) ----------------- +## 2.0 (2020-12-10) - Release -2.0b1 (2020-11-20) ------------------- +## 2.0b1 (2020-11-20) - Add alias upport and an alias for "virtualenv" for the venv command [MrTango] @@ -92,8 +119,7 @@ History [MrTango] -2.0a2 (2020-11-19) ------------------- +## 2.0a2 (2020-11-19) - Fix #63 generate_mrbob_ini and the acconding tests [MrTango] @@ -114,15 +140,13 @@ History [staeff] -1.1 (2019-04-14) ----------------- +## 1.1 (2019-04-14) - Add new build option -p to define the python binary to use for the virtualenv [MrTango] -1.0 (2019-03-08) ----------------- +## 1.0 (2019-03-08) - add note for including install directory to $PATH [fgrcon] @@ -134,8 +158,7 @@ History [MrTango] -0.3.0 (2018-10-17) ------------------- +## 0.3.0 (2018-10-17) - Sort templates on --list-templates/-l command output [MrTango] @@ -146,8 +169,7 @@ History - Use the now released click library in version >= 7.0, you have to uninstall plonecli-click before upgrading! [MrTango] -0.2.2 (2018-08-13) ------------------- +## 0.2.2 (2018-08-13) - Add -t and -s options to the test command [kakshay21] @@ -156,8 +178,7 @@ History [kakshay21] -0.2.1 (2018-07-09) ------------------- +## 0.2.1 (2018-07-09) - Add plonecli config command to configure mrbob's global user settings [kakshay21] @@ -169,15 +190,13 @@ History [kakshay21] -0.2.0 (2018-04-03) ------------------- +## 0.2.0 (2018-04-03) - Add test command to allow running test from plonecli [MrTango] -0.1.1 (2018-03-28) ------------------- +## 0.1.1 (2018-03-28) - Improve command line output with colors and remove verbose option [MrTango] @@ -186,51 +205,44 @@ History [MrTango] -0.1.1b6 (2018-03-28) --------------------- +## 0.1.1b6 (2018-03-28) - fix autocomletion for top level commands [MrTango] -0.1.1b5 (2018-03-27) --------------------- +## 0.1.1b5 (2018-03-27) - use bobtemplates.plone>=3.0.0b5 [MrTango] -0.1.1b4 (2018-03-26) --------------------- +## 0.1.1b4 (2018-03-26) - Use a forked version of click library (plonecli-click) as dependency for now [MrTango] -0.1.1b3 (2018-03-23) --------------------- +## 0.1.1b3 (2018-03-23) - Fix broken release -0.1.1b2 (2018-03-22) --------------------- +## 0.1.1b2 (2018-03-22) - Fix dist on pypi -0.1.1b1 (2018-03-22) --------------------- +## 0.1.1b1 (2018-03-22) - Add requirements.txt referencing the special Click version. - This makes a ``pip`` installation possible. + This makes a `pip` installation possible. [jensens] - Refactored registry to use the new bobtemplate.cfg [MrTango] -0.1.0a4 (2017-10-30) --------------------- +## 0.1.0a4 (2017-10-30) - provide plonecli_autocomplete.sh for bash autocompletion [MrTango] @@ -238,22 +250,19 @@ History [MrTango] -0.1.0a3 (2017-10-24) --------------------- +## 0.1.0a3 (2017-10-24) - Update README to use easy_install instead of pip for now [MrTango] -0.1.0a2 (2017-10-24) --------------------- +## 0.1.0a2 (2017-10-24) - fix setup.py to use the github version of click, until click >6.7 is released [MrTango] -0.1.0a1 (2017-10-24) --------------------- +## 0.1.0a1 (2017-10-24) - initital version with list templates support and bobtemplates.plone integration [MrTango, tmassman, Gomez] diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..fa36d49 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,2 @@ +- use native uv, not uv pi or pip +- if editing of copier-templates is need, do it in dev path: develop/plone/src/copier-templates, not in the local copy in .copier-templates dir. diff --git a/CONTRIBUTING.rst b/CONTRIBUTING.md similarity index 55% rename from CONTRIBUTING.rst rename to CONTRIBUTING.md index d93653c..3414a2a 100644 --- a/CONTRIBUTING.rst +++ b/CONTRIBUTING.md @@ -1,19 +1,13 @@ -.. highlight:: shell - -============ -Contributing -============ +# Contributing Contributions are welcome, and they are greatly appreciated! Every little bit helps, and credit will always be given. You can contribute in many ways: -Types of Contributions ----------------------- +## Types of Contributions -Report Bugs -~~~~~~~~~~~ +### Report Bugs Report bugs at https://github.com/plone/plonecli/issues. @@ -23,27 +17,23 @@ If you are reporting a bug, please include: * Any details about your local setup that might be helpful in troubleshooting. * Detailed steps to reproduce the bug. -Fix Bugs -~~~~~~~~ +### Fix Bugs Look through the GitHub issues for bugs. Anything tagged with "bug" and "help wanted" is open to whoever wants to implement it. -Implement Features -~~~~~~~~~~~~~~~~~~ +### Implement Features Look through the GitHub issues for features. Anything tagged with "enhancement" and "help wanted" is open to whoever wants to implement it. -Write Documentation -~~~~~~~~~~~~~~~~~~~ +### Write Documentation Plone CLI could always use more documentation, whether as part of the official Plone CLI docs, in docstrings, or even on the web in blog posts, articles, and such. -Submit Feedback -~~~~~~~~~~~~~~~ +### Submit Feedback The best way to send feedback is to file an issue at https://github.com/plone/plonecli/issues. @@ -54,58 +44,66 @@ If you are proposing a feature: * Remember that this is a volunteer-driven project, and that contributions are welcome :) -Get Started! ------------- +## Get Started! Ready to contribute? Here's how to set up `plonecli` for local development. -1. Clone the repo locally:: +1. Clone the repo locally: - $ git clone git@github.com:plone/plonecli.git + ```shell + git clone git@github.com:plone/plonecli.git + ``` -2. Create a branch of the `plonecli` repo:: +2. Create a branch of the `plonecli` repo: - $ git checkout -b yourgithubuser_topic + ```shell + git checkout -b yourgithubuser_topic + ``` -3. Create a virtualenv and install your local copy.:: +3. Create a virtualenv and install your local copy: - $ python3 -m venv venv - $ ./venv/bin/pip install -e .[dev] + ```shell + python3 -m venv venv + ./venv/bin/pip install -e .[dev] + ``` - Now you can make your changes locally. + Now you can make your changes locally. -5. When you're done making changes, run test and linters with tox:: +4. When you're done making changes, run test and linters with tox: - $ tox + ```shell + tox + ``` -To get flake8 and tox, just pip install them into your virtualenv. + To get flake8 and tox, just pip install them into your virtualenv. -6. Commit your changes and push your branch to GitHub:: +5. Commit your changes and push your branch to GitHub: - $ git add . - $ git commit -m "Your detailed description of your changes." - $ git push + ```shell + git add . + git commit -m "Your detailed description of your changes." + git push + ``` -7. Submit a pull request through the GitHub website. +6. Submit a pull request through the GitHub website. -Pull Request Guidelines ------------------------ +## Pull Request Guidelines Before you submit a pull request, check that it meets these guidelines: -1. Update CHANGES.rst file +1. Update CHANGES.md file -Tips ----- +## Tips -To run a subset of tests:: +To run a subset of tests: -$ tox -l +```shell +tox -l py27 py37 py38 py39 flake8 -$ tox -e flake8 - +tox -e flake8 +``` diff --git a/README.md b/README.md new file mode 100644 index 0000000..87b42e8 --- /dev/null +++ b/README.md @@ -0,0 +1,432 @@ +[![CI](https://github.com/plone/plonecli/actions/workflows/python-package.yml/badge.svg)](https://github.com/plone/plonecli/actions/workflows/python-package.yml) +[![PyPI](https://img.shields.io/pypi/v/plonecli.svg)](https://pypi.python.org/pypi/plonecli/) + +# Plone CLI + +![Plone CLI Logo](https://raw.githubusercontent.com/plone/plonecli/master/docs/plone_cli_logo.svg) + +**A Plone CLI for creating Plone packages** + +The Plone CLI is meant for developing Plone packages. It uses [copier](https://copier.readthedocs.io/) templates to scaffold Plone backend addons, Zope project setups, and add features like content types, behaviors, and REST API services. + + +## Installation + +### UV Tool (Recommended) + +The recommended way to install plonecli is as a UV tool, which makes it available globally: + +```shell +uv tool install plonecli +``` + +To upgrade: + +```shell +uv tool upgrade plonecli +``` + +### Run Without Installing (uvx) + +You can run plonecli without installing it using `uvx`: + +```shell +uvx plonecli create addon my.addon +``` + +### In a Virtual Environment + +```shell +uv venv +source .venv/bin/activate +uv pip install plonecli +``` + +### With pipx + +```shell +pipx install plonecli +``` + + +## Shell Completion + +plonecli supports tab-completion for commands and template names in **bash**, **zsh**, and **fish**. + +### Quick Install + +```shell +plonecli completion --install +``` + +This auto-detects your shell and appends the activation line to your `~/.bashrc`, `~/.zshrc`, or fish completions directory. Restart your shell afterward. + +### Manual Setup + +If you prefer to set it up yourself: + +**Bash** (add to `~/.bashrc`): +```shell +eval "$(_PLONECLI_COMPLETE=bash_source plonecli)" +``` + +**Zsh** (add to `~/.zshrc`): +```shell +eval "$(_PLONECLI_COMPLETE=zsh_source plonecli)" +``` + +**Fish** (add to `~/.config/fish/completions/plonecli.fish`): +```shell +env _PLONECLI_COMPLETE=fish_source plonecli | source +``` + +### Faster Startup (Optional) + +The `eval` approach generates the completion script on every shell start. For faster startup, save it to a file: + +```shell +# Generate once +_PLONECLI_COMPLETE=bash_source plonecli > ~/.plonecli-complete.bash + +# Then source from your ~/.bashrc instead of eval +source ~/.plonecli-complete.bash +``` + + +## First Run + +On first run, plonecli will clone the copier-templates repository to `~/.copier-templates/plone-copier-templates`. + +Configure your author defaults: + +```shell +plonecli config +``` + +This creates `~/.plonecli/config.toml` with your settings. + + +## Usage + +### Available Commands + +```shell +plonecli --help + +Commands: + add Add features to your existing Plone package + config Configure plonecli global settings + create Create a new Plone package + debug Start the Plone instance in debug mode + serve Start the Plone instance + setup Run zope-setup inside an existing backend_addon + test Run the tests in your package + update Update copier-templates and check for plonecli updates + +Options: + -l, --list-templates List available templates + -V, --versions Show version information + -h, --help Show this message and exit. +``` + + +### Creating a Plone Add-on + +```shell +plonecli create addon collective.todo +``` + +Or create a Zope project setup: + +```shell +plonecli create zope-setup my-project +``` + + +### Adding Features to Your Plone Add-on + +Inside your addon directory, you can add features through subtemplates: + +```shell +cd collective.todo + +plonecli add content_type +plonecli add behavior +plonecli add restapi_service +``` + + +### Setting Up a Zope Project + +Inside an existing addon, set up the Zope project infrastructure: + +```shell +cd collective.todo +plonecli setup +``` + + +### Running Your Application + +```shell +plonecli serve +``` + +This delegates to `uv run invoke start` which is configured by the project templates. + + +### Running Tests + +```shell +plonecli test +``` + +With verbose output: + +```shell +plonecli test --verbose +``` + + +### Debug Mode + +```shell +plonecli debug +``` + + +### Updating Templates + +```shell +plonecli update +``` + +This pulls the latest copier-templates and checks PyPI for plonecli updates. + + +### Listing Templates + +```shell +plonecli -l + +Available templates: + + Project templates (plonecli create