@@ -48,12 +48,11 @@ import semver from 'semver'
4848
4949import { whichBin } from '@socketsecurity/lib/bin'
5050import { WIN32 } from '@socketsecurity/lib/constants/platform'
51- import { httpDownload } from '@socketsecurity/lib/http-request '
51+ import { downloadBinary , getDlxCachePath } from '@socketsecurity/lib/dlx-binary '
5252import { spawn } from '@socketsecurity/lib/spawn'
5353
5454import ENV from '../../constants/env.mts'
5555import { PYTHON_MIN_VERSION } from '../../constants/packages.mts'
56- import { getDlxCachePath } from '../dlx/binary.mts'
5756import { resolvePyCli } from '../dlx/resolve-binary.mjs'
5857import { getErrorCause , InputError } from '../error/errors.mts'
5958
@@ -186,31 +185,32 @@ export async function checkSystemPython(): Promise<string | null> {
186185}
187186
188187/**
189- * Download and extract Python from python-build-standalone.
188+ * Download and extract Python from python-build-standalone using downloadBinary.
189+ * Uses downloadBinary for caching, checksum verification, and download management.
190190 */
191191async function downloadPython ( pythonDir : string ) : Promise < void > {
192192 const url = getPythonStandaloneUrl ( )
193- const tarballPath = path . join ( pythonDir , 'python.tar.gz' )
193+ const tarballName = 'python-standalone .tar.gz'
194194
195- // Ensure directory exists
195+ // Ensure directory exists.
196196 await fs . mkdir ( pythonDir , { recursive : true } )
197197
198- // Download with Node's native http module
199198 try {
200- await httpDownload ( url , tarballPath )
199+ // Use downloadBinary to download the tarball with caching (without execution).
200+ const result = await downloadBinary ( {
201+ url,
202+ name : tarballName ,
203+ } )
204+
205+ // Extract the tarball to pythonDir.
206+ await spawn ( 'tar' , [ '-xzf' , result . binaryPath , '-C' , pythonDir ] , {
207+ shell : WIN32 ,
208+ } )
201209 } catch ( error ) {
202210 throw new InputError (
203211 `Failed to download Python: ${ error instanceof Error ? error . message : String ( error ) } ` ,
204212 )
205213 }
206-
207- // Extract using system tar command
208- await spawn ( 'tar' , [ '-xzf' , tarballPath , '-C' , pythonDir ] , {
209- shell : WIN32 ,
210- } )
211-
212- // Clean up tarball.
213- await fs . rm ( tarballPath , { force : true } )
214214}
215215
216216/**
@@ -260,6 +260,26 @@ async function isSocketCliInstalled(pythonBin: string): Promise<boolean> {
260260 }
261261}
262262
263+ /**
264+ * Convert npm caret range (^2.2.15) to pip version specifier (>=2.2.15,<3.0.0).
265+ */
266+ function convertCaretToPipRange ( caretRange : string ) : string {
267+ if ( ! caretRange ) {
268+ return ''
269+ }
270+
271+ if ( ! caretRange . startsWith ( '^' ) ) {
272+ return `==${ caretRange } `
273+ }
274+
275+ const version = caretRange . slice ( 1 ) // Remove '^'
276+ const parts = version . split ( '.' )
277+ const major = Number . parseInt ( parts [ 0 ] || '0' , 10 )
278+ const nextMajor = major + 1
279+
280+ return `>=${ version } ,<${ nextMajor } .0.0`
281+ }
282+
263283/**
264284 * Install socketsecurity package into the Python environment.
265285 */
@@ -269,10 +289,15 @@ export async function ensureSocketCli(pythonBin: string): Promise<void> {
269289 return
270290 }
271291
272- // Install socketsecurity
292+ // Get version constraint from inlined environment variable.
293+ const pyCliVersion = ENV . INLINED_SOCKET_CLI_PYCLI_VERSION
294+ const versionSpec = convertCaretToPipRange ( pyCliVersion || '' )
295+ const packageSpec = versionSpec ? `socketsecurity${ versionSpec } ` : 'socketsecurity'
296+
297+ // Install socketsecurity with version constraint.
273298 await spawn (
274299 pythonBin ,
275- [ '-m' , 'pip' , 'install' , '--quiet' , 'socketsecurity' ] ,
300+ [ '-m' , 'pip' , 'install' , '--quiet' , packageSpec ] ,
276301 {
277302 shell : WIN32 ,
278303 stdio : 'inherit' ,
0 commit comments