From 0cbac78bbfd68ecc11174c502af50e8ccf70910f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89rico=20Andrei?= Date: Tue, 7 May 2024 12:24:49 -0300 Subject: [PATCH] Initial commit --- .editorconfig | 36 ++ .github/CODEOWNERS | 2 + .github/workflows/backend_addon.yml | 44 +++ .github/workflows/frontend_addon.yml | 168 +++++++++ .gitignore | 11 + .reports/.gitkeep | 0 .scripts/report_context.py | 33 ++ .scripts/report_keys_usage.py | 79 ++++ LICENSE | 21 ++ Makefile | 53 +++ README.md | 49 +++ backend_addon/Makefile | 44 +++ backend_addon/README.md | 65 ++++ backend_addon/cookiecutter.json | 41 +++ backend_addon/hooks/post_gen_project.py | 73 ++++ backend_addon/hooks/pre_prompt.py | 50 +++ backend_addon/tests/__init__.py | 0 backend_addon/tests/conftest.py | 122 +++++++ backend_addon/tests/test_cutter.py | 58 +++ .../tests/test_cutter_no_headless.py | 63 ++++ .../.editorconfig | 54 +++ .../{{ cookiecutter.__folder_name }}/.flake8 | 22 ++ .../.github/workflows/meta.yml | 68 ++++ .../.gitignore | 51 +++ .../.meta.toml | 19 + .../.pre-commit-config.yaml | 94 +++++ .../CHANGES.md | 10 + .../CONTRIBUTORS.md | 3 + .../LICENSE.GPL | 339 ++++++++++++++++++ .../LICENSE.md | 15 + .../MANIFEST.in | 18 + .../{{ cookiecutter.__folder_name }}/Makefile | 116 ++++++ .../README.md | 36 ++ .../constraints.txt | 1 + .../docs/.gitkeep | 0 .../instance.yaml | 8 + .../{{ cookiecutter.__folder_name }}/mx.ini | 16 + .../news/.changelog_template.jinja | 15 + .../news/.gitkeep | 1 + .../pyproject.toml | 161 +++++++++ .../requirements.txt | 2 + .../{{ cookiecutter.__folder_name }}/setup.py | 76 ++++ .../__init__.py | 1 + .../__init__.py | 10 + .../configure.zcml | 24 ++ .../content/__init__.py | 0 .../controlpanel/__init__.py | 0 .../controlpanel/configure.zcml | 8 + .../dependencies.zcml | 9 + .../indexers/__init__.py | 0 .../indexers/configure.zcml | 5 + .../interfaces.py | 6 + .../locales/__init__.py | 0 .../{{ cookiecutter.python_package_name }}.po | 15 + .../locales/update.py | 94 +++++ ...{{ cookiecutter.python_package_name }}.pot | 18 + .../profiles.zcml | 32 ++ .../profiles/default/browserlayer.xml | 6 + .../profiles/default/catalog.xml | 13 + .../profiles/default/controlpanel.xml | 4 + .../profiles/default/diff_tool.xml | 6 + .../profiles/default/metadata.xml | 7 + .../profiles/default/registry/.gitkeep | 0 .../profiles/default/repositorytool.xml | 6 + .../profiles/default/rolemap.xml | 6 + .../profiles/default/theme.xml | 5 + .../profiles/default/types.xml | 10 + .../profiles/default/types/.gitkeep | 0 .../profiles/uninstall/browserlayer.xml | 6 + .../serializers/__init__.py | 0 .../serializers/configure.zcml | 10 + .../serializers/summary.py | 9 + .../setuphandlers/__init__.py | 11 + .../testing.py | 50 +++ .../upgrades/__init__.py | 0 .../upgrades/configure.zcml | 19 + .../vocabularies/__init__.py | 0 .../vocabularies/configure.zcml | 3 + .../tests/conftest.py | 18 + .../tests/setup/test_setup_install.py | 17 + .../tests/setup/test_setup_uninstall.py | 19 + .../{{ cookiecutter.__folder_name }}/tox.ini | 211 +++++++++++ cookiecutter.json | 14 + frontend_addon/.gitignore | 1 + frontend_addon/Makefile | 44 +++ frontend_addon/README.md | 66 ++++ frontend_addon/cookiecutter.json | 42 +++ frontend_addon/hooks/post_gen_project.py | 30 ++ frontend_addon/hooks/pre_gen_project.py | 71 ++++ frontend_addon/hooks/pre_prompt.py | 63 ++++ frontend_addon/tests/__init__.py | 0 frontend_addon/tests/conftest.py | 64 ++++ frontend_addon/tests/test_cutter.py | 119 ++++++ .../.eslintrc.js | 22 ++ .../.github/workflows/acceptance.yml | 106 ++++++ .../.github/workflows/changelog.yml | 63 ++++ .../.github/workflows/code.yml | 51 +++ .../.github/workflows/i18n.yml | 51 +++ .../.github/workflows/storybook.yml | 62 ++++ .../.github/workflows/unit.yml | 51 +++ .../.gitignore | 12 + .../.npmignore | 16 + .../{{ cookiecutter.__folder_name }}/.npmrc | 6 + .../.prettierignore | 3 + .../.prettierrc | 12 + .../.storybook/main.js | 188 ++++++++++ .../.storybook/preview.jsx | 26 ++ .../.stylelintrc | 32 ++ .../{{ cookiecutter.__folder_name }}/Makefile | 103 ++++++ .../README.md | 193 ++++++++++ .../cypress.config.js | 13 + .../cypress/.gitkeep | 0 .../cypress/support/commands.js | 1 + .../cypress/support/e2e.js | 15 + .../cypress/tests/.gitkeep | 0 .../cypress/tests/example.cy.js | 20 ++ .../jest-addon.config.js | 17 + .../mrs.developer.json | 9 + .../package.json | 44 +++ .../.gitignore | 3 + .../.release-it.json | 19 + .../CHANGELOG.md | 9 + .../babel.config.js | 17 + .../locales/de/LC_MESSAGES/volto.po | 12 + .../locales/en/LC_MESSAGES/volto.po | 12 + .../locales/es/LC_MESSAGES/volto.po | 19 + .../locales/pt_BR/LC_MESSAGES/volto.po | 17 + .../locales/volto.pot | 14 + .../news/.gitkeep | 0 .../package.json | 41 +++ .../src/components/.gitkeep | 0 .../src/index.js | 5 + .../towncrier.toml | 33 ++ .../tsconfig.json | 32 ++ .../pnpm-workspace.yaml | 4 + .../volto.config.js | 7 + hooks/pre_prompt.py | 23 ++ pyproject.toml | 27 ++ requirements.txt | 9 + 139 files changed, 4728 insertions(+) create mode 100644 .editorconfig create mode 100644 .github/CODEOWNERS create mode 100644 .github/workflows/backend_addon.yml create mode 100644 .github/workflows/frontend_addon.yml create mode 100644 .gitignore create mode 100644 .reports/.gitkeep create mode 100644 .scripts/report_context.py create mode 100644 .scripts/report_keys_usage.py create mode 100644 LICENSE create mode 100644 Makefile create mode 100644 README.md create mode 100644 backend_addon/Makefile create mode 100644 backend_addon/README.md create mode 100644 backend_addon/cookiecutter.json create mode 100644 backend_addon/hooks/post_gen_project.py create mode 100644 backend_addon/hooks/pre_prompt.py create mode 100644 backend_addon/tests/__init__.py create mode 100644 backend_addon/tests/conftest.py create mode 100644 backend_addon/tests/test_cutter.py create mode 100644 backend_addon/tests/test_cutter_no_headless.py create mode 100644 backend_addon/{{ cookiecutter.__folder_name }}/.editorconfig create mode 100644 backend_addon/{{ cookiecutter.__folder_name }}/.flake8 create mode 100644 backend_addon/{{ cookiecutter.__folder_name }}/.github/workflows/meta.yml create mode 100644 backend_addon/{{ cookiecutter.__folder_name }}/.gitignore create mode 100644 backend_addon/{{ cookiecutter.__folder_name }}/.meta.toml create mode 100644 backend_addon/{{ cookiecutter.__folder_name }}/.pre-commit-config.yaml create mode 100644 backend_addon/{{ cookiecutter.__folder_name }}/CHANGES.md create mode 100644 backend_addon/{{ cookiecutter.__folder_name }}/CONTRIBUTORS.md create mode 100644 backend_addon/{{ cookiecutter.__folder_name }}/LICENSE.GPL create mode 100644 backend_addon/{{ cookiecutter.__folder_name }}/LICENSE.md create mode 100644 backend_addon/{{ cookiecutter.__folder_name }}/MANIFEST.in create mode 100644 backend_addon/{{ cookiecutter.__folder_name }}/Makefile create mode 100644 backend_addon/{{ cookiecutter.__folder_name }}/README.md create mode 100644 backend_addon/{{ cookiecutter.__folder_name }}/constraints.txt create mode 100644 backend_addon/{{ cookiecutter.__folder_name }}/docs/.gitkeep create mode 100644 backend_addon/{{ cookiecutter.__folder_name }}/instance.yaml create mode 100644 backend_addon/{{ cookiecutter.__folder_name }}/mx.ini create mode 100644 backend_addon/{{ cookiecutter.__folder_name }}/news/.changelog_template.jinja create mode 100644 backend_addon/{{ cookiecutter.__folder_name }}/news/.gitkeep create mode 100644 backend_addon/{{ cookiecutter.__folder_name }}/pyproject.toml create mode 100644 backend_addon/{{ cookiecutter.__folder_name }}/requirements.txt create mode 100644 backend_addon/{{ cookiecutter.__folder_name }}/setup.py create mode 100644 backend_addon/{{ cookiecutter.__folder_name }}/src/{{ cookiecutter.__package_namespace }}/__init__.py create mode 100644 backend_addon/{{ cookiecutter.__folder_name }}/src/{{ cookiecutter.__package_namespace }}/{{ cookiecutter.__package_name }}/__init__.py create mode 100644 backend_addon/{{ cookiecutter.__folder_name }}/src/{{ cookiecutter.__package_namespace }}/{{ cookiecutter.__package_name }}/configure.zcml create mode 100644 backend_addon/{{ cookiecutter.__folder_name }}/src/{{ cookiecutter.__package_namespace }}/{{ cookiecutter.__package_name }}/content/__init__.py create mode 100644 backend_addon/{{ cookiecutter.__folder_name }}/src/{{ cookiecutter.__package_namespace }}/{{ cookiecutter.__package_name }}/controlpanel/__init__.py create mode 100644 backend_addon/{{ cookiecutter.__folder_name }}/src/{{ cookiecutter.__package_namespace }}/{{ cookiecutter.__package_name }}/controlpanel/configure.zcml create mode 100644 backend_addon/{{ cookiecutter.__folder_name }}/src/{{ cookiecutter.__package_namespace }}/{{ cookiecutter.__package_name }}/dependencies.zcml create mode 100644 backend_addon/{{ cookiecutter.__folder_name }}/src/{{ cookiecutter.__package_namespace }}/{{ cookiecutter.__package_name }}/indexers/__init__.py create mode 100644 backend_addon/{{ cookiecutter.__folder_name }}/src/{{ cookiecutter.__package_namespace }}/{{ cookiecutter.__package_name }}/indexers/configure.zcml create mode 100644 backend_addon/{{ cookiecutter.__folder_name }}/src/{{ cookiecutter.__package_namespace }}/{{ cookiecutter.__package_name }}/interfaces.py create mode 100644 backend_addon/{{ cookiecutter.__folder_name }}/src/{{ cookiecutter.__package_namespace }}/{{ cookiecutter.__package_name }}/locales/__init__.py create mode 100644 backend_addon/{{ cookiecutter.__folder_name }}/src/{{ cookiecutter.__package_namespace }}/{{ cookiecutter.__package_name }}/locales/en/LC_MESSAGES/{{ cookiecutter.python_package_name }}.po create mode 100644 backend_addon/{{ cookiecutter.__folder_name }}/src/{{ cookiecutter.__package_namespace }}/{{ cookiecutter.__package_name }}/locales/update.py create mode 100644 backend_addon/{{ cookiecutter.__folder_name }}/src/{{ cookiecutter.__package_namespace }}/{{ cookiecutter.__package_name }}/locales/{{ cookiecutter.python_package_name }}.pot create mode 100644 backend_addon/{{ cookiecutter.__folder_name }}/src/{{ cookiecutter.__package_namespace }}/{{ cookiecutter.__package_name }}/profiles.zcml create mode 100644 backend_addon/{{ cookiecutter.__folder_name }}/src/{{ cookiecutter.__package_namespace }}/{{ cookiecutter.__package_name }}/profiles/default/browserlayer.xml create mode 100644 backend_addon/{{ cookiecutter.__folder_name }}/src/{{ cookiecutter.__package_namespace }}/{{ cookiecutter.__package_name }}/profiles/default/catalog.xml create mode 100644 backend_addon/{{ cookiecutter.__folder_name }}/src/{{ cookiecutter.__package_namespace }}/{{ cookiecutter.__package_name }}/profiles/default/controlpanel.xml create mode 100644 backend_addon/{{ cookiecutter.__folder_name }}/src/{{ cookiecutter.__package_namespace }}/{{ cookiecutter.__package_name }}/profiles/default/diff_tool.xml create mode 100644 backend_addon/{{ cookiecutter.__folder_name }}/src/{{ cookiecutter.__package_namespace }}/{{ cookiecutter.__package_name }}/profiles/default/metadata.xml create mode 100644 backend_addon/{{ cookiecutter.__folder_name }}/src/{{ cookiecutter.__package_namespace }}/{{ cookiecutter.__package_name }}/profiles/default/registry/.gitkeep create mode 100644 backend_addon/{{ cookiecutter.__folder_name }}/src/{{ cookiecutter.__package_namespace }}/{{ cookiecutter.__package_name }}/profiles/default/repositorytool.xml create mode 100644 backend_addon/{{ cookiecutter.__folder_name }}/src/{{ cookiecutter.__package_namespace }}/{{ cookiecutter.__package_name }}/profiles/default/rolemap.xml create mode 100644 backend_addon/{{ cookiecutter.__folder_name }}/src/{{ cookiecutter.__package_namespace }}/{{ cookiecutter.__package_name }}/profiles/default/theme.xml create mode 100644 backend_addon/{{ cookiecutter.__folder_name }}/src/{{ cookiecutter.__package_namespace }}/{{ cookiecutter.__package_name }}/profiles/default/types.xml create mode 100644 backend_addon/{{ cookiecutter.__folder_name }}/src/{{ cookiecutter.__package_namespace }}/{{ cookiecutter.__package_name }}/profiles/default/types/.gitkeep create mode 100644 backend_addon/{{ cookiecutter.__folder_name }}/src/{{ cookiecutter.__package_namespace }}/{{ cookiecutter.__package_name }}/profiles/uninstall/browserlayer.xml create mode 100644 backend_addon/{{ cookiecutter.__folder_name }}/src/{{ cookiecutter.__package_namespace }}/{{ cookiecutter.__package_name }}/serializers/__init__.py create mode 100644 backend_addon/{{ cookiecutter.__folder_name }}/src/{{ cookiecutter.__package_namespace }}/{{ cookiecutter.__package_name }}/serializers/configure.zcml create mode 100644 backend_addon/{{ cookiecutter.__folder_name }}/src/{{ cookiecutter.__package_namespace }}/{{ cookiecutter.__package_name }}/serializers/summary.py create mode 100644 backend_addon/{{ cookiecutter.__folder_name }}/src/{{ cookiecutter.__package_namespace }}/{{ cookiecutter.__package_name }}/setuphandlers/__init__.py create mode 100644 backend_addon/{{ cookiecutter.__folder_name }}/src/{{ cookiecutter.__package_namespace }}/{{ cookiecutter.__package_name }}/testing.py create mode 100644 backend_addon/{{ cookiecutter.__folder_name }}/src/{{ cookiecutter.__package_namespace }}/{{ cookiecutter.__package_name }}/upgrades/__init__.py create mode 100644 backend_addon/{{ cookiecutter.__folder_name }}/src/{{ cookiecutter.__package_namespace }}/{{ cookiecutter.__package_name }}/upgrades/configure.zcml create mode 100644 backend_addon/{{ cookiecutter.__folder_name }}/src/{{ cookiecutter.__package_namespace }}/{{ cookiecutter.__package_name }}/vocabularies/__init__.py create mode 100644 backend_addon/{{ cookiecutter.__folder_name }}/src/{{ cookiecutter.__package_namespace }}/{{ cookiecutter.__package_name }}/vocabularies/configure.zcml create mode 100644 backend_addon/{{ cookiecutter.__folder_name }}/tests/conftest.py create mode 100644 backend_addon/{{ cookiecutter.__folder_name }}/tests/setup/test_setup_install.py create mode 100644 backend_addon/{{ cookiecutter.__folder_name }}/tests/setup/test_setup_uninstall.py create mode 100644 backend_addon/{{ cookiecutter.__folder_name }}/tox.ini create mode 100644 cookiecutter.json create mode 100644 frontend_addon/.gitignore create mode 100644 frontend_addon/Makefile create mode 100644 frontend_addon/README.md create mode 100644 frontend_addon/cookiecutter.json create mode 100644 frontend_addon/hooks/post_gen_project.py create mode 100644 frontend_addon/hooks/pre_gen_project.py create mode 100644 frontend_addon/hooks/pre_prompt.py create mode 100644 frontend_addon/tests/__init__.py create mode 100644 frontend_addon/tests/conftest.py create mode 100644 frontend_addon/tests/test_cutter.py create mode 100644 frontend_addon/{{ cookiecutter.__folder_name }}/.eslintrc.js create mode 100644 frontend_addon/{{ cookiecutter.__folder_name }}/.github/workflows/acceptance.yml create mode 100644 frontend_addon/{{ cookiecutter.__folder_name }}/.github/workflows/changelog.yml create mode 100644 frontend_addon/{{ cookiecutter.__folder_name }}/.github/workflows/code.yml create mode 100644 frontend_addon/{{ cookiecutter.__folder_name }}/.github/workflows/i18n.yml create mode 100644 frontend_addon/{{ cookiecutter.__folder_name }}/.github/workflows/storybook.yml create mode 100644 frontend_addon/{{ cookiecutter.__folder_name }}/.github/workflows/unit.yml create mode 100644 frontend_addon/{{ cookiecutter.__folder_name }}/.gitignore create mode 100644 frontend_addon/{{ cookiecutter.__folder_name }}/.npmignore create mode 100644 frontend_addon/{{ cookiecutter.__folder_name }}/.npmrc create mode 100644 frontend_addon/{{ cookiecutter.__folder_name }}/.prettierignore create mode 100644 frontend_addon/{{ cookiecutter.__folder_name }}/.prettierrc create mode 100644 frontend_addon/{{ cookiecutter.__folder_name }}/.storybook/main.js create mode 100644 frontend_addon/{{ cookiecutter.__folder_name }}/.storybook/preview.jsx create mode 100644 frontend_addon/{{ cookiecutter.__folder_name }}/.stylelintrc create mode 100644 frontend_addon/{{ cookiecutter.__folder_name }}/Makefile create mode 100644 frontend_addon/{{ cookiecutter.__folder_name }}/README.md create mode 100644 frontend_addon/{{ cookiecutter.__folder_name }}/cypress.config.js create mode 100644 frontend_addon/{{ cookiecutter.__folder_name }}/cypress/.gitkeep create mode 100644 frontend_addon/{{ cookiecutter.__folder_name }}/cypress/support/commands.js create mode 100644 frontend_addon/{{ cookiecutter.__folder_name }}/cypress/support/e2e.js create mode 100644 frontend_addon/{{ cookiecutter.__folder_name }}/cypress/tests/.gitkeep create mode 100644 frontend_addon/{{ cookiecutter.__folder_name }}/cypress/tests/example.cy.js create mode 100644 frontend_addon/{{ cookiecutter.__folder_name }}/jest-addon.config.js create mode 100644 frontend_addon/{{ cookiecutter.__folder_name }}/mrs.developer.json create mode 100644 frontend_addon/{{ cookiecutter.__folder_name }}/package.json create mode 100644 frontend_addon/{{ cookiecutter.__folder_name }}/packages/{{ cookiecutter.frontend_addon_name }}/.gitignore create mode 100644 frontend_addon/{{ cookiecutter.__folder_name }}/packages/{{ cookiecutter.frontend_addon_name }}/.release-it.json create mode 100644 frontend_addon/{{ cookiecutter.__folder_name }}/packages/{{ cookiecutter.frontend_addon_name }}/CHANGELOG.md create mode 100644 frontend_addon/{{ cookiecutter.__folder_name }}/packages/{{ cookiecutter.frontend_addon_name }}/babel.config.js create mode 100644 frontend_addon/{{ cookiecutter.__folder_name }}/packages/{{ cookiecutter.frontend_addon_name }}/locales/de/LC_MESSAGES/volto.po create mode 100644 frontend_addon/{{ cookiecutter.__folder_name }}/packages/{{ cookiecutter.frontend_addon_name }}/locales/en/LC_MESSAGES/volto.po create mode 100644 frontend_addon/{{ cookiecutter.__folder_name }}/packages/{{ cookiecutter.frontend_addon_name }}/locales/es/LC_MESSAGES/volto.po create mode 100644 frontend_addon/{{ cookiecutter.__folder_name }}/packages/{{ cookiecutter.frontend_addon_name }}/locales/pt_BR/LC_MESSAGES/volto.po create mode 100644 frontend_addon/{{ cookiecutter.__folder_name }}/packages/{{ cookiecutter.frontend_addon_name }}/locales/volto.pot create mode 100644 frontend_addon/{{ cookiecutter.__folder_name }}/packages/{{ cookiecutter.frontend_addon_name }}/news/.gitkeep create mode 100644 frontend_addon/{{ cookiecutter.__folder_name }}/packages/{{ cookiecutter.frontend_addon_name }}/package.json create mode 100644 frontend_addon/{{ cookiecutter.__folder_name }}/packages/{{ cookiecutter.frontend_addon_name }}/src/components/.gitkeep create mode 100644 frontend_addon/{{ cookiecutter.__folder_name }}/packages/{{ cookiecutter.frontend_addon_name }}/src/index.js create mode 100644 frontend_addon/{{ cookiecutter.__folder_name }}/packages/{{ cookiecutter.frontend_addon_name }}/towncrier.toml create mode 100644 frontend_addon/{{ cookiecutter.__folder_name }}/packages/{{ cookiecutter.frontend_addon_name }}/tsconfig.json create mode 100644 frontend_addon/{{ cookiecutter.__folder_name }}/pnpm-workspace.yaml create mode 100644 frontend_addon/{{ cookiecutter.__folder_name }}/volto.config.js create mode 100644 hooks/pre_prompt.py create mode 100644 pyproject.toml create mode 100644 requirements.txt diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..63aa5e6 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,36 @@ +# EditorConfig Configurtaion file, for more details see: +# https://EditorConfig.org +# EditorConfig is a convention description, that could be interpreted +# by multiple editors to enforce common coding conventions for specific +# file types + +# top-most EditorConfig file: +# Will ignore other EditorConfig files in Home directory or upper tree level. +root = true + + +[*] # For All Files +# Unix-style newlines with a newline ending every file +end_of_line = lf +insert_final_newline = true +trim_trailing_whitespace = true +# Set default charset +charset = utf-8 +# Indent style default +indent_style = space +# Max Line Length - a hard line wrap, should be disabled +max_line_length = off + +[*.{py,cfg,ini}] +# 4 space indentation +indent_size = 4 + +[*.{html,dtml,pt,zpt,xml,zcml,js,json,less,css,yml,yaml}] +# 2 space indentation +indent_size = 2 + +[{Makefile,.gitmodules}] +# Tab indentation (no size specified, but view as 4 spaces) +indent_style = tab +indent_size = unset +tab_width = unset diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS new file mode 100644 index 0000000..dc84d78 --- /dev/null +++ b/.github/CODEOWNERS @@ -0,0 +1,2 @@ +/backend_addon/ @ericof +/frontend_addon/ @sneridagh diff --git a/.github/workflows/backend_addon.yml b/.github/workflows/backend_addon.yml new file mode 100644 index 0000000..7131dde --- /dev/null +++ b/.github/workflows/backend_addon.yml @@ -0,0 +1,44 @@ +name: Plone Backend Add-on CI +on: + push: + paths: + - "backend_addon/**" + - ".github/workflows/backend_addon.yml" + workflow_dispatch: + +jobs: + + generation: + + runs-on: ubuntu-latest + + strategy: + fail-fast: false + matrix: + python-version: + - "3.10" + - "3.11" + - "3.12" + + steps: + # git checkout + - name: Checkout codebase + uses: actions/checkout@v4 + + # python setup + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v5 + with: + python-version: ${{ matrix.python-version }} + cache: 'pip' + + # python install + - name: Install dependencies + run: | + pip install -r requirements.txt + + # Test + - name: Run tests + run: | + cd backend_addon + python -m pytest tests diff --git a/.github/workflows/frontend_addon.yml b/.github/workflows/frontend_addon.yml new file mode 100644 index 0000000..287e51f --- /dev/null +++ b/.github/workflows/frontend_addon.yml @@ -0,0 +1,168 @@ +name: Plone Frontend Add-on CI +on: + push: + paths: + - "frontend_addon/**" + - ".github/workflows/frontend_addon.yml" + workflow_dispatch: + +env: + NODE_VERSION: 20.x + PYTHON_VERSION: "3.10" + +jobs: + + generation: + + runs-on: ubuntu-latest + + strategy: + fail-fast: false + matrix: + python-version: + - "3.10" + - "3.11" + - "3.12" + + steps: + # git checkout + - name: Checkout codebase + uses: actions/checkout@v4 + + # python setup + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v5 + with: + python-version: ${{ matrix.python-version }} + cache: 'pip' + + # python install + - name: Install dependencies + run: | + pip install -r requirements.txt + + # Test + - name: Run tests + run: | + cd frontend_addon + python -m pytest tests + + functional: + runs-on: ubuntu-latest + + steps: + - name: Checkout codebase + uses: actions/checkout@v4 + + - name: Set up Python ${{ env.PYTHON_VERSION }} + uses: actions/setup-python@v5 + with: + python-version: '${{ env.PYTHON_VERSION }}' + cache: 'pip' + + - name: Install dependencies + run: | + pip install -r requirements.txt + + - name: Generate + working-directory: frontend_addon + run: | + make generate + + - name: Use Node.js ${{ env.NODE_VERSION }} + uses: actions/setup-node@v4 + with: + node-version: ${{ env.NODE_VERSION }} + + - uses: pnpm/action-setup@v3 + name: Install pnpm + with: + version: 8 + # We don't want to install until later, + # when the cache and Cypress are in place + run_install: false + + - name: Get pnpm store directory + shell: bash + run: | + echo "STORE_PATH=$(pnpm store path --silent)" >> $GITHUB_ENV + + - uses: actions/cache@v4 + name: Setup pnpm cache + with: + path: ${{ env.STORE_PATH }} + key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }} + restore-keys: | + ${{ runner.os }}-pnpm-store- + + - name: Cache Cypress Binary + id: cache-cypress-binary + uses: actions/cache@v4 + with: + path: ~/.cache/Cypress + key: binary-${{ env.NODE_VERSION }}-${{ hashFiles('pnpm-lock.yaml') }} + + - name: Install generated package + working-directory: frontend_addon/volto-addon + run: | + make install + + - name: Run unit tests + working-directory: frontend_addon/volto-addon + run: | + make test-ci + + - name: Run i18n tests + working-directory: frontend_addon/volto-addon + run: | + make i18n + + - name: Run linting + working-directory: frontend_addon/volto-addon + run: | + make lint + + - name: Run formatting + working-directory: frontend_addon/volto-addon + run: | + make format + + - name: Run Storybook + working-directory: frontend_addon/volto-addon + run: | + make storybook-build + + - name: Start Servers + uses: JarvusInnovations/background-action@v1 + with: + working-directory: frontend_addon/volto-addon + run: | + make start-test-acceptance-server-ci & + make start-test-acceptance-frontend & + # your step-level and job-level environment variables are available to your commands as-is + # npm install will count towards the wait-for timeout + # whenever possible, move unrelated scripts to a different step + # to background multiple processes: add & to the end of the command + + wait-on: | + http-get://localhost:55001/plone + http://localhost:3000 + # IMPORTANT: to use environment variables in wait-on, you must use this form: ${{ env.VAR }} + # See wait-on section below for all resource types and prefixes + + tail: true # true = stderr,stdout + # This will allow you to monitor the progress live + + log-output-resume: stderr + # Eliminates previosuly output stderr log entries from post-run output + + wait-for: 10m + + log-output: stderr,stdout # same as true + + log-output-if: failure + + - name: Run acceptance tests + working-directory: frontend_addon/volto-addon + run: | + make test-acceptance-headless diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..8a7b479 --- /dev/null +++ b/.gitignore @@ -0,0 +1,11 @@ +bin/ +include/ +lib/ +lib64/ +pip-selfcheck.json +.vscode +collective.plonedistribution +.pytest_cache +.reports/*.csv +.reports/*.json +*/__pycache__/ diff --git a/.reports/.gitkeep b/.reports/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/.scripts/report_context.py b/.scripts/report_context.py new file mode 100644 index 0000000..8a5c2df --- /dev/null +++ b/.scripts/report_context.py @@ -0,0 +1,33 @@ +import json +from datetime import date +from pathlib import Path + +from git import Repo + +cwd = Path().cwd() +reports = cwd / ".reports" + +repo = Repo(cwd) +last_commit = repo.head.commit + +report_filename = f"{date.today()}-{last_commit.hexsha[:7]}-report.csv" + +folders = ["backend_addon", "frontend_addon"] +ignore = ["__prompts__", "_copy_without_render", "_extensions"] +data = [] + +for folder in folders: + file_ = cwd / folder / "cookiecutter.json" + questions = json.loads(file_.read_text()) + items = [ + (folder, key, value) for key, value in questions.items() if key not in ignore + ] + data.extend(items) + +report_path = reports / report_filename +with open(report_path, "w") as fout: + fout.write("template\tkey\tvalue\n") + for addon, key, value in data: + fout.write(f'"{addon}"\t"{key}"\t"{value}"\n') + +print(f"Report available at {report_path}") diff --git a/.scripts/report_keys_usage.py b/.scripts/report_keys_usage.py new file mode 100644 index 0000000..682470e --- /dev/null +++ b/.scripts/report_keys_usage.py @@ -0,0 +1,79 @@ +import json +import re +from collections import defaultdict +from datetime import date +from pathlib import Path + +from binaryornot.check import is_binary +from git import Repo + +PATTERN = "{{ ?(cookiecutter)[.]([a-zA-Z0-9-_]*)" +RE_OBJ = re.compile(PATTERN) + +cwd = Path().cwd() +reports = cwd / ".reports" + +repo = Repo(cwd) +last_commit = repo.head.commit + +report_filename = f"{date.today()}-{last_commit.hexsha[:7]}-usage.json" + +folders = ["backend_addon", "frontend_addon"] +ignore = [ + "__prompts__", +] + + +def as_sorted_list(value: set) -> list: + """Convert a set to a list and sort it.""" + value = list(value) + return sorted(value) + + +def find_and_add_keys(used_keys: set, data: str) -> set: + matches = RE_OBJ.findall(data) or [] + for match in matches: + used_keys.add(match[1]) + return used_keys + + +def valid_key(key: str) -> bool: + """Check if we will check for this key.""" + return all( + [ + key not in ignore, + key.startswith("__") or not key.startswith("_"), + ] + ) + + +keys = defaultdict(dict) +for folder in folders: + base_path = cwd / folder + file_ = base_path / "cookiecutter.json" + template_folder = base_path / "{{ cookiecutter.__folder_name }}" + raw_context = file_.read_text() + used_keys = find_and_add_keys({"__folder_name"}, raw_context) + questions = json.loads(raw_context) + items = {key for key in questions.keys() if valid_key(key)} + all_files = template_folder.glob("**/*") + # Already add __folder_name + for filepath in all_files: + data = filepath.name + is_file = filepath.is_file() + if is_file and is_binary(f"{filepath}"): + continue + if is_file: + data = f"{data} {filepath.read_text()}" + used_keys = find_and_add_keys(used_keys, data) + keys[folder]["all"] = as_sorted_list(items) + keys[folder]["used"] = as_sorted_list(used_keys & items) + keys[folder]["not_used"] = as_sorted_list(items.difference(used_keys)) + keys[folder]["missing"] = as_sorted_list(used_keys.difference(items)) + + +report_path = reports / report_filename +with open(report_path, "w") as fout: + json.dump(keys, fout, indent=2) + +print(f"Report available at {report_path}") diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..20e1dea --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +The MIT License + +Copyright 2024 Plone Foundation . + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..a6becd0 --- /dev/null +++ b/Makefile @@ -0,0 +1,53 @@ +SHELL := /bin/bash +CURRENT_DIR:=$(shell dirname $(realpath $(lastword $(MAKEFILE_LIST)))) + + +# We like colors +# From: https://coderwall.com/p/izxssa/colored-makefile-for-golang-projects +RED=`tput setaf 1` +GREEN=`tput setaf 2` +RESET=`tput sgr0` +YELLOW=`tput setaf 3` + +.PHONY: all +all: bin/cookieplone + +# Add the following 'help' target to your Makefile +# And add help text after each target name starting with '\#\#' +.PHONY: help +help: ## This help message + @grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | sort | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}' + +.PHONY: clean +clean: ## Clean + rm -rf bin include lib lib64 pyvenv.cfg .Python + +bin/cookieplone: ## Create virtualenv and install dependencies + @echo "$(GREEN)==> Setup Virtual Env$(RESET)" + python3 -m venv . + bin/pip install pip --upgrade + bin/pip install -r requirements.txt --upgrade + +.PHONY: format +format: bin/cookieplone ## Format code + @echo "$(GREEN)==> Formatting codebase $(RESET)" + bin/black hooks .scripts + bin/isort hooks .scripts + $(MAKE) -C "./backend_addon/" format + $(MAKE) -C "./frontend_addon/" format + +.PHONY: test +test: bin/cookieplone ## Test all cookiecutters + @echo "$(GREEN)==> Test all cookiecutters$(RESET)" + $(MAKE) -C "./backend_addon/" test + $(MAKE) -C "./frontend_addon/" test + +.PHONY: report-context +report-context: bin/cookieplone ## Generate a report of all context options + @echo "$(GREEN)==> Generate a report of all context options$(RESET)" + bin/python .scripts/report_context.py + +.PHONY: report-keys-usage +report-keys-usage: bin/cookieplone ## Generate a report of usage of context keys + @echo "$(GREEN)==> Generate a report of usage of context keys$(RESET)" + bin/python .scripts/report_keys_usage.py diff --git a/README.md b/README.md new file mode 100644 index 0000000..0dd3640 --- /dev/null +++ b/README.md @@ -0,0 +1,49 @@ +![GitHub](https://img.shields.io/github/license/plone/cookiecutter-plone) +[![Black code style](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/ambv/black) + +# Cookieplone Templates + +Powered by [Cookieplone](https://github.com/plone/cookieplone), this is a collection of templates intended to be used by Plone integrators. + +## Getting Started 🏁 + +### Prerequisites + +- **pipx**: A handy tool for installing and running Python applications. + +### Installation Guide πŸ› οΈ + +**pipx** + +```shell +pip install pipx +``` + +### Choose a template πŸŽ‰ + +First run the command: + +```shell +pipx run cookieplone +``` + +And choose a template: +``` + [1/1] Select a template + 1 - Backend Add-on for Plone + 2 - Frontend Add-on +``` + +| Template | Description | | +| --------- | --------- | --------- | +| `Backend Add-on for Plone` | Creates a new Python package to be used with Plone | [Read More](./backend_addon/README.md) | +| `Frontend Add-on for Plone` | Creates a new Node package to be used with Volto | [Read More](./frontend_addon/README.md) | + + +## License πŸ“œ + +This project is licensed under the [MIT License](/LICENSE). + +## Let's Get Building! πŸš€ + +Happy coding! diff --git a/backend_addon/Makefile b/backend_addon/Makefile new file mode 100644 index 0000000..f1124d7 --- /dev/null +++ b/backend_addon/Makefile @@ -0,0 +1,44 @@ +SHELL := /bin/bash +CURRENT_DIR:=$(shell dirname $(realpath $(lastword $(MAKEFILE_LIST)))) + + +# We like colors +# From: https://coderwall.com/p/izxssa/colored-makefile-for-golang-projects +RED=`tput setaf 1` +GREEN=`tput setaf 2` +RESET=`tput sgr0` +YELLOW=`tput setaf 3` + +.PHONY: all +all: build + + +# Add the following 'help' target to your Makefile +# And add help text after each target name starting with '\#\#' +.PHONY: help +help: ## This help message + @grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | sort | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}' + +.PHONY: clean +clean: ## Clean + rm -rf collective.addon + +../bin/cookieplone: ## cookieplone installation + $(MAKE) -C ".." bin/cookieplone + +.PHONY: format +format: ../bin/cookieplone ## Format code + @echo "$(GREEN)==> Formatting codebase $(RESET)" + ../bin/black hooks tests + ../bin/isort hooks tests + +.PHONY: generate +generate: ../bin/cookieplone ## Create a sample package + @echo "$(GREEN)==> Creating new test package$(RESET)" + rm -rf collective.addon + ../bin/cookieplone . --no-input + +.PHONY: test +test: ../bin/cookieplone ## Create a sample package and tests it + @echo "$(GREEN)==> Creating new test package$(RESET)" + ../bin/python -m pytest tests diff --git a/backend_addon/README.md b/backend_addon/README.md new file mode 100644 index 0000000..61dced2 --- /dev/null +++ b/backend_addon/README.md @@ -0,0 +1,65 @@ +[![Cookieplone Backend Add-on CI](https://github.com/plone/cookieplone-templates/actions/workflows/backend_addon.yml/badge.svg)](https://github.com/plone/cookieplone-templates/actions/workflows/backend_addon.yml) +[![Built with Cookiecutter](https://img.shields.io/badge/built%20with-Cookiecutter-ff69b4.svg?logo=cookiecutter)](https://github.com/plone/cookieplone-templates/) +![GitHub](https://img.shields.io/github/license/plone/cookiecutter-plone) +[![Black code style](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/ambv/black) + +# Cookieplone Backend Add-on + +Powered by [cookieplone](https://github.com/plone/cookieplone) and [Cookiecutter](https://github.com/cookiecutter/cookiecutter), [Cookieplone Backend Add-on](https://github.com/plone/cookieplone-templates/backend_addon) is intended to be used by Plone developers to create new addon packages. + +## Getting Started 🏁 + +### Prerequisites + +- **pipx**: A handy tool for installing and running Python applications. + +### Installation Guide πŸ› οΈ + +1. **pipx** + +```shell +pip install pipx +``` +### Generate Your Plone Addon πŸŽ‰ + +```shell +pipx run cookieplone backend_addon +``` + + +## Project Generation Options + +These are all the template options that will be prompted by the [Cookiecutter CLI](https://github.com/cookiecutter/cookiecutter) before generating your project. + +| Option | Description | Example | +| --------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------- | +| `title` | Your addon's human-readable name, capitals and spaces allowed. | **Plone Blog** | +| `description` | Describes your addon and gets used in places like ``README.md`` and such. | **Create awesome blogs with Plone.** | +| `author` | This is you! The value goes into places like ``LICENSE``, ``setup.py`` and such. | **Our Company** | +| `email` | The email address you want to identify yourself in the project. | **email@example.com** | +| `github_organization` | Used for GitHub and Docker repositories. | **collective** | +| `python_package_name` | Name of the Python package used to configure your project. It needs to be Python-importable, so no dashes, spaces or special characters are allowed. | **collective.blog** | + + +## Code Quality Assurance 🧐 + +Your package comes equipped with linters to ensure code quality. Run the following to automatically format your code: + +```shell +make format +``` + +## Internationalization 🌐 + +Generate translation files with ease: + +```shell +make i18n +``` +## License πŸ“œ + +This project is licensed under the [MIT License](/LICENSE). + +## Let's Get Building! πŸš€ + +Happy coding! diff --git a/backend_addon/cookiecutter.json b/backend_addon/cookiecutter.json new file mode 100644 index 0000000..541eab3 --- /dev/null +++ b/backend_addon/cookiecutter.json @@ -0,0 +1,41 @@ +{ + "title": "Addon", + "description": "A new addon for Plone", + "author": "Plone Community", + "email": "collective@plone.org", + "github_organization": "collective", + "python_package_name": "{{ cookiecutter.github_organization|lower }}.{{ cookiecutter.title|replace(' ', '')|replace('-', '_')|replace('.', '')|lower }}", + "feature_headless": [ + "1", + "0" + ], + "__package_name": "{{ cookiecutter.python_package_name | package_name }}", + "__package_namespace": "{{ cookiecutter.python_package_name | package_namespace }}", + "__folder_name": "{{ cookiecutter.python_package_name }}", + "__python_package_name_upper": "{{ cookiecutter.python_package_name | pascal_case }}", + "__version_package": "1.0.0a0", + "__profile_version": "1000", + "__gha_enable": true, + "__generator_date_short": "{% now 'utc', '%Y-%m-%d' %}", + "__generator_date_long": "{% now 'utc', '%Y-%m-%d %H:%M:%S' %}", + "__generator_signature": "This was generated by [cookiecutter-plone](https://github.com/plone/cookieplone-templates/backend_addon) on {{ cookiecutter.__generator_date_long }}", + "__prompts__": { + "title": "Addon Title", + "description": "A short description of your addon", + "github_organization": "GitHub Username or Organization", + "python_package_name": "Python package name", + "author": "Author", + "email": "Author E-mail", + "feature_headless": { + "__prompt__": "Support headless Plone?", + "1": "Yes", + "0": "No" + } + }, + "_copy_without_render": [], + "_extensions": [ + "cookieplone.filters.pascal_case", + "cookieplone.filters.package_name", + "cookieplone.filters.package_namespace" + ] +} diff --git a/backend_addon/hooks/post_gen_project.py b/backend_addon/hooks/post_gen_project.py new file mode 100644 index 0000000..2f44baa --- /dev/null +++ b/backend_addon/hooks/post_gen_project.py @@ -0,0 +1,73 @@ +"""Post generation hook.""" + +import subprocess +import sys +from pathlib import Path + +from cookieplone.utils import console, files + +# PATH OF CONTENT TO BE REMOVED +TO_REMOVE_PATHS = { + "feature_headless": [ + "serializers", + ] +} + + +def run_cmd(command: str, shell: bool, cwd: str) -> bool: + proc = subprocess.run(command, shell=shell, cwd=cwd, capture_output=True) + if proc.returncode: + # Write errors to the main process stderr + console.error(f"Error while running {command}") + return False if proc.returncode else True + + +def remove_files(category: str): + to_remove = TO_REMOVE_PATHS.get(category, []) + package_namespace = "{{ cookiecutter.__package_namespace }}" + package_name = "{{ cookiecutter.__package_name }}" + base_path = Path("src") / package_namespace / package_name + # Remove all files + files.remove_files(base_path, to_remove) + + +def initialize_git(): + """Apply black and isort to the generated codebase.""" + console.info("Git repository") + steps = [ + ["Initialize", ["git", "init", "."], False, "."], + ["Add files", ["git", "add", "."], False, "."], + ] + for step in steps: + msg, command, shell, cwd = step + console.info(f" - {msg}") + result = run_cmd(command, shell=shell, cwd=cwd) + if not result: + sys.exit(1) + + +def main(): + """Final fixes.""" + keep_headless = int("{{ cookiecutter.feature_headless }}") + if not keep_headless: + remove_files("feature_headless") + initialize_git() + msg = """ + [bold blue]{{ cookiecutter.title }}[/bold blue] + + Now, enter the repositorym run the code formatter with: + + make format + + start coding, and push to your organization. + + Sorry for the convenience, + The Plone Community. + """ + console.panel( + title="New addon was generated", subtitle="", msg=msg, url="https://plone.org/" + ) + + +if __name__ == "__main__": + main() diff --git a/backend_addon/hooks/pre_prompt.py b/backend_addon/hooks/pre_prompt.py new file mode 100644 index 0000000..e78c17e --- /dev/null +++ b/backend_addon/hooks/pre_prompt.py @@ -0,0 +1,50 @@ +"""Pre Prompt hook.""" + +import sys + +try: + from cookieplone import data + from cookieplone.utils import commands, console, sanity + + HAS_COOKIEPLONE = True +except ModuleNotFoundError: + HAS_COOKIEPLONE = False + + +SUPPORTED_PYTHON_VERSIONS = [ + "3.8", + "3.9", + "3.10", + "3.11", + "3.12", +] + + +def sanity_check() -> bool: + """Run sanity checks on the system.""" + checks = [ + data.SanityCheck( + "Python", + commands.check_python_version, + [SUPPORTED_PYTHON_VERSIONS], + "error", + ), + data.SanityCheck("git", commands.check_command_is_available, ["git"], "error"), + ] + result = sanity.run_sanity_checks(checks) + return result.status + + +def main(): + """Validate context.""" + if not HAS_COOKIEPLONE: + print("This template should be run with cookieplone") + sys.exit(1) + + console.panel(title="Plone Addon", msg="Creating a new Plone Addon") + if not sanity_check(): + sys.exit(1) + + +if __name__ == "__main__": + main() diff --git a/backend_addon/tests/__init__.py b/backend_addon/tests/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/backend_addon/tests/conftest.py b/backend_addon/tests/conftest.py new file mode 100644 index 0000000..bc22ff8 --- /dev/null +++ b/backend_addon/tests/conftest.py @@ -0,0 +1,122 @@ +"""Pytest configuration.""" + +import re +from copy import deepcopy +from pathlib import Path +from typing import List + +import pytest + +ROOT_FILES = [ + ".editorconfig", + ".github/workflows/meta.yml", + ".gitignore", + ".meta.toml", + ".pre-commit-config.yaml", + "CHANGES.md", + "constraints.txt", + "CONTRIBUTORS.md", + "instance.yaml", + "LICENSE.GPL", + "LICENSE.md", + "Makefile", + "MANIFEST.in", + "mx.ini", + "pyproject.toml", + "README.md", + "requirements.txt", + "setup.py", + "tox.ini", +] + + +PKG_SRC_FILES = [ + "__init__.py", + "configure.zcml", + "content/__init__.py", + "controlpanel/__init__.py", + "controlpanel/configure.zcml", + "dependencies.zcml", + "indexers/__init__.py", + "indexers/configure.zcml", + "profiles/default/browserlayer.xml", + "profiles/default/catalog.xml", + "profiles/default/controlpanel.xml", + "profiles/default/diff_tool.xml", + "profiles/default/metadata.xml", + "profiles/default/repositorytool.xml", + "profiles/default/rolemap.xml", + "profiles/default/theme.xml", + "profiles/default/types.xml", + "profiles/default/types/.gitkeep", + "profiles/uninstall/browserlayer.xml", + "setuphandlers/__init__.py", + "testing.py", + "upgrades/__init__.py", + "upgrades/configure.zcml", + "vocabularies/__init__.py", + "vocabularies/configure.zcml", +] + + +PKG_SRC_FEATURE_HEADLESS = [ + "serializers/__init__.py", + "serializers/configure.zcml", +] + + +@pytest.fixture(scope="session") +def variable_pattern(): + return re.compile("{{( ?cookiecutter)[.](.*?)}}") + + +@pytest.fixture(scope="session") +def context() -> dict: + """Cookiecutter context.""" + return { + "title": "Addon", + "description": "A Tech blog.", + "github_organization": "collective", + "python_package_name": "collective.addon", + "author": "Plone Collective", + "email": "collective@plone.org", + "feature_headless": "1", + } + + +@pytest.fixture(scope="session") +def context_no_headless(context) -> dict: + """Cookiecutter context without headless feature enabled.""" + new_context = deepcopy(context) + new_context["python_package_name"] = "collective.addonredux" + new_context["feature_headless"] = "0" + return new_context + + +@pytest.fixture(scope="session") +def bad_context() -> dict: + """Cookiecutter context with invalid data.""" + return { + "title": "Addon", + "description": "A Tech blog.", + "github_organization": "collective", + "python_package_name": "collective_addon", + "author": "Plone Collective", + "email": "collective@plone.org", + "feature_headless": "1", + } + + +@pytest.fixture +def build_files_list(): + def func(root_dir: Path) -> List[Path]: + """Build a list containing absolute paths to the generated files.""" + return [path for path in Path(root_dir).glob("*") if path.is_file()] + + return func + + +@pytest.fixture(scope="session") +def cutter_result(cookies_session, context): + """Cookiecutter result.""" + return cookies_session.bake(extra_context=context) diff --git a/backend_addon/tests/test_cutter.py b/backend_addon/tests/test_cutter.py new file mode 100644 index 0000000..b9ba1be --- /dev/null +++ b/backend_addon/tests/test_cutter.py @@ -0,0 +1,58 @@ +"""Test cookiecutter generation with all features enabled.""" + +import pytest + +from .conftest import PKG_SRC_FEATURE_HEADLESS, PKG_SRC_FILES, ROOT_FILES + + +def test_creation(cookies, context: dict): + """Generated project should match provided value.""" + result = cookies.bake(extra_context=context) + assert result.exception is None + assert result.exit_code == 0 + assert result.project_path.name == context["python_package_name"] + assert result.project_path.is_dir() + + +def test_variable_substitution(build_files_list, variable_pattern, cutter_result): + """Check if no file was unprocessed.""" + paths = build_files_list(cutter_result.project_path) + for path in paths: + for line in open(path): + match = variable_pattern.search(line) + msg = f"cookiecutter variable not replaced in {path}" + assert match is None, msg + + +@pytest.mark.parametrize( + "file_path", + ROOT_FILES, +) +def test_root_files_generated(cutter_result, file_path): + """Check if root files were generated.""" + path = cutter_result.project_path / file_path + assert path.exists() + assert path.is_file() + + +@pytest.mark.parametrize("file_path", PKG_SRC_FILES) +def test_pkg_src_files_generated(cutter_result, file_path: str): + """Check if distribution files were generated.""" + package_namespace = cutter_result.context["__package_namespace"] + package_name = cutter_result.context["__package_name"] + file_path = file_path.format(package_name=package_name) + src_path = cutter_result.project_path / "src" / package_namespace / package_name + path = src_path / file_path + assert path.exists() + assert path.is_file() + + +@pytest.mark.parametrize("file_path", PKG_SRC_FEATURE_HEADLESS) +def test_pkg_src_feature_files_generated(cutter_result, file_path: str): + """Check if feature-specific files were generated.""" + package_namespace = cutter_result.context["__package_namespace"] + package_name = cutter_result.context["__package_name"] + src_path = cutter_result.project_path / "src" / package_namespace / package_name + path = src_path / file_path + assert path.exists() + assert path.is_file() diff --git a/backend_addon/tests/test_cutter_no_headless.py b/backend_addon/tests/test_cutter_no_headless.py new file mode 100644 index 0000000..50efa96 --- /dev/null +++ b/backend_addon/tests/test_cutter_no_headless.py @@ -0,0 +1,63 @@ +"""Test cookiecutter generation with features enabled.""" + +import pytest + +from .conftest import PKG_SRC_FEATURE_HEADLESS, PKG_SRC_FILES, ROOT_FILES + + +@pytest.fixture(scope="session") +def cutter_result(cookies_session, context_no_headless): + """Cookiecutter result.""" + return cookies_session.bake(extra_context=context_no_headless) + + +def test_creation(cookies, context_no_headless: dict): + """Generated project should match provided value.""" + result = cookies.bake(extra_context=context_no_headless) + assert result.exception is None + assert result.exit_code == 0 + assert result.project_path.name == context_no_headless["python_package_name"] + assert result.project_path.is_dir() + + +def test_variable_substitution(build_files_list, variable_pattern, cutter_result): + """Check if no file was unprocessed.""" + paths = build_files_list(cutter_result.project_path) + for path in paths: + for line in open(path): + match = variable_pattern.search(line) + msg = f"cookiecutter variable not replaced in {path}" + assert match is None, msg + + +@pytest.mark.parametrize( + "file_path", + ROOT_FILES, +) +def test_root_files_generated(cutter_result, file_path): + """Check if root files were generated.""" + path = cutter_result.project_path / file_path + assert path.exists() + assert path.is_file() + + +@pytest.mark.parametrize("file_path", PKG_SRC_FILES) +def test_pkg_src_files_generated(cutter_result, file_path: str): + """Check if distribution files were generated.""" + package_namespace = cutter_result.context["__package_namespace"] + package_name = cutter_result.context["__package_name"] + file_path = file_path.format(package_name=package_name) + src_path = cutter_result.project_path / "src" / package_namespace / package_name + path = src_path / file_path + assert path.exists() + assert path.is_file() + + +@pytest.mark.parametrize("file_path", PKG_SRC_FEATURE_HEADLESS) +def test_pkg_src_headless_files_not_generated(cutter_result, file_path: str): + """Check feature-specific files were not generated.""" + package_namespace = cutter_result.context["__package_namespace"] + package_name = cutter_result.context["__package_name"] + src_path = cutter_result.project_path / "src" / package_namespace / package_name + path = src_path / file_path + assert path.exists() is False diff --git a/backend_addon/{{ cookiecutter.__folder_name }}/.editorconfig b/backend_addon/{{ cookiecutter.__folder_name }}/.editorconfig new file mode 100644 index 0000000..8ae05aa --- /dev/null +++ b/backend_addon/{{ cookiecutter.__folder_name }}/.editorconfig @@ -0,0 +1,54 @@ +# Generated from: +# https://github.com/plone/meta/tree/master/config/default +# See the inline comments on how to expand/tweak this configuration file +# +# EditorConfig Configuration file, for more details see: +# http://EditorConfig.org +# EditorConfig is a convention description, that could be interpreted +# by multiple editors to enforce common coding conventions for specific +# file types + +# top-most EditorConfig file: +# Will ignore other EditorConfig files in Home directory or upper tree level. +root = true + + +[*] # For All Files +# Unix-style newlines with a newline ending every file +end_of_line = lf +insert_final_newline = true +trim_trailing_whitespace = true +# Set default charset +charset = utf-8 +# Indent style default +indent_style = space +# Max Line Length - a hard line wrap, should be disabled +max_line_length = off + +[*.{py,cfg,ini}] +# 4 space indentation +indent_size = 4 + +[*.{yml,zpt,pt,dtml,zcml}] +# 2 space indentation +indent_size = 2 + +[*.{json,jsonl,js,jsx,ts,tsx,css,less,scss,html}] # Frontend development +# 2 space indentation +indent_size = 2 +max_line_length = 80 + +[{Makefile,.gitmodules}] +# Tab indentation (no size specified, but view as 4 spaces) +indent_style = tab +indent_size = unset +tab_width = unset + + +## +# Add extra configuration options in .meta.toml: +# [editorconfig] +# extra_lines = """ +# _your own configuration lines_ +# """ +## diff --git a/backend_addon/{{ cookiecutter.__folder_name }}/.flake8 b/backend_addon/{{ cookiecutter.__folder_name }}/.flake8 new file mode 100644 index 0000000..7ef4f64 --- /dev/null +++ b/backend_addon/{{ cookiecutter.__folder_name }}/.flake8 @@ -0,0 +1,22 @@ +# Generated from: +# https://github.com/plone/meta/tree/master/config/default +# See the inline comments on how to expand/tweak this configuration file +[flake8] +doctests = 1 +ignore = + # black takes care of line length + E501, + # black takes care of where to break lines + W503, + # black takes care of spaces within slicing (list[:]) + E203, + # black takes care of spaces after commas + E231, + +## +# Add extra configuration options in .meta.toml: +# [flake8] +# extra_lines = """ +# _your own configuration lines_ +# """ +## diff --git a/backend_addon/{{ cookiecutter.__folder_name }}/.github/workflows/meta.yml b/backend_addon/{{ cookiecutter.__folder_name }}/.github/workflows/meta.yml new file mode 100644 index 0000000..39a164d --- /dev/null +++ b/backend_addon/{{ cookiecutter.__folder_name }}/.github/workflows/meta.yml @@ -0,0 +1,68 @@ +# Generated from: +# https://github.com/plone/meta/tree/master/config/default +# See the inline comments on how to expand/tweak this configuration file +name: Meta +on: + push: + branches: + - master + - main + pull_request: + branches: + - master + - main + workflow_dispatch: + +## +# To set environment variables for all jobs, add in .meta.toml: +# [github] +# env = """ +# debug: 1 +# image-name: 'org/image' +# image-tag: 'latest' +# """ +## + +jobs: + qa: + uses: plone/meta/.github/workflows/qa.yml@main + test: + uses: plone/meta/.github/workflows/test.yml@main + coverage: + uses: plone/meta/.github/workflows/coverage.yml@main + dependencies: + uses: plone/meta/.github/workflows/dependencies.yml@main + release_ready: + uses: plone/meta/.github/workflows/release_ready.yml@main + circular: + uses: plone/meta/.github/workflows/circular.yml@main + +## +# To modify the list of default jobs being created add in .meta.toml: +# [github] +# jobs = [ +# "qa", +# "test", +# "coverage", +# "dependencies", +# "release_ready", +# "circular", +# ] +## + +## +# To request that some OS level dependencies get installed +# when running tests/coverage jobs, add in .meta.toml: +# [github] +# os_dependencies = "git libxml2 libxslt" +## + + +## +# Specify additional jobs in .meta.toml: +# [github] +# extra_lines = """ +# another: +# uses: org/repo/.github/workflows/file.yml@main +# """ +## diff --git a/backend_addon/{{ cookiecutter.__folder_name }}/.gitignore b/backend_addon/{{ cookiecutter.__folder_name }}/.gitignore new file mode 100644 index 0000000..81594fd --- /dev/null +++ b/backend_addon/{{ cookiecutter.__folder_name }}/.gitignore @@ -0,0 +1,51 @@ +# Generated from: +# https://github.com/plone/meta/tree/master/config/default +# See the inline comments on how to expand/tweak this configuration file +# python related +*.egg-info +*.pyc +*.pyo + +# tools related +build/ +.coverage +coverage.xml +dist/ +docs/_build +__pycache__/ +.tox +.vscode/ +node_modules/ + +# venv / buildout related +bin/ +develop-eggs/ +eggs/ +.eggs/ +etc/ +.installed.cfg +include/ +lib/ +lib64 +.mr.developer.cfg +parts/ +pyvenv.cfg +var/ + +# mxdev +/instance/ +/.make-sentinels/ +/*-mxdev.txt +/reports/ +/sources/ +/venv/ +.installed.txt + + +## +# Add extra configuration options in .meta.toml: +# [gitignore] +# extra_lines = """ +# _your own configuration lines_ +# """ +## diff --git a/backend_addon/{{ cookiecutter.__folder_name }}/.meta.toml b/backend_addon/{{ cookiecutter.__folder_name }}/.meta.toml new file mode 100644 index 0000000..8cb7797 --- /dev/null +++ b/backend_addon/{{ cookiecutter.__folder_name }}/.meta.toml @@ -0,0 +1,19 @@ +# Generated from: +# https://github.com/plone/meta/tree/master/config/default +# See the inline comments on how to expand/tweak this configuration file +[meta] +template = "default" +commit-id = "25d2fa7f" + +[pyproject] +codespell_skip = "*.min.js" +codespell_ignores = "vew" +dependencies_ignores = "['plone.volto', 'zestreleaser.towncrier', 'zest.releaser', 'pytest', 'pytest-cov', 'pytest-plone']" +dependencies_mappings = [ + "Plone = ['Products.CMFPlone', 'Products.CMFCore', 'Products.GenericSetup']", + ] + +[tox] +use_mxdev = true +test_runner = "pytest" +test_path = "/tests" diff --git a/backend_addon/{{ cookiecutter.__folder_name }}/.pre-commit-config.yaml b/backend_addon/{{ cookiecutter.__folder_name }}/.pre-commit-config.yaml new file mode 100644 index 0000000..b6eb043 --- /dev/null +++ b/backend_addon/{{ cookiecutter.__folder_name }}/.pre-commit-config.yaml @@ -0,0 +1,94 @@ +# Generated from: +# https://github.com/plone/meta/tree/master/config/default +# See the inline comments on how to expand/tweak this configuration file +ci: + autofix_prs: false + autoupdate_schedule: monthly + +repos: +- repo: https://github.com/asottile/pyupgrade + rev: v3.14.0 + hooks: + - id: pyupgrade + args: [--py38-plus] +- repo: https://github.com/pycqa/isort + rev: 5.12.0 + hooks: + - id: isort +- repo: https://github.com/psf/black + rev: 23.9.1 + hooks: + - id: black +- repo: https://github.com/collective/zpretty + rev: 3.1.0 + hooks: + - id: zpretty + +## +# Add extra configuration options in .meta.toml: +# [pre_commit] +# zpretty_extra_lines = """ +# _your own configuration lines_ +# """ +## +- repo: https://github.com/PyCQA/flake8 + rev: 6.1.0 + hooks: + - id: flake8 + +## +# Add extra configuration options in .meta.toml: +# [pre_commit] +# flake8_extra_lines = """ +# _your own configuration lines_ +# """ +## +- repo: https://github.com/codespell-project/codespell + rev: v2.2.6 + hooks: + - id: codespell + additional_dependencies: + - tomli + +## +# Add extra configuration options in .meta.toml: +# [pre_commit] +# codespell_extra_lines = """ +# _your own configuration lines_ +# """ +## +- repo: https://github.com/mgedmin/check-manifest + rev: "0.49" + hooks: + - id: check-manifest +- repo: https://github.com/regebro/pyroma + rev: "4.2" + hooks: + - id: pyroma +- repo: https://github.com/mgedmin/check-python-versions + rev: "0.21.3" + hooks: + - id: check-python-versions + args: ['--only', 'setup.py,pyproject.toml'] +- repo: https://github.com/collective/i18ndude + rev: "6.1.0" + hooks: + - id: i18ndude + + +## +# Add extra configuration options in .meta.toml: +# [pre_commit] +# i18ndude_extra_lines = """ +# _your own configuration lines_ +# """ +## + + +## +# Add extra configuration options in .meta.toml: +# [pre_commit] +# extra_lines = """ +# _your own configuration lines_ +# """ +## diff --git a/backend_addon/{{ cookiecutter.__folder_name }}/CHANGES.md b/backend_addon/{{ cookiecutter.__folder_name }}/CHANGES.md new file mode 100644 index 0000000..3020bae --- /dev/null +++ b/backend_addon/{{ cookiecutter.__folder_name }}/CHANGES.md @@ -0,0 +1,10 @@ +# Changelog + + + + diff --git a/backend_addon/{{ cookiecutter.__folder_name }}/CONTRIBUTORS.md b/backend_addon/{{ cookiecutter.__folder_name }}/CONTRIBUTORS.md new file mode 100644 index 0000000..3f84a5f --- /dev/null +++ b/backend_addon/{{ cookiecutter.__folder_name }}/CONTRIBUTORS.md @@ -0,0 +1,3 @@ +# Contributors + +- {{ cookiecutter.github_organization }} [{{ cookiecutter.email }}] diff --git a/backend_addon/{{ cookiecutter.__folder_name }}/LICENSE.GPL b/backend_addon/{{ cookiecutter.__folder_name }}/LICENSE.GPL new file mode 100644 index 0000000..d159169 --- /dev/null +++ b/backend_addon/{{ cookiecutter.__folder_name }}/LICENSE.GPL @@ -0,0 +1,339 @@ + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Lesser General Public License instead.) You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must show them these terms so they know their +rights. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + 1. You may copy and distribute verbatim copies of the Program's +source code as you receive it, in any medium, provided that you +conspicuously and appropriately publish on each copy an appropriate +copyright notice and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. + + 5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new versions +of the General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY +FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES +PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED +OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS +TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, +REPAIR OR CORRECTION. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING +OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED +TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY +YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER +PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) year name of author + Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, the commands you use may +be called something other than `show w' and `show c'; they could even be +mouse-clicks or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the program + `Gnomovision' (which makes passes at compilers) written by James Hacker. + + , 1 April 1989 + Ty Coon, President of Vice + +This General Public License does not permit incorporating your program into +proprietary programs. If your program is a subroutine library, you may +consider it more useful to permit linking proprietary applications with the +library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. diff --git a/backend_addon/{{ cookiecutter.__folder_name }}/LICENSE.md b/backend_addon/{{ cookiecutter.__folder_name }}/LICENSE.md new file mode 100644 index 0000000..b361d42 --- /dev/null +++ b/backend_addon/{{ cookiecutter.__folder_name }}/LICENSE.md @@ -0,0 +1,15 @@ +{{ cookiecutter.python_package_name }} Copyright 2023, {{ cookiecutter.author }} + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License version 2 +as published by the Free Software Foundation. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 59 Temple Place, Suite 330, Boston, +MA 02111-1307 USA. diff --git a/backend_addon/{{ cookiecutter.__folder_name }}/MANIFEST.in b/backend_addon/{{ cookiecutter.__folder_name }}/MANIFEST.in new file mode 100644 index 0000000..8c760dd --- /dev/null +++ b/backend_addon/{{ cookiecutter.__folder_name }}/MANIFEST.in @@ -0,0 +1,18 @@ +graft src/{{ cookiecutter.github_organization }} +graft docs +graft news +graft tests +include .coveragerc +include .dockerignore +include .editorconfig +include *.txt +include *.yml +include *.md +exclude *-mxdev.txt +exclude Dockerfile +exclude mx.ini +exclude Makefile +recursive-exclude frontend * +exclude instance.yaml +global-exclude *.pyc +global-exclude .DS_Store diff --git a/backend_addon/{{ cookiecutter.__folder_name }}/Makefile b/backend_addon/{{ cookiecutter.__folder_name }}/Makefile new file mode 100644 index 0000000..82e7ca3 --- /dev/null +++ b/backend_addon/{{ cookiecutter.__folder_name }}/Makefile @@ -0,0 +1,116 @@ +### Defensive settings for make: +# https://tech.davis-hansson.com/p/make/ +SHELL:=bash +.ONESHELL: +.SHELLFLAGS:=-xeu -o pipefail -O inherit_errexit -c +.SILENT: +.DELETE_ON_ERROR: +MAKEFLAGS+=--warn-undefined-variables +MAKEFLAGS+=--no-builtin-rules + +# We like colors +# From: https://coderwall.com/p/izxssa/colored-makefile-for-golang-projects +RED=`tput setaf 1` +GREEN=`tput setaf 2` +RESET=`tput sgr0` +YELLOW=`tput setaf 3` + +# Set distributions still in development +DISTRIBUTIONS="{{ cookiecutter.__package_name }}" +ALLOWED_DISTRIBUTIONS="{{ cookiecutter.__package_name }}" + +PLONE6=6.0-latest + +# Python checks +PYTHON?=python3 + +# installed? +ifeq (, $(shell which $(PYTHON) )) + $(error "PYTHON=$(PYTHON) not found in $(PATH)") +endif + +# version ok? +PYTHON_VERSION_MIN=3.8 +PYTHON_VERSION_OK=$(shell $(PYTHON) -c "import sys; print((int(sys.version_info[0]), int(sys.version_info[1])) >= tuple(map(int, '$(PYTHON_VERSION_MIN)'.split('.'))))") +ifeq ($(PYTHON_VERSION_OK),0) + $(error "Need python $(PYTHON_VERSION) >= $(PYTHON_VERSION_MIN)") +endif + +BACKEND_FOLDER=$(shell dirname $(realpath $(firstword $(MAKEFILE_LIST)))) + +GIT_FOLDER=$(BACKEND_FOLDER)/.git + + +all: build + +# Add the following 'help' target to your Makefile +# And add help text after each target name starting with '\#\#' +.PHONY: help +help: ## This help message + @grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | sort | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}' + +bin/pip bin/tox bin/mxdev: + @echo "$(GREEN)==> Setup Virtual Env$(RESET)" + $(PYTHON) -m venv . + bin/pip install -U "pip" "wheel" "cookiecutter" "mxdev" "tox" "pre-commit" + if [ -d $(GIT_FOLDER) ]; then bin/pre-commit install; else echo "$(RED) Not installing pre-commit$(RESET)";fi + +.PHONY: config +config: bin/pip ## Create instance configuration + @echo "$(GREEN)==> Create instance configuration$(RESET)" + bin/cookiecutter -f --no-input --config-file instance.yaml gh:plone/cookiecutter-zope-instance + +.PHONY: build-dev +build-dev: config ## pip install Plone packages + @echo "$(GREEN)==> Setup Build$(RESET)" + bin/mxdev -c mx.ini + bin/pip install -r requirements-mxdev.txt + +.PHONY: install +install: build-dev ## Install Plone 6.0 + + +.PHONY: build +build: build-dev ## Install Plone 6.0 + + +.PHONY: clean +clean: ## Remove old virtualenv and creates a new one + @echo "$(RED)==> Cleaning environment and build$(RESET)" + rm -rf bin lib lib64 include share etc var inituser pyvenv.cfg .installed.cfg instance .tox .pytest_cache + +.PHONY: start +start: ## Start a Plone instance on localhost:8080 + PYTHONWARNINGS=ignore ./bin/runwsgi instance/etc/zope.ini + +.PHONY: console +console: ## Start a zope console + PYTHONWARNINGS=ignore ./bin/zconsole debug instance/etc/zope.conf + +.PHONY: format +format: bin/tox ## Format the codebase according to our standards + @echo "$(GREEN)==> Format codebase$(RESET)" + bin/tox -e format + +.PHONY: lint +lint: ## check code style + bin/tox -e lint + +# i18n +bin/i18ndude: bin/pip + @echo "$(GREEN)==> Install translation tools$(RESET)" + bin/pip install i18ndude + +.PHONY: i18n +i18n: bin/i18ndude ## Update locales + @echo "$(GREEN)==> Updating locales$(RESET)" + bin/update_dist_locale + +# Tests +.PHONY: test +test: bin/tox ## run tests + bin/tox -e test + +.PHONY: test-coverage +test-coverage: bin/tox ## run tests with coverage + bin/tox -e coverage diff --git a/backend_addon/{{ cookiecutter.__folder_name }}/README.md b/backend_addon/{{ cookiecutter.__folder_name }}/README.md new file mode 100644 index 0000000..f8ab205 --- /dev/null +++ b/backend_addon/{{ cookiecutter.__folder_name }}/README.md @@ -0,0 +1,36 @@ +# {{ cookiecutter.python_package_name }} + +{{ cookiecutter.description }} + +## Features + +### Content Types + +- TBD + +### Initial content + +This package contains a simple volto configuration. + +Installation +------------ + +Install {{ cookiecutter.python_package_name }} with `pip`: + +```shell +pip install {{ cookiecutter.python_package_name }} +``` +And to create the Plone site: + +```shell +make create_site +``` + +## Contribute + +- [Issue Tracker](https://github.com/{{ cookiecutter.github_organization }}/{{ cookiecutter.python_package_name }}/issues) +- [Source Code](https://github.com/{{ cookiecutter.github_organization }}/{{ cookiecutter.python_package_name }}/) + +## License + +The project is licensed under GPLv2. diff --git a/backend_addon/{{ cookiecutter.__folder_name }}/constraints.txt b/backend_addon/{{ cookiecutter.__folder_name }}/constraints.txt new file mode 100644 index 0000000..228b6cc --- /dev/null +++ b/backend_addon/{{ cookiecutter.__folder_name }}/constraints.txt @@ -0,0 +1 @@ +-c https://dist.plone.org/release/6.0-latest/constraints.txt diff --git a/backend_addon/{{ cookiecutter.__folder_name }}/docs/.gitkeep b/backend_addon/{{ cookiecutter.__folder_name }}/docs/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/backend_addon/{{ cookiecutter.__folder_name }}/instance.yaml b/backend_addon/{{ cookiecutter.__folder_name }}/instance.yaml new file mode 100644 index 0000000..0e531f0 --- /dev/null +++ b/backend_addon/{{ cookiecutter.__folder_name }}/instance.yaml @@ -0,0 +1,8 @@ +default_context: + initial_user_name: 'admin' + initial_user_password: 'admin' + + load_zcml: + package_includes: ['{{ cookiecutter.python_package_name }}'] + + db_storage: direct diff --git a/backend_addon/{{ cookiecutter.__folder_name }}/mx.ini b/backend_addon/{{ cookiecutter.__folder_name }}/mx.ini new file mode 100644 index 0000000..3629ad6 --- /dev/null +++ b/backend_addon/{{ cookiecutter.__folder_name }}/mx.ini @@ -0,0 +1,16 @@ +; This is a mxdev configuration file +; it can be used to override versions of packages already defined in the +; constraints files and to add new packages from VCS like git. +; to learn more about mxdev visit https://pypi.org/project/mxdev/ + +[settings] +; example how to override a package version +; version-overrides = +; example.package==2.1.0a2 + +; example section to use packages from git +; [example.contenttype] +; url = https://github.com/collective/example.contenttype.git +; pushurl = git@github.com:collective/example.contenttype.git +; extras = test +; branch = feature-7 diff --git a/backend_addon/{{ cookiecutter.__folder_name }}/news/.changelog_template.jinja b/backend_addon/{{ cookiecutter.__folder_name }}/news/.changelog_template.jinja new file mode 100644 index 0000000..0314cef --- /dev/null +++ b/backend_addon/{{ cookiecutter.__folder_name }}/news/.changelog_template.jinja @@ -0,0 +1,15 @@ +{{"{% if sections[""] %} +{% for category, val in definitions.items() if category in sections[""] %} + +### {{ definitions[category]['name'] }} + +{% for text, values in sections[""][category].items() %} +- {{ text }} {{ values|join(', ') }} +{% endfor %} + +{% endfor %} +{% else %} +No significant changes. + + +{% endif %}"}} diff --git a/backend_addon/{{ cookiecutter.__folder_name }}/news/.gitkeep b/backend_addon/{{ cookiecutter.__folder_name }}/news/.gitkeep new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/backend_addon/{{ cookiecutter.__folder_name }}/news/.gitkeep @@ -0,0 +1 @@ + diff --git a/backend_addon/{{ cookiecutter.__folder_name }}/pyproject.toml b/backend_addon/{{ cookiecutter.__folder_name }}/pyproject.toml new file mode 100644 index 0000000..e9b79b5 --- /dev/null +++ b/backend_addon/{{ cookiecutter.__folder_name }}/pyproject.toml @@ -0,0 +1,161 @@ +# Generated from: +# https://github.com/plone/meta/tree/master/config/default +# See the inline comments on how to expand/tweak this configuration file +[tool.towncrier] +directory = "news/" +filename = "CHANGES.md" +start_string = "\n" +title_format = "## {version} ({project_date})" +template = "news/.changelog_template.jinja" +underlines = ["", "", ""] + +[[tool.towncrier.type]] +directory = "breaking" +name = "Breaking changes:" +showcontent = true + +[[tool.towncrier.type]] +directory = "feature" +name = "New features:" +showcontent = true + +[[tool.towncrier.type]] +directory = "bugfix" +name = "Bug fixes:" +showcontent = true + +[[tool.towncrier.type]] +directory = "internal" +name = "Internal:" +showcontent = true + +[[tool.towncrier.type]] +directory = "documentation" +name = "Documentation:" +showcontent = true + +[[tool.towncrier.type]] +directory = "tests" +name = "Tests" +showcontent = true + +## +# Add extra configuration options in .meta.toml: +# [pyproject] +# towncrier_extra_lines = """ +# extra_configuration +# """ +## + +[tool.isort] +profile = "plone" + +## +# Add extra configuration options in .meta.toml: +# [pyproject] +# isort_extra_lines = """ +# extra_configuration +# """ +## + +[tool.black] +target-version = ["py38"] + +## +# Add extra configuration options in .meta.toml: +# [pyproject] +# black_extra_lines = """ +# extra_configuration +# """ +## + +[tool.codespell] +ignore-words-list = "discreet,vew" +skip = "*.po,*.min.js" +## +# Add extra configuration options in .meta.toml: +# [pyproject] +# codespell_ignores = "foo,bar" +# codespell_skip = "*.po,*.map,package-lock.json" +## + +[tool.dependencychecker] +Zope = [ + # Zope own provided namespaces + 'App', 'OFS', 'Products.Five', 'Products.OFSP', 'Products.PageTemplates', + 'Products.SiteAccess', 'Shared', 'Testing', 'ZPublisher', 'ZTUtils', + 'Zope2', 'webdav', 'zmi', + # ExtensionClass own provided namespaces + 'ExtensionClass', 'ComputedAttribute', 'MethodObject', + # Zope dependencies + 'AccessControl', 'Acquisition', 'AuthEncoding', 'beautifulsoup4', 'BTrees', + 'cffi', 'Chameleon', 'DateTime', 'DocumentTemplate', + 'MultiMapping', 'multipart', 'PasteDeploy', 'Persistence', 'persistent', + 'pycparser', 'python-gettext', 'pytz', 'RestrictedPython', 'roman', + 'soupsieve', 'transaction', 'waitress', 'WebOb', 'WebTest', 'WSGIProxy2', + 'z3c.pt', 'zc.lockfile', 'ZConfig', 'zExceptions', 'ZODB', 'zodbpickle', + 'zope.annotation', 'zope.browser', 'zope.browsermenu', 'zope.browserpage', + 'zope.browserresource', 'zope.cachedescriptors', 'zope.component', + 'zope.configuration', 'zope.container', 'zope.contentprovider', + 'zope.contenttype', 'zope.datetime', 'zope.deferredimport', + 'zope.deprecation', 'zope.dottedname', 'zope.event', 'zope.exceptions', + 'zope.filerepresentation', 'zope.globalrequest', 'zope.hookable', + 'zope.i18n', 'zope.i18nmessageid', 'zope.interface', 'zope.lifecycleevent', + 'zope.location', 'zope.pagetemplate', 'zope.processlifetime', 'zope.proxy', + 'zope.ptresource', 'zope.publisher', 'zope.schema', 'zope.security', + 'zope.sequencesort', 'zope.site', 'zope.size', 'zope.structuredtext', + 'zope.tal', 'zope.tales', 'zope.testbrowser', 'zope.testing', + 'zope.traversing', 'zope.viewlet' +] +'Products.CMFCore' = [ + 'docutils', 'five.localsitemanager', 'Missing', 'Products.BTreeFolder2', + 'Products.GenericSetup', 'Products.MailHost', 'Products.PythonScripts', + 'Products.StandardCacheManagers', 'Products.ZCatalog', 'Record', + 'zope.sendmail', 'Zope' +] +'plone.base' = [ + 'plone.batching', 'plone.registry', 'plone.schema','plone.z3cform', + 'Products.CMFCore', 'Products.CMFDynamicViewFTI', +] +python-dateutil = ['dateutil'] +ignore-packages = ['plone.volto', 'zestreleaser.towncrier', 'zest.releaser', 'pytest', 'pytest-cov', 'pytest-plone'] +Plone = ['Products.CMFPlone', 'Products.CMFCore', 'Products.GenericSetup'] + +## +# Add extra configuration options in .meta.toml: +# [pyproject] +# dependencies_ignores = "['zestreleaser.towncrier']" +# dependencies_mappings = [ +# "gitpython = ['git']", +# "pygithub = ['github']", +# ] +# """ +## + +[tool.check-manifest] +ignore = [ + ".editorconfig", + ".meta.toml", + ".pre-commit-config.yaml", + "tox.ini", + ".flake8", + "mx.ini", + +] +## +# Add extra configuration options in .meta.toml: +# [pyproject] +# check_manifest_ignores = """ +# "*.map.js", +# "*.pyc", +# """ +## + + +## +# Add extra configuration options in .meta.toml: +# [pyproject] +# extra_lines = """ +# _your own configuration lines_ +# """ +## diff --git a/backend_addon/{{ cookiecutter.__folder_name }}/requirements.txt b/backend_addon/{{ cookiecutter.__folder_name }}/requirements.txt new file mode 100644 index 0000000..9dd6dd4 --- /dev/null +++ b/backend_addon/{{ cookiecutter.__folder_name }}/requirements.txt @@ -0,0 +1,2 @@ +-c constraints.txt +-e ".[test]" diff --git a/backend_addon/{{ cookiecutter.__folder_name }}/setup.py b/backend_addon/{{ cookiecutter.__folder_name }}/setup.py new file mode 100644 index 0000000..15eafc3 --- /dev/null +++ b/backend_addon/{{ cookiecutter.__folder_name }}/setup.py @@ -0,0 +1,76 @@ +"""Installer for the {{ cookiecutter.python_package_name }} package.""" +from pathlib import Path +from setuptools import find_packages +from setuptools import setup + + +long_description = f""" +{Path("README.md").read_text()}\n +{Path("CONTRIBUTORS.md").read_text()}\n +{Path("CHANGES.md").read_text()}\n +""" + + +setup( + name="{{ cookiecutter.python_package_name }}", + version="{{ cookiecutter.__version_package }}", + description="{{ cookiecutter.description }}.", + long_description=long_description, + long_description_content_type="text/markdown", + classifiers=[ + "Development Status :: 3 - Alpha", + "Environment :: Web Environment", + "Framework :: Plone", + "Framework :: Plone :: Addon", + "Framework :: Plone :: Distribution", + "Framework :: Plone :: 6.0", + "Programming Language :: Python", + "Programming Language :: Python :: 3.8", + "Programming Language :: Python :: 3.9", + "Programming Language :: Python :: 3.10", + "Programming Language :: Python :: 3.11", + "Operating System :: OS Independent", + "License :: OSI Approved :: GNU General Public License v2 (GPLv2)", + ], + keywords="Python Plone CMS", + author="{{ cookiecutter.author }}", + author_email="{{ cookiecutter.email }}", + url="https://github.com/{{ cookiecutter.github_organization }}/{{ cookiecutter.python_package_name }}", + project_urls={ + "PyPI": "https://pypi.python.org/pypi/{{ cookiecutter.python_package_name }}", + "Source": "https://github.com/{{ cookiecutter.github_organization }}/{{ cookiecutter.python_package_name }}", + "Tracker": "https://github.com/{{ cookiecutter.github_organization }}/{{ cookiecutter.python_package_name }}/issues", + }, + license="GPL version 2", + packages=find_packages("src", exclude=["ez_setup"]), + namespace_packages=["{{ cookiecutter.__package_namespace }}"], + package_dir={"": "src"}, + include_package_data=True, + zip_safe=False, + python_requires=">=3.8", + install_requires=[ + "setuptools", + "Plone", + "plone.api", + {%- if cookiecutter.feature_headless == '1' %} + "plone.restapi", + {%- endif %} + ], + extras_require={ + "test": [ + "zest.releaser[recommended]", + "zestreleaser.towncrier", + "plone.app.testing", + "plone.restapi[test]", + "pytest", + "pytest-cov", + "pytest-plone>=0.2.0", + ], + }, + entry_points=""" + [z3c.autoinclude.plugin] + target = plone + [console_scripts] + update_dist_locale = {{ cookiecutter.python_package_name }}.locales.update:update_locale + """, +) diff --git a/backend_addon/{{ cookiecutter.__folder_name }}/src/{{ cookiecutter.__package_namespace }}/__init__.py b/backend_addon/{{ cookiecutter.__folder_name }}/src/{{ cookiecutter.__package_namespace }}/__init__.py new file mode 100644 index 0000000..5284146 --- /dev/null +++ b/backend_addon/{{ cookiecutter.__folder_name }}/src/{{ cookiecutter.__package_namespace }}/__init__.py @@ -0,0 +1 @@ +__import__("pkg_resources").declare_namespace(__name__) diff --git a/backend_addon/{{ cookiecutter.__folder_name }}/src/{{ cookiecutter.__package_namespace }}/{{ cookiecutter.__package_name }}/__init__.py b/backend_addon/{{ cookiecutter.__folder_name }}/src/{{ cookiecutter.__package_namespace }}/{{ cookiecutter.__package_name }}/__init__.py new file mode 100644 index 0000000..0848945 --- /dev/null +++ b/backend_addon/{{ cookiecutter.__folder_name }}/src/{{ cookiecutter.__package_namespace }}/{{ cookiecutter.__package_name }}/__init__.py @@ -0,0 +1,10 @@ +"""Init and utils.""" +import logging +from zope.i18nmessageid import MessageFactory + + +PACKAGE_NAME = "{{ cookiecutter.python_package_name }}" + +_ = MessageFactory(PACKAGE_NAME) + +logger = logging.getLogger(PACKAGE_NAME) diff --git a/backend_addon/{{ cookiecutter.__folder_name }}/src/{{ cookiecutter.__package_namespace }}/{{ cookiecutter.__package_name }}/configure.zcml b/backend_addon/{{ cookiecutter.__folder_name }}/src/{{ cookiecutter.__package_namespace }}/{{ cookiecutter.__package_name }}/configure.zcml new file mode 100644 index 0000000..6f3fc90 --- /dev/null +++ b/backend_addon/{{ cookiecutter.__folder_name }}/src/{{ cookiecutter.__package_namespace }}/{{ cookiecutter.__package_name }}/configure.zcml @@ -0,0 +1,24 @@ + + + + + + + + + + + + {%- if cookiecutter.feature_headless == '1' %} + + {%- endif %} + + + diff --git a/backend_addon/{{ cookiecutter.__folder_name }}/src/{{ cookiecutter.__package_namespace }}/{{ cookiecutter.__package_name }}/content/__init__.py b/backend_addon/{{ cookiecutter.__folder_name }}/src/{{ cookiecutter.__package_namespace }}/{{ cookiecutter.__package_name }}/content/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/backend_addon/{{ cookiecutter.__folder_name }}/src/{{ cookiecutter.__package_namespace }}/{{ cookiecutter.__package_name }}/controlpanel/__init__.py b/backend_addon/{{ cookiecutter.__folder_name }}/src/{{ cookiecutter.__package_namespace }}/{{ cookiecutter.__package_name }}/controlpanel/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/backend_addon/{{ cookiecutter.__folder_name }}/src/{{ cookiecutter.__package_namespace }}/{{ cookiecutter.__package_name }}/controlpanel/configure.zcml b/backend_addon/{{ cookiecutter.__folder_name }}/src/{{ cookiecutter.__package_namespace }}/{{ cookiecutter.__package_name }}/controlpanel/configure.zcml new file mode 100644 index 0000000..09a3d2c --- /dev/null +++ b/backend_addon/{{ cookiecutter.__folder_name }}/src/{{ cookiecutter.__package_namespace }}/{{ cookiecutter.__package_name }}/controlpanel/configure.zcml @@ -0,0 +1,8 @@ + + + diff --git a/backend_addon/{{ cookiecutter.__folder_name }}/src/{{ cookiecutter.__package_namespace }}/{{ cookiecutter.__package_name }}/dependencies.zcml b/backend_addon/{{ cookiecutter.__folder_name }}/src/{{ cookiecutter.__package_namespace }}/{{ cookiecutter.__package_name }}/dependencies.zcml new file mode 100644 index 0000000..d096392 --- /dev/null +++ b/backend_addon/{{ cookiecutter.__folder_name }}/src/{{ cookiecutter.__package_namespace }}/{{ cookiecutter.__package_name }}/dependencies.zcml @@ -0,0 +1,9 @@ + + + + {%- if cookiecutter.feature_headless == '1' %} + + + {%- endif %} + + diff --git a/backend_addon/{{ cookiecutter.__folder_name }}/src/{{ cookiecutter.__package_namespace }}/{{ cookiecutter.__package_name }}/indexers/__init__.py b/backend_addon/{{ cookiecutter.__folder_name }}/src/{{ cookiecutter.__package_namespace }}/{{ cookiecutter.__package_name }}/indexers/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/backend_addon/{{ cookiecutter.__folder_name }}/src/{{ cookiecutter.__package_namespace }}/{{ cookiecutter.__package_name }}/indexers/configure.zcml b/backend_addon/{{ cookiecutter.__folder_name }}/src/{{ cookiecutter.__package_namespace }}/{{ cookiecutter.__package_name }}/indexers/configure.zcml new file mode 100644 index 0000000..bad8247 --- /dev/null +++ b/backend_addon/{{ cookiecutter.__folder_name }}/src/{{ cookiecutter.__package_namespace }}/{{ cookiecutter.__package_name }}/indexers/configure.zcml @@ -0,0 +1,5 @@ + + + + + diff --git a/backend_addon/{{ cookiecutter.__folder_name }}/src/{{ cookiecutter.__package_namespace }}/{{ cookiecutter.__package_name }}/interfaces.py b/backend_addon/{{ cookiecutter.__folder_name }}/src/{{ cookiecutter.__package_namespace }}/{{ cookiecutter.__package_name }}/interfaces.py new file mode 100644 index 0000000..d5c32d0 --- /dev/null +++ b/backend_addon/{{ cookiecutter.__folder_name }}/src/{{ cookiecutter.__package_namespace }}/{{ cookiecutter.__package_name }}/interfaces.py @@ -0,0 +1,6 @@ +"""Module where all interfaces, events and exceptions live.""" +from zope.publisher.interfaces.browser import IDefaultBrowserLayer + + +class IBrowserLayer(IDefaultBrowserLayer): + """Marker interface that defines a browser layer.""" diff --git a/backend_addon/{{ cookiecutter.__folder_name }}/src/{{ cookiecutter.__package_namespace }}/{{ cookiecutter.__package_name }}/locales/__init__.py b/backend_addon/{{ cookiecutter.__folder_name }}/src/{{ cookiecutter.__package_namespace }}/{{ cookiecutter.__package_name }}/locales/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/backend_addon/{{ cookiecutter.__folder_name }}/src/{{ cookiecutter.__package_namespace }}/{{ cookiecutter.__package_name }}/locales/en/LC_MESSAGES/{{ cookiecutter.python_package_name }}.po b/backend_addon/{{ cookiecutter.__folder_name }}/src/{{ cookiecutter.__package_namespace }}/{{ cookiecutter.__package_name }}/locales/en/LC_MESSAGES/{{ cookiecutter.python_package_name }}.po new file mode 100644 index 0000000..6d516c8 --- /dev/null +++ b/backend_addon/{{ cookiecutter.__folder_name }}/src/{{ cookiecutter.__package_namespace }}/{{ cookiecutter.__package_name }}/locales/en/LC_MESSAGES/{{ cookiecutter.python_package_name }}.po @@ -0,0 +1,15 @@ +msgid "" +msgstr "" +"Project-Id-Version: PACKAGE VERSION\n" +"POT-Creation-Date: 2022-05-25 17:12+0000\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI +ZONE\n" +"Last-Translator: FULL NAME \n" +"Language-Team: LANGUAGE \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=utf-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=1; plural=0\n" +"Language-Code: en\n" +"Language-Name: English\n" +"Preferred-Encodings: utf-8 latin1\n" +"Domain: {{ cookiecutter.python_package_name }}\n" diff --git a/backend_addon/{{ cookiecutter.__folder_name }}/src/{{ cookiecutter.__package_namespace }}/{{ cookiecutter.__package_name }}/locales/update.py b/backend_addon/{{ cookiecutter.__folder_name }}/src/{{ cookiecutter.__package_namespace }}/{{ cookiecutter.__package_name }}/locales/update.py new file mode 100644 index 0000000..39b8d6f --- /dev/null +++ b/backend_addon/{{ cookiecutter.__folder_name }}/src/{{ cookiecutter.__package_namespace }}/{{ cookiecutter.__package_name }}/locales/update.py @@ -0,0 +1,94 @@ +"""Update locales.""" +from pathlib import Path + +import logging +import re +import subprocess + + +logging.basicConfig() +logger = logging.getLogger("i18n") +logger.setLevel(logging.DEBUG) + + +PATTERN = r"^[a-z]{2}.*" +cwd = Path.cwd() +target_path = Path(__file__).parent.parent.resolve() +locale_path = target_path / "locales" + +i18ndude = cwd / "bin" / "i18ndude" +if not i18ndude.exists(): + i18ndude = cwd / "i18ndude" + +# ignore node_modules files resulting in errors +excludes = '"*.html *json-schema*.xml"' + + +def _get_languages_folders(): + folders = [path for path in locale_path.glob("*") if path.is_dir()] + language_folders = sorted( + [path for path in folders if not path.name.startswith("_")], + key=lambda item: item.name, + ) + return language_folders + + +def locale_folder_setup(domain: str): + languages = _get_languages_folders() + for lang_folder in languages: + lc_messages_path = lang_folder / "LC_MESSAGES" + lang = lang_folder.name + if lc_messages_path.exists(): + continue + elif re.match(PATTERN, lang): + lc_messages_path.mkdir() + cmd = ( + f"msginit --locale={lang} " + f"--input={locale_path}/{domain}.pot " + f"--output={locale_path}/{lang}/LC_MESSAGES/{domain}.po" + ) + subprocess.call( + cmd, + shell=True, + ) + + +def _rebuild(domain: str): + cmd = ( + f"{i18ndude} rebuild-pot --pot {locale_path}/{domain}.pot " + f"--exclude {excludes} " + f"--create {domain} {target_path}" + ) + subprocess.call( + cmd, + shell=True, + ) + + +def _sync(domain: str): + for path in locale_path.glob("*/LC_MESSAGES/"): + # Check if domain file exists + domain_file = path / f"{domain}.po" + if not domain_file.exists(): + # Create an empty file + domain_file.write_text("") + cmd = ( + f"{i18ndude} sync --pot {locale_path}/{domain}.pot " + f"{locale_path}/*/LC_MESSAGES/{domain}.po" + ) + subprocess.call( + cmd, + shell=True, + ) + + +def update_locale(): + domains = [path.name[:-4] for path in locale_path.glob("*.pot")] + if i18ndude.exists(): + for domain in domains: + logger.info(f"Updating translations for {domain}") + locale_folder_setup(domain) + _rebuild(domain) + _sync(domain) + else: + logger.error("Not able to find i18ndude") diff --git a/backend_addon/{{ cookiecutter.__folder_name }}/src/{{ cookiecutter.__package_namespace }}/{{ cookiecutter.__package_name }}/locales/{{ cookiecutter.python_package_name }}.pot b/backend_addon/{{ cookiecutter.__folder_name }}/src/{{ cookiecutter.__package_namespace }}/{{ cookiecutter.__package_name }}/locales/{{ cookiecutter.python_package_name }}.pot new file mode 100644 index 0000000..6286849 --- /dev/null +++ b/backend_addon/{{ cookiecutter.__folder_name }}/src/{{ cookiecutter.__package_namespace }}/{{ cookiecutter.__package_name }}/locales/{{ cookiecutter.python_package_name }}.pot @@ -0,0 +1,18 @@ +#--- PLEASE EDIT THE LINES BELOW CORRECTLY --- +#SOME DESCRIPTIVE TITLE. +#FIRST AUTHOR , YEAR. +msgid "" +msgstr "" +"Project-Id-Version: PACKAGE VERSION\n" +"POT-Creation-Date: 2022-05-25 17:12+0000\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI +ZONE\n" +"Last-Translator: FULL NAME \n" +"Language-Team: LANGUAGE \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=utf-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=1; plural=0\n" +"Language-Code: en\n" +"Language-Name: English\n" +"Preferred-Encodings: utf-8 latin1\n" +"Domain: {{ cookiecutter.python_package_name }}\n" diff --git a/backend_addon/{{ cookiecutter.__folder_name }}/src/{{ cookiecutter.__package_namespace }}/{{ cookiecutter.__package_name }}/profiles.zcml b/backend_addon/{{ cookiecutter.__folder_name }}/src/{{ cookiecutter.__package_namespace }}/{{ cookiecutter.__package_name }}/profiles.zcml new file mode 100644 index 0000000..6200880 --- /dev/null +++ b/backend_addon/{{ cookiecutter.__folder_name }}/src/{{ cookiecutter.__package_namespace }}/{{ cookiecutter.__package_name }}/profiles.zcml @@ -0,0 +1,32 @@ + + + + + + + + + + + + + diff --git a/backend_addon/{{ cookiecutter.__folder_name }}/src/{{ cookiecutter.__package_namespace }}/{{ cookiecutter.__package_name }}/profiles/default/browserlayer.xml b/backend_addon/{{ cookiecutter.__folder_name }}/src/{{ cookiecutter.__package_namespace }}/{{ cookiecutter.__package_name }}/profiles/default/browserlayer.xml new file mode 100644 index 0000000..bd25364 --- /dev/null +++ b/backend_addon/{{ cookiecutter.__folder_name }}/src/{{ cookiecutter.__package_namespace }}/{{ cookiecutter.__package_name }}/profiles/default/browserlayer.xml @@ -0,0 +1,6 @@ + + + + diff --git a/backend_addon/{{ cookiecutter.__folder_name }}/src/{{ cookiecutter.__package_namespace }}/{{ cookiecutter.__package_name }}/profiles/default/catalog.xml b/backend_addon/{{ cookiecutter.__folder_name }}/src/{{ cookiecutter.__package_namespace }}/{{ cookiecutter.__package_name }}/profiles/default/catalog.xml new file mode 100644 index 0000000..388f539 --- /dev/null +++ b/backend_addon/{{ cookiecutter.__folder_name }}/src/{{ cookiecutter.__package_namespace }}/{{ cookiecutter.__package_name }}/profiles/default/catalog.xml @@ -0,0 +1,13 @@ + + + + + diff --git a/backend_addon/{{ cookiecutter.__folder_name }}/src/{{ cookiecutter.__package_namespace }}/{{ cookiecutter.__package_name }}/profiles/default/controlpanel.xml b/backend_addon/{{ cookiecutter.__folder_name }}/src/{{ cookiecutter.__package_namespace }}/{{ cookiecutter.__package_name }}/profiles/default/controlpanel.xml new file mode 100644 index 0000000..a75d00c --- /dev/null +++ b/backend_addon/{{ cookiecutter.__folder_name }}/src/{{ cookiecutter.__package_namespace }}/{{ cookiecutter.__package_name }}/profiles/default/controlpanel.xml @@ -0,0 +1,4 @@ + + + + diff --git a/backend_addon/{{ cookiecutter.__folder_name }}/src/{{ cookiecutter.__package_namespace }}/{{ cookiecutter.__package_name }}/profiles/default/diff_tool.xml b/backend_addon/{{ cookiecutter.__folder_name }}/src/{{ cookiecutter.__package_namespace }}/{{ cookiecutter.__package_name }}/profiles/default/diff_tool.xml new file mode 100644 index 0000000..6a1c5f4 --- /dev/null +++ b/backend_addon/{{ cookiecutter.__folder_name }}/src/{{ cookiecutter.__package_namespace }}/{{ cookiecutter.__package_name }}/profiles/default/diff_tool.xml @@ -0,0 +1,6 @@ + + + + + + diff --git a/backend_addon/{{ cookiecutter.__folder_name }}/src/{{ cookiecutter.__package_namespace }}/{{ cookiecutter.__package_name }}/profiles/default/metadata.xml b/backend_addon/{{ cookiecutter.__folder_name }}/src/{{ cookiecutter.__package_namespace }}/{{ cookiecutter.__package_name }}/profiles/default/metadata.xml new file mode 100644 index 0000000..48b97b9 --- /dev/null +++ b/backend_addon/{{ cookiecutter.__folder_name }}/src/{{ cookiecutter.__package_namespace }}/{{ cookiecutter.__package_name }}/profiles/default/metadata.xml @@ -0,0 +1,7 @@ + + + {{ cookiecutter.__profile_version }} + + + + diff --git a/backend_addon/{{ cookiecutter.__folder_name }}/src/{{ cookiecutter.__package_namespace }}/{{ cookiecutter.__package_name }}/profiles/default/registry/.gitkeep b/backend_addon/{{ cookiecutter.__folder_name }}/src/{{ cookiecutter.__package_namespace }}/{{ cookiecutter.__package_name }}/profiles/default/registry/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/backend_addon/{{ cookiecutter.__folder_name }}/src/{{ cookiecutter.__package_namespace }}/{{ cookiecutter.__package_name }}/profiles/default/repositorytool.xml b/backend_addon/{{ cookiecutter.__folder_name }}/src/{{ cookiecutter.__package_namespace }}/{{ cookiecutter.__package_name }}/profiles/default/repositorytool.xml new file mode 100644 index 0000000..4f674d6 --- /dev/null +++ b/backend_addon/{{ cookiecutter.__folder_name }}/src/{{ cookiecutter.__package_namespace }}/{{ cookiecutter.__package_name }}/profiles/default/repositorytool.xml @@ -0,0 +1,6 @@ + + + + + + diff --git a/backend_addon/{{ cookiecutter.__folder_name }}/src/{{ cookiecutter.__package_namespace }}/{{ cookiecutter.__package_name }}/profiles/default/rolemap.xml b/backend_addon/{{ cookiecutter.__folder_name }}/src/{{ cookiecutter.__package_namespace }}/{{ cookiecutter.__package_name }}/profiles/default/rolemap.xml new file mode 100644 index 0000000..71ca583 --- /dev/null +++ b/backend_addon/{{ cookiecutter.__folder_name }}/src/{{ cookiecutter.__package_namespace }}/{{ cookiecutter.__package_name }}/profiles/default/rolemap.xml @@ -0,0 +1,6 @@ + + + + + + diff --git a/backend_addon/{{ cookiecutter.__folder_name }}/src/{{ cookiecutter.__package_namespace }}/{{ cookiecutter.__package_name }}/profiles/default/theme.xml b/backend_addon/{{ cookiecutter.__folder_name }}/src/{{ cookiecutter.__package_namespace }}/{{ cookiecutter.__package_name }}/profiles/default/theme.xml new file mode 100644 index 0000000..a328066 --- /dev/null +++ b/backend_addon/{{ cookiecutter.__folder_name }}/src/{{ cookiecutter.__package_namespace }}/{{ cookiecutter.__package_name }}/profiles/default/theme.xml @@ -0,0 +1,5 @@ + + + barceloneta + true + diff --git a/backend_addon/{{ cookiecutter.__folder_name }}/src/{{ cookiecutter.__package_namespace }}/{{ cookiecutter.__package_name }}/profiles/default/types.xml b/backend_addon/{{ cookiecutter.__folder_name }}/src/{{ cookiecutter.__package_namespace }}/{{ cookiecutter.__package_name }}/profiles/default/types.xml new file mode 100644 index 0000000..087e087 --- /dev/null +++ b/backend_addon/{{ cookiecutter.__folder_name }}/src/{{ cookiecutter.__package_namespace }}/{{ cookiecutter.__package_name }}/profiles/default/types.xml @@ -0,0 +1,10 @@ + + + + diff --git a/backend_addon/{{ cookiecutter.__folder_name }}/src/{{ cookiecutter.__package_namespace }}/{{ cookiecutter.__package_name }}/profiles/default/types/.gitkeep b/backend_addon/{{ cookiecutter.__folder_name }}/src/{{ cookiecutter.__package_namespace }}/{{ cookiecutter.__package_name }}/profiles/default/types/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/backend_addon/{{ cookiecutter.__folder_name }}/src/{{ cookiecutter.__package_namespace }}/{{ cookiecutter.__package_name }}/profiles/uninstall/browserlayer.xml b/backend_addon/{{ cookiecutter.__folder_name }}/src/{{ cookiecutter.__package_namespace }}/{{ cookiecutter.__package_name }}/profiles/uninstall/browserlayer.xml new file mode 100644 index 0000000..297a801 --- /dev/null +++ b/backend_addon/{{ cookiecutter.__folder_name }}/src/{{ cookiecutter.__package_namespace }}/{{ cookiecutter.__package_name }}/profiles/uninstall/browserlayer.xml @@ -0,0 +1,6 @@ + + + + diff --git a/backend_addon/{{ cookiecutter.__folder_name }}/src/{{ cookiecutter.__package_namespace }}/{{ cookiecutter.__package_name }}/serializers/__init__.py b/backend_addon/{{ cookiecutter.__folder_name }}/src/{{ cookiecutter.__package_namespace }}/{{ cookiecutter.__package_name }}/serializers/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/backend_addon/{{ cookiecutter.__folder_name }}/src/{{ cookiecutter.__package_namespace }}/{{ cookiecutter.__package_name }}/serializers/configure.zcml b/backend_addon/{{ cookiecutter.__folder_name }}/src/{{ cookiecutter.__package_namespace }}/{{ cookiecutter.__package_name }}/serializers/configure.zcml new file mode 100644 index 0000000..905cbdc --- /dev/null +++ b/backend_addon/{{ cookiecutter.__folder_name }}/src/{{ cookiecutter.__package_namespace }}/{{ cookiecutter.__package_name }}/serializers/configure.zcml @@ -0,0 +1,10 @@ + + + + + + + diff --git a/backend_addon/{{ cookiecutter.__folder_name }}/src/{{ cookiecutter.__package_namespace }}/{{ cookiecutter.__package_name }}/serializers/summary.py b/backend_addon/{{ cookiecutter.__folder_name }}/src/{{ cookiecutter.__package_namespace }}/{{ cookiecutter.__package_name }}/serializers/summary.py new file mode 100644 index 0000000..d2a21f6 --- /dev/null +++ b/backend_addon/{{ cookiecutter.__folder_name }}/src/{{ cookiecutter.__package_namespace }}/{{ cookiecutter.__package_name }}/serializers/summary.py @@ -0,0 +1,9 @@ +from plone.restapi.interfaces import IJSONSummarySerializerMetadata +from zope.interface import implementer + + +@implementer(IJSONSummarySerializerMetadata) +class JSONSummarySerializerMetadata: + """Additional metadata to be exposed on listings.""" + def default_metadata_fields(self): + return {"image_field", "image_scales", "effective", "Subject"} diff --git a/backend_addon/{{ cookiecutter.__folder_name }}/src/{{ cookiecutter.__package_namespace }}/{{ cookiecutter.__package_name }}/setuphandlers/__init__.py b/backend_addon/{{ cookiecutter.__folder_name }}/src/{{ cookiecutter.__package_namespace }}/{{ cookiecutter.__package_name }}/setuphandlers/__init__.py new file mode 100644 index 0000000..141f942 --- /dev/null +++ b/backend_addon/{{ cookiecutter.__folder_name }}/src/{{ cookiecutter.__package_namespace }}/{{ cookiecutter.__package_name }}/setuphandlers/__init__.py @@ -0,0 +1,11 @@ +from Products.CMFPlone.interfaces import INonInstallable +from zope.interface import implementer + + +@implementer(INonInstallable) +class HiddenProfiles: + def getNonInstallableProfiles(self): + """Hide uninstall profile from site-creation and quickinstaller.""" + return [ + "{{ cookiecutter.python_package_name }}:uninstall", + ] diff --git a/backend_addon/{{ cookiecutter.__folder_name }}/src/{{ cookiecutter.__package_namespace }}/{{ cookiecutter.__package_name }}/testing.py b/backend_addon/{{ cookiecutter.__folder_name }}/src/{{ cookiecutter.__package_namespace }}/{{ cookiecutter.__package_name }}/testing.py new file mode 100644 index 0000000..4e859b6 --- /dev/null +++ b/backend_addon/{{ cookiecutter.__folder_name }}/src/{{ cookiecutter.__package_namespace }}/{{ cookiecutter.__package_name }}/testing.py @@ -0,0 +1,50 @@ +import {{ cookiecutter.python_package_name }} # noQA +from plone.app.contenttypes.testing import PLONE_APP_CONTENTTYPES_FIXTURE +from plone.app.robotframework.testing import REMOTE_LIBRARY_BUNDLE_FIXTURE +from plone.app.testing import applyProfile +from plone.app.testing import FunctionalTesting +from plone.app.testing import IntegrationTesting +from plone.app.testing import PloneSandboxLayer +from plone.testing.zope import WSGI_SERVER_FIXTURE + + + +class Layer(PloneSandboxLayer): + defaultBases = (PLONE_APP_CONTENTTYPES_FIXTURE,) + + def setUpZope(self, app, configurationContext): + # Load any other ZCML that is required for your tests. + # The z3c.autoinclude feature is disabled in the Plone fixture base + # layer. + {%- if cookiecutter.feature_headless == '1' %} + import plone.restapi + + self.loadZCML(package=plone.restapi) + {%- endif %} + self.loadZCML(package={{ cookiecutter.python_package_name }}) + + def setUpPloneSite(self, portal): + applyProfile(portal, "{{ cookiecutter.python_package_name }}:default") + +FIXTURE = Layer() + +INTEGRATION_TESTING = IntegrationTesting( + bases=(FIXTURE,), + name="{{ cookiecutter.__python_package_name_upper }}Layer:IntegrationTesting", +) + + +FUNCTIONAL_TESTING = FunctionalTesting( + bases=(FIXTURE, WSGI_SERVER_FIXTURE), + name="{{ cookiecutter.__python_package_name_upper }}Layer:FunctionalTesting", +) + + +ACCEPTANCE_TESTING = FunctionalTesting( + bases=( + FIXTURE, + REMOTE_LIBRARY_BUNDLE_FIXTURE, + WSGI_SERVER_FIXTURE, + ), + name="{{ cookiecutter.__python_package_name_upper }}Layer:AcceptanceTesting", +) diff --git a/backend_addon/{{ cookiecutter.__folder_name }}/src/{{ cookiecutter.__package_namespace }}/{{ cookiecutter.__package_name }}/upgrades/__init__.py b/backend_addon/{{ cookiecutter.__folder_name }}/src/{{ cookiecutter.__package_namespace }}/{{ cookiecutter.__package_name }}/upgrades/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/backend_addon/{{ cookiecutter.__folder_name }}/src/{{ cookiecutter.__package_namespace }}/{{ cookiecutter.__package_name }}/upgrades/configure.zcml b/backend_addon/{{ cookiecutter.__folder_name }}/src/{{ cookiecutter.__package_namespace }}/{{ cookiecutter.__package_name }}/upgrades/configure.zcml new file mode 100644 index 0000000..0a61e0d --- /dev/null +++ b/backend_addon/{{ cookiecutter.__folder_name }}/src/{{ cookiecutter.__package_namespace }}/{{ cookiecutter.__package_name }}/upgrades/configure.zcml @@ -0,0 +1,19 @@ + + + + + diff --git a/backend_addon/{{ cookiecutter.__folder_name }}/src/{{ cookiecutter.__package_namespace }}/{{ cookiecutter.__package_name }}/vocabularies/__init__.py b/backend_addon/{{ cookiecutter.__folder_name }}/src/{{ cookiecutter.__package_namespace }}/{{ cookiecutter.__package_name }}/vocabularies/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/backend_addon/{{ cookiecutter.__folder_name }}/src/{{ cookiecutter.__package_namespace }}/{{ cookiecutter.__package_name }}/vocabularies/configure.zcml b/backend_addon/{{ cookiecutter.__folder_name }}/src/{{ cookiecutter.__package_namespace }}/{{ cookiecutter.__package_name }}/vocabularies/configure.zcml new file mode 100644 index 0000000..fb8b793 --- /dev/null +++ b/backend_addon/{{ cookiecutter.__folder_name }}/src/{{ cookiecutter.__package_namespace }}/{{ cookiecutter.__package_name }}/vocabularies/configure.zcml @@ -0,0 +1,3 @@ + + + diff --git a/backend_addon/{{ cookiecutter.__folder_name }}/tests/conftest.py b/backend_addon/{{ cookiecutter.__folder_name }}/tests/conftest.py new file mode 100644 index 0000000..6f29bf8 --- /dev/null +++ b/backend_addon/{{ cookiecutter.__folder_name }}/tests/conftest.py @@ -0,0 +1,18 @@ +from pytest_plone import fixtures_factory +from {{cookiecutter.python_package_name}}.testing import ACCEPTANCE_TESTING +from {{cookiecutter.python_package_name}}.testing import FUNCTIONAL_TESTING +from {{cookiecutter.python_package_name}}.testing import INTEGRATION_TESTING + + +pytest_plugins = ["pytest_plone"] + + +globals().update( + fixtures_factory( + ( + (ACCEPTANCE_TESTING, "acceptance"), + (FUNCTIONAL_TESTING, "functional"), + (INTEGRATION_TESTING, "integration"), + ) + ) +) diff --git a/backend_addon/{{ cookiecutter.__folder_name }}/tests/setup/test_setup_install.py b/backend_addon/{{ cookiecutter.__folder_name }}/tests/setup/test_setup_install.py new file mode 100644 index 0000000..88f8bee --- /dev/null +++ b/backend_addon/{{ cookiecutter.__folder_name }}/tests/setup/test_setup_install.py @@ -0,0 +1,17 @@ +from {{ cookiecutter.python_package_name }} import PACKAGE_NAME + + +class TestSetupInstall: + def test_addon_installed(self, installer): + """Test if {{ cookiecutter.python_package_name }} is installed.""" + assert installer.is_product_installed(PACKAGE_NAME) is True + + def test_browserlayer(self, browser_layers): + """Test that IBrowserLayer is registered.""" + from {{ cookiecutter.python_package_name }}.interfaces import IBrowserLayer + + assert IBrowserLayer in browser_layers + + def test_latest_version(self, profile_last_version): + """Test latest version of default profile.""" + assert profile_last_version(f"{PACKAGE_NAME}:default") == "{{ cookiecutter.__profile_version }}" diff --git a/backend_addon/{{ cookiecutter.__folder_name }}/tests/setup/test_setup_uninstall.py b/backend_addon/{{ cookiecutter.__folder_name }}/tests/setup/test_setup_uninstall.py new file mode 100644 index 0000000..8db79da --- /dev/null +++ b/backend_addon/{{ cookiecutter.__folder_name }}/tests/setup/test_setup_uninstall.py @@ -0,0 +1,19 @@ +from {{ cookiecutter.python_package_name }} import PACKAGE_NAME + +import pytest + + +class TestSetupUninstall: + @pytest.fixture(autouse=True) + def uninstalled(self, installer): + installer.uninstall_product(PACKAGE_NAME) + + def test_addon_uninstalled(self, installer): + """Test if {{ cookiecutter.python_package_name }} is uninstalled.""" + assert installer.is_product_installed(PACKAGE_NAME) is False + + def test_browserlayer_not_registered(self, browser_layers): + """Test that IBrowserLayer is not registered.""" + from {{ cookiecutter.python_package_name }}.interfaces import IBrowserLayer + + assert IBrowserLayer not in browser_layers diff --git a/backend_addon/{{ cookiecutter.__folder_name }}/tox.ini b/backend_addon/{{ cookiecutter.__folder_name }}/tox.ini new file mode 100644 index 0000000..04fa197 --- /dev/null +++ b/backend_addon/{{ cookiecutter.__folder_name }}/tox.ini @@ -0,0 +1,211 @@ +# Generated from: +# https://github.com/plone/meta/tree/master/config/default +# See the inline comments on how to expand/tweak this configuration file +[tox] +# We need 4.4.0 for constrain_package_deps. +min_version = 4.4.0 +envlist = + lint + test + dependencies + + +## +# Add extra configuration options in .meta.toml: +# [tox] +# envlist_lines = """ +# my_other_environment +# """ +# config_lines = """ +# my_extra_top_level_tox_configuration_lines +# """ +## + +[testenv] +skip_install = true +allowlist_externals = + echo + false +# Make sure typos like `tox -e formaat` are caught instead of silently doing nothing. +# See https://github.com/tox-dev/tox/issues/2858. +commands = + echo "Unrecognized environment name {envname}" + false + +[testenv:init] +description = Prepare environment +skip_install = true +deps = + mxdev +commands = + mxdev -c mx.ini + echo "Initial setup for mxdev" + + +[testenv:format] +description = automatically reformat code +skip_install = true +deps = + pre-commit +commands = + pre-commit run -a pyupgrade + pre-commit run -a isort + pre-commit run -a black + pre-commit run -a zpretty + +[testenv:lint] +description = run linters that will help improve the code style +skip_install = true +deps = + pre-commit +commands = + pre-commit run -a + +[testenv:dependencies] +description = check if the package defines all its dependencies +skip_install = true +deps = + build + z3c.dependencychecker==2.11 +commands = + python -m build --sdist --no-isolation + dependencychecker + +[testenv:dependencies-graph] +description = generate a graph out of the dependencies of the package +skip_install = false +allowlist_externals = + sh +deps = + pipdeptree==2.5.1 + graphviz # optional dependency of pipdeptree +commands = + sh -c 'pipdeptree --exclude setuptools,wheel,pipdeptree,zope.interface,zope.component --graph-output svg > dependencies.svg' + +[testenv:test] +description = run the distribution tests +use_develop = true +skip_install = false +constrain_package_deps = true +set_env = + ROBOT_BROWSER=headlesschrome + +## +# Specify extra test environment variables in .meta.toml: +# [tox] +# test_environment_variables = """ +# PIP_EXTRA_INDEX_URL=https://my-pypi.my-server.com/ +# """ +# +# Set constrain_package_deps .meta.toml: +# [tox] +# constrain_package_deps = false +## +deps = + pytest-plone + pytest + -c https://dist.plone.org/release/6.0-dev/constraints.txt + +## +# Specify additional deps in .meta.toml: +# [tox] +# test_deps_additional = """ +# -esources/plonegovbr.portal_base[test] +# """ +# +# Specify a custom constraints file in .meta.toml: +# [tox] +# constraints_file = "https://my-server.com/constraints.txt" +## +commands = + pytest --disable-warnings {posargs} {toxinidir}/tests +extras = + test + + +[testenv:coverage] +description = get a test coverage report +use_develop = true +skip_install = false +constrain_package_deps = true +set_env = + ROBOT_BROWSER=headlesschrome + +## +# Specify extra test environment variables in .meta.toml: +# [tox] +# test_environment_variables = """ +# PIP_EXTRA_INDEX_URL=https://my-pypi.my-server.com/ +# """ +# +# Set constrain_package_deps .meta.toml: +# [tox] +# constrain_package_deps = "false" +## +deps = + pytest-plone + pytest + coverage + -c https://dist.plone.org/release/6.0-dev/constraints.txt + +commands = + coverage run --source plone.distribution -m pytest {posargs} --disable-warnings {toxinidir}/tests + coverage report -m --format markdown + coverage xml +extras = + test + + +[testenv:release-check] +description = ensure that the distribution is ready to release +skip_install = true +deps = + twine + build + towncrier + -c https://dist.plone.org/release/6.0-dev/constraints.txt + +commands = + # fake version to not have to install the package + # we build the change log as news entries might break + # the README that is displayed on PyPI + towncrier build --version=100.0.0 --yes + python -m build --sdist --no-isolation + twine check dist/* + +[testenv:circular] +description = ensure there are no cyclic dependencies +use_develop = true +skip_install = false +set_env = + +## +# Specify extra test environment variables in .meta.toml: +# [tox] +# test_environment_variables = """ +# PIP_EXTRA_INDEX_URL=https://my-pypi.my-server.com/ +# """ +## +allowlist_externals = + sh +deps = + pipdeptree + pipforester + -c https://dist.plone.org/release/6.0-dev/constraints.txt + +commands = + # Generate the full dependency tree + sh -c 'pipdeptree -j > forest.json' + # Generate a DOT graph with the circular dependencies, if any + pipforester -i forest.json -o forest.dot --cycles + # Report if there are any circular dependencies, i.e. error if there are any + pipforester -i forest.json --check-cycles -o /dev/null + + +## +# Add extra configuration options in .meta.toml: +# [tox] +# extra_lines = """ +# _your own configuration lines_ +# """ +## diff --git a/cookiecutter.json b/cookiecutter.json new file mode 100644 index 0000000..5dc2f68 --- /dev/null +++ b/cookiecutter.json @@ -0,0 +1,14 @@ +{ + "templates": { + "backend_addon": { + "path": "./backend_addon", + "title": "Backend Add-on for Plone", + "description": "Creates a new Python package to be used with Plone" + }, + "frontend_addon": { + "path": "./frontend_addon", + "title": "Frontend Add-on for Plone", + "description": "Creates a new Node package to be used with Volto" + } + } +} diff --git a/frontend_addon/.gitignore b/frontend_addon/.gitignore new file mode 100644 index 0000000..cea4004 --- /dev/null +++ b/frontend_addon/.gitignore @@ -0,0 +1 @@ +volto-addon diff --git a/frontend_addon/Makefile b/frontend_addon/Makefile new file mode 100644 index 0000000..895c6fb --- /dev/null +++ b/frontend_addon/Makefile @@ -0,0 +1,44 @@ +SHELL := /bin/bash +CURRENT_DIR:=$(shell dirname $(realpath $(lastword $(MAKEFILE_LIST)))) + + +# We like colors +# From: https://coderwall.com/p/izxssa/colored-makefile-for-golang-projects +RED=`tput setaf 1` +GREEN=`tput setaf 2` +RESET=`tput sgr0` +YELLOW=`tput setaf 3` + +.PHONY: all +all: build + + +# Add the following 'help' target to your Makefile +# And add help text after each target name starting with '\#\#' +.PHONY: help +help: ## This help message + @grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | sort | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}' + +.PHONY: clean +clean: ## Clean + rm -rf volto-addon + +../bin/cookieplone: ## cookieplone installation + $(MAKE) -C ".." bin/cookieplone + +.PHONY: format +format: ../bin/cookieplone ## Format code + @echo "$(GREEN)==> Formatting codebase $(RESET)" + ../bin/black hooks tests + ../bin/isort hooks tests + +.PHONY: generate +generate: ../bin/cookieplone ## Create a sample package + @echo "$(GREEN)==> Creating new test package$(RESET)" + rm -rf volto-addon + ../bin/cookiecutter . --no-input + +.PHONY: test +test: ../bin/cookieplone ## Create a sample package and tests it + @echo "$(GREEN)==> Creating new test package$(RESET)" + ../bin/python -m pytest tests diff --git a/frontend_addon/README.md b/frontend_addon/README.md new file mode 100644 index 0000000..2784abd --- /dev/null +++ b/frontend_addon/README.md @@ -0,0 +1,66 @@ +[![Cookieplone Frontend Add-on CI](https://github.com/plone/cookieplone-templates/actions/workflows/frontend_addon.yml/badge.svg)](https://github.com/plone/cookieplone-templates/actions/workflows/frontend_addon.yml) +[![Built with Cookiecutter](https://img.shields.io/badge/built%20with-Cookiecutter-ff69b4.svg?logo=cookiecutter)](https://github.com/plone/cookieplone-templates/) +![GitHub](https://img.shields.io/github/license/plone/cookiecutter-plone) +[![Black code style](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/ambv/black) + +# Cookieplone Frontend Add-on + +Powered by [cookieplone](https://github.com/plone/cookieplone) and [Cookiecutter](https://github.com/cookiecutter/cookiecutter), [Cookieplone Frontend Add-on](https://github.com/plone/cookieplone-templates/frontend_addon) is intended to be used by Plone developers to create new add-on packages for Volto. + +## Getting Started 🏁 + +### Prerequisites + +- **pipx**: A handy tool for installing and running Python applications. + +### Installation Guide πŸ› οΈ + +1. **pipx** + +```shell +pip install pipx +``` +### Generate Your Plone Add-on πŸŽ‰ + +```shell +pipx run cookieplone frontend_addon +``` + + +## Project Generation Options + +These are all the template options that will be prompted by the [Cookiecutter CLI](https://github.com/cookiecutter/cookiecutter) before generating your project. + +| Option | Description | Example | +| --------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------- | +| `frontend_addon_name` | Your addon's short name. | **volto-weather-block** | +| `title` | Your addon's human-readable name, capitals and spaces allowed. | **Weather Block for Volto** | +| `description` | Describes your add-on and gets used in places like ``README.md`` and such. | **Add a weather block to your site.** | +| `author` | This is you! The value goes into places like ``LICENSE``, ``package.json`` and such. | **Our Company** | +| `email` | The email address you want to identify yourself in the project. | **email@example.com** | +| `github_organization` | Used for GitHub repositories. | **collective** | +| `npm_package_name` | Name of the Node package, including the organization (if any). | **@plone-collective/volto-weather-block** | + + +## Code Quality Assurance 🧐 + +Your package comes equipped with linters to ensure code quality. Run the following to automatically format your code: + +```shell +make format +``` + +## Internationalization 🌐 + +Generate translation files with ease: + +```shell +make i18n +``` +## License πŸ“œ + +This project is licensed under the [MIT License](/LICENSE). + +## Let's Get Building! πŸš€ + +Happy coding! diff --git a/frontend_addon/cookiecutter.json b/frontend_addon/cookiecutter.json new file mode 100644 index 0000000..61e6e54 --- /dev/null +++ b/frontend_addon/cookiecutter.json @@ -0,0 +1,42 @@ +{ + "title": "Volto Add-on", + "frontend_addon_name": "volto-addon", + "description": "A new add-on for Volto", + "author": "Plone Community", + "email": "collective@plone.org", + "github_organization": "collective", + "npm_package_name": "{{ cookiecutter.frontend_addon_name }}", + "__folder_name": "{{ cookiecutter.frontend_addon_name }}", + "__gha_enable": true, + "__version_package": "1.0.0-alpha.0", + "__version_plone_volto": "18.0.0-alpha.25", + "__version_plone_components": "1.7.0", + "__version_plone_scripts": "^3.6.1", + "__version_mrs_developer": "^2.2.0", + "__version_pnpm": "8.15.4", + "__version_pnpm_major": "8", + "__version_release_it": "^17.1.1", + "__gha_version_node": "20.x", + "__gha_version_checkout": "v4", + "__gha_version_setup_node": "v4", + "__gha_version_pnpm_action_setup": "v3", + "__gha_version_cache": "v4", + "__gha_version_background_action": "v1", + "__gha_version_upload_artifact": "v4", + "__gha_version_pages_deploy": "v4", + "__generator_date_short": "{% now 'utc', '%Y-%m-%d' %}", + "__generator_date_long": "{% now 'utc', '%Y-%m-%d %H:%M:%S' %}", + "__generator_signature": "This was generated by [cookiecutter-volto](https://github.com/plone/cookiecutter-volto/frontend_addon) on {{ cookiecutter.__generator_date_long }}", + "__prompts__": { + "frontend_addon_name": "Add-on (Short name of the addon)", + "title": "Add-on Title", + "description": "A short description of your addon", + "github_organization": "GitHub Username or Organization", + "npm_package_name": "Package name on NPM", + "author": "Author", + "email": "Author E-mail" + }, + "_copy_without_render": [], + "_extensions": [ + ] +} diff --git a/frontend_addon/hooks/post_gen_project.py b/frontend_addon/hooks/post_gen_project.py new file mode 100644 index 0000000..d56daef --- /dev/null +++ b/frontend_addon/hooks/post_gen_project.py @@ -0,0 +1,30 @@ +"""Post generation hook.""" + +from cookieplone.utils import console + + +def main(): + """Final fixes.""" + msg = """ + [bold blue]{{ cookiecutter.frontend_addon_name }}[/bold blue] + + Now, enter the generated directory and finish the install: + + cd {{ cookiecutter.frontend_addon_name }} + make install + + start coding, and push to your organization. + + Sorry for the convenience, + The Plone Community. + """ + console.panel( + title=":tada: New addon was generated :tada:", + subtitle="", + msg=msg, + url="https://plone.org/", + ) + + +if __name__ == "__main__": + main() diff --git a/frontend_addon/hooks/pre_gen_project.py b/frontend_addon/hooks/pre_gen_project.py new file mode 100644 index 0000000..4597964 --- /dev/null +++ b/frontend_addon/hooks/pre_gen_project.py @@ -0,0 +1,71 @@ +"""Pre generation hook.""" + +import sys +from pathlib import Path +from textwrap import dedent + +from cookieplone import data +from cookieplone.utils import console, validators + +output_path = Path().resolve() + +context = { + "frontend_addon_name": "{{ cookiecutter.frontend_addon_name }}", + "title": "{{ cookiecutter.title }}", + "description": "{{ cookiecutter.description }}", + "author": "{{ cookiecutter.author }}", + "email": "{{ cookiecutter.email }}", + "github_organization": "{{ cookiecutter.github_organization }}", + "npm_package_name": "{{ cookiecutter.npm_package_name }}", +} + + +def check_errors(context: dict) -> data.ContextValidatorResult: + """Check for errors in the provided data.""" + validations = [ + data.ItemValidator( + "frontend_addon_name", validators.validate_volto_addon_name + ), + data.ItemValidator("npm_package_name", validators.validate_npm_package_name), + ] + result = validators.run_context_validations(context, validations) + return result + + +def main(): + """Validate context.""" + validation_result = check_errors(context) + success = validation_result.status + if not success: + msg = dedent( + """ + [bold red]Error[/bold red] + It will not be possible to generate the addon. + + Please review the errors: + """ + ) + for validation in validation_result.validations: + if validation.status: + continue + label = "red" + msg = ( + f"{msg}\n - {validation.key}: [{label}]{validation.message}[/{label}]" + ) + else: + msg = dedent( + f""" + Summary: + + - Volto version: [bold blue]{{ cookiecutter.__version_plone_volto }}[/bold blue] + - Output folder: [bold blue]{output_path}[/bold blue] + + """ + ) + console.panel(title="{{ cookiecutter.title }} generation", msg=msg) + if not success: + sys.exit(1) + + +if __name__ == "__main__": + main() diff --git a/frontend_addon/hooks/pre_prompt.py b/frontend_addon/hooks/pre_prompt.py new file mode 100644 index 0000000..01da2f4 --- /dev/null +++ b/frontend_addon/hooks/pre_prompt.py @@ -0,0 +1,63 @@ +"""Pre Prompt hook.""" + +import sys +from textwrap import dedent + +try: + from cookieplone import data + from cookieplone.utils import commands, console, sanity + + HAS_COOKIEPLONE = True +except ModuleNotFoundError: + HAS_COOKIEPLONE = False + + +SUPPORTED_NODE_VERSIONS = [ + "18", + "19", + "20", + "21", + "22", +] + + +def sanity_check() -> data.SanityCheckResults: + """Run sanity checks on the system.""" + checks = [ + data.SanityCheck( + "Node", + commands.check_node_version, + [SUPPORTED_NODE_VERSIONS], + "error", + ), + data.SanityCheck("git", commands.check_command_is_available, ["git"], "error"), + ] + result = sanity.run_sanity_checks(checks) + return result + + +def main(): + """Validate context.""" + if not HAS_COOKIEPLONE: + print("This template should be run with cookieplone") + sys.exit(1) + + check_results = sanity_check() + msg = dedent( + """ + Creating a new Volto Addon + + Sanity check results: + + """ + ) + for check in check_results.checks: + label = "green" if check.status else "red" + msg = f"{msg}\n - {check.name}: [{label}]{check.message}[/{label}]" + console.panel(title="Volto Addon Generator", msg=f"{msg}\n") + if not check_results.status: + sys.exit(1) + + +if __name__ == "__main__": + main() diff --git a/frontend_addon/tests/__init__.py b/frontend_addon/tests/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/frontend_addon/tests/conftest.py b/frontend_addon/tests/conftest.py new file mode 100644 index 0000000..d32aeb4 --- /dev/null +++ b/frontend_addon/tests/conftest.py @@ -0,0 +1,64 @@ +"""Pytest configuration.""" + +import re +from copy import deepcopy +from pathlib import Path +from typing import List + +import pytest + + +@pytest.fixture(scope="session") +def variable_pattern(): + return re.compile("{{( ?cookiecutter)[.](.*?)}}") + + +@pytest.fixture(scope="session") +def context() -> dict: + """Cookiecutter context.""" + return { + "frontend_addon_name": "volto-addon", + "title": "Volto Add-on", + "description": "Add new features to your Volto Project.", + "github_organization": "collective", + "npm_package_name": "@plone-collective/volto-addon", + "author": "Plone Collective", + "email": "collective@plone.org", + } + + +@pytest.fixture(scope="session") +def context_no_npm_organization(context) -> dict: + """Cookiecutter context without a NPM organization.""" + new_context = deepcopy(context) + new_context["npm_package_name"] = "volto-addon" + return new_context + + +@pytest.fixture(scope="session") +def bad_context() -> dict: + """Cookiecutter context with invalid data.""" + return { + "frontend_addon_name": "volto addon", + "title": "Volto Add-on", + "description": "Add new features to your Volto Project.", + "github_organization": "collective", + "npm_package_name": "plone-collective/volto-addon", + "author": "Plone Collective", + "email": "collective@plone.org", + } + + +@pytest.fixture +def build_files_list(): + def func(root_dir: Path) -> List[Path]: + """Build a list containing absolute paths to the generated files.""" + return [path for path in Path(root_dir).glob("*") if path.is_file()] + + return func + + +@pytest.fixture(scope="session") +def cutter_result(cookies_session, context): + """Cookiecutter result.""" + return cookies_session.bake(extra_context=context) diff --git a/frontend_addon/tests/test_cutter.py b/frontend_addon/tests/test_cutter.py new file mode 100644 index 0000000..22cc7a2 --- /dev/null +++ b/frontend_addon/tests/test_cutter.py @@ -0,0 +1,119 @@ +"""Test cookiecutter generation with all features enabled.""" + +import pytest + +GITHUB_ACTIONS = [ + ".github/workflows/acceptance.yml", + ".github/workflows/changelog.yml", + ".github/workflows/code.yml", + ".github/workflows/i18n.yml", + ".github/workflows/storybook.yml", + ".github/workflows/unit.yml", +] + + +ROOT_FILES = GITHUB_ACTIONS + [ + ".storybook/main.js", + ".storybook/preview.jsx", + "cypress/support/commands.js", + "cypress/support/e2e.js", + "cypress/tests/.gitkeep", + "cypress/tests/example.cy.js", + "cypress/.gitkeep", + ".eslintrc.js", + ".gitignore", + ".npmignore", + ".npmrc", + ".prettierignore", + ".prettierrc", + ".stylelintrc", + "cypress.config.js", + "jest-addon.config.js", + "Makefile", + "mrs.developer.json", + "package.json", + "pnpm-workspace.yaml", + "README.md", + "volto.config.js", +] + + +PKG_SRC_FILES = [ + ".gitignore", + ".release-it.json", + "babel.config.js", + "CHANGELOG.md", + "locales/de/LC_MESSAGES/volto.po", + "locales/en/LC_MESSAGES/volto.po", + "locales/es/LC_MESSAGES/volto.po", + "locales/pt_BR/LC_MESSAGES/volto.po", + "locales/volto.pot", + "news/.gitkeep", + "package.json", + "src/components/.gitkeep", + "src/index.js", + "towncrier.toml", + "tsconfig.json", +] + + +def test_creation(cookies, context: dict): + """Generated project should match provided value.""" + result = cookies.bake(extra_context=context) + assert result.exception is None + assert result.exit_code == 0 + assert result.project_path.name == context["frontend_addon_name"] + assert result.project_path.is_dir() + + +def test_variable_substitution(build_files_list, variable_pattern, cutter_result): + """Check if no file was unprocessed.""" + paths = build_files_list(cutter_result.project_path) + for path in paths: + for line in open(path): + match = variable_pattern.search(line) + msg = f"cookiecutter variable not replaced in {path}" + assert match is None, msg + + +@pytest.mark.parametrize( + "file_path", + ROOT_FILES, +) +def test_root_files_generated(cutter_result, file_path): + """Check if root files were generated.""" + path = cutter_result.project_path / file_path + assert path.exists() + assert path.is_file() + + +@pytest.mark.parametrize("file_path", PKG_SRC_FILES) +def test_pkg_src_files_generated(cutter_result, file_path: str): + """Check if package files were generated.""" + package_name = cutter_result.context["frontend_addon_name"] + file_path = file_path.format(package_name=package_name) + src_path = cutter_result.project_path / "packages" / package_name + path = src_path / file_path + assert path.exists() + assert path.is_file() + + +@pytest.mark.parametrize( + "file_path,schema_name", + [ + [".github/workflows/acceptance.yml", "github-workflow"], + [".github/workflows/changelog.yml", "github-workflow"], + [".github/workflows/code.yml", "github-workflow"], + [".github/workflows/i18n.yml", "github-workflow"], + [".github/workflows/storybook.yml", "github-workflow"], + [".github/workflows/unit.yml", "github-workflow"], + ["package.json", "package"], + ["packages/volto-addon/package.json", "package"], + ["packages/volto-addon/tsconfig.json", "tsconfig"], + ], +) +def test_json_schema( + cutter_result, schema_validate_file, file_path: str, schema_name: str +): + path = cutter_result.project_path / file_path + assert schema_validate_file(path, schema_name) diff --git a/frontend_addon/{{ cookiecutter.__folder_name }}/.eslintrc.js b/frontend_addon/{{ cookiecutter.__folder_name }}/.eslintrc.js new file mode 100644 index 0000000..cb3ce41 --- /dev/null +++ b/frontend_addon/{{ cookiecutter.__folder_name }}/.eslintrc.js @@ -0,0 +1,22 @@ +module.exports = { + extends: './core/packages/volto/.eslintrc', + rules: { + 'import/no-unresolved': 1, + }, + settings: { + 'import/resolver': { + alias: { + map: [ + ['@plone/volto', './core/packages/volto/src'], + ['@plone/volto-slate', './core/packages/volto-slate/src'], + ['@plone/registry', './core/packages/registry/src'], + [ + '{{ cookiecutter.npm_package_name }}', + './packages/{{ cookiecutter.frontend_addon_name }}/src', + ], + ], + extensions: ['.js', '.jsx', '.ts', '.tsx', '.json'], + }, + }, + }, +}; diff --git a/frontend_addon/{{ cookiecutter.__folder_name }}/.github/workflows/acceptance.yml b/frontend_addon/{{ cookiecutter.__folder_name }}/.github/workflows/acceptance.yml new file mode 100644 index 0000000..639203a --- /dev/null +++ b/frontend_addon/{{ cookiecutter.__folder_name }}/.github/workflows/acceptance.yml @@ -0,0 +1,106 @@ +name: Acceptance tests +on: + push: + paths: + - "*.js" + - "*.json" + - "*.yaml" + - "cypress/**" + - "packages/**" + +env: + NODE_VERSION: {{ cookiecutter.__gha_version_node }} + CYPRESS_RETRIES: 2 + +jobs: + + acceptance: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@{{ cookiecutter.__gha_version_checkout }} + + - name: Use Node.js + uses: actions/setup-node@{{ cookiecutter.__gha_version_setup_node }} + with: + node-version: {{ "${{ env.NODE_VERSION }}" }} + + - uses: pnpm/action-setup@{{ cookiecutter.__gha_version_pnpm_action_setup }} + name: Install pnpm + with: + version: {{ cookiecutter.__version_pnpm_major }} + # We don't want to install until later, + # when the cache and Cypress are in place + run_install: false + + - name: Get pnpm store directory + shell: bash + run: | + echo "STORE_PATH=$(pnpm store path --silent)" >> $GITHUB_ENV + + - uses: actions/cache@{{ cookiecutter.__gha_version_cache }} + name: Setup pnpm cache + with: + path: {{ "${{ env.STORE_PATH }}" }} + key: {{ "${{ runner.os }}" }}-pnpm-store-{{ "${{ hashFiles('**/pnpm-lock.yaml') }}" }} + restore-keys: | + {{ "${{ runner.os }}" }}-pnpm-store- + + - name: Cache Cypress Binary + id: cache-cypress-binary + uses: actions/cache@{{ cookiecutter.__gha_version_cache }} + with: + path: ~/.cache/Cypress + key: binary-{{ "${{ env.NODE_VERSION }}" }}-{{ "${{ hashFiles('pnpm-lock.yaml') }}" }} + + - name: Install dependencies + run: make install + + - name: Install Cypress if not in cache + if: steps.cache-cypress-binary.outputs.cache-hit != 'true' + working-directory: core/packages/volto + run: make cypress-install + + - uses: JarvusInnovations/background-action@{{ cookiecutter.__gha_version_background_action }} + name: Start Servers + with: + run: | + make start-test-acceptance-server-ci & + make start-test-acceptance-frontend & + # your step-level and job-level environment variables are available to your commands as-is + # npm install will count towards the wait-for timeout + # whenever possible, move unrelated scripts to a different step + # to background multiple processes: add & to the end of the command + + wait-on: | + http-get://localhost:55001/plone + http://localhost:3000 + # IMPORTANT: to use environment variables in wait-on, you must use this form: {{ "${{ env.VAR }}" }} + # See wait-on section below for all resource types and prefixes + + tail: true # true = stderr,stdout + # This will allow you to monitor the progress live + + log-output-resume: stderr + # Eliminates previosuly output stderr log entries from post-run output + + wait-for: 10m + + log-output: stderr,stdout # same as true + + log-output-if: failure + + - run: make test-acceptance-headless + + # Upload Cypress screenshots + - uses: actions/upload-artifact@{{ cookiecutter.__gha_version_upload_artifact }} + if: failure() + with: + name: cypress-screenshots-acceptance + path: acceptance/cypress/screenshots + + # Upload Cypress videos + - uses: actions/upload-artifact@{{ cookiecutter.__gha_version_upload_artifact }} + if: failure() + with: + name: cypress-videos-acceptance + path: acceptance/cypress/videos diff --git a/frontend_addon/{{ cookiecutter.__folder_name }}/.github/workflows/changelog.yml b/frontend_addon/{{ cookiecutter.__folder_name }}/.github/workflows/changelog.yml new file mode 100644 index 0000000..dbf9be0 --- /dev/null +++ b/frontend_addon/{{ cookiecutter.__folder_name }}/.github/workflows/changelog.yml @@ -0,0 +1,63 @@ +name: Changelog check +on: + pull_request: + types: [assigned, opened, synchronize, reopened, labeled, unlabeled] + branches: + - main + +env: + NODE_VERSION: {{ cookiecutter.__gha_version_node }} + ADDON_NAME: {{ cookiecutter.frontend_addon_name }} + +jobs: + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@{{ cookiecutter.__gha_version_checkout }} + with: + # Fetch all history + fetch-depth: '0' + + - name: Install pipx + run: pip install towncrier + + - name: Use Node.js + uses: actions/setup-node@{{ cookiecutter.__gha_version_setup_node }} + with: + node-version: {{ "${{ env.NODE_VERSION }}" }} + + - uses: pnpm/action-setup@{{ cookiecutter.__gha_version_pnpm_action_setup }} + name: Install pnpm + with: + version: {{ cookiecutter.__version_pnpm_major }} + # We don't want to install until later, + # when the cache and Cypress are in place + run_install: false + + - name: Get pnpm store directory + shell: bash + run: | + echo "STORE_PATH=$(pnpm store path --silent)" >> $GITHUB_ENV + + - uses: actions/cache@{{ cookiecutter.__gha_version_cache }} + name: Setup pnpm cache + with: + path: {{ "${{ env.STORE_PATH }}" }} + key: {{ "${{ runner.os }}" }}-pnpm-store-{{ "${{ hashFiles('**/pnpm-lock.yaml') }}" }} + restore-keys: | + {{ "${{ runner.os }}" }}-pnpm-store- + + - name: Install dependencies + run: | + make install + + - name: Check for presence of a Change Log fragment (only pull requests) + run: | + # Fetch the pull request' base branch so towncrier will be able to + # compare the current branch with the base branch. + # Source: https://github.com/actions/checkout/#fetch-all-branches. + git fetch --no-tags origin ${BASE_BRANCH} + towncrier check --dir packages/${ADDON_NAME} + env: + BASE_BRANCH: {{ "${{ github.base_ref }}" }} + if: github.event_name == 'pull_request' diff --git a/frontend_addon/{{ cookiecutter.__folder_name }}/.github/workflows/code.yml b/frontend_addon/{{ cookiecutter.__folder_name }}/.github/workflows/code.yml new file mode 100644 index 0000000..a299258 --- /dev/null +++ b/frontend_addon/{{ cookiecutter.__folder_name }}/.github/workflows/code.yml @@ -0,0 +1,51 @@ +name: Code analysis checks +on: + push: + paths: + - "*.js" + - "*.json" + - "*.yaml" + - "packages/**" + +env: + NODE_VERSION: {{ cookiecutter.__gha_version_node }} + +jobs: + codeanalysis: + runs-on: ubuntu-latest + + steps: + - name: Main checkout + uses: actions/checkout@{{ cookiecutter.__gha_version_checkout }} + + - name: Use Node.js + uses: actions/setup-node@{{ cookiecutter.__gha_version_setup_node }} + with: + node-version: {{ "${{ env.NODE_VERSION }}" }} + + - uses: pnpm/action-setup@{{ cookiecutter.__gha_version_pnpm_action_setup }} + name: Install pnpm + with: + version: {{ cookiecutter.__version_pnpm_major }} + # We don't want to install until later, + # when the cache is in place + run_install: false + + - name: Get pnpm store directory + shell: bash + run: | + echo "STORE_PATH=$(pnpm store path --silent)" >> $GITHUB_ENV + + - uses: actions/cache@{{ cookiecutter.__gha_version_cache }} + name: Setup pnpm cache + with: + path: {{ "${{ env.STORE_PATH }}" }} + key: {{ "${{ runner.os }}" }}-pnpm-store-{{ "${{ hashFiles('**/pnpm-lock.yaml') }}" }} + restore-keys: | + {{ "${{ runner.os }}" }}-pnpm-store- + + - name: Install dependencies + run: make install + + - name: Linting + run: make lint diff --git a/frontend_addon/{{ cookiecutter.__folder_name }}/.github/workflows/i18n.yml b/frontend_addon/{{ cookiecutter.__folder_name }}/.github/workflows/i18n.yml new file mode 100644 index 0000000..5a1d484 --- /dev/null +++ b/frontend_addon/{{ cookiecutter.__folder_name }}/.github/workflows/i18n.yml @@ -0,0 +1,51 @@ +name: i18n +on: + push: + paths: + - "*.js" + - "*.json" + - "*.yaml" + - "packages/**" + +env: + NODE_VERSION: {{ cookiecutter.__gha_version_node }} + +jobs: + unit: + runs-on: ubuntu-latest + + steps: + - name: Main checkout + uses: actions/checkout@{{ cookiecutter.__gha_version_checkout }} + + - name: Use Node.js + uses: actions/setup-node@{{ cookiecutter.__gha_version_setup_node }} + with: + node-version: {{ "${{ env.NODE_VERSION }}" }} + + - uses: pnpm/action-setup@{{ cookiecutter.__gha_version_pnpm_action_setup }} + name: Install pnpm + with: + version: {{ cookiecutter.__version_pnpm_major }} + # We don't want to install until later, + # when the cache and Cypress are in place + run_install: false + + - name: Get pnpm store directory + shell: bash + run: | + echo "STORE_PATH=$(pnpm store path --silent)" >> $GITHUB_ENV + + - uses: actions/cache@{{ cookiecutter.__gha_version_cache }} + name: Setup pnpm cache + with: + path: {{ "${{ env.STORE_PATH }}" }} + key: {{ "${{ runner.os }}" }}-pnpm-store-{{ "${{ hashFiles('**/pnpm-lock.yaml') }}" }} + restore-keys: | + {{ "${{ runner.os }}" }}-pnpm-store- + + - name: Install dependencies + run: make install + + - name: test i18n command + run: make i18n diff --git a/frontend_addon/{{ cookiecutter.__folder_name }}/.github/workflows/storybook.yml b/frontend_addon/{{ cookiecutter.__folder_name }}/.github/workflows/storybook.yml new file mode 100644 index 0000000..9d14955 --- /dev/null +++ b/frontend_addon/{{ cookiecutter.__folder_name }}/.github/workflows/storybook.yml @@ -0,0 +1,62 @@ +name: Storybook +on: + push: + paths: + - "*.js" + - "*.json" + - "*.yaml" + - "packages/**" + - ".github/workflows/storybook.yml" + +env: + NODE_VERSION: {{ cookiecutter.__gha_version_node }} + +permissions: + contents: write + +jobs: + build-and-deploy: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@{{ cookiecutter.__gha_version_checkout }} + + - name: Use Node.js + uses: actions/setup-node@{{ cookiecutter.__gha_version_setup_node }} + with: + node-version: {{ "${{ env.NODE_VERSION }}" }} + + - uses: pnpm/action-setup@{{ cookiecutter.__gha_version_pnpm_action_setup }} + name: Install pnpm + with: + version: {{ cookiecutter.__version_pnpm_major }} + # We don't want to install until later, + # when the cache and Cypress are in place + run_install: false + + - name: Get pnpm store directory + shell: bash + run: | + echo "STORE_PATH=$(pnpm store path --silent)" >> $GITHUB_ENV + + - uses: actions/cache@{{ cookiecutter.__gha_version_cache }} + name: Setup pnpm cache + with: + path: {{ "${{ env.STORE_PATH }}" }} + key: {{ "${{ runner.os }}" }}-pnpm-store-{{ "${{ hashFiles('**/pnpm-lock.yaml') }}" }} + restore-keys: | + {{ "${{ runner.os }}" }}-pnpm-store- + + - name: Install dependencies + run: make install + + - name: Generate Storybook + run: | + make storybook-build + + - name: Deploy to GitHub pages + uses: JamesIves/github-pages-deploy-action@{{ cookiecutter.__gha_version_pages_deploy }} + if: {{ "${{ github.ref == 'refs/heads/main' }}" }} + with: + branch: gh-pages + folder: .storybook-build diff --git a/frontend_addon/{{ cookiecutter.__folder_name }}/.github/workflows/unit.yml b/frontend_addon/{{ cookiecutter.__folder_name }}/.github/workflows/unit.yml new file mode 100644 index 0000000..1252769 --- /dev/null +++ b/frontend_addon/{{ cookiecutter.__folder_name }}/.github/workflows/unit.yml @@ -0,0 +1,51 @@ +name: Unit Tests +on: + push: + paths: + - "*.js" + - "*.json" + - "*.yaml" + - "packages/**" + +env: + NODE_VERSION: {{ cookiecutter.__gha_version_node }} + +jobs: + unit: + runs-on: ubuntu-latest + + steps: + - name: Main checkout + uses: actions/checkout@{{ cookiecutter.__gha_version_checkout }} + + - name: Use Node.js + uses: actions/setup-node@{{ cookiecutter.__gha_version_setup_node }} + with: + node-version: {{ "${{ env.NODE_VERSION }}" }} + + - uses: pnpm/action-setup@{{ cookiecutter.__gha_version_pnpm_action_setup }} + name: Install pnpm + with: + version: {{ cookiecutter.__version_pnpm_major }} + # We don't want to install until later, + # when the cache and Cypress are in place + run_install: false + + - name: Get pnpm store directory + shell: bash + run: | + echo "STORE_PATH=$(pnpm store path --silent)" >> $GITHUB_ENV + + - uses: actions/cache@{{ cookiecutter.__gha_version_cache }} + name: Setup pnpm cache + with: + path: {{ "${{ env.STORE_PATH }}" }} + key: {{ "${{ runner.os }}" }}-pnpm-store-{{ "${{ hashFiles('**/pnpm-lock.yaml') }}" }} + restore-keys: | + {{ "${{ runner.os }}" }}-pnpm-store- + + - name: Install dependencies + run: make install + + - name: Unit tests + run: make test-ci diff --git a/frontend_addon/{{ cookiecutter.__folder_name }}/.gitignore b/frontend_addon/{{ cookiecutter.__folder_name }}/.gitignore new file mode 100644 index 0000000..0e623ce --- /dev/null +++ b/frontend_addon/{{ cookiecutter.__folder_name }}/.gitignore @@ -0,0 +1,12 @@ +.*project +.settings/ +.vscode +*~ +acceptance/cypress/videos/ +acceptance/node_modules +.storybook-build +build +core +node_modules +results +yarn.lock diff --git a/frontend_addon/{{ cookiecutter.__folder_name }}/.npmignore b/frontend_addon/{{ cookiecutter.__folder_name }}/.npmignore new file mode 100644 index 0000000..eb6fa94 --- /dev/null +++ b/frontend_addon/{{ cookiecutter.__folder_name }}/.npmignore @@ -0,0 +1,16 @@ +.vscode/ +.history +logs +*.log +npm-debug.log* +.DS_Store +*.swp +yarn-error.log + +node_modules +dockerfiles +acceptance +build +dist +yarn.lock +.storybook diff --git a/frontend_addon/{{ cookiecutter.__folder_name }}/.npmrc b/frontend_addon/{{ cookiecutter.__folder_name }}/.npmrc new file mode 100644 index 0000000..71c6843 --- /dev/null +++ b/frontend_addon/{{ cookiecutter.__folder_name }}/.npmrc @@ -0,0 +1,6 @@ +public-hoist-pattern[]=*eslint* +public-hoist-pattern[]=*prettier* +public-hoist-pattern[]=*stylelint* +public-hoist-pattern[]=*cypress* +public-hoist-pattern[]=*process* +public-hoist-pattern[]=*parcel* diff --git a/frontend_addon/{{ cookiecutter.__folder_name }}/.prettierignore b/frontend_addon/{{ cookiecutter.__folder_name }}/.prettierignore new file mode 100644 index 0000000..16f05c7 --- /dev/null +++ b/frontend_addon/{{ cookiecutter.__folder_name }}/.prettierignore @@ -0,0 +1,3 @@ +.storybook +CHANGELOG.md +README.md diff --git a/frontend_addon/{{ cookiecutter.__folder_name }}/.prettierrc b/frontend_addon/{{ cookiecutter.__folder_name }}/.prettierrc new file mode 100644 index 0000000..c56f626 --- /dev/null +++ b/frontend_addon/{{ cookiecutter.__folder_name }}/.prettierrc @@ -0,0 +1,12 @@ +{ + "trailingComma": "all", + "singleQuote": true, + "overrides": [ + { + "files": "*.overrides", + "options": { + "parser": "less" + } + } + ] + } diff --git a/frontend_addon/{{ cookiecutter.__folder_name }}/.storybook/main.js b/frontend_addon/{{ cookiecutter.__folder_name }}/.storybook/main.js new file mode 100644 index 0000000..1b15d3b --- /dev/null +++ b/frontend_addon/{{ cookiecutter.__folder_name }}/.storybook/main.js @@ -0,0 +1,188 @@ +const webpack = require('webpack'); +const fs = require('fs'); +const path = require('path'); + +const projectRootPath = path.resolve('.'); +const lessPlugin = require('@plone/volto/webpack-plugins/webpack-less-plugin'); +const scssPlugin = require('razzle-plugin-scss'); + +const createConfig = require('razzle/config/createConfigAsync.js'); +const razzleConfig = require(path.join(projectRootPath, 'razzle.config.js')); + +const SVGLOADER = { + test: /icons\/.*\.svg$/, + use: [ + { + loader: 'svg-loader', + }, + { + loader: 'svgo-loader', + options: { + plugins: [ + { + name: 'preset-default', + params: { + overrides: { + convertPathData: false, + removeViewBox: false, + }, + }, + }, + 'removeTitle', + 'removeUselessStrokeAndFill', + ], + }, + }, + ], +}; + +const defaultRazzleOptions = { + verbose: false, + debug: {}, + buildType: 'iso', + cssPrefix: 'static/css', + jsPrefix: 'static/js', + enableSourceMaps: true, + enableReactRefresh: true, + enableTargetBabelrc: false, + enableBabelCache: true, + forceRuntimeEnvVars: [], + mediaPrefix: 'static/media', + staticCssInDev: false, + emitOnErrors: false, + disableWebpackbar: false, + browserslist: [ + '>1%', + 'last 4 versions', + 'Firefox ESR', + 'not ie 11', + 'not dead', + ], +}; + +module.exports = { + stories: [ + '../packages/**/*.mdx', + '../packages/**/*.stories.@(js|jsx|ts|tsx)', + ], + addons: [ + '@storybook/addon-links', + '@storybook/addon-essentials', + '@storybook/addon-webpack5-compiler-babel', + ], + framework: { + name: '@storybook/react-webpack5', + options: { builder: { useSWC: true } }, + }, + typescript: { + check: false, + checkOptions: {}, + reactDocgen: 'react-docgen-typescript', + reactDocgenTypescriptOptions: { + compilerOptions: { + allowSyntheticDefaultImports: false, + esModuleInterop: false, + }, + propFilter: () => true, + }, + }, + webpackFinal: async (config, { configType }) => { + // `configType` has a value of 'DEVELOPMENT' or 'PRODUCTION' + // You can change the configuration based on that. + // 'PRODUCTION' is used when building the static version of storybook. + + // Make whatever fine-grained changes you need + let baseConfig; + baseConfig = await createConfig( + 'web', + 'dev', + { + // clearConsole: false, + modifyWebpackConfig: razzleConfig.modifyWebpackConfig, + plugins: razzleConfig.plugins, + }, + webpack, + false, + undefined, + [], + defaultRazzleOptions, + ); + const AddonConfigurationRegistry = require('@plone/registry/src/addon-registry'); + + const registry = new AddonConfigurationRegistry(projectRootPath); + + config = lessPlugin({ registry }).modifyWebpackConfig({ + env: { target: 'web', dev: 'dev' }, + webpackConfig: config, + webpackObject: webpack, + options: {}, + }); + + config = scssPlugin.modifyWebpackConfig({ + env: { target: 'web', dev: 'dev' }, + webpackConfig: config, + webpackObject: webpack, + options: { razzleOptions: {} }, + }); + + // Put the SVG loader on top and prevent the asset/resource rule + // from processing the app's SVGs + config.module.rules.unshift(SVGLOADER); + const fileLoaderRule = config.module.rules.find((rule) => + rule.test.test('.svg'), + ); + fileLoaderRule.exclude = /icons\/.*\.svg$/; + + config.plugins.unshift( + new webpack.DefinePlugin({ + __DEVELOPMENT__: true, + __CLIENT__: true, + __SERVER__: false, + }), + ); + + const resultConfig = { + ...config, + resolve: { + ...config.resolve, + alias: { ...config.resolve.alias, ...baseConfig.resolve.alias }, + fallback: { ...config.resolve.fallback, zlib: false }, + }, + }; + + // Add-ons have to be loaded with babel + const addonPaths = registry + .getAddons() + .map((addon) => fs.realpathSync(addon.modulePath)); + + resultConfig.module.rules[13].exclude = (input) => + // exclude every input from node_modules except from @plone/volto + /node_modules\/(?!(@plone\/volto)\/)/.test(input) && + // Storybook default exclusions + /storybook-config-entry\.js$/.test(input) && + /storybook-stories\.js$/.test(input) && + // If input is in an addon, DON'T exclude it + !addonPaths.some((p) => input.includes(p)); + + resultConfig.module.rules[13].include = [ + /preview\.jsx/, + ...resultConfig.module.rules[13].include, + ...addonPaths, + ]; + + const addonExtenders = registry.getAddonExtenders().map((m) => require(m)); + + const extendedConfig = addonExtenders.reduce( + (acc, extender) => + extender.modify(acc, { target: 'web', dev: 'dev' }, config), + resultConfig, + ); + + // Note: we don't actually support razzle plugins, which are also a feature + // of the razzle.extend.js addons file. Those features are probably + // provided in a different manner by Storybook plugins (for example scss + // loaders). + + return extendedConfig; + }, +}; diff --git a/frontend_addon/{{ cookiecutter.__folder_name }}/.storybook/preview.jsx b/frontend_addon/{{ cookiecutter.__folder_name }}/.storybook/preview.jsx new file mode 100644 index 0000000..1d2ac84 --- /dev/null +++ b/frontend_addon/{{ cookiecutter.__folder_name }}/.storybook/preview.jsx @@ -0,0 +1,26 @@ +import '@plone/volto/config'; // This is the bootstrap for the global config - client side +import React from 'react'; +import { StaticRouter } from 'react-router-dom'; +import { IntlProvider } from 'react-intl'; +import enMessages from '@root/../locales/en.json'; + +import '@root/theme'; + +export const parameters = { + controls: { + matchers: { + color: /(background|color)$/i, + date: /Date$/, + }, + }, +}; + +export const decorators = [ + (Story) => ( + + + + + + ), +]; diff --git a/frontend_addon/{{ cookiecutter.__folder_name }}/.stylelintrc b/frontend_addon/{{ cookiecutter.__folder_name }}/.stylelintrc new file mode 100644 index 0000000..09d4a27 --- /dev/null +++ b/frontend_addon/{{ cookiecutter.__folder_name }}/.stylelintrc @@ -0,0 +1,32 @@ +{ + "extends": [ + "stylelint-config-idiomatic-order" + ], + "plugins": [ + "stylelint-prettier" + ], + "overrides": [ + { + "files": [ + "**/*.less" + ], + "customSyntax": "postcss-less" + }, + { + "files": [ + "**/*.overrides" + ], + "customSyntax": "postcss-less" + }, + { + "files": [ + "**/*.scss" + ], + "customSyntax": "postcss-scss" + } + ], + "rules": { + "prettier/prettier": true, + "order/properties-alphabetical-order": null + } +} diff --git a/frontend_addon/{{ cookiecutter.__folder_name }}/Makefile b/frontend_addon/{{ cookiecutter.__folder_name }}/Makefile new file mode 100644 index 0000000..382e02c --- /dev/null +++ b/frontend_addon/{{ cookiecutter.__folder_name }}/Makefile @@ -0,0 +1,103 @@ +### Defensive settings for make: +# https://tech.davis-hansson.com/p/make/ +SHELL:=bash +.ONESHELL: +.SHELLFLAGS:=-eu -o pipefail -c +.SILENT: +.DELETE_ON_ERROR: +MAKEFLAGS+=--warn-undefined-variables +MAKEFLAGS+=--no-builtin-rules + +CURRENT_DIR:=$(shell dirname $(realpath $(lastword $(MAKEFILE_LIST)))) + +# Recipe snippets for reuse + +# We like colors +# From: https://coderwall.com/p/izxssa/colored-makefile-for-golang-projects +RED=`tput setaf 1` +GREEN=`tput setaf 2` +RESET=`tput sgr0` +YELLOW=`tput setaf 3` + +PLONE_VERSION=6 +DOCKER_IMAGE=plone/server-dev:${PLONE_VERSION} +DOCKER_IMAGE_ACCEPTANCE=plone/server-acceptance:${PLONE_VERSION} + +ADDON_NAME='{{ cookiecutter.npm_package_name }}' + +.PHONY: help +help: ## Show this help + @echo -e "$$(grep -hE '^\S+:.*##' $(MAKEFILE_LIST) | sed -e 's/:.*##\s*/:/' -e 's/^\(.\+\):\(.*\)/\\x1b[36m\1\\x1b[m:\2/' | column -c2 -t -s :)" + +# Dev Helpers + +.PHONY: install +install: ## Install task, checks if missdev (mrs-developer) is present and runs it + pnpm dlx mrs-developer missdev --no-config --fetch-https + pnpm i + +.PHONY: i18n +i18n: ## Sync i18n + pnpm --filter $(ADDON_NAME) i18n + +.PHONY: format +format: ## Format codebase + pnpm lint:fix + pnpm prettier:fix + pnpm stylelint:fix + +.PHONY: lint +lint: ## Lint Codebase + pnpm lint + pnpm prettier + pnpm stylelint + +.PHONY: test +test: ## Run unit tests + pnpm test + +.PHONY: test-ci +test-ci: ## Run unit tests in CI + CI=1 RAZZLE_JEST_CONFIG=$(CURRENT_DIR)/jest-addon.config.js pnpm --filter @plone/volto test -- --passWithNoTests + +.PHONY: start-backend-docker +start-backend-docker: ## Starts a Docker-based backend for developing + @echo "$(GREEN)==> Start Docker-based Plone Backend$(RESET)" + docker run -it --rm --name=backend -p 8080:8080 -e SITE=Plone $(DOCKER_IMAGE) + +## Storybook +.PHONY: storybook-start +storybook-start: ## Storybook: Start server on port 6006 + @echo "$(GREEN)==> Start Storybook$(RESET)" + pnpm run storybook + +.PHONY: storybook-build +storybook-build: ## Storybook: Build + @echo "$(GREEN)==> Build Storybook$(RESET)" + mkdir -p $(CURRENT_DIR)/.storybook-build + pnpm run build-storybook -o $(CURRENT_DIR)/.storybook-build + +## Acceptance +.PHONY: start-test-acceptance-frontend-dev +start-test-acceptance-frontend-dev: ## Start acceptance frontend in dev mode + RAZZLE_API_PATH=http://127.0.0.1:55001/plone pnpm start + +.PHONY: start-test-acceptance-frontend +start-test-acceptance-frontend: ## Start acceptance frontend in prod mode + RAZZLE_API_PATH=http://127.0.0.1:55001/plone pnpm build && pnpm start:prod + +.PHONY: start-test-acceptance-server +start-test-acceptance-server: ## Start acceptance server + docker run -it --rm -p 55001:55001 $(DOCKER_IMAGE_ACCEPTANCE) + +.PHONY: start-test-acceptance-server-ci +start-test-acceptance-server-ci: ## Start acceptance server in CI mode (no terminal attached) + docker run -i --rm -p 55001:55001 $(DOCKER_IMAGE_ACCEPTANCE) + +.PHONY: test-acceptance +test-acceptance: ## Start Cypress in interactive mode + pnpm --filter @plone/volto exec cypress open --config-file $(CURRENT_DIR)/cypress.config.js --config specPattern=$(CURRENT_DIR)'/cypress/tests/**/*.{js,jsx,ts,tsx}' + +.PHONY: test-acceptance-headless +test-acceptance-headless: ## Run cypress tests in headless mode for CI + pnpm --filter @plone/volto exec cypress run --config-file $(CURRENT_DIR)/cypress.config.js --config specPattern=$(CURRENT_DIR)'/cypress/tests/**/*.{js,jsx,ts,tsx}' diff --git a/frontend_addon/{{ cookiecutter.__folder_name }}/README.md b/frontend_addon/{{ cookiecutter.__folder_name }}/README.md new file mode 100644 index 0000000..03313dd --- /dev/null +++ b/frontend_addon/{{ cookiecutter.__folder_name }}/README.md @@ -0,0 +1,193 @@ +# {{ cookiecutter.title }} ({{ cookiecutter.npm_package_name }}) + +{{ cookiecutter.description }} + +[![npm](https://img.shields.io/npm/v/{{ cookiecutter.npm_package_name }})](https://www.npmjs.com/package/{{ cookiecutter.npm_package_name }}) +[![](https://img.shields.io/badge/-Storybook-ff4785?logo=Storybook&logoColor=white&style=flat-square)](https://{{ cookiecutter.github_organization }}.github.io/{{ cookiecutter.frontend_addon_name }}/) +[![Code analysis checks](https://github.com/{{ cookiecutter.github_organization }}/{{ cookiecutter.frontend_addon_name }}/actions/workflows/code.yml/badge.svg)](https://github.com/{{ cookiecutter.github_organization }}/{{ cookiecutter.frontend_addon_name }}/actions/workflows/code.yml) +[![Unit tests](https://github.com/{{ cookiecutter.github_organization }}/{{ cookiecutter.frontend_addon_name }}/actions/workflows/unit.yml/badge.svg)](https://github.com/{{ cookiecutter.github_organization }}/{{ cookiecutter.frontend_addon_name }}/actions/workflows/unit.yml) + +## Features + + + +## Installation + +To install your project, you must choose the method appropriate to your version of Volto. + + +### Volto 17 and earlier + +Create a new Volto project (you can skip this step if you already have one): + +``` +npm install -g yo @plone/generator-volto +yo @plone/volto my-volto-project --addon {{ cookiecutter.npm_package_name }} +cd my-volto-project +``` + +Add `{{ cookiecutter.npm_package_name }}` to your package.json: + +```JSON +"addons": [ + "{{ cookiecutter.npm_package_name }}" +], + +"dependencies": { + "{{ cookiecutter.npm_package_name }}": "*" +} +``` + +Download and install the new add-on by running: + +``` +yarn install +``` + +Start volto with: + +``` +yarn start +``` + +### Volto 18 and later + +Add `{{ cookiecutter.npm_package_name }}` to your `package.json`: + +```json +"dependencies": { + "{{ cookiecutter.npm_package_name }}": "*" +} +``` + +Add `{{ cookiecutter.npm_package_name }}` to your `volto.config.js`: + +```javascript +const addons = ['{{ cookiecutter.npm_package_name }}']; +``` + +If this package provides a Volto theme, and you want to activate it, then add the following to your `volto.config.js`: + +```javascript +const theme = '{{ cookiecutter.npm_package_name }}'; +``` + +## Test installation + +Visit http://localhost:3000/ in a browser, login, and check the awesome new features. + + +## Development + +The development of this add-on is done in isolation using a new approach using pnpm workspaces and latest `mrs-developer` and other Volto core improvements. +For this reason, it only works with pnpm and Volto 18 (currently in alpha). + + +### Pre-requisites + +- [Node.js](https://6.docs.plone.org/install/create-project.html#node-js) +- [Make](https://6.docs.plone.org/install/create-project.html#make) +- [Docker](https://6.docs.plone.org/install/create-project.html#docker) + + +### Make convenience commands + +Run `make help` to list the available commands. + +```text +help Show this help +install Installs the dev environment using mrs-developer +i18n Sync i18n +format Format codebase +lint Lint Codebase +test Run unit tests +test-ci Run unit tests in CI +storybook-start Start Storybook server on port 6006 +storybook-build Build Storybook +start-backend-docker Starts a Docker-based backend for developing +start-test-acceptance-frontend-dev Start acceptance frontend in dev mode +start-test-acceptance-frontend Start acceptance frontend in prod mode +start-test-acceptance-server Start acceptance server +test-acceptance Start Cypress in interactive mode +test-acceptance-headless Run cypress tests in headless mode for CI +``` + +### Development environment set up + +Install package requirements. + +```shell +make install +``` + +### Start developing + +Start the backend. + +```shell +make start-backend-docker +``` + +In a separate terminal session, start the frontend. + +```shell +pnpm start +``` + +### Lint code + +Run ESlint, Prettier, and Stylelint in analyze mode. + +```shell +make lint +``` + +### Format code + +Run ESlint, Prettier, and Stylelint in fix mode. + +```shell +make format +``` + +### i18n + +Extract the i18n messages to locales. + +```shell +make i18n +``` + +### Unit tests + +Run unit tests. + +```shell +make test +``` + +### Run Cypress tests + +Run each of these steps in separate terminal sessions. + +In the first session, start the frontend in development mode. + +```shell +make start-test-acceptance-frontend-dev +``` + +In the second session, start the backend acceptance server. + +```shell +make start-test-acceptance-server +``` + +In the third session, start the Cypress interactive test runner. + +```shell +make test-acceptance +``` + +## License + +The project is licensed under the MIT license. diff --git a/frontend_addon/{{ cookiecutter.__folder_name }}/cypress.config.js b/frontend_addon/{{ cookiecutter.__folder_name }}/cypress.config.js new file mode 100644 index 0000000..dba4b58 --- /dev/null +++ b/frontend_addon/{{ cookiecutter.__folder_name }}/cypress.config.js @@ -0,0 +1,13 @@ +const { defineConfig } = require('cypress'); + +module.exports = defineConfig({ + viewportWidth: 1280, + viewportHeight: 1280, + retries: { + runMode: 3, + }, + e2e: { + baseUrl: 'http://localhost:3000', + specPattern: 'cypress/tests/**/*.cy.{js,jsx,ts,tsx}', + }, +}); diff --git a/frontend_addon/{{ cookiecutter.__folder_name }}/cypress/.gitkeep b/frontend_addon/{{ cookiecutter.__folder_name }}/cypress/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/frontend_addon/{{ cookiecutter.__folder_name }}/cypress/support/commands.js b/frontend_addon/{{ cookiecutter.__folder_name }}/cypress/support/commands.js new file mode 100644 index 0000000..6a44064 --- /dev/null +++ b/frontend_addon/{{ cookiecutter.__folder_name }}/cypress/support/commands.js @@ -0,0 +1 @@ +import '@plone/volto/cypress/add-commands'; diff --git a/frontend_addon/{{ cookiecutter.__folder_name }}/cypress/support/e2e.js b/frontend_addon/{{ cookiecutter.__folder_name }}/cypress/support/e2e.js new file mode 100644 index 0000000..4ff23d6 --- /dev/null +++ b/frontend_addon/{{ cookiecutter.__folder_name }}/cypress/support/e2e.js @@ -0,0 +1,15 @@ +import 'cypress-axe'; +import 'cypress-file-upload'; +import './commands'; +import 'cypress-axe'; +import { setup, teardown } from '@plone/volto/cypress/support/reset-fixture'; + +beforeEach(function () { + cy.log('Setting up API fixture'); + setup(); +}); + +afterEach(function () { + cy.log('Tearing down API fixture'); + teardown(); +}); diff --git a/frontend_addon/{{ cookiecutter.__folder_name }}/cypress/tests/.gitkeep b/frontend_addon/{{ cookiecutter.__folder_name }}/cypress/tests/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/frontend_addon/{{ cookiecutter.__folder_name }}/cypress/tests/example.cy.js b/frontend_addon/{{ cookiecutter.__folder_name }}/cypress/tests/example.cy.js new file mode 100644 index 0000000..f3a1fad --- /dev/null +++ b/frontend_addon/{{ cookiecutter.__folder_name }}/cypress/tests/example.cy.js @@ -0,0 +1,20 @@ +context('Example Acceptance Tests', () => { + describe('Visit a page', () => { + beforeEach(() => { + // Given a logged in editor + cy.viewport('macbook-16'); + cy.createContent({ + contentType: 'Document', + contentId: 'document', + contentTitle: 'Test document', + }); + cy.autologin(); + }); + + it('As editor I can add edit a Page', function () { + cy.visit('/document'); + cy.navigate('/document/edit'); + cy.get('#toolbar-save').click(); + }); + }); +}); diff --git a/frontend_addon/{{ cookiecutter.__folder_name }}/jest-addon.config.js b/frontend_addon/{{ cookiecutter.__folder_name }}/jest-addon.config.js new file mode 100644 index 0000000..984c267 --- /dev/null +++ b/frontend_addon/{{ cookiecutter.__folder_name }}/jest-addon.config.js @@ -0,0 +1,17 @@ +module.exports = { + roots: ['../../../packages'], + testMatch: ['/../../../../**/?(*.)+(spec|test).[jt]s?(x)'], + collectCoverageFrom: [ + 'src/addons/**/src/**/*.{js,jsx,ts,tsx}', + '!src/**/*.d.ts', + ], + transformIgnorePatterns: ['node_modules/(?!(volto-slate|@plone/volto)/)'], + coverageThreshold: { + global: { + branches: 5, + functions: 5, + lines: 5, + statements: 5, + }, + }, +}; diff --git a/frontend_addon/{{ cookiecutter.__folder_name }}/mrs.developer.json b/frontend_addon/{{ cookiecutter.__folder_name }}/mrs.developer.json new file mode 100644 index 0000000..c0dabd8 --- /dev/null +++ b/frontend_addon/{{ cookiecutter.__folder_name }}/mrs.developer.json @@ -0,0 +1,9 @@ +{ + "core": { + "output": "./", + "package": "@plone/volto", + "url": "git@github.com:plone/volto.git", + "https": "https://github.com/plone/volto.git", + "tag": "{{ cookiecutter.__version_plone_volto }}" + } +} diff --git a/frontend_addon/{{ cookiecutter.__folder_name }}/package.json b/frontend_addon/{{ cookiecutter.__folder_name }}/package.json new file mode 100644 index 0000000..a85d9a2 --- /dev/null +++ b/frontend_addon/{{ cookiecutter.__folder_name }}/package.json @@ -0,0 +1,44 @@ +{ + "name": "{{ cookiecutter.npm_package_name }}-dev", + "version": "{{ cookiecutter.__version_package }}", + "description": "{{ cookiecutter.description }}", + "author": "{{ cookiecutter.author }}", + "homepage": "https://github.com/{{ cookiecutter.github_organization }}/{{ cookiecutter.frontend_addon_name }}", + "license": "MIT", + "keywords": [ + "volto-addon", + "volto", + "plone", + "react" + ], + "scripts": { + "preinstall": "npx only-allow pnpm", + "start": "pnpm build:deps && VOLTOCONFIG=$(pwd)/volto.config.js pnpm --filter @plone/volto start", + "start:prod": "pnpm --filter @plone/volto start:prod", + "build": "pnpm build:deps && VOLTOCONFIG=$(pwd)/volto.config.js pnpm --filter @plone/volto build", + "build:deps": "pnpm --filter @plone/registry --filter @plone/components build", + "i18n": "pnpm --filter {{ cookiecutter.npm_package_name }} i18n", + "test": "RAZZLE_JEST_CONFIG=$(pwd)/jest-addon.config.js pnpm --filter @plone/volto test", + "lint": "eslint --max-warnings=0 'packages/**/src/**/*.{js,jsx,ts,tsx}'", + "lint:fix": "eslint --fix 'packages/**/src/**/*.{js,jsx,ts,tsx}'", + "prettier": "prettier --check 'packages/**/src/**/*.{js,jsx,ts,tsx}'", + "prettier:fix": "prettier --write 'packages/**/src/**/*.{js,jsx,ts,tsx}' ", + "stylelint": "stylelint 'packages/**/src/**/*.{css,scss,less}' --allow-empty-input", + "stylelint:fix": "stylelint 'packages/**/src/**/*.{css,scss,less}' --fix --allow-empty-input", + "dry-release": "pnpm --filter {{ cookiecutter.npm_package_name }} dry-release", + "release": "pnpm --filter {{ cookiecutter.npm_package_name }} release", + "release-major-alpha": "pnpm --filter {{ cookiecutter.npm_package_name }} release-major-alpha", + "release-alpha": "pnpm --filter {{ cookiecutter.npm_package_name }} release-alpha", + "storybook": "pnpm build:deps && VOLTOCONFIG=$(pwd)/volto.config.js pnpm --filter @plone/volto storybook dev -p 6006 -c $(pwd)/.storybook", + "build-storybook": "pnpm build:deps && VOLTOCONFIG=$(pwd)/volto.config.js pnpm --filter @plone/volto build-storybook -c $(pwd)/.storybook" + }, + "dependencies": { + "@plone/volto": "workspace:*", + "@plone/registry": "workspace:*", + "{{ cookiecutter.npm_package_name }}": "workspace:*" + }, + "devDependencies": { + "mrs-developer": "{{ cookiecutter.__version_mrs_developer }}" + }, + "packageManager": "pnpm@{{ cookiecutter.__version_pnpm }}" +} diff --git a/frontend_addon/{{ cookiecutter.__folder_name }}/packages/{{ cookiecutter.frontend_addon_name }}/.gitignore b/frontend_addon/{{ cookiecutter.__folder_name }}/packages/{{ cookiecutter.frontend_addon_name }}/.gitignore new file mode 100644 index 0000000..b462bc9 --- /dev/null +++ b/frontend_addon/{{ cookiecutter.__folder_name }}/packages/{{ cookiecutter.frontend_addon_name }}/.gitignore @@ -0,0 +1,3 @@ +node_modules +build +README.md diff --git a/frontend_addon/{{ cookiecutter.__folder_name }}/packages/{{ cookiecutter.frontend_addon_name }}/.release-it.json b/frontend_addon/{{ cookiecutter.__folder_name }}/packages/{{ cookiecutter.frontend_addon_name }}/.release-it.json new file mode 100644 index 0000000..ccd9061 --- /dev/null +++ b/frontend_addon/{{ cookiecutter.__folder_name }}/packages/{{ cookiecutter.frontend_addon_name }}/.release-it.json @@ -0,0 +1,19 @@ +{ + "hooks": { + "after:bump": "pipx run towncrier build --draft --yes --version ${version} > .changelog.draft && pipx run towncrier build --yes --version ${version} && cp ../../README.md ./", + "after:release": "rm .changelog.draft README.md" + }, + "git": { + "changelog": "pipx run towncrier build --draft --yes --version 0.0.0", + "requireUpstream": false, + "requireCleanWorkingDir": false, + "commitMessage": "Release ${version}", + "tagName": "${version}", + "tagAnnotation": "Release ${version}" + }, + "github": { + "release": true, + "releaseName": "${version}", + "releaseNotes": "cat .changelog.draft" + } +} diff --git a/frontend_addon/{{ cookiecutter.__folder_name }}/packages/{{ cookiecutter.frontend_addon_name }}/CHANGELOG.md b/frontend_addon/{{ cookiecutter.__folder_name }}/packages/{{ cookiecutter.frontend_addon_name }}/CHANGELOG.md new file mode 100644 index 0000000..e24f3a8 --- /dev/null +++ b/frontend_addon/{{ cookiecutter.__folder_name }}/packages/{{ cookiecutter.frontend_addon_name }}/CHANGELOG.md @@ -0,0 +1,9 @@ +# Changelog + + + + diff --git a/frontend_addon/{{ cookiecutter.__folder_name }}/packages/{{ cookiecutter.frontend_addon_name }}/babel.config.js b/frontend_addon/{{ cookiecutter.__folder_name }}/packages/{{ cookiecutter.frontend_addon_name }}/babel.config.js new file mode 100644 index 0000000..51bd52b --- /dev/null +++ b/frontend_addon/{{ cookiecutter.__folder_name }}/packages/{{ cookiecutter.frontend_addon_name }}/babel.config.js @@ -0,0 +1,17 @@ +module.exports = function (api) { + api.cache(true); + const presets = ['razzle']; + const plugins = [ + [ + 'react-intl', // React Intl extractor, required for the whole i18n infrastructure to work + { + messagesDir: './build/messages/', + }, + ], + ]; + + return { + plugins, + presets, + }; +}; diff --git a/frontend_addon/{{ cookiecutter.__folder_name }}/packages/{{ cookiecutter.frontend_addon_name }}/locales/de/LC_MESSAGES/volto.po b/frontend_addon/{{ cookiecutter.__folder_name }}/packages/{{ cookiecutter.frontend_addon_name }}/locales/de/LC_MESSAGES/volto.po new file mode 100644 index 0000000..796f3cf --- /dev/null +++ b/frontend_addon/{{ cookiecutter.__folder_name }}/packages/{{ cookiecutter.frontend_addon_name }}/locales/de/LC_MESSAGES/volto.po @@ -0,0 +1,12 @@ +msgid "" +msgstr "" +"Project-Id-Version: \n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: \n" +"PO-Revision-Date: \n" +"Last-Translator: \n" +"Language: de\n" +"Language-Team: \n" +"Content-Type: \n" +"Content-Transfer-Encoding: \n" +"Plural-Forms: \n" diff --git a/frontend_addon/{{ cookiecutter.__folder_name }}/packages/{{ cookiecutter.frontend_addon_name }}/locales/en/LC_MESSAGES/volto.po b/frontend_addon/{{ cookiecutter.__folder_name }}/packages/{{ cookiecutter.frontend_addon_name }}/locales/en/LC_MESSAGES/volto.po new file mode 100644 index 0000000..38c369e --- /dev/null +++ b/frontend_addon/{{ cookiecutter.__folder_name }}/packages/{{ cookiecutter.frontend_addon_name }}/locales/en/LC_MESSAGES/volto.po @@ -0,0 +1,12 @@ +msgid "" +msgstr "" +"Project-Id-Version: \n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: \n" +"PO-Revision-Date: \n" +"Last-Translator: \n" +"Language: en\n" +"Language-Team: \n" +"Content-Type: \n" +"Content-Transfer-Encoding: \n" +"Plural-Forms: \n" diff --git a/frontend_addon/{{ cookiecutter.__folder_name }}/packages/{{ cookiecutter.frontend_addon_name }}/locales/es/LC_MESSAGES/volto.po b/frontend_addon/{{ cookiecutter.__folder_name }}/packages/{{ cookiecutter.frontend_addon_name }}/locales/es/LC_MESSAGES/volto.po new file mode 100644 index 0000000..7288b05 --- /dev/null +++ b/frontend_addon/{{ cookiecutter.__folder_name }}/packages/{{ cookiecutter.frontend_addon_name }}/locales/es/LC_MESSAGES/volto.po @@ -0,0 +1,19 @@ +msgid "" +msgstr "" +"Project-Id-Version: Plone\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2023-05-09 11:45-0400\n" +"PO-Revision-Date: 2023-05-10 11:34-0400\n" +"Last-Translator: Leonardo J. Caballero G. \n" +"Language: es\n" +"Language-Team: ES \n" +"Content-Type: text/plain; charset=utf-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" +"Preferred-Encodings: utf-8\n" +"MIME-Version: 1.0\n" +"Language-Code: es\n" +"Language-Name: EspaΓ±ol\n" +"Domain: volto\n" +"X-Is-Fallback-For: es-ar es-bo es-cl es-co es-cr es-do es-ec es-es es-sv es-gt es-hn es-mx es-ni es-pa es-py es-pe es-pr es-us es-uy es-ve\n" +"X-Generator: Poedit 2.2.1\n" diff --git a/frontend_addon/{{ cookiecutter.__folder_name }}/packages/{{ cookiecutter.frontend_addon_name }}/locales/pt_BR/LC_MESSAGES/volto.po b/frontend_addon/{{ cookiecutter.__folder_name }}/packages/{{ cookiecutter.frontend_addon_name }}/locales/pt_BR/LC_MESSAGES/volto.po new file mode 100644 index 0000000..e01604d --- /dev/null +++ b/frontend_addon/{{ cookiecutter.__folder_name }}/packages/{{ cookiecutter.frontend_addon_name }}/locales/pt_BR/LC_MESSAGES/volto.po @@ -0,0 +1,17 @@ +msgid "" +msgstr "" +"Project-Id-Version: Plone\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2023-04-06T16:01:32.969Z\n" +"PO-Revision-Date: \n" +"Last-Translator: Plone i18n \n" +"Language: \n" +"Language-Team: Plone i18n \n" +"Content-Type: text/plain; charset=utf-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=1; plural=0;\n" +"MIME-Version: 1.0\n" +"Language-Code: pt_BR\n" +"Language-Name: PortuguΓͺs do Brasil\n" +"Preferred-Encodings: utf-8\n" +"Domain: volto\n" diff --git a/frontend_addon/{{ cookiecutter.__folder_name }}/packages/{{ cookiecutter.frontend_addon_name }}/locales/volto.pot b/frontend_addon/{{ cookiecutter.__folder_name }}/packages/{{ cookiecutter.frontend_addon_name }}/locales/volto.pot new file mode 100644 index 0000000..ea538b1 --- /dev/null +++ b/frontend_addon/{{ cookiecutter.__folder_name }}/packages/{{ cookiecutter.frontend_addon_name }}/locales/volto.pot @@ -0,0 +1,14 @@ +msgid "" +msgstr "" +"Project-Id-Version: Plone\n" +"POT-Creation-Date: 2024-03-22T12:43:34.158Z\n" +"Last-Translator: Plone i18n \n" +"Language-Team: Plone i18n \n" +"Content-Type: text/plain; charset=utf-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=1; plural=0;\n" +"MIME-Version: 1.0\n" +"Language-Code: en\n" +"Language-Name: English\n" +"Preferred-Encodings: utf-8\n" +"Domain: volto\n" diff --git a/frontend_addon/{{ cookiecutter.__folder_name }}/packages/{{ cookiecutter.frontend_addon_name }}/news/.gitkeep b/frontend_addon/{{ cookiecutter.__folder_name }}/packages/{{ cookiecutter.frontend_addon_name }}/news/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/frontend_addon/{{ cookiecutter.__folder_name }}/packages/{{ cookiecutter.frontend_addon_name }}/package.json b/frontend_addon/{{ cookiecutter.__folder_name }}/packages/{{ cookiecutter.frontend_addon_name }}/package.json new file mode 100644 index 0000000..11df89a --- /dev/null +++ b/frontend_addon/{{ cookiecutter.__folder_name }}/packages/{{ cookiecutter.frontend_addon_name }}/package.json @@ -0,0 +1,41 @@ +{ + "name": "{{ cookiecutter.npm_package_name }}", + "version": "{{ cookiecutter.__version_package }}", + "description": "{{ cookiecutter.description }}", + "main": "src/index.js", + "license": "MIT", + "keywords": [ + "volto-addon", + "volto", + "plone", + "react" + ], + "author": "{{ cookiecutter.author }}", + "homepage": "https://github.com/{{ cookiecutter.github_organization }}/{{ cookiecutter.frontend_addon_name }}#readme", + "repository": { + "type": "git", + "url": "git@github.com:{{ cookiecutter.github_organization }}/{{ cookiecutter.frontend_addon_name }}.git" + }, + "publishConfig": { + "access": "public" + }, + "scripts": { + "i18n": "rm -rf build/messages && NODE_ENV=production i18n --addon", + "dry-release": "release-it --dry-run", + "release": "release-it", + "release-major-alpha": "release-it major --preRelease=alpha", + "release-alpha": "release-it --preRelease=alpha" + }, + "dependencies": { + "@plone/components": "{{ cookiecutter.__version_plone_components }}" + }, + "peerDependencies": { + "react": "18.2.0", + "react-dom": "18.2.0", + "react-intl": "3.8.0" + }, + "devDependencies": { + "@plone/scripts": "{{ cookiecutter.__version_plone_scripts }}", + "release-it": "{{ cookiecutter.__version_release_it }}" + } +} diff --git a/frontend_addon/{{ cookiecutter.__folder_name }}/packages/{{ cookiecutter.frontend_addon_name }}/src/components/.gitkeep b/frontend_addon/{{ cookiecutter.__folder_name }}/packages/{{ cookiecutter.frontend_addon_name }}/src/components/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/frontend_addon/{{ cookiecutter.__folder_name }}/packages/{{ cookiecutter.frontend_addon_name }}/src/index.js b/frontend_addon/{{ cookiecutter.__folder_name }}/packages/{{ cookiecutter.frontend_addon_name }}/src/index.js new file mode 100644 index 0000000..cb042f0 --- /dev/null +++ b/frontend_addon/{{ cookiecutter.__folder_name }}/packages/{{ cookiecutter.frontend_addon_name }}/src/index.js @@ -0,0 +1,5 @@ +const applyConfig = (config) => { + return config; +}; + +export default applyConfig; diff --git a/frontend_addon/{{ cookiecutter.__folder_name }}/packages/{{ cookiecutter.frontend_addon_name }}/towncrier.toml b/frontend_addon/{{ cookiecutter.__folder_name }}/packages/{{ cookiecutter.frontend_addon_name }}/towncrier.toml new file mode 100644 index 0000000..ec069ea --- /dev/null +++ b/frontend_addon/{{ cookiecutter.__folder_name }}/packages/{{ cookiecutter.frontend_addon_name }}/towncrier.toml @@ -0,0 +1,33 @@ +[tool.towncrier] +filename = "CHANGELOG.md" +directory = "news/" +title_format = "## {version} ({project_date})" +underlines = ["", "", ""] +template = "./node_modules/@plone/scripts/templates/towncrier_template.jinja" +start_string = "\n" +issue_format = "[#{issue}](https://github.com/{{ cookiecutter.github_organization }}/{{ cookiecutter.frontend_addon_name }}/issue/{issue})" + +[[tool.towncrier.type]] +directory = "breaking" +name = "Breaking" +showcontent = true + +[[tool.towncrier.type]] +directory = "feature" +name = "Feature" +showcontent = true + +[[tool.towncrier.type]] +directory = "bugfix" +name = "Bugfix" +showcontent = true + +[[tool.towncrier.type]] +directory = "internal" +name = "Internal" +showcontent = true + +[[tool.towncrier.type]] +directory = "documentation" +name = "Documentation" +showcontent = true diff --git a/frontend_addon/{{ cookiecutter.__folder_name }}/packages/{{ cookiecutter.frontend_addon_name }}/tsconfig.json b/frontend_addon/{{ cookiecutter.__folder_name }}/packages/{{ cookiecutter.frontend_addon_name }}/tsconfig.json new file mode 100644 index 0000000..7bb754d --- /dev/null +++ b/frontend_addon/{{ cookiecutter.__folder_name }}/packages/{{ cookiecutter.frontend_addon_name }}/tsconfig.json @@ -0,0 +1,32 @@ +{ + "compilerOptions": { + "target": "ESNext", + "lib": ["DOM", "DOM.Iterable", "ESNext"], + "module": "commonjs", + "allowJs": true, + "skipLibCheck": true, + "esModuleInterop": true, + "allowSyntheticDefaultImports": true, + "strict": true, + "forceConsistentCasingInFileNames": true, + "strictPropertyInitialization": false, + "moduleResolution": "Node", + "resolveJsonModule": true, + "isolatedModules": true, + "noEmit": true, + "jsx": "react-jsx", + "paths": { + "@plone/volto/*": ["../../core/packages/volto/src/*"] + } + }, + "include": ["src", "src/**/*.js"], + "exclude": [ + "node_modules", + "build", + "public", + "coverage", + "src/**/*.test.{js,jsx,ts,tsx}", + "src/**/*.spec.{js,jsx,ts,tsx}", + "src/**/*.stories.{js,jsx,ts,tsx}" + ] +} diff --git a/frontend_addon/{{ cookiecutter.__folder_name }}/pnpm-workspace.yaml b/frontend_addon/{{ cookiecutter.__folder_name }}/pnpm-workspace.yaml new file mode 100644 index 0000000..f9c0485 --- /dev/null +++ b/frontend_addon/{{ cookiecutter.__folder_name }}/pnpm-workspace.yaml @@ -0,0 +1,4 @@ +packages: + # all packages in direct subdirs of packages/ + - 'core/packages/*' + - 'packages/*' diff --git a/frontend_addon/{{ cookiecutter.__folder_name }}/volto.config.js b/frontend_addon/{{ cookiecutter.__folder_name }}/volto.config.js new file mode 100644 index 0000000..5c0ebec --- /dev/null +++ b/frontend_addon/{{ cookiecutter.__folder_name }}/volto.config.js @@ -0,0 +1,7 @@ +const addons = ['{{ cookiecutter.npm_package_name }}']; +const theme = ''; + +module.exports = { + addons, + theme, +}; diff --git a/hooks/pre_prompt.py b/hooks/pre_prompt.py new file mode 100644 index 0000000..a52dd8c --- /dev/null +++ b/hooks/pre_prompt.py @@ -0,0 +1,23 @@ +"""Pre Prompt hook.""" + +import sys + +try: + from cookieplone.utils import console + + HAS_COOKIEPLONE = True +except ModuleNotFoundError: + HAS_COOKIEPLONE = False + + +def main(): + """Check if we have cookieplone installed.""" + if not HAS_COOKIEPLONE: + print("This template should be run with cookieplone") + sys.exit(1) + else: + console.print_plone_banner() + + +if __name__ == "__main__": + main() diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..14b5d75 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,27 @@ +[tool.black] +line-length = 88 +target-version = ['py311'] +include = '\.pyi?$' +exclude = ''' +( + /( + \.eggs # exclude a few common directories in the + | \.git # root of the project + | \.hg + | \.mypy_cache + | \.tox + | \.venv + | _build + | buck-out + | build + | dist + | {{cookiecutter.python_package_name}} + )/ +) +''' + +[tool.isort] +profile = "black" + +[tool.pytest] +testpaths='tests' diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..b45fd36 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,9 @@ +black +cookiecutter +isort +pytest +pytest-cookies +pytest-jsonschema >= 1.0.0a2 +cookieplone>=0.4.1 +GitPython +wheel