odoo-dev 1.1.0__tar.gz → 1.2.1__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 (66) hide show
  1. odoo_dev-1.2.1/.release-please-manifest.json +3 -0
  2. odoo_dev-1.2.1/CHANGELOG.md +60 -0
  3. {odoo_dev-1.1.0 → odoo_dev-1.2.1}/PKG-INFO +55 -3
  4. {odoo_dev-1.1.0 → odoo_dev-1.2.1}/README.md +54 -2
  5. {odoo_dev-1.1.0 → odoo_dev-1.2.1}/pyproject.toml +1 -1
  6. odoo_dev-1.2.1/src/odoo_dev/__init__.py +3 -0
  7. {odoo_dev-1.1.0 → odoo_dev-1.2.1}/src/odoo_dev/cli.py +2 -0
  8. {odoo_dev-1.1.0 → odoo_dev-1.2.1}/src/odoo_dev/commands/run.py +53 -12
  9. {odoo_dev-1.1.0 → odoo_dev-1.2.1}/src/odoo_dev/commands/setup.py +80 -20
  10. odoo_dev-1.2.1/src/odoo_dev/commands/vendor.py +287 -0
  11. {odoo_dev-1.1.0 → odoo_dev-1.2.1}/src/odoo_dev/config.py +10 -0
  12. odoo_dev-1.2.1/src/odoo_dev/vendor/__init__.py +10 -0
  13. odoo_dev-1.2.1/src/odoo_dev/vendor/develop.py +321 -0
  14. odoo_dev-1.2.1/src/odoo_dev/vendor/edit.py +91 -0
  15. odoo_dev-1.2.1/src/odoo_dev/vendor/lock.py +86 -0
  16. odoo_dev-1.2.1/src/odoo_dev/vendor/materialize.py +130 -0
  17. odoo_dev-1.2.1/src/odoo_dev/vendor/migrate.py +208 -0
  18. odoo_dev-1.2.1/src/odoo_dev/vendor/sources.py +146 -0
  19. odoo_dev-1.2.1/src/odoo_dev/vendor/sync.py +33 -0
  20. odoo_dev-1.2.1/src/odoo_dev/vendor/update.py +124 -0
  21. odoo_dev-1.2.1/src/odoo_dev/vendor/verify.py +189 -0
  22. {odoo_dev-1.1.0 → odoo_dev-1.2.1}/tests/test_setup.py +125 -7
  23. odoo_dev-1.2.1/tests/test_vendor_develop.py +226 -0
  24. odoo_dev-1.2.1/tests/test_vendor_edit.py +139 -0
  25. odoo_dev-1.2.1/tests/test_vendor_lock.py +108 -0
  26. odoo_dev-1.2.1/tests/test_vendor_materialize.py +145 -0
  27. odoo_dev-1.2.1/tests/test_vendor_migrate.py +173 -0
  28. odoo_dev-1.2.1/tests/test_vendor_update.py +148 -0
  29. odoo_dev-1.2.1/tests/test_vendor_verify.py +180 -0
  30. odoo_dev-1.1.0/.release-please-manifest.json +0 -3
  31. odoo_dev-1.1.0/CHANGELOG.md +0 -26
  32. odoo_dev-1.1.0/src/odoo_dev/__init__.py +0 -3
  33. {odoo_dev-1.1.0 → odoo_dev-1.2.1}/.github/workflows/release-please.yml +0 -0
  34. {odoo_dev-1.1.0 → odoo_dev-1.2.1}/.gitignore +0 -0
  35. {odoo_dev-1.1.0 → odoo_dev-1.2.1}/.gitmodules +0 -0
  36. {odoo_dev-1.1.0 → odoo_dev-1.2.1}/.python-version +0 -0
  37. {odoo_dev-1.1.0 → odoo_dev-1.2.1}/release-please-config.json +0 -0
  38. {odoo_dev-1.1.0 → odoo_dev-1.2.1}/src/odoo_dev/commands/__init__.py +0 -0
  39. {odoo_dev-1.1.0 → odoo_dev-1.2.1}/src/odoo_dev/commands/bump.py +0 -0
  40. {odoo_dev-1.1.0 → odoo_dev-1.2.1}/src/odoo_dev/commands/db.py +0 -0
  41. {odoo_dev-1.1.0 → odoo_dev-1.2.1}/src/odoo_dev/commands/docker.py +0 -0
  42. {odoo_dev-1.1.0 → odoo_dev-1.2.1}/src/odoo_dev/preflight.py +0 -0
  43. {odoo_dev-1.1.0 → odoo_dev-1.2.1}/src/odoo_dev/templates/__init__.py +0 -0
  44. {odoo_dev-1.1.0 → odoo_dev-1.2.1}/src/odoo_dev/templates/docker/Dockerfile +0 -0
  45. {odoo_dev-1.1.0 → odoo_dev-1.2.1}/src/odoo_dev/templates/docker/__init__.py +0 -0
  46. {odoo_dev-1.1.0 → odoo_dev-1.2.1}/src/odoo_dev/templates/docker/docker-entrypoint.sh +0 -0
  47. {odoo_dev-1.1.0 → odoo_dev-1.2.1}/src/odoo_dev/templates/vscode/__init__.py +0 -0
  48. {odoo_dev-1.1.0 → odoo_dev-1.2.1}/src/odoo_dev/templates/vscode/launch.json +0 -0
  49. {odoo_dev-1.1.0 → odoo_dev-1.2.1}/src/odoo_dev/templates/vscode/settings.json +0 -0
  50. {odoo_dev-1.1.0 → odoo_dev-1.2.1}/src/odoo_dev/templates/vscode/tasks.json +0 -0
  51. {odoo_dev-1.1.0 → odoo_dev-1.2.1}/src/odoo_dev/utils/__init__.py +0 -0
  52. {odoo_dev-1.1.0 → odoo_dev-1.2.1}/src/odoo_dev/utils/console.py +0 -0
  53. {odoo_dev-1.1.0 → odoo_dev-1.2.1}/src/odoo_dev/utils/manifest.py +0 -0
  54. {odoo_dev-1.1.0 → odoo_dev-1.2.1}/tests/__init__.py +0 -0
  55. {odoo_dev-1.1.0 → odoo_dev-1.2.1}/tests/fixtures/__init__.py +0 -0
  56. {odoo_dev-1.1.0 → odoo_dev-1.2.1}/tests/test_bump.py +0 -0
  57. {odoo_dev-1.1.0 → odoo_dev-1.2.1}/tests/test_cli.py +0 -0
  58. {odoo_dev-1.1.0 → odoo_dev-1.2.1}/tests/test_config.py +0 -0
  59. {odoo_dev-1.1.0 → odoo_dev-1.2.1}/tests/test_db.py +0 -0
  60. {odoo_dev-1.1.0 → odoo_dev-1.2.1}/tests/test_db_config.py +0 -0
  61. {odoo_dev-1.1.0 → odoo_dev-1.2.1}/tests/test_drop_database.py +0 -0
  62. {odoo_dev-1.1.0 → odoo_dev-1.2.1}/tests/test_integration.py +0 -0
  63. {odoo_dev-1.1.0 → odoo_dev-1.2.1}/tests/test_preflight.py +0 -0
  64. {odoo_dev-1.1.0 → odoo_dev-1.2.1}/tests/test_run.py +0 -0
  65. {odoo_dev-1.1.0 → odoo_dev-1.2.1}/tests/test_setup_db.py +0 -0
  66. {odoo_dev-1.1.0 → odoo_dev-1.2.1}/uv.lock +0 -0
