splent-cli 0.0.2__tar.gz → 0.0.3__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (47) hide show
  1. splent_cli-0.0.3/PKG-INFO +38 -0
  2. splent_cli-0.0.3/pyproject.toml +59 -0
  3. {splent_cli-0.0.2 → splent_cli-0.0.3}/src/splent_cli/cli.py +1 -1
  4. {splent_cli-0.0.2 → splent_cli-0.0.3}/src/splent_cli/commands/db_seed.py +38 -33
  5. {splent_cli-0.0.2 → splent_cli-0.0.3}/src/splent_cli/commands/feature_create.py +32 -24
  6. {splent_cli-0.0.2 → splent_cli-0.0.3}/src/splent_cli/commands/feature_list.py +8 -8
  7. {splent_cli-0.0.2 → splent_cli-0.0.3}/src/splent_cli/commands/product_create.py +53 -22
  8. {splent_cli-0.0.2 → splent_cli-0.0.3}/src/splent_cli/commands/webpack_compile.py +5 -15
  9. {splent_cli-0.0.2 → splent_cli-0.0.3}/src/splent_cli/utils/dynamic_imports.py +6 -2
  10. splent_cli-0.0.3/src/splent_cli/utils/feature_installer.py +47 -0
  11. splent_cli-0.0.3/src/splent_cli/utils/feature_utils.py +24 -0
  12. {splent_cli-0.0.2 → splent_cli-0.0.3}/src/splent_cli/utils/path_utils.py +12 -24
  13. splent_cli-0.0.3/src/splent_cli.egg-info/PKG-INFO +38 -0
  14. {splent_cli-0.0.2 → splent_cli-0.0.3}/src/splent_cli.egg-info/SOURCES.txt +1 -1
  15. splent_cli-0.0.3/src/splent_cli.egg-info/requires.txt +26 -0
  16. splent_cli-0.0.2/PKG-INFO +0 -22
  17. splent_cli-0.0.2/pyproject.toml +0 -39
  18. splent_cli-0.0.2/src/splent_cli/commands/update.py +0 -159
  19. splent_cli-0.0.2/src/splent_cli/utils/feature_installer.py +0 -54
  20. splent_cli-0.0.2/src/splent_cli.egg-info/PKG-INFO +0 -22
  21. splent_cli-0.0.2/src/splent_cli.egg-info/requires.txt +0 -9
  22. {splent_cli-0.0.2 → splent_cli-0.0.3}/LICENSE +0 -0
  23. {splent_cli-0.0.2 → splent_cli-0.0.3}/README.md +0 -0
  24. {splent_cli-0.0.2 → splent_cli-0.0.3}/setup.cfg +0 -0
  25. {splent_cli-0.0.2 → splent_cli-0.0.3}/src/splent_cli/__init__.py +0 -0
  26. {splent_cli-0.0.2 → splent_cli-0.0.3}/src/splent_cli/__main__.py +0 -0
  27. {splent_cli-0.0.2 → splent_cli-0.0.3}/src/splent_cli/commands/__init__.py +0 -0
  28. {splent_cli-0.0.2 → splent_cli-0.0.3}/src/splent_cli/commands/clear_cache.py +0 -0
  29. {splent_cli-0.0.2 → splent_cli-0.0.3}/src/splent_cli/commands/clear_log.py +0 -0
  30. {splent_cli-0.0.2 → splent_cli-0.0.3}/src/splent_cli/commands/clear_uploads.py +0 -0
  31. {splent_cli-0.0.2 → splent_cli-0.0.3}/src/splent_cli/commands/compose_env.py +0 -0
  32. {splent_cli-0.0.2 → splent_cli-0.0.3}/src/splent_cli/commands/coverage.py +0 -0
  33. {splent_cli-0.0.2 → splent_cli-0.0.3}/src/splent_cli/commands/db_console.py +0 -0
  34. {splent_cli-0.0.2 → splent_cli-0.0.3}/src/splent_cli/commands/db_dump.py +0 -0
  35. {splent_cli-0.0.2 → splent_cli-0.0.3}/src/splent_cli/commands/db_migrate.py +0 -0
  36. {splent_cli-0.0.2 → splent_cli-0.0.3}/src/splent_cli/commands/db_reset.py +0 -0
  37. {splent_cli-0.0.2 → splent_cli-0.0.3}/src/splent_cli/commands/env.py +0 -0
  38. {splent_cli-0.0.2 → splent_cli-0.0.3}/src/splent_cli/commands/info.py +0 -0
  39. {splent_cli-0.0.2 → splent_cli-0.0.3}/src/splent_cli/commands/linter.py +0 -0
  40. {splent_cli-0.0.2 → splent_cli-0.0.3}/src/splent_cli/commands/locust.py +0 -0
  41. {splent_cli-0.0.2 → splent_cli-0.0.3}/src/splent_cli/commands/route_list.py +0 -0
  42. {splent_cli-0.0.2 → splent_cli-0.0.3}/src/splent_cli/commands/selenium.py +0 -0
  43. {splent_cli-0.0.2 → splent_cli-0.0.3}/src/splent_cli/commands/test.py +0 -0
  44. {splent_cli-0.0.2 → splent_cli-0.0.3}/src/splent_cli/utils/__init__.py +0 -0
  45. {splent_cli-0.0.2 → splent_cli-0.0.3}/src/splent_cli.egg-info/dependency_links.txt +0 -0
  46. {splent_cli-0.0.2 → splent_cli-0.0.3}/src/splent_cli.egg-info/entry_points.txt +0 -0
  47. {splent_cli-0.0.2 → splent_cli-0.0.3}/src/splent_cli.egg-info/top_level.txt +0 -0
