Skip to content

Commit b146514

Browse files
TKaltofenTomKaltofen
authored andcommitted
feat: add bin/customize.sh and end-to-end scaffold CI test
Introduces bin/customize.sh as the single source of truth for the template rename (directory, pyproject.toml, .releaserc.yaml, imports) and a new scaffold-test workflow that copies the repo to a temp dir, runs the script, and runs tox against the renamed package. README, CLAUDE.md, and AGENTS.md are updated to point at the script instead of the previous 6-step manual walkthrough. Closes #46
1 parent eab9079 commit b146514

5 files changed

Lines changed: 219 additions & 36 deletions

File tree

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
name: Scaffold rename
2+
3+
on:
4+
push:
5+
branches: [main]
6+
pull_request:
7+
branches: [main]
8+
workflow_dispatch:
9+
10+
jobs:
11+
scaffold:
12+
runs-on: ubuntu-latest
13+
steps:
14+
- uses: actions/checkout@v4
15+
16+
- name: Set up Python 3.10
17+
uses: actions/setup-python@v5
18+
with:
19+
python-version: "3.10"
20+
21+
- name: Copy template to /tmp/scaffold
22+
run: |
23+
mkdir -p /tmp/scaffold
24+
rsync -a \
25+
--exclude='.git' \
26+
--exclude='.tox' \
27+
--exclude='.venv' \
28+
--exclude='__pycache__' \
29+
--exclude='*.egg-info' \
30+
./ /tmp/scaffold/
31+
32+
- name: Run bin/customize.sh against the copy
33+
working-directory: /tmp/scaffold
34+
run: |
35+
./bin/customize.sh acme \
36+
--author "Acme CI" \
37+
--email ci@example.com \
38+
--description "End-to-end scaffold test" \
39+
--repository-url https://github.com/acme/acme-my-plugin
40+
41+
- name: Install uv and tox
42+
run: |
43+
python -m pip install --upgrade pip
44+
python -m pip install uv
45+
uv tool install tox --with tox-uv
46+
47+
- name: Run tox on renamed scaffold
48+
working-directory: /tmp/scaffold
49+
run: tox
50+
timeout-minutes: 3

AGENTS.md

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,10 @@
44

55
If a `placeholder/` directory still exists in this repo, the template has not been initialized yet. Before doing any other work:
66

7-
1. Walk the user through `### Setup Your Plugin` in [README.md](README.md): rename `placeholder/` to their package name, update `pyproject.toml` (`name`, `authors`, `tool.setuptools.packages.find.include`, `tool.pytest.ini_options.testpaths`), update `.releaserc.yaml` (`message`, `repositoryUrl`), and replace `from placeholder.` imports in the 6 files listed in the README.
7+
1. Run `bin/customize.sh <package-name>` with appropriate `--author`, `--email`, `--description`, and `--repository-url` flags (see `### Setup Your Plugin` in [README.md](README.md) for details). This handles the directory rename, `pyproject.toml`/`.releaserc.yaml` edits, and `from placeholder.` import rewrites in one shot.
88
2. Verify with `uv venv && source .venv/bin/activate && uv sync --all-extras && tox`.
9-
3. **Delete this `## First-time setup` section from both `CLAUDE.md` and `AGENTS.md`** — it only applies to fresh-template repos and would be misleading once the placeholder is gone.
9+
3. Remove the template-only files: `rm CONTRIBUTING.md bin/customize.sh`.
10+
4. **Delete this `## First-time setup` section from both `CLAUDE.md` and `AGENTS.md`** — it only applies to fresh-template repos and would be misleading once the placeholder is gone.
1011

1112
Must read [README.md](README.md) first.
1213

CLAUDE.md

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,10 @@
44

55
If a `placeholder/` directory still exists in this repo, the template has not been initialized yet. Before doing any other work:
66