@@ -0,0 +1,3 @@
1
+ {
2
+ ".": "1.2.1"
3
+ }
@@ -0,0 +1,60 @@
1
+ # Changelog
2
+
3
+ ## [1.2.1](https://github.com/bemade/odoo-dev/compare/v1.2.0...v1.2.1) (2026-07-14)
4
+
5
+
6
+ ### Bug Fixes
7
+
8
+ * **test:** make full-suite runs work under a dev odoo.conf ([2a1b752](https://github.com/bemade/odoo-dev/commit/2a1b7527d29b3bf8bb776e41ce739b4864543b73))
9
+ * **vendor:** bump clears stale version when pinning to a commit ([19544ba](https://github.com/bemade/odoo-dev/commit/19544bac55a3dded4b912cc9f2ac7226f5d72d61))
10
+ * **vendor:** constrain `vendor update` to the pin's own major series ([5b58e26](https://github.com/bemade/odoo-dev/commit/5b58e2676de6c1b63a678d1dd365efca9e4c8b99))
11
+ * **vendor:** migrate sets version only when the tag resolves to the pin ([ea59b1d](https://github.com/bemade/odoo-dev/commit/ea59b1d62c9844fb831925d9973b4ec179b803e6))
12
+
13
+ ## [1.2.0](https://github.com/bemade/odoo-dev/compare/v1.1.0...v1.2.0) (2026-07-07)
14
+
15
+
16
+ ### Features
17
+
18
+ * add 'vendor' command for per-addon vendoring ([7dfe8cb](https://github.com/bemade/odoo-dev/commit/7dfe8cbbf20031b012bc3326485f9aa3b634146c))
19
+ * **setup:** add --no-docker and --yes for headless agentic setup ([daa62c4](https://github.com/bemade/odoo-dev/commit/daa62c496ceb923152305091c5f94958014f1a51))
20
+ * **setup:** skip submodule init for fully-vendored repos ([83b7818](https://github.com/bemade/odoo-dev/commit/83b78186a0a23b3d33eb12a5364d312794315158))
21
+ * **vendor:** add `vendor develop` local dev-loop for vendored addons ([89325f8](https://github.com/bemade/odoo-dev/commit/89325f88a9a51bfc4d4cb4ea755381c55e62989a))
22
+ * **vendor:** add `vendor update` — the pull side of vendoring ([fb88c1e](https://github.com/bemade/odoo-dev/commit/fb88c1e3b62604f59932811b772cd5e420eb5152))
23
+ * **vendor:** add vendored/ to the addons_path when present ([0fdc512](https://github.com/bemade/odoo-dev/commit/0fdc512a9c9df2541028a8743d1afb33240ac6de))
24
+ * **vendor:** assert-no-hybrid guard in `vendor check --no-hybrid` ([d424eca](https://github.com/bemade/odoo-dev/commit/d424eca69fac72f27a44e24ea3ca68fe9e7b37af))
25
+ * **vendor:** test and cover vendored/ addons alongside local ones ([a989d17](https://github.com/bemade/odoo-dev/commit/a989d174cc6cc84da5cae7fad327c017cbaf8030))
26
+
27
+
28
+ ### Bug Fixes
29
+
30
+ * **vendor:** compare python-dep package NAMES, not full spec strings ([6903fa3](https://github.com/bemade/odoo-dev/commit/6903fa307dfab977cb40b9d7dfee2d017f67a0ff))
31
+
32
+
33
+ ### Documentation
34
+
35
+ * document the vendor command + agentic setup flag ([523ac05](https://github.com/bemade/odoo-dev/commit/523ac050bcd103d18b99abc8cc3a50ab0c6886c4))
36
+
37
+ ## [1.1.0](https://github.com/bemade/odoo-dev/compare/v1.0.0...v1.1.0) (2026-06-26)
38
+
39
+
40
+ ### Features
41
+
42
+ * add `bump` command for series-agnostic manifest version bumps ([#7](https://github.com/bemade/odoo-dev/issues/7)) ([992c8aa](https://github.com/bemade/odoo-dev/commit/992c8aa3731d262803a6f329209275462d88d80a))
43
+
44
+ ## [1.0.0](https://github.com/bemade/odoo-dev/compare/v0.4.0...v1.0.0) (2026-06-11)
45
+
46
+
47
+ ### ⚠ BREAKING CHANGES
48
+
49
+ * macOS `setup` no longer installs PostgreSQL. Fresh macOS setups must install a server themselves (or point DB_HOST/DB_PORT at a remote/Docker server).
50
+
51
+ ### Features
52
+
53
+ * treat PostgreSQL as an external prerequisite on all platforms ([#5](https://github.com/bemade/odoo-dev/issues/5)) ([e817107](https://github.com/bemade/odoo-dev/commit/e817107ac434a5b23dd2675d59a95d1a6f8d1e71))
54
+
55
+ ## [0.4.0](https://github.com/bemade/odoo-dev/compare/v0.3.3...v0.4.0) (2026-06-11)
56
+
57
+
58
+ ### Features
59
+
60
+ * configurable DB connection + connectivity preflight ([#3](https://github.com/bemade/odoo-dev/issues/3)) ([632738e](https://github.com/bemade/odoo-dev/commit/632738e37c1d22d79ec4a03cba72a28ca580f7c8))
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: odoo-dev
3
- Version: 1.1.0
3
+ Version: 1.2.1
4
4
  Summary: Odoo Development Environment Helper
5
5
  Project-URL: Homepage, https://git.bemade.org/bemade/odoo-dev
6
6
  Project-URL: Repository, https://git.bemade.org/bemade/odoo-dev
@@ -102,7 +102,7 @@ odoo-dev shell mydb # Open an Odoo shell
102
102
  odoo-dev update base -d mydb # Update modules
103
103
  odoo-dev test my_module # Run a module's tests (coverage on by default)
104
104
  odoo-dev test my_module --test-tags my_module --no-coverage
105
- odoo-dev test # Auto-discover & test all addons in addons/
105
+ odoo-dev test # Auto-discover & test all addons in addons/ + vendored/
106
106
  odoo-dev scaffold my_module # Create a new module
107
107
  ```
108
108
 
@@ -133,12 +133,58 @@ odoo-dev docker psql # PostgreSQL shell
133
133
  ### Setup Commands
134
134
 
135
135
  ```bash
136
- odoo-dev setup # Full setup
136
+ odoo-dev setup # Full setup (interactive; offers Docker)
137
137
  odoo-dev setup --community # Community edition only
138
+ odoo-dev setup --no-docker --yes # Headless/agentic: clone + venv + conf, no Docker, no prompts
138
139
  odoo-dev setup-venv # Just create the venv (no repo clone)
139
140
  odoo-dev vscode # Configure VSCode debugging
140
141
  ```
141
142
 
143
+ ### Vendored Addons
144
+
145
+ Shared addons can be **vendored** — materialized as real committed directories
146
+ under `vendored/`, pinned per-addon by `addons.lock` — instead of pulled in as
147
+ `.repos/` git submodules. This makes each promotion a normal, reviewable file
148
+ diff and deploys as plain files (what Odoo.sh needs). `vendored/` and `addons/`
149
+ are both on the addons path.
150
+
151
+ ```bash
152
+ odoo-dev vendor migrate # Convert .repos submodule+symlink addons -> vendored/
153
+ odoo-dev vendor migrate --dry-run # Preview the pins without changing anything
154
+ odoo-dev vendor sync # Materialize vendored/ from addons.lock
155
+ odoo-dev vendor check # CI gate: verify vendored/ byte-matches the pins
156
+ odoo-dev vendor add fsm --source github.com/bemade/bemade-addons --version 18.0.1.3.2
157
+ odoo-dev vendor bump fsm --version 18.0.1.4.0 # Move a pin and re-materialize
158
+ odoo-dev vendor update # Pull newest upstream for all tracked addons
159
+ odoo-dev vendor update fsm --dry-run # Show what would update, change nothing
160
+ odoo-dev vendor develop fsm # Edit fsm against a live source clone
161
+ odoo-dev vendor develop fsm --stop # Leave develop mode (keeps the clone)
162
+ odoo-dev vendor status # Show vendored pins, symlinks, develop mode
163
+ ```
164
+
165
+ Because `vendored/<addon>` is a materialized copy (re-`sync` clobbers it), you
166
+ don't edit it in place. `vendor develop <addon>` clones the addon's source repo
167
+ into a git-ignored `.vendor-dev/`, checks out a work branch at the pinned commit,
168
+ and prepends an overlay to your **local** `conf/odoo.conf` addons_path so Odoo
169
+ loads the live tree instead of the vendored copy (first path wins). `vendored/`
170
+ stays byte-for-byte pristine and only the local conf is touched, so nothing
171
+ dev-only can leak into a commit or into CI/prod. Edit in the clone, run/test in
172
+ this project's Odoo, commit + push upstream, then `vendor bump` once the new
173
+ version is tagged. `--branch NAME` names the work branch; `--base REF` bases it on
174
+ something other than the current pin.
175
+
176
+ `vendor check` verifies, per addon: the vendored files byte-match the pinned
177
+ commit; a `version` tag (if set) still resolves to that commit; every manifest
178
+ `external_dependencies['python']` is named in `requirements.txt`; and no addon
179
+ name collides between `addons/` and `vendored/`.
180
+
181
+ `vendor update` is the **pull** side: each client owns its pins. For every addon
182
+ that tracks upstream — a `version` (bump to the newest `<addon>/<version>` tag) or
183
+ a `branch` (bump to its HEAD) — it moves the pin forward and re-materializes.
184
+ Addons pinned to a bare commit are left alone. Run it on a schedule (a client's own
185
+ CI, its own token, opening a `vendor-bump/…` MR) so shared-addon changes propagate
186
+ without any per-source push/fan-out machinery.
187
+
142
188
  ## Project Structure
143
189
 
144
190
  odoo-dev expects this project structure:
@@ -147,6 +193,8 @@ odoo-dev expects this project structure:
147
193
  my-odoo-project/
148
194
  ├── .env # Optional: ODOO_VERSION, PYTHON_VERSION
149
195
  ├── addons/ # Your custom addons
196
+ ├── vendored/ # Vendored shared addons (real files, pinned by addons.lock)
197
+ ├── addons.lock # Per-addon pins for vendored/ (see `vendor`)
150
198
  ├── requirements.txt # Project-specific Python deps
151
199
  ├── odoo/ # Cloned by setup
152
200
  ├── enterprise/ # Cloned by setup (unless --community)
@@ -156,6 +204,10 @@ my-odoo-project/
156
204
  └── odoo.conf # Created by setup
157
205
  ```
158
206
 
207
+ `vendored/` and `addons.lock` are only present once a project adopts vendoring
208
+ (`odoo-dev vendor migrate`); submodule-based projects use `.repos/` + symlinks
209
+ into `addons/` instead.
210
+
159
211
  ## Configuration
160
212
 
161
213
  Create a `.env` file in your project root:
@@ -88,7 +88,7 @@ odoo-dev shell mydb # Open an Odoo shell
88
88
  odoo-dev update base -d mydb # Update modules
89
89
  odoo-dev test my_module # Run a module's tests (coverage on by default)
90
90
  odoo-dev test my_module --test-tags my_module --no-coverage
91
- odoo-dev test # Auto-discover & test all addons in addons/
91
+ odoo-dev test # Auto-discover & test all addons in addons/ + vendored/
92
92
  odoo-dev scaffold my_module # Create a new module
93
93
  ```
94
94
 
@@ -119,12 +119,58 @@ odoo-dev docker psql # PostgreSQL shell
119
119
  ### Setup Commands
120
120
 
121
121
  ```bash
122
- odoo-dev setup # Full setup
122
+ odoo-dev setup # Full setup (interactive; offers Docker)
123
123
  odoo-dev setup --community # Community edition only
124
+ odoo-dev setup --no-docker --yes # Headless/agentic: clone + venv + conf, no Docker, no prompts
124
125
  odoo-dev setup-venv # Just create the venv (no repo clone)
125
126
  odoo-dev vscode # Configure VSCode debugging
126
127
  ```
127
128
 
129
+ ### Vendored Addons
130
+
131
+ Shared addons can be **vendored** — materialized as real committed directories
132
+ under `vendored/`, pinned per-addon by `addons.lock` — instead of pulled in as
133
+ `.repos/` git submodules. This makes each promotion a normal, reviewable file
134
+ diff and deploys as plain files (what Odoo.sh needs). `vendored/` and `addons/`
135
+ are both on the addons path.
136
+
137
+ ```bash
138
+ odoo-dev vendor migrate # Convert .repos submodule+symlink addons -> vendored/
139
+ odoo-dev vendor migrate --dry-run # Preview the pins without changing anything
140
+ odoo-dev vendor sync # Materialize vendored/ from addons.lock
141
+ odoo-dev vendor check # CI gate: verify vendored/ byte-matches the pins
142
+ odoo-dev vendor add fsm --source github.com/bemade/bemade-addons --version 18.0.1.3.2
143
+ odoo-dev vendor bump fsm --version 18.0.1.4.0 # Move a pin and re-materialize
144
+ odoo-dev vendor update # Pull newest upstream for all tracked addons
145
+ odoo-dev vendor update fsm --dry-run # Show what would update, change nothing
146
+ odoo-dev vendor develop fsm # Edit fsm against a live source clone
147
+ odoo-dev vendor develop fsm --stop # Leave develop mode (keeps the clone)
148
+ odoo-dev vendor status # Show vendored pins, symlinks, develop mode
149
+ ```
150
+
151
+ Because `vendored/<addon>` is a materialized copy (re-`sync` clobbers it), you
152
+ don't edit it in place. `vendor develop <addon>` clones the addon's source repo
153
+ into a git-ignored `.vendor-dev/`, checks out a work branch at the pinned commit,
154
+ and prepends an overlay to your **local** `conf/odoo.conf` addons_path so Odoo
155
+ loads the live tree instead of the vendored copy (first path wins). `vendored/`
156
+ stays byte-for-byte pristine and only the local conf is touched, so nothing
157
+ dev-only can leak into a commit or into CI/prod. Edit in the clone, run/test in
158
+ this project's Odoo, commit + push upstream, then `vendor bump` once the new
159
+ version is tagged. `--branch NAME` names the work branch; `--base REF` bases it on
160
+ something other than the current pin.
161
+
162
+ `vendor check` verifies, per addon: the vendored files byte-match the pinned
163
+ commit; a `version` tag (if set) still resolves to that commit; every manifest
164
+ `external_dependencies['python']` is named in `requirements.txt`; and no addon
165
+ name collides between `addons/` and `vendored/`.
166
+
167
+ `vendor update` is the **pull** side: each client owns its pins. For every addon
168
+ that tracks upstream — a `version` (bump to the newest `<addon>/<version>` tag) or
169
+ a `branch` (bump to its HEAD) — it moves the pin forward and re-materializes.
170
+ Addons pinned to a bare commit are left alone. Run it on a schedule (a client's own
171
+ CI, its own token, opening a `vendor-bump/…` MR) so shared-addon changes propagate
172
+ without any per-source push/fan-out machinery.
173
+
128
174
  ## Project Structure
129
175
 
130
176
  odoo-dev expects this project structure:
@@ -133,6 +179,8 @@ odoo-dev expects this project structure:
133
179
  my-odoo-project/
134
180
  ├── .env # Optional: ODOO_VERSION, PYTHON_VERSION
135
181
  ├── addons/ # Your custom addons
182
+ ├── vendored/ # Vendored shared addons (real files, pinned by addons.lock)
183
+ ├── addons.lock # Per-addon pins for vendored/ (see `vendor`)
136
184
  ├── requirements.txt # Project-specific Python deps
137
185
  ├── odoo/ # Cloned by setup
138
186
  ├── enterprise/ # Cloned by setup (unless --community)
@@ -142,6 +190,10 @@ my-odoo-project/
142
190
  └── odoo.conf # Created by setup
143
191
  ```
144
192
 
193
+ `vendored/` and `addons.lock` are only present once a project adopts vendoring
194
+ (`odoo-dev vendor migrate`); submodule-based projects use `.repos/` + symlinks
195
+ into `addons/` instead.
196
+
145
197
  ## Configuration
146
198
 
147
199
  Create a `.env` file in your project root:
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "odoo-dev"
3
- version = "1.1.0"
3
+ version = "1.2.1"
4
4
  description = "Odoo Development Environment Helper"
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.12"
@@ -0,0 +1,3 @@
1
+ """Odoo Development Environment Helper."""
2
+
3
+ __version__ = "1.2.1" # x-release-please-version
@@ -7,6 +7,7 @@ import typer
7
7
  from odoo_dev import __version__
8
8
  from odoo_dev.commands import bump as bump_cmd
9
9
  from odoo_dev.commands import db, docker, run, setup
10
+ from odoo_dev.commands import vendor as vendor_cmd
10
11
 
11
12
 
12
13
  def _version_callback(value: bool) -> None:
@@ -40,6 +41,7 @@ def _main(
40
41
  # Command groups
41
42
  app.add_typer(db.app, name="db")
42
43
  app.add_typer(docker.app, name="docker")
44
+ app.add_typer(vendor_cmd.app, name="vendor")
43
45
 
44
46
  # Setup commands
45
47
  app.command()(setup.setup)
@@ -235,17 +235,18 @@ def test(
235
235
  break
236
236
 
237
237
  # Auto-load exclusions from repo_deps.yaml when not explicitly provided
238
- if exclude is None and repo_deps_file is not None:
238
+ no_ci_modules: list[str] = []
239
+ if repo_deps_file is not None:
239
240
  try:
240
241
  import yaml
241
242
 
242
243
  data = yaml.safe_load(repo_deps_file.read_text()) or {}
243
- no_ci = data.get("no_ci", [])
244
- if no_ci:
245
- exclude = ",".join(no_ci)
246
- info(f"Excluding (from {repo_deps_file} no_ci): {exclude}")
244
+ no_ci_modules = list(data.get("no_ci", []) or [])
247
245
  except Exception:
248
- pass
246
+ no_ci_modules = []
247
+ if exclude is None and no_ci_modules:
248
+ exclude = ",".join(no_ci_modules)
249
+ info(f"Excluding (from {repo_deps_file} no_ci): {exclude}")
249
250
 
250
251
  # Verify setup
251
252
  if not cfg.venv_path.exists():
@@ -268,7 +269,9 @@ def test(
268
269
 
269
270
  # Determine modules to test
270
271
  if modules is None:
271
- # Use manifestoo to list addons in the addons directory
272
+ # Use manifestoo to list addons across addons/ and vendored/, so a no-args
273
+ # run tests all custom AND vendored addons together (catches vendored
274
+ # addons that break when combined with local addons or with each other).
272
275
  manifestoo_cmd = [
273
276
  str(venv_python),
274
277
  "-m",
@@ -276,6 +279,8 @@ def test(
276
279
  "--select-addons-dir",
277
280
  str(cfg.addons_dir),
278
281
  ]
282
+ if cfg.vendored_dir.is_dir():
283
+ manifestoo_cmd.extend(["--select-addons-dir", str(cfg.vendored_dir)])
279
284
  if exclude:
280
285
  manifestoo_cmd.extend(["--select-exclude", exclude])
281
286
  manifestoo_cmd.extend(["list", "--separator=,"])
@@ -287,7 +292,7 @@ def test(
287
292
  )
288
293
  modules = result.stdout.strip()
289
294
  if not modules:
290
- error(f"No addons found in {cfg.addons_dir}")
295
+ error(f"No addons found in {cfg.addons_dir} or {cfg.vendored_dir}")
291
296
  raise typer.Exit(1)
292
297
 
293
298
  # Clone external repo dependencies declared in repo_deps.yaml and symlink into addons_dir
@@ -372,6 +377,9 @@ def test(
372
377
  "--stop-after-init",
373
378
  "--http-port",
374
379
  str(http_port),
380
+ # No need to fork prefork workers for a stop-after-init install.
381
+ "--workers",
382
+ "0",
375
383
  ],
376
384
  )
377
385
 
@@ -387,9 +395,9 @@ def test(
387
395
  success(f"Running tests: {modules}")
388
396
 
389
397
  if coverage:
390
- # Build coverage source paths
398
+ # Build coverage source paths (a module may live in addons/ or vendored/)
391
399
  coverage_source = ",".join(
392
- str(cfg.addons_dir / m) for m in modules.split(",")
400
+ str(_module_dir(cfg, m)) for m in modules.split(",")
393
401
  )
394
402
 
395
403
  test_cmd = [
@@ -418,11 +426,36 @@ def test(
418
426
  "--stop-after-init",
419
427
  "--http-port",
420
428
  str(http_port),
429
+ # HttpCase needs the threaded server (ThreadedServer.httpd);
430
+ # a prefork/multi-worker conf raises
431
+ # "'PreforkServer' object has no attribute 'httpd'" in
432
+ # setUpClass. Force single-process regardless of odoo.conf.
433
+ "--workers",
434
+ "0",
435
+ # A restrictive dbfilter in odoo.conf (e.g. `dbfilter = ^rwi$`)
436
+ # never matches the ephemeral test DB, so HttpCase requests get
437
+ # "dbfilter rejects it; logging session out" -> auth='user'
438
+ # routes 404 and JSON endpoints return non-JSON. Scope the
439
+ # filter to this run's test DB so the test server serves it.
440
+ f"--db-filter=^{db_name}$",
421
441
  ]
422
442
  )
423
443
 
424
- if test_tags:
425
- test_cmd.extend(["--test-tags", test_tags])
444
+ # Excluding no_ci modules from the tested-module *list* is not enough:
445
+ # they still install as transitive deps, and Odoo's button_upgrade
446
+ # cascades 'to upgrade' to downstream dependents of any upgraded target
447
+ # module (`-u modules`). A no_ci module that depends on a tested module
448
+ # thus gets re-upgraded and its post_install tests run. Exclude their
449
+ # tests explicitly via Odoo test-tags. The TagsSelector auto-adds
450
+ # '+standard' when only '-' filters are present, so this keeps running
451
+ # every standard test except those belonging to a no_ci module.
452
+ effective_test_tags = test_tags
453
+ if effective_test_tags is None and no_ci_modules:
454
+ effective_test_tags = ",".join(f"-/{m}" for m in no_ci_modules)
455
+ info(f"Excluding no_ci module tests via --test-tags: {effective_test_tags}")
456
+
457
+ if effective_test_tags:
458
+ test_cmd.extend(["--test-tags", effective_test_tags])
426
459
 
427
460
  result = subprocess.run(test_cmd)
428
461
  test_exit_code = result.returncode
@@ -575,6 +608,14 @@ def _terminate_connections(db_name: str, db_config: dict[str, str]) -> None:
575
608
  )
576
609
 
577
610
 
611
+ def _module_dir(cfg, module: str) -> Path:
612
+ """Return the directory of ``module`` — under addons/ or vendored/."""
613
+ for base in (cfg.addons_dir, cfg.vendored_dir):
614
+ if (base / module).is_dir():
615
+ return base / module
616
+ return cfg.addons_dir / module
617
+
618
+
578
619
  def _get_addons_path(config_file) -> str:
579
620
  """Extract addons_path from odoo.conf."""
580
621
  for line in config_file.read_text().splitlines():
@@ -18,11 +18,13 @@ from odoo_dev.config import (
18
18
  from odoo_dev.utils.console import info, success, warning
19
19
 
20
20
 
21
- def _prompt_for_versions() -> None:
22
- """Prompt for ODOO_VERSION and PYTHON_VERSION if not set.
21
+ def _prompt_for_versions(non_interactive: bool = False) -> None:
22
+ """Resolve ODOO_VERSION and PYTHON_VERSION.
23
23
 
24
- Checks environment and .env file. If not found, prompts interactively
25
- and optionally saves to .env.
24
+ Checks environment and .env file. If unset and ``non_interactive`` is False,
25
+ prompts interactively and optionally saves to .env. If ``non_interactive``,
26
+ falls back to the defaults with no prompts (agentic/headless use) and records
27
+ them to .env silently.
26
28
  """
27
29
  project_dir = find_project_root()
28
30
  env_file = project_dir / ".env"
@@ -36,24 +38,28 @@ def _prompt_for_versions() -> None:
36
38
  updates = {}
37
39
 
38
40
  if not odoo_version:
39
- odoo_version = typer.prompt(
40
- "Odoo version",
41
- default=DEFAULT_ODOO_VERSION,
41
+ odoo_version = (
42
+ DEFAULT_ODOO_VERSION
43
+ if non_interactive
44
+ else typer.prompt("Odoo version", default=DEFAULT_ODOO_VERSION)
42
45
  )
43
46
  os.environ["ODOO_VERSION"] = odoo_version
44
47
  updates["ODOO_VERSION"] = odoo_version
45
48
 
46
49
  if not python_version:
47
- python_version = typer.prompt(
48
- "Python version",
49
- default=DEFAULT_PYTHON_VERSION,
50
+ python_version = (
51
+ DEFAULT_PYTHON_VERSION
52
+ if non_interactive
53
+ else typer.prompt("Python version", default=DEFAULT_PYTHON_VERSION)
50
54
  )
51
55
  os.environ["PYTHON_VERSION"] = python_version
52
56
  updates["PYTHON_VERSION"] = python_version
53
57
 
54
- # Offer to save to .env if we prompted for anything
58
+ # Persist anything we resolved. Non-interactive saves silently; interactive asks.
55
59
  if updates:
56
- if typer.confirm("Save these settings to .env?", default=True):
60
+ if non_interactive or typer.confirm(
61
+ "Save these settings to .env?", default=True
62
+ ):
57
63
  _update_env_file(env_file, updates)
58
64
  success(f"Settings saved to {env_file}")
59
65
 
@@ -98,10 +104,27 @@ def setup(
98
104
  "--clean",
99
105
  help="Clean up before setup (remove existing venv, etc.)",
100
106
  ),
107
+ no_docker: bool = typer.Option(
108
+ False,
109
+ "--no-docker",
110
+ help="Skip Docker file setup and image build (local venv only).",
111
+ ),
112
+ yes: bool = typer.Option(
113
+ False,
114
+ "--yes",
115
+ "-y",
116
+ help="Non-interactive: no prompts, use defaults. For agentic/headless use "
117
+ "(combine with --no-docker for a fully unattended local setup).",
118
+ ),
101
119
  ) -> None:
102
- """Complete setup: clone Odoo repos, configure VSCode, build image."""
103
- # Prompt for versions if not configured
104
- _prompt_for_versions()
120
+ """Complete setup: clone Odoo repos, configure VSCode, build image.
121
+
122
+ For headless/agentic use run ``odoo-dev setup --no-docker --yes``: clones the
123
+ Odoo repos, builds the local venv and odoo.conf, and never prompts or touches
124
+ Docker.
125
+ """
126
+ # Resolve versions (no prompts under --yes)
127
+ _prompt_for_versions(non_interactive=yes)
105
128
 
106
129
  cfg = load_config()
107
130
 
@@ -128,9 +151,14 @@ def setup(
128
151
  )
129
152
  setup_venv()
130
153
 
131
- # Prompt for Docker setup
132
- warning("\nDo you want to set up the Docker environment?")
133
- if typer.confirm("Continue with Docker setup?", default=True):
154
+ # Docker setup — skipped entirely under --no-docker; auto-confirmed under --yes.
155
+ if no_docker:
156
+ info(
157
+ "Skipping Docker setup (--no-docker). Run 'odoo-dev build' later if needed."
158
+ )
159
+ elif yes or typer.confirm(
160
+ "\nDo you want to set up the Docker environment?", default=True
161
+ ):
134
162
  # Set up Docker files first
135
163
  _setup_docker_files(cfg, community_only=community)
136
164
 
@@ -254,6 +282,22 @@ def vscode(cfg=None) -> None:
254
282
  success("VSCode configuration set up successfully!")
255
283
 
256
284
 
285
+ def _fully_vendored(project_dir: Path) -> bool:
286
+ """True if the repo is fully vendored: ``addons.lock`` present and no
287
+ ``addons/`` symlink still points into ``.repos/``.
288
+
289
+ Such a repo needs no submodule checkout — its shared addons are committed real
290
+ files under ``vendored/``. A hybrid (still carries ``.repos`` symlinks) does
291
+ need its submodules, so this returns False there.
292
+ """
293
+ from odoo_dev.vendor.verify import hybrid_submodule_addons
294
+
295
+ project_dir = Path(project_dir)
296
+ return (project_dir / "addons.lock").exists() and not hybrid_submodule_addons(
297
+ project_dir
298
+ )
299
+
300
+
257
301
  def _init_submodules(cfg) -> None:
258
302
  """Initialize and update git submodules if present."""
259
303
  gitmodules = cfg.project_dir / ".gitmodules"
@@ -261,6 +305,10 @@ def _init_submodules(cfg) -> None:
261
305
  if not gitmodules.exists():
262
306
  return
263
307
 
308
+ if _fully_vendored(cfg.project_dir):
309
+ info("Fully vendored (addons.lock, no .repos symlinks); skipping submodule init.")
310
+ return
311
+
264
312
  success("Initializing git submodules...")
265
313
 
266
314
  result = subprocess.run(
@@ -303,6 +351,7 @@ def _setup_odoo_config(cfg, community_only: bool = False) -> None:
303
351
  [
304
352
  str(cfg.project_dir / "design-themes"),
305
353
  str(cfg.project_dir / "addons"),
354
+ str(cfg.project_dir / "vendored"),
306
355
  ]
307
356
  )
308
357
 
@@ -360,7 +409,9 @@ def _setup_docker_files(cfg, community_only: bool = False) -> None:
360
409
  _setup_docker_odoo_config(cfg, community_only=community_only)
361
410
 
362
411
 
363
- def _generate_docker_odoo_conf(community_only: bool = False) -> str:
412
+ def _generate_docker_odoo_conf(
413
+ community_only: bool = False, has_vendored: bool = False
414
+ ) -> str:
364
415
  """Generate Docker-specific odoo.conf content."""
365
416
  # Build addons path for Docker (using /opt/project paths)
366
417
  addons_paths = [
@@ -378,6 +429,10 @@ def _generate_docker_odoo_conf(community_only: bool = False) -> str:
378
429
  "/opt/project/addons",
379
430
  ]
380
431
  )
432
+ # Only add vendored/ when the repo actually has it — an addons_path entry that
433
+ # doesn't exist makes Odoo warn on every boot.
434
+ if has_vendored:
435
+ addons_paths.append("/opt/project/vendored")
381
436
 
382
437
  return f"""[options]
383
438
  addons_path = {",".join(addons_paths)}
@@ -411,7 +466,12 @@ def _setup_docker_odoo_config(cfg, community_only: bool = False) -> None:
411
466
  return
412
467
 
413
468
  success("Creating Docker Odoo configuration file...")
414
- conf_file.write_text(_generate_docker_odoo_conf(community_only=community_only))
469
+ has_vendored = (cfg.project_dir / "vendored").is_dir()
470
+ conf_file.write_text(
471
+ _generate_docker_odoo_conf(
472
+ community_only=community_only, has_vendored=has_vendored
473
+ )
474
+ )
415
475
  conf_file.chmod(0o600)
416
476
  success(f"Docker config file created at {conf_file}")
417
477