odoo-toolkit 4.1.2__tar.gz → 4.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 (104) hide show
  1. odoo_toolkit-4.2.0/.devcontainer/Dockerfile +5 -0
  2. odoo_toolkit-4.2.0/.devcontainer/devcontainer.json +48 -0
  3. {odoo_toolkit-4.1.2 → odoo_toolkit-4.2.0}/.github/workflows/ci.yml +2 -1
  4. odoo_toolkit-4.2.0/.github/workflows/sync-odoo-ruff.yml +27 -0
  5. {odoo_toolkit-4.1.2 → odoo_toolkit-4.2.0}/.github/workflows/sync-runbot.yml +1 -1
  6. {odoo_toolkit-4.1.2 → odoo_toolkit-4.2.0}/PKG-INFO +74 -5
  7. {odoo_toolkit-4.1.2 → odoo_toolkit-4.2.0}/README.md +73 -4
  8. odoo_toolkit-4.2.0/odoo_toolkit/code.py +62 -0
  9. {odoo_toolkit-4.1.2 → odoo_toolkit-4.2.0}/odoo_toolkit/dev/run.py +31 -1
  10. {odoo_toolkit-4.1.2 → odoo_toolkit-4.2.0}/odoo_toolkit/dev/runtime.py +62 -10
  11. {odoo_toolkit-4.1.2 → odoo_toolkit-4.2.0}/odoo_toolkit/docker/dev.Dockerfile +3 -3
  12. {odoo_toolkit-4.1.2 → odoo_toolkit-4.2.0}/odoo_toolkit/docker/runbot/Docker18.Dockerfile +1 -1
  13. {odoo_toolkit-4.1.2 → odoo_toolkit-4.2.0}/odoo_toolkit/docker/runbot/Docker19.Dockerfile +1 -1
  14. {odoo_toolkit-4.1.2 → odoo_toolkit-4.2.0}/odoo_toolkit/docker/runbot/Docker191.Dockerfile +1 -1
  15. {odoo_toolkit-4.1.2 → odoo_toolkit-4.2.0}/odoo_toolkit/docker/runbot/Docker194.Dockerfile +1 -1
  16. odoo_toolkit-4.2.0/odoo_toolkit/docker/runbot/Docker20.Dockerfile +139 -0
  17. {odoo_toolkit-4.1.2 → odoo_toolkit-4.2.0}/odoo_toolkit/docker/runbot/DockerMaster.Dockerfile +1 -1
  18. {odoo_toolkit-4.1.2 → odoo_toolkit-4.2.0}/odoo_toolkit/main.py +9 -2
  19. odoo_toolkit-4.2.0/odoo_toolkit/multiverse_config/.devcontainer/devcontainer.json +47 -0
  20. odoo_toolkit-4.2.0/odoo_toolkit/multiverse_config/.vscode/extensions.json +11 -0
  21. {odoo_toolkit-4.1.2 → odoo_toolkit-4.2.0}/odoo_toolkit/multiverse_config/requirements.txt +1 -0
  22. odoo_toolkit-4.1.2/odoo_toolkit/multiverse_config/ruff.toml → odoo_toolkit-4.2.0/odoo_toolkit/multiverse_config/ruff-odoo.toml +13 -4
  23. {odoo_toolkit-4.1.2 → odoo_toolkit-4.2.0}/odoo_toolkit/mv/config.py +47 -0
  24. {odoo_toolkit-4.1.2 → odoo_toolkit-4.2.0}/odoo_toolkit/mv/remotes.py +6 -1
  25. {odoo_toolkit-4.1.2 → odoo_toolkit-4.2.0}/odoo_toolkit/mv/setup.py +235 -13
  26. odoo_toolkit-4.2.0/odoo_toolkit/update_check.py +111 -0
  27. {odoo_toolkit-4.1.2 → odoo_toolkit-4.2.0}/odoo_toolkit/versions.json +1 -1
  28. {odoo_toolkit-4.1.2 → odoo_toolkit-4.2.0}/odoo_toolkit/wl/common.py +1 -5
  29. {odoo_toolkit-4.1.2 → odoo_toolkit-4.2.0}/pyproject.toml +13 -1
  30. {odoo_toolkit-4.1.2 → odoo_toolkit-4.2.0}/tests/conftest.py +6 -0
  31. {odoo_toolkit-4.1.2 → odoo_toolkit-4.2.0}/tests/dev/test_runtime.py +84 -0
  32. {odoo_toolkit-4.1.2 → odoo_toolkit-4.2.0}/tests/mv/test_remotes.py +14 -0
  33. {odoo_toolkit-4.1.2 → odoo_toolkit-4.2.0}/tests/mv/test_setup.py +180 -1
  34. odoo_toolkit-4.2.0/tests/test_code.py +96 -0
  35. odoo_toolkit-4.2.0/tests/test_update_check.py +84 -0
  36. {odoo_toolkit-4.1.2 → odoo_toolkit-4.2.0}/uv.lock +64 -2
  37. odoo_toolkit-4.1.2/odoo_toolkit/multiverse_config/.vscode/extensions.json +0 -16
  38. {odoo_toolkit-4.1.2 → odoo_toolkit-4.2.0}/.github/dependabot.yml +0 -0
  39. {odoo_toolkit-4.1.2 → odoo_toolkit-4.2.0}/.github/workflows/images.yml +0 -0
  40. {odoo_toolkit-4.1.2 → odoo_toolkit-4.2.0}/.github/workflows/release.yml +0 -0
  41. {odoo_toolkit-4.1.2 → odoo_toolkit-4.2.0}/.gitignore +0 -0
  42. {odoo_toolkit-4.1.2 → odoo_toolkit-4.2.0}/.vscode/settings.json +0 -0
  43. {odoo_toolkit-4.1.2 → odoo_toolkit-4.2.0}/LICENSE +0 -0
  44. {odoo_toolkit-4.1.2 → odoo_toolkit-4.2.0}/images/mailpit.png +0 -0
  45. {odoo_toolkit-4.1.2 → odoo_toolkit-4.2.0}/odoo_toolkit/__init__.py +0 -0
  46. {odoo_toolkit-4.1.2 → odoo_toolkit-4.2.0}/odoo_toolkit/__main__.py +0 -0
  47. {odoo_toolkit-4.1.2 → odoo_toolkit-4.2.0}/odoo_toolkit/common.py +0 -0
  48. {odoo_toolkit-4.1.2 → odoo_toolkit-4.2.0}/odoo_toolkit/dev/__init__.py +0 -0
  49. {odoo_toolkit-4.1.2 → odoo_toolkit-4.2.0}/odoo_toolkit/dev/common.py +0 -0
  50. {odoo_toolkit-4.1.2 → odoo_toolkit-4.2.0}/odoo_toolkit/dev/db.py +0 -0
  51. {odoo_toolkit-4.1.2 → odoo_toolkit-4.2.0}/odoo_toolkit/dev/images.py +0 -0
  52. {odoo_toolkit-4.1.2 → odoo_toolkit-4.2.0}/odoo_toolkit/dev/stop.py +0 -0
  53. {odoo_toolkit-4.1.2 → odoo_toolkit-4.2.0}/odoo_toolkit/docker/compose.yaml +0 -0
  54. {odoo_toolkit-4.1.2 → odoo_toolkit-4.2.0}/odoo_toolkit/docker/pgadmin/servers.json +0 -0
  55. {odoo_toolkit-4.1.2 → odoo_toolkit-4.2.0}/odoo_toolkit/docker/runbot/Docker17.Dockerfile +0 -0
  56. {odoo_toolkit-4.1.2 → odoo_toolkit-4.2.0}/odoo_toolkit/git_cli.py +0 -0
  57. {odoo_toolkit-4.1.2 → odoo_toolkit-4.2.0}/odoo_toolkit/multiverse_config/.vscode/launch.json +0 -0
  58. {odoo_toolkit-4.1.2 → odoo_toolkit-4.2.0}/odoo_toolkit/multiverse_config/.vscode/settings.json +0 -0
  59. {odoo_toolkit-4.1.2 → odoo_toolkit-4.2.0}/odoo_toolkit/multiverse_config/.vscode/tasks.json +0 -0
  60. {odoo_toolkit-4.1.2 → odoo_toolkit-4.2.0}/odoo_toolkit/multiverse_config/build-constraints.txt +0 -0
  61. {odoo_toolkit-4.1.2 → odoo_toolkit-4.2.0}/odoo_toolkit/multiverse_config/compat-requirements.txt +0 -0
  62. {odoo_toolkit-4.1.2 → odoo_toolkit-4.2.0}/odoo_toolkit/mv/__init__.py +0 -0
  63. {odoo_toolkit-4.1.2 → odoo_toolkit-4.2.0}/odoo_toolkit/mv/cleanup.py +0 -0
  64. {odoo_toolkit-4.1.2 → odoo_toolkit-4.2.0}/odoo_toolkit/mv/link.py +0 -0
  65. {odoo_toolkit-4.1.2 → odoo_toolkit-4.2.0}/odoo_toolkit/mv/repos.py +0 -0
  66. {odoo_toolkit-4.1.2 → odoo_toolkit-4.2.0}/odoo_toolkit/mv/reset.py +0 -0
  67. {odoo_toolkit-4.1.2 → odoo_toolkit-4.2.0}/odoo_toolkit/mv/switch.py +0 -0
  68. {odoo_toolkit-4.1.2 → odoo_toolkit-4.2.0}/odoo_toolkit/po/__init__.py +0 -0
  69. {odoo_toolkit-4.1.2 → odoo_toolkit-4.2.0}/odoo_toolkit/po/cldr-common-48.2/LICENSE +0 -0
  70. {odoo_toolkit-4.1.2 → odoo_toolkit-4.2.0}/odoo_toolkit/po/cldr-common-48.2/common/dtd/ldmlSupplemental.dtd +0 -0
  71. {odoo_toolkit-4.1.2 → odoo_toolkit-4.2.0}/odoo_toolkit/po/cldr-common-48.2/common/supplemental/plurals.xml +0 -0
  72. {odoo_toolkit-4.1.2 → odoo_toolkit-4.2.0}/odoo_toolkit/po/cldr.csv +0 -0
  73. {odoo_toolkit-4.1.2 → odoo_toolkit-4.2.0}/odoo_toolkit/po/common.py +0 -0
  74. {odoo_toolkit-4.1.2 → odoo_toolkit-4.2.0}/odoo_toolkit/po/create.py +0 -0
  75. {odoo_toolkit-4.1.2 → odoo_toolkit-4.2.0}/odoo_toolkit/po/export.py +0 -0
  76. {odoo_toolkit-4.1.2 → odoo_toolkit-4.2.0}/odoo_toolkit/po/update.py +0 -0
  77. {odoo_toolkit-4.1.2 → odoo_toolkit-4.2.0}/odoo_toolkit/versions.py +0 -0
  78. {odoo_toolkit-4.1.2 → odoo_toolkit-4.2.0}/odoo_toolkit/wl/__init__.py +0 -0
  79. {odoo_toolkit-4.1.2 → odoo_toolkit-4.2.0}/odoo_toolkit/wl/authors.py +0 -0
  80. {odoo_toolkit-4.1.2 → odoo_toolkit-4.2.0}/odoo_toolkit/wl/config.py +0 -0
  81. {odoo_toolkit-4.1.2 → odoo_toolkit-4.2.0}/odoo_toolkit/wl/copy.py +0 -0
  82. {odoo_toolkit-4.1.2 → odoo_toolkit-4.2.0}/odoo_toolkit/wl/download.py +0 -0
  83. {odoo_toolkit-4.1.2 → odoo_toolkit-4.2.0}/odoo_toolkit/wl/update_teams.py +0 -0
  84. {odoo_toolkit-4.1.2 → odoo_toolkit-4.2.0}/odoo_toolkit/wl/upload.py +0 -0
  85. {odoo_toolkit-4.1.2 → odoo_toolkit-4.2.0}/scripts/build_images.py +0 -0
  86. {odoo_toolkit-4.1.2 → odoo_toolkit-4.2.0}/scripts/sync_runbot.py +0 -0
  87. {odoo_toolkit-4.1.2 → odoo_toolkit-4.2.0}/tests/__init__.py +0 -0
  88. {odoo_toolkit-4.1.2 → odoo_toolkit-4.2.0}/tests/dev/__init__.py +0 -0
  89. {odoo_toolkit-4.1.2 → odoo_toolkit-4.2.0}/tests/dev/test_common.py +0 -0
  90. {odoo_toolkit-4.1.2 → odoo_toolkit-4.2.0}/tests/dev/test_images.py +0 -0
  91. {odoo_toolkit-4.1.2 → odoo_toolkit-4.2.0}/tests/mv/__init__.py +0 -0
  92. {odoo_toolkit-4.1.2 → odoo_toolkit-4.2.0}/tests/mv/conftest.py +0 -0
  93. {odoo_toolkit-4.1.2 → odoo_toolkit-4.2.0}/tests/mv/test_config.py +0 -0
  94. {odoo_toolkit-4.1.2 → odoo_toolkit-4.2.0}/tests/mv/test_switch_reset.py +0 -0
  95. {odoo_toolkit-4.1.2 → odoo_toolkit-4.2.0}/tests/po/__init__.py +0 -0
  96. {odoo_toolkit-4.1.2 → odoo_toolkit-4.2.0}/tests/po/test_export.py +0 -0
  97. {odoo_toolkit-4.1.2 → odoo_toolkit-4.2.0}/tests/po/test_po_files.py +0 -0
  98. {odoo_toolkit-4.1.2 → odoo_toolkit-4.2.0}/tests/test_git_cli.py +0 -0
  99. {odoo_toolkit-4.1.2 → odoo_toolkit-4.2.0}/tests/test_http.py +0 -0
  100. {odoo_toolkit-4.1.2 → odoo_toolkit-4.2.0}/tests/test_versions.py +0 -0
  101. {odoo_toolkit-4.1.2 → odoo_toolkit-4.2.0}/tests/wl/__init__.py +0 -0
  102. {odoo_toolkit-4.1.2 → odoo_toolkit-4.2.0}/tests/wl/test_authors.py +0 -0
  103. {odoo_toolkit-4.1.2 → odoo_toolkit-4.2.0}/tests/wl/test_client.py +0 -0
  104. {odoo_toolkit-4.1.2 → odoo_toolkit-4.2.0}/tests/wl/test_commands.py +0 -0
