Skip to content

Commit afe16a2

Browse files
authored
Add myfy frontend build command for production builds (#5)
1 parent ad6d012 commit afe16a2

File tree

3 files changed

+162
-1
lines changed

3 files changed

+162
-1
lines changed

packages/myfy-cli/myfy_cli/commands/frontend.py

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
# Check if frontend module is available
99
try:
1010
from myfy.core.config import load_settings
11+
from myfy.frontend import BuildError, build_frontend
1112
from myfy.frontend.config import FrontendSettings
1213
from myfy.frontend.scaffold import check_frontend_initialized, scaffold_frontend
1314

@@ -18,6 +19,9 @@
1819
frontend_app = typer.Typer(help="Frontend development commands")
1920
console = Console()
2021

22+
# Timeout for npm operations (install and build)
23+
NPM_TIMEOUT = 300 # 5 minutes
24+
2125

2226
def _show_missing_module_error() -> None:
2327
"""Display error message when frontend module is not installed."""
@@ -169,3 +173,55 @@ def init(
169173
except Exception as e:
170174
console.print(f"[red]✗ Error initializing frontend: {e}[/red]")
171175
sys.exit(1)
176+
177+
178+
@frontend_app.command(name="build")
179+
def build() -> None:
180+
"""
181+
Build frontend assets for production.
182+
183+
Runs Vite build to generate optimized, hashed assets with manifest.json
184+
for cache busting.
185+
186+
The build process:
187+
- Compiles JavaScript and CSS with Vite
188+
- Generates unique hashes for each asset (e.g., main-abc123.js)
189+
- Creates manifest.json mapping source files to hashed versions
190+
- Outputs to frontend/static/dist/
191+
192+
Examples:
193+
myfy frontend build
194+
"""
195+
# Check if frontend module is installed
196+
if not HAS_FRONTEND:
197+
_show_missing_module_error()
198+
sys.exit(1)
199+
200+
# Run the build
201+
console.print("[cyan]🏗️ Building frontend assets...[/cyan]")
202+
console.print("")
203+
204+
try:
205+
output = build_frontend(timeout=NPM_TIMEOUT)
206+
207+
# Show build output
208+
if output:
209+
console.print(output)
210+
211+
# Show success message
212+
console.print("")
213+
console.print("[green]✨ Build completed successfully![/green]")
214+
console.print("")
215+
console.print("[bold]Generated files:[/bold]")
216+
console.print(" • frontend/static/dist/.vite/manifest.json")
217+
console.print(" • frontend/static/dist/js/*.js (with unique hashes)")
218+
console.print(" • frontend/static/dist/css/*.css (with unique hashes)")
219+
console.print("")
220+
console.print("[bold]Next steps:[/bold]")
221+
console.print(" 1. Set MYFY_FRONTEND_ENVIRONMENT=production")
222+
console.print(" 2. Deploy your application")
223+
console.print(" 3. Assets will be served from the manifest")
224+
225+
except BuildError as e:
226+
console.print(f"[red]✗ {e}[/red]")
227+
sys.exit(1)

packages/myfy-frontend/myfy/frontend/__init__.py

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,16 @@
55
and zero-config setup for rapid development.
66
"""
77

8+
from .build import BuildError, build_frontend, ensure_npm_dependencies_installed
89
from .module import FrontendModule
910
from .templates import render_template
1011
from .version import __version__
1112

12-
__all__ = ["FrontendModule", "__version__", "render_template"]
13+
__all__ = [
14+
"BuildError",
15+
"FrontendModule",
16+
"__version__",
17+
"build_frontend",
18+
"ensure_npm_dependencies_installed",
19+
"render_template",
20+
]
Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
"""Build logic for frontend assets."""
2+
3+
import subprocess
4+
from pathlib import Path
5+
6+
7+
class BuildError(Exception):
8+
"""Raised when build fails."""
9+
10+
11+
def ensure_npm_dependencies_installed(timeout: int = 300) -> None:
12+
"""
13+
Ensure npm dependencies are installed.
14+
15+
Args:
16+
timeout: Timeout in seconds for npm install
17+
18+
Raises:
19+
BuildError: If npm install fails or times out
20+
"""
21+
node_modules = Path("node_modules")
22+
if node_modules.exists():
23+
return
24+
25+
try:
26+
subprocess.run(
27+
["npm", "install"],
28+
check=True,
29+
capture_output=True,
30+
text=True,
31+
timeout=timeout,
32+
)
33+
except subprocess.TimeoutExpired as e:
34+
raise BuildError(
35+
f"npm install timed out after {timeout} seconds. "
36+
"Please check your network connection and try again."
37+
) from e
38+
except subprocess.CalledProcessError as e:
39+
raise BuildError(f"Failed to install dependencies: {e.stderr}") from e
40+
except FileNotFoundError as e:
41+
raise BuildError("npm not found. Please install Node.js to build the frontend.") from e
42+
43+
44+
def build_frontend(timeout: int = 300) -> str:
45+
"""
46+
Build frontend assets for production.
47+
48+
Runs npm run build to execute Vite build, which:
49+
- Compiles JavaScript and CSS with Vite
50+
- Generates unique hashes for each asset (e.g., main-abc123.js)
51+
- Creates manifest.json mapping source files to hashed versions
52+
- Outputs to frontend/static/dist/
53+
54+
Args:
55+
timeout: Timeout in seconds for the build process
56+
57+
Returns:
58+
Build output as string
59+
60+
Raises:
61+
BuildError: If package.json is missing, build fails, or times out
62+
"""
63+
# Check if package.json exists
64+
package_json = Path("package.json")
65+
if not package_json.exists():
66+
raise BuildError(
67+
"No package.json found. "
68+
"Please run 'myfy frontend init' first to initialize the frontend."
69+
)
70+
71+
# Ensure dependencies are installed
72+
ensure_npm_dependencies_installed(timeout=timeout)
73+
74+
# Run the build
75+
try:
76+
result = subprocess.run(
77+
["npm", "run", "build"],
78+
check=True,
79+
capture_output=True,
80+
text=True,
81+
timeout=timeout,
82+
)
83+
return result.stdout
84+
except subprocess.TimeoutExpired as e:
85+
raise BuildError(
86+
f"Build timed out after {timeout} seconds. "
87+
"The build process is taking too long. Please check for issues."
88+
) from e
89+
except subprocess.CalledProcessError as e:
90+
error_msg = "Build failed"
91+
if e.stderr:
92+
error_msg += f": {e.stderr}"
93+
if e.stdout:
94+
error_msg += f"\n{e.stdout}"
95+
raise BuildError(error_msg) from e
96+
except FileNotFoundError as e:
97+
raise BuildError("npm not found. Please install Node.js to build the frontend.") from e

0 commit comments

Comments
 (0)