#!/bin/bash cd "$(dirname "$0")" || exit # Commands such as git pull return exit code if there are no changes # set -e if [[ -a ".env" ]]; then source .env fi help-table() { local cmd_width=26 local opt_width=20 local desc_width=78 local column="| %-${cmd_width}s | %-${opt_width}s | %-${desc_width}s |\n" printf "$column" 'Command' 'Option' 'Description' echo "|$(printf '%*s' $((cmd_width + 2)) '' | tr ' ' '-')|$(printf '%*s' $((opt_width + 2)) '' | tr ' ' '-')|$(printf '%*s' $((desc_width + 2)) '' | tr ' ' '-')|" # Env printf "$column" 'backup-env-files' '[path]' 'Archive and copy env files to target location.' printf "$column" 'copy-env' '[env][env]' 'Copy env file.' printf "$column" 'create-nextcloud-env' '[env]' 'Create env file for Nextcloud instance.' printf "$column" 'create-odoo-env' '[env]' 'Create env file for Odoo instance.' printf "$column" 'edit-env' '[env]' 'Open env file in default editor.' printf "$column" 'list-env' '' 'List env files.' printf "$column" 'load-dotenv' '' 'Restore content of .env from pass entry.' printf "$column" 'load-env' '[env]' 'Load and export env file.' printf "$column" 'remove-env' '[env]' 'Remove environment config.' printf "$column" 'remove-venv' '' 'Remove virtualenv.' printf "$column" 'rename-env' '[env][env]' 'Rename env file.' printf "$column" 'restore-env-files' '[path]' 'Extract and copy env files from backup file.' printf "$column" 'save-dotenv' '' 'Store content of .env in pass entry.' printf "$column" 'show-env' '[env]' 'Output content of the env file.' # Git printf "$column" 'aggregate-git-folders' '[path]' 'Run gitaggregate for the provide repos.yaml.' printf "$column" 'add-git-submodule' '[url] [path]' 'Add git submodule.' printf "$column" 'checkout' '[version]' 'Checkout Odoo version.' printf "$column" 'checkout-git-folder' '' 'Checkout git commit.' printf "$column" 'commit-git-folder' '[message][path]' 'Commit all changes in path.' printf "$column" 'clean-git-folder' '' 'Clean git folder.' printf "$column" 'clone-git-folder' '[submodule][version]' 'Clone git folder listed in the .gitmodules file.' printf "$column" 'create-git-feature-branch' '[path]' 'Create feature branch for Odoo module.' printf "$column" 'create-git-mig-branch' '[path]' 'Create migration branch for Odoo module.' printf "$column" 'download-git-folder' '[grep]' 'Download git folder listed in the .gitmodules file.' printf "$column" 'status-git-folder' '[path]' 'Show status for git folder in path.' printf "$column" 'list-git-folder' '[grep]' 'List path and url of git folders.' printf "$column" 'pull-git-folder' '' 'Pull all git folders listed in the .gitmodules file.' printf "$column" 'push-git-folder' '' 'Push all git folders in path.' printf "$column" 'remove-git-folders' '' 'Delete all git folders.' printf "$column" 'remove-git-submodule' '[path]' 'Remove a git submodule.' printf "$column" 'ls-git-folder' '[grep]' 'List git folders path space separated.' printf "$column" 'reset-git-folder' '' 'Abort rebase and reset submodules listed in the .gitmodules file.' printf "$column" 'stage-git-folder' '[path]' 'Stage all files in git folders in path.' printf "$column" 'switch-git-folder' '[version]' 'Switch branch for all git folders listed in the .gitmodules file.' printf "$column" 'sync-git-folder' '' 'Switch, stash and pull all git folders.' # Docker printf "$column" 'archive-docker-tags' '' 'Archive Docker image tags on hub older than 1 year.' printf "$column" 'build' '[folder][output]' 'Build Docker image from target folder.' printf "$column" 'login-docker' '[user][token]' 'Setup Docker Hub login credentials.' printf "$column" 'login-podman' '[user][token]' 'Login into registry with podman.' printf "$column" 'container-ps' '' 'List container processes.' printf "$column" 'push' '[folder]' 'Publish Docker image from target folder.' printf "$column" 'remove' '[name]' 'Remove containers and volumes.' printf "$column" 'test-image' '[folder]' 'Test Docker image from target folder.' # Vuepress printf "$column" 'build-vuepress' '' 'Create Vuepress build.' printf "$column" 'install-vuepress' '' 'Install Node build dependencies.' printf "$column" 'dev-vuepress' '' 'Start Vuepress development server.' printf "$column" 'serve-vuepress' '' 'Serve Vuepress build.' # Database printf "$column" 'change-uuid' '[env]' 'Change database uuid via xmlrpc.' printf "$column" 'clear-assets' '[db]' 'Clear all assets of Odoo database.' printf "$column" 'clear-filestore' '[db]' 'Clear local filestore folder. No param will clear all filestores.' printf "$column" 'clear-views' '[db]' 'Clear all views of Odoo database.' printf "$column" 'cloc-odoo' '[db]' 'Count custom line of codes.' printf "$column" 'drop-db' '[db]' 'Drop target Odoo database.' printf "$column" 'init-db' '[db]' 'Initialize the Odoo database.' printf "$column" 'load-language' '[db][lang]' 'Install language package in Odoo db.' printf "$column" 'patch-database' '[db][path]' 'Apply sql file to database.' printf "$column" 'reset-views' '[db][key]' 'Execute hard reset on views matching keys.' printf "$column" 'set-admin' '[db]' 'Sets the password for the first user in database.' printf "$column" 'update-module' '[db][name,path]' 'Update target Odoo module.' printf "$column" 'update-module-list' '[db]' 'Update module list of Odoo database.' # Revision printf "$column" 'checkout-latest-revision' '[version]' 'Checkout the latest revision of the Odoo version.' printf "$column" 'checkout-revision' '[revision]' 'Load Odoo revision env var and checkout git folders.' printf "$column" 'commit-and-push-revision' '[revision]' 'Commit all changes and tag with current revision.' printf "$column" 'create-revision' '[revision]' 'Create new Odoo revision.' printf "$column" 'list-revision' '' 'List available Odoo revisions.' printf "$column" 'load-latest-revision' '[version]' 'Load the latest revision of the Odoo version.' printf "$column" 'load-revision' '[revision]' 'Load env var from specified revision.' printf "$column" 'show-revision' '[revision]' 'Show references of Odoo revision.' # LLM printf "$column" 'commit-with-llm' '' 'Commit with llm generated commit message.' printf "$column" 'show-odoo-mcp-config' '' 'Show the Odoo MCP server config.' printf "$column" 'update-with-llm' '[glob][prompt]' 'Feed module files with prompt to LLM and apply file changes.' # Snippets printf "$column" 'create-snippet' '[id]' 'Create snippet from template.' printf "$column" 'install-snippet' '[env][path]' 'Install snippet definition.' printf "$column" 'disable-snippet' '[env][path]' 'Disable snippet definition.' printf "$column" 'lint-snippets' '' 'Run checks for all snippets.' printf "$column" 'remove-snippet' '[env][path]' 'Remove snippet definition.' printf "$column" 'update-snippet' '[env][path]' 'Update snippet definition.' # Setup printf "$column" 'activate-venv' '' 'Activate virtualenv.' printf "$column" 'init-venv' '' 'Initialize python virtual env.' printf "$column" 'disable-mailserver' '[env]' 'Disable mail server settings via xmlrpc.' printf "$column" 'install' '' 'Install Odoo requirements in source folder.' printf "$column" 'install-odoo-scripts' '' 'Install Odoo scripts.' printf "$column" 'install-requirements' '[db][path]' 'Install python packages from requirements.txt.' printf "$column" 'list-packages' '' 'List installed python packages.' printf "$column" 'init-module' '[db][path,module]' 'Initialize Odoo module.' printf "$column" 'set-addons-path' '' 'Set Odoo addons path env variable.' # Migration printf "$column" 'export-website-data' '[env]' 'Export website data from Odoo database.' printf "$column" 'import-csv' '[db][path]' 'Import data from csv. Filename must match PostgreSQL table name.' printf "$column" 'import-website-data' '[env]' 'Import website data to Odoo database.' # Generate printf "$column" 'generate-module-docs' '[path]' 'Generate readme file for module with OCA tools.' printf "$column" 'generate-module-model' '[path][model]' 'Generate model in module folder.' printf "$column" 'generate-module-inherit' '[path][model]' 'Generate inherited model in module folder.' printf "$column" 'generate-module-views' '[path][model]' 'Generate model views in module folder.' printf "$column" 'generate-module-security' '[path][model]' 'Generate model access file.' printf "$column" 'generate-module-snippet' '[path][model][ref]' 'Generate snippet for referefenced view.' printf "$column" 'generate-module-wizard' '[path][model]' 'Generate wizard for a model in module folder.' printf "$column" 'generate-module' '[path]' 'Create new Odoo module from template.' printf "$column" 'generate-module-repo' '[path]' 'Initialize Odoo module repo from template.' # Module printf "$column" 'lint-module' '[path]' 'Run pylint odoo for module.' printf "$column" 'lint-module-repo' '[path]' 'Run pylint odoo for modules in repo folder.' printf "$column" 'list-modules' '[path]' 'Get modules in path as bash array.' printf "$column" 'pytest-module' '[path]' 'Run module tests with pytest.' printf "$column" 'release-module' '[path]' 'Create GitHub release for a module.' printf "$column" 'remove-module' '[db][name]' 'Remove target Odoo module.' printf "$column" 'test-module' '[path]' 'Test target Odoo module.' printf "$column" 'test-modules' '[path]' 'Test target Odoo modules in repo folder.' printf "$column" 'translate-module' '[path][lang][db]' 'Generate translation for Odoo module.' printf "$column" 'upload-module' '[env][path]' 'Zip and upload Odoo module.' printf "$column" 'visualize-dependencies' '[path]' 'Generate visualizations of module dependencies.' printf "$column" 'zip-module' '[path]' 'Create zip file for module.' # Helper printf "$column" 'generate-admin-password' '[pass]' 'Generate hash for Odoo master password.' printf "$column" 'generate-ssh-keys' '' 'Generate ssh key pair.' printf "$column" 'generate-pg-ssl-keys' '' 'Generate PostgreSQL SSL key material.' printf "$column" 'get-addons-path' '' 'Output addons path.' printf "$column" 'get-modules' '[path][option]' 'Get list of modules in path. Option is '\''basename'\'' or '\''dirname'\''.' printf "$column" 'get-module-version' '[path]' 'Get module version from manifest.' printf "$column" 'get-module-checksum' '[path]' 'Get module checksum.' printf "$column" 'help' '[grep]' 'Show help for commands.' printf "$column" 'info' '' 'Show values of project env vars.' printf "$column" 'load-ssh-key' '' 'Load SSH private key from env var.' printf "$column" 'update-module-license' '[grep]' 'Update LICENSE file for each matching module.' # Run printf "$column" 'debug' '[name]' 'Debugg application. Options: source.' printf "$column" 'exec' '[name][cmd]' 'Run command in container.' printf "$column" 'logs' '[name]' 'Tail container logs. Default is '\''odoo'\''.' printf "$column" 'odoocli' '[param]' 'Execute odoocli cli.' printf "$column" 'psql' '[db]' 'Start interactive psql shell.' printf "$column" 'restart' '[name]' 'Restart container.' printf "$column" 'run' '[name][cmd]' 'Run container with command.' printf "$column" 'shell' '[db][code]' 'Start interactive odoo shell or run code.' printf "$column" 'source' '' 'Source the Python virtual env.' printf "$column" 'start' '[name][db]' 'Start application. Options: none, admin, db, mailgate, mailpit, source, odoo, hatch.' printf "$column" 'stop' '[name]' 'Stop containers.' printf "$column" 'test-xmlrpc' '[env]' 'Test json rpc connection.' # Project printf "$column" 'lint' '' 'Run precommit for this project.' printf "$column" 'list-versions' '' 'List available Odoo versions.' printf "$column" 'check-version' '' 'Check if Odoo source is the correct version.' printf "$column" 'load-version' '[version]' 'Load git refs from version folder.' printf "$column" 'save-version' '' 'Save git folder refs to version folder.' printf "$column" 'template-compose' '' 'Template the Docker compose file.' printf "$column" 'template-odoo-rc' '' 'Template the Odoo config file.' printf "$column" 'template-repo' '[path]' 'Update the repo folder from template.' printf "$column" 'test-project' '[clean]' 'Run tests for this project.' printf "$column" 'version' '' 'Show version of required tools.' # Performance printf "$column" 'record-with-memray' '[name]' 'Record application memory usage with memray. Options: source.' printf "$column" 'record-with-py-spy' '[pid]' 'Record and create flamechart for a process.' # Docs printf "$column" 'update-docs' '' 'Update all project docs.' printf "$column" 'update-help-doc' '' 'Write help table to task.md file.' printf "$column" 'update-modules-doc' '' 'Update modules docs file.' printf "$column" 'update-revisions-doc' '' 'Update revisions doc file.' printf "$column" 'update-snippets-doc' '' 'Update snippets doc file.' # Upgrade printf "$column" 'migrate-module' '[path]' 'Migrate module code from to target Odoo version.' printf "$column" 'run-enterprise-upgrade' '[db][version][mode]' 'Run the Odoo Enterprise upgrade scripts. Default mode ist '\''test'\''.' printf "$column" 'run-open-upgrade' '[db][version]' 'Run the Odoo OpenUpgrade scripts.' } help() { echo if [[ -n "$1" ]]; then help-table | grep -i "$1" | column -t -s'|' else echo 'task [options]' echo echo 'commands:' echo help-table fi echo } # Static env vars PASS_ENTRY=$(echo "env/${PWD##*/}" | tr '[:upper:]' '[:lower:]') ODOO_RC="odoo.conf" TASK_CONFIG_DIR="$HOME/.config/odoo-build" export PYTHONPATH="$PWD/odoo" # Required so upgrade-util does not overwrite the odoo namespace ODOO_VERSION=$(echo "$ODOO_REVISION" | cut -d'.' -f1-2) CONTAINER_CONFIG="$HOME/.docker/$CONTAINER_REGISTRY" # Updateable env vars ORG_NAME=${ORG_NAME:=Mint-System} ODOO_DATABASE=${ODOO_DATABASE:="$ODOO_VERSION"} WITHOUT_DEMO=${WITHOUT_DEMO:=False} ODOO_LANGUAGE=${ODOO_LANGUAGE:=de_CH} ODOO_PORT=${ODOO_PORT:=8069} POSTGRES_IMAGE=${POSTGRES_IMAGE:=pgvector/pgvector:pg16-trixie} POSTGRES_PORT=${POSTGRES_PORT:=5432} POSTGRES_SSL=${POSTGRES_SSL:=on} PGSSLMODE=${PGSSLMODE:=prefer} LOG_LEVEL=${LOG_LEVEL:=info} CONTAINER_REGISTRY=${CONTAINER_REGISTRY:=mintsystem} PLATFORM=${PLATFORM:=linux/amd64} BROWSER_OPEN=${BROWSER_OPEN:=true} CONTAINER_ENGINE=${CONTAINER_ENGINE:=docker} COMPOSE_COMMAND=${COMPOSE_COMMAND:=docker compose} LLM_MODEL=${LLM_MODEL:=llama} SMTP_SERVER=${SMTP_SERVER:=mailpit} SMTP_PORT=${SMTP_PORT:=1025} SMTP_SSL=${SMTP_SSL:=False} EMAIL_FROM=${EMAIL_FROM:=info@yourcompany.com} MAIL_ALIAS_DOMAIN=${MAIL_ALIAS_DOMAIN:=yourcompany.com} LIST_DB=${LIST_DB:=True} CONTAINER_TAG="odoo:$ODOO_REVISION" ODOO_INIT_LOGIN=${ODOO_INIT_LOGIN:=admin} ODOO_INIT_PASSWORD=${ODOO_INIT_PASSWORD:=admin} # Conditional env vars if [[ "$CONTAINER_ENGINE" == "podman" ]]; then COMPOSE_COMMAND="podman-compose" fi if [[ "$(uname)" == "Darwin" ]]; then OS_RELEASE="Darwin" PYTHON_NOTIFY="" OS_OPEN="open" else OS_RELEASE=$(awk -F= '/^NAME/{print $2}' /etc/os-release | tr -d '"') PYTHON_NOTIFY="inotify" OS_OPEN="xdg-open" fi if [ "$WITHOUT_DEMO" = "True" ]; then ODOO_PARAM="--without-demo=all" fi # Check commands if ! (command -v grep &> /dev/null && grep --version | grep -q GNU) && [[ "$OS_RELEASE" =~ ^(Darwin)$ ]]; then echo 'Error: On Darwin systems, GNU grep must be installed.' exit 1 fi if ! command -v envsubst &> /dev/null; then echo 'Error: '\''envsubst'\'' is not available. Install '\''gettext'\'' package.' exit 1 fi # Import commands clone-taskfile(){ if [[ ! -d "$HOME/taskfile.build" ]]; then echo -e '\033[38;5;214mGit\033[0m: Clone taskfile repo' git clone https://git.taskfile.build "$HOME/taskfile.build" else echo -e '\033[38;5;214mGit\033[0m: Pull taskfile repo' git -C "$HOME/taskfile.build" pull fi } if [[ -d "$HOME/taskfile.build/bin" ]]; then for file in "$HOME/taskfile.build/bin/"*; do if [[ -f "$file" ]]; then source "$file" fi done fi # Help commands info() { set-addons-path echo "OS Release: $OS_RELEASE" echo "Pass Entry: $PASS_ENTRY" echo "Odoo Revision: $ODOO_REVISION" echo "Odoo Version: $ODOO_VERSION" echo "Odoo Port: $ODOO_PORT" echo "Postgres Port: $POSTGRES_PORT" echo "Odoo Language: $ODOO_LANGUAGE" echo "Container Registry: $CONTAINER_REGISTRY" echo "Container Config: $CONTAINER_CONFIG" echo "Addons Path: $ADDONS_PATH" } version() { uv --version wkhtmltopdf -V if [[ "$CONTAINER_ENGINE" == "docker" ]]; then docker -v docker compose version else podman --version podman-compose --version fi } generate-admin-password() { if [[ -z "$1" ]]; then echo '\$1 is empty.'; exit; fi export ODOO_PASSWORD="$1" bin/hash-password } show-odoo-mcp-config() { echo 'Add this config to your IDE: ' cat << EOF { "mcpServers": { "odoo": { "command": "$PWD/venv$ODOO_VERSION/bin/python", "args": ["$PWD/images/odoo-mcp/bin/server"], "env": { "ODOO_URL": "http://localhost:8069", "ODOO_DATABASE": "$ODOO_DATABASE", "ODOO_USERNAME": "admin", "ODOO_PASSWORD": "admin" } } } } EOF } # Env commands init-venv() { echo "Ensure Python version $(cat .python-version) is installed." uv python install if [[ ! -d ".venv$ODOO_VERSION" ]]; then echo "Init .venv$ODOO_VERSION with $(uv --version)." uv venv ".venv$ODOO_VERSION" fi } activate-venv() { if [[ -f ".venv$ODOO_VERSION/bin/activate" ]]; then source ".venv$ODOO_VERSION/bin/activate" else echo "Could not find .venv$ODOO_VERSION/bin/active. Use init-venv to initalize." fi } remove-venv() { echo "Remove virtualenv venv$ODOO_VERSION." rm -rf "venv$ODOO_VERSION" } get-addons-path() { set-addons-path echo "$ADDONS_PATH" } template-odoo-rc() { echo "Template $ODOO_RC" # Load and export vars defined in odoo conf template for var in $(grep -oE '\$([A-Z_][A-Z0-9_]*)|\$\{([A-Z_][A-Z0-9_]*)\}' odoo.conf.template); do var=${var/\$/} var=${var//\{\//} export $var done local ir_config_parameter=${IR_CONFIG_PARAMETER:-""} if [[ -n "$GIT_SSH_PRIVATE_KEY" ]]; then local git_ssh_private_key=$(echo -e "$GIT_SSH_PRIVATE_KEY" | base64 -w0) ir_config_parameter+=$'\n'"git.ssh_private_key = \"$git_ssh_private_key\"" fi if [[ -n "$GIT_SSH_PUBLIC_KEY" ]]; then ir_config_parameter+=$'\n'"git.ssh_public_key = \"$GIT_SSH_PUBLIC_KEY\"" fi if [[ -n "$BASE_URL_WEBSITE" ]]; then ir_config_parameter+=$'\n'"base_url_website = \"$BASE_URL_WEBSITE\"" fi if [[ -n "$BASE_URL_APP" ]]; then ir_config_parameter+=$'\n'"base_url_app = \"$BASE_URL_APP\"" fi if [[ -n "$UPLOADCARE_PUBLIC_KEY" ]]; then ir_config_parameter+=$'\n'"uploadcare.public_key = \"$UPLOADCARE_PUBLIC_KEY\"" fi if [[ -n "$UPLOADCARE_SECRET_KEY" ]]; then ir_config_parameter+=$'\n'"uploadcare.secret_key = \"$UPLOADCARE_SECRET_KEY\"" fi if [[ -n "$KEYCLOAK_BASE_URL" ]]; then ir_config_parameter+=$'\n'"keycloak.base_url = \"$KEYCLOAK_BASE_URL\"" fi if [[ -n "$KEYCLOAK_CLIENT_ID" ]]; then ir_config_parameter+=$'\n'"keycloak.client_id = \"$KEYCLOAK_CLIENT_ID\"" fi if [[ -n "$KEYCLOAK_CLIENT_SECRET" ]]; then ir_config_parameter+=$'\n'"keycloak.client_secret = \"$KEYCLOAK_CLIENT_SECRET\"" fi if [[ -n "$KEYCLOAK_REALM" ]]; then ir_config_parameter+=$'\n'"keycloak.realm = \"$KEYCLOAK_REALM\"" fi export IR_CONFIG_PARAMETER="$ir_config_parameter" envsubst < "odoo.conf.template" > "$ODOO_RC" } set-addons-path() { # Read addons path from git module local git_folders_path="$(git config --file .gitmodules --get-regexp path | awk '{ print $2 }' | sort | tr '\n' ',' | sed 's/,*$//g')" # Remove excluded addons local excludes="addons/theme_mint_system addons/company addons/web" for exclude in $excludes; do git_folders_path=$(echo "$git_folders_path" | sed "s|${exclude},||g") done # Check if addons dir exist for addon in $git_folders_path; do if [[ -d "$addon" ]]; then echo "Path does not exist: $addon" exit 1 fi done # Convert to source paths local addons_path="$(echo $git_folders_path | sed 's|,odoo|,odoo/addons|g')" # Append paths from config env var if [[ -n "$ODOO_ADDONS_PATH" ]]; then addons_path="${ODOO_ADDONS_PATH},${addons_path}" fi # Export the addons path to make it available to other functions export ADDONS_PATH="$addons_path" } load-ssh-key() { if [[ -n "$GIT_SSH_PRIVATE_KEY" ]]; then echo 'Setup SSH key from env var.' local decoded_git_ssh_private_key=$(echo -e "$GIT_SSH_PRIVATE_KEY" | base64 -d) mkdir -p ~/.ssh echo "$decoded_git_ssh_private_key" > ~/.ssh/id_ed25519 chmod 600 ~/.ssh/id_ed25519 eval "$(ssh-agent -s)" ssh-add ~/.ssh/id_ed25519 || (echo 'Dumping ~/.ssh/id_ed25519 content:' && cat ~/.ssh/id_ed25519) else echo 'The SSH key env var is empty.' exit 1 fi } # Docs commands lint() { echo "Run pre-commit in $PWD" pre-commit run } update-help-doc() { echo 'Pipe help table into task.md.' help-table > task.md } update-snippets-doc() { ./bin/update-snippets-doc } update-revisions-doc() { echo 'Update revisions doc file.' rm -f "revisions.md" echo -e "# Odoo Revisions \n\ A Odoo revision is a snapshot of git references of the Odoo source and modules at a specific date.\n\ For each major release there are multiple revisions.\n" > "revisions.md" declare -A revisions local path_url="https://github.com/${ORG_NAME}/Odoo-Build/tree/main" for file in $(ls revisions/ | sort -rV); do local revision="$file" local version=$(echo "$revision" | cut -d'.' -f1-2) if [[ -z "${revisions[$version]}" ]]; then revisions[$version]="## $version" fi revisions[$version]+=$'\n\n' revisions[$version]+="#### $revision" revisions[$version]+=$'\n\n```bash\n' revisions[$version]+=$(cat "revisions/$file") revisions[$version]+=$'\n```' done for revision in $(echo "${!revisions[@]}" | tr ' ' '\n' | sort -rV); do echo -e "${revisions[$revision]}\n" >> "revisions.md" done } update-modules-doc() { echo 'Update module doc file.' ./bin/update-modules-doc } update-docs() { update-modules-doc update-revisions-doc update-snippets-doc update-help-doc } update-module-license() { if [[ -z "$1" ]]; then echo '\$1 is empty.'; exit; fi local git_folders=$(list-module "$1") for git_folder in $git_folders; do echo "Update license file for $git_folder." cp templates/module/LICENSE "$git_folder/" done } template-repo(){ if [[ -z "$1" ]]; then echo '\$1 is empty.'; exit; fi local repo_path="$1" echo "Copy task file to $repo_path." cp "templates/task" "$repo_path" echo "Template README.md to $repo_path." export REPO_FOLDER=$(basename "$repo_path") export REPO_MODEL=${REPO_FOLDER%%_*} export REPO_TITLE=$(echo "$REPO_FOLDER" | sed 's/_/ /g' | sed 's/\b\(.\)/\u\1/g') export REPO_NAME=$(echo "$REPO_FOLDER" | sed 's/_/-/g' | sed 's/\b\(.\)/\u\1/g') envsubst < "templates/README.md" > "$repo_path/README.md" echo "Copy $ODOO_VERSION template files to $repo_path." cp -r "templates/$ODOO_VERSION/." "$repo_path" echo 'Remove deprecated repo files.' rm -f "$repo_path/.flake8" rm -f "$repo_path/.isort.cfg" rm -f "$repo_path/.pylintrc-mandatory" } # Module commands get-modules() { if [[ -z "$1" ]]; then echo '\$1 is empty.'; exit; fi local option="$2" if [[ -z "$option" ]]; then option="dirname" fi local modules if [[ "$option" == "dirname" ]]; then modules=$(echo "$1" | tr "," "\n" | xargs -I {} find {} -type f -name "__manifest__.py" | xargs grep -l -iE "version.*['\"]([0-9]+\.[0-9]+['\"]|${ODOO_VERSION})" | xargs -r dirname | sort -u | tr "\n" "," | sed 's/,$//') fi if [[ "$option" == "basename" ]]; then modules=$(echo "$1" | tr "," "\n" | xargs -I {} find {} -type f -name "__manifest__.py" | xargs grep -l -iE "version.*['\"]([0-9]+\.[0-9]+['\"]|${ODOO_VERSION})" | xargs -r dirname | xargs -r -I {} basename {} | sort -u | tr "\n" "," | sed 's/,$//') fi echo "$modules" } get-module-version() { if [[ -z "$1" ]]; then echo '\$1 is empty.' else # Get version of module local version=$(grep -m 1 \"version "$1/__manifest__.py" | sed "s;';\";g" | sed "s/,//g" | sed 's/#.*//g') version=$(echo "{ $version }" | jq .version | sed 's/"//g' | sed 's/null//g') # Set default version [[ -z "$version" ]] && version=0.0 local count_dots=$(echo "$version" | grep -o "\." | wc -l) # Check if oca version or enterprise version if [[ $count_dots == 2 ]]; then version="$ODOO_VERSION.$version" fi if [[ $count_dots == 1 ]]; then version="$ODOO_VERSION.$version" fi echo "$version" fi } get-module-checksum() { if [[ -z "$1" ]]; then echo '\$1 is empty.' else bin/get-module-checksum "$1" fi } list-modules() { if [[ -z "$1" ]]; then echo '\$1 is empty.'; exit; fi local modules=$(get-modules "$1" dirname) echo "$modules" | tr ',' '\n' } # Container commands archive-docker-tags() { export CONTAINER_CONFIG python bin/archive-docker-tags } template-compose() { echo 'Template compose.yml' # Load and export vars defined in compose template for var in $(grep -oE '\$\{?[A-Z_][A-Z0-9_]*\}?' compose.yml.template); do # Strip $, ${, } var=${var#\$\{} # Remove leading ${ var=${var%\}} # Remove trailing } var=${var#\$} # Remove leading $ export "$var" done envsubst < "compose.yml.template" > "compose.yml" } container-ps() { $CONTAINER_ENGINE ps } build() { if [[ -z "$1" ]]; then echo '$1 is empty.' exit fi # Set container tag based on Dockerfile local manifest_file local latest_tag if [[ "$1" == "images/odoo" ]]; then manifest_file="images/odoo/Dockerfile" latest_tag="odoo:$ODOO_VERSION" fi if [[ "$1" == "images/odoo-mailgate" ]]; then manifest_file="images/odoo-mailgate/Dockerfile" CONTAINER_TAG="odoo-mailgate" latest_tag="odoo-mailgate:latest" elif [[ "$1" == "images/odoo-upgrade" ]]; then manifest_file="images/odoo-upgrade/Dockerfile" CONTAINER_TAG="odoo-upgrade" latest_tag="odoo-upgrade:latest" elif [[ "$1" == "images/odoocli" ]]; then manifest_file="images/odoocli/Dockerfile" CONTAINER_TAG="odoocli" latest_tag="odoocli:latest" elif [[ "$1" == "images/odoo-mcp" ]]; then manifest_file="images/odoo-mcp/Dockerfile" CONTAINER_TAG="odoo-mcp" latest_tag="odoo-mcp:latest" fi # Load Odoo revision local odoo_date=$(echo "$ODOO_REVISION" | cut -d'.' -f3-) source "revisions/$ODOO_REVISION" # Select Python version case "$ODOO_VERSION" in "15.0") local python_version="3.9" PLATFORM="linux/amd64" ;; "16.0") local python_version="3.11" ;; "17.0") local python_version="3.11" ;; "18.0") local python_version="3.12" ;; "19.0") local python_version="3.13" ;; *) local python_version="3.12" ;; esac local docker_output="--load" if [[ -n "$2" ]]; then docker_output="$2" fi # Output configuration echo "Python Version: $python_version" echo "Platform: $PLATFORM" echo "Docker Output: $docker_output" echo "Remove container image with tag ${CONTAINER_REGISTRY}/${CONTAINER_TAG}" docker image rm -f "${CONTAINER_REGISTRY}/${CONTAINER_TAG}" if ! docker buildx inspect odoo_builder >/dev/null 2>&1; then docker buildx create --name odoo_builder --use docker buildx inspect --bootstrap else docker buildx use odoo_builder fi echo "Run Docker build ${CONTAINER_REGISTRY}/${CONTAINER_TAG} for ${PLATFORM}" SOURCE_DATE_EPOCH=$(git -C odoo log -1 --pretty=%ct) docker buildx build --platform "$PLATFORM" . \ --file "$manifest_file" \ --build-arg PYTHON_VERSION="$python_version" \ --build-arg ODOO_VERSION="$ODOO_VERSION" \ --build-arg ODOO_DATE="$odoo_date" \ --build-arg ODOO_ENTERPRISE_REF="$ODOO_ENTERPRISE_REF" \ --tag "${CONTAINER_REGISTRY}/${CONTAINER_TAG}" \ --tag "${CONTAINER_REGISTRY}/${latest_tag}" \ "$docker_output" } load-latest-revision() { if [[ -n "$1" ]]; then ODOO_VERSION="$1" fi ODOO_REVISION=$(list-revision | grep "$ODOO_VERSION" | tail -n1) load-revision "$ODOO_REVISION" } checkout-latest-revision() { if [[ -n "$1" ]]; then ODOO_VERSION="$1" fi ODOO_REVISION=$(list-revision | grep "$ODOO_VERSION" | tail -n1) checkout-revision "$ODOO_REVISION" check-version } container-login() { if [[ -n "$1" ]]; then CONTAINER_REGISTRY_USERNAME="$1" fi if [[ -n "$2" ]]; then CONTAINER_REGISTRY_PASSWORD="$2" fi echo "Docker login with username $CONTAINER_REGISTRY_USERNAME." echo "$CONTAINER_REGISTRY_PASSWORD" | docker --config "$CONTAINER_CONFIG" login --username "$CONTAINER_REGISTRY_USERNAME" --password-stdin } login-podman() { if [[ -n "$1" ]]; then CONTAINER_REGISTRY_USERNAME="$1" fi if [[ -n "$2" ]]; then CONTAINER_REGISTRY_PASSWORD="$2" fi echo "Podman login with username $CONTAINER_REGISTRY_USERNAME." podman login --username "$CONTAINER_REGISTRY_USERNAME" --password "$CONTAINER_REGISTRY_PASSWORD" docker.io } test-image() { if [[ -z "$1" ]]; then echo '$1 is empty.' exit fi if [[ "$1" == "images/odoo-upgrade" ]]; then CONTAINER_TAG="odoo-upgrade" elif [[ "$1" == "images/odoocli" ]]; then CONTAINER_TAG="odoocli" fi if [[ "$CONTAINER_TAG" =~ ^odoo: ]]; then generate-ssh-keys export GIT_SSH_PRIVATE_KEY export GIT_SSH_PUBLIC_KEY export PATH=./images/odoo/bin/:$PATH export LOCAL_PATH="./tmp" mkdir -p "$LOCAL_PATH" echo -e "\033[38;5;214mTEST\033[0m: Run git clone addons" local server_tools_git_ref=$(curl -s "https://api.github.com/repos/OCA/server-tools/branches/$ODOO_VERSION" | grep '"sha":' | head -n 1 | awk -F '"' '{print $4}') export ADDONS_GIT_REPOS="https://github.com/OCA/server-tools.git#${ODOO_VERSION}#${server_tools_git_ref}" # This will remove all SSH keys at the end # ./images/odoo/bin/clone-git-addons rm -rf "$LOCAL_PATH/github.com" # Cleanup for next run echo -e "\033[38;5;214mTEST\033[0m: Check addons path regex with version: $ODOO_VERSION" echo "'version': '1.0'" | grep -iE "version.*['\"]([0-9]+\.[0-9]+['\"]|${ODOO_VERSION})" echo "'version': '1.1'" | grep -iE "version.*['\"]([0-9]+\.[0-9]+['\"]|${ODOO_VERSION})" echo "'version': '2.5'" | grep -iE "version.*['\"]([0-9]+\.[0-9]+['\"]|${ODOO_VERSION})" echo "'version': '13.0.1.0.0'" | grep -iE "version.*['\"]([0-9]+\.[0-9]+['\"]|${ODOO_VERSION})" || echo "Must not match" echo "'version': '14.0.2.1.1'" | grep -iE "version.*['\"]([0-9]+\.[0-9]+['\"]|${ODOO_VERSION})" || echo "Must not match" echo "'version': '${ODOO_VERSION}.0.0.0'" | grep -iE "version.*['\"]([0-9]+\.[0-9]+['\"]|${ODOO_VERSION})" echo '"Version": "1.0"' | grep -iE "version.*['\"]([0-9]+\.[0-9]+['\"]|${ODOO_VERSION})" echo -e "\033[38;5;214mTEST\033[0m: Install python package." export PYTHON_INSTALL="prometheus-client,pyjwt" ./images/odoo/bin/install-python-packages echo -e "\033[38;5;214mTEST\033[0m: Sync python project." cat > tmp/pyproject.toml << 'EOF' [project] name = "test-project" version = "0.1.0" description = "Test project for uv sync" dependencies = [ "requests>=2.28.0", "click>=8.0.0", ] [build-system] requires = ["setuptools>=61.0", "wheel"] build-backend = "setuptools.build_meta" [tool.uv] dev-dependencies = [ "pytest>=7.0.0", "black>=23.0.0", ] EOF ./images/odoo/bin/sync-python-project uv pip list echo -e "\033[38;5;214mTEST\033[0m: Run set-addons-path" export TEST_ADDONS_DIR="./templates" export ODOO_VERSION="18.0" source ./images/odoo/bin/set-addons-path echo $ADDONS_PATH | grep "./templates" fi if [[ "$CONTAINER_TAG" =~ ^odoo-upgrade ]]; then echo -e "\033[38;5;214mTEST\033[0m: Show help of odoo-upgrade." docker run -it mintsystem/odoo-upgrade --help fi if [[ "$CONTAINER_TAG" =~ ^odoocli ]]; then echo -e "\033[38;5;214mTEST\033[0m: Show help of odoocli." docker_odoocli() { docker run -it \ -e ODOO_URL="$ODOO_URL" -e ODOO_DATABASE="$ODOO_DATABASE" -e ODOO_USERNAME="$ODOO_USERNAME" -e ODOO_PASSWORD="$ODOO_PASSWORD" \ mintsystem/odoocli "$@" } docker_odoocli --help fi } push() { if [[ -z "$1" ]]; then echo '$1 is empty.' exit fi local latest_tag if [[ "$1" == "images/odoo" ]]; then latest_tag="odoo:$ODOO_VERSION" elif [[ "$1" == "images/odoo-mailgate" ]]; then CONTAINER_TAG="odoo-mailgate" latest_tag="odoo-mailgate:latest" elif [[ "$1" == "images/odoo-upgrade" ]]; then CONTAINER_TAG="odoo-upgrade" latest_tag="odoo-upgrade:latest" elif [[ "$1" == "images/odoocli" ]]; then CONTAINER_TAG="odoocli" latest_tag="odoocli:latest" elif [[ "$1" == "images/odoo-mcp" ]]; then CONTAINER_TAG="odoo-mcp" latest_tag="odoocli:latest" fi echo "Tag container image ${CONTAINER_REGISTRY}/${CONTAINER_TAG} with ${CONTAINER_REGISTRY}/${latest_tag}" $CONTAINER_ENGINE tag "${CONTAINER_REGISTRY}/${CONTAINER_TAG}" "${CONTAINER_REGISTRY}/${latest_tag}" if [[ "$CONTAINER_ENGINE" == "docker" ]]; then echo "Push container image to ${CONTAINER_REGISTRY}/${CONTAINER_TAG}" docker --config "$CONTAINER_CONFIG" push "${CONTAINER_REGISTRY}/${CONTAINER_TAG}" docker --config "$CONTAINER_CONFIG" push "${CONTAINER_REGISTRY}/${latest_tag}" fi if [[ "$CONTAINER_ENGINE" == "podman" ]]; then echo "Push container image to ${CONTAINER_REGISTRY}/${CONTAINER_TAG}" podman push "${CONTAINER_REGISTRY}/${CONTAINER_TAG}" podman push "${CONTAINER_REGISTRY}/${latest_tag}" fi } upload-module() { if [[ -z "$1" ]]; then echo '$1 is empty.' exit fi if [[ -z "$2" ]]; then echo '$2 is empty.' exit fi # Check if path is root folder local modules="$2" if [[ ! -f "$2/__manifest__.py" ]]; then echo 'Identified as root folder' modules="$modules/*" fi for module in $modules; do if [[ -f "$module/__manifest__.py" ]]; then # Create module zip zip-module "$module" # Get path to zip file and upload url local version=$(get-module-version "$module") local module_name=$(basename "$module") local file_path="tmp/$module_name-$version.zip" load-env "$1" local nextcloud_upload_url="$NEXTCLOUD_URL/remote.php/dav/files/$NEXTCLOUD_USERNAME/Odoo-Apps/" # Upload zip file curl -u "$NEXTCLOUD_USERNAME:$NEXTCLOUD_PASSWORD" -T "$file_path" "$nextcloud_upload_url" echo "File $file_path uploaded to $nextcloud_upload_url." UPLOADS+=" - name: $module_name\n" UPLOADS+=" version: $version\n" fi done echo 'odoo_apps:' echo -e "$UPLOADS" } logs() { local container="$1" container=${container:="odoo"} $CONTAINER_ENGINE logs --follow "$container" } # Script commands install-odoo-scripts() { echo 'Installing Odoo scripts with user flag.' curl -L https://ansible.build/odoo_scripts/install | bash -s -- --user } run-enterprise-upgrade() { if [[ -z "$1" ]]; then echo '$1/database is empty.' exit fi if [[ -z "$2" ]]; then echo '$2/version is empty.' exit fi local database="$1" local odoo_target_version="$2" local mode="test" if [[ -n "$3" ]]; then # Options: test, production mode="$3" fi local new_database="${database}_${odoo_target_version}" echo "Execute Odoo upgrade script in $mode mode for $database with version $odoo_target_version." echo "The database will be restored as $new_database." export PGHOST=localhost export PGUSER=odoo export PGPASSWORD=odoo python <(curl -s https://upgrade.odoo.com/upgrade) "$mode" -d "$database" -t "$odoo_target_version" -r "$new_database" echo 'Upgrade finished successfully.' } run-open-upgrade() { if [[ -z "$1" ]]; then echo '$1/database is empty.' exit fi if [[ -z "$2" ]]; then echo '$2/version is empty.' exit fi local database="$1" local odoo_target_version="$2" local new_database="${database}_${odoo_target_version}" echo "Get the current version of the database." echo "Checkout the Odoo version 15.0..." echo "Run the upgrade script for 15.0..." echo "Checkout the Odoo version 16.0..." echo "Run the upgrade script for 16.0..." echo "Checkout the Odoo version 17.0..." echo "Run the upgrade script for 17.0..." echo "Checkout the Odoo version 18.0..." echo "Run the upgrade script for 18.0..." } # Install commands install() { init-venv activate-venv if [[ ! -d "$HOME/taskfile.build" ]]; then echo -e "\033[38;5;214mGit\033[0m: Clone taskfile repo" git clone https://github.com/janikvonrotz/taskfile.build.git ~/taskfile.build else echo -e "\033[38;5;214mGit\033[0m: Pull taskfile repo" git -C ~/taskfile.build pull fi # OS specific installations if [[ "$OS_RELEASE" =~ ^(Debian|Ubuntu|Pop!_OS)$ ]]; then echo -e "\033[38;5;214mApt\033[0m: Update apt repo" sudo apt update echo -e "\033[38;5;214mApt\033[0m: Install python-ldap and psycopg2 build dependencies" sudo apt-get install -y libsasl2-dev libldap2-dev libssl-dev libpq-dev python3-dev echo -e "\033[38;5;214mApt\033[0m: Install xmllint, ripgrep and gettext" sudo apt install -y libxml2-utils ripgrep gettext elif [[ "$OS_RELEASE" =~ ^(Fedora Linux)$ ]]; then echo -e "\033[38;5;214mDnf\033[0m: Update dnf repo" sudo dnf update echo -e "\033[38;5;214mDnf\033[0m: Install psycopg2 build dependencies" sudo dnf install -y postgresql-devel echo -e "\033[38;5;214mApt\033[0m: Install ripgrep and gettext" sudo dnf install -y ripgrep gettext elif [[ "$OS_RELEASE" =~ ^(Darwin)$ ]]; then echo 'Welcome daring Mac user. Let'\''s try our best to get this working.' echo -e "\033[38;5;214mBrew\033[0m: Install required packages with brew." brew install gettext libsasl2 openldap openssl libxml2 grep ripgrep elif [[ "$OS_RELEASE" =~ ^(Arch Linux|SteamOS)$ ]]; then echo 'Arch is the best' echo -e "\033[38;5;214mPacman\033[0m: Install python-ldap and psycopg2 build dependencies" sudo pacman -S --noconfirm base-devel postgresql echo -e "\033[38;5;214mApt\033[0m: Install ripgrep" sudo pacman -S --noconfirm ripgrep else echo "The operating system $OS_RELEASE is not supported." exit 1 fi # Install wkhtmltopdf if [[ "$OS_RELEASE" =~ ^(Arch Linux|Darwin|Debian|Fedora Linux|Ubuntu|Pop!_OS|SteamOS)$ ]]; then # Source: https://gist.github.com/faniska/37f896d5e9de5fee925925d7caf3cb9e local installed_version=$(wkhtmltopdf --version 2>&1 | sed -E 's/^wkhtmltopdf +([0-9.]+).*/\1/') if [[ "$installed_version" != "0.12.4" ]]; then echo -e "\033[38;5;214mCurl\033[0m: Install wkhtmltopdf" curl -L -O https://github.com/wkhtmltopdf/wkhtmltopdf/releases/download/0.12.4/wkhtmltox-0.12.4_linux-generic-amd64.tar.xz tar xvf wkhtmltox-0.12.4_linux-generic-amd64.tar.xz sudo mv wkhtmltox/bin/wkhtmlto* /usr/bin/ rm -rf wkhtmltox wkhtmltox-0.12.4_linux-generic-amd64.tar.xz else echo 'wkhtmltopdf version is already 0.12.4' fi fi # Odoo Build requirements echo -e "\033[38;5;214mPython\033[0m: Install Odoo Build development tools" uv pip install -r requirements.txt # Odoo requirements echo -e "\033[38;5;214mPython\033[0m: Install Odoo and dependencies." cd odoo uv pip install -e . grep -v "python-ldap" requirements.txt | uv pip install -r - cd .. # Install inotify echo -e "\033[38;5;214mPython\033[0m: Install $PYTHON_NOTIFY." if [[ -n "$PYTHON_NOTIFY" ]]; then uv pip install "$PYTHON_NOTIFY" fi if [[ "$PYTHON_NOTIFY" == "inotify" ]] && ! sudo grep -q "fs.inotify.max_user_watches=524288" /etc/sysctl.d/99-sysctl.conf; then sudo bash -c 'echo "fs.inotify.max_user_watches=524288" >> /etc/sysctl.d/99-sysctl.conf' sudo sysctl -p /etc/sysctl.d/99-sysctl.conf echo -e "\033[38;5;214mSystem\033[0m: Updated fs.inotify.max_user_watches to 524288" fi # Fixes for Odoo versions if [[ "$ODOO_VERSION" == "14.0" ]] ; then echo -e "\033[38;5;214mPython\033[0m: Fix shebang for odoo binary." sed -i '1s|.*|#!'"$PWD"'/.venv14.0/bin/python|' .venv14.0/bin/odoo fi } # Process commands open-url-with-delay() { if [[ -z "$1" ]]; then echo '$1 is empty.' exit fi if $BROWSER_OPEN; then sleep 2 $OS_OPEN "$1" fi } start() { set-addons-path if [[ "$POSTGRES_SSL" == "on" ]]; then generate-pg-ssl-keys fi template-compose # Use default database if second param is not given local database="$2" if [[ -z "$2" ]]; then database="$ODOO_DATABASE" fi if [[ -z "$1" ]]; then echo "Start all container ${CONTAINER_REGISTRY}/${CONTAINER_TAG}" echo 'Open http://localhost:8069 url in your browser.' $COMPOSE_COMMAND up -d fi if [[ "$1" =~ "admin" ]]; then echo 'Open http://localhost:8000 url in your browser.' open-url-with-delay "http://localhost:8000" & $COMPOSE_COMMAND up -d admin fi if [[ "$1" =~ "db" ]]; then $COMPOSE_COMMAND up -d db fi if [[ "$1" =~ "mailgate" ]]; then echo 'Send emails to: smtp://localhost:587' $COMPOSE_COMMAND up -d mailgate fi if [[ "$1" =~ "mailpit" ]]; then echo 'Open http://localhost:8025 url in your browser.' open-url-with-delay "http://localhost:8025" & $COMPOSE_COMMAND up -d mailpit fi if [[ "$1" =~ "source" ]]; then export SMTP_SERVER=localhost template-odoo-rc local port=8069 while [[ $port -le 8079 ]]; do if ! lsof -i:$port >/dev/null 2>&1; then break fi port=$((port + 1)) done echo "Open http://localhost:$port url in your browser." open-url-with-delay "http://localhost:$port/web?debug=1&db=$database" & odoo --database="$database" --config "$ODOO_RC" \ --http-port="$port" --addons-path="$ADDONS_PATH" --dev=all --log-level="$LOG_LEVEL" fi if [[ "$1" =~ "hatch" ]]; then export SMTP_SERVER=localhost template-odoo-rc local port=8069 while [[ $port -le 8079 ]]; do if ! lsof -i:$port >/dev/null 2>&1; then break fi port=$((port + 1)) done echo "Open http://localhost:$port url in your browser." open-url-with-delay "http://localhost:$port/web?debug=1&db=$database" & odoo --database="$database" --config "$ODOO_RC" \ --http-port="$port" --dev=all --log-level="$LOG_LEVEL" fi if [[ "$1" =~ "odoo" ]]; then echo "Start Odoo container ${CONTAINER_REGISTRY}/${CONTAINER_TAG}" echo 'Open http://localhost:8069 url in your browser.' open-url-with-delay "http://localhost:8069" & $COMPOSE_COMMAND up -d odoo fi } debug() { if [[ -z "$1" ]]; then echo '$1 is empty.' exit fi set-addons-path local port=8069 if [[ "$1" =~ "source" ]]; then # Disable file validation to avoid frozen modules warning export PYDEVD_DISABLE_FILE_VALIDATION=1 echo 'Starting Odoo with the debugger on port 5678.' echo "Attach a debugpy debugger and open http://localhost:$port url in your browser." python -m debugpy --listen 5678 --wait-for-client $(which odoo) --database="$DATABASE" --config "$ODOO_RC" \ --http-port="$port" --addons-path="$ADDONS_PATH" --dev=all --log-level="$LOG_LEVEL" fi } run() { if [[ -z "$1" ]]; then echo '$1 is empty.' exit fi if [[ -z "$2" ]]; then echo '$2 is empty.' exit fi template-compose echo "Run '$1' container with command '$2'." $COMPOSE_COMMAND run --rm "$1" "$2" "$3" } exec() { if [[ -z "$1" ]]; then echo '$1 is empty.' exit fi local container="$1" shift local cmd="$@" echo "Execute command '$cmd' with $CONTAINER_EXEC_PARAM in container '$container'." $CONTAINER_ENGINE exec $CONTAINER_EXEC_PARAM "$container" $cmd } record-with-py-spy() { if [[ -z "$1" ]]; then echo '$1 is empty.' exit fi local pid="$1" local output_file="tmp/speedscope-capture.json" local speedscope_url="https://www.speedscope.app/" mkdir -p tmp echo "source task source; py-spy record --pid $pid --output $output_file --format speedscope" | sudo bash -- $OS_OPEN "$speedscope_url" } record-with-memray() { if [[ -z "$1" ]]; then echo '$1 is empty.' exit fi set-addons-path local port=8069 local output_file="tmp/memray-capture.bin" if [[ "$1" =~ "source" ]]; then echo 'Starting Odoo with memray.' echo "Open http://localhost:$port url in your browser." mkdir -p tmp rm "$output_file" python3 -m memray run -o "$output_file" $(which odoo) --database="$DATABASE" --config "$ODOO_RC" \ --http-port="$port" --addons-path="$ADDONS_PATH" --dev=all --log-level="$LOG_LEVEL" python3 -m memray flamegraph "$output_file" $OS_OPEN "tmp/memray-flamegraph-output.html" fi } remove() { template-compose if [[ -z "$1" ]]; then $COMPOSE_COMMAND down -v fi if [[ "$1" =~ "db" ]]; then $COMPOSE_COMMAND rm -f -s -v db fi if [[ "$1" =~ "admin" ]]; then $COMPOSE_COMMAND rm -f -s -v admin fi if [[ "$1" =~ "odoo" ]]; then $COMPOSE_COMMAND rm -f -s -v odoo fi if [[ "$1" =~ "mailpit" ]]; then $COMPOSE_COMMAND rm -f -s -v mail fi if [[ "$1" =~ "mailgate" ]]; then $COMPOSE_COMMAND rm -f -s -v mailgate fi } restart() { template-compose if [[ -z "$1" ]]; then $COMPOSE_COMMAND restart fi if [[ "db|mail|odoo" =~ $1 ]]; then $COMPOSE_COMMAND restart "$1" fi } stop() { template-compose if [[ -z "$1" ]]; then $COMPOSE_COMMAND stop fi $COMPOSE_COMMAND stop "$1" } generate-ssh-keys() { if [[ -z "$GIT_SSH_PRIVATE_KEY" ]]; then echo 'Generate SSH private and public keys.' mkdir -p tmp if [[ ! -f "tmp/ed25519" ]]; then ssh-keygen -t ed25519 -C admin@example.com -f tmp/ed25519 -N '' fi GIT_SSH_PRIVATE_KEY=$(cat tmp/ed25519 | base64 -w0) GIT_SSH_PUBLIC_KEY=$(cat tmp/ed25519.pub) fi } generate-pg-ssl-keys() { local keymaterial_path="tmp/postgres-odoo" if [[ -z "$PG_SSL_CA_CRT" ]] || [[ -z "$PG_SSL_SERVER_CRT" ]] || [[ -z "$PG_SSL_SERVER_KEY" ]]; then echo 'Generate PostgreSQL SSL/TLS certificates.' mkdir -p "$keymaterial_path" if [[ ! -f "$keymaterial_path/ca.crt" ]]; then openssl genrsa -out "$keymaterial_path/ca.key" 4096 chmod 600 "$keymaterial_path/ca.key" openssl req -x509 -new -nodes -key "$keymaterial_path/ca.key" -sha256 -days 3650 \ -out "$keymaterial_path/ca.crt" -subj "/C=CH/ST=State/L=Locality/O=Organization/CN=Root CA" fi if [[ ! -f "$keymaterial_path/server.crt" ]]; then openssl genrsa -out "$keymaterial_path/server.key" 2048 openssl req -new -key "$keymaterial_path/server.key" -out "$keymaterial_path/server.csr" \ -subj "/C=CH/ST=State/L=Locality/O=Organization/CN=localhost" openssl x509 -req -in "$keymaterial_path/server.csr" -CA "$keymaterial_path/ca.crt" \ -CAkey "$keymaterial_path/ca.key" -CAcreateserial -out "$keymaterial_path/server.crt" \ -days 365 -sha256 chmod 600 "$keymaterial_path/server.key" chmod 644 "$keymaterial_path/server.crt" sudo chown 999:999 "$keymaterial_path/server.key" sudo chown 999:999 "$keymaterial_path/server.crt" fi fi } test-project() { if [[ "$1" == "clean" ]]; then echo -e "\033[38;5;214mTEST\033[0m: Cleanup test environment." drop-db test remove odoo git -C oca/partner-contact clean -df git -C oca/partner-contact checkout . remove-env test else version info set -e echo -e "\033[38;5;214mTEST\033[0m: Clone module repo with https" (git clone -b "$ODOO_VERSION" https://github.com/OCA/partner-contact.git oca/partner-contact) || true echo -e "\033[38;5;214mTEST\033[0m: Start container environment." export BROWSER_OPEN=false start db,mail echo -e "\033[38;5;214mTEST\033[0m: Clone Odoo addons." generate-ssh-keys export GIT_SSH_PRIVATE_KEY export GIT_SSH_PUBLIC_KEY export ADDONS_GIT_REPOS="https://github.com/OCA/server-env.git#${ODOO_VERSION},https://github.com/OCA/server-tools.git#${ODOO_VERSION},https://github.com/Mint-System/Odoo-Apps-Server-Tools.git#${ODOO_VERSION}" run odoo clone-git-addons echo -e "\033[38;5;214mTEST\033[0m: Init Odoo database." export ODOO_ADDONS_PATH="/mnt/oca/partner-contact" export ODOO_DATABASE=test export WITHOUT_DEMO=False export ODOO_INIT_LOGIN=test export ODOO_INIT_PASSWORD=test export ODOO_INIT_LANG=de_CH export ODOO_INIT_ADDONS=server_environment_ir_config_parameter,module_auto_update,prometheus_exporter,mail_server_filter,contacts,web_enterprise export PYTHON_INSTALL=prometheus-client,manifestoo run odoo init-db echo -e "\033[38;5;214mTEST\033[0m: Start Odoo container." export ODOO_MAIL_SMTP_HOST=mail export ODOO_MAIL_SMTP_PORT=1025 export ODOO_MAIL_SMTP_ENCRYPTION=none export SERVER_WIDE_MODULES=module_change_auto_install # export SESSION_DB_URI=postgres://odoo:odoo@db/test export MODULE_AUTO_INSTALL_DISABLED=odoo_test_xmlrunner export AUTO_UPDATE_MODULES=True export AUTO_UPDATE_TRANSLATIONS=True export AUTO_UPDATE_MODULES_LIST=True export MAIL_DEFAULT_FROM="test" export BROWSER_OPEN=false export TEST_ADDONS_DIR=/mnt/oca/partner-contact export TEST_INCLUDE=partner_firstname start odoo TIMEOUT=60 ELAPSED=0 until curl --silent --fail "http://localhost:8069/web/login" &> /dev/null; do if [ "$ELAPSED" -ge "$TIMEOUT" ]; then echo "Odoo did not start within $TIMEOUT seconds." docker logs odoo exit 1 fi echo 'Waiting for Odoo to be ready...' sleep 3 ELAPSED=$((ELAPSED + 3)) done echo -e "\033[38;5;214mTEST\033[0m: Init repos.yml file." cat << EOF > tmp/repos.yml github.com/Mint-System/Odoo-Apps-Partner-Contact: remotes: mint_system: https://github.com/Mint-System/Odoo-Apps-Partner-Contact.git merges: - mint_system ${ODOO_VERSION} target: mint_system ${ODOO_VERSION} EOF docker cp tmp/repos.yml odoo:/var/lib/odoo/git/repos.yml echo -e "\033[38;5;214mTEST\033[0m: Init OCA module." run odoo init-module partner_firstname echo -e "\033[38;5;214mTEST\033[0m: Setup and check RPC credentials." export ODOO_URL="http://localhost:8069" export ODOO_USERNAME=test export ODOO_PASSWORD=test if ! show-env test; then create-odoo-env test fi show-env test test-xmlrpc test echo -e "\033[38;5;214mTEST\033[0m: Install, update, disable and remove snippet." install-snippet test snippets/base.view_partner_form.show_type.xml update-snippet test snippets/base.view_partner_form.show_type.xml disable-snippet test snippets/base.view_partner_form.show_type.xml remove-snippet test snippets/base.view_partner_form.show_type.xml echo -e "\033[38;5;214mTEST\033[0m: Check module auto update." sed -i 's/First name/Firstname/g' oca/partner-contact/partner_firstname/models/res_partner.py restart odoo until curl --silent --fail "http://localhost:8069/web/login" &> /dev/null; do echo 'Waiting for Odoo to be ready...' sleep 3 done docker logs odoo docker logs odoo 2>&1 | grep "Run click-odoo-update." echo -e "\033[38;5;214mTEST\033[0m: Check translations auto update." sed -i 's/Nachname/Familienname/g' oca/partner-contact/partner_firstname/i18n/de.po restart odoo echo -e "\033[38;5;214mTEST\033[0m: Drop the test database." drop-db test echo -e "\033[38;5;214mTEST\033[0m: Prepare container for testing." exec odoo setup-tests exec odoo ls /var/lib/odoo/git/github.com/Mint-System/Odoo-Apps-Partner-Contact echo -e "\033[38;5;214mTEST\033[0m: Run module tests." CONTAINER_EXEC_PARAM="-w /mnt/oca/partner-contact" exec odoo run-tests echo -e "\033[38;5;214mTEST\033[0m: Run module upgrade." mkdir -p oca/partner-contact/partner_firstname/migrations/${ODOO_VERSION}.2.0.0 cat << EOF > oca/partner-contact/partner_firstname/migrations/${ODOO_VERSION}.2.0.0/post-migration.py import logging from odoo.upgrade import util _logger = logging.getLogger(__name__) def migrate(cr, version): env = util.env(cr) partners = env["res.partner"].search([]) for partner in partners: partner.name += "!" _logger.info("Updated %s partners", len(partners)) EOF sed -i "/\"version\":/c\ \"version\": \"${ODOO_VERSION}.2.0.0\"," oca/partner-contact/partner_firstname/__manifest__.py exec odoo update-module partner_firstname 2>&1 | grep "module partner_firstname: Running migration" fi } # Interactive Shell commands shell() { local database="$1" if [[ -z "$database" ]]; then database="$ODOO_DATABASE" fi local code="$2" set-addons-path if [[ -n "$code" ]]; then echo "$code" | odoo shell --database "$database" --config "$ODOO_RC" --addons-path="$ADDONS_PATH" --no-http else odoo shell --database "$database" --config "$ODOO_RC" --addons-path="$ADDONS_PATH" --no-http fi } psql() { local database="$1" if [[ -z "$database" ]]; then database="$ODOO_DATABASE" fi $CONTAINER_ENGINE exec -it db psql "postgres://odoo:odoo@localhost:5432/$database" } # Database commands init-db() { local odoo_init_addons="${ODOO_INIT_ADDONS:-"web"}" local database="$1" if [[ -z "$database" ]]; then database="$ODOO_DATABASE" fi set-addons-path template-odoo-rc echo "Initialize database $database with $odoo_init_addons." if [[ -n "$ODOO_PARAM" ]]; then odoo -d "$database" -i "$odoo_init_addons" --config "$ODOO_RC" --addons-path="$ADDONS_PATH" --stop-after-init --no-http --load-language "$ODOO_LANGUAGE" "$ODOO_PARAM" else odoo -d "$database" -i "$odoo_init_addons" --config "$ODOO_RC" --addons-path="$ADDONS_PATH" --stop-after-init --no-http --load-language "$ODOO_LANGUAGE" fi } import-csv() { if [[ -z "$1" ]]; then echo '$1 is empty.' exit fi local database="$1" local file_path="$2" # Use default database if second param is not given if [[ -z "$2" ]]; then database="$ODOO_DATABASE" file_path="$1" fi local file_name=$(basename "$file_path") echo "Importing $file_name to ${database}..." $CONTAINER_ENGINE cp "$file_path" db:/tmp/ $CONTAINER_ENGINE exec db psql "postgres://odoo:odoo@localhost:5432/$database" -c "\copy $file_name FROM '/tmp/$file_name' DELIMITER ',' CSV HEADER;" } drop-db() { local database="$1" if [[ -z "$database" ]]; then database="$ODOO_DATABASE" fi $CONTAINER_ENGINE exec -i db psql "postgres://odoo:odoo@localhost:5432/postgres" -c "SELECT pg_terminate_backend(pid) FROM pg_stat_activity WHERE datname = '$database';" $CONTAINER_ENGINE exec -i db psql "postgres://odoo:odoo@localhost:5432/postgres" -c "DROP DATABASE \"$database\";" } load-language() { if [[ -z "$1" ]]; then echo '$1 is empty.' exit fi local database="$1" local language="$2" # Use default database if second param is not given if [[ -z "$2" ]]; then database="$ODOO_DATABASE" language="$1" fi set-addons-path echo "Setup language $language for database $database:" odoo -d "$database" -u all --config "$ODOO_RC" --addons-path="$ADDONS_PATH" --stop-after-init --no-http --load-language "$language" } set-admin() { local database="$1" if [[ -z "$database" ]]; then database="$ODOO_DATABASE" fi $CONTAINER_ENGINE exec -i db psql "postgres://odoo:odoo@localhost:5432/$database" -c "UPDATE res_users SET active=true,password='\$pbkdf2-sha512\$25000\$JuScEwIg5JzTGqNUivFeqw\$yWTOcix2Afr3XGP2NPY7w4w49e9vpsu14NRndDYXAkbtMF4zkrmx6inVsoLl0zZY30xI/0GzhwonWsK9TUmjWA' WHERE id=2;" $CONTAINER_ENGINE exec -i db psql "postgres://odoo:odoo@localhost:5432/$database" -c "SELECT login, password FROM res_users WHERE id=2;" } clear-assets() { local database="$1" if [[ -z "$database" ]]; then database="$ODOO_DATABASE" fi echo "Delete these assets for $database:" $CONTAINER_ENGINE exec -i db psql "postgres://odoo:odoo@localhost/$database" -c "select id,name from ir_attachment where res_model='ir.ui.view' and name like '%assets_%';" $CONTAINER_ENGINE exec -i db psql "postgres://odoo:odoo@localhost/$database" -c "delete from ir_attachment where res_model='ir.ui.view' and name like '%assets_%';" } clear-views() { local database="$1" if [[ -z "$database" ]]; then database="$ODOO_DATABASE" fi echo 'Archive views with prefix '\''mint_system'\''.' $CONTAINER_ENGINE exec -i db psql "postgres://odoo:odoo@localhost/$database" -c "SELECT id,name from ir_ui_view WHERE name like '%mint_system%';" $CONTAINER_ENGINE exec -i db psql "postgres://odoo:odoo@localhost/$database" -c "UPDATE ir_ui_view SET active=false WHERE name like '%mint_system%';" echo 'Archive views with prefix '\''Odoo Studio:'\''.' $CONTAINER_ENGINE exec -i db psql "postgres://odoo:odoo@localhost/$database" -c "SELECT id,name from ir_ui_view WHERE name like '%Odoo Studio:%';" $CONTAINER_ENGINE exec -i db psql "postgres://odoo:odoo@localhost/$database" -c "UPDATE ir_ui_view SET active=false WHERE name like '%Odoo Studio:%';" echo "Delete all active views for $database in $CONTAINER :" $CONTAINER_ENGINE exec -i db psql "postgres://odoo:odoo@localhost/$database" -c "DELETE FROM report_layout WHERE view_id IN (SELECT id FROM ir_ui_view WHERE active = TRUE);" $CONTAINER_ENGINE exec -i db psql "postgres://odoo:odoo@localhost/$database" -c "WITH RECURSIVE views_to_delete AS (SELECT id, inherit_id FROM ir_ui_view WHERE active = TRUE AND inherit_id IS NULL UNION ALL SELECT v.id, v.inherit_id FROM ir_ui_view v JOIN views_to_delete p ON v.inherit_id = p.id) DELETE FROM ir_ui_view WHERE id IN (SELECT id FROM views_to_delete);" } clear-filestore() { echo "Remove directory $HOME/.local/share/Odoo/filestore/$1" rm -rf "$HOME/.local/share/Odoo/filestore/$1" } disable-mailserver() { if [[ -z "$1" ]]; then echo '$1 is empty.' exit fi load-env "$1" images/odoocli/bin/odoocli --method 'write' --model 'ir.mail_server' --field 'active' --value False --domain "[]" images/odoocli/bin/odoocli --method 'write' --model 'fetchmail.server' --field 'active' --value False --domain "[]" } change-uuid() { if [[ -z "$1" ]]; then echo '$1 is empty.' exit fi load-env "$1" local uuid=$(uuidgen) echo "Set uuid $uuid for $1." images/odoocli/bin/odoocli --method 'write' --model 'ir.config_parameter' --domain "[('key', '=', 'database.uuid')]" --field 'value' --value "'$uuid'" } update-module-list() { if [[ -z "$1" ]]; then echo '$1 is empty.' exit fi set-addons-path echo "env['ir.module.module'].update_list()" | odoo shell --database "$1" --config "$ODOO_RC" --addons-path="$ADDONS_PATH" --no-http } patch-database() { if [[ -z "$1" ]]; then echo '$1 is empty.' exit fi local database="$1" local filename="$2" # Use default database if second param is not given if [[ -z "$2" ]]; then database="$ODOO_DATABASE" filename="$1" fi echo "Apply patch $filename to Database $database." cat "$filename" | $CONTAINER_ENGINE exec -i db psql "postgres://$POSTGRES_USER:$POSTGRES_PASSWORD@localhost/$database" echo 'Patching database succeeded' } # Report commands cloc-odoo() { local database="$1" if [[ -z "$database" ]]; then database="$ODOO_DATABASE" fi set-addons-path odoo cloc -d "$database" --config "$ODOO_RC" --addons-path="$ADDONS_PATH" --stop-after-init --no-http } visualize-dependencies() { if [[ -z "$1" ]]; then echo '$1 is empty.' exit fi mkdir -p tmp ./bin/odoo-module-dependencies "$1" $OS_OPEN tmp/odoo-module-dependencies.html } # Setup commands install-requirements() { if [[ -z "$1" ]]; then echo '$1 is empty.' exit fi local database="$1" local file_path="$2" # Use default database if second param is not given if [[ -z "$2" ]]; then database="$ODOO_DATABASE" file_path="$1" fi local file_name=$(basename "$file_path") if ! [[ "$file_name" =~ .*requirements\.txt$ ]]; then echo '$1 must point to requirements.txt file.' exit fi local container_running=$($CONTAINER_ENGINE container inspect -f '{{.State.Running}}' odoo) if [[ "$container_running" == "true" ]]; then echo 'Installing python packages in container...' $CONTAINER_ENGINE exec -u root odoo python -m pip install -r "/mnt/$file_path" else echo 'Installing python packages in virutal env...' uv pip install -r "$file_path" fi } list-packages() { uv pip list } generate-module-repo() { if [[ -z "$1" ]]; then echo '$1 is empty.' exit fi local repo_path="$1" local repo_name=$(basename "$repo_path") # If repo already exists, create new branch if [[ -d "$repo_path/.git" ]]; then echo "Create new branch $repo_name in $repo_path" cd "$repo_path" || exit git switch --orphan -c "$ODOO_VERSION" echo "Remove all files from $repo_path" rm -rf "$repo_path/*" fi echo "Copy template files from templates/$ODOO_VERSION to $repo_path" cp "templates/$ODOO_VERSION/*" "$repo_path" } lint-module-repo() { if [[ -z "$1" ]]; then echo '$1 is empty.' exit fi local module_paths=$(get-modules "$MODULE_PATH") for module_path in ${module_paths//,/ }; do lint-module "$module_path" done } # Module commands init-module() { if [[ -z "$1" ]]; then echo '$1 is empty.' exit fi local database="$1" local module_path="$2" # Use default database if second param is not given if [[ -z "$2" ]]; then database="$ODOO_DATABASE" module_path="$1" fi # Check if module path is a module repo if [[ -f "$module_path/README.md" ]]; then # Look for manifest files in repo folder # Get parent folder name of manifest files # Create a comma separated string of folder names local modules=$(get-modules "$module_path" basename) else local modules=$(basename "$module_path") fi set-addons-path echo "Initialize module $modules on ${database}..." odoo -d "$database" -i "$modules" --config "$ODOO_RC" \ --addons-path="$ADDONS_PATH" --stop-after-init --no-http } migrate-module() { if [[ -z "$1" ]]; then echo '$1 is empty.' exit fi local module_path="$1" local source_odoo_version=$(get-module-version $module_path | cut -d'.' -f1,2) local directory=$(dirname "$module_path") local module=$(basename "$module_path") # if [[ "$ODOO_VERSION" == "17.0" ]]; then # echo "Migrate views of module $module_path to ${ODOO_VERSION}..." # odoo --database "$DATABASE" -i "$module_path" --config "$ODOO_RC" --addons-path="$ADDONS_PATH" --load=base,web,views_migration_17 --stop-after-init # fi # if [[ "$ODOO_VERSION" == "18.0" ]]; then # echo "Run Odoo upgrade_code command for $module_path from ${source_odoo_version}..." # odoo upgrade_code --addons-path "$MODULE" --from "$source_odoo_version" # echo "Replace manifest version $source_odoo_version with $ODOO_VERSION." # sed -i -E "s/$source_odoo_version/$ODOO_VERSION/g" "$module_path/__manifest__.py" # else echo "Run odoo-module-migrator for $module with source $source_odoo_version and target ${ODOO_VERSION}..." odoo-module-migrate --directory $(dirname "$module_path") --modules $(basename "$module_path") \ --init-version-name $source_odoo_version --target-version-name $ODOO_VERSION --no-commit } update-module() { if [[ -z "$1" ]]; then echo '$1 is empty.' exit fi local database="$1" local module_name=$(basename "$2") # Use default database if second param is not given if [[ -z "$2" ]]; then database="$ODOO_DATABASE" module_name=$(basename "$1") fi set-addons-path echo "Updating module $module_name on ${database}..." if [[ -n "$ODOO_PARAM" ]]; then odoo --database "$database" --update "$module_name" \ --config "$ODOO_RC" --addons-path="$ADDONS_PATH" --stop-after-init --no-http "$ODOO_PARAM" else odoo --database "$database" --update "$module_name" \ --config "$ODOO_RC" --addons-path="$ADDONS_PATH" --stop-after-init --no-http fi } translate-module() { if [[ -z "$1" ]]; then echo '$1 is empty.' exit fi local module_path="$1" local module_name=$(basename "$1") local language="$2" local database="$3" if [[ -z "$2" ]]; then language="$ODOO_LANGUAGE" fi if [[ -z "$3" ]]; then database="$ODOO_DATABASE" fi set-addons-path echo "Generate $language tranlsation file for $module_name on ${database}..." mkdir -p "$module_path/i18n/" odoo -d "$database" --config "$ODOO_RC" --addons-path="$ADDONS_PATH" --modules "$module_name" -l "$language" --i18n-export "$module_path/i18n/$language.po" # If lang is de_CH then rename the file to de.po. if [[ "$language" == "de_CH" ]]; then mv "$module_path/i18n/$language.po" "$module_path/i18n/de.po" fi } lint-module() { if [[ -z "$1" ]]; then echo '$1 is empty.' exit fi echo "Set file and folder permissions on $1" find "$1" -type d -exec chmod u=rwx,go=rx {} \; find "$1" -type f -exec chmod u=rw,go=r {} \; echo 'Update index.html' rst2html5 "$1/README.rst" "$1/static/description/index.html" pushd . cd "$1" echo "Run pre-commit in $PWD" pre-commit run --all-files # --show-diff-on-failure --color=always echo 'Stage changes' git add . popd } test-module() { if [[ -z "$1" ]]; then echo '$1 is empty.' exit fi local module=$(basename "$1") set-addons-path echo "Testing module $module..." odoo -d "$ODOO_DATABASE" -i "$module" --config "$ODOO_RC" --addons-path="$ADDONS_PATH" --stop-after-init --no-http --test-tags /"$module_name" } test-modules() { if [[ -z "$1" ]]; then echo '$1 is empty.' exit fi local modules=$(get-modules "$1" basename) set-addons-path echo "Collect requirements.txt files in $1." find "$1" -type f -name "requirements.txt" -exec uv pip install -r {} \; echo "Collect test-requirements.txt files in $1." find "$1" -type f -name "test-requirements.txt" -exec uv pip install -r {} \; depends_modules=$(manifestoo --select-addons-dir "$1" list-depends --separator=,) echo "Setup test database with "$ODOO_LANGUAGE" lang and depends modules: $depends_modules" odoo -d "$ODOO_DATABASE" -i "$depends_modules" --config "$ODOO_RC" --addons-path="$ADDONS_PATH" --stop-after-init --no-http --load-language "$ODOO_LANGUAGE" echo "Testing modules: $modules" odoo -d "$ODOO_DATABASE" -i "$modules" --config "$ODOO_RC" --addons-path="$ADDONS_PATH" --stop-after-init --no-http --test-enable } pytest-module() { if [[ -z "$1" ]]; then echo '$1 is empty.' exit fi local module_path="$1" echo "Setup custom tmp/odoo.conf for pytest." set-addons-path local odoo_config="tmp/odoo.conf" echo "[options]" > "$odoo_config" echo "addons_path=$ADDONS_PATH" >> "$odoo_config" echo "db_host = localhost" >> "$odoo_config" echo "db_password = odoo" >> "$odoo_config" echo "db_user = odoo" >> "$odoo_config" echo "Run pytest with odoo-http option..." pytest -s --odoo-http --odoo-config="$odoo_config" --odoo-database="$ODOO_DATABASE" "$module_path" } remove-module() { if [[ -z "$1" ]]; then echo '$1 is empty.' exit fi local database="$1" local module_name=$(basename "$2") # Use default database if second param is not given if [[ -z "$2" ]]; then database="$ODOO_DATABASE" module_name=$(basename "$1") fi set-addons-path echo "Remove module $module_name" echo "self.env['ir.module.module'].search([('name', '=', '$module_name')]).button_immediate_uninstall()" | odoo shell --database "$database" --config "$ODOO_RC" --addons-path="$ADDONS_PATH" --no-http } generate-module() { if [[ -z "$1" ]]; then echo '$1 is empty.' exit fi local module_name=$(basename "$1") local parent_dir=$(dirname "$1") echo "Scaffolding module $module_name in ${parent_dir}..." odoo scaffold "$module_name" "$parent_dir" -t templates/module } generate-module-docs() { if [[ -z "$1" ]]; then echo '$1 is empty.' exit fi local module_path="$1" local manifest_path="$module_path/__manifest__.py" echo 'Setup readme folder' mkdir -p "$module_path/readme" cp -n -r "templates/module/readme" "$module_path" echo 'Update DESCRIPTION.rst' export MODULE_SUMMARY=$(python3 -c "import ast; d=ast.literal_eval(open('$manifest_path').read()); print(d.get('summary', '').replace('\n', ' ').strip())") envsubst < templates/module/readme/DESCRIPTION.md > "$module_path/readme/DESCRIPTION.md" echo 'Update CONTRIBUTORS.rst' export GIT_USER_NAME="$(git config user.name)" export GIT_USER_EMAIL="$(git config user.email)" envsubst < templates/module/readme/CONTRIBUTORS.md > "$module_path/readme/CONTRIBUTORS.md" echo 'Copy icon.png' cp templates/module/static/description/icon.png "$module_path/static/description/icon.png" echo 'Copy LICENSE' cp templates/module/LICENSE "$module_path/" echo "Generate OCA readme for $ORG_NAME org." local repo_url=$(git -C "$(dirname $module_path)" remote get-url origin) local repo_name=$(basename "${repo_url%.git}") oca-gen-addon-readme --repo-name=$repo_name --branch=$ODOO_VERSION --org-name=$ORG_NAME --addon-dir=$module_path } template-module-readme() { if [[ -z "$1" ]]; then echo '$1 is empty.' exit fi local module_path="$1" local manifest_path="$module_path/__manifest__.py" echo "Update $module_path/README.rst" export MODULE_TITLE=$(python3 -c "import ast; d=ast.literal_eval(open('$manifest_path').read()); print(d.get('name', ''))") export MODULE_UNDERLINE=$(python3 -c "import ast; d=ast.literal_eval(open('$manifest_path').read()); print('=' * len(d.get('name', '')))") export MODULE_SUMMARY=$(python3 -c "import ast; d=ast.literal_eval(open('$manifest_path').read()); print(d.get('summary', '').replace('\n', ' ').strip())") export MODULE_WIKI_PATH=$(basename "$module_path" | sed 's/_/-/g') envsubst < templates/README.rst > "$module_path/README.rst" } generate-module-security() { if [[ -z "$1" ]]; then echo '$1 is empty.' exit fi if [[ -z "$2" ]]; then echo '$2 is empty.' exit fi local module_name=$(basename "$1") local parent_dir=$(dirname "$1") local model_dot_name="$2" local model_name=$(echo "$model_dot_name" | tr '.' '_') local access_file="$parent_dir/$module_name/security/ir.model.access.csv" mkdir -p "$parent_dir/$module_name/security" if [[ ! -f "$access_file" ]]; then echo "Init security file: $access_file" echo "id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink" > "$access_file" fi # id # name # model_id:id # group_id:id # perm_read # perm_write # perm_create # perm_unlink echo "Add model access to $access_file" echo "access_${model_name}_user,access.$model_dot_name.user,model_${model_name},base.group_user,1,0,0,0" >> "$access_file" echo "access_${model_name}_manager,access.$model_dot_name.manager,model_${model_name},base.group_erp_manager,1,1,1,1" >> "$access_file" } generate-module-model() { if [[ -z "$1" ]]; then echo '$1 is empty.' exit fi if [[ -z "$2" ]]; then echo '$2 is empty.' exit fi local module_path="$1" local module_name=$(basename "$1") echo 'Substitute model template' export MODEL_DOT_NAME="$2" # res.partner export MODEL_NAME=$(echo "$MODEL_DOT_NAME" | tr '.' '_') # res_partner export NAME=$(echo "$MODEL_DOT_NAME" | cut -d"." -f2 | sed 's/[^_]\+/\L\u&/g') # Partners export MODEL_DESCRIPTION=$(echo "$MODEL_NAME" | sed 's/[^_]\+/\L\u&/g' | sed 's/_/ /g') # Res Partner export MODEL_CAMEL_NAME=$(echo "$MODEL_DESCRIPTION" | sed 's/ //g') # ResPartner envsubst < templates/model.py > "$module_path/models/$MODEL_NAME.py" echo "from . import $model_name" >> "$module_path/models/__init__.py" echo "File "$module_path/models/$MODEL_NAME.py created. } generate-module-inherit() { if [[ -z "$1" ]]; then echo '$1 is empty.' exit fi if [[ -z "$2" ]]; then echo '$2 is empty.' exit fi local module_path="$1" local module_name=$(basename "$1") echo 'Substitute model template' export MODEL_DOT_NAME="$2" # res.partner export MODEL_NAME=$(echo "$MODEL_DOT_NAME" | tr '.' '_') # res_partner export NAME=$(echo "$MODEL_DOT_NAME" | cut -d"." -f2 | sed 's/[^_]\+/\L\u&/g') # Partners export MODEL_DESCRIPTION=$(echo "$MODEL_NAME" | sed 's/[^_]\+/\L\u&/g' | sed 's/_/ /g') # Res Partner export MODEL_CAMEL_NAME=$(echo "$MODEL_DESCRIPTION" | sed 's/ //g') # ResPartner envsubst < templates/inherit.py > "$module_path/models/$MODEL_NAME.py" echo "from . import $MODEL_NAME" >> "$module_path/models/__init__.py" echo "File "$module_path/models/$MODEL_NAME.py created. } generate-module-views() { if [[ -z "$1" ]]; then echo '$1 is empty.' exit fi if [[ -z "$2" ]]; then echo '$2 is empty.' exit fi local module_path="$1" local module_name=$(basename "$1") echo 'Substitute view template' mkdir -p "$module_path/views" export MODEL_DOT_NAME="$2" # res.partner export MODEL_NAME=$(echo "$MODEL_DOT_NAME" | tr '.' '_') # res_partner export NAME=$(echo "$MODEL_DOT_NAME" | cut -d"." -f2 | sed 's/[^_]\+/\L\u&/g') # Partners export MODEL_DESCRIPTION=$(echo "$MODEL_NAME" | sed 's/[^_]\+/\L\u&/g' | sed 's/_/ /g') # Res Partner export MODEL_CAMEL_NAME=$(echo "$MODEL_DESCRIPTION" | sed 's/ //g') # ResPartner envsubst < templates/model_views.xml > "$module_path/views/${model_name}_views.xml" echo "Update file $module_name/__manifest__.py with:" echo -e "\t\"views/${model_name}_views.xml\"," } generate-module-snippet() { if [[ -z "$1" ]]; then echo '$1 is empty.' exit fi if [[ -z "$2" ]]; then echo '$2 is empty.' exit fi if [[ -z "$2" ]]; then echo '$2 is empty.' exit fi local module_path="$1" # addons/sale_workflow/sale_helm echo 'Substitute snippet template' export MODEL_DOT_NAME="$2" # sale.order export VIEW_REF="$3" # sale.view_order_form export MODEL_NAME=${MODEL_DOT_NAME//\./_} export MODULE_NAME=$(basename "$1") export SRC_MODULE_NAME=$(echo "$VIEW_REF" | cut -d"." -f1) export XML_ID=$(echo "$view_ref" | cut -d"." -f2) mkdir -p "$module_path/views" envsubst < templates/model_snippet.xml > "$module_path/views/${MODEL_NAME}_views.xml" echo "Update file $MODULE_NAME/__manifest__.py with: \"views/${MODEL_NAME}_views.xml\"" } release-module() { if [[ -z "$1" ]]; then echo '$1 is empty.' exit fi local version=$(get-module-version "$1") cd "$1" || exit local last_tag=$(git describe --tags) local release_notes=$(git log "$last_tag"..HEAD --oneline) echo "Tag with v${version}..." git tag -a "v$version" -m "v$version" echo 'Release notes:' echo "$release_notes" } zip-module() { if [[ -z "$1" ]]; then echo '$1 is empty.' exit fi # Get version and module name local version=$(get-module-version "$1") local module=$(basename "$1") # Copy module to tmp folder echo "Copy module $module version $version to tmp folder" local module_name="$module-$version" local module_path="tmp/$module_name" rm -rf "$module_path" mkdir -p "$module_path" cp -r "$1/." "$module_path" find "$module_path" -type d -exec chmod u=rwx,go=rx {} \; find "$module_path" -type f -exec chmod u=rw,go=r {} \; # Configure and zip module echo 'Remove auto install option' sed -i'' -e "s/'auto_install': True,/'auto_install': False,/" "$module_path/__manifest__.py" echo 'Remove pycache and hidden folders' find "$module_path" | grep -E "(\.git|__pycache__|\.pyc|\.pyo$)" | xargs rm -rf echo "Zip module $module" cd tmp || exit rm -f "$module_name".zip zip -q -r "$module_name".zip "$module_name" cd .. || exit } # Snippet commands reset-views() { if [[ -z "$1" ]]; then echo '$1 is empty.' exit fi local database="$1" local key=$(basename "$2") # Use default database if second param is not given if [[ -z "$2" ]]; then database="$ODOO_DATABASE" key=$(basename "$1") fi set-addons-path echo "Reset views for key $key" echo "self.env['ir.ui.view'].search([('key', 'ilike', '$key')]).reset_arch(mode='hard')" | odoo shell --database "$database" --config "$ODOO_RC" --addons-path="$ADDONS_PATH" --no-http } create-snippet() { if [[ -z "$1" ]]; then echo '$1 is empty.' exit fi export INHERIT_ID=$(echo "$1" | cut -d. -f1-2) local file="snippets/$1.xml" echo "Create snippet from template: $file" envsubst '$INHERIT_ID' < templates/snippet.xml > "$file" } install-snippet() { if [[ -z "$1" ]]; then echo '$1 is empty.' exit fi if [[ -z "$2" ]]; then echo '$2 is empty.' exit fi # Generate view name from filename local file=$(basename "$2") local name="mint_system.${file%.xml}" local inherit_id=$(echo 'cat //data/@inherit_id' | xmllint --shell "$2" | awk -F'[="]' '!/>/{print $(NF-1)}') local type=$(echo 'cat //data/@type' | xmllint --shell "$2" | awk -F'[="]' '!/>/{print $(NF-1)}') local model=$(echo 'cat //data/@model' | xmllint --shell "$2" | awk -F'[="]' '!/>/{print $(NF-1)}') local priority=$(echo 'cat //data/@priority' | xmllint --shell "$2" | awk -F'[="]' '!/>/{print $(NF-1)}') local module_name=$(echo "$inherit_id" | cut -d. -f 1 ) local xml_id=$(echo "$inherit_id" | cut -d. -f 2 ) # Set default values : ${type:='qweb'} load-env "$1" echo "Search for view with name $module_name.$xml_id" inherit_id=$(images/odoocli/bin/odoocli --method "read" --model "ir.model.data" --field "res_id" --domain "[('name', '=', '$xml_id'), ('module', '=', '$module_name')]") echo "Found view id $inherit_id" echo "Apply view $name" images/odoocli/bin/odoocli --method 'create' --model 'ir.ui.view' --value "{ 'name': '$name', 'type': '$type', 'model': '$model', 'mode': 'extension', 'priority': $priority, 'inherit_id': $inherit_id, 'arch_base': '''$(cat "$2")''' }" } update-snippet() { if [[ -z "$1" ]]; then echo '$1 is empty.' exit fi if [[ -z "$2" ]]; then echo '$2 is empty.' exit fi # Generate view name from filename local file=$(basename "$2") local name="mint_system.${file%.xml}" load-env "$1" echo "Update view with name $name" images/odoocli/bin/odoocli --method 'write' --model 'ir.ui.view' --domain "[('name', '=', '$name')]" --field 'arch_base' --value "'''$(cat "$2")'''" } disable-snippet() { if [[ -z "$1" ]]; then echo '$1 is empty.' exit fi if [[ -z "$2" ]]; then echo '$2 is empty.' exit fi # Generate view name from filename local file=$(basename "$2") local name="mint_system.${file%.xml}" load-env "$1" echo "Disable view with name $name" images/odoocli/bin/odoocli --method 'write' --model "ir.ui.view" --domain "[('name', '=', '$name')]" --field "active" --value "False" } remove-snippet() { if [[ -z "$1" ]]; then echo '$1 is empty.' exit fi if [[ -z "$2" ]]; then echo '$2 is empty.' exit fi # Generate view name from filename local file=$(basename "$2") local name="mint_system.${file%.xml}" load-env "$1" echo "Remove view with name $name" images/odoocli/bin/odoocli --method "unlink" --model "ir.ui.view" --domain "[('name', '=', '$name')]" } lint-snippets() { echo 'Lint snippets files...' local files="snippets/*.xml" for file in $files; do # Access various snippet parameters local filename=$(basename "$file") local snippet_module=$(echo "$filename" | cut -d. -f1) local report=$(echo "$filename" | cut -d. -f2) local edit=$(echo "$filename" | cut -d. -f3) local inherit_id=$(echo 'cat //data/@inherit_id' | xmllint --shell "$file" | awk -F'[="]' '!/>/{print $(NF-1)}') local type=$(echo 'cat //data/@type' | xmllint --shell "$file" | awk -F'[="]' '!/>/{print $(NF-1)}') local model=$(echo 'cat //data/@model' | xmllint --shell "$file" | awk -F'[="]' '!/>/{print $(NF-1)}') local priority=$(echo 'cat //data/@priority' | xmllint --shell "$file" | awk -F'[="]' '!/>/{print $(NF-1)}') local module_name=$(echo "$inherit_id" | cut -d. -f 1 ) local xml_id=$(echo "$inherit_id" | cut -d. -f 2 ) # Check filename local count_dots=$(echo "$filename" | grep -o "\." | wc -l) if [[ "$count_dots" -ge 4 ]]; then echo "Linting failed for $file" echo "Dots count: $count_dots" echo " Filename: $snippet_module.$report" exit 1 fi # Check if inherit id matches the filename if [[ -n "$inherit_id" ]]; then if [[ "$inherit_id" != "$snippet_module.$report" ]]; then echo "Linting failed for $file" echo " Inherit: $inherit_id" echo "Filename: $snippet_module.$report" exit 1 fi fi # Check if priority is set if [[ -z "$priority" ]] && [[ -n "$inherit_id" ]]; then echo "Linting failed for $file" echo 'No priority is set.' exit 1 fi done echo 'No problems with snippets found.' } # Git commands add-git-submodule() { if [[ -z "$1" ]]; then echo '$1 is empty.' exit fi if [[ -z "$2" ]]; then echo '$2 is empty.' exit fi # Add to .gitmodules git submodule add -f -b "$ODOO_VERSION" "$1" "$2" # Ensure branch is Odoo version git -C "$2" switch "$ODOO_VERSION" # Save version save-version # Restore staged submodule files git restore --staged .gitmodules "$2" # Remove from working tree # rm -rf .git/modules/"$2" || true # git rm --cached "$2" || true } aggregate-git-folders() { if [[ -z "$1" ]]; then echo '$1 is empty.' exit fi gitaggregate -c "$1" --expand-env } ls-git-folder() { if [[ -n "$1" ]]; then echo $(git config --file .gitmodules --get-regexp url | sed -e 's/submodule.//g' -e 's/.url.*//g' | grep "$1" | sort) else echo $(git config --file .gitmodules --get-regexp url | sed -e 's/submodule.//g' -e 's/.url.*//g' | sort) fi } list-git-folder() { # Define column widths local path_width=35 local url_width=50 local branch_width=8 # Print table header printf "| %-${path_width}s | %-${url_width}s | %-${branch_width}s |\n" "PATH" "URL" "BRANCH" echo "|$(printf '%*s' $((path_width + 2)) '' | tr ' ' '-')|$(printf '%*s' $((url_width + 2)) '' | tr ' ' '-')|$(printf '%*s' $((branch_width + 2)) '' | tr ' ' '-')|" local git_folders=$(ls-git-folder "$1") for git_folder in $git_folders; do local url=$(git config --file .gitmodules --get "submodule.$git_folder.url") url=${url#git@github.com:} url=${url%.git} local branch=$(git -C "$git_folder" branch --show-current 2>/dev/null || echo "detached") printf "| %-${path_width}s | %-${url_width}s | %-${branch_width}s |\n" "$git_folder" "$url" "${branch:-detached}" done } remove-git-folders() { local git_folders=$(ls-git-folder) echo "Delete: $git_folders" read -p "Are you sure you want to delete these git folders? (yes/N): " confirm if [[ "${confirm,,}" != "yes" ]]; then echo 'Aborted.' return 1 fi for git_folder in $git_folders; do echo "Delete git folder $git_folder." rm -rf "$git_folder" done } remove-git-submodule() { if [[ -z "$1" ]]; then echo "\$1 is empty."; exit; fi echo 'Deinit submodule' git submodule deinit -f "$1" echo 'Remove submodule from work tree' git rm -f "$1" echo 'Remove submodule from .git folder' rm -rf ".git/modules/$1" echo 'Remove submodule from .gitmodules file' git config --file .gitmodules --remove-section submodule.addons/website save-version } clone-git-folder() { local git_folders=$(ls-git-folder "$1") for git_folder in $git_folders; do local git_folder_path=$(git config --file .gitmodules --get submodule."$git_folder".path) local git_folder_url=$(git config --file .gitmodules --get submodule."$git_folder".url) local shallow=$(git config --file .gitmodules --get submodule."$git_folder".shallow) local git_folder_branch=$(git config --file .gitmodules --get submodule."$git_folder".branch) if [[ "$shallow" = "true" ]]; then echo "Clone git folder $git_folder (shallow) with $git_folder_branch branch" git -C "$git_folder" config remote.origin.fetch "+refs/heads/*:refs/remotes/origin/*" git clone --depth 1 --no-single-branch "$git_folder_url" "$git_folder_path" else echo "Clone git folder $git_folder with $git_folder_branch branch" git clone --branch="$git_folder_branch" "$git_folder_url" "$git_folder_path" fi done } download-git-folder() { local git_folders=$(ls-git-folder "$1") for git_folder in $git_folders; do local git_folder_path=$(git config --file .gitmodules --get submodule."$git_folder".path) local git_folder_url=$(git config --file .gitmodules --get submodule."$git_folder".url) local git_folder_branch=$(git config --file .gitmodules --get submodule."$git_folder".branch) # Convert git url to http url. git_folder_url="$(echo $git_folder_url | sed 's|git@|https://|g' | sed 's|m:|m/|g' | sed 's|\.git||g')" # Get commit ref from .gitmodule.csv local commit_ref=$(grep "$git_folder_path" "versions/$ODOO_VERSION/.gitmodules.csv" | cut -d',' -f2) echo "Download $git_folder_url to $git_folder_path." mkdir -p "$git_folder_path" curl -L -o "$git_folder_path/archive.tar.gz" \ "$git_folder_url/archive/$commit_ref.tar.gz" echo "Extract and delete $git_folder_path/archive.tar.gz" tar -xzf "$git_folder_path/archive.tar.gz" --strip-components=1 -C "$git_folder_path" rm "$git_folder_path/archive.tar.gz" done } pull-git-folder() { local git_folders=$(ls-git-folder) for git_folder in $git_folders; do echo "Pull git folder $git_folder." git -C "$git_folder" stash local update=$(git config submodule."$git_folder".update) if [[ -z "$update" ]]; then git -C "$git_folder" pull origin "$ODOO_VERSION" fi; done } reset-git-folder() { local git_folders=$(ls-git-folder) for git_folder in $git_folders; do echo "Abort rebase and reset git folder $git_folder." git -C "$git_folder" rebase --abort git -C "$git_folder" reset --hard done } clean-git-folder() { local git_folders=$(ls-git-folder) for git_folder in $git_folders; do echo "Clean git folder $git_folder." git -C "$git_folder" clean -df done } checkout-git-folder() { echo 'Checkout submodule commits.' local git_folders=$(ls-git-folder) for git_folder in $git_folders; do local shallow=$(git config --file .gitmodules --get submodule."$git_folder".shallow) local commit_ref=$(grep "$git_folder" "versions/$ODOO_VERSION/.gitmodules.csv" | cut -d',' -f2) if [[ "$shallow" = "true" ]]; then echo "Checkout git folder $git_folder (shallow) to $commit_ref" git -C "$git_folder" stash git -C "$git_folder" git fetch origin "$ODOO_VERSION:$ODOO_VERSION" git -C "$git_folder" checkout "$commit_ref" else echo "Checkout git folder $git_folder to $commit_ref" git -C "$git_folder" stash git -C "$git_folder" checkout "$commit_ref" fi done } switch-git-folder() { if [[ -n "$1" ]]; then ODOO_VERSION="$1" fi local git_folders=$(ls-git-folder) for git_folder in $git_folders; do local shallow=$(git config --file .gitmodules --get submodule."$git_folder".shallow) if [[ "$shallow" = "true" ]]; then echo "Fetch branch $ODOO_VERSION (shallow) from origin and then switch." git -C "$git_folder" fetch origin "$ODOO_VERSION:$ODOO_VERSION" git -C "$git_folder" switch "$ODOO_VERSION" else echo "Switch git folder branch to $ODOO_VERSION: $git_folder" git -C "$git_folder" stash git -C "$git_folder" switch "$ODOO_VERSION" fi done } status-git-folder() { if [[ -z "$1" ]]; then echo "\$1 is empty."; exit; fi local git_folders=$(ls-git-folder "$1") echo 'Show status in git folders.' for git_folder in $git_folders; do git -C "$git_folder" status done } stage-git-folder() { if [[ -z "$1" ]]; then echo "\$1 is empty."; exit; fi local git_folders=$(ls-git-folder "$1") for git_folder in $git_folders; do echo "Stage all files in $git_folder." git -C "$git_folder" add --all done } commit-git-folder() { if [[ -z "$1" ]]; then echo "\$1 is empty."; exit; fi if [[ -z "$2" ]]; then echo "\$2 is empty."; exit; fi local git_folders=$(ls-git-folder "$1") for git_folder in $git_folders; do echo "Commit in $git_folder." git -C "$git_folder" commit --message "$2" done } push-git-folder() { if [[ -z "$1" ]]; then echo "\$1 is empty."; exit; fi local git_folders=$(ls-git-folder "$1") for git_folder in $git_folders; do echo "Push in $git_folder." git -C "$git_folder" push done } sync-git-folder() { echo 'Switch and pull submodules.' switch-git-folder pull-git-folder } create-git-feature-branch(){ if [[ -z "$1" ]]; then echo "\$1 is empty."; exit; fi local module_name=$(basename "$1") local module_path=$(dirname "$1") echo "Create feature branch for $module_name in ${module_path}..." cd ./"$module_path" || exit pwd git switch -c "feature-$module_name" } create-git-mig-branch(){ if [[ -z "$1" ]]; then echo "\$1 is empty."; exit; fi local module_name=$(basename "$1") local module_path=$(dirname "$1") echo "Create mig branch for $module_name in ${module_path}..." cd ./"$module_path" || exit pwd git switch -c "$ODOO_VERSION-mig-$module_name" } # Project commands load-version() { if [[ -n "$1" ]]; then ODOO_VERSION="$1" fi echo "Copy .gitmodules and .python-version from 'versions/$ODOO_VERSION'." cp "versions/$ODOO_VERSION/.gitmodules" . cp "versions/$ODOO_VERSION/.python-version" . } checkout() { if [[ -z "$1" ]]; then echo "\$1 is empty."; exit; fi ODOO_VERSION="$1" # Load .gitmodules and .python-version load-version "$ODOO_VERSION" # Set .env load-revision "$ODOO_VERSION" # Clone the addons clone-git-folder # Checkout commit ref checkout-git-folder # Upate branch switch-git-folder } save-version() { local git_folders_file="versions/$ODOO_VERSION/.gitmodules.csv" echo "Save submodule refs to $git_folders_file." local git_folders=$(ls-git-folder) echo "path,rev" > "$git_folders_file" for git_folder in $git_folders; do local commit_ref=$(git -C "$git_folder" rev-parse HEAD) echo "$git_folder,$commit_ref" >> "$git_folders_file" done echo "Copy .gitmodules and .python-version to 'versions/$ODOO_VERSION'" cp ".gitmodules" "versions/$ODOO_VERSION/" cp ".python-version" "versions/$ODOO_VERSION/" } list-versions() { ls -1 versions } check-version() { echo 'Check if Odoo version matches the release file:' local odoo_version=$(echo "$ODOO_VERSION" | cut -d'.' -f1) grep -E "version_info.+$odoo_version" odoo/odoo/release.py } # Revision commands list-revision() { ls -1 revisions } show-revision() { if [[ -n "$1" ]]; then ODOO_REVISION="$1" fi echo "Odoo Revision: $ODOO_REVISION" source "revisions/$ODOO_REVISION" echo "Odoo Ref: $ODOO_REF" echo "Odoo Enterprise Ref: $ODOO_ENTERPRISE_REF" echo "Odoo Themes Ref: $ODOO_THEME_REF" } load-revision() { if [[ -n "$1" ]]; then ODOO_REVISION="$1" fi echo 'Update revision var in .env file.' if grep -q "ODOO_REVISION" .env; then sed -i -e "s/^ODOO_REVISION=.*/ODOO_REVISION=${ODOO_REVISION}/" .env else echo "ODOO_REVISION=${ODOO_REVISION}" >> .env fi echo "Load refs for revision: $ODOO_REVISION" export $(cat "revisions/$ODOO_REVISION" | sed 's/#.*//g' | xargs) } checkout-revision() { if [[ -n "$1" ]]; then ODOO_REVISION="$1" fi load-revision "$ODOO_REVISION" echo 'Checkout Odoo git repos...' git -C odoo checkout $ODOO_REF git -C enterprise checkout $ODOO_ENTERPRISE_REF git -C themes checkout $ODOO_THEME_REF } create-revision() { if [[ -z "$1" ]]; then echo "\$1 is empty."; exit; fi local revision="$1" local odoo_date=$(echo "$revision" | cut -d'.' -f3-) local before_date=$(date -d "$odoo_date -1 day" +"%Y-%m-%d") echo "Checkout Odoo repos on $before_date." # Get git commit by date cd odoo git fetch echo -e "\nShow last 3 commits from $before_date:" git --no-pager log -n 3 --pretty=format:"%h %ad %s" --date=short --before="$before_date" origin/$ODOO_VERSION local ref=$(git rev-list -n 1 --before="$before_date" origin/$ODOO_VERSION) git checkout "$ref" cd .. cd enterprise git fetch echo -e "\nShow last 3 commits from $before_date:" git --no-pager log -n 3 --pretty=format:"%h %ad %s" --date=short --before="$before_date" origin/$ODOO_VERSION local enterprise_ref=$(git rev-list -n 1 --before="$before_date" origin/$ODOO_VERSION) git checkout "$enterprise_ref" cd .. cd themes git fetch echo -e "\nShow last 3 commits from $before_date:" git --no-pager log -n 3 --pretty=format:"%h %ad %s" --date=short --before="$before_date" origin/$ODOO_VERSION local theme_ref=$(git rev-list -n 1 --before="$before_date" origin/$ODOO_VERSION) git checkout "$theme_ref" cd .. echo -e "\nCreate revision: $revision" export ODOO_REVISION=$revision export ODOO_REF=$ref export ODOO_ENTERPRISE_REF=$enterprise_ref export ODOO_THEME_REF=$theme_ref envsubst < templates/odoo_revision > "revisions/$revision" chmod +x "revisions/$revision" echo "Checkout revision: $revision" checkout-revision "$revision" } commit-and-push-revision() { if [[ -n "$1" ]]; then ODOO_REVISION="$1" fi echo 'Remove existing tag...' git tag -d "$ODOO_REVISION" echo "Commit with revision $ODOO_REVISION tag..." git commit --all --message "feat(revision): release $ODOO_REVISION" git tag "$ODOO_REVISION" echo 'Push tags...' git push git push -f --tags } # RPC commands test-xmlrpc() { load-env "$1" echo "Connect to database and count res.partner records: $(images/odoocli/bin/odoocli --model 'res.partner' --method 'search_count' --domain '[]')" } odoocli(){ images/odoocli/bin/odoocli "$@" } export-website-data() { if [[ -n "$1" ]]; then load-env $1 fi export ODOO_URL export ODOO_DATABASE export ODOO_USERNAME export ODOO_PASSWORD export ODOO_WEBSITE_ID echo -e "\nPython Output:\n\n---" python "bin/export-website-data" local exit_code=$? echo -e "---\n" echo -e "Exit code: $exit_code\n" } import-website-data() { if [[ -n "$1" ]]; then load-env $1 fi export ODOO_URL export ODOO_DATABASE export ODOO_USERNAME export ODOO_PASSWORD export ODOO_WEBSITE_ID echo -e "\nPython Output:\n\n---" python "bin/import-website-data" local exit_code=$? echo -e "---\n" echo -e "Exit code: $exit_code\n" } # Vuepress commands install-vuepress() { echo 'Install Node.' pnpm env use --global lts echo 'Install npm packages.' pnpm install } dev-vuepress() { pnpm run dev } build-vuepress() { pnpm run build } serve-vuepress() { cd .vuepress/dist pnpx serve } if declare -f "$1" > /dev/null; then activate-venv "$1" "${@:2}" else case "$1" in source) activate-venv ;; *) echo "Unknown command: $1" help exit 1 ;; esac fi