1
- import json
2
1
import os
3
2
import subprocess
4
3
import sys
11
10
from bolt .runtime import APP_PATH
12
11
13
12
from .db import cli as db_cli
14
- from .services import cli as services_cli
13
+ from .pid import Pid
14
+ from .services import Services
15
15
from .utils import boltpackage_installed , has_pyproject_toml
16
16
17
17
try :
@@ -35,82 +35,117 @@ def cli(ctx, port):
35
35
if ctx .invoked_subcommand :
36
36
return
37
37
38
- # TODO check docker is available first
39
- project_root = APP_PATH .parent
40
-
41
- bolt_env = {
42
- ** os .environ ,
43
- "PYTHONUNBUFFERED" : "true" ,
44
- }
45
-
46
- if "GITHUB_CODESPACES_PORT_FORWARDING_DOMAIN" in os .environ :
47
- codespace_base_url = f"https://{ os .environ ['CODESPACE_NAME' ]} -{ port } .{ os .environ ['GITHUB_CODESPACES_PORT_FORWARDING_DOMAIN' ]} "
48
- click .secho (
49
- f"Automatically using Codespace BASE_URL={ click .style (codespace_base_url , underline = True )} " ,
50
- bold = True ,
51
- )
52
- bolt_env ["BASE_URL" ] = codespace_base_url
53
-
54
- if subprocess .run (["bolt" , "preflight" ], env = bolt_env ).returncode :
55
- click .secho ("Preflight check failed!" , fg = "red" )
56
- sys .exit (1 )
57
-
58
- bolt_db_installed = find_spec ("bolt.db" ) is not None
59
-
60
- manager = HonchoManager ()
61
-
62
- # TODO not necessarily watching the right .env...
63
- # could return path from env.load?
64
- extra_watch_files = []
65
- for f in os .listdir (project_root ):
66
- if f .startswith (".env" ):
67
- # Will include some extra, but good enough for now
68
- extra_watch_files .append (f )
69
-
70
- reload_extra = " " .join (f"--reload-extra-file { f } " for f in extra_watch_files )
71
- gunicorn = f"gunicorn --bind 127.0.0.1:{ port } --reload bolt.wsgi:app --timeout 0 --workers 2 --access-logfile - --error-logfile - { reload_extra } --access-logformat '\" %(r)s\" status=%(s)s length=%(b)s dur=%(M)sms'"
72
-
73
- if bolt_db_installed :
74
- runserver_cmd = f"bolt db wait && bolt legacy migrate && { gunicorn } "
75
- manager .add_process ("services" , "bolt dev services up" )
76
- else :
77
- runserver_cmd = gunicorn
78
-
79
- manager .add_process ("bolt" , runserver_cmd , env = bolt_env )
80
-
81
- if boltpackage_installed ("tailwind" ):
82
- manager .add_process ("tailwind" , "bolt tailwind compile --watch" )
83
-
84
- custom_env = {
85
- ** bolt_env ,
86
- "PORT" : port ,
87
- "PYTHONPATH" : os .path .join (project_root , "app" ),
88
- }
89
-
90
- if project_root and has_pyproject_toml (project_root ):
91
- with open (Path (project_root , "pyproject.toml" ), "rb" ) as f :
38
+ returncode = Dev (port = port ).run ()
39
+ if returncode :
40
+ sys .exit (returncode )
41
+
42
+
43
+ class Dev :
44
+ def __init__ (self , * , port ):
45
+ self .manager = HonchoManager ()
46
+ self .port = port
47
+ self .bolt_env = {
48
+ ** os .environ ,
49
+ "PYTHONUNBUFFERED" : "true" ,
50
+ }
51
+ self .custom_process_env = {
52
+ ** self .bolt_env ,
53
+ "PORT" : str (self .port ),
54
+ "PYTHONPATH" : os .path .join (APP_PATH .parent , "app" ),
55
+ }
56
+
57
+ def run (self ):
58
+ pid = Pid ()
59
+ pid .write ()
60
+
61
+ try :
62
+ self .add_github_codespace_support ()
63
+ self .run_preflight ()
64
+ self .add_gunicorn ()
65
+ self .add_tailwind ()
66
+ self .add_pyproject_run ()
67
+ self .add_services ()
68
+
69
+ self .manager .loop ()
70
+
71
+ return self .manager .returncode
72
+ finally :
73
+ pid .rm ()
74
+
75
+ def add_github_codespace_support (self ):
76
+ if "GITHUB_CODESPACES_PORT_FORWARDING_DOMAIN" in os .environ :
77
+ codespace_base_url = f"https://{ os .environ ['CODESPACE_NAME' ]} -{ self .port } .{ os .environ ['GITHUB_CODESPACES_PORT_FORWARDING_DOMAIN' ]} "
78
+ click .secho (
79
+ f"Automatically using Codespace BASE_URL={ click .style (codespace_base_url , underline = True )} " ,
80
+ bold = True ,
81
+ )
82
+
83
+ # Set BASE_URL for bolt and custom processes
84
+ self .bolt_env ["BASE_URL" ] = codespace_base_url
85
+ self .custom_process_env ["BASE_URL" ] = codespace_base_url
86
+
87
+ def run_preflight (self ):
88
+ if subprocess .run (["bolt" , "preflight" ], env = self .bolt_env ).returncode :
89
+ click .secho ("Preflight check failed!" , fg = "red" )
90
+ sys .exit (1 )
91
+
92
+ def add_gunicorn (self ):
93
+ bolt_db_installed = find_spec ("bolt.db" ) is not None
94
+
95
+ # TODO not necessarily watching the right .env...
96
+ # could return path from env.load?
97
+ extra_watch_files = []
98
+ for f in os .listdir (APP_PATH .parent ):
99
+ if f .startswith (".env" ):
100
+ # Will include some extra, but good enough for now
101
+ extra_watch_files .append (f )
102
+
103
+ reload_extra = " " .join (f"--reload-extra-file { f } " for f in extra_watch_files )
104
+ gunicorn = f"gunicorn --bind 127.0.0.1:{ self .port } --reload bolt.wsgi:app --timeout 60 --access-logfile - --error-logfile - { reload_extra } --access-logformat '\" %(r)s\" status=%(s)s length=%(b)s dur=%(M)sms'"
105
+
106
+ if bolt_db_installed :
107
+ runserver_cmd = f"bolt db wait && bolt legacy migrate && { gunicorn } "
108
+ else :
109
+ runserver_cmd = gunicorn
110
+
111
+ if "WEB_CONCURRENCY" not in self .bolt_env :
112
+ # Default to two workers so request log etc are less
113
+ # likely to get locked up
114
+ self .bolt_env ["WEB_CONCURRENCY" ] = "2"
115
+
116
+ self .manager .add_process ("bolt" , runserver_cmd , env = self .bolt_env )
117
+
118
+ def add_tailwind (self ):
119
+ if not boltpackage_installed ("tailwind" ):
120
+ return
121
+
122
+ self .manager .add_process ("tailwind" , "bolt tailwind compile --watch" )
123
+
124
+ def add_pyproject_run (self ):
125
+ if not has_pyproject_toml (APP_PATH .parent ):
126
+ return
127
+
128
+ with open (Path (APP_PATH .parent , "pyproject.toml" ), "rb" ) as f :
92
129
pyproject = tomllib .load (f )
130
+
93
131
for name , data in (
94
132
pyproject .get ("tool" , {}).get ("bolt" , {}).get ("dev" , {}).get ("run" , {})
95
133
).items ():
96
134
env = {
97
- ** custom_env ,
135
+ ** self . custom_process_env ,
98
136
** data .get ("env" , {}),
99
137
}
100
- manager .add_process (name , data ["cmd" ], env = env )
101
-
102
- package_json = Path ("package.json" )
103
- if package_json .exists ():
104
- with package_json .open () as f :
105
- package = json .load (f )
106
-
107
- if package .get ("scripts" , {}).get ("dev" ):
108
- manager .add_process ("npm" , "npm run dev" , env = custom_env )
138
+ self .manager .add_process (name , data ["cmd" ], env = env )
109
139
110
- manager .loop ()
111
-
112
- sys .exit (manager .returncode )
140
+ def add_services (self ):
141
+ services = Services .get_services (APP_PATH .parent )
142
+ for name , data in services .items ():
143
+ env = {
144
+ ** os .environ ,
145
+ "PYTHONUNBUFFERED" : "true" ,
146
+ ** data .get ("env" , {}),
147
+ }
148
+ self .manager .add_process (name , data ["cmd" ], env = env )
113
149
114
150
115
151
cli .add_command (db_cli )
116
- cli .add_command (services_cli )
0 commit comments