7-
1. Walk the user through `### Setup Your Plugin` in [README.md](README.md): rename `placeholder/` to their package name, update `pyproject.toml` (`name`, `authors`, `tool.setuptools.packages.find.include`, `tool.pytest.ini_options.testpaths`), update `.releaserc.yaml` (`message`, `repositoryUrl`), and replace `from placeholder.` imports in the 6 files listed in the README.
7+
1. Run `bin/customize.sh <package-name>` with appropriate `--author`, `--email`, `--description`, and `--repository-url` flags (see `### Setup Your Plugin` in [README.md](README.md) for details). This handles the directory rename, `pyproject.toml`/`.releaserc.yaml` edits, and `from placeholder.` import rewrites in one shot.
88
2. Verify with `uv venv && source .venv/bin/activate && uv sync --all-extras && tox`.
9-
3. **Delete this `## First-time setup` section from both `CLAUDE.md` and `AGENTS.md`** — it only applies to fresh-template repos and would be misleading once the placeholder is gone.
9+
3. Remove the template-only files: `rm CONTRIBUTING.md bin/customize.sh`.
10+
4. **Delete this `## First-time setup` section from both `CLAUDE.md` and `AGENTS.md`** — it only applies to fresh-template repos and would be misleading once the placeholder is gone.
1011

1112
Must read [README.md](README.md) first.
1213

README.md

Lines changed: 16 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -50,54 +50,38 @@ placeholder/
5050

5151
### Setup Your Plugin
5252

53-
#### 1. Rename the directory
53+
#### 1. Run the customization script
5454

5555
```bash
56-
mv placeholder acme
56+
./bin/customize.sh <your-package-name> \
57+
--author "Your Name" \
58+
--email you@example.com \
59+
--description "Your plugin description" \
60+
--repository-url https://github.com/<your-org>/<your-repo>
5761
```
5862

59-
#### 2. Update pyproject.toml
63+
This renames `placeholder/` to `<your-package-name>/`, updates `pyproject.toml` (`name`, `authors`, `description`, `packages.find.include`, `pytest.testpaths`), updates `.releaserc.yaml` (`message`, `repositoryUrl`), and rewrites `from placeholder.` imports across the package.
6064

61-
Edit the following fields in `pyproject.toml`:
65+
The package name must be a valid Python identifier (lowercase letters, digits, underscores; must start with a letter). All option flags are optional; if you omit them you can edit the corresponding fields by hand later.
6266

63-
- `name`: change `"placeholder-my-plugin"` to `"acme-my-plugin"`
64-
- `authors`: update name and email
65-
- `description`: update to describe your plugin
66-
- `tool.setuptools.packages.find.include`: change `["placeholder*"]` to `["acme*"]`
67-
- `tool.pytest.ini_options.testpaths`: change `["placeholder", "tests"]` to `["acme", "tests"]`
68-
69-
#### 3. Update .releaserc.yaml
70-
71-
Edit the following fields in `.releaserc.yaml`:
72-
73-
- `message`: change `mloda-plugin-template` to your package name (e.g., `"chore(release acme-my-plugin): ${nextRelease.version}"`)
74-
- `repositoryUrl`: change to your repository URL
75-
76-
#### 4. Update Python imports
77-
78-
Update imports in these files (change `from placeholder.` to `from acme.`):
79-
80-
- `acme/feature_groups/my_plugin/__init__.py`
81-
- `acme/feature_groups/my_plugin/tests/test_my_feature_group.py`
82-
- `acme/compute_frameworks/my_plugin/__init__.py`
83-
- `acme/compute_frameworks/my_plugin/tests/test_my_compute_framework.py`
84-
- `acme/extenders/my_plugin/__init__.py`
85-
- `acme/extenders/my_plugin/tests/test_my_extender.py`
86-
87-
#### 5. Verify setup
67+
#### 2. Verify setup
8868

8969
```bash
9070
uv venv && source .venv/bin/activate && uv sync --all-extras && tox
9171
```
9272

93-
#### 6. Remove the template-only contributor guide
73+
#### 3. Remove template-only files
9474

