odoo-dev 1.0.0__tar.gz → 1.2.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.
Files changed (66) hide show
  1. odoo_dev-1.2.0/.release-please-manifest.json +3 -0
  2. odoo_dev-1.2.0/CHANGELOG.md +50 -0
  3. {odoo_dev-1.0.0 → odoo_dev-1.2.0}/PKG-INFO +55 -3
  4. {odoo_dev-1.0.0 → odoo_dev-1.2.0}/README.md +54 -2
  5. {odoo_dev-1.0.0 → odoo_dev-1.2.0}/pyproject.toml +1 -1
  6. odoo_dev-1.2.0/src/odoo_dev/__init__.py +3 -0
  7. {odoo_dev-1.0.0 → odoo_dev-1.2.0}/src/odoo_dev/cli.py +6 -0
  8. odoo_dev-1.2.0/src/odoo_dev/commands/bump.py +88 -0
  9. {odoo_dev-1.0.0 → odoo_dev-1.2.0}/src/odoo_dev/commands/run.py +16 -4
  10. {odoo_dev-1.0.0 → odoo_dev-1.2.0}/src/odoo_dev/commands/setup.py +80 -20
  11. odoo_dev-1.2.0/src/odoo_dev/commands/vendor.py +278 -0
  12. {odoo_dev-1.0.0 → odoo_dev-1.2.0}/src/odoo_dev/config.py +10 -0
  13. odoo_dev-1.2.0/src/odoo_dev/utils/manifest.py +81 -0
  14. odoo_dev-1.2.0/src/odoo_dev/vendor/__init__.py +10 -0
  15. odoo_dev-1.2.0/src/odoo_dev/vendor/develop.py +306 -0
  16. odoo_dev-1.2.0/src/odoo_dev/vendor/edit.py +84 -0
  17. odoo_dev-1.2.0/src/odoo_dev/vendor/lock.py +86 -0
  18. odoo_dev-1.2.0/src/odoo_dev/vendor/materialize.py +130 -0
  19. odoo_dev-1.2.0/src/odoo_dev/vendor/migrate.py +209 -0
  20. odoo_dev-1.2.0/src/odoo_dev/vendor/sources.py +146 -0
  21. odoo_dev-1.2.0/src/odoo_dev/vendor/sync.py +33 -0
  22. odoo_dev-1.2.0/src/odoo_dev/vendor/update.py +105 -0
  23. odoo_dev-1.2.0/src/odoo_dev/vendor/verify.py +189 -0
  24. odoo_dev-1.2.0/tests/test_bump.py +61 -0
  25. {odoo_dev-1.0.0 → odoo_dev-1.2.0}/tests/test_cli.py +5 -0
  26. {odoo_dev-1.0.0 → odoo_dev-1.2.0}/tests/test_setup.py +125 -7
  27. odoo_dev-1.2.0/tests/test_vendor_develop.py +189 -0
  28. odoo_dev-1.2.0/tests/test_vendor_edit.py +106 -0
  29. odoo_dev-1.2.0/tests/test_vendor_lock.py +108 -0
  30. odoo_dev-1.2.0/tests/test_vendor_materialize.py +145 -0
  31. odoo_dev-1.2.0/tests/test_vendor_migrate.py +128 -0
  32. odoo_dev-1.2.0/tests/test_vendor_update.py +123 -0
  33. odoo_dev-1.2.0/tests/test_vendor_verify.py +180 -0
  34. odoo_dev-1.0.0/.release-please-manifest.json +0 -3
  35. odoo_dev-1.0.0/CHANGELOG.md +0 -19
  36. odoo_dev-1.0.0/src/odoo_dev/__init__.py +0 -3
  37. {odoo_dev-1.0.0 → odoo_dev-1.2.0}/.github/workflows/release-please.yml +0 -0
  38. {odoo_dev-1.0.0 → odoo_dev-1.2.0}/.gitignore +0 -0
  39. {odoo_dev-1.0.0 → odoo_dev-1.2.0}/.gitmodules +0 -0
  40. {odoo_dev-1.0.0 → odoo_dev-1.2.0}/.python-version +0 -0
  41. {odoo_dev-1.0.0 → odoo_dev-1.2.0}/release-please-config.json +0 -0
  42. {odoo_dev-1.0.0 → odoo_dev-1.2.0}/src/odoo_dev/commands/__init__.py +0 -0
  43. {odoo_dev-1.0.0 → odoo_dev-1.2.0}/src/odoo_dev/commands/db.py +0 -0
  44. {odoo_dev-1.0.0 → odoo_dev-1.2.0}/src/odoo_dev/commands/docker.py +0 -0
  45. {odoo_dev-1.0.0 → odoo_dev-1.2.0}/src/odoo_dev/preflight.py +0 -0
  46. {odoo_dev-1.0.0 → odoo_dev-1.2.0}/src/odoo_dev/templates/__init__.py +0 -0
  47. {odoo_dev-1.0.0 → odoo_dev-1.2.0}/src/odoo_dev/templates/docker/Dockerfile +0 -0
  48. {odoo_dev-1.0.0 → odoo_dev-1.2.0}/src/odoo_dev/templates/docker/__init__.py +0 -0
  49. {odoo_dev-1.0.0 → odoo_dev-1.2.0}/src/odoo_dev/templates/docker/docker-entrypoint.sh +0 -0
  50. {odoo_dev-1.0.0 → odoo_dev-1.2.0}/src/odoo_dev/templates/vscode/__init__.py +0 -0
  51. {odoo_dev-1.0.0 → odoo_dev-1.2.0}/src/odoo_dev/templates/vscode/launch.json +0 -0
  52. {odoo_dev-1.0.0 → odoo_dev-1.2.0}/src/odoo_dev/templates/vscode/settings.json +0 -0
  53. {odoo_dev-1.0.0 → odoo_dev-1.2.0}/src/odoo_dev/templates/vscode/tasks.json +0 -0
  54. {odoo_dev-1.0.0 → odoo_dev-1.2.0}/src/odoo_dev/utils/__init__.py +0 -0
  55. {odoo_dev-1.0.0 → odoo_dev-1.2.0}/src/odoo_dev/utils/console.py +0 -0
  56. {odoo_dev-1.0.0 → odoo_dev-1.2.0}/tests/__init__.py +0 -0
  57. {odoo_dev-1.0.0 → odoo_dev-1.2.0}/tests/fixtures/__init__.py +0 -0
  58. {odoo_dev-1.0.0 → odoo_dev-1.2.0}/tests/test_config.py +0 -0
  59. {odoo_dev-1.0.0 → odoo_dev-1.2.0}/tests/test_db.py +0 -0
  60. {odoo_dev-1.0.0 → odoo_dev-1.2.0}/tests/test_db_config.py +0 -0
  61. {odoo_dev-1.0.0 → odoo_dev-1.2.0}/tests/test_drop_database.py +0 -0
  62. {odoo_dev-1.0.0 → odoo_dev-1.2.0}/tests/test_integration.py +0 -0
  63. {odoo_dev-1.0.0 → odoo_dev-1.2.0}/tests/test_preflight.py +0 -0
  64. {odoo_dev-1.0.0 → odoo_dev-1.2.0}/tests/test_run.py +0 -0
  65. {odoo_dev-1.0.0 → odoo_dev-1.2.0}/tests/test_setup_db.py +0 -0
  66. {odoo_dev-1.0.0 → odoo_dev-1.2.0}/uv.lock +0 -0
