plone-codemod 0.1.0__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.
- plone_codemod-0.1.0/.github/workflows/ci.yaml +21 -0
- plone_codemod-0.1.0/.github/workflows/qa.yaml +37 -0
- plone_codemod-0.1.0/.github/workflows/release.yaml +80 -0
- plone_codemod-0.1.0/.github/workflows/tests.yaml +34 -0
- plone_codemod-0.1.0/.gitignore +13 -0
- plone_codemod-0.1.0/.pre-commit-config.yaml +24 -0
- plone_codemod-0.1.0/PKG-INFO +274 -0
- plone_codemod-0.1.0/README.md +247 -0
- plone_codemod-0.1.0/pyproject.toml +95 -0
- plone_codemod-0.1.0/src/plone_codemod/__init__.py +1 -0
- plone_codemod-0.1.0/src/plone_codemod/cli.py +274 -0
- plone_codemod-0.1.0/src/plone_codemod/import_migrator.py +264 -0
- plone_codemod-0.1.0/src/plone_codemod/migration_config.yaml +688 -0
- plone_codemod-0.1.0/src/plone_codemod/pt_migrator.py +116 -0
- plone_codemod-0.1.0/src/plone_codemod/semgrep_rules/plone6_deprecated.yaml +367 -0
- plone_codemod-0.1.0/src/plone_codemod/zcml_migrator.py +157 -0
- plone_codemod-0.1.0/tests/__init__.py +0 -0
- plone_codemod-0.1.0/tests/test_import_migrator.py +273 -0
- plone_codemod-0.1.0/tests/test_pt_migrator.py +222 -0
- plone_codemod-0.1.0/tests/test_zcml_migrator.py +191 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: CI
|
|
3
|
+
|
|
4
|
+
on:
|
|
5
|
+
push:
|
|
6
|
+
branches-ignore: [main]
|
|
7
|
+
pull_request:
|
|
8
|
+
|
|
9
|
+
permissions:
|
|
10
|
+
contents: read
|
|
11
|
+
|
|
12
|
+
concurrency:
|
|
13
|
+
group: ci-${{ github.ref }}
|
|
14
|
+
cancel-in-progress: true
|
|
15
|
+
|
|
16
|
+
jobs:
|
|
17
|
+
qa:
|
|
18
|
+
uses: "./.github/workflows/qa.yaml"
|
|
19
|
+
|
|
20
|
+
tests:
|
|
21
|
+
uses: "./.github/workflows/tests.yaml"
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: QA
|
|
3
|
+
|
|
4
|
+
on:
|
|
5
|
+
workflow_call:
|
|
6
|
+
|
|
7
|
+
permissions:
|
|
8
|
+
contents: read
|
|
9
|
+
|
|
10
|
+
jobs:
|
|
11
|
+
lint:
|
|
12
|
+
name: Ruff
|
|
13
|
+
runs-on: ubuntu-latest
|
|
14
|
+
steps:
|
|
15
|
+
- uses: actions/checkout@v4
|
|
16
|
+
|
|
17
|
+
- uses: astral-sh/setup-uv@v6
|
|
18
|
+
|
|
19
|
+
- name: Run ruff check
|
|
20
|
+
run: uvx ruff check .
|
|
21
|
+
|
|
22
|
+
- name: Run ruff format check
|
|
23
|
+
run: uvx ruff format --check .
|
|
24
|
+
|
|
25
|
+
typecheck:
|
|
26
|
+
name: ty
|
|
27
|
+
runs-on: ubuntu-latest
|
|
28
|
+
steps:
|
|
29
|
+
- uses: actions/checkout@v4
|
|
30
|
+
|
|
31
|
+
- uses: astral-sh/setup-uv@v6
|
|
32
|
+
|
|
33
|
+
- name: Install dependencies
|
|
34
|
+
run: uv venv && uv pip install -e ".[dev]"
|
|
35
|
+
|
|
36
|
+
- name: Run ty type checker
|
|
37
|
+
run: uvx ty check --output-format github
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: Build & upload PyPI package
|
|
3
|
+
|
|
4
|
+
on:
|
|
5
|
+
push:
|
|
6
|
+
branches: [main]
|
|
7
|
+
release:
|
|
8
|
+
types:
|
|
9
|
+
- published
|
|
10
|
+
workflow_dispatch:
|
|
11
|
+
|
|
12
|
+
concurrency:
|
|
13
|
+
group: release-${{ github.ref }}
|
|
14
|
+
cancel-in-progress: true
|
|
15
|
+
|
|
16
|
+
jobs:
|
|
17
|
+
qa:
|
|
18
|
+
uses: "./.github/workflows/qa.yaml"
|
|
19
|
+
|
|
20
|
+
tests:
|
|
21
|
+
uses: "./.github/workflows/tests.yaml"
|
|
22
|
+
|
|
23
|
+
build-package:
|
|
24
|
+
name: Build & verify package
|
|
25
|
+
needs:
|
|
26
|
+
- qa
|
|
27
|
+
- tests
|
|
28
|
+
runs-on: ubuntu-latest
|
|
29
|
+
permissions:
|
|
30
|
+
contents: read
|
|
31
|
+
|
|
32
|
+
steps:
|
|
33
|
+
- uses: actions/checkout@v4
|
|
34
|
+
with:
|
|
35
|
+
fetch-depth: 0
|
|
36
|
+
persist-credentials: false
|
|
37
|
+
|
|
38
|
+
- uses: hynek/build-and-inspect-python-package@v2
|
|
39
|
+
|
|
40
|
+
release-test-pypi:
|
|
41
|
+
name: Publish in-dev package to test.pypi.org
|
|
42
|
+
environment: release-test-pypi
|
|
43
|
+
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
|
|
44
|
+
runs-on: ubuntu-latest
|
|
45
|
+
needs:
|
|
46
|
+
- build-package
|
|
47
|
+
permissions:
|
|
48
|
+
id-token: write
|
|
49
|
+
|
|
50
|
+
steps:
|
|
51
|
+
- name: Download packages built by build-and-inspect-python-package
|
|
52
|
+
uses: actions/download-artifact@v4
|
|
53
|
+
with:
|
|
54
|
+
name: Packages
|
|
55
|
+
path: dist
|
|
56
|
+
|
|
57
|
+
- name: Upload package to Test PyPI
|
|
58
|
+
uses: pypa/gh-action-pypi-publish@release/v1
|
|
59
|
+
with:
|
|
60
|
+
repository-url: https://test.pypi.org/legacy/
|
|
61
|
+
|
|
62
|
+
release-pypi:
|
|
63
|
+
name: Publish released package to pypi.org
|
|
64
|
+
environment: release-pypi
|
|
65
|
+
if: github.event.action == 'published'
|
|
66
|
+
runs-on: ubuntu-latest
|
|
67
|
+
needs:
|
|
68
|
+
- build-package
|
|
69
|
+
permissions:
|
|
70
|
+
id-token: write
|
|
71
|
+
|
|
72
|
+
steps:
|
|
73
|
+
- name: Download packages built by build-and-inspect-python-package
|
|
74
|
+
uses: actions/download-artifact@v4
|
|
75
|
+
with:
|
|
76
|
+
name: Packages
|
|
77
|
+
path: dist
|
|
78
|
+
|
|
79
|
+
- name: Upload package to PyPI
|
|
80
|
+
uses: pypa/gh-action-pypi-publish@release/v1
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: Tests
|
|
3
|
+
|
|
4
|
+
on:
|
|
5
|
+
workflow_call:
|
|
6
|
+
|
|
7
|
+
permissions:
|
|
8
|
+
contents: read
|
|
9
|
+
|
|
10
|
+
jobs:
|
|
11
|
+
test:
|
|
12
|
+
name: Python ${{ matrix.python-version }}
|
|
13
|
+
runs-on: ubuntu-latest
|
|
14
|
+
strategy:
|
|
15
|
+
fail-fast: false
|
|
16
|
+
matrix:
|
|
17
|
+
python-version: ["3.12", "3.13", "3.14"]
|
|
18
|
+
|
|
19
|
+
steps:
|
|
20
|
+
- uses: actions/checkout@v4
|
|
21
|
+
|
|
22
|
+
- uses: astral-sh/setup-uv@v6
|
|
23
|
+
with:
|
|
24
|
+
enable-cache: true
|
|
25
|
+
cache-dependency-glob: "pyproject.toml"
|
|
26
|
+
|
|
27
|
+
- name: Set up Python ${{ matrix.python-version }}
|
|
28
|
+
run: uv python install ${{ matrix.python-version }}
|
|
29
|
+
|
|
30
|
+
- name: Install dependencies
|
|
31
|
+
run: uv venv && uv pip install -e ".[dev]"
|
|
32
|
+
|
|
33
|
+
- name: Run tests
|
|
34
|
+
run: uv run pytest -v
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
repos:
|
|
2
|
+
- repo: https://github.com/astral-sh/ruff-pre-commit
|
|
3
|
+
rev: v0.15.0
|
|
4
|
+
hooks:
|
|
5
|
+
- id: ruff
|
|
6
|
+
args: [--fix]
|
|
7
|
+
- id: ruff-format
|
|
8
|
+
|
|
9
|
+
- repo: https://github.com/pre-commit/pre-commit-hooks
|
|
10
|
+
rev: v5.0.0
|
|
11
|
+
hooks:
|
|
12
|
+
- id: trailing-whitespace
|
|
13
|
+
- id: end-of-file-fixer
|
|
14
|
+
- id: check-yaml
|
|
15
|
+
- id: check-added-large-files
|
|
16
|
+
|
|
17
|
+
- repo: local
|
|
18
|
+
hooks:
|
|
19
|
+
- id: ty
|
|
20
|
+
name: ty type checker
|
|
21
|
+
entry: uvx ty check
|
|
22
|
+
language: system
|
|
23
|
+
types: [python]
|
|
24
|
+
pass_filenames: false
|
|
@@ -0,0 +1,274 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: plone-codemod
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Automated code migration tool for Plone 5.2 → 6.x upgrades
|
|
5
|
+
Project-URL: Homepage, https://github.com/bluedynamics/plone-codemod
|
|
6
|
+
Project-URL: Repository, https://github.com/bluedynamics/plone-codemod
|
|
7
|
+
Project-URL: Issues, https://github.com/bluedynamics/plone-codemod/issues
|
|
8
|
+
License-Expression: GPL-2.0-only
|
|
9
|
+
Classifier: Development Status :: 4 - Beta
|
|
10
|
+
Classifier: Framework :: Plone
|
|
11
|
+
Classifier: Intended Audience :: Developers
|
|
12
|
+
Classifier: License :: OSI Approved :: GNU General Public License v2 (GPLv2)
|
|
13
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
17
|
+
Classifier: Topic :: Software Development :: Code Generators
|
|
18
|
+
Requires-Python: >=3.12
|
|
19
|
+
Requires-Dist: libcst>=1.0
|
|
20
|
+
Requires-Dist: pyyaml>=6.0
|
|
21
|
+
Requires-Dist: semgrep
|
|
22
|
+
Provides-Extra: dev
|
|
23
|
+
Requires-Dist: coverage[toml]>=7.0; extra == 'dev'
|
|
24
|
+
Requires-Dist: pytest; extra == 'dev'
|
|
25
|
+
Requires-Dist: pytest-cov; extra == 'dev'
|
|
26
|
+
Description-Content-Type: text/markdown
|
|
27
|
+
|
|
28
|
+
# plone-codemod
|
|
29
|
+
|
|
30
|
+
Automated code migration tool for upgrading Plone add-ons and projects from Plone 5.2 to Plone 6.x.
|
|
31
|
+
|
|
32
|
+
Unlike simple `sed`/`find` scripts, plone-codemod uses [libcst](https://github.com/Instagram/LibCST) (a concrete syntax tree parser) to correctly handle multi-line imports, aliased imports, mixed imports, and scoped usage-site renaming.
|
|
33
|
+
|
|
34
|
+
## What it does
|
|
35
|
+
|
|
36
|
+
**Python files** (libcst-based, AST-aware):
|
|
37
|
+
- Rewrites 129+ import paths (`Products.CMFPlone.*` → `plone.base.*`, etc.)
|
|
38
|
+
- Renames functions at usage sites (`safe_unicode()` → `safe_text()`, `getNavigationRoot()` → `get_navigation_root()`, etc.)
|
|
39
|
+
- Splits mixed imports when names move to different modules
|
|
40
|
+
- Preserves aliases, comments, and formatting
|
|
41
|
+
|
|
42
|
+
**ZCML files** (string replacement):
|
|
43
|
+
- Updates dotted names in `class=`, `for=`, `provides=`, `interface=` and other attributes
|
|
44
|
+
|
|
45
|
+
**GenericSetup XML** (string replacement):
|
|
46
|
+
- Updates interface references in `registry.xml` and profile XML
|
|
47
|
+
- Replaces removed view names (`folder_summary_view` → `folder_listing`, etc.)
|
|
48
|
+
|
|
49
|
+
**Page templates** (string replacement):
|
|
50
|
+
- `context/main_template` → `context/@@main_template` (acquisition → browser view)
|
|
51
|
+
- `here/` → `context/` (deprecated alias)
|
|
52
|
+
- `prefs_main_template` → `@@prefs_main_template`
|
|
53
|
+
|
|
54
|
+
**Bootstrap 3 → 5** (opt-in via `--bootstrap`):
|
|
55
|
+
- `data-toggle` → `data-bs-toggle` (and 17 other data attributes)
|
|
56
|
+
- CSS class renames: `pull-right` → `float-end`, `panel` → `card`, `btn-default` → `btn-secondary`, etc.
|
|
57
|
+
- Plone-specific overrides: `plone-btn` → `btn`, etc.
|
|
58
|
+
|
|
59
|
+
**Audit** (semgrep, optional):
|
|
60
|
+
- 35+ rules to detect deprecated imports, removed skin scripts, portal_properties usage, Bootstrap 3 patterns, and more
|
|
61
|
+
- Use in CI to prevent regressions
|
|
62
|
+
|
|
63
|
+
## Installation
|
|
64
|
+
|
|
65
|
+
```bash
|
|
66
|
+
pip install plone-codemod
|
|
67
|
+
|
|
68
|
+
# Or with uv
|
|
69
|
+
uv pip install plone-codemod
|
|
70
|
+
|
|
71
|
+
# Optional: for audit phase
|
|
72
|
+
pip install plone-codemod[audit]
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
## Usage
|
|
76
|
+
|
|
77
|
+
```bash
|
|
78
|
+
# Preview what would change (no files modified)
|
|
79
|
+
plone-codemod /path/to/your/src/ --dry-run
|
|
80
|
+
|
|
81
|
+
# Apply all migrations (without Bootstrap)
|
|
82
|
+
plone-codemod /path/to/your/src/
|
|
83
|
+
|
|
84
|
+
# Include Bootstrap 3→5 migration
|
|
85
|
+
plone-codemod /path/to/your/src/ --bootstrap
|
|
86
|
+
|
|
87
|
+
# Preview Bootstrap changes
|
|
88
|
+
plone-codemod /path/to/your/src/ --bootstrap --dry-run
|
|
89
|
+
|
|
90
|
+
# Run only specific phases
|
|
91
|
+
plone-codemod /path/to/your/src/ --skip-python # ZCML + XML + PT only
|
|
92
|
+
plone-codemod /path/to/your/src/ --skip-zcml # Python + XML + PT only
|
|
93
|
+
plone-codemod /path/to/your/src/ --skip-pt # Skip page templates
|
|
94
|
+
plone-codemod /path/to/your/src/ --skip-audit # Skip semgrep audit
|
|
95
|
+
|
|
96
|
+
# Use a custom config
|
|
97
|
+
plone-codemod /path/to/your/src/ --config my_config.yaml
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
After running, review changes with `git diff` and commit.
|
|
101
|
+
|
|
102
|
+
## How it works
|
|
103
|
+
|
|
104
|
+
### Phase 1: Python imports (libcst)
|
|
105
|
+
|
|
106
|
+
The codemod reads `migration_config.yaml` and rewrites import statements using libcst's concrete syntax tree. This means it correctly handles cases that `sed` cannot:
|
|
107
|
+
|
|
108
|
+
```python
|
|
109
|
+
# Multi-line imports
|
|
110
|
+
from Products.CMFPlone.utils import (
|
|
111
|
+
safe_unicode, # → safe_text
|
|
112
|
+
base_hasattr, # stays, module path updated
|
|
113
|
+
)
|
|
114
|
+
|
|
115
|
+
# Aliased imports (alias preserved)
|
|
116
|
+
from Products.CMFPlone.utils import safe_unicode as su
|
|
117
|
+
# → from plone.base.utils import safe_text as su
|
|
118
|
+
|
|
119
|
+
# Mixed imports split when destinations differ
|
|
120
|
+
from Products.CMFPlone.utils import safe_unicode, directlyProvides
|
|
121
|
+
# → from plone.base.utils import safe_text
|
|
122
|
+
# → from zope.interface import directlyProvides
|
|
123
|
+
|
|
124
|
+
# Usage sites renamed only when imported from the old module
|
|
125
|
+
text = safe_unicode(value) # → safe_text(value)
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
### Phase 2: ZCML migration
|
|
129
|
+
|
|
130
|
+
String replacement of dotted names in `.zcml` files:
|
|
131
|
+
|
|
132
|
+
```xml
|
|
133
|
+
<!-- Before -->
|
|
134
|
+
<browser:page for="plone.app.layout.navigation.interfaces.INavigationRoot" />
|
|
135
|
+
|
|
136
|
+
<!-- After -->
|
|
137
|
+
<browser:page for="plone.base.interfaces.siteroot.INavigationRoot" />
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
### Phase 3: GenericSetup XML
|
|
141
|
+
|
|
142
|
+
Updates `registry.xml` and type profile XML files:
|
|
143
|
+
|
|
144
|
+
```xml
|
|
145
|
+
<!-- Before -->
|
|
146
|
+
<records interface="Products.CMFPlone.interfaces.controlpanel.IEditingSchema">
|
|
147
|
+
<property name="default_view">folder_summary_view</property>
|
|
148
|
+
|
|
149
|
+
<!-- After -->
|
|
150
|
+
<records interface="plone.base.interfaces.controlpanel.IEditingSchema">
|
|
151
|
+
<property name="default_view">folder_listing</property>
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
### Phase 4: Page templates
|
|
155
|
+
|
|
156
|
+
Safe automated fixes for `.pt` files:
|
|
157
|
+
|
|
158
|
+
```xml
|
|
159
|
+
<!-- Before -->
|
|
160
|
+
<html metal:use-macro="context/main_template/macros/master">
|
|
161
|
+
<div tal:define="x here/title">
|
|
162
|
+
|
|
163
|
+
<!-- After -->
|
|
164
|
+
<html metal:use-macro="context/@@main_template/macros/master">
|
|
165
|
+
<div tal:define="x context/title">
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
### Phase 5: Bootstrap 3 → 5 (opt-in)
|
|
169
|
+
|
|
170
|
+
Only runs when `--bootstrap` is passed. Handles data attributes and CSS classes:
|
|
171
|
+
|
|
172
|
+
```html
|
|
173
|
+
<!-- Before -->
|
|
174
|
+
<button data-toggle="modal" data-target="#m" class="btn btn-default pull-right">
|
|
175
|
+
<div class="panel panel-default"><div class="panel-body">...</div></div>
|
|
176
|
+
|
|
177
|
+
<!-- After -->
|
|
178
|
+
<button data-bs-toggle="modal" data-bs-target="#m" class="btn btn-secondary float-end">
|
|
179
|
+
<div class="card"><div class="card-body">...</div></div>
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
Bootstrap migration is opt-in because some projects intentionally keep Bootstrap 3 for parts of their UI.
|
|
183
|
+
|
|
184
|
+
### Phase 6: Audit (optional)
|
|
185
|
+
|
|
186
|
+
Runs semgrep rules to detect issues that need manual attention:
|
|
187
|
+
|
|
188
|
+
```bash
|
|
189
|
+
# Standalone semgrep usage
|
|
190
|
+
semgrep --config semgrep_rules/ /path/to/your/src/
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
Detects: deprecated imports, removed skin scripts (`queryCatalog`, `getFolderContents`, `pretty_title_or_id`), `portal_properties` usage, `checkPermission` builtin in templates, `getIcon`, `normalizeString`, glyphicons, Bootstrap 3 patterns, and more.
|
|
194
|
+
|
|
195
|
+
## Migration config
|
|
196
|
+
|
|
197
|
+
All mappings live in `migration_config.yaml`. To add a new migration rule, add an entry:
|
|
198
|
+
|
|
199
|
+
```yaml
|
|
200
|
+
imports:
|
|
201
|
+
- old: old.module.path.OldName
|
|
202
|
+
new: new.module.path.NewName
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
The tool splits on the last `.` to determine module vs name.
|
|
206
|
+
|
|
207
|
+
### Coverage
|
|
208
|
+
|
|
209
|
+
| Category | Count |
|
|
210
|
+
|----------|-------|
|
|
211
|
+
| `Products.CMFPlone.utils` → `plone.base.utils` | 18 functions |
|
|
212
|
+
| `Products.CMFPlone.interfaces` → `plone.base.interfaces` | 60+ interfaces |
|
|
213
|
+
| Control panel interfaces | 20+ |
|
|
214
|
+
| TinyMCE interfaces | 5 |
|
|
215
|
+
| Navigation root functions | 3 |
|
|
216
|
+
| Syndication interfaces | 4 |
|
|
217
|
+
| `plone.dexterity.utils` → `plone.dexterity.schema` | 4 |
|
|
218
|
+
| Message factory, batch, permissions, defaultpage, i18n | 10+ |
|
|
219
|
+
| Special case: `ILanguageSchema` → `plone.i18n` | 1 |
|
|
220
|
+
| Page template patterns | 5 |
|
|
221
|
+
| Bootstrap data attributes | 17 |
|
|
222
|
+
| Bootstrap CSS class renames | 30+ |
|
|
223
|
+
|
|
224
|
+
### What it does NOT cover (manual migration needed)
|
|
225
|
+
|
|
226
|
+
- **Archetypes removal** — AT content types must be migrated to Dexterity before upgrading
|
|
227
|
+
- **`getFolderContents()`** → `restrictedTraverse("@@contentlisting")()` (method call rewrite, flagged by semgrep)
|
|
228
|
+
- **`queryCatalog`** removal (flagged by semgrep)
|
|
229
|
+
- **`portal_properties`** removal (flagged by semgrep — needs registry migration)
|
|
230
|
+
- **Removed skin scripts** in TAL expressions (flagged by semgrep)
|
|
231
|
+
- **`getViewTemplateId`** deprecation (flagged by semgrep)
|
|
232
|
+
- **Buildout → pip/mxdev** migration (different config format)
|
|
233
|
+
- **Python 2 cleanup** (`six`, `__future__`, `u""` strings) — use [pyupgrade](https://github.com/asottile/pyupgrade) for this
|
|
234
|
+
- **Resource registry / LESS** changes (complete rewrite needed)
|
|
235
|
+
- **Glyphicons** → Bootstrap Icons / SVG (flagged by semgrep)
|
|
236
|
+
- **Dynamic imports** (`importlib.import_module("Products.CMFPlone.utils")`)
|
|
237
|
+
|
|
238
|
+
## Development
|
|
239
|
+
|
|
240
|
+
```bash
|
|
241
|
+
# Clone and install in dev mode
|
|
242
|
+
git clone https://github.com/bluedynamics/plone-codemod.git
|
|
243
|
+
cd plone-codemod
|
|
244
|
+
uv venv && uv pip install -e ".[dev]"
|
|
245
|
+
|
|
246
|
+
# Run tests
|
|
247
|
+
uv run pytest tests/ -v
|
|
248
|
+
|
|
249
|
+
# Lint
|
|
250
|
+
uvx ruff check .
|
|
251
|
+
uvx ruff format --check .
|
|
252
|
+
```
|
|
253
|
+
|
|
254
|
+
## Architecture
|
|
255
|
+
|
|
256
|
+
```
|
|
257
|
+
plone-codemod/
|
|
258
|
+
src/plone_codemod/
|
|
259
|
+
cli.py # Orchestrator with --dry-run, --bootstrap
|
|
260
|
+
import_migrator.py # libcst codemod: Python imports + usage sites
|
|
261
|
+
zcml_migrator.py # ZCML + GenericSetup XML transformer
|
|
262
|
+
pt_migrator.py # Page template + Bootstrap migrator
|
|
263
|
+
migration_config.yaml # Declarative old→new mapping (YAML)
|
|
264
|
+
semgrep_rules/
|
|
265
|
+
plone6_deprecated.yaml # 35+ audit/detection rules
|
|
266
|
+
tests/
|
|
267
|
+
test_import_migrator.py # 32 tests for Python migration
|
|
268
|
+
test_zcml_migrator.py # 17 tests for ZCML/XML migration
|
|
269
|
+
test_pt_migrator.py # 24 tests for PT + Bootstrap migration
|
|
270
|
+
```
|
|
271
|
+
|
|
272
|
+
## License
|
|
273
|
+
|
|
274
|
+
GPL-2.0 — same as Plone.
|