Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Packageless frontend #15

Merged
merged 3 commits into from
May 16, 2024
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
48 changes: 48 additions & 0 deletions .github/workflows/sub_frontend_project.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
name: Plone Frontend Project CI
on:
push:
paths:
- "sub/frontend_project/**"
- ".github/workflows/sub_frontend_project.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 sub/frontend_project
python -m pytest tests
2 changes: 2 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -35,12 +35,14 @@ format: bin/cookieplone ## Format code
bin/isort hooks .scripts
$(MAKE) -C "./backend_addon/" format
$(MAKE) -C "./frontend_addon/" format
$(MAKE) -C "./sub/frontend_project/" 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
$(MAKE) -C "./sub/frontend_project/" test

.PHONY: report-context
report-context: bin/cookieplone ## Generate a report of all context options
Expand Down
4 changes: 1 addition & 3 deletions frontend_addon/hooks/pre_gen_project.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,7 @@
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("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)
Expand Down
2 changes: 1 addition & 1 deletion frontend_addon/tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ def context() -> dict:
"email": "[email protected]",
"github_organization": "collective",
"npm_package_name": "@plone-collective/volto-addon",
"volto_version": "18.0.0-alpha.31"
"volto_version": "18.0.0-alpha.31",
}


Expand Down
44 changes: 44 additions & 0 deletions sub/frontend_project/Makefile
Original file line number Diff line number Diff line change
@@ -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
22 changes: 22 additions & 0 deletions sub/frontend_project/cookiecutter.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
{
"title": "Frontend Project",
"volto_version": "{{ 'Yes' | latest_volto }}",
"author": "Plone Community",
"email": "[email protected]",
"__folder_name": "app",
"__gha_enable": true,
"__version_plone_volto": "{{ cookiecutter.volto_version }}",
"__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 [cookieplone-templates](https://github.com/plone/cookieplone-templates) on {{ cookiecutter.__generator_date_long }}",
"__prompts__": {
"title": "Project name",
"volto_version": "Volto version"
},
"_copy_without_render": [
"_project_files"
],
"_extensions": [
"cookieplone.filters.latest_volto"
]
}
66 changes: 66 additions & 0 deletions sub/frontend_project/hooks/post_gen_project.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
"""Post generation hook."""

from collections import OrderedDict # noQA
from pathlib import Path

from cookieplone import generator
from cookieplone.utils import console, files

context = {{cookiecutter}}


LOCAL_FILES_FOLDER_NAME = "_project_files"


TO_REMOVE = [".github", "packages/volto-addon"]


def generate_addon(context, output_dir):
"""Run volto generator."""
folder_name = output_dir.name
output_dir = output_dir.parent
context["frontend_addon_name"] = "volto-addon"
generator.generate_subtemplate(
"../../frontend_addon", output_dir, folder_name, context, TO_REMOVE
)


def cleanup(context, output_dir):
"""Remove references to volto-addon."""
project_files_folder = output_dir / LOCAL_FILES_FOLDER_NAME
project_files: list[Path] = [path for path in project_files_folder.glob("*")]
filenames = [path.name for path in project_files]
# Remove old files
files.remove_files(output_dir, filenames)
for path in project_files:
name = path.name
path.rename(output_dir / name)
# Remove templates folder
files.remove_files(output_dir, [LOCAL_FILES_FOLDER_NAME])


def main():
"""Final fixes."""
output_dir = Path().cwd()
# Setup frontend
generate_addon(context, output_dir)
# Cleanup
cleanup(context, output_dir)
msg = """
[bold blue]{{ cookiecutter.title }}[/bold blue]

Now, code it, create a git repository, push to your organization.

Sorry for the convenience,
The Plone Community.
"""
console.panel(
title="New project was generated",
subtitle="",
msg=msg,
url="https://plone.org/",
)


if __name__ == "__main__":
main()
63 changes: 63 additions & 0 deletions sub/frontend_project/hooks/pre_gen_project.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
"""Pre generation hook."""

import sys
from collections import OrderedDict # noQA
from pathlib import Path
from textwrap import dedent

from cookieplone import data
from cookieplone.utils import console, validators

output_path = Path().resolve()

context = {{cookiecutter}}


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
# ),
]
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()
Empty file.
50 changes: 50 additions & 0 deletions sub/frontend_project/tests/conftest.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
"""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 {
"title": "Frontend project",
"author": "Plone Collective",
"email": "[email protected]",
"volto_version": "18.0.0-alpha.31",
}


@pytest.fixture(scope="session")
def bad_context() -> dict:
"""Cookiecutter context with invalid data."""
return {
"title": "Frontend project",
"author": "Plone Collective",
"email": "[email protected]",
"volto_version": "---",
}


@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)
44 changes: 44 additions & 0 deletions sub/frontend_project/tests/test_cutter.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
"""Test cookiecutter generation with all features enabled."""

from pathlib import Path

import pytest


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 == "app"
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,text,expected",
[
[".eslintrc.js", "volto-addon", False],
["Makefile", "volto-addon", False],
["package.json", "volto-addon-dev", False],
["package.json", "project-dev", True],
["volto.config.js", "volto-addon", False],
],
)
def test_root_files_do_not_mention_addon(
cutter_result, file_path: Path, text: str, expected: bool
):
"""Check if root files were generated and have no reference to the addon."""
path = cutter_result.project_path / file_path
assert path.exists()
assert path.is_file()
assert (text in path.read_text()) is expected
Loading