import { join , dirname , basename } from 'path' ; import execa from 'execa' ; import fs from 'fs' ; import { promisify } from 'util' ; const readFile = promisify ( fs . readFile ) ; const writeFile = promisify ( fs . writeFile ) ; import { GlobOptions , BuildOptions , getWriteableDirectory , download , glob , createLambda , shouldServe , debug , NowBuildError , } from '@vercel/build-utils' ; import { installRequirement , installRequirementsFile } from './install' ; async function pipenvConvert ( cmd , srcDir ) { debug ( 'Running pipfile2req...' ) ; try { const out = await execa . stdout ( cmd , [ ] , { cwd : srcDir , } ) ; debug ( 'Contents of requirements.txt is: ' + out ) ; fs . writeFileSync ( join ( srcDir , 'requirements.txt' ) , out ) ; } catch ( err ) { console . log ( 'Failed to run "pipfile2req"' ) ; throw err ; } } export const version = 3 ; export async function downloadFilesInWorkPath ( { entrypoint , workPath , files , meta = { } , } ) { debug ( 'Downloading user files...' ) ; let downloadedFiles = await download ( files , workPath , meta ) ; if ( meta . isDev ) { const { devCacheDir = join ( workPath , '.now' , 'cache' ) } = meta ; const destCache = join ( devCacheDir , basename ( entrypoint , '.py' ) ) ; await download ( downloadedFiles , destCache ) ; downloadedFiles = await glob ( '**' , destCache ) ; workPath = destCache ; } return workPath ; } export const build = async ( { workPath , files : originalFiles , entrypoint , meta = { } , config , } ) => { let pipPath = meta . isDev ? 'pip3' : 'pip3.9' ; let pythonPath = meta . isDev ? 'python3' : 'python3.9' ; let pythonRuntime = meta . isDev ? 'python3' : 'python3.9' ; workPath = await downloadFilesInWorkPath ( { workPath , files : originalFiles , entrypoint , meta , config , } ) ; try { if ( meta . isDev ) { const setupCfg = join ( workPath , 'setup.cfg' ) ; await writeFile ( setupCfg , '[install]\nprefix=\n' ) ; } } catch ( err ) { console . log ( 'Failed to create "setup.cfg" file' ) ; throw err ; } console . log ( 'Installing required dependencies...' ) ; await installRequirement ( { pythonPath , pipPath , dependency : 'werkzeug' , version : '1.0.1' , workPath , meta , } ) ; let fsFiles = await glob ( '**' , workPath ) ; const entryDirectory = dirname ( entrypoint ) ; const pipfileLockDir = fsFiles [ join ( entryDirectory , 'Pipfile.lock' ) ] ? join ( workPath , entryDirectory ) : fsFiles [ 'Pipfile.lock' ] ? workPath : null ; if ( pipfileLockDir ) { debug ( 'Found "Pipfile.lock"' ) ; try { const json = await readFile ( join ( pipfileLockDir , 'Pipfile.lock' ) , 'utf8' ) ; const obj = JSON . parse ( json ) ; const version = obj ?. _meta ?. requires ?. python_version ; if ( ! meta . isDev ) { if ( version === '3.6' ) { pipPath = 'pip3.6' ; pythonPath = 'python3.6' ; pythonRuntime = 'python3.6' ; console . warn ( `Warning: Python version "${ version }" detected in Pipfile.lock will reach End-Of-Life December 2021. Please upgrade. http://vercel.link/python-version` ) ; } else if ( version === '3.9' ) { pipPath = 'pip3.9' ; pythonPath = 'python3.9' ; pythonRuntime = 'python3.9' ; } else { console . warn ( `Warning: Invalid Python version "${ version }" detected in Pipfile.lock will be ignored. http://vercel.link/python-version` ) ; } } } catch ( err ) { throw new NowBuildError ( { code : 'INVALID_PIPFILE_LOCK' , message : 'Unable to parse Pipfile.lock' , } ) ; } const tempDir = await getWriteableDirectory ( ) ; await installRequirement ( { pythonPath , pipPath , dependency : 'pipfile-requirements' , version : '0.3.0' , workPath : tempDir , meta , args : [ '--no-warn-script-location' ] , } ) ; process . env . PYTHONPATH = tempDir ; const convertCmd = join ( tempDir , 'bin' , 'pipfile2req' ) ; await pipenvConvert ( convertCmd , pipfileLockDir ) ; } fsFiles = await glob ( '**' , workPath ) ; const requirementsTxt = join ( entryDirectory , 'requirements.txt' ) ; if ( fsFiles [ requirementsTxt ] ) { debug ( 'Found local "requirements.txt"' ) ; const requirementsTxtPath = fsFiles [ requirementsTxt ] . fsPath ; await installRequirementsFile ( { pythonPath , pipPath , filePath : requirementsTxtPath , workPath , meta , } ) ; } else if ( fsFiles [ 'requirements.txt' ] ) { debug ( 'Found global "requirements.txt"' ) ; const requirementsTxtPath = fsFiles [ 'requirements.txt' ] . fsPath ; await installRequirementsFile ( { pythonPath , pipPath , filePath : requirementsTxtPath , workPath , meta , } ) ; } const originalPyPath = join ( __dirname , '..' , 'vc_init.py' ) ; const originalHandlerPyContents = await readFile ( originalPyPath , 'utf8' ) ; debug ( 'Entrypoint is' , entrypoint ) ; const moduleName = entrypoint . replace ( /\//g , '.' ) . replace ( /\.py$/ , '' ) ; const suffix = meta . isDev && ! entrypoint . endsWith ( '.py' ) ? '.py' : '' ; const entrypointWithSuffix = `${ entrypoint }${ suffix }` ; debug ( 'Entrypoint with suffix is' , entrypointWithSuffix ) ; const handlerPyContents = originalHandlerPyContents . replace ( /__VC_HANDLER_MODULE_NAME/g , moduleName ) . replace ( /__VC_HANDLER_ENTRYPOINT/g , entrypointWithSuffix ) ; const handlerPyFilename = 'vc__handler__python' ; await writeFile ( join ( workPath , `${ handlerPyFilename }.py` ) , handlerPyContents ) ; const runtime = meta . isDev ? 'python3' : pythonRuntime ; const globOptions = { cwd : workPath , ignore : config && typeof config . excludeFiles === 'string' ? config . excludeFiles : 'node_modules/**' , } ; const lambda = await createLambda ( { files : await glob ( '**' , globOptions ) , handler : `${ handlerPyFilename }.vc_handler` , runtime , environment : { } , } ) ; return { output : lambda } ; } ; export { shouldServe } ; export { installRequirement , installRequirementsFile } ;