@@ -32,6 +32,23 @@ def main(argv: list[str] | None = None) -> int:
3232 args = parse_args (argv )
3333 ensure_docker ()
3434
35+ if args .refresh :
36+ args .refresh_image = True
37+ args .refresh_cache = True
38+
39+ image = args .docker_image
40+
41+ if args .refresh_image or args .refresh_cache :
42+ if args .refresh_image :
43+ maybe_build_image (image , refresh = True )
44+
45+ if args .cache_volume and args .refresh_cache :
46+ reset_docker_volume (args .cache_volume )
47+ ensure_docker_volume (args .cache_volume )
48+
49+ print ("Refresh complete." )
50+ return 0
51+
3552 package_root = args .package_root .resolve ()
3653 if not package_root .is_dir ():
3754 print (f"Error: package root does not exist: { package_root } " , file = sys .stderr )
@@ -50,11 +67,7 @@ def main(argv: list[str] | None = None) -> int:
5067 package_root , args .file , args .tests_dir , args .pattern
5168 )
5269
53- image = args .docker_image
54- maybe_build_image (image , args )
55-
56- if args .pull :
57- run_checked (["docker" , "pull" , image ])
70+ maybe_build_image (image , refresh = False )
5871
5972 if args .cache_volume :
6073 ensure_docker_volume (args .cache_volume )
@@ -77,8 +90,12 @@ def main(argv: list[str] | None = None) -> int:
7790 print (f"Package name: { package_name } " )
7891 print (f"Docker image: { image } " )
7992 print (f"Scheduler delay: { args .scheduler_delay_ms } ms" )
93+ if args .refresh_image :
94+ print ("Image refresh: enabled" )
8095 if args .cache_volume :
8196 print (f"Cache volume: { args .cache_volume } " )
97+ if args .refresh_cache :
98+ print ("Cache refresh: enabled" )
8299 if tests_dir and pattern :
83100 print (f"Test target: { tests_dir } /{ pattern } " )
84101
@@ -97,75 +114,75 @@ def parse_args(argv: list[str] | None) -> argparse.Namespace:
97114 type = Path ,
98115 help = "Path to the package root (default: current directory)." ,
99116 )
100- parser .add_argument ("--file" , help = "Run only tests from this file." )
101- parser .add_argument ("--pattern" , help = "Custom unittest discovery pattern." )
102- parser .add_argument ("--tests-dir" , help = "Custom tests directory." )
103- parser .add_argument ("--package-name" , help = "Override package name." )
104- parser .add_argument ("--coverage" , action = "store_true" , help = "Enable coverage." )
105- parser .add_argument ("--failfast" , action = "store_true" , help = "Stop on first failure." )
106117
107- parser .add_argument (
118+ test_group = parser .add_argument_group ("test options" )
119+ test_group .add_argument ("--file" , help = "Run only tests from this file." )
120+ test_group .add_argument ("--pattern" , help = "Custom unittest discovery pattern." )
121+ test_group .add_argument ("--tests-dir" , help = "Custom tests directory." )
122+ test_group .add_argument ("--package-name" , help = "Override package name." )
123+ test_group .add_argument ("--coverage" , action = "store_true" , help = "Enable coverage." )
124+ test_group .add_argument ("--failfast" , action = "store_true" , help = "Stop on first failure." )
125+ test_group .add_argument (
126+ "--scheduler-delay-ms" ,
127+ type = int ,
128+ default = 0 ,
129+ help = "Delay before running scheduled tests inside Sublime (default: 0)." ,
130+ )
131+
132+ docker_group = parser .add_argument_group ("docker options" )
133+ docker_group .add_argument (
134+ "--refresh" ,
135+ action = "store_true" ,
136+ help = "Rebuild image and recreate cache volume." ,
137+ )
138+ docker_group .add_argument (
108139 "--docker-image" ,
109140 default = DEFAULT_IMAGE ,
110141 help = f"Docker image to run (default: { DEFAULT_IMAGE } )." ,
111142 )
112- parser .add_argument (
143+ docker_group .add_argument (
144+ "--refresh-image" ,
145+ action = "store_true" ,
146+ help = "Rebuild the local Docker image." ,
147+ )
148+ docker_group .add_argument (
113149 "--cache-volume" ,
114150 default = DEFAULT_CACHE_VOLUME ,
115151 help = (
116152 "Docker volume mounted at /root to cache Sublime setup "
117153 f"(default: { DEFAULT_CACHE_VOLUME } )."
118154 ),
119155 )
120- parser .add_argument (
156+ docker_group .add_argument (
121157 "--no-cache-volume" ,
122158 dest = "cache_volume" ,
123159 action = "store_const" ,
124160 const = None ,
125161 help = "Disable persistent cache volume." ,
126162 )
127- parser .add_argument (
163+ docker_group .add_argument (
128164 "--sublime-text-version" ,
129165 type = int ,
130166 default = 4 ,
131167 help = "Sublime Text major version inside container." ,
132168 )
133- parser .add_argument (
134- "--scheduler-delay-ms" ,
135- type = int ,
136- default = 0 ,
137- help = "Delay before running scheduled tests inside Sublime (default: 0)." ,
138- )
139- parser .add_argument (
140- "--pull" ,
141- action = "store_true" ,
142- help = "Pull docker image before running." ,
143- )
144-
145- parser .add_argument (
146- "--build-image" ,
169+ docker_group .add_argument (
170+ "--refresh-cache" ,
147171 action = "store_true" ,
148- help = "Force rebuild of local docker image from script directory." ,
149- )
150- parser .add_argument (
151- "--build-if-missing" ,
152- dest = "build_if_missing" ,
153- action = "store_true" ,
154- default = True ,
155- help = "Build image from script directory if missing (default: true)." ,
156- )
157- parser .add_argument (
158- "--no-build-if-missing" ,
159- dest = "build_if_missing" ,
160- action = "store_false" ,
161- help = "Do not auto-build image if missing." ,
172+ help = (
173+ "Recreate the cache volume so Sublime Text and Package Control "
174+ "are re-installed."
175+ ),
162176 )
163177
164178 args = parser .parse_args (argv )
165179
166180 if args .file and args .pattern :
167181 parser .error ("--file and --pattern are mutually exclusive" )
168182
183+ if args .refresh_cache and not args .cache_volume :
184+ parser .error ("--refresh-cache requires a cache volume (omit --no-cache-volume)" )
185+
169186 return args
170187
171188
@@ -174,7 +191,7 @@ def ensure_docker() -> None:
174191 raise SystemExit ("Error: docker executable not found in PATH" )
175192
176193
177- def maybe_build_image (image : str , args : argparse . Namespace ) -> None :
194+ def maybe_build_image (image : str , refresh : bool ) -> None :
178195 context_dir = Path (__file__ ).resolve ().parent
179196 if not context_dir .is_dir ():
180197 raise SystemExit (f"Error: missing docker build context: { context_dir } " )
@@ -184,14 +201,11 @@ def maybe_build_image(image: str, args: argparse.Namespace) -> None:
184201 image_hash = docker_image_context_hash (image ) if image_exists else None
185202 context_changed = image_exists and image_hash != context_hash
186203
187- should_build = args .build_image
188- should_build = should_build or (args .build_if_missing and not image_exists )
189- should_build = should_build or context_changed
190-
204+ should_build = refresh or not image_exists or context_changed
191205 if not should_build :
192206 return
193207
194- if context_changed and not args . build_image :
208+ if context_changed and not refresh :
195209 print ("Docker context changed since last image build, rebuilding..." )
196210
197211 print (f"Building docker image '{ image } ' from { context_dir } ..." )
@@ -266,6 +280,19 @@ def ensure_docker_volume(name: str) -> None:
266280 run_checked (["docker" , "volume" , "create" , name ])
267281
268282
283+ def reset_docker_volume (name : str ) -> None :
284+ result = subprocess .run (
285+ ["docker" , "volume" , "inspect" , name ],
286+ stdout = subprocess .DEVNULL ,
287+ stderr = subprocess .DEVNULL ,
288+ )
289+ if result .returncode != 0 :
290+ return
291+
292+ print (f"Resetting cache volume: { name } " )
293+ run_checked (["docker" , "volume" , "rm" , name ])
294+
295+
269296def resolve_test_target (
270297 package_root : Path ,
271298 test_file : str | None ,
0 commit comments