44from loguru import logger
55from pathlib import Path
66from tempfile import TemporaryDirectory
7- from typing import Optional , List , Union , Dict , Sequence , Any , cast
7+ from typing import Optional , List , Union , Dict , Sequence
88
99from .utils import Spinner
1010
@@ -56,6 +56,7 @@ def register_web_module(name: str, source: Union[str, Path]) -> str:
5656
5757def delete_web_modules (names : Sequence [str ], skip_missing : bool = False ) -> None :
5858 paths = []
59+ cache = Cache (BUILD_DIR )
5960 for name in _to_list_of_str (names ):
6061 exists = False
6162
@@ -78,9 +79,13 @@ def delete_web_modules(names: Sequence[str], skip_missing: bool = False) -> None
7879 if not exists and not skip_missing :
7980 raise ValueError (f"Module '{ name } ' does not exist." )
8081
82+ cache .delete_package (name )
83+
8184 for p in paths :
8285 _delete_os_paths (p )
8386
87+ cache .save ()
88+
8489
8590def installed () -> List [str ]:
8691 names : List [str ] = []
@@ -91,56 +96,33 @@ def installed() -> List[str]:
9196 return list (sorted (names ))
9297
9398
94- def install (
95- packages : Sequence [str ], exports : Sequence [str ] = (), force : bool = False
96- ) -> None :
97- package_list = _to_list_of_str (packages )
98- export_list = _to_list_of_str (exports )
99-
100- for pkg in package_list :
101- at_count = pkg .count ("@" )
102- if pkg .startswith ("@" ) and at_count == 1 :
103- export_list .append (pkg )
104- else :
105- # this works even if there are no @ symbols
106- export_list .append (pkg .rsplit ("@" , 1 )[0 ])
107-
108- if force :
109- for exp in export_list :
110- delete_web_modules (exp , skip_missing = True )
111-
99+ def install (packages : Sequence [str ], exports : Sequence [str ] = ()) -> None :
112100 with TemporaryDirectory () as tempdir :
113101 tempdir_path = Path (tempdir )
114102 temp_static_dir = tempdir_path / "static"
103+ temp_build_dir = temp_static_dir / "build"
115104
105+ # copy over the whole ./static directory into the temp one
116106 shutil .copytree (STATIC_DIR , temp_static_dir , symlinks = True )
117- assert (temp_static_dir / "package.json" ).exists ()
107+
108+ cache = Cache (temp_build_dir )
109+ cache .add_packages (packages , exports )
118110
119111 with open (temp_static_dir / "package.json" ) as f :
120112 package_json = json .load (f )
121113
122- temp_build_dir = temp_static_dir / "build"
123-
124- cache = _read_idom_build_cache (temp_build_dir )
125-
126- export_list = list (set (cache ["export_list" ] + export_list ))
127- package_list = list (set (cache ["package_list" ] + package_list ))
128-
129114 pkg_snowpack = package_json .setdefault ("snowpack" , {})
130- pkg_snowpack .setdefault ("install" , []).extend (export_list )
115+ pkg_snowpack .setdefault ("install" , []).extend (cache . export_list )
131116
132117 with (temp_static_dir / "package.json" ).open ("w+" ) as f :
133118 json .dump (package_json , f )
134119
135- with Spinner (f"Installing: { ', ' .join (package_list )} " ):
120+ with Spinner (f"Installing: { ', ' .join (packages )} " ):
136121 _run_subprocess (["npm" , "install" ], temp_static_dir )
137- _run_subprocess (["npm" , "install" ] + package_list , temp_static_dir )
122+ _run_subprocess (["npm" , "install" ] + cache . package_list , temp_static_dir )
138123 _run_subprocess (["npm" , "run" , "build" ], temp_static_dir )
139124
140- cache ["export_list" ] = export_list
141- cache ["package_list" ] = package_list
142-
143- _write_idom_build_cache (temp_build_dir , cache )
125+ cache .save ()
144126
145127 if BUILD_DIR .exists ():
146128 shutil .rmtree (BUILD_DIR )
@@ -150,12 +132,56 @@ def install(
150132
151133def restore () -> None :
152134 with Spinner ("Restoring" ):
153- _delete_os_paths (WEB_MODULES , NODE_MODULES )
135+ _delete_os_paths (BUILD_DIR )
154136 _run_subprocess (["npm" , "install" ], STATIC_DIR )
155137 _run_subprocess (["npm" , "run" , "build" ], STATIC_DIR )
156138 STATIC_SHIMS .clear ()
157139
158140
141+ class Cache :
142+ """Manages a cache file stored at ``build/.idom-cache.json``"""
143+
144+ __slots__ = "_file" , "package_list" , "export_list"
145+
146+ def __init__ (self , path : Path ) -> None :
147+ self ._file = path / ".idom-cache.json"
148+ if not self ._file .exists ():
149+ self .package_list : List [str ] = []
150+ self .export_list : List [str ] = []
151+ else :
152+ self ._load ()
153+
154+ def add_packages (self , packages : Sequence [str ], exports : Sequence [str ]) -> None :
155+ package_list = _to_list_of_str (packages )
156+ export_list = _to_list_of_str (exports )
157+ export_list .extend (map (_export_name_from_package , package_list ))
158+ self .package_list = list (set (self .package_list + package_list ))
159+ self .export_list = list (set (self .export_list + export_list ))
160+
161+ def delete_package (self , export_name : str ) -> None :
162+ self .export_list .remove (export_name )
163+ for i , pkg in enumerate (self .package_list ):
164+ if _export_name_from_package (pkg ) == export_name :
165+ del self .package_list [i ]
166+ break
167+
168+ def save (self ) -> None :
169+ cache = {
170+ name : getattr (self , name )
171+ for name in self .__slots__
172+ if not name .startswith ("_" )
173+ }
174+ with self ._file .open ("w+" ) as f :
175+ json .dump (cache , f )
176+
177+ def _load (self ) -> None :
178+ with self ._file .open () as f :
179+ cache = json .load (f )
180+ for name in self .__slots__ :
181+ if not name .startswith ("_" ):
182+ setattr (self , name , cache [name ])
183+
184+
159185def _run_subprocess (args : List [str ], cwd : Union [str , Path ]) -> None :
160186 try :
161187 subprocess .run (
@@ -176,23 +202,14 @@ def _delete_os_paths(*paths: Path) -> None:
176202 shutil .rmtree (p )
177203
178204
179- def _read_idom_build_cache (path : Path ) -> Dict [str , Any ]:
180- cache_file = path / ".idom-cache.json"
181- if not cache_file .exists ():
182- return {
183- "package_list" : [],
184- "export_list" : [],
185- }
186- else :
187- with cache_file .open () as f :
188- return cast (Dict [str , Any ], json .load (f ))
189-
190-
191- def _write_idom_build_cache (path : Path , cache : Dict [str , Any ]) -> None :
192- cache_file = path / ".idom-cache.json"
193- with cache_file .open ("w+" ) as f :
194- json .dump (cache , f )
195-
196-
197205def _to_list_of_str (value : Sequence [str ]) -> List [str ]:
198206 return [value ] if isinstance (value , str ) else list (value )
207+
208+
209+ def _export_name_from_package (pkg : str ) -> str :
210+ at_count = pkg .count ("@" )
211+ if pkg .startswith ("@" ) and at_count == 1 :
212+ return pkg
213+ else :
214+ # this works even if there are no @ symbols
215+ return pkg .rsplit ("@" , 1 )[0 ]
0 commit comments