webstarter-cli 0.1.0__py3-none-any.whl
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.
- webstarter/__init__.py +8 -0
- webstarter/cli.py +28 -0
- webstarter/commands/add.py +48 -0
- webstarter/commands/create.py +40 -0
- webstarter/commands/init.py +28 -0
- webstarter/core/__init__.py +16 -0
- webstarter/core/exceptions.py +21 -0
- webstarter/core/generator.py +74 -0
- webstarter/core/logger.py +37 -0
- webstarter/core/project_builder.py +31 -0
- webstarter/core/validator.py +34 -0
- webstarter/templates/base/css/style.css +9 -0
- webstarter/templates/base/index.html +28 -0
- webstarter/templates/base/js/main.js +1 -0
- webstarter/templates/bootstrap/cdn.html +2 -0
- webstarter/templates/components/navbar.html +10 -0
- webstarter/templates/dark/dark-theme.css +10 -0
- webstarter/templates/multipage/about.html +18 -0
- webstarter/templates/multipage/router.js +6 -0
- webstarter/utils/copier.py +29 -0
- webstarter/utils/file_manager.py +46 -0
- webstarter_cli-0.1.0.dist-info/METADATA +42 -0
- webstarter_cli-0.1.0.dist-info/RECORD +27 -0
- webstarter_cli-0.1.0.dist-info/WHEEL +5 -0
- webstarter_cli-0.1.0.dist-info/entry_points.txt +2 -0
- webstarter_cli-0.1.0.dist-info/licenses/LICENSE +21 -0
- webstarter_cli-0.1.0.dist-info/top_level.txt +1 -0
webstarter/__init__.py
ADDED
webstarter/cli.py
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import typer
|
|
2
|
+
from webstarter.commands import create, add, init
|
|
3
|
+
from webstarter.core.logger import Logger
|
|
4
|
+
|
|
5
|
+
# Creamos la instancia principal de Typer
|
|
6
|
+
app = typer.Typer(
|
|
7
|
+
name="webstarter",
|
|
8
|
+
help="WebStarter: Generador profesional de proyectos web desde la CLI.",
|
|
9
|
+
add_completion=False
|
|
10
|
+
)
|
|
11
|
+
|
|
12
|
+
# Registramos los subcomandos de la carpeta commands
|
|
13
|
+
app.command(name="create")(create.execute)
|
|
14
|
+
app.command(name="add")(add.execute)
|
|
15
|
+
app.command(name="init")(init.execute)
|
|
16
|
+
|
|
17
|
+
@app.callback(invoke_without_command=True)
|
|
18
|
+
def main(ctx: typer.Context):
|
|
19
|
+
"""
|
|
20
|
+
Herramienta para automatizar la creación de entornos frontend.
|
|
21
|
+
"""
|
|
22
|
+
# Si el usuario no invoca ningún subcomando (ej: solo escribe 'webstarter')
|
|
23
|
+
if ctx.invoked_subcommand is None:
|
|
24
|
+
Logger.welcome() # Mostramos el panel visual pro que creamos en logger.py
|
|
25
|
+
print(ctx.get_help())
|
|
26
|
+
|
|
27
|
+
if __name__ == "__main__":
|
|
28
|
+
app()
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import typer
|
|
2
|
+
from pathlib import Path
|
|
3
|
+
from webstarter.core.logger import Logger
|
|
4
|
+
from webstarter.utils.file_manager import FileManager
|
|
5
|
+
|
|
6
|
+
def execute(
|
|
7
|
+
component: str = typer.Argument(..., help="Componente a añadir (ej: navbar, card, footer)")
|
|
8
|
+
):
|
|
9
|
+
"""
|
|
10
|
+
Añade un componente específico al proyecto actual inyectándolo antes del cierre del body.
|
|
11
|
+
"""
|
|
12
|
+
# 1. Detectar el directorio de trabajo actual (donde el usuario quiere añadir el componente)
|
|
13
|
+
cwd = Path.cwd()
|
|
14
|
+
index_path = cwd / "index.html"
|
|
15
|
+
|
|
16
|
+
# 2. Verificación de seguridad: ¿Existe el archivo destino?
|
|
17
|
+
if not index_path.exists():
|
|
18
|
+
Logger.error("No se encontró 'index.html'. Asegúrate de estar en la raíz de un proyecto WebStarter.")
|
|
19
|
+
raise typer.Exit(code=1)
|
|
20
|
+
|
|
21
|
+
# 3. LOCALIZACIÓN DINÁMICA DE PLANTILLAS:
|
|
22
|
+
# Resolvemos la ruta absoluta del paquete instalado para encontrar la carpeta templates
|
|
23
|
+
base_package_path = Path(__file__).resolve().parent.parent
|
|
24
|
+
template_path = base_package_path / "templates" / "components" / f"{component}.html"
|
|
25
|
+
|
|
26
|
+
# 4. Verificación de la plantilla solicitada
|
|
27
|
+
if not template_path.exists():
|
|
28
|
+
Logger.error(f"El componente '{component}' no existe en la biblioteca de WebStarter.")
|
|
29
|
+
raise typer.Exit(code=1)
|
|
30
|
+
|
|
31
|
+
try:
|
|
32
|
+
# 5. Lectura del componente y del proyecto actual
|
|
33
|
+
component_html = template_path.read_text(encoding="utf-8")
|
|
34
|
+
current_index = index_path.read_text(encoding="utf-8")
|
|
35
|
+
|
|
36
|
+
# 6. Lógica de inyección de código
|
|
37
|
+
if "</body>" in current_index:
|
|
38
|
+
# Insertamos el componente con una indentación limpia antes de cerrar el body
|
|
39
|
+
new_index = current_index.replace("</body>", f" {component_html}\n</body>")
|
|
40
|
+
FileManager.write_file(index_path, new_index)
|
|
41
|
+
Logger.success(f"Componente '[bold cyan]{component}[/bold cyan]' añadido con éxito a index.html")
|
|
42
|
+
else:
|
|
43
|
+
Logger.error("Error estructural: No se pudo encontrar la etiqueta </body> en index.html")
|
|
44
|
+
raise typer.Exit(code=1)
|
|
45
|
+
|
|
46
|
+
except Exception as e:
|
|
47
|
+
Logger.error(f"Error inesperado al procesar el archivo: {e}")
|
|
48
|
+
raise typer.Exit(code=1)
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import typer
|
|
2
|
+
from typing import Optional
|
|
3
|
+
from webstarter.core.generator import ProjectGenerator
|
|
4
|
+
from webstarter.core.logger import Logger
|
|
5
|
+
from webstarter.core.exceptions import WebStarterError
|
|
6
|
+
from webstarter.core.validator import Validator # Importación necesaria
|
|
7
|
+
|
|
8
|
+
def execute(
|
|
9
|
+
name: str = typer.Argument(..., help="Nombre de la carpeta del proyecto"),
|
|
10
|
+
bootstrap: bool = typer.Option(False, "--bootstrap", "-b", help="Incluye soporte para Bootstrap"),
|
|
11
|
+
dark: bool = typer.Option(False, "--dark", "-d", help="Configura el proyecto en modo oscuro")
|
|
12
|
+
):
|
|
13
|
+
"""
|
|
14
|
+
Crea un nuevo proyecto web con una estructura profesional.
|
|
15
|
+
"""
|
|
16
|
+
try:
|
|
17
|
+
# VALIDACIÓN PREVENTIVA: Aseguramos que el nombre sea apto
|
|
18
|
+
Validator.validate_name(name)
|
|
19
|
+
|
|
20
|
+
# Instanciamos el generador con las opciones elegidas
|
|
21
|
+
generator = ProjectGenerator(
|
|
22
|
+
project_name=name,
|
|
23
|
+
bootstrap=bootstrap,
|
|
24
|
+
dark_mode=dark
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
# Feedback visual para el usuario
|
|
28
|
+
Logger.info(f"Construyendo proyecto: [bold cyan]{name}[/bold cyan]...")
|
|
29
|
+
|
|
30
|
+
# Ejecución del motor de generación
|
|
31
|
+
generator.build()
|
|
32
|
+
|
|
33
|
+
except WebStarterError as e:
|
|
34
|
+
# Errores controlados de nuestra aplicación
|
|
35
|
+
Logger.error(str(e))
|
|
36
|
+
raise typer.Exit(code=1)
|
|
37
|
+
except Exception as e:
|
|
38
|
+
# Errores inesperados del sistema
|
|
39
|
+
Logger.error(f"Ocurrió un error inesperado: {e}")
|
|
40
|
+
raise typer.Exit(code=1)
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import typer
|
|
2
|
+
from pathlib import Path
|
|
3
|
+
from webstarter.core.logger import Logger
|
|
4
|
+
|
|
5
|
+
def execute():
|
|
6
|
+
"""
|
|
7
|
+
Inicializa una configuración de WebStarter en el directorio actual.
|
|
8
|
+
"""
|
|
9
|
+
Logger.info("Inicializando configuración de WebStarter...")
|
|
10
|
+
|
|
11
|
+
# 1. Definimos la ruta del archivo de configuración en el directorio actual
|
|
12
|
+
config_path = Path.cwd() / ".webstarter.json"
|
|
13
|
+
|
|
14
|
+
# 2. Verificamos si ya existe para evitar sobrescribir datos del usuario
|
|
15
|
+
if not config_path.exists():
|
|
16
|
+
try:
|
|
17
|
+
# 3. Creamos un archivo de configuración base
|
|
18
|
+
# Esto permitirá que en el futuro tu CLI sepa qué versión se usó o qué plugins hay
|
|
19
|
+
config_content = '{"version": "0.1.0", "plugins": [], "created_at": "2026"}'
|
|
20
|
+
config_path.write_text(config_content, encoding="utf-8")
|
|
21
|
+
|
|
22
|
+
Logger.success("Archivo de configuración '.webstarter.json' creado exitosamente.")
|
|
23
|
+
Logger.success("Entorno preparado.")
|
|
24
|
+
except Exception as e:
|
|
25
|
+
Logger.error(f"No se pudo crear el archivo de configuración: {e}")
|
|
26
|
+
raise typer.Exit(code=1)
|
|
27
|
+
else:
|
|
28
|
+
Logger.warning("El entorno ya contiene una configuración de WebStarter activa.")
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# webstarter/core/__init__.py
|
|
2
|
+
|
|
3
|
+
from .generator import ProjectGenerator
|
|
4
|
+
from .logger import Logger
|
|
5
|
+
from .validator import Validator
|
|
6
|
+
from .project_builder import ProjectBuilder
|
|
7
|
+
from .exceptions import WebStarterError
|
|
8
|
+
|
|
9
|
+
# Esto define qué se exporta cuando alguien hace: from webstarter.core import *
|
|
10
|
+
__all__ = [
|
|
11
|
+
"ProjectGenerator",
|
|
12
|
+
"Logger",
|
|
13
|
+
"Validator",
|
|
14
|
+
"ProjectBuilder",
|
|
15
|
+
"WebStarterError"
|
|
16
|
+
]
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
class WebStarterError(Exception):
|
|
2
|
+
"""Error base para la aplicación."""
|
|
3
|
+
pass
|
|
4
|
+
|
|
5
|
+
class ProjectAlreadyExistsError(WebStarterError):
|
|
6
|
+
"""Se lanza si la carpeta de destino ya existe."""
|
|
7
|
+
pass
|
|
8
|
+
|
|
9
|
+
class TemplateNotFoundError(WebStarterError):
|
|
10
|
+
"""Se lanza si no se encuentra una carpeta de plantilla."""
|
|
11
|
+
pass
|
|
12
|
+
|
|
13
|
+
# --- ADICIONES RECOMENDADAS PARA COMPLETAR EL CORE ---
|
|
14
|
+
|
|
15
|
+
class ValidationError(WebStarterError):
|
|
16
|
+
"""Se lanza si el nombre del proyecto o componente es inválido."""
|
|
17
|
+
pass
|
|
18
|
+
|
|
19
|
+
class InjectionError(WebStarterError):
|
|
20
|
+
"""Se lanza si falla la inyección de componentes (ej. falta etiqueta </body>)."""
|
|
21
|
+
pass
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
from pathlib import Path
|
|
2
|
+
from jinja2 import Environment, FileSystemLoader
|
|
3
|
+
from webstarter.core.logger import Logger
|
|
4
|
+
from webstarter.utils.file_manager import FileManager
|
|
5
|
+
from webstarter.core.project_builder import ProjectBuilder
|
|
6
|
+
from webstarter.utils.copier import Copier # Importación añadida
|
|
7
|
+
|
|
8
|
+
class ProjectGenerator:
|
|
9
|
+
def __init__(self, project_name: str, bootstrap: bool = False, dark_mode: bool = False, multi_page: bool = False):
|
|
10
|
+
self.project_name = project_name
|
|
11
|
+
self.bootstrap = bootstrap
|
|
12
|
+
self.dark_mode = dark_mode
|
|
13
|
+
self.multi_page = multi_page # Nueva opción para proyectos MPA
|
|
14
|
+
self.target_path = Path.cwd() / project_name
|
|
15
|
+
|
|
16
|
+
# Resolución de ruta absoluta para las plantillas instaladas
|
|
17
|
+
self.template_dir = Path(__file__).resolve().parent.parent / "templates"
|
|
18
|
+
|
|
19
|
+
# Configuración de Jinja2
|
|
20
|
+
self.env = Environment(loader=FileSystemLoader(str(self.template_dir)))
|
|
21
|
+
|
|
22
|
+
def build(self):
|
|
23
|
+
"""Ejecuta el flujo completo de construcción del proyecto."""
|
|
24
|
+
try:
|
|
25
|
+
# 1. Crear estructura física de carpetas
|
|
26
|
+
ProjectBuilder.init_structure(self.target_path)
|
|
27
|
+
|
|
28
|
+
# 2. Generar index.html principal
|
|
29
|
+
self._generate_index()
|
|
30
|
+
|
|
31
|
+
# 3. Copiar assets estáticos base (CSS/JS)
|
|
32
|
+
base_templates = self.template_dir / "base"
|
|
33
|
+
FileManager.copy_static_files(base_templates / "css", self.target_path / "css")
|
|
34
|
+
FileManager.copy_static_files(base_templates / "js", self.target_path / "js")
|
|
35
|
+
|
|
36
|
+
# 4. Lógica Multi-Página (Si se solicita)
|
|
37
|
+
if self.multi_page:
|
|
38
|
+
self._generate_multipage()
|
|
39
|
+
|
|
40
|
+
Logger.success(f"Proyecto '[bold cyan]{self.project_name}[/bold cyan]' generado correctamente.")
|
|
41
|
+
|
|
42
|
+
except Exception as e:
|
|
43
|
+
Logger.error(f"Fallo en la construcción del proyecto: {e}")
|
|
44
|
+
raise
|
|
45
|
+
|
|
46
|
+
def _generate_index(self):
|
|
47
|
+
"""Renderiza la plantilla index.html aplicando las opciones elegidas."""
|
|
48
|
+
template = self.env.get_template("base/index.html")
|
|
49
|
+
output = template.render(
|
|
50
|
+
title=self.project_name,
|
|
51
|
+
use_bootstrap=self.bootstrap,
|
|
52
|
+
dark_mode=self.dark_mode
|
|
53
|
+
)
|
|
54
|
+
FileManager.write_file(self.target_path / "index.html", output)
|
|
55
|
+
|
|
56
|
+
def _generate_multipage(self):
|
|
57
|
+
"""Genera archivos adicionales para una arquitectura de varias páginas."""
|
|
58
|
+
Logger.info("Añadiendo soporte multi-página...")
|
|
59
|
+
|
|
60
|
+
# Renderizar página About (Nosotros)
|
|
61
|
+
template_about = self.env.get_template("multipage/about.html")
|
|
62
|
+
output_about = template_about.render(
|
|
63
|
+
title=self.project_name,
|
|
64
|
+
use_bootstrap=self.bootstrap,
|
|
65
|
+
dark_mode=self.dark_mode
|
|
66
|
+
)
|
|
67
|
+
FileManager.write_file(self.target_path / "about.html", output_about)
|
|
68
|
+
|
|
69
|
+
# Copiar lógica de navegación usando Copier
|
|
70
|
+
# Esto copia el contenido de multipage/js a la carpeta js del proyecto
|
|
71
|
+
multi_js_src = self.template_dir / "multipage"
|
|
72
|
+
if (multi_js_src / "router.js").exists():
|
|
73
|
+
router_content = (multi_js_src / "router.js").read_text(encoding="utf-8")
|
|
74
|
+
FileManager.write_file(self.target_path / "js" / "router.js", router_content)
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
from rich.console import Console
|
|
2
|
+
from rich.panel import Panel
|
|
3
|
+
|
|
4
|
+
# Inicializamos la consola globalmente para este módulo
|
|
5
|
+
console = Console()
|
|
6
|
+
|
|
7
|
+
class Logger:
|
|
8
|
+
@staticmethod
|
|
9
|
+
def welcome():
|
|
10
|
+
"""Muestra un panel de bienvenida al iniciar la herramienta."""
|
|
11
|
+
console.print(
|
|
12
|
+
Panel.fit(
|
|
13
|
+
"🚀 [bold cyan]WebStarter CLI[/bold cyan]\n[italic white]Tu generador profesional de proyectos web[/italic white]",
|
|
14
|
+
border_style="blue",
|
|
15
|
+
title="v0.1.0"
|
|
16
|
+
)
|
|
17
|
+
)
|
|
18
|
+
|
|
19
|
+
@staticmethod
|
|
20
|
+
def info(message: str):
|
|
21
|
+
"""Muestra un mensaje informativo en azul."""
|
|
22
|
+
console.print(f"[bold blue]INFO:[/bold blue] {message}")
|
|
23
|
+
|
|
24
|
+
@staticmethod
|
|
25
|
+
def success(message: str):
|
|
26
|
+
"""Muestra un mensaje de éxito en verde."""
|
|
27
|
+
console.print(f"[bold green]SUCCESS:[/bold green] {message}")
|
|
28
|
+
|
|
29
|
+
@staticmethod
|
|
30
|
+
def error(message: str):
|
|
31
|
+
"""Muestra un mensaje de error crítico en rojo."""
|
|
32
|
+
console.print(f"[bold red]ERROR:[/bold red] {message}")
|
|
33
|
+
|
|
34
|
+
@staticmethod
|
|
35
|
+
def warning(message: str):
|
|
36
|
+
"""Muestra un aviso o advertencia en amarillo."""
|
|
37
|
+
console.print(f"[bold yellow]WARNING:[/bold yellow] {message}")
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
from pathlib import Path
|
|
2
|
+
from webstarter.utils.file_manager import FileManager
|
|
3
|
+
|
|
4
|
+
class ProjectBuilder:
|
|
5
|
+
"""
|
|
6
|
+
Clase responsable de construir la estructura física de directorios
|
|
7
|
+
del nuevo proyecto web.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
@staticmethod
|
|
11
|
+
def init_structure(base_path: Path):
|
|
12
|
+
"""
|
|
13
|
+
Crea el esqueleto de carpetas inicial del proyecto.
|
|
14
|
+
|
|
15
|
+
Args:
|
|
16
|
+
base_path (Path): La ruta raíz donde se creará el proyecto.
|
|
17
|
+
"""
|
|
18
|
+
# 1. Crear el directorio raíz del proyecto
|
|
19
|
+
FileManager.create_directory(base_path)
|
|
20
|
+
|
|
21
|
+
# 2. Definir la lista de subcarpetas necesarias para un proyecto profesional
|
|
22
|
+
folders = [
|
|
23
|
+
"css", # Para hojas de estilo
|
|
24
|
+
"js", # Para lógica de scripts
|
|
25
|
+
"assets/img", # Para recursos multimedia e imágenes
|
|
26
|
+
]
|
|
27
|
+
|
|
28
|
+
# 3. Crear cada subcarpeta de forma iterativa
|
|
29
|
+
for folder in folders:
|
|
30
|
+
subfolder_path = base_path / folder
|
|
31
|
+
FileManager.create_directory(subfolder_path)
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import re
|
|
2
|
+
from webstarter.core.exceptions import WebStarterError # Usamos nuestra excepción base
|
|
3
|
+
|
|
4
|
+
class Validator:
|
|
5
|
+
"""
|
|
6
|
+
Clase encargada de validar las entradas del usuario para asegurar
|
|
7
|
+
la integridad del sistema de archivos.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
@staticmethod
|
|
11
|
+
def validate_name(name: str) -> bool:
|
|
12
|
+
"""
|
|
13
|
+
Valida que el nombre del proyecto sea seguro.
|
|
14
|
+
|
|
15
|
+
Reglas:
|
|
16
|
+
- Solo letras (A-Z, a-z), números (0-9).
|
|
17
|
+
- Permite guiones (-) y guiones bajos (_).
|
|
18
|
+
- No permite espacios ni caracteres especiales.
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
# Expresión regular: permite letras, números, guiones y guiones bajos
|
|
22
|
+
pattern = r'^[a-zA-Z0-9_-]+$'
|
|
23
|
+
|
|
24
|
+
if not re.match(pattern, name):
|
|
25
|
+
raise WebStarterError(
|
|
26
|
+
f"El nombre '{name}' es inválido. "
|
|
27
|
+
"Usa solo letras, números, guiones (-) o guiones bajos (_), sin espacios."
|
|
28
|
+
)
|
|
29
|
+
|
|
30
|
+
# Opcional: Evitar nombres demasiado cortos
|
|
31
|
+
if len(name) < 2:
|
|
32
|
+
raise WebStarterError("El nombre del proyecto es demasiado corto.")
|
|
33
|
+
|
|
34
|
+
return True
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="es">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8">
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
6
|
+
<title>{{ title }}</title>
|
|
7
|
+
|
|
8
|
+
{% if use_bootstrap %}
|
|
9
|
+
{% include "bootstrap/cdn.html" %}
|
|
10
|
+
{% endif %}
|
|
11
|
+
|
|
12
|
+
<link rel="stylesheet" href="css/style.css">
|
|
13
|
+
</head>
|
|
14
|
+
<body class="{% if dark_mode %}bg-dark text-white{% endif %}">
|
|
15
|
+
|
|
16
|
+
<div class="container mt-5">
|
|
17
|
+
<header class="text-center mb-5">
|
|
18
|
+
<h1>Bienvenido a {{ title }}</h1>
|
|
19
|
+
<p class="lead">Proyecto generado profesionalmente con WebStarter CLI.</p>
|
|
20
|
+
</header>
|
|
21
|
+
|
|
22
|
+
<main>
|
|
23
|
+
</main>
|
|
24
|
+
</div>
|
|
25
|
+
|
|
26
|
+
<script src="js/script.js"></script>
|
|
27
|
+
</body>
|
|
28
|
+
</html>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
console.log("WebStarter: ¡Proyecto cargado con éxito!");
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
<nav class="navbar navbar-expand-lg border-bottom mb-4">
|
|
2
|
+
<div class="container-fluid">
|
|
3
|
+
<a class="navbar-brand" href="#">{{ title|default('WebStarter') }}</a>
|
|
4
|
+
<div class="navbar-nav">
|
|
5
|
+
<a class="nav-link active" href="#">Inicio</a>
|
|
6
|
+
<a class="nav-link" href="#">Servicios</a>
|
|
7
|
+
<a class="nav-link" href="#">Contacto</a>
|
|
8
|
+
</div>
|
|
9
|
+
</div>
|
|
10
|
+
</nav>
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="es">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8">
|
|
5
|
+
<title>Nosotros - {{ title }}</title>
|
|
6
|
+
{% if use_bootstrap %}
|
|
7
|
+
{% include "bootstrap/cdn.html" %}
|
|
8
|
+
{% endif %}
|
|
9
|
+
<link rel="stylesheet" href="css/style.css">
|
|
10
|
+
</head>
|
|
11
|
+
<body class="{% if dark_mode %}bg-dark text-white{% endif %}">
|
|
12
|
+
<div class="container mt-5">
|
|
13
|
+
<h1>Sobre Nosotros</h1>
|
|
14
|
+
<p>Esta es una página secundaria generada automáticamente.</p>
|
|
15
|
+
<a href="index.html" class="btn btn-primary">Volver al Inicio</a>
|
|
16
|
+
</div>
|
|
17
|
+
</body>
|
|
18
|
+
</html>
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import shutil
|
|
2
|
+
from pathlib import Path
|
|
3
|
+
from webstarter.core.logger import Logger
|
|
4
|
+
|
|
5
|
+
class Copier:
|
|
6
|
+
"""
|
|
7
|
+
Clase utilitaria encargada de la transferencia física de directorios
|
|
8
|
+
y archivos de plantillas hacia el proyecto de destino.
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
@staticmethod
|
|
12
|
+
def copy_template_folder(src: Path, dest: Path):
|
|
13
|
+
"""
|
|
14
|
+
Copia recursivamente una carpeta completa al destino especificado.
|
|
15
|
+
|
|
16
|
+
Args:
|
|
17
|
+
src (Path): Carpeta de origen (dentro de templates).
|
|
18
|
+
dest (Path): Carpeta de destino (dentro del nuevo proyecto).
|
|
19
|
+
"""
|
|
20
|
+
if not src.exists():
|
|
21
|
+
Logger.warning(f"La fuente de plantillas '{src.name}' no existe. Saltando copia.")
|
|
22
|
+
return
|
|
23
|
+
|
|
24
|
+
try:
|
|
25
|
+
# dirs_exist_ok=True permite fusionar carpetas si ya existen
|
|
26
|
+
shutil.copytree(src, dest, dirs_exist_ok=True)
|
|
27
|
+
except Exception as e:
|
|
28
|
+
Logger.error(f"Error crítico al copiar archivos de {src.name}: {e}")
|
|
29
|
+
raise
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import shutil
|
|
2
|
+
from pathlib import Path
|
|
3
|
+
from webstarter.core.exceptions import ProjectAlreadyExistsError, WebStarterError
|
|
4
|
+
from webstarter.core.logger import Logger
|
|
5
|
+
|
|
6
|
+
class FileManager:
|
|
7
|
+
"""
|
|
8
|
+
Clase encargada de las operaciones físicas de entrada/salida (I/O)
|
|
9
|
+
en el sistema de archivos.
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
@staticmethod
|
|
13
|
+
def create_directory(path: Path):
|
|
14
|
+
"""Crea un directorio de forma segura."""
|
|
15
|
+
if path.exists():
|
|
16
|
+
# Usamos la excepción personalizada que definiste
|
|
17
|
+
raise ProjectAlreadyExistsError(f"El directorio '{path.name}' ya existe.")
|
|
18
|
+
|
|
19
|
+
try:
|
|
20
|
+
path.mkdir(parents=True, exist_ok=True)
|
|
21
|
+
except PermissionError:
|
|
22
|
+
raise WebStarterError(f"No tienes permisos para crear la carpeta en: {path}")
|
|
23
|
+
|
|
24
|
+
@staticmethod
|
|
25
|
+
def copy_static_files(src: Path, dest: Path):
|
|
26
|
+
"""Copia archivos estáticos (CSS, JS, Imágenes) al proyecto."""
|
|
27
|
+
if not src.exists():
|
|
28
|
+
return # Evitamos errores si una carpeta base opcional no existe
|
|
29
|
+
|
|
30
|
+
try:
|
|
31
|
+
shutil.copytree(src, dest, dirs_exist_ok=True)
|
|
32
|
+
except Exception as e:
|
|
33
|
+
Logger.error(f"Error al copiar archivos estáticos: {e}")
|
|
34
|
+
|
|
35
|
+
@staticmethod
|
|
36
|
+
def write_file(path: Path, content: str):
|
|
37
|
+
"""Escribe contenido de texto en un archivo con codificación UTF-8."""
|
|
38
|
+
try:
|
|
39
|
+
# Aseguramos que la carpeta que contendrá al archivo exista
|
|
40
|
+
if not path.parent.exists():
|
|
41
|
+
path.parent.mkdir(parents=True, exist_ok=True)
|
|
42
|
+
|
|
43
|
+
path.write_text(content, encoding="utf-8")
|
|
44
|
+
except Exception as e:
|
|
45
|
+
Logger.error(f"No se pudo escribir el archivo {path.name}: {e}")
|
|
46
|
+
raise WebStarterError(f"Fallo en la escritura de: {path}")
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: webstarter-cli
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Herramienta CLI profesional para generar proyectos web HTML/CSS/JS.
|
|
5
|
+
Author-email: Alex <tu-email@ejemplo.com>
|
|
6
|
+
Requires-Python: >=3.8
|
|
7
|
+
Description-Content-Type: text/markdown
|
|
8
|
+
License-File: LICENSE
|
|
9
|
+
Requires-Dist: typer[all]
|
|
10
|
+
Requires-Dist: rich
|
|
11
|
+
Requires-Dist: jinja2
|
|
12
|
+
Dynamic: license-file
|
|
13
|
+
|
|
14
|
+
# 🚀 WebStarter CLI
|
|
15
|
+
|
|
16
|
+
**WebStarter** es una herramienta de línea de comandos (CLI) profesional diseñada para automatizar la creación de entornos de desarrollo web frontend. Genera estructuras de proyectos limpias con HTML, CSS y JavaScript en segundos.
|
|
17
|
+
|
|
18
|
+
Desarrollado por **Alex**.
|
|
19
|
+
|
|
20
|
+
---
|
|
21
|
+
|
|
22
|
+
## ✨ Características
|
|
23
|
+
|
|
24
|
+
* **Generación Instantánea**: Crea la estructura de carpetas (`css/`, `js/`, `assets/`) de forma automática.
|
|
25
|
+
* **Plantillas Dinámicas**: Usa Jinja2 para renderizar archivos base.
|
|
26
|
+
* **Soporte Bootstrap**: Opción para incluir el CDN de Bootstrap 5 con una sola flag.
|
|
27
|
+
* **Modo Oscuro**: Configuración nativa de temas oscuros desde la creación.
|
|
28
|
+
* **Arquitectura Modular**: Código extensible y fácil de mantener.
|
|
29
|
+
* **Biblioteca de Componentes**: Añade componentes como `navbar` a proyectos existentes.
|
|
30
|
+
|
|
31
|
+
---
|
|
32
|
+
|
|
33
|
+
## 🛠️ Instalación
|
|
34
|
+
|
|
35
|
+
Asegúrate de tener Python 3.8 o superior instalado.
|
|
36
|
+
|
|
37
|
+
1. Clona este repositorio o descarga el código.
|
|
38
|
+
2. Abre una terminal en la carpeta raíz del proyecto.
|
|
39
|
+
3. Instala la herramienta de forma local:
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
pip install .
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
webstarter/__init__.py,sha256=ayLA0VEt_L5Ojz4rNKehMBYRnEoQ4f_LgR3R1lEmLjI,160
|
|
2
|
+
webstarter/cli.py,sha256=rqDiJhw7Z4Ox3VZULRfP2r3rvRLXt0rRL0AqyS_tpA8,921
|
|
3
|
+
webstarter/commands/add.py,sha256=r4uy57G8kNg4pjIsgbAjvHpc04ixuNZXlPuCRjBVOmw,2246
|
|
4
|
+
webstarter/commands/create.py,sha256=hAvWoQzd_qclDVRWAWzaARY-J9inkpG5BgmxBIkrtcU,1535
|
|
5
|
+
webstarter/commands/init.py,sha256=uLrvDpc7wM16BofAXZH3itQgoJITccKTXggoV4FYD38,1263
|
|
6
|
+
webstarter/core/__init__.py,sha256=4QKMauBZw0xt0mjsoWJ-BweoesrqqGHaBCpYd2UJWak,424
|
|
7
|
+
webstarter/core/exceptions.py,sha256=vwAZtZGan5UY64pMYBvLBbJJfYTWuauNpuCY_h7vjCU,654
|
|
8
|
+
webstarter/core/generator.py,sha256=8ASdkGM2UCBBhZONLaQ0BMZ8T8SU0GFZDhb1XK2ef8A,3437
|
|
9
|
+
webstarter/core/logger.py,sha256=FwkIAMdhKR2GsOthhafKHL8uYfucRV4Vb5Mpy8t3fHs,1262
|
|
10
|
+
webstarter/core/project_builder.py,sha256=XnunjhlHiqZb9ImbyQr9ND82wxarTMQHK0fDebpDnWQ,1067
|
|
11
|
+
webstarter/core/validator.py,sha256=q19XbgzPp85uAH8bYjvz5GC2cz8DUPrYY4Q1uKy9jNk,1160
|
|
12
|
+
webstarter/templates/base/index.html,sha256=2U0ItvKEbiNqwuaZoAtf2jD81DJRDKKA6bNUh2k3O1k,743
|
|
13
|
+
webstarter/templates/base/css/style.css,sha256=Zovz5syfvYxpSZkKqcztYQSwwcmkoFM3HHZZuK6R0i0,205
|
|
14
|
+
webstarter/templates/base/js/main.js,sha256=vq9Y46ex8yQZjQgq46CmGPc4hoeIETgLPczfbmiu2Ws,58
|
|
15
|
+
webstarter/templates/bootstrap/cdn.html,sha256=RQiRPt2pSjctsk058YGX_--9UGHMpFFu7Zr020ai7pk,210
|
|
16
|
+
webstarter/templates/components/navbar.html,sha256=UE1UAd57KhWfHIEzTDyZsbUhYOePYOwTA1v7P3_z2vo,378
|
|
17
|
+
webstarter/templates/dark/dark-theme.css,sha256=mDkuIbYlCPzKgehC8aSQO-Ke1BXeBffD4QstWekuRwM,189
|
|
18
|
+
webstarter/templates/multipage/about.html,sha256=2r5nPItRpwksqEcTegJAJosV6GXWOp3Js3zJ0ZQUFZ0,566
|
|
19
|
+
webstarter/templates/multipage/router.js,sha256=n7Mk4ejfN73QEeBqlXXLIIz7AD6Yg6VfAV7r8jU1NRc,187
|
|
20
|
+
webstarter/utils/copier.py,sha256=Pm-NxmI4QXORv51TliR7iBDWoci1ygSJ6B-oJWuzp3Q,1024
|
|
21
|
+
webstarter/utils/file_manager.py,sha256=DmmdYDktksX2_8q83WXp19yYr5We4uWFnPC9w5kul_U,1842
|
|
22
|
+
webstarter_cli-0.1.0.dist-info/licenses/LICENSE,sha256=4HhLGFwdWmQqQNlvV8Safxah32c4jPjKK8EHZpmNuBQ,1080
|
|
23
|
+
webstarter_cli-0.1.0.dist-info/METADATA,sha256=gd9bZWED9iEb4ul-0PkGstYk0hY8hnGqigbH0swQ8zg,1472
|
|
24
|
+
webstarter_cli-0.1.0.dist-info/WHEEL,sha256=YCfwYGOYMi5Jhw2fU4yNgwErybb2IX5PEwBKV4ZbdBo,91
|
|
25
|
+
webstarter_cli-0.1.0.dist-info/entry_points.txt,sha256=09bXLVOhVr9SgYGGAc0Oe626PV5v48nATsgzfHAXy7Q,50
|
|
26
|
+
webstarter_cli-0.1.0.dist-info/top_level.txt,sha256=yDxcNz-fpLJaMjZeLHMhdAcLNtFOH5x7HsURqusQP4k,11
|
|
27
|
+
webstarter_cli-0.1.0.dist-info/RECORD,,
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Alex
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
webstarter
|