@@ -0,0 +1,5 @@
1
+ FROM mcr.microsoft.com/devcontainers/python:3.12
2
+
3
+ # Claude Code keeps its login in a volume mounted here. Docker gives a new volume the owner of this folder, so the user of
4
+ # the dev container can write in it.
5
+ RUN mkdir /home/vscode/.claude && chown vscode:vscode /home/vscode/.claude
@@ -0,0 +1,48 @@
1
+ {
2
+ "name": "odoo-toolkit",
3
+ "build": {
4
+ "dockerfile": "Dockerfile"
5
+ },
6
+ "remoteUser": "vscode",
7
+ "updateRemoteUserUID": true,
8
+ "runArgs": ["--cap-drop=ALL", "--security-opt=no-new-privileges", "--init"],
9
+ "mounts": [
10
+ "source=odoo-toolkit-claude,target=/home/vscode/.claude,type=volume",
11
+ "source=${localWorkspaceFolder}/.devcontainer,target=${containerWorkspaceFolder}/.devcontainer,type=bind,readonly",
12
+ "source=${localWorkspaceFolder}/.vscode,target=${containerWorkspaceFolder}/.vscode,type=bind,readonly",
13
+ "source=${localWorkspaceFolder}/.git/hooks,target=${containerWorkspaceFolder}/.git/hooks,type=bind,readonly"
14
+ ],
15
+ "containerEnv": {
16
+ "CLAUDE_CONFIG_DIR": "/home/vscode/.claude",
17
+ "UV_PROJECT_ENVIRONMENT": "/home/vscode/.venv",
18
+ "UV_LINK_MODE": "copy"
19
+ },
20
+ "remoteEnv": {
21
+ "PATH": "/home/vscode/.venv/bin:/home/vscode/.local/bin:${containerEnv:PATH}",
22
+ "SSH_AUTH_SOCK": null
23
+ },
24
+ "onCreateCommand": "pip install --user --quiet uv && uv sync --quiet",
25
+ "postStartCommand": "git config --global --unset-all credential.helper || true",
26
+ "postAttachCommand": "rm -f /tmp/vscode-ssh-auth-*.sock",
27
+ "otherPortsAttributes": {
28
+ "onAutoForward": "ignore"
29
+ },
30
+ "customizations": {
31
+ "vscode": {
32
+ "extensions": [
33
+ "anthropic.claude-code",
34
+ "charliermarsh.ruff",
35
+ "ms-python.python",
36
+ "ms-python.debugpy",
37
+ "njpwerner.autodocstring"
38
+ ],
39
+ "settings": {
40
+ "python.defaultInterpreterPath": "/home/vscode/.venv/bin/python",
41
+ "remote.autoForwardPorts": false,
42
+ "git.terminalAuthentication": false,
43
+ "github.gitAuthentication": false,
44
+ "git.enableCommitSigning": false
45
+ }
46
+ }
47
+ }
48
+ }
@@ -15,7 +15,8 @@ jobs:
15
15
  steps:
