splent-cli 0.0.1__tar.gz → 0.0.2__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 (49) hide show
  1. splent_cli-0.0.2/PKG-INFO +22 -0
  2. {splent_cli-0.0.1 → splent_cli-0.0.2}/pyproject.toml +13 -9
  3. {splent_cli-0.0.1 → splent_cli-0.0.2/src}/splent_cli/__main__.py +4 -0
  4. {splent_cli-0.0.1 → splent_cli-0.0.2/src}/splent_cli/cli.py +7 -10
  5. {splent_cli-0.0.1 → splent_cli-0.0.2/src}/splent_cli/commands/db_reset.py +6 -5
  6. splent_cli-0.0.2/src/splent_cli/commands/db_seed.py +103 -0
  7. splent_cli-0.0.2/src/splent_cli/commands/feature_create.py +104 -0
  8. splent_cli-0.0.2/src/splent_cli/commands/feature_list.py +29 -0
  9. {splent_cli-0.0.1 → splent_cli-0.0.2/src}/splent_cli/commands/locust.py +3 -3
  10. splent_cli-0.0.2/src/splent_cli/commands/product_create.py +99 -0
  11. {splent_cli-0.0.1 → splent_cli-0.0.2/src}/splent_cli/commands/selenium.py +1 -1
  12. splent_cli-0.0.2/src/splent_cli/commands/webpack_compile.py +62 -0
  13. splent_cli-0.0.2/src/splent_cli/utils/__init__.py +0 -0
  14. splent_cli-0.0.2/src/splent_cli/utils/dynamic_imports.py +116 -0
  15. splent_cli-0.0.2/src/splent_cli/utils/feature_installer.py +54 -0
  16. {splent_cli-0.0.1 → splent_cli-0.0.2/src}/splent_cli/utils/path_utils.py +35 -49
  17. splent_cli-0.0.2/src/splent_cli.egg-info/PKG-INFO +22 -0
  18. splent_cli-0.0.2/src/splent_cli.egg-info/SOURCES.txt +39 -0
  19. {splent_cli-0.0.1 → splent_cli-0.0.2/src}/splent_cli.egg-info/requires.txt +2 -0
  20. splent_cli-0.0.1/PKG-INFO +0 -133
  21. splent_cli-0.0.1/splent_cli/__init__.py +0 -6
  22. splent_cli-0.0.1/splent_cli/commands/db_seed.py +0 -123
  23. splent_cli-0.0.1/splent_cli/commands/module_create.py +0 -138
  24. splent_cli-0.0.1/splent_cli/commands/module_list.py +0 -28
  25. splent_cli-0.0.1/splent_cli/commands/webpack_compile.py +0 -68
  26. splent_cli-0.0.1/splent_cli.egg-info/PKG-INFO +0 -133
  27. splent_cli-0.0.1/splent_cli.egg-info/SOURCES.txt +0 -36
  28. {splent_cli-0.0.1 → splent_cli-0.0.2}/LICENSE +0 -0
  29. {splent_cli-0.0.1 → splent_cli-0.0.2}/README.md +0 -0
  30. {splent_cli-0.0.1 → splent_cli-0.0.2}/setup.cfg +0 -0
  31. {splent_cli-0.0.1/splent_cli/commands → splent_cli-0.0.2/src/splent_cli}/__init__.py +0 -0
  32. {splent_cli-0.0.1/splent_cli/utils → splent_cli-0.0.2/src/splent_cli/commands}/__init__.py +0 -0
  33. {splent_cli-0.0.1 → splent_cli-0.0.2/src}/splent_cli/commands/clear_cache.py +0 -0
  34. {splent_cli-0.0.1 → splent_cli-0.0.2/src}/splent_cli/commands/clear_log.py +0 -0
  35. {splent_cli-0.0.1 → splent_cli-0.0.2/src}/splent_cli/commands/clear_uploads.py +0 -0
  36. {splent_cli-0.0.1 → splent_cli-0.0.2/src}/splent_cli/commands/compose_env.py +0 -0
  37. {splent_cli-0.0.1 → splent_cli-0.0.2/src}/splent_cli/commands/coverage.py +0 -0
  38. {splent_cli-0.0.1 → splent_cli-0.0.2/src}/splent_cli/commands/db_console.py +0 -0
  39. {splent_cli-0.0.1 → splent_cli-0.0.2/src}/splent_cli/commands/db_dump.py +0 -0
  40. {splent_cli-0.0.1 → splent_cli-0.0.2/src}/splent_cli/commands/db_migrate.py +0 -0
  41. {splent_cli-0.0.1 → splent_cli-0.0.2/src}/splent_cli/commands/env.py +0 -0
  42. {splent_cli-0.0.1 → splent_cli-0.0.2/src}/splent_cli/commands/info.py +0 -0
  43. {splent_cli-0.0.1 → splent_cli-0.0.2/src}/splent_cli/commands/linter.py +0 -0
  44. {splent_cli-0.0.1 → splent_cli-0.0.2/src}/splent_cli/commands/route_list.py +0 -0
  45. {splent_cli-0.0.1 → splent_cli-0.0.2/src}/splent_cli/commands/test.py +0 -0
  46. {splent_cli-0.0.1 → splent_cli-0.0.2/src}/splent_cli/commands/update.py +0 -0
  47. {splent_cli-0.0.1 → splent_cli-0.0.2/src}/splent_cli.egg-info/dependency_links.txt +0 -0
  48. {splent_cli-0.0.1 → splent_cli-0.0.2/src}/splent_cli.egg-info/entry_points.txt +0 -0
  49. {splent_cli-0.0.1 → splent_cli-0.0.2/src}/splent_cli.egg-info/top_level.txt +0 -0
