@@ -80,67 +80,93 @@ exports.default = async function afterPack(context) {
8080 }
8181 }
8282
83- // Clean Python symlinks in venv/bin
84- const venvBinDir = path . join ( prebuiltPath , 'venv' , 'bin' ) ;
85- if ( fs . existsSync ( venvBinDir ) ) {
86- const pythonNames = [ 'python' , 'python3' , 'python3.10' , 'python3.11' , 'python3.12' ] ;
87- const bundlePath = path . resolve ( appPath ) ;
88-
89- for ( const pythonName of pythonNames ) {
90- const pythonSymlink = path . join ( venvBinDir , pythonName ) ;
83+ // Find prebuilt Python executable in uv_python directory
84+ function findPrebuiltPython ( ) {
85+ const uvPythonDir = path . join ( prebuiltPath , 'uv_python' ) ;
86+ if ( ! fs . existsSync ( uvPythonDir ) ) {
87+ return null ;
88+ }
9189
92- if ( fs . existsSync ( pythonSymlink ) ) {
93- try {
94- const stats = fs . lstatSync ( pythonSymlink ) ;
95- if ( stats . isSymbolicLink ( ) ) {
96- const target = fs . readlinkSync ( pythonSymlink ) ;
97- const resolvedPath = path . resolve ( path . dirname ( pythonSymlink ) , target ) ;
98-
99- // If symlink points outside bundle, remove it
100- if ( ! resolvedPath . startsWith ( bundlePath ) ) {
101- console . log ( `Removing invalid ${ pythonName } symlink: ${ target } ` ) ;
102- fs . unlinkSync ( pythonSymlink ) ;
103- }
90+ // UV stores Python in cpython-* subdirectories
91+ try {
92+ const entries = fs . readdirSync ( uvPythonDir , { withFileTypes : true } ) ;
93+ for ( const entry of entries ) {
94+ if ( entry . isDirectory ( ) && entry . name . startsWith ( 'cpython-' ) ) {
95+ const pythonPath = path . join ( uvPythonDir , entry . name , 'install' , 'bin' , 'python' ) ;
96+ if ( fs . existsSync ( pythonPath ) ) {
97+ return pythonPath ;
10498 }
105- } catch ( error ) {
106- console . warn ( `Warning: Could not process ${ pythonName } symlink: ${ error . message } ` ) ;
10799 }
108100 }
101+ } catch ( error ) {
102+ console . warn ( `Warning: Could not search for prebuilt Python: ${ error . message } ` ) ;
109103 }
104+ return null ;
105+ }
106+
107+ const prebuiltPython = findPrebuiltPython ( ) ;
108+ if ( prebuiltPython ) {
109+ console . log ( `Found prebuilt Python: ${ prebuiltPython } ` ) ;
110110 }
111111
112- // Clean Python symlinks in terminal_venv/bin (same as venv/bin)
113- const terminalVenvBinDir = path . join ( prebuiltPath , 'terminal_venv' , 'bin' ) ;
114- if ( fs . existsSync ( terminalVenvBinDir ) ) {
112+ // Clean and fix Python symlinks in a venv bin directory
113+ function fixPythonSymlinks ( binDir , venvName ) {
114+ if ( ! fs . existsSync ( binDir ) ) {
115+ return ;
116+ }
117+
115118 const pythonNames = [ 'python' , 'python3' , 'python3.10' , 'python3.11' , 'python3.12' ] ;
116119 const bundlePath = path . resolve ( appPath ) ;
117120
118121 for ( const pythonName of pythonNames ) {
119- const pythonSymlink = path . join ( terminalVenvBinDir , pythonName ) ;
120-
121- if ( fs . existsSync ( pythonSymlink ) ) {
122- try {
123- const stats = fs . lstatSync ( pythonSymlink ) ;
124- if ( stats . isSymbolicLink ( ) ) {
125- const target = fs . readlinkSync ( pythonSymlink ) ;
126- const resolvedPath = path . resolve ( path . dirname ( pythonSymlink ) , target ) ;
127-
128- // If symlink points outside bundle, remove it
129- if ( ! resolvedPath . startsWith ( bundlePath ) ) {
130- console . log ( `Removing invalid terminal_venv ${ pythonName } symlink: ${ target } ` ) ;
131- fs . unlinkSync ( pythonSymlink ) ;
122+ const pythonSymlink = path . join ( binDir , pythonName ) ;
123+
124+ try {
125+ const stats = fs . lstatSync ( pythonSymlink ) ;
126+ if ( stats . isSymbolicLink ( ) ) {
127+ const target = fs . readlinkSync ( pythonSymlink ) ;
128+ const resolvedPath = path . resolve ( path . dirname ( pythonSymlink ) , target ) ;
129+
130+ // If symlink points outside bundle or is broken, remove and recreate it
131+ if ( ! resolvedPath . startsWith ( bundlePath ) || ! fs . existsSync ( resolvedPath ) ) {
132+ console . log ( `Removing invalid ${ venvName } ${ pythonName } symlink: ${ target } ` ) ;
133+ fs . unlinkSync ( pythonSymlink ) ;
134+
135+ // Recreate symlink pointing to prebuilt Python (only for main 'python')
136+ if ( prebuiltPython && pythonName === 'python' ) {
137+ const relativePath = path . relative ( binDir , prebuiltPython ) ;
138+ fs . symlinkSync ( relativePath , pythonSymlink ) ;
139+ console . log ( `Created ${ venvName } ${ pythonName } symlink -> ${ relativePath } ` ) ;
132140 }
133141 }
134- } catch ( error ) {
135- console . warn ( `Warning: Could not process terminal_venv ${ pythonName } symlink: ${ error . message } ` ) ;
142+ }
143+ } catch ( error ) {
144+ // Symlink doesn't exist, create it if this is the main python symlink
145+ if ( error . code === 'ENOENT' && prebuiltPython && pythonName === 'python' ) {
146+ try {
147+ const relativePath = path . relative ( binDir , prebuiltPython ) ;
148+ fs . symlinkSync ( relativePath , pythonSymlink ) ;
149+ console . log ( `Created missing ${ venvName } ${ pythonName } symlink -> ${ relativePath } ` ) ;
150+ } catch ( createError ) {
151+ console . warn ( `Warning: Could not create ${ venvName } ${ pythonName } symlink: ${ createError . message } ` ) ;
152+ }
136153 }
137154 }
138155 }
139156 }
140157
141- // Recursively clean other invalid symlinks
158+ // Fix Python symlinks in both venv directories
159+ fixPythonSymlinks ( path . join ( prebuiltPath , 'venv' , 'bin' ) , 'venv' ) ;
160+ fixPythonSymlinks ( path . join ( prebuiltPath , 'terminal_venv' , 'bin' ) , 'terminal_venv' ) ;
161+
162+ // Recursively clean other invalid symlinks (skip already-processed venv bin directories)
163+ const processedDirs = new Set ( [
164+ path . join ( prebuiltPath , 'venv' , 'bin' ) ,
165+ path . join ( prebuiltPath , 'terminal_venv' , 'bin' ) ,
166+ ] ) ;
167+
142168 function cleanSymlinks ( dir , bundleRoot ) {
143- if ( ! fs . existsSync ( dir ) ) {
169+ if ( ! fs . existsSync ( dir ) || processedDirs . has ( dir ) ) {
144170 return ;
145171 }
146172
0 commit comments