diff --git a/.gitea/scripts/odoo-tests.sh b/.gitea/scripts/odoo-tests.sh new file mode 100755 index 0000000..56e1f97 --- /dev/null +++ b/.gitea/scripts/odoo-tests.sh @@ -0,0 +1,42 @@ +#!/bin/bash +# +# Run the Python tests of the given Odoo modules. +# +# This script is meant to be executed *inside* an Elabore doodba image +# (docker.0k.io/mirror/odoo:rc_-ELABORE-LIGHT), with a +# PostgreSQL service reachable at $PGHOST. The modules under test are +# read from the current working directory (the checked-out repository), +# which is prepended to the addons path so that the PR's code takes +# precedence over the modules already baked in the image. +# +# Usage: odoo-tests.sh + +set -euo pipefail + +modules="${1:?usage: odoo-tests.sh }" + +export PGHOST=postgres +export PGUSER=odoo +export PGPASSWORD=odoopassword +export PGDATABASE="test_$(basename "$PWD" | tr '.-' '__')" +export WITHOUT_DEMO=all +export DB_FILTER='.*' + +# Generate /opt/odoo/auto/odoo.conf from the environment and wait for +# the database to accept connections. +/opt/odoo/common/entrypoint + +# Odoo test-tags format: "/module1,/module2" (one leading slash). +tags="/$(echo "$modules" | tr ',' ',/')" + +exec odoo \ + --config=/opt/odoo/auto/odoo.conf \ + --max-cron-threads=0 \ + --limit-time-real=10000 \ + --workers=0 \ + --test-enable \ + --stop-after-init \ + --addons-path="$PWD,/opt/odoo/auto/addons" \ + -d "$PGDATABASE" \ + -i "$modules" \ + --test-tags "$tags" diff --git a/.gitea/workflows/pre-commit.yml b/.gitea/workflows/pre-commit.yml index 039e8d9..dacf086 100644 --- a/.gitea/workflows/pre-commit.yml +++ b/.gitea/workflows/pre-commit.yml @@ -2,8 +2,6 @@ name: pre-commit on: pull_request: - branches: - - "18.0*" jobs: pre-commit: @@ -15,10 +13,10 @@ jobs: python-version: "3.11" - name: Get python version run: echo "PY=$(python -VV | sha256sum | cut -d' ' -f1)" >> $GITHUB_ENV - # - uses: actions/cache@v4 - # with: - # path: ~/.cache/pre-commit - # key: pre-commit|${{ env.PY }}|${{ hashFiles('.pre-commit-config.yaml') }} + - uses: https://gitea.com/actions/cache@v3 + with: + path: ~/.cache/pre-commit + key: pre-commit|${{ env.PY }}|${{ hashFiles('.pre-commit-config.yaml') }} - name: Install pre-commit run: pip install pre-commit - name: Run pre-commit diff --git a/.gitea/workflows/test.yml b/.gitea/workflows/test.yml new file mode 100644 index 0000000..6864da3 --- /dev/null +++ b/.gitea/workflows/test.yml @@ -0,0 +1,79 @@ +name: tests + +on: + pull_request: + branches: + - "18.0*" + workflow_dispatch: + +jobs: + test: + runs-on: odoo-ci + container: + image: docker.0k.io/mirror/odoo:rc_18.0-ELABORE-LIGHT + services: + postgres: + image: docker.0k.io/postgres:17.2.0-myc + env: + POSTGRES_USER: odoo + POSTGRES_PASSWORD: odoopassword + POSTGRES_DB: postgres + steps: + # The doodba image ships no Node.js, so actions/checkout@v4 cannot + # run here. The repository is private, so the job token is required. + - name: Checkout + env: + SECRET_GITEA_TOKEN: ${{ secrets.GITEA_TOKEN }} + SECRET_GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + set -eu + token="${GITEA_TOKEN:-${GITHUB_TOKEN:-${SECRET_GITEA_TOKEN:-${SECRET_GITHUB_TOKEN:-}}}}" + if [ -z "$token" ]; then + echo "::error::No job token available to clone the repository" + exit 1 + fi + server="${GITHUB_SERVER_URL:-https://git.elabore.coop}" + repo="${GITHUB_REPOSITORY:?GITHUB_REPOSITORY is not set}" + ref="${GITHUB_REF:-${GITHUB_SHA:?neither GITHUB_REF nor GITHUB_SHA is set}}" + auth_url="$(printf '%s' "${server%/}/${repo}.git" | + sed -E "s#^https://#https://${token}@#")" + git init -q . + git config --global --add safe.directory "$PWD" + git remote add origin "$auth_url" + git fetch -q --depth=1 origin "$ref" + git checkout -q FETCH_HEAD + + - name: Detect changed modules + id: changed + run: | + set -eu + if [ -n "${GITHUB_BASE_REF:-}" ]; then + git fetch -q --depth=1 origin "refs/heads/$GITHUB_BASE_REF" + base_rev="FETCH_HEAD" + scope="diff against $GITHUB_BASE_REF" + else + base_rev="" + scope="all installable modules (manual run)" + fi + if [ -n "$base_rev" ]; then + modules="$( + git diff --name-only "$base_rev" HEAD | + awk -F/ '{print $1}' | sort -u | + while read -r dir; do + [ -f "$dir/__manifest__.py" ] && echo "$dir" + done | paste -sd, - + )" + else + modules="$( + for dir in */; do + dir="${dir%/}" + [ -f "$dir/__manifest__.py" ] && echo "$dir" + done | paste -sd, - + )" + fi + echo "modules=$modules" >> "$GITHUB_OUTPUT" + echo "Modules to test ($scope): ${modules:-}" + + - name: Run Odoo tests + if: steps.changed.outputs.modules != '' + run: bash .gitea/scripts/odoo-tests.sh "${{ steps.changed.outputs.modules }}" diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 9abafc6..f4ed539 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -22,7 +22,7 @@ exclude: | (LICENSE.*|COPYING.*) default_language_version: python: python3 - node: "16.17.0" + node: "22.9.0" repos: - repo: local hooks: @@ -38,8 +38,12 @@ repos: entry: found a en.po file language: fail files: '[a-zA-Z0-9_]*/i18n/en\.po$' + - repo: https://github.com/sbidoul/whool + rev: v1.3 + hooks: + - id: whool-init - repo: https://github.com/oca/maintainer-tools - rev: f9b919b9868143135a9c9cb03021089cabba8223 + rev: b89f767503be6ab2b11e4f50a7557cb20066e667 hooks: # update the NOT INSTALLABLE ADDONS section above - id: oca-update-pre-commit-excluded-addons @@ -49,13 +53,18 @@ repos: $(git rev-parse --show-toplevel))"' - id: oca-gen-addon-readme entry: - bash -c 'oca-gen-addon-readme --addons-dir=. --branch=$(git symbolic-ref + bash -c 'oca-gen-addon-readme + --addons-dir=. + --branch=$(git symbolic-ref refs/remotes/origin/HEAD | sed "s@^refs/remotes/origin/@@") - --repo-name=$(basename $(git rev-parse --show-toplevel)) --org-name="Elabore" - --if-source-changed --keep-source-digest' - + --repo-name=$(basename $(git rev-parse --show-toplevel)) + --org-name="Elabore" + --if-source-changed + --keep-source-digest + --convert-fragments-to-markdown' + - id: oca-gen-external-dependencies - repo: https://github.com/OCA/odoo-pre-commit-hooks - rev: v0.1.4 + rev: v0.0.33 hooks: - id: oca-checks-odoo-module - id: oca-checks-po @@ -74,8 +83,8 @@ repos: files: \.(css|htm|html|js|json|jsx|less|md|scss|toml|ts|xml|yaml|yml)$ language: node additional_dependencies: - - "prettier@2.7.1" - - "@prettier/plugin-xml@2.2.0" + - "prettier@3.3.3" + - "@prettier/plugin-xml@3.4.1" - repo: local hooks: - id: eslint @@ -88,10 +97,11 @@ repos: types: [javascript] language: node additional_dependencies: - - "eslint@8.24.0" - - "eslint-plugin-jsdoc@" + - "eslint@9.12.0" + - "eslint-plugin-jsdoc@50.3.1" + - "globals@16.0.0" - repo: https://github.com/pre-commit/pre-commit-hooks - rev: v5.0.0 + rev: v4.6.0 hooks: - id: trailing-whitespace # exclude autogenerated files @@ -112,23 +122,8 @@ repos: - id: check-xml - id: mixed-line-ending args: ["--fix=lf"] - - repo: https://github.com/PyCQA/docformatter - rev: v1.7.7 - hooks: - - id: docformatter - args: [ - "--in-place", # modify the files - "--recursive", # run on all the files - "--wrap-summaries", - "88", # max length of 1st line - "--wrap-descriptions", - "88", # max length of other lines - "--pre-summary-newline", # new line before a long summary - "--make-summary-multi-line", # force summary on multilines - ] - additional_dependencies: ["tomli"] # if Python <3.11 - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.12.0 + rev: v0.6.8 hooks: - id: ruff args: [--fix, --exit-non-zero-on-fix] diff --git a/.pylintrc b/.pylintrc index 8d996d7..47e79c4 100644 --- a/.pylintrc +++ b/.pylintrc @@ -25,19 +25,25 @@ disable=all enable=anomalous-backslash-in-string, api-one-deprecated, api-one-multi-together, - assignment-from-none, - attribute-deprecated, class-camelcase, - dangerous-default-value, dangerous-view-replace-wo-priority, - development-status-allowed, duplicate-id-csv, - duplicate-key, duplicate-xml-fields, duplicate-xml-record-id, eval-referenced, - eval-used, incoherent-interpreter-exec-perm, + openerp-exception-warning, + redundant-modulename-xml, + relative-import, + rst-syntax-error, + wrong-tabs-instead-of-spaces, + xml-syntax-error, + assignment-from-none, + attribute-deprecated, + dangerous-default-value, + development-status-allowed, + duplicate-key, + eval-used, license-allowed, manifest-author-string, manifest-deprecated-key, @@ -48,73 +54,68 @@ enable=anomalous-backslash-in-string, method-inverse, method-required-super, method-search, - openerp-exception-warning, pointless-statement, pointless-string-statement, print-used, redundant-keyword-arg, - redundant-modulename-xml, reimported, - relative-import, return-in-init, - rst-syntax-error, sql-injection, too-few-format-args, translation-field, translation-required, unreachable, use-vim-comment, - wrong-tabs-instead-of-spaces, - xml-syntax-error, - attribute-string-redundant, character-not-valid-in-resource-link, - consider-merging-classes-inherited, - context-overridden, create-user-wo-reset-password, dangerous-filter-wo-user, dangerous-qweb-replace-wo-priority, deprecated-data-xml-node, deprecated-openerp-xml-node, duplicate-po-message-definition, - except-pass, file-not-used, - invalid-commit, - manifest-maintainers-list, missing-newline-extrafiles, - missing-readme, - missing-return, - odoo-addons-relative-import, old-api7-method-defined, po-msgstr-variables, po-syntax-error, - renamed-field-parameter, - resource-not-exist, str-format-used, - test-folder-imported, - translation-contains-variable, - translation-positional-used, unnecessary-utf8-coding-comment, - website-manifest-key-not-valid-uri, xml-attribute-translatable, xml-deprecated-qweb-directive, xml-deprecated-tree-attribute, - external-request-timeout, - # messages that do not cause the lint step to fail + attribute-string-redundant, consider-merging-classes-inherited, + context-overridden, + except-pass, + invalid-commit, + manifest-maintainers-list, + missing-readme, + missing-return, + odoo-addons-relative-import, + renamed-field-parameter, + resource-not-exist, + test-folder-imported, + translation-contains-variable, + translation-positional-used, + website-manifest-key-not-valid-uri, + external-request-timeout, + missing-manifest-dependency, + too-complex,, create-user-wo-reset-password, dangerous-filter-wo-user, - deprecated-module, file-not-used, - invalid-commit, - missing-manifest-dependency, missing-newline-extrafiles, - missing-readme, no-utf8-coding-comment, - odoo-addons-relative-import, old-api7-method-defined, + unnecessary-utf8-coding-comment, + # messages that do not cause the lint step to fail + consider-merging-classes-inherited, + deprecated-module, + invalid-commit, + missing-readme, + odoo-addons-relative-import, redefined-builtin, - too-complex, - unnecessary-utf8-coding-comment + manifest-external-assets [REPORTS] diff --git a/.pylintrc-mandatory b/.pylintrc-mandatory index aa43e02..00f2c70 100644 --- a/.pylintrc-mandatory +++ b/.pylintrc-mandatory @@ -17,19 +17,25 @@ disable=all enable=anomalous-backslash-in-string, api-one-deprecated, api-one-multi-together, - assignment-from-none, - attribute-deprecated, class-camelcase, - dangerous-default-value, dangerous-view-replace-wo-priority, - development-status-allowed, duplicate-id-csv, - duplicate-key, duplicate-xml-fields, duplicate-xml-record-id, eval-referenced, - eval-used, incoherent-interpreter-exec-perm, + openerp-exception-warning, + redundant-modulename-xml, + relative-import, + rst-syntax-error, + wrong-tabs-instead-of-spaces, + xml-syntax-error, + assignment-from-none, + attribute-deprecated, + dangerous-default-value, + development-status-allowed, + duplicate-key, + eval-used, license-allowed, manifest-author-string, manifest-deprecated-key, @@ -40,56 +46,50 @@ enable=anomalous-backslash-in-string, method-inverse, method-required-super, method-search, - openerp-exception-warning, pointless-statement, pointless-string-statement, print-used, redundant-keyword-arg, - redundant-modulename-xml, reimported, - relative-import, return-in-init, - rst-syntax-error, sql-injection, too-few-format-args, translation-field, translation-required, unreachable, use-vim-comment, - wrong-tabs-instead-of-spaces, - xml-syntax-error, - attribute-string-redundant, character-not-valid-in-resource-link, - consider-merging-classes-inherited, - context-overridden, create-user-wo-reset-password, dangerous-filter-wo-user, dangerous-qweb-replace-wo-priority, deprecated-data-xml-node, deprecated-openerp-xml-node, duplicate-po-message-definition, - except-pass, file-not-used, - invalid-commit, - manifest-maintainers-list, missing-newline-extrafiles, - missing-readme, - missing-return, - odoo-addons-relative-import, old-api7-method-defined, po-msgstr-variables, po-syntax-error, - renamed-field-parameter, - resource-not-exist, str-format-used, - test-folder-imported, - translation-contains-variable, - translation-positional-used, unnecessary-utf8-coding-comment, - website-manifest-key-not-valid-uri, xml-attribute-translatable, xml-deprecated-qweb-directive, xml-deprecated-tree-attribute, + attribute-string-redundant, + consider-merging-classes-inherited, + context-overridden, + except-pass, + invalid-commit, + manifest-maintainers-list, + missing-readme, + missing-return, + odoo-addons-relative-import, + renamed-field-parameter, + resource-not-exist, + test-folder-imported, + translation-contains-variable, + translation-positional-used, + website-manifest-key-not-valid-uri, external-request-timeout [REPORTS] diff --git a/eslint.config.cjs b/eslint.config.cjs new file mode 100644 index 0000000..dd0cbe0 --- /dev/null +++ b/eslint.config.cjs @@ -0,0 +1,205 @@ +var globals = require('globals'); +jsdoc = require("eslint-plugin-jsdoc"); + +const config = [{ + plugins: { + jsdoc, + }, + + languageOptions: { + globals: { + _: "readonly", + $: "readonly", + fuzzy: "readonly", + jQuery: "readonly", + moment: "readonly", + odoo: "readonly", + openerp: "readonly", + owl: "readonly", + luxon: "readonly", + QUnit: "readonly", + ...globals.browser, + }, + + ecmaVersion: 2024, + sourceType: "script", + }, + + rules: { + "accessor-pairs": "warn", + "array-callback-return": "warn", + "callback-return": "warn", + "capitalized-comments": ["warn", "always", { + ignoreConsecutiveComments: true, + ignoreInlineComments: true, + }], + complexity: ["warn", 15], + "constructor-super": "warn", + "dot-notation": "warn", + eqeqeq: "warn", + "global-require": "warn", + "handle-callback-err": "warn", + "id-blacklist": "warn", + "id-match": "warn", + "init-declarations": "error", + "max-depth": "warn", + "max-nested-callbacks": "warn", + "max-statements-per-line": "warn", + "no-alert": "warn", + "no-array-constructor": "warn", + "no-caller": "warn", + "no-case-declarations": "warn", + "no-class-assign": "warn", + "no-cond-assign": "error", + "no-const-assign": "error", + "no-constant-condition": "warn", + "no-control-regex": "warn", + "no-debugger": "error", + "no-delete-var": "warn", + "no-div-regex": "warn", + "no-dupe-args": "error", + "no-dupe-class-members": "error", + "no-dupe-keys": "error", + "no-duplicate-case": "error", + "no-duplicate-imports": "error", + "no-else-return": "warn", + "no-empty-character-class": "warn", + "no-empty-function": "error", + "no-empty-pattern": "error", + "no-empty": "warn", + "no-eq-null": "error", + "no-eval": "error", + "no-ex-assign": "error", + "no-extend-native": "warn", + "no-extra-bind": "warn", + "no-extra-boolean-cast": "warn", + "no-extra-label": "warn", + "no-fallthrough": "warn", + "no-func-assign": "error", + "no-global-assign": "error", + "no-implicit-coercion": ["warn", { + allow: ["~"], + }], + "no-implicit-globals": "warn", + "no-implied-eval": "warn", + "no-inline-comments": "warn", + "no-inner-declarations": "warn", + "no-invalid-regexp": "warn", + "no-irregular-whitespace": "warn", + "no-iterator": "warn", + "no-label-var": "warn", + "no-labels": "warn", + "no-lone-blocks": "warn", + "no-lonely-if": "error", + "no-mixed-requires": "error", + "no-multi-str": "warn", + "no-native-reassign": "error", + "no-negated-condition": "warn", + "no-negated-in-lhs": "error", + "no-new-func": "warn", + "no-new-object": "warn", + "no-new-require": "warn", + "no-new-symbol": "warn", + "no-new-wrappers": "warn", + "no-new": "warn", + "no-obj-calls": "warn", + "no-octal-escape": "warn", + "no-octal": "warn", + "no-param-reassign": "warn", + "no-path-concat": "warn", + "no-process-env": "warn", + "no-process-exit": "warn", + "no-proto": "warn", + "no-prototype-builtins": "warn", + "no-redeclare": "warn", + "no-regex-spaces": "warn", + "no-restricted-globals": "warn", + "no-restricted-imports": "warn", + "no-restricted-modules": "warn", + "no-restricted-syntax": "warn", + "no-return-assign": "error", + "no-script-url": "warn", + "no-self-assign": "warn", + "no-self-compare": "warn", + "no-sequences": "warn", + "no-shadow-restricted-names": "warn", + "no-shadow": "warn", + "no-sparse-arrays": "warn", + "no-sync": "warn", + "no-this-before-super": "warn", + "no-throw-literal": "warn", + "no-undef-init": "warn", + "no-undef": "error", + "no-unmodified-loop-condition": "warn", + "no-unneeded-ternary": "error", + "no-unreachable": "error", + "no-unsafe-finally": "error", + "no-unused-expressions": "error", + "no-unused-labels": "error", + "no-unused-vars": "error", + "no-use-before-define": "error", + "no-useless-call": "warn", + "no-useless-computed-key": "warn", + "no-useless-concat": "warn", + "no-useless-constructor": "warn", + "no-useless-escape": "warn", + "no-useless-rename": "warn", + "no-void": "warn", + "no-with": "warn", + "operator-assignment": ["error", "always"], + "prefer-const": "warn", + radix: "warn", + "require-yield": "warn", + "sort-imports": "warn", + "spaced-comment": ["error", "always"], + strict: ["error", "function"], + "use-isnan": "error", + + "jsdoc/check-tag-names": "warn", + "jsdoc/check-types": "warn", + "jsdoc/require-param-description": "off", + "jsdoc/require-return": "off", + "jsdoc/require-return-description": "off", + "jsdoc/require-return-type": "off", + + "valid-typeof": "warn", + yoda: "warn", + }, + + settings: { + jsdoc: { + tagNamePreference: { + arg: "param", + argument: "param", + augments: "extends", + constructor: "class", + exception: "throws", + func: "function", + method: "function", + prop: "property", + return: "returns", + virtual: "abstract", + yield: "yields", + }, + preferredTypes: { + array: "Array", + bool: "Boolean", + boolean: "Boolean", + number: "Number", + object: "Object", + str: "String", + string: "String", + }, + }, + }, + +}, { + files: ["**/*.esm.js", "**/*test.js"], + + languageOptions: { + ecmaVersion: 2024, + sourceType: "module", + }, +}]; + +module.exports = config diff --git a/prettier.config.cjs b/prettier.config.cjs new file mode 100644 index 0000000..e66cd82 --- /dev/null +++ b/prettier.config.cjs @@ -0,0 +1,14 @@ +/** @type {import('prettier').Config} */ + +const config = { + // https://github.com/prettier/prettier/issues/15388#issuecomment-1717746872 + plugins: [require.resolve("@prettier/plugin-xml")], + bracketSpacing: false, + printWidth: 88, + proseWrap: "always", + semi: true, + trailingComma: "es5", + xmlWhitespaceSensitivity: "preserve", +}; + +module.exports = config;