16
16
  - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
17
17
  - uses: astral-sh/setup-uv@1c37ad07a6a961277cf70c0d37d6f313000f5884 # v10.3.0
18
- - run: uvx ruff@0.15.0 check odoo_toolkit scripts tests
18
+ - run: uv run ruff check odoo_toolkit scripts tests
19
+ - run: uv run pyright
19
20
 
20
21
  test:
21
22
  strategy:
@@ -0,0 +1,27 @@
1
+ name: Sync Odoo's Ruff rules
2
+
3
+ # Checks every week whether Odoo changed the ruff.toml of its master branch, and opens a pull request with the changes.
4
+ # `otk mv setup` copies it into the multiverse, for the repositories that don't have a version of Odoo to extend.
5
+ on:
6
+ schedule:
7
+ - cron: "0 5 * * 0"
8
+ workflow_dispatch:
9
+
10
+ permissions:
11
+ contents: write
12
+ pull-requests: write
13
+
14
+ jobs:
15
+ sync:
16
+ runs-on: ubuntu-24.04
17
+ steps:
18
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
19
+ - run: curl --fail --silent --show-error --location --output odoo_toolkit/multiverse_config/ruff-odoo.toml https://raw.githubusercontent.com/odoo/odoo/master/ruff.toml
20
+ - uses: peter-evans/create-pull-request@5f6978faf089d4d20b00c7766989d076bb2fc7f1 # v8.1.1
21
+ with:
22
+ branch: sync-odoo-ruff
23
+ commit-message: "[IMP] mv: sync Odoo's Ruff rules"
24
+ title: "[IMP] mv: sync Odoo's Ruff rules"
25
+ body: |
26
+ The ruff.toml of Odoo's master branch changed. Review the diff.
27
+ delete-branch: true
@@ -3,7 +3,7 @@ name: Sync with Runbot
3
3
  # Checks Runbot every week for new versions or changed Dockerfiles, and opens a pull request with the changes.
4
4
  on:
5
5
  schedule:
6
- - cron: "0 5 * * 1"
6
+ - cron: "0 5 * * 6"
7
7
  workflow_dispatch:
8
8
 
9
9
  permissions:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: odoo-toolkit
3
- Version: 4.1.2
3
+ Version: 4.2.0
4
4
  Summary: This toolkit contains a few useful tools for Odoo development.
5
5
  Author-email: Dylan Kiss <dyki@odoo.com>
6
6
  License-File: LICENSE
@@ -35,6 +35,9 @@ $ uv tool install --force git+https://github.com/dylankiss/odoo-toolkit.git
35
35
  > [!TIP]
36
36
  > You can run any command with the `--help` option to find out all options you can use.
37
37
 
