11#!/usr/bin/env node
2+ /* global console */
23// ========= Copyright 2025-2026 @ Eigent.ai All Rights Reserved. =========
34// Licensed under the Apache License, Version 2.0 (the "License");
45// you may not use this file except in compliance with the License.
2829 * - Works on any machine after packaging
2930 */
3031
32+ import { Buffer } from 'buffer' ;
3133import fs from 'fs' ;
3234import path from 'path' ;
3335import { fileURLToPath } from 'url' ;
@@ -50,12 +52,22 @@ function findPythonExecutable(uvPythonDir) {
5052 // Find cpython directory
5153 for ( const entry of entries ) {
5254 if ( entry . isDirectory ( ) && entry . name . startsWith ( 'cpython-' ) ) {
53- // Try different possible paths
5455 const possiblePaths = [
56+ // Unix paths
5557 path . join ( uvPythonDir , entry . name , 'bin' , 'python3.10' ) ,
5658 path . join ( uvPythonDir , entry . name , 'bin' , 'python' ) ,
5759 path . join ( uvPythonDir , entry . name , 'install' , 'bin' , 'python3.10' ) ,
5860 path . join ( uvPythonDir , entry . name , 'install' , 'bin' , 'python' ) ,
61+ // Windows paths
62+ path . join ( uvPythonDir , entry . name , 'Scripts' , 'python.exe' ) ,
63+ path . join (
64+ uvPythonDir ,
65+ entry . name ,
66+ 'install' ,
67+ 'Scripts' ,
68+ 'python.exe'
69+ ) ,
70+ path . join ( uvPythonDir , entry . name , 'python.exe' ) ,
5971 ] ;
6072
6173 for ( const pythonPath of possiblePaths ) {
@@ -64,7 +76,7 @@ function findPythonExecutable(uvPythonDir) {
6476 absolutePath : pythonPath ,
6577 cpythonDir : entry . name ,
6678 // Extract relative path from uv_python
67- relativePath : path . relative ( uvPythonDir , pythonPath )
79+ relativePath : path . relative ( uvPythonDir , pythonPath ) ,
6880 } ;
6981 }
7082 }
@@ -82,14 +94,29 @@ function findPythonExecutable(uvPythonDir) {
8294 */
8395function fixVenvSymlinks ( venvPath , venvName ) {
8496 const binDir = path . join ( venvPath , 'bin' ) ;
97+ const scriptsDir = path . join ( venvPath , 'Scripts' ) ;
98+
99+ // Windows uses Scripts directory and doesn't need symlink fixes
100+ if ( fs . existsSync ( scriptsDir ) && ! fs . existsSync ( binDir ) ) {
101+ console . log ( `\n📝 Processing ${ venvName } ` ) ;
102+ console . log (
103+ ` ℹ️ Windows venv detected - skipping symlink fixes (not needed)`
104+ ) ;
105+ return true ;
106+ }
85107
86108 if ( ! fs . existsSync ( binDir ) ) {
87109 console . log ( `⚠️ ${ venvName } bin directory not found: ${ binDir } ` ) ;
88110 return false ;
89111 }
90112
91113 // Find Python in uv_python
92- const uvPythonDir = path . join ( projectRoot , 'resources' , 'prebuilt' , 'uv_python' ) ;
114+ const uvPythonDir = path . join (
115+ projectRoot ,
116+ 'resources' ,
117+ 'prebuilt' ,
118+ 'uv_python'
119+ ) ;
93120 const pythonInfo = findPythonExecutable ( uvPythonDir ) ;
94121
95122 if ( ! pythonInfo ) {
@@ -112,22 +139,34 @@ function fixVenvSymlinks(venvPath, venvName) {
112139 let needsFix = false ;
113140 let currentTarget = null ;
114141
115- if ( fs . existsSync ( symlinkPath ) || fs . lstatSync ( symlinkPath ) . isSymbolicLink ( ) ) {
142+ if (
143+ fs . existsSync ( symlinkPath ) ||
144+ fs . lstatSync ( symlinkPath ) . isSymbolicLink ( )
145+ ) {
116146 try {
117147 currentTarget = fs . readlinkSync ( symlinkPath ) ;
118148
119149 // Check if it's a broken symlink or points to wrong location
120- if ( path . isAbsolute ( currentTarget ) || currentTarget . includes ( 'cache' ) ) {
150+ if (
151+ path . isAbsolute ( currentTarget ) ||
152+ currentTarget . includes ( 'cache' )
153+ ) {
121154 needsFix = true ;
122- console . log ( ` ❌ ${ symlinkName } : broken symlink -> ${ currentTarget } ` ) ;
155+ console . log (
156+ ` ❌ ${ symlinkName } : broken symlink -> ${ currentTarget } `
157+ ) ;
123158 } else {
124- console . log ( ` ✓ ${ symlinkName } : already fixed -> ${ currentTarget } ` ) ;
159+ console . log (
160+ ` ✓ ${ symlinkName } : already fixed -> ${ currentTarget } `
161+ ) ;
125162 continue ;
126163 }
127164 } catch ( err ) {
128165 // Broken symlink
129166 needsFix = true ;
130- console . log ( ` ❌ ${ symlinkName } : broken symlink (target doesn't exist)` ) ;
167+ console . log (
168+ ` ❌ ${ symlinkName } : broken symlink (target doesn't exist), ${ err . message } `
169+ ) ;
131170 }
132171 } else {
133172 needsFix = true ;
@@ -137,10 +176,16 @@ function fixVenvSymlinks(venvPath, venvName) {
137176 if ( needsFix ) {
138177 // Remove existing symlink
139178 try {
140- if ( fs . existsSync ( symlinkPath ) || fs . lstatSync ( symlinkPath ) . isSymbolicLink ( ) ) {
179+ if (
180+ fs . existsSync ( symlinkPath ) ||
181+ fs . lstatSync ( symlinkPath ) . isSymbolicLink ( )
182+ ) {
141183 fs . unlinkSync ( symlinkPath ) ;
142184 }
143185 } catch ( err ) {
186+ console . error (
187+ ` ❌ Failed to remove ${ symlinkName } : ${ err . message } `
188+ ) ;
144189 // Ignore
145190 }
146191
@@ -171,9 +216,20 @@ function fixVenvSymlinks(venvPath, venvName) {
171216
172217/**
173218 * Fix shebang lines in all executable scripts in venv/bin directory
219+ * Note: Windows doesn't use shebangs - it uses .exe wrappers instead
174220 */
175221function fixScriptShebangs ( venvPath , venvName ) {
176222 const binDir = path . join ( venvPath , 'bin' ) ;
223+ const scriptsDir = path . join ( venvPath , 'Scripts' ) ;
224+
225+ // Windows uses Scripts directory and doesn't need shebang fixes
226+ if ( fs . existsSync ( scriptsDir ) && ! fs . existsSync ( binDir ) ) {
227+ console . log ( `\n📝 Processing ${ venvName } ` ) ;
228+ console . log (
229+ ` ℹ️ Windows venv detected - skipping shebang fixes (not needed)`
230+ ) ;
231+ return { fixed : 0 , skipped : 0 } ;
232+ }
177233
178234 if ( ! fs . existsSync ( binDir ) ) {
179235 console . log ( `⚠️ ${ venvName } bin directory not found: ${ binDir } ` ) ;
@@ -198,6 +254,7 @@ function fixScriptShebangs(venvPath, venvName) {
198254 continue ;
199255 }
200256 } catch ( err ) {
257+ console . error ( ` ❌ Failed to check ${ entry } : ${ err . message } ` ) ;
201258 continue ;
202259 }
203260
@@ -222,7 +279,17 @@ function fixScriptShebangs(venvPath, venvName) {
222279 }
223280
224281 // Check if it contains an absolute path to the venv
225- if ( ! path . isAbsolute ( firstLine . substring ( 2 ) . trim ( ) ) && ! firstLine . includes ( '/resources/prebuilt/venv/' ) ) {
282+ // Support both Unix (/resources/prebuilt/venv/) and Windows (\resources\prebuilt\venv\ or \resources\prebuilt\Scripts\)
283+ const shebangPath = firstLine . substring ( 2 ) . trim ( ) ;
284+ const hasVenvPath =
285+ firstLine . includes ( '/resources/prebuilt/venv/' ) ||
286+ firstLine . includes ( '\\resources\\prebuilt\\venv\\' ) ||
287+ firstLine . includes ( '/resources/prebuilt/terminal_venv/' ) ||
288+ firstLine . includes ( '\\resources\\prebuilt\\terminal_venv\\' ) ||
289+ firstLine . includes ( '/resources/prebuilt/Scripts/' ) ||
290+ firstLine . includes ( '\\resources\\prebuilt\\Scripts\\' ) ;
291+
292+ if ( ! path . isAbsolute ( shebangPath ) && ! hasVenvPath ) {
226293 skippedCount ++ ;
227294 continue ;
228295 }
@@ -244,21 +311,24 @@ function fixScriptShebangs(venvPath, venvName) {
244311 fs . chmodSync ( filePath , 0o755 ) ;
245312 fixedCount ++ ;
246313
247- if ( fixedCount <= 5 ) { // Only show first 5 for brevity
314+ if ( fixedCount <= 5 ) {
315+ // Only show first 5 for brevity
248316 console . log ( ` ✅ Fixed: ${ entry } ` ) ;
249317 }
250318 }
251319 } catch ( err ) {
252320 // Silently skip files that can't be processed
321+ console . error ( ` ❌ Failed to process ${ entry } : ${ err . message } ` ) ;
253322 continue ;
254323 }
255324 }
256325
257326 if ( fixedCount > 5 ) {
258327 console . log ( ` ✅ ... and ${ fixedCount - 5 } more files` ) ;
259328 }
260- console . log ( ` 📊 Total: ${ fixedCount } fixed, ${ skippedCount } already correct` ) ;
261-
329+ console . log (
330+ ` 📊 Total: ${ fixedCount } fixed, ${ skippedCount } already correct`
331+ ) ;
262332 } catch ( error ) {
263333 console . error ( `❌ Error processing ${ venvName } : ${ error . message } ` ) ;
264334 }
@@ -276,12 +346,12 @@ function main() {
276346 const venvDirs = [
277347 {
278348 path : path . join ( projectRoot , 'resources' , 'prebuilt' , 'venv' ) ,
279- name : 'backend venv'
349+ name : 'backend venv' ,
280350 } ,
281351 {
282352 path : path . join ( projectRoot , 'resources' , 'prebuilt' , 'terminal_venv' ) ,
283- name : 'terminal venv'
284- }
353+ name : 'terminal venv' ,
354+ } ,
285355 ] ;
286356
287357 let symlinkSuccessCount = 0 ;
@@ -307,9 +377,13 @@ function main() {
307377
308378 console . log ( '\n==========================================================' ) ;
309379 if ( totalCount === 0 ) {
310- console . log ( '⚠️ No venv directories found - this is OK for development builds' ) ;
380+ console . log (
381+ '⚠️ No venv directories found - this is OK for development builds'
382+ ) ;
311383 } else {
312- console . log ( `✅ Fixed symlinks in ${ symlinkSuccessCount } /${ totalCount } venv(s)` ) ;
384+ console . log (
385+ `✅ Fixed symlinks in ${ symlinkSuccessCount } /${ totalCount } venv(s)`
386+ ) ;
313387 console . log ( `✅ Fixed shebangs in ${ totalShebangsFixed } script(s)` ) ;
314388 console . log ( '✅ Venvs are now fully portable!' ) ;
315389 }
0 commit comments