@@ -0,0 +1,3 @@
1
+ {
2
+ ".": "1.2.0"
3
+ }
@@ -0,0 +1,50 @@
1
+ # Changelog
2
+
3
+ ## [1.2.0](https://github.com/bemade/odoo-dev/compare/v1.1.0...v1.2.0) (2026-07-07)
4
+
5
+
6
+ ### Features
7
+
8
+ * add 'vendor' command for per-addon vendoring ([7dfe8cb](https://github.com/bemade/odoo-dev/commit/7dfe8cbbf20031b012bc3326485f9aa3b634146c))
9
+ * **setup:** add --no-docker and --yes for headless agentic setup ([daa62c4](https://github.com/bemade/odoo-dev/commit/daa62c496ceb923152305091c5f94958014f1a51))
10
+ * **setup:** skip submodule init for fully-vendored repos ([83b7818](https://github.com/bemade/odoo-dev/commit/83b78186a0a23b3d33eb12a5364d312794315158))
11
+ * **vendor:** add `vendor develop` local dev-loop for vendored addons ([89325f8](https://github.com/bemade/odoo-dev/commit/89325f88a9a51bfc4d4cb4ea755381c55e62989a))
12
+ * **vendor:** add `vendor update` — the pull side of vendoring ([fb88c1e](https://github.com/bemade/odoo-dev/commit/fb88c1e3b62604f59932811b772cd5e420eb5152))
13
+ * **vendor:** add vendored/ to the addons_path when present ([0fdc512](https://github.com/bemade/odoo-dev/commit/0fdc512a9c9df2541028a8743d1afb33240ac6de))
14
+ * **vendor:** assert-no-hybrid guard in `vendor check --no-hybrid` ([d424eca](https://github.com/bemade/odoo-dev/commit/d424eca69fac72f27a44e24ea3ca68fe9e7b37af))
15
+ * **vendor:** test and cover vendored/ addons alongside local ones ([a989d17](https://github.com/bemade/odoo-dev/commit/a989d174cc6cc84da5cae7fad327c017cbaf8030))
16
+
17
+
18
+ ### Bug Fixes
19
+
20
+ * **vendor:** compare python-dep package NAMES, not full spec strings ([6903fa3](https://github.com/bemade/odoo-dev/commit/6903fa307dfab977cb40b9d7dfee2d017f67a0ff))
21
+
22
+
23
+ ### Documentation
24
+
25
+ * document the vendor command + agentic setup flag ([523ac05](https://github.com/bemade/odoo-dev/commit/523ac050bcd103d18b99abc8cc3a50ab0c6886c4))
26
+
27
+ ## [1.1.0](https://github.com/bemade/odoo-dev/compare/v1.0.0...v1.1.0) (2026-06-26)
28
+
29
+
30
+ ### Features
31
+
32
+ * 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))
33
+
34
+ ## [1.0.0](https://github.com/bemade/odoo-dev/compare/v0.4.0...v1.0.0) (2026-06-11)
35
+
36
+
37
+ ### ⚠ BREAKING CHANGES
38
+
39
+ * 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).
40
+
41
+ ### Features
42
+
43
+ * 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))
44
+
45
+ ## [0.4.0](https://github.com/bemade/odoo-dev/compare/v0.3.3...v0.4.0) (2026-06-11)
46
+
47
+
48
+ ### Features
49
+
50
+ * 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.0.0
3
+ Version: 1.2.0
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.0.0"
3
+ version = "1.2.0"
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.0" # x-release-please-version
@@ -5,7 +5,9 @@ from typing import Optional
5
5
  import typer
6
6
 
7
7
  from odoo_dev import __version__
8
+ from odoo_dev.commands import bump as bump_cmd
8
9
  from odoo_dev.commands import db, docker, run, setup
10
+ from odoo_dev.commands import vendor as vendor_cmd
9
11
 
10
12
 
11
13
  def _version_callback(value: bool) -> None:
@@ -39,6 +41,7 @@ def _main(
39
41
  # Command groups
40
42
  app.add_typer(db.app, name="db")
41
43
  app.add_typer(docker.app, name="docker")
44
+ app.add_typer(vendor_cmd.app, name="vendor")
42
45
 
43
46
  # Setup commands
44
47
  app.command()(setup.setup)
@@ -52,6 +55,9 @@ app.command()(run.update)
52
55
  app.command()(run.test)
53
56
  app.command()(run.scaffold)
54
57
 
58
+ # Versioning
59
+ app.command()(bump_cmd.bump)
60
+
55
61
 
56
62
  def main() -> None:
57
63
  """Entry point for the CLI."""
@@ -0,0 +1,88 @@
1
+ """Bump an Odoo module's manifest version."""
2
+
3
+ import subprocess
4
+ from pathlib import Path
5
+ from typing import Annotated
6
+
7
+ import typer
8
+
9
+ from odoo_dev.config import load_config
10
+ from odoo_dev.utils.console import error, info, success
11
+ from odoo_dev.utils.manifest import (
12
+ bump_version_string,
13
+ find_module_root,
14
+ manifest_path,
15
+ read_version,
16
+ set_version,
17
+ )
18
+
19
+
20
+ def bump(
21
+ module: Annotated[
22
+ str,
23
+ typer.Argument(help="Module name (under addons/) or path to a module dir"),
24
+ ],
25
+ level: Annotated[
26
+ str, typer.Argument(help="Bump level: patch | minor | major")
27
+ ] = "patch",
28
+ no_stage: Annotated[
29
+ bool, typer.Option("--no-stage", help="Do not git add the changed manifest")
30
+ ] = False,
31
+ ) -> None:
32
+ """Bump a module's __manifest__.py version (patch | minor | major).
33
+
34
+ Series-agnostic: only the trailing major.minor.patch segments move, never the
35
+ Odoo series prefix. Edits the manifest in place and stages it, so the bump
36
+ lands in the commit you're about to make (which the version-bump check
37
+ requires for any module you've changed).
38
+ """
39
+ if level not in ("patch", "minor", "major"):
40
+ error(f"level must be one of patch|minor|major (got {level!r})")
41
+ raise typer.Exit(2)
42
+
43
+ # Resolve the module: an explicit path, else a name under addons/.
44
+ candidate = Path(module)
45
+ root = find_module_root(candidate) if candidate.exists() else None
46
+ if root is None and candidate.is_dir():
47
+ root = candidate
48
+ if root is None:
49
+ cfg = load_config()
50
+ under_addons = cfg.addons_dir / module
51
+ if under_addons.exists():
52
+ root = under_addons
53
+ else:
54
+ error(
55
+ f"No module found for {module!r} "
56
+ f"(checked the path and {cfg.addons_dir}/{module})"
57
+ )
58
+ raise typer.Exit(1)
59
+
60
+ mf = manifest_path(root)
61
+ if mf is None:
62
+ error(f"No __manifest__.py in {root}")
63
+ raise typer.Exit(1)
64
+
65
+ # Resolve through any addons/ -> .repos/ symlink so we edit and stage the
66
+ # real file in whichever repo (parent or submodule) actually owns it.
67
+ real_mf = mf.resolve()
68
+ text = real_mf.read_text()
69
+ current = read_version(text)
70
+ if current is None:
71
+ error(f"No version string in {real_mf}")
72
+ raise typer.Exit(1)
73
+
74
+ try:
75
+ new_version = bump_version_string(current, level)
76
+ except ValueError as exc:
77
+ error(str(exc))
78
+ raise typer.Exit(1)
79
+
80
+ real_mf.write_text(set_version(text, new_version))
81
+ success(f"{root.name}: {current} -> {new_version}")
82
+
83
+ if not no_stage:
84
+ result = subprocess.run(
85
+ ["git", "add", real_mf.name], cwd=real_mf.parent, check=False
86
+ )
87
+ if result.returncode != 0:
88
+ info(f"(could not git add {real_mf}; stage it yourself)")
@@ -268,7 +268,9 @@ def test(
268
268
 
269
269
  # Determine modules to test
270
270
  if modules is None:
271
- # Use manifestoo to list addons in the addons directory
271
+ # Use manifestoo to list addons across addons/ and vendored/, so a no-args
272
+ # run tests all custom AND vendored addons together (catches vendored
273
+ # addons that break when combined with local addons or with each other).
272
274
  manifestoo_cmd = [
273
275
  str(venv_python),
274
276
  "-m",
@@ -276,6 +278,8 @@ def test(
276
278
  "--select-addons-dir",
277
279
  str(cfg.addons_dir),
278
280
  ]
281
+ if cfg.vendored_dir.is_dir():
282
+ manifestoo_cmd.extend(["--select-addons-dir", str(cfg.vendored_dir)])
279
283
  if exclude:
280
284
  manifestoo_cmd.extend(["--select-exclude", exclude])
281
285
  manifestoo_cmd.extend(["list", "--separator=,"])
@@ -287,7 +291,7 @@ def test(
287
291
  )
288
292
  modules = result.stdout.strip()
289
293
  if not modules:
290
- error(f"No addons found in {cfg.addons_dir}")
294
+ error(f"No addons found in {cfg.addons_dir} or {cfg.vendored_dir}")
291
295
  raise typer.Exit(1)
292
296
 
293
297
  # Clone external repo dependencies declared in repo_deps.yaml and symlink into addons_dir
@@ -387,9 +391,9 @@ def test(
387
391
  success(f"Running tests: {modules}")
388
392
 
389
393
  if coverage:
390
- # Build coverage source paths
394
+ # Build coverage source paths (a module may live in addons/ or vendored/)
391
395
  coverage_source = ",".join(
392
- str(cfg.addons_dir / m) for m in modules.split(",")
396
+ str(_module_dir(cfg, m)) for m in modules.split(",")
393
397
  )
394
398
 
395
399
  test_cmd = [
@@ -575,6 +579,14 @@ def _terminate_connections(db_name: str, db_config: dict[str, str]) -> None:
575
579
  )
576
580
 
577
581
 
582
+ def _module_dir(cfg, module: str) -> Path:
583
+ """Return the directory of ``module`` — under addons/ or vendored/."""
584
+ for base in (cfg.addons_dir, cfg.vendored_dir):
585
+ if (base / module).is_dir():
586
+ return base / module
587
+ return cfg.addons_dir / module
588
+
589
+
578
590
  def _get_addons_path(config_file) -> str:
579
591
  """Extract addons_path from odoo.conf."""
580
592
  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