@@ -0,0 +1,22 @@
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
@@ -4,12 +4,12 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "splent_cli"
7
- version = "0.0.1"
7
+ version = "0.0.2"
8
8
  description = "SPLENT-CLI is a CLI to be able to work on your development more easily."
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.12"
11
- authors = [{ name = "David Romero", email = "drorganvidez@us.es" }]
12
- license = { file = "LICENSE" }
11
+ authors = [{ name = "DiversoLab", email = "diversolab@us.es" }]
12
+ license-files = ["LICENSE"]
13
13
  dependencies = [
14
14
  "click",
15
15
  "python-dotenv",
@@ -17,18 +17,22 @@ dependencies = [
17
17
  "setuptools",
18
18
  "pytest",
19
19
  "pytest-cov",
20
- "ruff"
20
+ "ruff",
21
+ "build",
22
+ "twine"
21
23
  ]
22
24
 
23
- [tool.black]
24
- line-length = 79
25
-
26
25
  [project.scripts]
27
26
  splent_cli = "splent_cli.__main__:main"
28
27
 
28
+ [tool.setuptools]
29
+ package-dir = { "" = "src" }
30
+
29
31
  [tool.setuptools.packages.find]
30
- where = ["."]
31
- include = ["splent_cli*"]
32
+ where = ["src"]
33
+
34
+ [tool.black]
35
+ line-length = 79
32
36
 
33
37
  [project.urls]
34
38
  Homepage = "https://github.com/diverso-lab/splent_cli"
@@ -1,11 +1,15 @@
1
1
  from splent_cli.cli import check_working_dir, cli, load_commands
2
+ from splent_cli.utils.feature_installer import ensure_editable_features_installed
2
3
 
3
4
 
4
5
  def main():
5
6
  check_working_dir()
7
+ ensure_editable_features_installed()
6
8
  load_commands(cli)
7
9
  cli()
8
10
 
9
11
 
10
12
  if __name__ == "__main__":
11
13
  main()
14
+
15
+ __all__ = ["main"]
@@ -3,8 +3,8 @@ import sys
3
3
  import importlib
4
4
  import click
5
5
  from dotenv import load_dotenv
6
- from flask.cli import FlaskGroup # 📌 Añadir FlaskGroup
7
- from splent_app import create_app # 📌 Importar la app de Flask
6
+ from flask.cli import FlaskGroup
7
+ from splent_cli.utils.dynamic_imports import get_app
8
8
 
9
9
  from splent_cli.utils.path_utils import PathUtils
10
10
 
@@ -14,17 +14,14 @@ load_dotenv()
14
14
  def check_working_dir():
15
15
  working_dir = os.getenv("WORKING_DIR", "").strip()
16
16
 
17
- if not working_dir:
18
- return
19
-
20
- if working_dir in ["/app", "/vagrant", "/app/", "/vagrant/"] and not os.path.exists(working_dir):
21
- print(f"⚠️ WARNING: WORKING_DIR is set to '{working_dir}', but the directory does not exist.")
17
+ if working_dir != "/workspace":
18
+ print(f"❌ ERROR: WORKING_DIR must be set to '/workspace', but got '{working_dir}'.")
22
19
  sys.exit(1)
23
20
 
24
21
 
25
- class SPLENTCLI(FlaskGroup): # 📌 Usamos FlaskGroup para conectar con Flask
22
+ class SPLENTCLI(FlaskGroup):
26
23
  def __init__(self, **kwargs):
27
- super().__init__(create_app=create_app, **kwargs)
24
+ super().__init__(create_app=get_app, **kwargs)
28
25
 
29
26
  def get_command(self, ctx, cmd_name):
30
27
  rv = super().get_command(ctx, cmd_name)
@@ -34,7 +31,7 @@ class SPLENTCLI(FlaskGroup): # 📌 Usamos FlaskGroup para conectar con Flask
34
31
  return rv
35
32
 
36
33
 
37
- def load_commands(cli_group, commands_dir="splent_cli/commands"):
34
+ def load_commands(cli_group):
38
35
  """
39
36
  Dynamically import all commands in the specified directory and add them to the CLI group.
40
37
  """
@@ -3,10 +3,13 @@ import shutil
3
3
  import os
4
4
  import subprocess
5
5
  from flask.cli import with_appcontext
6
- from splent_app import create_app, db
6
+ from splent_cli.utils.dynamic_imports import get_create_app, get_db
7
7
  from sqlalchemy import MetaData
8
8
 
9
9
  from splent_cli.commands.clear_uploads import clear_uploads
10
+ from splent_cli.utils.path_utils import PathUtils
11
+
12
+ db = get_db()
10
13
 
11
14
 
12
15
  @click.command(
@@ -26,7 +29,7 @@ from splent_cli.commands.clear_uploads import clear_uploads
26
29
  )
27
30
  @with_appcontext
28
31
  def db_reset(clear_migrations, yes):
29
- app = create_app()
32
+ app = get_create_app()()
30
33
  with app.app_context():
31
34
  if not yes and not click.confirm(
32
35
  "WARNING: This will delete all data and clear uploads. Are you sure?",
@@ -60,9 +63,7 @@ def db_reset(clear_migrations, yes):
60
63
 
61
64
  if clear_migrations:
62
65
  # Delete the migration folder if it exists.
63
- migrations_dir = os.path.join(
64
- os.getenv("WORKING_DIR", ""), "migrations"
65
- )
66
+ migrations_dir = PathUtils.get_migrations_dir()
66
67
  if os.path.isdir(migrations_dir):
67
68
  shutil.rmtree(migrations_dir)
68
69
  click.echo(
@@ -0,0 +1,103 @@
1
+ import inspect
2
+ import os
3
+ import importlib
4
+ import click
5
+ from flask.cli import with_appcontext
6
+
7
+ from splent_cli.commands.db_reset import db_reset
8
+ from splent_cli.utils.path_utils import PathUtils
9
+ from splent_framework.core.seeders.BaseSeeder import BaseSeeder
10
+
11
+
12
+ def get_installed_seeders(features_file_path, specific_module=None):
13
+ seeders = []
14
+
15
+ if not os.path.isfile(features_file_path):
16
+ click.echo(f"⚠️ Features file not found: {features_file_path}")
17
+ return seeders
18
+
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
+ )
45
+
46
+ seeders.sort(key=lambda s: s.priority)
47
+ return seeders
48
+
49
+
50
+ @click.command(
51
+ "db:seed",
52
+ help="Populates the database with the seeders defined in each feature.",
53
+ )
54
+ @click.option("--reset", is_flag=True, help="Reset the database before seeding.")
55
+ @click.option(
56
+ "-y",
57
+ "--yes",
58
+ is_flag=True,
59
+ help="Confirm the operation without prompting.",
60
+ )
61
+ @click.argument("module", required=False)
62
+ @with_appcontext
63
+ def db_seed(reset, yes, module):
64
+ if reset:
65
+ if yes or click.confirm(
66
+ click.style(
67
+ "This will reset the database, do you want to continue?",
68
+ fg="red",
69
+ ),
70
+ abort=True,
71
+ ):
72
+ click.echo(click.style("Resetting the database...", fg="yellow"))
73
+ ctx = click.get_current_context()
74
+ ctx.invoke(db_reset, clear_migrations=False, yes=True)
75
+ else:
76
+ click.echo(click.style("Database reset cancelled.", fg="yellow"))
77
+ return
78
+
79
+ features_file_path = PathUtils.get_features_file()
80
+ seeders = get_installed_seeders(features_file_path, specific_module=module)
81
+ success = True
82
+
83
+ if module:
84
+ click.echo(click.style(f"Seeding data for the '{module}' feature...", fg="green"))
85
+ else:
86
+ click.echo(click.style("Seeding data for all features...", fg="green"))
87
+
88
+ for seeder in seeders:
89
+ try:
90
+ seeder.run()
91
+ click.echo(click.style(f"{seeder.__class__.__name__} performed.", fg="blue"))
92
+ except Exception as e:
93
+ click.echo(
94
+ click.style(f"Error running {seeder.__class__.__name__}: {e}", fg="red")
95
+ )
96
+ click.echo(
97
+ click.style("Rolling back session for safety.", fg="yellow")
98
+ )
99
+ success = False
100
+ break
101
+
102
+ if success:
103
+ click.echo(click.style("Database populated with test data.", fg="green"))
@@ -0,0 +1,104 @@
1
+ import os
2
+ import stat
3
+ import click
4
+ from pathlib import Path
5
+ from jinja2 import Environment, FileSystemLoader, select_autoescape
6
+
7
+ from splent_cli.utils.path_utils import PathUtils
8
+
9
+
10
+ def pascalcase(s):
11
+ return "".join(word.capitalize() for word in s.split("_"))
12
+
13
+
14
+ def setup_jinja_env():
15
+ env = Environment(
16
+ loader=FileSystemLoader(searchpath=PathUtils.get_splent_cli_templates_dir()),
17
+ autoescape=select_autoescape(["html", "xml", "j2"]),
18
+ )
19
+ env.filters["pascalcase"] = pascalcase
20
+ return env
21
+
22
+
23
+ def render_and_write_file(env, template_name, filename, context):
24
+ content = env.get_template(template_name).render(context) + "\n"
25
+ os.makedirs(os.path.dirname(filename), exist_ok=True)
26
+ with open(filename, "w") as f:
27
+ f.write(content)
28
+
29
+
30
+ @click.command("feature:create", help="Creates a new feature with a given name.")
31
+ @click.argument("name")
32
+ def make_feature(name):
33
+ feature_name = f"splent_feature_{name}"
34
+ base_path = os.path.join(PathUtils.get_working_dir(), feature_name)
35
+ src_path = os.path.join(base_path, "src", feature_name)
36
+
37
+ if os.path.exists(base_path):
38
+ click.echo(click.style(f"The feature '{feature_name}' already exists.", fg="red"))
39
+ return
40
+
41
+ env = setup_jinja_env()
42
+ context = {"module_name": name}
43
+
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",
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",
60
+ }
61
+
62
+ # Crear todos los archivos de código en src_path
63
+ for filename, template in files_and_templates.items():
64
+ full_path = os.path.join(src_path, filename)
65
+ if template:
66
+ render_and_write_file(env, template, full_path, context)
67
+ else:
68
+ os.makedirs(os.path.dirname(full_path), exist_ok=True)
69
+ open(full_path, "a").close()
70
+
71
+ # Crear src/__init__.py vacío
72
+ src_root = os.path.join(base_path, "src")
73
+ os.makedirs(src_root, exist_ok=True)
74
+ open(os.path.join(src_root, "__init__.py"), "a").close()
75
+
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
+ # Cambiar permisos y propietario
85
+ uid = 1000
86
+ gid = 1000
87
+
88
+ os.chown(base_path, uid, gid)
89
+ os.chmod(base_path, stat.S_IRWXU | stat.S_IRWXG | stat.S_IROTH | stat.S_IXOTH)
90
+
91
+ for root, dirs, files in os.walk(base_path):
92
+ for d in dirs:
93
+ dir_path = os.path.join(root, d)
94
+ os.chown(dir_path, uid, gid)
95
+ os.chmod(dir_path, stat.S_IRWXU | stat.S_IRWXG | stat.S_IROTH | stat.S_IXOTH)
96
+ for f in files:
97
+ file_path = os.path.join(root, f)
98
+ os.chown(file_path, uid, gid)
99
+ os.chmod(
100
+ file_path,
101
+ stat.S_IRUSR | stat.S_IWUSR | stat.S_IRGRP | stat.S_IWGRP | stat.S_IROTH,
102
+ )
103
+
104
+ click.echo(click.style(f"Feature '{feature_name}' created successfully in {base_path}", fg="green"))
@@ -0,0 +1,29 @@
1
+ import click
2
+ from flask.cli import with_appcontext
3
+ from splent_cli.utils.dynamic_imports import get_app
4
+ from splent_framework.core.managers.feature_manager import FeatureManager
5
+
6
+
7
+ @click.command(
8
+ "feature:list", help="Lists all feautures and those ignored by .featureignore."
9
+ )
10
+ @with_appcontext
11
+ def module_list():
12
+ app = get_app
13
+ manager = FeatureManager(app)
14
+
15
+ loaded_modules, ignored_modules = manager.get_modules()
16
+
17
+ click.echo(
18
+ click.style(f"Loaded features ({len(loaded_modules)}):", fg="green")
19
+ )
20
+ for module in loaded_modules:
21
+ click.echo(f"- {module}")
22
+
23
+ click.echo(
24
+ click.style(
25
+ f"\nIgnored features ({len(ignored_modules)}):", fg="bright_yellow"
26
+ )
27
+ )
28
+ for module in ignored_modules:
29
+ click.echo(click.style(f"- {module}", fg="bright_yellow"))
@@ -76,7 +76,7 @@ def locust(module):
76
76
  "-p",
77
77
  "8089:8089",
78
78
  "-v",
79
- f"{volume_name}:/app",
79
+ f"{volume_name}:/workspace",
80
80
  "--name",
81
81
  "locust_container",
82
82
  "--network",
@@ -142,7 +142,7 @@ def locust(module):
142
142
  if module:
143
143
  validate_module(module)
144
144
 
145
- if working_dir == "/app/":
145
+ if working_dir == "/workspace/":
146
146
  client = docker.from_env()
147
147
 
148
148
  try:
@@ -207,7 +207,7 @@ def stop():
207
207
  # Remove the Locust container
208
208
  subprocess.run(rm_command)
209
209
 
210
- if working_dir == "/app/":
210
+ if working_dir == "/workspace/":
211
211
  stop_docker_locust()
212
212
 
213
213
  elif working_dir == "" or working_dir == "/vagrant/":
@@ -0,0 +1,99 @@
1
+ import os
2
+ import shutil
3
+ import stat
4
+ import click
5
+ from pathlib import Path
6
+ from jinja2 import Environment, FileSystemLoader, select_autoescape
7
+
8
+ from splent_cli.utils.path_utils import PathUtils
9
+
10
+ def pascalcase(s):
11
+ return "".join(word.capitalize() for word in s.split("_"))
12
+
13
+
14
+ def setup_jinja_env():
15
+ env = Environment(
16
+ loader=FileSystemLoader(searchpath=PathUtils.get_splent_cli_templates_dir()),
17
+ autoescape=select_autoescape(["html", "xml", "j2"]),
18
+ )
19
+ env.filters["pascalcase"] = pascalcase
20
+ return env
21
+
22
+ def render_and_write_file(env, template_name, filename, context):
23
+ content = env.get_template(template_name).render(context) + "\n"
24
+ os.makedirs(os.path.dirname(filename), exist_ok=True)
25
+ with open(filename, "w") as f:
26
+ f.write(content)
27
+
28
+ @click.command("product:create", help="Creates a new product with a given name.")
29
+ @click.argument("name")
30
+ @click.option("--features-file", type=click.Path(exists=True), help="Path to features.txt")
31
+ def make_product(name, features_file):
32
+ env = setup_jinja_env()
33
+ context = {
34
+ "product_name": name,
35
+ "pascal_name": pascalcase(name)
36
+ }
37
+
38
+ base_path = os.path.join(PathUtils.get_working_dir(), name)
39
+ src_path = os.path.join(base_path, "src", name)
40
+
41
+ if os.path.exists(base_path):
42
+ click.echo(click.style(f"The product '{name}' already exists.", fg="red"))
43
+ return
44
+
45
+ # Crear carpetas base
46
+ for subdir in ["entrypoints", "scripts", f"src/{name}"]:
47
+ os.makedirs(os.path.join(base_path, subdir), exist_ok=True)
48
+ open(os.path.join(base_path, "src", "__init__.py"), "a").close()
49
+
50
+ # Archivos desde plantillas
51
+ files_and_templates = {
52
+ "entrypoints/dev_entrypoint.sh": "product/product_dev_entrypoint.sh.j2",
53
+ "scripts/00_install_features.sh": "product/product_00_install_features.sh.j2",
54
+ "scripts/01_compile_assets.sh": "product/product_01_compile_assets.sh.j2",
55
+ "scripts/02_0_db_wait_connection.sh": "product/product_02_0_db_wait_connection.sh.j2",
56
+ "scripts/02_1_db_create_db_test.sh": "product/product_02_1_db_create_db_test.sh.j2",
57
+ "scripts/03_initialize_migrations.sh": "product/product_03_initialize_migrations.sh.j2",
58
+ "scripts/04_handle_migrations.sh": "product/product_04_handle_migrations.sh.j2",
59
+ "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",
63
+ "LICENSE": "product/product_LICENSE.j2",
64
+ "package.json": "product/product_package.json.j2",
65
+ ".gitignore": "product/product_.gitignore.j2",
66
+ f"src/{name}/__init__.py": "product/product_init.py.j2",
67
+
68
+ }
69
+
70
+ for rel_path, tpl in files_and_templates.items():
71
+ 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
80
+ uid = 1000
81
+ gid = 1000
82
+
83
+ os.chown(base_path, uid, gid)
84
+ os.chmod(base_path, stat.S_IRWXU | stat.S_IRWXG | stat.S_IROTH | stat.S_IXOTH)
85
+
86
+ for root, dirs, files in os.walk(base_path):
87
+ for d in dirs:
88
+ dir_path = os.path.join(root, d)
89
+ os.chown(dir_path, uid, gid)
90
+ os.chmod(dir_path, stat.S_IRWXU | stat.S_IRWXG | stat.S_IROTH | stat.S_IXOTH)
91
+ for f in files:
92
+ file_path = os.path.join(root, f)
93
+ os.chown(file_path, uid, gid)
94
+ os.chmod(
95
+ file_path,
96
+ stat.S_IRUSR | stat.S_IWUSR | stat.S_IRGRP | stat.S_IWGRP | stat.S_IROTH,
97
+ )
98
+
99
+ click.echo(click.style(f"✅ Product '{name}' created successfully in {base_path}", fg="green"))
@@ -56,7 +56,7 @@ def selenium(module):
56
56
  if module:
57
57
  validate_module(module)
58
58
 
59
- if working_dir == "/app/":
59
+ if working_dir == "/workspace/":
60
60
 
61
61
  click.echo(
62
62
  click.style(
@@ -0,0 +1,62 @@
1
+ import logging
2
+ import click
3
+ import os
4
+ import subprocess
5
+
6
+ from splent_cli.utils.path_utils import PathUtils
7
+
8
+ logger = logging.getLogger(__name__)
9
+
10
+ FEATURES_FILE = PathUtils.get_features_file()
11
+
12
+
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()]
18
+
19
+
20
+ @click.command("webpack:compile", help="Compile webpack for one or all features.")
21
+ @click.argument("feature_name", required=False)
22
+ @click.option("--watch", is_flag=True, help="Enable watch mode for development.")
23
+ def webpack_compile(feature_name, watch):
24
+ production = os.getenv("FLASK_ENV", "develop") == "production"
25
+
26
+ def normalize_feature_name(name):
27
+ return name if name.startswith("splent_feature_") else f"splent_feature_{name}"
28
+
29
+ features = (
30
+ [normalize_feature_name(feature_name)]
31
+ if feature_name
32
+ else get_features()
33
+ )
34
+
35
+ for feature in features:
36
+ compile_feature(feature, watch, production)
37
+
38
+
39
+ def compile_feature(feature, watch, production):
40
+ webpack_file = os.path.join("/workspace", feature, "src", feature, "assets", "js", "webpack.config.js")
41
+
42
+ if not os.path.exists(webpack_file):
43
+ click.echo(click.style(f"⚠ No webpack.config.js found in {feature}, skipping...", fg="yellow"))
44
+ return
45
+
46
+ click.echo(click.style(f"🚀 Compiling {feature}...", fg="cyan"))
47
+
48
+ mode = "production" if production else "development"
49
+ extra_flags = "--devtool source-map --no-cache" if not production else ""
50
+ watch_flag = "--watch" if watch and not production else ""
51
+
52
+ webpack_command = f"npx webpack --config {webpack_file} --mode {mode} {watch_flag} {extra_flags} --color"
53
+
54
+ try:
55
+ if watch:
56
+ subprocess.Popen(webpack_command, shell=True, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
57
+ click.echo(click.style(f"👀 Watching {feature} in {mode} mode...", fg="blue"))
58
+ else:
59
+ subprocess.run(webpack_command, shell=True, check=True)
60
+ click.echo(click.style(f"✅ Successfully compiled {feature} in {mode} mode!", fg="green"))
61
+ except subprocess.CalledProcessError as e:
62
+ click.echo(click.style(f"❌ Error compiling {feature}: {e}", fg="red"))
File without changes