95-
`CONTRIBUTING.md` describes how to contribute to the template repo itself; it does not apply to your plugin. Remove it after `tox` passes:
75+
After `tox` passes, remove the files that only exist to support the template itself:
9676

9777
```bash
98-
rm CONTRIBUTING.md
78+
rm CONTRIBUTING.md bin/customize.sh
9979
```
10080

81+
`CONTRIBUTING.md` describes how to contribute to the template repo; `bin/customize.sh` is a one-shot scaffold script that has nothing left to do.
82+
83+
Also delete the `## First-time setup` section from `CLAUDE.md` and `AGENTS.md` — those instructions only apply to fresh-template repos.
84+
10185
The remaining baseline files apply to your plugin out of the box and can be edited to match your conventions:
10286

10387
- `AGENTS.md` and `CLAUDE.md` — toolchain and project practices for the same `tox`/ruff/mypy/bandit pipeline you inherit. Tune the bullets if you change the toolchain.

bin/customize.sh

Lines changed: 147 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,147 @@
1+
#!/usr/bin/env bash
2+
# Rename the placeholder scaffold to your plugin's package name.
3+
# Run once on a fresh clone of the template, then delete this script.
4+
set -euo pipefail
5+
6+
usage() {
7+
cat <<'EOF'
8+
Usage: bin/customize.sh <package-name> [options]
9+
10+
Renames the placeholder/ package to <package-name> and updates all
11+
references in pyproject.toml, .releaserc.yaml, and Python imports.
12+
13+
Arguments:
14+
<package-name> Lowercase Python identifier (e.g. acme, my_org)
15+
16+
Options:
17+
--author "Your Name" Set authors[].name in pyproject.toml
18+
--email you@example.com Set authors[].email in pyproject.toml
19+
--description "..." Set project.description in pyproject.toml
20+
(must not contain the '|' character)
21+
--repository-url URL Set repositoryUrl in .releaserc.yaml
22+
-h, --help Show this message
23+
24+
After running, verify with:
25+
uv venv && source .venv/bin/activate && uv sync --all-extras && tox
26+
EOF
27+
}
28+
29+
if [[ $# -eq 0 ]]; then
30+
usage
31+
exit 2
32+
fi
33+
34+
PACKAGE=""
35+
AUTHOR=""
36+
EMAIL=""
37+
DESCRIPTION=""
38+
REPOSITORY_URL=""
39+
40+
while [[ $# -gt 0 ]]; do
41+
case "$1" in
42+
-h|--help) usage; exit 0 ;;
43+
--author) AUTHOR="${2:-}"; shift 2 ;;
44+
--email) EMAIL="${2:-}"; shift 2 ;;
45+
--description) DESCRIPTION="${2:-}"; shift 2 ;;
46+
--repository-url) REPOSITORY_URL="${2:-}"; shift 2 ;;
47+
--*) echo "Error: unknown option: $1" >&2; usage >&2; exit 2 ;;
48+
*)
49+
if [[ -z "$PACKAGE" ]]; then
50+
PACKAGE="$1"
51+
shift
52+
else
53+
echo "Error: unexpected positional argument: $1" >&2
54+
usage >&2
55+
exit 2
56+
fi
57+
;;
58+
esac
59+
done
60+
61+
if [[ -z "$PACKAGE" ]]; then
62+
echo "Error: <package-name> is required." >&2
63+
usage >&2
64+
exit 2
65+
fi
66+
67+
if [[ "$PACKAGE" == "placeholder" ]]; then
68+
echo "Error: package name cannot be 'placeholder'." >&2
69+
exit 2
70+
fi
71+
72+
if ! [[ "$PACKAGE" =~ ^[a-z][a-z0-9_]*$ ]]; then
73+
echo "Error: package name must be lowercase letters/digits/underscores, starting with a letter." >&2
74+
echo "Got: $PACKAGE" >&2
75+
exit 2
76+
fi
77+
78+
if [[ "$DESCRIPTION" == *"|"* ]]; then
79+
echo "Error: --description must not contain the '|' character." >&2
80+
exit 2
81+
fi
82+
83+
REPO_ROOT="$(cd "$(dirname "$0")/.." && pwd)"
84+
cd "$REPO_ROOT"
85+
86+
if [[ ! -d placeholder ]]; then
87+
echo "Error: placeholder/ directory not found at $REPO_ROOT." >&2
88+
echo "This template appears to be already customized (or you ran the script from the wrong directory)." >&2
89+
exit 1
90+
fi
91+
92+
# Portable in-place sed: works on both GNU and BSD sed.
93+
sed_inplace() {
94+
local pattern="$1"
95+
local file="$2"
96+
sed -i.bak "$pattern" "$file"
97+
rm -f "${file}.bak"
98+
}
99+
100+
echo "==> Renaming placeholder/ to ${PACKAGE}/"
101+
mv placeholder "$PACKAGE"
102+
103+
echo "==> Updating pyproject.toml"
104+
sed_inplace "s/^name = \"placeholder-my-plugin\"\$/name = \"${PACKAGE}-my-plugin\"/" pyproject.toml
105+
sed_inplace "s/^include = \[\"placeholder\\*\"\]\$/include = [\"${PACKAGE}*\"]/" pyproject.toml
106+
sed_inplace "s/^testpaths = \[\"placeholder\", \"tests\"\]\$/testpaths = [\"${PACKAGE}\", \"tests\"]/" pyproject.toml
107+
108+
if [[ -n "$AUTHOR" || -n "$EMAIL" ]]; then
109+
AUTHOR_NAME="${AUTHOR:-Your Name placeholder}"
110+
AUTHOR_EMAIL="${EMAIL:-placeholder@placeholder.com}"
111+
sed_inplace "s|^authors = .*|authors = [{ name = \"${AUTHOR_NAME}\", email = \"${AUTHOR_EMAIL}\" }]|" pyproject.toml
112+
fi
113+
114+
if [[ -n "$DESCRIPTION" ]]; then
115+
sed_inplace "s|^description = .*|description = \"${DESCRIPTION}\"|" pyproject.toml
116+
fi
117+
118+
echo "==> Updating .releaserc.yaml"
119+
sed_inplace "s|chore(release mloda-plugin-template):|chore(release ${PACKAGE}-my-plugin):|" .releaserc.yaml
120+
if [[ -n "$REPOSITORY_URL" ]]; then
121+
sed_inplace "s|^repositoryUrl: .*|repositoryUrl: \"${REPOSITORY_URL}\"|" .releaserc.yaml
122+
fi
123+
124+
echo "==> Updating Python imports under ${PACKAGE}/"
125+
find "$PACKAGE" -type f -name '*.py' -print0 | while IFS= read -r -d '' f; do
126+
sed_inplace "s/from placeholder\\./from ${PACKAGE}./g; s/import placeholder\\./import ${PACKAGE}./g" "$f"
127+
done
128+
129+
echo "==> Checking for stale 'placeholder' references"
130+
STALE="$(grep -rn 'placeholder' "$PACKAGE" pyproject.toml .releaserc.yaml || true)"
131+
if [[ -n "$STALE" ]]; then
132+
echo "Error: stale 'placeholder' references found in customized files:" >&2
133+
printf '%s\n' "$STALE" >&2
134+
exit 1
135+
fi
136+
137+
cat <<EOF
138+
139+
Customization complete.
140+
141+
Next steps:
142+
1. Verify the build:
143+
uv venv && source .venv/bin/activate && uv sync --all-extras && tox
144+
2. Remove template-only files:
145+
rm CONTRIBUTING.md bin/customize.sh
146+
3. Delete the '## First-time setup' section from CLAUDE.md and AGENTS.md.
147+
EOF

0 commit comments

Comments
 (0)