@@ -686,14 +686,42 @@ class DependencyExtractor:
686686 def __init__ (self , config_loader : ConfigLoader ):
687687 self .config = config_loader
688688
689+ def _get_package_version (self , import_name : str ) -> str | None :
690+ """Get installed version of a package (strips local version identifiers)."""
691+ try :
692+ module = importlib .import_module (import_name )
693+ version = getattr (module , '__version__' , None )
694+ if version :
695+ # Strip local version identifier (e.g., +g22f8f27) - not supported by PyPI
696+ version = version .split ('+' )[0 ]
697+ return version
698+ except ImportError :
699+ return None
700+
689701 def extract (self ) -> dict :
690- """Extract all dependency information."""
702+ """Extract all dependency information including installed versions ."""
691703 pyodide_config = self .config .load_pyodide_config ()
692704 pyodide_packages = self .config .load_requirements ("requirements-pyodide.txt" )
693705
706+ # Get installed versions and create pinned package specs
707+ extracted_versions = {}
708+ for pkg in pyodide_packages :
709+ version = self ._get_package_version (pkg ["import" ])
710+ if version :
711+ extracted_versions [pkg ["import" ]] = version
712+ base_name = pkg ["pip" ].split (">=" )[0 ].split ("==" )[0 ].split ("<=" )[0 ].split ("<" )[0 ].split (">" )[0 ]
713+
714+ # Only pin release versions (not dev versions which aren't on PyPI)
715+ if ".dev" in version :
716+ print (f" { base_name } { version } is dev version, keeping original spec: { pkg ['pip' ]} " )
717+ else :
718+ pkg ["pip" ] = f"{ base_name } =={ version } "
719+ print (f" Pinned { base_name } to version { version } " )
720+
694721 return {
695722 "pyodide" : pyodide_config ,
696- "packages" : pyodide_packages
723+ "packages" : pyodide_packages ,
724+ "extracted_versions" : extracted_versions
697725 }
698726
699727
@@ -816,16 +844,32 @@ def write_dependencies(self, data: dict) -> None:
816844 """Write dependencies.ts file."""
817845 pyodide = data .get ("pyodide" , {})
818846 packages = data .get ("packages" , [])
847+ extracted_versions = data .get ("extracted_versions" , {})
848+
849+ # Read PathView version from package.json (at project root, 2 levels up from src/lib)
850+ package_json_path = self .output_dir .parent .parent / "package.json"
851+ pathview_version = "0.0.0"
852+ if package_json_path .exists ():
853+ try :
854+ package_data = json .loads (package_json_path .read_text (encoding = "utf-8" ))
855+ pathview_version = package_data .get ("version" , "0.0.0" )
856+ except Exception :
857+ pass
819858
820859 lines = [
821860 "// Auto-generated by scripts/extract.py - DO NOT EDIT" ,
822861 "// Source: scripts/config/requirements-pyodide.txt, scripts/config/pyodide.json" ,
823862 "" ,
863+ f"export const PATHVIEW_VERSION = '{ pathview_version } ';" ,
864+ "" ,
824865 f"export const PYODIDE_VERSION = '{ pyodide .get ('version' , '0.26.2' )} ';" ,
825866 "export const PYODIDE_CDN_URL = `https://cdn.jsdelivr.net/pyodide/v${PYODIDE_VERSION}/full/pyodide.mjs`;" ,
826867 "" ,
827868 f"export const PYODIDE_PRELOAD = { json .dumps (pyodide .get ('preload' , []))} as const;" ,
828869 "" ,
870+ "/** Package versions extracted at build time (pinned for runtime) */" ,
871+ f"export const EXTRACTED_VERSIONS: Record<string, string> = { json .dumps (extracted_versions )} ;" ,
872+ "" ,
829873 "export interface PackageConfig {" ,
830874 " pip: string;" ,
831875 " pre: boolean;" ,
0 commit comments