1818from .download import download_batch , installer_path , print_failed
1919from .fetch import resolve_and_download_packages , resolve_with_deps
2020from .manifest import print_summary , write_csv , write_json
21- from .repack import finalize_nupkg , process_nupkg_phase1
22- from .term import bold , dim , err , info , ok , section , set_verbose , warn
21+ from .repack import build_nupkg , collect_urls
22+ from .term import (
23+ abort ,
24+ bold ,
25+ dim ,
26+ err ,
27+ fatal ,
28+ info ,
29+ ok ,
30+ section ,
31+ set_verbose ,
32+ vlog ,
33+ warn ,
34+ )
2335
2436
2537def build_parser () -> argparse .ArgumentParser :
@@ -181,63 +193,69 @@ def main(argv=None) -> int:
181193 unique .append (n )
182194 nupkgs = unique
183195
184- # ---- Phase 1: rewrite URLs ----
196+ # ---- Collect URLs (read-only scan of all nupkgs) ----
185197 all_mappings = []
186- processed_nupkgs = [] # only nupkgs written this run
187-
188- section ("Phase 1 — Rewriting URLs" )
189- with tempfile .TemporaryDirectory (prefix = "chomp_p1_" ) as tmp :
190- for nupkg in nupkgs :
191- mappings = process_nupkg_phase1 (
192- nupkg = nupkg ,
193- base_url = base_url ,
194- out_dir = out_dir ,
195- work_dir = Path (tmp ),
196- mode = mode ,
197- dry_run = is_dry ,
198- force = force ,
199- )
200- if mappings is not None :
201- all_mappings .extend (mappings )
202- processed_nupkgs .append (out_dir / nupkg .name )
198+ nupkg_mappings : dict [Path , list [dict ]] = {} # per-package URL maps
199+
200+ section ("Phase 1 — Scanning packages" )
201+ for nupkg in nupkgs :
202+ result = collect_urls (
203+ nupkg = nupkg ,
204+ base_url = base_url ,
205+ out_dir = out_dir ,
206+ mode = mode ,
207+ force = force ,
208+ )
209+ if result is None :
210+ continue # already exists, skip
211+ nupkg_mappings [nupkg ] = result
212+ all_mappings .extend (result )
203213
204- if not all_mappings :
205- print (warn ("No external URLs found ." ))
214+ if not nupkg_mappings :
215+ print (warn ("Nothing to do ." ))
206216 return 0
207217
208- # ---- Phase 2: download + finalize ----
209- if not args .skip_download and not is_dry :
218+ if is_dry :
219+ pass # fall through to summary
220+
221+ # ---- Download installers ----
222+ elif not args .skip_download :
210223 section ("Phase 2 — Downloading installers" )
211- all_mappings = download_batch (
212- items = all_mappings ,
213- installer_dir = installer_dir ,
214- use_pwsh = args .pwsh ,
215- quiet = args .quiet ,
216- interactive = args .interactive ,
217- force = force ,
218- )
224+ try :
225+ all_mappings = download_batch (
226+ items = all_mappings ,
227+ installer_dir = installer_dir ,
228+ use_pwsh = args .pwsh ,
229+ quiet = args .quiet ,
230+ interactive = args .interactive ,
231+ force = force ,
232+ )
233+ except KeyboardInterrupt :
234+ abort ()
235+ done = sum (1 for m in all_mappings if m .get ("downloaded" ) == "ok" )
236+ print (warn (f" { done } installer(s) downloaded before interrupt" ))
237+ raise
219238
220- section ("Phase 2b — Finalizing packages" )
239+ # ---- Build final nupkgs ----
240+ section ("Phase 3 — Building packages" )
221241
222242 def resolve (m ):
223243 return installer_path (m , installer_dir )
224244
225- with tempfile .TemporaryDirectory (prefix = "chomp_p2_" ) as tmp2 :
226- for nupkg in processed_nupkgs :
227- if not nupkg .exists ():
228- continue
229- pkg_id = nupkg .stem .split ("." )[0 ]
230- pkg_maps = [m for m in all_mappings if m ["package" ] == pkg_id ]
231- if not pkg_maps :
232- continue
233- finalize_nupkg (
234- nupkg = nupkg ,
235- mappings = pkg_maps ,
236- local_file_resolver = resolve ,
237- out_dir = out_dir ,
238- work_dir = Path (tmp2 ),
239- mode = mode ,
240- )
245+ with tempfile .TemporaryDirectory (prefix = "chomp_" ) as tmp :
246+ try :
247+ for nupkg , mappings in nupkg_mappings .items ():
248+ build_nupkg (
249+ nupkg = nupkg ,
250+ mappings = mappings ,
251+ local_file_resolver = resolve ,
252+ out_dir = out_dir ,
253+ work_dir = Path (tmp ),
254+ mode = mode ,
255+ )
256+ except KeyboardInterrupt :
257+ abort ()
258+ raise
241259
242260 # ---- Output ----
243261 if not is_dry :
@@ -257,4 +275,28 @@ def resolve(m):
257275
258276
259277def entry_point ():
260- sys .exit (main ())
278+ import os
279+ import traceback
280+
281+ _show_tb = (
282+ os .environ .get ("CHOMP_TRACEBACK" ) or "--verbose" in sys .argv or "-v" in sys .argv
283+ )
284+
285+ try :
286+ sys .exit (main ())
287+ except KeyboardInterrupt :
288+ abort ()
289+ sys .exit (130 )
290+ except Exception as exc :
291+ if _show_tb :
292+ traceback .print_exc ()
293+ else :
294+ # Walk cause chain for a useful one-liner
295+ msg = str (exc ).strip () or type (exc ).__name__
296+ cause = exc .__cause__ or exc .__context__
297+ if cause and str (cause ).strip ():
298+ msg = f"{ msg } : { str (cause ).strip ()} "
299+ fatal (
300+ msg , hint = "re-run with -v for full traceback, or set CHOMP_TRACEBACK=1"
301+ )
302+ sys .exit (1 )
0 commit comments