38
+ > [!NOTE]
39
+ > At most once a day, in the background, the toolkit checks [PyPI](https://pypi.org/project/odoo-toolkit/) for a new version and tells you after the command has run. It doesn't check in scripts, in CI, or when the toolkit isn't installed from PyPI, like a linked development version. Set `OTK_NO_UPDATE_CHECK` to any value to turn it off.
40
+
38
41
  > [!TIP]
39
42
  > Anywhere you can provide modules as arguments or options, you can also use [glob patterns](https://docs.python.org/3/library/fnmatch.html) to match multiple similar ones.
40
43
 
@@ -257,6 +260,15 @@ What stays between runs:
257
260
 
258
261
  Your SSH agent (the one in `SSH_AUTH_SOCK`, like Bitwarden's or 1Password's) is available in the container, for Odoo modules that run Git commands. Your keys never leave your machine.
259
262
 
263
+ With [OrbStack](https://orbstack.dev), which provides its own agent forwarding, an agent like Bitwarden's needs an `IdentityAgent` line in your `~/.ssh/config`. Add it below any `Include` lines, then restart OrbStack:
264
+
265
+ ```
266
+ Host *
267
+ IdentityAgent "~/Library/Containers/com.bitwarden.desktop/Data/.bitwarden-ssh-agent.sock"
268
+ ```
269
+
270
+ Use the socket in your `SSH_AUTH_SOCK` for the path, which is different for other agents, like 1Password's.
271
+
260
272
  On Linux, the container runs as your user, so the files Odoo writes are yours. The images on Docker Hub run as user ID 1000, the first user on most Linux machines. With another user ID, build the image on your machine once with `--build`.
261
273
 
262
274
  > [!TIP]
@@ -271,7 +283,7 @@ It starts PostgreSQL and Mailpit if needed, and runs Odoo with:
271
283
  - the database and mail server set up,
272
284
  - no time limits, so requests don't stop while you're paused in the debugger.
273
285
 
274
- Use `--community` to only load Odoo Community, or `--exclude` to leave out repositories, like `-x enterprise`. Pass any other Odoo options after `--`. Stop Odoo with `Ctrl+C`.
286
+ Use `--community` to only load Odoo Community, or `--exclude` to leave out repositories, like `-x enterprise`. Use `--volume` and `--env` to give the container files and settings of your machine, like the configuration of a linked repository. Pass any other Odoo options after `--`. Stop Odoo with `Ctrl+C`.
275
287
 
276
288
  ### Usage
277
289
 
@@ -283,6 +295,7 @@ e.g.
283
295
  $ otk dev run -- -d mydb -i sale
284
296
  $ otk dev run --port 8070 --debug -- -d mydb
285
297
  $ otk dev run --community -- -d community-db
298
+ $ otk dev run -v ~/etc-odoo:/etc/odoo:ro -e KEY=value -- -d mydb
286
299
  ```
287
300
 
288
301
  ### Options
@@ -293,6 +306,8 @@ $ otk dev run --community -- -d community-db
293
306
  * `--upgrade`: Add the upgrade scripts of `upgrade` and `upgrade-util` to the upgrade path.
294
307
  * `--community`: Only load the modules of Odoo Community (`odoo/addons`).
295
308
  * `-x, --exclude TEXT`: Leave these repositories out of the addons path, like `enterprise` or `odoofin,design-themes`.
309
+ * `-v, --volume TEXT`: Mount a file or folder of your machine in the container, like `~/etc-odoo:/etc/odoo:ro`. Can be used several times. A path starting with `~` or `.` is made absolute, and the path must exist.
310
+ * `-e, --env TEXT`: Set an environment variable in the container, like `REQUESTS_CA_BUNDLE=/etc/ca.pem`. Can be used several times.
296
311
  * `--build`: Build the image on your machine instead of pulling it.
297
312
  * `--pull`: Pull the latest image, even if you have one already.
298
313
  * `--db-port INTEGER`: The port to reach PostgreSQL on, on your machine. Defaults to the one it runs on, or `5432`. Changing it restarts PostgreSQL.
@@ -626,7 +641,7 @@ The easiest way to set this up is by creating a directory for your multiverse se
626
641
  > [!IMPORTANT]
627
642
  > Make sure you have set up your GitHub SSH key beforehand in order to clone the repositories, and installed [`uv`](https://docs.astral.sh/uv/getting-started/installation/) and `npm`.
628
643
 
629
- You can run the command as many times as you want. It only adds what's missing, and never touches your own files. Pass `--reset-config` to recreate the Python environments and overwrite the configuration files. Without `-b` or `-r`, it updates the versions and repositories that are already set up.
644
+ You can run the command as many times as you want. It only adds what's missing, and never touches your own files. Pass `--update-config` to overwrite the configuration files with the latest ones, or `--reset-config` to recreate the Python environments as well. Without `-b` or `-r`, it updates the versions and repositories that are already set up.
630
645
 
631
646
  ### Repositories
632
647
 
@@ -697,6 +712,7 @@ After all that is done, your directory structure should look like this with the
697
712
  ├── 19.0
698
713
  │ ├── .venv (Python virtual environment)
699
714
  │ ├── .vscode (Visual Studio Code configuration)
715
+ │ ├── ruff.toml (extends the one of odoo, for the other repositories)
700
716
  │ ├── odoo (19.0)
701
717
  │ ├── enterprise (19.0)
702
718
  │ ├── design-themes (19.0)
@@ -709,11 +725,33 @@ After all that is done, your directory structure should look like this with the
709
725
  ├── upgrade (master)
710
726
  ├── upgrade-util (master)
711
727
  ├── requirements.txt
712
- └── ruff.toml
728
+ ├── ruff-odoo.toml (Odoo's Ruff rules of master)
729
+ └── ruff.toml (extends ruff-odoo.toml)
713
730
  ```
714
731
 
715
732
  Open a version directory (like `code <multiverse-folder>/19.0`) in your editor to work on that version.
716
733
 
734
+ ### Dev Container
735
+
736
+ With `--devcontainer`, every version directory gets a [dev container](https://containers.dev) configuration. Open the version in Visual Studio Code and run **Dev Containers: Reopen in Container**. The window, its extensions and any AI agent in it then only see your multiverse directory (mounted at the same path as on your machine): not your home folder, SSH keys or Git credentials.
737
+
738
+ - Opening the container starts the shared services (`otk dev db`), since it joins their network. Odoo starts from the **Odoo (container): run and debug** launch configuration, because `otk dev run` needs Docker.
739
+ - Git commands that need GitHub (fetch, pull, push) don't work in the container. Run them from a terminal on your machine.
740
+ - The `.devcontainer`, `.vscode` and `.venv` folders are read-only in the container, because they hold what runs on your machine later (like tasks and the dev container's own configuration). Change them from your machine, for example with `otk mv setup --update-config`.
741
+ - Everything else in the multiverse can be read and changed in the container, and it has network access, including to the databases of `otk dev`. Don't keep secrets or data you can't share in your multiverse or those databases if you use AI agents.
742
+ - Only windows opened in the container are isolated, not agents you start from a terminal on your machine.
743
+ - Visual Studio Code shares your Git config and credential helper with every dev container, which a project can't turn off. Add `"dev.containers.copyGitConfig": false` and `"dev.containers.gitCredentialHelperConfigLocation": "none"` to your user settings.
744
+ - Extensions and settings you want in the dev containers of this multiverse, like a spell checker, go in a `[devcontainer]` table in `multiverse.toml`, which `--update-config` applies. Extensions you want in every dev container go in the `dev.containers.defaultExtensions` user setting.
745
+
746
+ ```toml
747
+ [devcontainer]
748
+ extensions = ["streetsidesoftware.code-spell-checker"]
749
+
750
+ [devcontainer.settings]
751
+ "cSpell.allowCompoundWords" = true
752
+ ```
753
+ - Claude Code is installed, but your login on your machine stays out. Log in once in the container: it's kept in the `otk-claude` volume, which all your dev containers share, so it survives rebuilds.
754
+
717
755
  ### Usage
718
756
 
719
757
  ```console
@@ -729,8 +767,10 @@ $ otk mv setup -b 18.0 -b 19.0 -b master -r odoo -r enterprise -r upgrade -r upg
729
767
  * `-b, --branches TEXT`: The Odoo versions to set up, like `19.0` or `saas-19.2`. Defaults to the ones already set up, or every supported version for a new multiverse.
730
768
  * `-r, --repositories TEXT`: The repositories to set up, like `odoo`, or `odoo-ps/psbe-internal` for another GitHub owner. Defaults to the ones already set up, or `odoo`, `enterprise`, `design-themes`, `upgrade` and `upgrade-util` for a new multiverse, plus the ones in `multiverse.toml`.
731
769
  * `-d, --multiverse-dir PATH`: The directory to set up the multiverse in. Defaults to this directory.
732
- * `--reset-config`: Recreate the Python virtual environments and overwrite the configuration files of every version.
770
+ * `--update-config`: Overwrite the configuration files of every version, like the Visual Studio Code and dev container ones, to get the latest from the toolkit. The Python virtual environments are kept.
771
+ * `--reset-config`: Recreate the Python virtual environments as well as overwriting the configuration files of every version.
733
772
  * `--vscode / --no-vscode`: Write the Visual Studio Code configuration in each version directory. [default: `--vscode`]
773
+ * `--devcontainer / --no-devcontainer`: Write a dev container configuration in each version directory, to open it in Visual Studio Code in a container that only sees your multiverse, for example to keep AI agents away from the rest of your machine. Versions that already have one keep it up to date, unless you pass `--no-devcontainer`.
734
774
  * `--branch-suffix TEXT`: The suffix of your own branches on the `dev` remote, like `abcd` for `master-fix-xyz-abcd`. It's remembered for later commands. Defaults to the remembered one, or else the part of your Git email before the `@`.
735
775
  * `--help`: Show this message and exit.
736
776
 
@@ -837,3 +877,32 @@ $ otk mv cleanup [OPTIONS]
837
877
  * `--branch-suffix TEXT`: The suffix of your own branches on the `dev` remote, like `abcd` for `master-fix-xyz-abcd`. Defaults to the one given to `otk mv setup`, or else the part of your Git email before the `@`.
838
878
  * `-y, --yes`: Don't ask for confirmation.
839
879
  * `--help`: Show this message and exit.
880
+
881
+ ## `otk code`
882
+
883
+ Open a version directory of your multiverse in Visual Studio Code, like `otk code 19.0`.
884
+
885
+ Run it anywhere inside your multiverse. Without a version, it opens the version directory you are in. If the version has a [dev container](#otk-mv-setup) (from `otk mv setup --devcontainer`), it opens in it. With a [profile](https://code.visualstudio.com/docs/configure/profiles), the window always has your extensions and settings for Odoo, so you don't have to associate every version directory with it in Visual Studio Code. Set it once in `multiverse.toml` to use it every time:
886
+
887
+ ```toml
888
+ [code]
889
+ profile = "Odoo"
890
+ ```
891
+
892
+ It needs the `code` command: run **Shell Command: Install 'code' command in PATH** in Visual Studio Code.
893
+
894
+ ### Usage
895
+
896
+ ```console
897
+ $ otk code [OPTIONS] [VERSION]
898
+ ```
899
+
900
+ ### Arguments
901
+
902
+ * `[VERSION]`: The version to open, like `19.0`. Defaults to the version directory you are in.
903
+
904
+ ### Options
905
+
906
+ * `-p, --profile TEXT`: The Visual Studio Code profile to open it with, like `Odoo`. Defaults to `profile` in the `[code]` table of `multiverse.toml`. A profile that doesn't exist yet is created empty.
907
+ * `--local`: Open it on your machine, even if it has a dev container.
908
+ * `--help`: Show this message and exit.
@@ -22,6 +22,9 @@ $ uv tool install --force git+https://github.com/dylankiss/odoo-toolkit.git
22
22
  > [!TIP]
23
23
  > You can run any command with the `--help` option to find out all options you can use.
24
24
 
25
+ > [!NOTE]
26
+ > At most once a day, in the background, the toolkit checks [PyPI](https://pypi.org/project/odoo-toolkit/) for a new version and tells you after the command has run. It doesn't check in scripts, in CI, or when the toolkit isn't installed from PyPI, like a linked development version. Set `OTK_NO_UPDATE_CHECK` to any value to turn it off.
27
+
25
28
  > [!TIP]
26
29
  > Anywhere you can provide modules as arguments or options, you can also use [glob patterns](https://docs.python.org/3/library/fnmatch.html) to match multiple similar ones.
27
30
 
@@ -244,6 +247,15 @@ What stays between runs:
244
247
 
245
248
  Your SSH agent (the one in `SSH_AUTH_SOCK`, like Bitwarden's or 1Password's) is available in the container, for Odoo modules that run Git commands. Your keys never leave your machine.
246
249
 
250
+ With [OrbStack](https://orbstack.dev), which provides its own agent forwarding, an agent like Bitwarden's needs an `IdentityAgent` line in your `~/.ssh/config`. Add it below any `Include` lines, then restart OrbStack:
251
+
252
+ ```
253
+ Host *
254
+ IdentityAgent "~/Library/Containers/com.bitwarden.desktop/Data/.bitwarden-ssh-agent.sock"
255
+ ```
256
+
257
+ Use the socket in your `SSH_AUTH_SOCK` for the path, which is different for other agents, like 1Password's.
258
+
247
259
  On Linux, the container runs as your user, so the files Odoo writes are yours. The images on Docker Hub run as user ID 1000, the first user on most Linux machines. With another user ID, build the image on your machine once with `--build`.
248
260
 
249
261
  > [!TIP]
@@ -258,7 +270,7 @@ It starts PostgreSQL and Mailpit if needed, and runs Odoo with:
258
270
  - the database and mail server set up,
259
271
  - no time limits, so requests don't stop while you're paused in the debugger.
260
272
 
261
- Use `--community` to only load Odoo Community, or `--exclude` to leave out repositories, like `-x enterprise`. Pass any other Odoo options after `--`. Stop Odoo with `Ctrl+C`.
273
+ Use `--community` to only load Odoo Community, or `--exclude` to leave out repositories, like `-x enterprise`. Use `--volume` and `--env` to give the container files and settings of your machine, like the configuration of a linked repository. Pass any other Odoo options after `--`. Stop Odoo with `Ctrl+C`.
262
274
 
263
275
  ### Usage
264
276
 
@@ -270,6 +282,7 @@ e.g.
270
282
  $ otk dev run -- -d mydb -i sale
271
283
  $ otk dev run --port 8070 --debug -- -d mydb
272
284
  $ otk dev run --community -- -d community-db
285
+ $ otk dev run -v ~/etc-odoo:/etc/odoo:ro -e KEY=value -- -d mydb
273
286
  ```
274
287
 
275
288
  ### Options
@@ -280,6 +293,8 @@ $ otk dev run --community -- -d community-db
280
293
  * `--upgrade`: Add the upgrade scripts of `upgrade` and `upgrade-util` to the upgrade path.
281
294
  * `--community`: Only load the modules of Odoo Community (`odoo/addons`).
282
295
  * `-x, --exclude TEXT`: Leave these repositories out of the addons path, like `enterprise` or `odoofin,design-themes`.
296
+ * `-v, --volume TEXT`: Mount a file or folder of your machine in the container, like `~/etc-odoo:/etc/odoo:ro`. Can be used several times. A path starting with `~` or `.` is made absolute, and the path must exist.
297
+ * `-e, --env TEXT`: Set an environment variable in the container, like `REQUESTS_CA_BUNDLE=/etc/ca.pem`. Can be used several times.
283
298
  * `--build`: Build the image on your machine instead of pulling it.
284
299
  * `--pull`: Pull the latest image, even if you have one already.
285
300
  * `--db-port INTEGER`: The port to reach PostgreSQL on, on your machine. Defaults to the one it runs on, or `5432`. Changing it restarts PostgreSQL.
@@ -613,7 +628,7 @@ The easiest way to set this up is by creating a directory for your multiverse se
613
628
  > [!IMPORTANT]
614
629
  > Make sure you have set up your GitHub SSH key beforehand in order to clone the repositories, and installed [`uv`](https://docs.astral.sh/uv/getting-started/installation/) and `npm`.
615
630
 
616
- You can run the command as many times as you want. It only adds what's missing, and never touches your own files. Pass `--reset-config` to recreate the Python environments and overwrite the configuration files. Without `-b` or `-r`, it updates the versions and repositories that are already set up.
631
+ You can run the command as many times as you want. It only adds what's missing, and never touches your own files. Pass `--update-config` to overwrite the configuration files with the latest ones, or `--reset-config` to recreate the Python environments as well. Without `-b` or `-r`, it updates the versions and repositories that are already set up.
617
632
 
618
633
  ### Repositories
619
634
 
@@ -684,6 +699,7 @@ After all that is done, your directory structure should look like this with the
684
699
  ├── 19.0
685
700
  │ ├── .venv (Python virtual environment)
686
701
  │ ├── .vscode (Visual Studio Code configuration)
702
+ │ ├── ruff.toml (extends the one of odoo, for the other repositories)
687
703
  │ ├── odoo (19.0)
688
704
  │ ├── enterprise (19.0)
689
705
  │ ├── design-themes (19.0)
@@ -696,11 +712,33 @@ After all that is done, your directory structure should look like this with the
696
712
  ├── upgrade (master)
697
713
  ├── upgrade-util (master)
698
714
  ├── requirements.txt
699
- └── ruff.toml
715
+ ├── ruff-odoo.toml (Odoo's Ruff rules of master)
716
+ └── ruff.toml (extends ruff-odoo.toml)
700
717
  ```
701
718
 
702
719
  Open a version directory (like `code <multiverse-folder>/19.0`) in your editor to work on that version.
703
720
 
721
+ ### Dev Container
722
+
723
+ With `--devcontainer`, every version directory gets a [dev container](https://containers.dev) configuration. Open the version in Visual Studio Code and run **Dev Containers: Reopen in Container**. The window, its extensions and any AI agent in it then only see your multiverse directory (mounted at the same path as on your machine): not your home folder, SSH keys or Git credentials.
724
+
725
+ - Opening the container starts the shared services (`otk dev db`), since it joins their network. Odoo starts from the **Odoo (container): run and debug** launch configuration, because `otk dev run` needs Docker.
726
+ - Git commands that need GitHub (fetch, pull, push) don't work in the container. Run them from a terminal on your machine.
727
+ - The `.devcontainer`, `.vscode` and `.venv` folders are read-only in the container, because they hold what runs on your machine later (like tasks and the dev container's own configuration). Change them from your machine, for example with `otk mv setup --update-config`.
728
+ - Everything else in the multiverse can be read and changed in the container, and it has network access, including to the databases of `otk dev`. Don't keep secrets or data you can't share in your multiverse or those databases if you use AI agents.
729
+ - Only windows opened in the container are isolated, not agents you start from a terminal on your machine.
730
+ - Visual Studio Code shares your Git config and credential helper with every dev container, which a project can't turn off. Add `"dev.containers.copyGitConfig": false` and `"dev.containers.gitCredentialHelperConfigLocation": "none"` to your user settings.
731
+ - Extensions and settings you want in the dev containers of this multiverse, like a spell checker, go in a `[devcontainer]` table in `multiverse.toml`, which `--update-config` applies. Extensions you want in every dev container go in the `dev.containers.defaultExtensions` user setting.
732
+
733
+ ```toml
734
+ [devcontainer]
735
+ extensions = ["streetsidesoftware.code-spell-checker"]
736
+
737
+ [devcontainer.settings]
738
+ "cSpell.allowCompoundWords" = true
739
+ ```
740
+ - Claude Code is installed, but your login on your machine stays out. Log in once in the container: it's kept in the `otk-claude` volume, which all your dev containers share, so it survives rebuilds.
741
+
704
742
  ### Usage
705
743
 
706
744
  ```console
@@ -716,8 +754,10 @@ $ otk mv setup -b 18.0 -b 19.0 -b master -r odoo -r enterprise -r upgrade -r upg
716
754
  * `-b, --branches TEXT`: The Odoo versions to set up, like `19.0` or `saas-19.2`. Defaults to the ones already set up, or every supported version for a new multiverse.
717
755
  * `-r, --repositories TEXT`: The repositories to set up, like `odoo`, or `odoo-ps/psbe-internal` for another GitHub owner. Defaults to the ones already set up, or `odoo`, `enterprise`, `design-themes`, `upgrade` and `upgrade-util` for a new multiverse, plus the ones in `multiverse.toml`.
718
756
  * `-d, --multiverse-dir PATH`: The directory to set up the multiverse in. Defaults to this directory.
719
- * `--reset-config`: Recreate the Python virtual environments and overwrite the configuration files of every version.
757
+ * `--update-config`: Overwrite the configuration files of every version, like the Visual Studio Code and dev container ones, to get the latest from the toolkit. The Python virtual environments are kept.
758
+ * `--reset-config`: Recreate the Python virtual environments as well as overwriting the configuration files of every version.
720
759
  * `--vscode / --no-vscode`: Write the Visual Studio Code configuration in each version directory. [default: `--vscode`]
760
+ * `--devcontainer / --no-devcontainer`: Write a dev container configuration in each version directory, to open it in Visual Studio Code in a container that only sees your multiverse, for example to keep AI agents away from the rest of your machine. Versions that already have one keep it up to date, unless you pass `--no-devcontainer`.
721
761
  * `--branch-suffix TEXT`: The suffix of your own branches on the `dev` remote, like `abcd` for `master-fix-xyz-abcd`. It's remembered for later commands. Defaults to the remembered one, or else the part of your Git email before the `@`.
722
762
  * `--help`: Show this message and exit.
723
763
 
@@ -824,3 +864,32 @@ $ otk mv cleanup [OPTIONS]
824
864
  * `--branch-suffix TEXT`: The suffix of your own branches on the `dev` remote, like `abcd` for `master-fix-xyz-abcd`. Defaults to the one given to `otk mv setup`, or else the part of your Git email before the `@`.
825
865
  * `-y, --yes`: Don't ask for confirmation.
826
866
  * `--help`: Show this message and exit.
867
+
868
+ ## `otk code`
869
+
870
+ Open a version directory of your multiverse in Visual Studio Code, like `otk code 19.0`.
871
+
872
+ Run it anywhere inside your multiverse. Without a version, it opens the version directory you are in. If the version has a [dev container](#otk-mv-setup) (from `otk mv setup --devcontainer`), it opens in it. With a [profile](https://code.visualstudio.com/docs/configure/profiles), the window always has your extensions and settings for Odoo, so you don't have to associate every version directory with it in Visual Studio Code. Set it once in `multiverse.toml` to use it every time:
873
+
874
+ ```toml
875
+ [code]
876
+ profile = "Odoo"
877
+ ```
878
+
879
+ It needs the `code` command: run **Shell Command: Install 'code' command in PATH** in Visual Studio Code.
880
+
881
+ ### Usage
882
+
883
+ ```console
884
+ $ otk code [OPTIONS] [VERSION]
885
+ ```
886
+
887
+ ### Arguments
888
+
889
+ * `[VERSION]`: The version to open, like `19.0`. Defaults to the version directory you are in.
890
+
891
+ ### Options
892
+
893
+ * `-p, --profile TEXT`: The Visual Studio Code profile to open it with, like `Odoo`. Defaults to `profile` in the `[code]` table of `multiverse.toml`. A profile that doesn't exist yet is created empty.
894
+ * `--local`: Open it on your machine, even if it has a dev container.
895
+ * `--help`: Show this message and exit.
@@ -0,0 +1,62 @@
1
+ import shutil
2
+ import subprocess
3
+ from typing import Annotated
4
+ from urllib.parse import quote
5
+
6
+ from typer import Argument, Exit, Option
7
+
8
+ from .common import print_error
9
+ from .mv.config import NOT_IN_VERSION_DIR, find_multiverse_dir, find_version_dir, get_code_profile, get_current_dir
10
+
11
+
12
+ def code(
13
+ version: Annotated[
14
+ str | None,
15
+ Argument(help="The version to open, like `19.0`. Defaults to the version directory you are in."),
16
+ ] = None,
17
+ profile: Annotated[
18
+ str | None,
19
+ Option(
20
+ "--profile",
21
+ "-p",
22
+ help="The Visual Studio Code profile to open it with, like `Odoo`. Defaults to `profile` in the `[code]` "
23
+ "table of `multiverse.toml`. A profile that doesn't exist yet is created empty.",
24
+ ),
25
+ ] = None,
26
+ local: Annotated[
27
+ bool,
28
+ Option("--local", help="Open it on your machine, even if it has a dev container."),
29
+ ] = False,
30
+ ) -> None:
31
+ """Open :keyboard: an Odoo Multiverse version directory in Visual Studio Code.
32
+
33
+ Run this command anywhere inside your multiverse, and it opens the version directory you are in, or the one you
34
+ name. If it has a dev container (see `otk mv setup --devcontainer`), it opens in it, so the window and any AI agent
35
+ in it only see your multiverse. With a profile, the window always has your extensions and settings for Odoo,
36
+ without having to associate every version directory with it in Visual Studio Code.\n
37
+ \n
38
+ It needs the `code` command: run *Shell Command: Install 'code' command in PATH* in Visual Studio Code.
39
+ """
40
+ if not (code_command := shutil.which("code")):
41
+ print_error("The [b]code[/b] command isn't available. Run *Shell Command: Install 'code' command in PATH*.\n")
42
+ raise Exit(1)
43
+
44
+ if version:
45
+ if not (multiverse_dir := find_multiverse_dir(get_current_dir())):
46
+ print_error(NOT_IN_VERSION_DIR)
47
+ raise Exit(1)
48
+ version_dir = multiverse_dir / version
49
+ if not version_dir.is_dir() or find_version_dir(version_dir) != version_dir:
50
+ print_error(f"There is no version directory [b]{version}[/b] in [u]{multiverse_dir}[/u].\n")
51
+ raise Exit(1)
52
+ elif not (version_dir := find_version_dir()):
53
+ print_error(NOT_IN_VERSION_DIR)
54
+ raise Exit(1)
55
+
56
+ profile = profile or get_code_profile(version_dir.parent)
57
+ target = [str(version_dir)]
58
+ if not local and (version_dir / ".devcontainer" / "devcontainer.json").is_file():
59
+ # A dev container is addressed by the hex of its folder on your machine, and the folder it opens inside it.
60
+ host_path = str(version_dir).encode().hex()
61
+ target = ["--folder-uri", f"vscode-remote://dev-container+{host_path}{quote(str(version_dir))}"]
62
+ subprocess.run([code_command, *(["--profile", profile] if profile else []), *target], check=False)
@@ -21,6 +21,7 @@ from .runtime import (
21
21
  get_git_identity,
22
22
  get_ssh_agent,
23
23
  is_port_free,
24
+ normalize_volume,
24
25
  )
25
26
 
26
27
  app = Typer()
@@ -55,6 +56,24 @@ def run(
55
56
  help="Leave these repositories out of the addons path, like `enterprise` or `odoofin,design-themes`.",
56
57
  ),
57
58
  ] = EMPTY_LIST,
59
+ volume: Annotated[
60
+ list[str],
61
+ Option(
62
+ "--volume",
63
+ "-v",
64
+ help="Mount a file or folder of your machine in the container, like `~/etc-odoo:/etc/odoo:ro`. Can be "
65
+ "used several times.",
66
+ ),
67
+ ] = EMPTY_LIST,
68
+ env: Annotated[
69
+ list[str],
70
+ Option(
71
+ "--env",
72
+ "-e",
73
+ help="Set an environment variable in the container, like `REQUESTS_CA_BUNDLE=/etc/ca.pem`. Can be used "
74
+ "several times.",
75
+ ),
76
+ ] = EMPTY_LIST,
58
77
  build: Annotated[bool, Option("--build", help="Build the image on your machine instead of pulling it.")] = False,
59
78
  pull: Annotated[bool, Option("--pull", help="Pull the latest image, even if you have one already.")] = False,
60
79
  db_port: Annotated[
@@ -82,10 +101,19 @@ def run(
82
101
  \n
83
102
  The addons path contains every repository of the version directory with modules in it, like `enterprise`,
84
103
  `odoo/addons`, and linked repositories like `odoofin`. Use `--community` to only load Odoo Community, or
85
- `--exclude` to leave out repositories. Pass any other Odoo options after `--`, like `otk dev run -- -d mydb -i sale`.
104
+ `--exclude` to leave out repositories. Use `--volume` and `--env` to give the container files and settings of your
105
+ machine, like the configuration of a linked repository. Pass any other Odoo options after `--`, like
106
+ `otk dev run -- -d mydb -i sale`.
86
107
  """
87
108
  version_dir, version = get_version_dir()
88
109
  exclude = normalize_list_option(exclude)
110
+ volumes = [normalize_volume(spec) for spec in volume]
111
+ for spec in volumes:
112
+ source = spec.partition(":")[0]
113
+ # Docker would create a missing folder as `root`, instead of telling you the path is wrong.
114
+ if source.startswith("/") and not Path(source).exists():
115
+ print_error(f"Can't mount [u]{source}[/u], since it doesn't exist.\n")
116
+ raise Exit(1)
89
117
  if unknown := sorted(set(exclude) - {path.name for path in get_repo_dirs(version_dir, include_linked=True)}):
90
118
  print_warning(f"There is no [b]{'[/b], [b]'.join(unknown)}[/b] in [u]{version_dir}[/u] to leave out.\n")
91
119
 
@@ -109,6 +137,8 @@ def run(
109
137
  arch=arch,
110
138
  exclude=exclude,
111
139
  community=community,
140
+ volumes=volumes,
141
+ env=env,
112
142
  )
113
143
  command = build_run_command(
114
144
  options,
@@ -42,8 +42,13 @@ VOLUMES = {
42
42
  "otk-filestore": f"{HOME}/.local/share/Odoo",
43
43
  "otk-pycache": f"{HOME}/.cache/pycache",
44
44
  }
45
+ # Where Claude Code keeps its login in a dev container: a volume of its own, so your login on your machine stays out.
46
+ # The Odoo containers don't get it.
47
+ CLAUDE_DIR = f"{HOME}/.claude"
48
+ DEVCONTAINER_VOLUMES = {**VOLUMES, "otk-claude": CLAUDE_DIR}
45
49
  # Where your SSH agent is available in the container, for Odoo modules that run Git commands.
46
50
  SSH_AGENT_SOCKET = "/run/otk-ssh-agent.sock"
51
+ ORBSTACK_SSH_AGENT = "/run/host-services/ssh-auth.sock"
47
52
  # Mounting the multiverse at one of these paths, or at one containing them, would hide files the image needs.
48
53
  SYSTEM_PATHS = ("/bin", "/etc", HOME, "/lib", "/opt", "/root", "/sbin", "/tmp", "/usr", "/var", "/venv") # noqa: S108
49
54
 
@@ -64,6 +69,9 @@ class RunOptions:
64
69
  exclude: list[str] = field(default_factory=list[str])
65
70
  # Whether to only load Odoo Community's modules.
66
71
  community: bool = False
72
+ # More `--volume` and `--env` arguments for the container, like `/etc/odoo:/etc/odoo:ro` and `KEY=value`.
73
+ volumes: list[str] = field(default_factory=list[str])
74
+ env: list[str] = field(default_factory=list[str])
67
75
  # Run this command instead of Odoo, like `bash`.
68
76
  command: list[str] | None = None
69
77
  # The architecture to run. Another than the machine's runs emulated, which is slower.
@@ -121,12 +129,19 @@ def get_upgrade_paths(version_dir: Path) -> list[Path]:
121
129
  return [path for path in candidates if path.is_dir()]
122
130
 
123
131
 
124
- def build_odoo_command(options: RunOptions) -> list[str]:
125
- """Build the command that starts Odoo in the container."""
126
- version_dir = options.version_dir
127
- addons_paths = get_addons_paths(version_dir, exclude=options.exclude, community=options.community)
128
- command = [
129
- str(version_dir / "odoo" / "odoo-bin"),
132
+ def get_odoo_args(
133
+ version_dir: Path, *, exclude: Collection[str] = (), community: bool = False, upgrade: bool = False,
134
+ ) -> list[str]:
135
+ """Get the Odoo options that load the modules of a version and connect Odoo to the shared services.
136
+
137
+ :param version_dir: The version directory.
138
+ :param exclude: The repositories to leave out of the addons path, like `enterprise`.
139
+ :param community: Whether to only load Odoo Community's modules.
140
+ :param upgrade: Whether to add the upgrade scripts, if their repositories are linked into the version.
141
+ :return: The options, without the path of `odoo-bin`.
142
+ """
143
+ addons_paths = get_addons_paths(version_dir, exclude=exclude, community=community)
144
+ args = [
130
145
  f"--addons-path={','.join(str(path) for path in addons_paths)}",
131
146
  f"--db_host={DB_HOST}",
132
147
  "--db_user=odoo",
@@ -141,9 +156,19 @@ def build_odoo_command(options: RunOptions) -> list[str]:
141
156
  "--limit-time-cpu=99999",
142
157
  "--limit-time-real=99999",
143
158
  ]
144
- if options.upgrade and (upgrade_paths := get_upgrade_paths(version_dir)):
145
- command.append(f"--upgrade-path={','.join(str(path) for path in upgrade_paths)}")
146
- command += options.odoo_args
159
+ if upgrade and (upgrade_paths := get_upgrade_paths(version_dir)):
160
+ args.append(f"--upgrade-path={','.join(str(path) for path in upgrade_paths)}")
161
+ return args
162
+
163
+
164
+ def build_odoo_command(options: RunOptions) -> list[str]:
165
+ """Build the command that starts Odoo in the container."""
166
+ version_dir = options.version_dir
167
+ command = [
168
+ str(version_dir / "odoo" / "odoo-bin"),
169
+ *get_odoo_args(version_dir, exclude=options.exclude, community=options.community, upgrade=options.upgrade),
170
+ *options.odoo_args,
171
+ ]
147
172
  if options.debug_port:
148
173
  # Only say the debugger is ready once it listens, since Visual Studio Code attaches right away.
149
174
  launcher = (
@@ -200,12 +225,39 @@ def build_run_command(
200
225
  run += ["--env", f"PYTHONPYCACHEPREFIX={VOLUMES['otk-pycache']}"]
201
226
  for key, value in git_identity.items():
202
227
  run += ["--env", f"{key}={value}"]
228
+ for volume in options.volumes:
229
+ run += ["--volume", volume]
230
+ for variable in options.env:
231
+ run += ["--env", variable]
203
232
  run += get_ssh_agent_args(ssh_agent)
204
233
  return [*run, get_image_tag(options.version), *command]
205
234
 
206
235
 
236
+ def normalize_volume(volume: str) -> str:
237
+ """Make the path on your machine in a `--volume` absolute, since Docker takes a relative one for a volume's name.
238
+
239
+ `~` stands for your home folder, and `.` or `..` for the current folder. Names of volumes stay as they are.
240
+ """
241
+ source, separator, rest = volume.partition(":")
242
+ if separator and source.startswith(("~", ".")):
243
+ source = str(Path(source).expanduser().resolve())
244
+ return f"{source}{separator}{rest}"
245
+
246
+
247
+ def is_orbstack() -> bool:
248
+ """Check whether Docker runs on OrbStack, which can't mount the socket of your SSH agent from your machine."""
249
+ result = subprocess.run(["docker", "info", "--format", "{{.Name}}"], capture_output=True, text=True, check=False)
250
+ return result.stdout.strip() == "orbstack"
251
+
252
+
207
253
  def get_ssh_agent() -> Path | None:
208
- """Get the socket of your SSH agent (like the one of Bitwarden or 1Password), if one is running."""
254
+ """Get the socket of your SSH agent (like the one of Bitwarden or 1Password), if one is running.
255
+
256
+ OrbStack provides your agent at a fixed path in its virtual machine instead. It uses the `IdentityAgent` of your
257
+ `~/.ssh/config`, if you have one.
258
+ """
259
+ if sys.platform == "darwin" and is_orbstack():
260
+ return Path(ORBSTACK_SSH_AGENT)
209
261
  socket_path = os.environ.get("SSH_AUTH_SOCK")
210
262
  if not socket_path:
211
263
  return None