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.
- splent_cli-0.0.2/PKG-INFO +22 -0
- {splent_cli-0.0.1 → splent_cli-0.0.2}/pyproject.toml +13 -9
- {splent_cli-0.0.1 → splent_cli-0.0.2/src}/splent_cli/__main__.py +4 -0
- {splent_cli-0.0.1 → splent_cli-0.0.2/src}/splent_cli/cli.py +7 -10
- {splent_cli-0.0.1 → splent_cli-0.0.2/src}/splent_cli/commands/db_reset.py +6 -5
- splent_cli-0.0.2/src/splent_cli/commands/db_seed.py +103 -0
- splent_cli-0.0.2/src/splent_cli/commands/feature_create.py +104 -0
- splent_cli-0.0.2/src/splent_cli/commands/feature_list.py +29 -0
- {splent_cli-0.0.1 → splent_cli-0.0.2/src}/splent_cli/commands/locust.py +3 -3
- splent_cli-0.0.2/src/splent_cli/commands/product_create.py +99 -0
- {splent_cli-0.0.1 → splent_cli-0.0.2/src}/splent_cli/commands/selenium.py +1 -1
- splent_cli-0.0.2/src/splent_cli/commands/webpack_compile.py +62 -0
- splent_cli-0.0.2/src/splent_cli/utils/__init__.py +0 -0
- splent_cli-0.0.2/src/splent_cli/utils/dynamic_imports.py +116 -0
- splent_cli-0.0.2/src/splent_cli/utils/feature_installer.py +54 -0
- {splent_cli-0.0.1 → splent_cli-0.0.2/src}/splent_cli/utils/path_utils.py +35 -49
- splent_cli-0.0.2/src/splent_cli.egg-info/PKG-INFO +22 -0
- splent_cli-0.0.2/src/splent_cli.egg-info/SOURCES.txt +39 -0
- {splent_cli-0.0.1 → splent_cli-0.0.2/src}/splent_cli.egg-info/requires.txt +2 -0
- splent_cli-0.0.1/PKG-INFO +0 -133
- splent_cli-0.0.1/splent_cli/__init__.py +0 -6
- splent_cli-0.0.1/splent_cli/commands/db_seed.py +0 -123
- splent_cli-0.0.1/splent_cli/commands/module_create.py +0 -138
- splent_cli-0.0.1/splent_cli/commands/module_list.py +0 -28
- splent_cli-0.0.1/splent_cli/commands/webpack_compile.py +0 -68
- splent_cli-0.0.1/splent_cli.egg-info/PKG-INFO +0 -133
- splent_cli-0.0.1/splent_cli.egg-info/SOURCES.txt +0 -36
- {splent_cli-0.0.1 → splent_cli-0.0.2}/LICENSE +0 -0
- {splent_cli-0.0.1 → splent_cli-0.0.2}/README.md +0 -0
- {splent_cli-0.0.1 → splent_cli-0.0.2}/setup.cfg +0 -0
- {splent_cli-0.0.1/splent_cli/commands → splent_cli-0.0.2/src/splent_cli}/__init__.py +0 -0
- {splent_cli-0.0.1/splent_cli/utils → splent_cli-0.0.2/src/splent_cli/commands}/__init__.py +0 -0
- {splent_cli-0.0.1 → splent_cli-0.0.2/src}/splent_cli/commands/clear_cache.py +0 -0
- {splent_cli-0.0.1 → splent_cli-0.0.2/src}/splent_cli/commands/clear_log.py +0 -0
- {splent_cli-0.0.1 → splent_cli-0.0.2/src}/splent_cli/commands/clear_uploads.py +0 -0
- {splent_cli-0.0.1 → splent_cli-0.0.2/src}/splent_cli/commands/compose_env.py +0 -0
- {splent_cli-0.0.1 → splent_cli-0.0.2/src}/splent_cli/commands/coverage.py +0 -0
- {splent_cli-0.0.1 → splent_cli-0.0.2/src}/splent_cli/commands/db_console.py +0 -0
- {splent_cli-0.0.1 → splent_cli-0.0.2/src}/splent_cli/commands/db_dump.py +0 -0
- {splent_cli-0.0.1 → splent_cli-0.0.2/src}/splent_cli/commands/db_migrate.py +0 -0
- {splent_cli-0.0.1 → splent_cli-0.0.2/src}/splent_cli/commands/env.py +0 -0
- {splent_cli-0.0.1 → splent_cli-0.0.2/src}/splent_cli/commands/info.py +0 -0
- {splent_cli-0.0.1 → splent_cli-0.0.2/src}/splent_cli/commands/linter.py +0 -0
- {splent_cli-0.0.1 → splent_cli-0.0.2/src}/splent_cli/commands/route_list.py +0 -0
- {splent_cli-0.0.1 → splent_cli-0.0.2/src}/splent_cli/commands/test.py +0 -0
- {splent_cli-0.0.1 → splent_cli-0.0.2/src}/splent_cli/commands/update.py +0 -0
- {splent_cli-0.0.1 → splent_cli-0.0.2/src}/splent_cli.egg-info/dependency_links.txt +0 -0
- {splent_cli-0.0.1 → splent_cli-0.0.2/src}/splent_cli.egg-info/entry_points.txt +0 -0
- {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.
|
|
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 = "
|
|
12
|
-
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
|
-
|
|
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
|
|
7
|
-
from
|
|
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
|
|
18
|
-
|
|
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):
|
|
22
|
+
class SPLENTCLI(FlaskGroup):
|
|
26
23
|
def __init__(self, **kwargs):
|
|
27
|
-
super().__init__(create_app=
|
|
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
|
|
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
|
|
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 =
|
|
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 =
|
|
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}:/
|
|
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 == "/
|
|
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 == "/
|
|
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"))
|
|
@@ -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
|