@@ -0,0 +1,38 @@
1
+ Metadata-Version: 2.4
2
+ Name: splent_cli
3
+ Version: 0.0.3
4
+ Summary: SPLENT-CLI is a CLI to be able to work on your development more easily.
5
+ Author-email: DiversoLab <diversolab@us.es>
6
+ Project-URL: Homepage, https://github.com/diverso-lab/splent_cli
7
+ Project-URL: Issues, https://github.com/diverso-lab/splent_cli/issues
8
+ Requires-Python: >=3.12
9
+ Description-Content-Type: text/markdown
10
+ License-File: LICENSE
11
+ Requires-Dist: click==8.1.8
12
+ Provides-Extra: dev
13
+ Requires-Dist: setuptools==80.3.1; extra == "dev"
14
+ Requires-Dist: pytest==8.3.4; extra == "dev"
15
+ Requires-Dist: pytest-cov==6.0.0; extra == "dev"
16
+ Requires-Dist: ruff==0.11.8; extra == "dev"
17
+ Requires-Dist: build==1.2.2.post1; extra == "dev"
18
+ Requires-Dist: twine==6.1.0; extra == "dev"
19
+ Requires-Dist: black==24.10.0; extra == "dev"
20
+ Requires-Dist: coverage==7.6.10; extra == "dev"
21
+ Requires-Dist: docker==7.1.0; extra == "dev"
22
+ Requires-Dist: Faker==33.3.1; extra == "dev"
23
+ Requires-Dist: flake8==7.1.1; extra == "dev"
24
+ Requires-Dist: graphviz==0.20.3; extra == "dev"
25
+ Requires-Dist: iniconfig==2.0.0; extra == "dev"
26
+ Requires-Dist: locust==2.32.6; extra == "dev"
27
+ Requires-Dist: mccabe==0.7.0; extra == "dev"
28
+ Requires-Dist: mypy-extensions==1.0.0; extra == "dev"
29
+ Requires-Dist: pathspec==0.12.1; extra == "dev"
30
+ Requires-Dist: platformdirs==4.3.6; extra == "dev"
31
+ Requires-Dist: pycodestyle==2.12.1; extra == "dev"
32
+ Requires-Dist: pyflakes==3.2.0; extra == "dev"
33
+ Requires-Dist: selenium==4.28.0; extra == "dev"
34
+ Requires-Dist: selenium-wire==5.1.0; extra == "dev"
35
+ Requires-Dist: pip-tools==7.4.1; extra == "dev"
36
+ Dynamic: license-file
37
+
38
+ # splent_cli
@@ -0,0 +1,59 @@
1
+ [build-system]
2
+ requires = ["setuptools>=80.3.1", "wheel"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "splent_cli"
7
+ version = "0.0.3"
8
+ description = "SPLENT-CLI is a CLI to be able to work on your development more easily."
9
+ readme = "README.md"
10
+ requires-python = ">=3.12"
11
+ authors = [{ name = "DiversoLab", email = "diversolab@us.es" }]
12
+ license-files = ["LICENSE"]
13
+
14
+ dependencies = [
15
+ "click==8.1.8"
16
+ ]
17
+
18
+ [project.optional-dependencies]
19
+ dev = [
20
+ "setuptools==80.3.1",
21
+ "pytest==8.3.4",
22
+ "pytest-cov==6.0.0",
23
+ "ruff==0.11.8",
24
+ "build==1.2.2.post1",
25
+ "twine==6.1.0",
26
+ "black==24.10.0",
27
+ "coverage==7.6.10",
28
+ "docker==7.1.0",
29
+ "Faker==33.3.1",
30
+ "flake8==7.1.1",
31
+ "graphviz==0.20.3",
32
+ "iniconfig==2.0.0",
33
+ "locust==2.32.6",
34
+ "mccabe==0.7.0",
35
+ "mypy-extensions==1.0.0",
36
+ "pathspec==0.12.1",
37
+ "platformdirs==4.3.6",
38
+ "pycodestyle==2.12.1",
39
+ "pyflakes==3.2.0",
40
+ "selenium==4.28.0",
41
+ "selenium-wire==5.1.0",
42
+ "pip-tools==7.4.1"
43
+ ]
44
+
45
+ [project.scripts]
46
+ splent_cli = "splent_cli.__main__:main"
47
+
48
+ [tool.setuptools]
49
+ package-dir = { "" = "src" }
50
+
51
+ [tool.setuptools.packages.find]
52
+ where = ["src"]
53
+
54
+ [tool.black]
55
+ line-length = 79
56
+
57
+ [project.urls]
58
+ Homepage = "https://github.com/diverso-lab/splent_cli"
59
+ Issues = "https://github.com/diverso-lab/splent_cli/issues"
@@ -15,7 +15,7 @@ def check_working_dir():
15
15
  working_dir = os.getenv("WORKING_DIR", "").strip()
16
16
 
17
17
  if working_dir != "/workspace":
18
- print(f"❌ ERROR: WORKING_DIR must be set to '/workspace', but got '{working_dir}'.")
18
+ print(f"❌ CLI ERROR: WORKING_DIR must be set to '/workspace', but got '{working_dir}'.")
19
19
  sys.exit(1)
20
20
 
21
21
 
@@ -2,46 +2,52 @@ import inspect
2
2
  import os
3
3
  import importlib
4
4
  import click
5
- from flask.cli import with_appcontext
5
+ import tomllib
6
6
 
7
+ from flask.cli import with_appcontext
7
8
  from splent_cli.commands.db_reset import db_reset
8
9
  from splent_cli.utils.path_utils import PathUtils
9
10
  from splent_framework.core.seeders.BaseSeeder import BaseSeeder
10
11
 
11
12
 
12
- def get_installed_seeders(features_file_path, specific_module=None):
13
+ def get_installed_seeders(specific_module=None):
13
14
  seeders = []
14
-
15
- if not os.path.isfile(features_file_path):
16
- click.echo(f"⚠️ Features file not found: {features_file_path}")
15
+
16
+ pyproject_path = os.path.join(PathUtils.get_app_base_dir(), "pyproject.toml")
17
+
18
+ if not os.path.exists(pyproject_path):
19
+ click.echo(click.style(f"❌ pyproject.toml not found at {pyproject_path}", fg="red"))
20
+ return seeders
21
+
22
+ try:
23
+ with open(pyproject_path, "rb") as f:
24
+ data = tomllib.load(f)
25
+ features = data["project"]["optional-dependencies"].get("features", [])
26
+ except Exception as e:
27
+ click.echo(click.style(f"❌ Failed to read features from pyproject.toml: {e}", fg="red"))
17
28
  return seeders
18
29
 
19
- with open(features_file_path) as f:
20
- for line in f:
21
- feature = line.strip()
22
- if not feature:
23
- continue
24
-
25
- if specific_module and specific_module != feature.split("_")[-1]:
26
- continue
27
-
28
- try:
29
- seeder_module = importlib.import_module(f"{feature}.seeders")
30
- importlib.reload(seeder_module)
31
-
32
- for attr in dir(seeder_module):
33
- obj = getattr(seeder_module, attr)
34
- if (
35
- inspect.isclass(obj)
36
- and issubclass(obj, BaseSeeder)
37
- and obj is not BaseSeeder
38
- ):
39
- seeders.append(obj())
40
- except Exception as e:
41
- click.echo(
42
- f"❌ Error loading seeders from {feature}: {e}",
43
- err=True,
44
- )
30
+ for feature in features:
31
+ if specific_module and specific_module != feature.split("_")[-1]:
32
+ continue
33
+
34
+ try:
35
+ seeder_module = importlib.import_module(f"{feature}.seeders")
36
+ importlib.reload(seeder_module)
37
+
38
+ for attr in dir(seeder_module):
39
+ obj = getattr(seeder_module, attr)
40
+ if (
41
+ inspect.isclass(obj)
42
+ and issubclass(obj, BaseSeeder)
43
+ and obj is not BaseSeeder
44
+ ):
45
+ seeders.append(obj())
46
+ except Exception as e:
47
+ click.echo(
48
+ click.style(f"❌ Error loading seeders from {feature}: {e}", fg="red"),
49
+ err=True,
50
+ )
45
51
 
46
52
  seeders.sort(key=lambda s: s.priority)
47
53
  return seeders
@@ -76,8 +82,7 @@ def db_seed(reset, yes, module):
76
82
  click.echo(click.style("Database reset cancelled.", fg="yellow"))
77
83
  return
78
84
 
79
- features_file_path = PathUtils.get_features_file()
80
- seeders = get_installed_seeders(features_file_path, specific_module=module)
85
+ seeders = get_installed_seeders(specific_module=module)
81
86
  success = True
82
87
 
83
88
  if module:
@@ -39,48 +39,56 @@ def make_feature(name):
39
39
  return
40
40
 
41
41
  env = setup_jinja_env()
42
- context = {"module_name": name}
42
+ context = {"feature_name": name}
43
43
 
44
44
  # Archivos que van dentro de src/splent_feature_<name>
45
- files_and_templates = {
46
- "__init__.py": "module_init.py.j2",
47
- "routes.py": "module_routes.py.j2",
48
- "models.py": "module_models.py.j2",
49
- "repositories.py": "module_repositories.py.j2",
50
- "services.py": "module_services.py.j2",
51
- "forms.py": "module_forms.py.j2",
52
- "seeders.py": "module_seeders.py.j2",
53
- os.path.join("templates", name, "index.html"): "module_templates_index.html.j2",
54
- os.path.join("assets", "js", "scripts.js"): "module_scripts.js.j2",
55
- os.path.join("assets", "js", "webpack.config.js"): "module_webpack.config.js.j2",
45
+ src_files_and_templates = {
46
+ "__init__.py": "feature/feature_init.py.j2",
47
+ "routes.py": "feature/feature_routes.py.j2",
48
+ "models.py": "feature/feature_models.py.j2",
49
+ "repositories.py": "feature/feature_repositories.py.j2",
50
+ "services.py": "feature/feature_services.py.j2",
51
+ "forms.py": "feature/feature_forms.py.j2",
52
+ "seeders.py": "feature/feature_seeders.py.j2",
53
+ os.path.join("templates", name, "index.html"): "feature/feature_templates_index.html.j2",
54
+ os.path.join("assets", "js", "scripts.js"): "feature/feature_scripts.js.j2",
55
+ os.path.join("assets", "js", "webpack.config.js"): "feature/feature_webpack.config.js.j2",
56
56
  os.path.join("tests", "__init__.py"): None,
57
- os.path.join("tests", "test_unit.py"): "module_tests_test_unit.py.j2",
58
- os.path.join("tests", "locustfile.py"): "module_tests_locustfile.py.j2",
59
- os.path.join("tests", "test_selenium.py"): "module_tests_test_selenium.py.j2",
57
+ os.path.join("tests", "test_unit.py"): "feature/feature_tests_test_unit.py.j2",
58
+ os.path.join("tests", "locustfile.py"): "feature/feature_tests_locustfile.py.j2",
59
+ os.path.join("tests", "test_selenium.py"): "feature/feature_tests_test_selenium.py.j2",
60
+ }
61
+
62
+ # Archivos que van en la raíz de splent_feature_<name>
63
+ base_files_and_templates = {
64
+ ".gitignore": "feature/feature_.gitignore.j2",
65
+ "pyproject.toml": "feature/feature_pyproject.toml.j2",
66
+ "MANIFEST.in": "feature/feature_MANIFEST.in.j2"
60
67
  }
61
68
 
62
69
  # Crear todos los archivos de código en src_path
63
- for filename, template in files_and_templates.items():
70
+ for filename, template in src_files_and_templates.items():
64
71
  full_path = os.path.join(src_path, filename)
65
72
  if template:
66
73
  render_and_write_file(env, template, full_path, context)
67
74
  else:
68
75
  os.makedirs(os.path.dirname(full_path), exist_ok=True)
69
76
  open(full_path, "a").close()
77
+
78
+ # Crear todos los archivos de código en base_path
79
+ for filename, template in base_files_and_templates.items():
80
+ full_path = os.path.join(base_path, filename)
81
+ if template:
82
+ render_and_write_file(env, template, full_path, context)
83
+ else:
84
+ os.makedirs(os.path.dirname(full_path), exist_ok=True)
85
+ open(full_path, "a").close()
70
86
 
71
87
  # Crear src/__init__.py vacío
72
88
  src_root = os.path.join(base_path, "src")
73
89
  os.makedirs(src_root, exist_ok=True)
74
90
  open(os.path.join(src_root, "__init__.py"), "a").close()
75
91
 
76
- # Crear el pyproject.toml en la raíz del módulo
77
- render_and_write_file(
78
- env,
79
- "module_pyproject.toml.j2",
80
- os.path.join(base_path, "pyproject.toml"),
81
- context,
82
- )
83
-
84
92
  # Cambiar permisos y propietario
85
93
  uid = 1000
86
94
  gid = 1000
@@ -8,22 +8,22 @@ from splent_framework.core.managers.feature_manager import FeatureManager
8
8
  "feature:list", help="Lists all feautures and those ignored by .featureignore."
9
9
  )
10
10
  @with_appcontext
11
- def module_list():
11
+ def feature_list():
12
12
  app = get_app
13
13
  manager = FeatureManager(app)
14
14
 
15
- loaded_modules, ignored_modules = manager.get_modules()
15
+ loaded_features, ignored_features = manager.get_features()
16
16
 
17
17
  click.echo(
18
- click.style(f"Loaded features ({len(loaded_modules)}):", fg="green")
18
+ click.style(f"Loaded features ({len(loaded_features)}):", fg="green")
19
19
  )
20
- for module in loaded_modules:
21
- click.echo(f"- {module}")
20
+ for feature in loaded_features:
21
+ click.echo(f"- {feature}")
22
22
 
23
23
  click.echo(
24
24
  click.style(
25
- f"\nIgnored features ({len(ignored_modules)}):", fg="bright_yellow"
25
+ f"\nIgnored features ({len(ignored_features)}):", fg="bright_yellow"
26
26
  )
27
27
  )
28
- for module in ignored_modules:
29
- click.echo(click.style(f"- {module}", fg="bright_yellow"))
28
+ for feature in ignored_features:
29
+ click.echo(click.style(f"- {feature}", fg="bright_yellow"))
@@ -2,6 +2,7 @@ import os
2
2
  import shutil
3
3
  import stat
4
4
  import click
5
+ import zlib
5
6
  from pathlib import Path
6
7
  from jinja2 import Environment, FileSystemLoader, select_autoescape
7
8
 
@@ -10,7 +11,6 @@ from splent_cli.utils.path_utils import PathUtils
10
11
  def pascalcase(s):
11
12
  return "".join(word.capitalize() for word in s.split("_"))
12
13
 
13
-
14
14
  def setup_jinja_env():
15
15
  env = Environment(
16
16
  loader=FileSystemLoader(searchpath=PathUtils.get_splent_cli_templates_dir()),
@@ -25,31 +25,54 @@ def render_and_write_file(env, template_name, filename, context):
25
25
  with open(filename, "w") as f:
26
26
  f.write(content)
27
27
 
28
+ def copy_raw_file(template_name, filename):
29
+ src = os.path.join(PathUtils.get_splent_cli_templates_dir(), template_name)
30
+ os.makedirs(os.path.dirname(filename), exist_ok=True)
31
+ shutil.copy(src, filename)
32
+
28
33
  @click.command("product:create", help="Creates a new product with a given name.")
29
34
  @click.argument("name")
30
35
  @click.option("--features-file", type=click.Path(exists=True), help="Path to features.txt")
31
36
  def make_product(name, features_file):
32
37
  env = setup_jinja_env()
38
+ offset = zlib.crc32(name.encode("utf-8")) % 1000 # 0–999
39
+ web_port = 5000 + offset
40
+ db_port = 33060 + offset
41
+ redis_port = 6379 + offset
33
42
  context = {
34
43
  "product_name": name,
35
- "pascal_name": pascalcase(name)
44
+ "pascal_name": pascalcase(name),
45
+ "web_port": web_port,
46
+ "db_port": db_port,
47
+ "redis_port": redis_port
36
48
  }
37
49
 
38
50
  base_path = os.path.join(PathUtils.get_working_dir(), name)
39
- src_path = os.path.join(base_path, "src", name)
40
51
 
41
52
  if os.path.exists(base_path):
42
53
  click.echo(click.style(f"The product '{name}' already exists.", fg="red"))
43
54
  return
44
55
 
45
- # Crear carpetas base
46
- for subdir in ["entrypoints", "scripts", f"src/{name}"]:
56
+ for subdir in [
57
+ "docker", "entrypoints", "scripts", f"src/{name}",
58
+ f"src/{name}/static", f"src/{name}/static/css",
59
+ f"src/{name}/static/fonts", f"src/{name}/static/js",
60
+ f"src/{name}/templates"
61
+ ]:
47
62
  os.makedirs(os.path.join(base_path, subdir), exist_ok=True)
63
+
48
64
  open(os.path.join(base_path, "src", "__init__.py"), "a").close()
49
65
 
50
- # Archivos desde plantillas
51
- files_and_templates = {
52
- "entrypoints/dev_entrypoint.sh": "product/product_dev_entrypoint.sh.j2",
66
+ jinja_templates = {
67
+ "docker/.env.dev.example": "product/product_.env.dev.example.j2",
68
+ "docker/.env.prod.example": "product/product_.env.prod.example.j2",
69
+ "docker/docker-compose.dev.yml": "product/product_docker-compose.dev.yml.j2",
70
+ "docker/docker-compose.prod.yml": "product/product_docker-compose.prod.yml.j2",
71
+ f"docker/Dockerfile.{name}.dev": "product/product_Dockerfile.dev.j2",
72
+ f"docker/Dockerfile.{name}.prod": "product/product_Dockerfile.prod.j2",
73
+ "entrypoints/entrypoint.dev.sh": "product/product_entrypoint.dev.sh.j2",
74
+ "entrypoints/entrypoint.prod.sh": "product/product_entrypoint.prod.sh.j2",
75
+ "scripts/00_core_requirements_dev.sh": "product/product_00_core_requirements_dev.sh.j2",
53
76
  "scripts/00_install_features.sh": "product/product_00_install_features.sh.j2",
54
77
  "scripts/01_compile_assets.sh": "product/product_01_compile_assets.sh.j2",
55
78
  "scripts/02_0_db_wait_connection.sh": "product/product_02_0_db_wait_connection.sh.j2",
@@ -57,29 +80,37 @@ def make_product(name, features_file):
57
80
  "scripts/03_initialize_migrations.sh": "product/product_03_initialize_migrations.sh.j2",
58
81
  "scripts/04_handle_migrations.sh": "product/product_04_handle_migrations.sh.j2",
59
82
  "scripts/05_0_start_app_dev.sh": "product/product_05_0_start_app_dev.sh.j2",
60
- "README.md": "product/product_README.md.j2",
61
- "pyproject.toml": "product/product_pyproject.toml.j2",
62
- "features.txt": "product/product_features.txt.j2",
83
+ "scripts/05_1_start_app_prod.sh": "product/product_05_1_start_app_prod.sh.j2",
84
+ ".gitignore": "product/product_.gitignore.j2",
63
85
  "LICENSE": "product/product_LICENSE.j2",
64
86
  "package.json": "product/product_package.json.j2",
65
- ".gitignore": "product/product_.gitignore.j2",
87
+ "pyproject.toml": "product/product_pyproject.toml.j2",
88
+ "README.md": "product/product_README.md.j2",
66
89
  f"src/{name}/__init__.py": "product/product_init.py.j2",
90
+ }
67
91
 
92
+ raw_files = {
93
+ f"src/{name}/static/css/app.css": "product/product_app.css",
94
+ f"src/{name}/static/css/dropzone.css": "product/product_dropzone.css",
95
+ f"src/{name}/static/css/own.css": "product/product_own.css",
96
+ f"src/{name}/static/js/app.js": "product/product_app.js",
97
+ f"src/{name}/templates/400.html": "product/product_400.html",
98
+ f"src/{name}/templates/401.html": "product/product_401.html",
99
+ f"src/{name}/templates/404.html": "product/product_404.html",
100
+ f"src/{name}/templates/500.html": "product/product_500.html",
101
+ f"src/{name}/templates/base_template.html": "product/product_base_template.html",
68
102
  }
69
103
 
70
- for rel_path, tpl in files_and_templates.items():
104
+ for rel_path, tpl in jinja_templates.items():
71
105
  abs_path = os.path.join(base_path, rel_path)
72
- if tpl:
73
- render_and_write_file(env, tpl, abs_path, context)
74
- else:
75
- if features_file:
76
- os.makedirs(os.path.dirname(abs_path), exist_ok=True)
77
- shutil.copy(features_file, abs_path)
78
-
79
- # Cambiar permisos y propietario
106
+ render_and_write_file(env, tpl, abs_path, context)
107
+
108
+ for rel_path, tpl in raw_files.items():
109
+ abs_path = os.path.join(base_path, rel_path)
110
+ copy_raw_file(tpl, abs_path)
111
+
80
112
  uid = 1000
81
113
  gid = 1000
82
-
83
114
  os.chown(base_path, uid, gid)
84
115
  os.chmod(base_path, stat.S_IRWXU | stat.S_IRWXG | stat.S_IROTH | stat.S_IXOTH)
85
116
 
@@ -2,19 +2,12 @@ import logging
2
2
  import click
3
3
  import os
4
4
  import subprocess
5
+ import tomllib
5
6
 
6
- from splent_cli.utils.path_utils import PathUtils
7
-
8
- logger = logging.getLogger(__name__)
9
-
10
- FEATURES_FILE = PathUtils.get_features_file()
7
+ from splent_cli.utils.feature_utils import get_features_from_pyproject,get_normalize_feature_name_in_splent_format
11
8
 
12
9
 
13
- def get_features():
14
- if not os.path.isfile(FEATURES_FILE):
15
- return []
16
- with open(FEATURES_FILE) as f:
17
- return [line.strip() for line in f if line.strip()]
10
+ logger = logging.getLogger(__name__)
18
11
 
19
12
 
20
13
  @click.command("webpack:compile", help="Compile webpack for one or all features.")
@@ -23,13 +16,10 @@ def get_features():
23
16
  def webpack_compile(feature_name, watch):
24
17
  production = os.getenv("FLASK_ENV", "develop") == "production"
25
18
 
26
- def normalize_feature_name(name):
27
- return name if name.startswith("splent_feature_") else f"splent_feature_{name}"
28
-
29
19
  features = (
30
- [normalize_feature_name(feature_name)]
20
+ [get_normalize_feature_name_in_splent_format(feature_name)]
31
21
  if feature_name
32
- else get_features()
22
+ else get_features_from_pyproject()
33
23
  )
34
24
 
35
25
  for feature in features:
@@ -6,13 +6,17 @@ import importlib
6
6
  from dotenv import load_dotenv
7
7
  from importlib.metadata import distributions
8
8
 
9
+ from splent_cli.utils.path_utils import PathUtils
10
+
11
+ load_dotenv()
12
+
9
13
  _app_instance = None
10
14
  _db_instance = None
11
15
  _module_cache = None
12
16
  _mail_service_instance = None
13
17
 
14
- module_name = os.environ.get("SPLENT_APP", "splent_app")
15
- dotenv_path = f"/workspace/splent_docker/products/{module_name}/.env"
18
+ module_name = os.getenv("SPLENT_APP", "splent_app")
19
+ dotenv_path = PathUtils.get_app_env_file()
16
20
 
17
21
  if os.path.exists(dotenv_path):
18
22
  load_dotenv(dotenv_path, override=True)
@@ -0,0 +1,47 @@
1
+ import subprocess
2
+ from pathlib import Path
3
+ import tomllib
4
+
5
+ from splent_cli.utils.feature_utils import get_features_from_pyproject
6
+
7
+
8
+ def get_installed_packages() -> set[str]:
9
+ result = subprocess.run(
10
+ ["pip", "list", "--format=freeze"],
11
+ stdout=subprocess.PIPE, text=True, check=True
12
+ )
13
+ return {line.split("==")[0] for line in result.stdout.strip().splitlines() if "==" in line}
14
+
15
+
16
+ def get_package_name(feature_path: Path) -> str | None:
17
+ pyproject_path = feature_path / "pyproject.toml"
18
+ if not pyproject_path.is_file():
19
+ return None
20
+ try:
21
+ with pyproject_path.open("rb") as f:
22
+ data = tomllib.load(f)
23
+ return data.get("project", {}).get("name")
24
+ except Exception:
25
+ return None
26
+
27
+
28
+ def ensure_editable_features_installed():
29
+ features = get_features_from_pyproject()
30
+ installed = get_installed_packages()
31
+
32
+ for feature in features:
33
+ feature_path = Path("/workspace") / feature
34
+ package_name = get_package_name(feature_path)
35
+
36
+ if not package_name:
37
+ print(f"⚠️ Skipping {feature}: no valid pyproject.toml or name.")
38
+ continue
39
+
40
+ if package_name in installed:
41
+ continue
42
+
43
+ print(f"➡️ Installing {package_name} in editable mode...")
44
+ subprocess.run(
45
+ ["pip", "install", "-e", str(feature_path)],
46
+ check=True
47
+ )
@@ -0,0 +1,24 @@
1
+ import os
2
+ import tomllib
3
+ from splent_cli.utils.path_utils import PathUtils
4
+
5
+
6
+ def get_features_from_pyproject():
7
+ """
8
+ Devuelve la lista de features declaradas en [project.optional-dependencies].features del pyproject.toml
9
+ """
10
+ pyproject_path = os.path.join(PathUtils.get_app_base_dir(), "pyproject.toml")
11
+
12
+ if not os.path.exists(pyproject_path):
13
+ return []
14
+
15
+ try:
16
+ with open(pyproject_path, "rb") as f:
17
+ data = tomllib.load(f)
18
+ return data["project"]["optional-dependencies"].get("features", [])
19
+ except Exception:
20
+ return []
21
+
22
+
23
+ def get_normalize_feature_name_in_splent_format(name):
24
+ return name if name.startswith("splent_feature_") else f"splent_feature_{name}"
@@ -17,22 +17,22 @@ class PathUtils:
17
17
  return os.getenv("WORKING_DIR", "")
18
18
 
19
19
  @staticmethod
20
- def get_features_file():
21
-
22
- splent_app = os.getenv("SPLENT_APP", "splent_app")
23
-
20
+ def get_app_base_dir():
24
21
  working_dir = PathUtils.get_working_dir()
25
-
26
- return os.path.join(working_dir, splent_app, "features.txt")
22
+ splent_app = os.getenv("SPLENT_APP", "splent_app")
23
+ return os.path.join(working_dir, splent_app)
27
24
 
28
25
  @staticmethod
29
26
  def get_app_dir():
30
27
 
31
28
  splent_app = os.getenv("SPLENT_APP", "splent_app")
32
-
29
+ return os.path.join(PathUtils.get_app_base_dir(), "src", splent_app)
30
+
31
+ @staticmethod
32
+ def get_app_env_file():
33
33
  working_dir = PathUtils.get_working_dir()
34
-
35
- return os.path.join(working_dir, splent_app, "src", splent_app)
34
+ splent_app = os.getenv("SPLENT_APP", "splent_app")
35
+ return os.path.join(working_dir, splent_app, "docker", ".env")
36
36
 
37
37
  @staticmethod
38
38
  def get_modules_dir():
@@ -44,12 +44,8 @@ class PathUtils:
44
44
 
45
45
  @staticmethod
46
46
  def get_splent_cli_dir():
47
- base_dir = os.getcwd()
48
-
49
- if is_splent_dev_mode():
50
- return os.path.join(base_dir, "splent_cli", "src", "splent_cli")
51
-
52
- return os.path.join(base_dir, "splent_cli")
47
+ working_dir = PathUtils.get_working_dir()
48
+ return os.path.join(working_dir, "splent_cli", "src", "splent_cli")
53
49
 
54
50
  @staticmethod
55
51
  def get_splent_cli_templates_dir():
@@ -66,15 +62,7 @@ class PathUtils:
66
62
  @staticmethod
67
63
  def get_splent_framework_dir():
68
64
  working_dir = PathUtils.get_working_dir()
69
-
70
- if is_splent_dev_mode():
71
- return os.path.join(working_dir, "splent_framework", "src", "splent_framework")
72
-
73
- package = importlib.util.find_spec("splent_framework")
74
- if package and package.origin:
75
- return os.path.dirname(package.origin)
76
-
77
- raise FileNotFoundError("Could not find 'splent_framework'. Check the installation.")
65
+ return os.path.join(working_dir, "splent_framework", "src", "splent_framework")
78
66
 
79
67
  @staticmethod
80
68
  def get_core_dir():
@@ -0,0 +1,38 @@
1
+ Metadata-Version: 2.4
2
+ Name: splent_cli
3
+ Version: 0.0.3
4
+ Summary: SPLENT-CLI is a CLI to be able to work on your development more easily.
5
+ Author-email: DiversoLab <diversolab@us.es>
6
+ Project-URL: Homepage, https://github.com/diverso-lab/splent_cli
7
+ Project-URL: Issues, https://github.com/diverso-lab/splent_cli/issues
8
+ Requires-Python: >=3.12
9
+ Description-Content-Type: text/markdown
10
+ License-File: LICENSE
11
+ Requires-Dist: click==8.1.8
12
+ Provides-Extra: dev
13
+ Requires-Dist: setuptools==80.3.1; extra == "dev"
14
+ Requires-Dist: pytest==8.3.4; extra == "dev"
15
+ Requires-Dist: pytest-cov==6.0.0; extra == "dev"
16
+ Requires-Dist: ruff==0.11.8; extra == "dev"
17
+ Requires-Dist: build==1.2.2.post1; extra == "dev"
18
+ Requires-Dist: twine==6.1.0; extra == "dev"
19
+ Requires-Dist: black==24.10.0; extra == "dev"
20
+ Requires-Dist: coverage==7.6.10; extra == "dev"
21
+ Requires-Dist: docker==7.1.0; extra == "dev"
22
+ Requires-Dist: Faker==33.3.1; extra == "dev"
23
+ Requires-Dist: flake8==7.1.1; extra == "dev"
24
+ Requires-Dist: graphviz==0.20.3; extra == "dev"
25
+ Requires-Dist: iniconfig==2.0.0; extra == "dev"
26
+ Requires-Dist: locust==2.32.6; extra == "dev"
27
+ Requires-Dist: mccabe==0.7.0; extra == "dev"
28
+ Requires-Dist: mypy-extensions==1.0.0; extra == "dev"
29
+ Requires-Dist: pathspec==0.12.1; extra == "dev"
30
+ Requires-Dist: platformdirs==4.3.6; extra == "dev"
31
+ Requires-Dist: pycodestyle==2.12.1; extra == "dev"
32
+ Requires-Dist: pyflakes==3.2.0; extra == "dev"
33
+ Requires-Dist: selenium==4.28.0; extra == "dev"
34
+ Requires-Dist: selenium-wire==5.1.0; extra == "dev"
35
+ Requires-Dist: pip-tools==7.4.1; extra == "dev"
36
+ Dynamic: license-file
37
+
38
+ # splent_cli
@@ -31,9 +31,9 @@ src/splent_cli/commands/product_create.py
31
31
  src/splent_cli/commands/route_list.py
32
32
  src/splent_cli/commands/selenium.py
33
33
  src/splent_cli/commands/test.py
34
- src/splent_cli/commands/update.py
35
34
  src/splent_cli/commands/webpack_compile.py
36
35
  src/splent_cli/utils/__init__.py
37
36
  src/splent_cli/utils/dynamic_imports.py
38
37
  src/splent_cli/utils/feature_installer.py
38
+ src/splent_cli/utils/feature_utils.py
39
39
  src/splent_cli/utils/path_utils.py
@@ -0,0 +1,26 @@
1
+ click==8.1.8
2
+
3
+ [dev]
4
+ setuptools==80.3.1
5
+ pytest==8.3.4
6
+ pytest-cov==6.0.0
7
+ ruff==0.11.8
8
+ build==1.2.2.post1
9
+ twine==6.1.0
10
+ black==24.10.0
11
+ coverage==7.6.10
12
+ docker==7.1.0
13
+ Faker==33.3.1
14
+ flake8==7.1.1
15
+ graphviz==0.20.3
16
+ iniconfig==2.0.0
17
+ locust==2.32.6
18
+ mccabe==0.7.0
19
+ mypy-extensions==1.0.0
20
+ pathspec==0.12.1
21
+ platformdirs==4.3.6
22
+ pycodestyle==2.12.1
23
+ pyflakes==3.2.0
24
+ selenium==4.28.0
25
+ selenium-wire==5.1.0
26
+ pip-tools==7.4.1
splent_cli-0.0.2/PKG-INFO DELETED
@@ -1,22 +0,0 @@
1
- Metadata-Version: 2.4
2
- Name: splent_cli
3
- Version: 0.0.2
4
- Summary: SPLENT-CLI is a CLI to be able to work on your development more easily.
5
- Author-email: DiversoLab <diversolab@us.es>
6
- Project-URL: Homepage, https://github.com/diverso-lab/splent_cli
7
- Project-URL: Issues, https://github.com/diverso-lab/splent_cli/issues
8
- Requires-Python: >=3.12
9
- Description-Content-Type: text/markdown
10
- License-File: LICENSE
11
- Requires-Dist: click
12
- Requires-Dist: python-dotenv
13
- Requires-Dist: flask
14
- Requires-Dist: setuptools
15
- Requires-Dist: pytest
16
- Requires-Dist: pytest-cov
17
- Requires-Dist: ruff
18
- Requires-Dist: build
19
- Requires-Dist: twine
20
- Dynamic: license-file
21
-
22
- # splent_cli
@@ -1,39 +0,0 @@
1
- [build-system]
2
- requires = ["setuptools>=61.0", "wheel"]
3
- build-backend = "setuptools.build_meta"
4
-
5
- [project]
6
- name = "splent_cli"
7
- version = "0.0.2"
8
- description = "SPLENT-CLI is a CLI to be able to work on your development more easily."
9
- readme = "README.md"
10
- requires-python = ">=3.12"
11
- authors = [{ name = "DiversoLab", email = "diversolab@us.es" }]
12
- license-files = ["LICENSE"]
13
- dependencies = [
14
- "click",
15
- "python-dotenv",
16
- "flask",
17
- "setuptools",
18
- "pytest",
19
- "pytest-cov",
20
- "ruff",
21
- "build",
22
- "twine"
23
- ]
24
-
25
- [project.scripts]
26
- splent_cli = "splent_cli.__main__:main"
27
-
28
- [tool.setuptools]
29
- package-dir = { "" = "src" }
30
-
31
- [tool.setuptools.packages.find]
32
- where = ["src"]
33
-
34
- [tool.black]
35
- line-length = 79
36
-
37
- [project.urls]
38
- Homepage = "https://github.com/diverso-lab/splent_cli"
39
- Issues = "https://github.com/diverso-lab/splent_cli/issues"
@@ -1,159 +0,0 @@
1
- import click
2
- import os
3
- import subprocess
4
-
5
-
6
- def create_temp_requirements(requirements_path, temp_requirements_path):
7
- """Create a temporary requirements file without versions and handle editable sources."""
8
- editable_package = None
9
- with (
10
- open(requirements_path) as f,
11
- open(temp_requirements_path, "w") as temp_f,
12
- ):
13
- for line in f:
14
- if line.startswith("-e"):
15
- editable_package = line.strip() # Store the editable package
16
- elif line.strip():
17
- package = line.split("==")[
18
- 0
19
- ].strip() # Remove version information
20
- temp_f.write(package + "\n")
21
- return editable_package
22
-
23
-
24
- def uninstall_packages():
25
- """Uninstall all non-editable packages."""
26
- installed_packages = (
27
- subprocess.check_output(["pip", "freeze"]).decode("utf-8").splitlines()
28
- )
29
- non_editable_packages = [
30
- pkg for pkg in installed_packages if not pkg.startswith("-e")
31
- ]
32
- if non_editable_packages:
33
- subprocess.run(
34
- ["pip", "uninstall", "-y"]
35
- + [pkg.split("==")[0] for pkg in non_editable_packages]
36
- )
37
-
38
-
39
- def install_packages(requirements_file):
40
- """Install packages from a requirements file."""
41
- subprocess.run(["pip", "install", "-r", requirements_file])
42
-
43
-
44
- def regenerate_requirements(requirements_path):
45
- """Regenerate requirements.txt with resolved versions."""
46
- freeze_output = subprocess.check_output(["pip", "freeze"]).decode("utf-8")
47
- with open(requirements_path, "w") as f:
48
- f.write(freeze_output)
49
-
50
-
51
- def reinstall_editable_package(editable_package):
52
- """Reinstall the editable package."""
53
- if editable_package:
54
- editable_path = editable_package.split()[
55
- 1
56
- ] # Extract the path from '-e ./app'
57
- subprocess.run(
58
- ["pip", "install", "-e", editable_path],
59
- stdout=subprocess.DEVNULL, # Suppress output
60
- stderr=subprocess.DEVNULL, # Suppress errors
61
- )
62
-
63
-
64
- def clean_up(temp_requirements_path):
65
- """Remove the temporary requirements file."""
66
- if os.path.exists(temp_requirements_path):
67
- os.remove(temp_requirements_path)
68
-
69
-
70
- def update_pip():
71
- """Update pip dependencies."""
72
- requirements_path = os.path.join(
73
- os.getenv("WORKING_DIR", ""), "requirements.txt"
74
- )
75
- temp_requirements_path = os.path.join(
76
- os.getenv("WORKING_DIR", ""), "temp_requirements.txt"
77
- )
78
-
79
- editable_package = create_temp_requirements(
80
- requirements_path, temp_requirements_path
81
- )
82
- uninstall_packages()
83
- install_packages(temp_requirements_path)
84
- regenerate_requirements(requirements_path)
85
- reinstall_editable_package(editable_package)
86
- clean_up(temp_requirements_path)
87
-
88
-
89
- def update_npm():
90
- """Update npm dependencies."""
91
- working_dir = os.getenv("WORKING_DIR", "")
92
- package_json_path = os.path.join(working_dir, "package.json")
93
-
94
- if not os.path.exists(package_json_path):
95
- click.echo(
96
- click.style(
97
- "No package.json found. Skipping npm update.", fg="yellow"
98
- )
99
- )
100
- return
101
-
102
- try:
103
- # Step 1: Update package.json to latest versions
104
- subprocess.run(["npx", "npm-check-updates", "-u"], cwd=working_dir)
105
-
106
- # Step 2: Install updated dependencies
107
- subprocess.run(["npm", "install"], cwd=working_dir)
108
-
109
- click.echo(
110
- click.style("NPM dependencies updated successfully!", fg="green")
111
- )
112
- except subprocess.CalledProcessError as e:
113
- click.echo(click.style(f"Error during npm update: {e}", fg="red"))
114
-
115
-
116
-
117
- @click.command("update", help="Upload all pip dependencies.")
118
- def update():
119
- """Update both pip and npm dependencies."""
120
- try:
121
- click.echo("Updating pip dependencies...")
122
- update_pip()
123
- click.echo("Updating npm dependencies...")
124
- update_npm()
125
- click.echo(
126
- click.style("All dependencies updated successfully!", fg="green")
127
- )
128
- except Exception as e:
129
- click.echo(click.style(f"Error during update: {e}", fg="red"))
130
-
131
-
132
- @click.command("update:pip", help="Upload all pip dependencies.")
133
- def update_pip_cmd():
134
- """Update only pip dependencies."""
135
- try:
136
- click.echo("Updating pip dependencies...")
137
- update_pip()
138
- click.echo(
139
- click.style("Pip dependencies updated successfully!", fg="green")
140
- )
141
- except Exception as e:
142
- click.echo(click.style(f"Error during pip update: {e}", fg="red"))
143
-
144
-
145
- @click.command("update:npm", help="Upload all npm dependencies.")
146
- def update_npm_cmd():
147
- """Update only npm dependencies."""
148
- try:
149
- click.echo("Updating npm dependencies...")
150
- update_npm()
151
- click.echo(
152
- click.style("NPM dependencies updated successfully!", fg="green")
153
- )
154
- except Exception as e:
155
- click.echo(click.style(f"Error during npm update: {e}", fg="red"))
156
-
157
-
158
- if __name__ == "__main__":
159
- cli()
@@ -1,54 +0,0 @@
1
- import os
2
- import sys
3
- import subprocess
4
- import tomllib # Python 3.11+
5
- from importlib.metadata import distributions
6
-
7
-
8
- def ensure_editable_features_installed():
9
- """Installs missing editable features defined in features.txt of the SPLENT app."""
10
- splent_app_name = os.getenv("SPLENT_APP")
11
- if not splent_app_name:
12
- print("❌ Environment variable SPLENT_APP not set.")
13
- return
14
-
15
- features_file = f"/workspace/{splent_app_name}/features.txt"
16
- if not os.path.isfile(features_file):
17
- print(f"⚠️ No features.txt found in {splent_app_name}, skipping feature installation.")
18
- return
19
-
20
- def get_installed_package_names():
21
- return {dist.metadata["Name"] for dist in distributions() if "Name" in dist.metadata}
22
-
23
- def extract_package_name(pyproject_path):
24
- with open(pyproject_path, "rb") as f:
25
- pyproject = tomllib.load(f)
26
- return pyproject["project"]["name"]
27
-
28
- installed_names = get_installed_package_names()
29
-
30
- with open(features_file) as f:
31
- for line in f:
32
- feature = line.strip()
33
- if not feature:
34
- continue
35
-
36
- path = os.path.join("/workspace", feature)
37
- pyproject_path = os.path.join(path, "pyproject.toml")
38
-
39
- if not os.path.isfile(pyproject_path):
40
- print(f"❌ pyproject.toml not found in {path}. Skipping.")
41
- continue
42
-
43
- try:
44
- package_name = extract_package_name(pyproject_path)
45
- except Exception as e:
46
- print(f"❌ Could not read package name from {pyproject_path}: {e}")
47
- continue
48
-
49
- if package_name not in installed_names:
50
- print(f"➡️ Installing {package_name}...")
51
- subprocess.run(
52
- [sys.executable, "-m", "pip", "install", "-e", path],
53
- check=True
54
- )
@@ -1,22 +0,0 @@
1
- Metadata-Version: 2.4
2
- Name: splent_cli
3
- Version: 0.0.2
4
- Summary: SPLENT-CLI is a CLI to be able to work on your development more easily.
5
- Author-email: DiversoLab <diversolab@us.es>
6
- Project-URL: Homepage, https://github.com/diverso-lab/splent_cli
7
- Project-URL: Issues, https://github.com/diverso-lab/splent_cli/issues
8
- Requires-Python: >=3.12
9
- Description-Content-Type: text/markdown
10
- License-File: LICENSE
11
- Requires-Dist: click
12
- Requires-Dist: python-dotenv
13
- Requires-Dist: flask
14
- Requires-Dist: setuptools
15
- Requires-Dist: pytest
16
- Requires-Dist: pytest-cov
17
- Requires-Dist: ruff
18
- Requires-Dist: build
19
- Requires-Dist: twine
20
- Dynamic: license-file
21
-
22
- # splent_cli
@@ -1,9 +0,0 @@
1
- click
2
- python-dotenv
3
- flask
4
- setuptools
5
- pytest
6
- pytest-cov
7
- ruff
8
- build
9
- twine
File without changes
File without changes
File without changes