11import * as tomljs from '@iarna/toml' ;
22import * as fse from 'fs-extra' ;
33import * as path from 'path' ;
4- import { l10n , LogOutputChannel , ProgressLocation , QuickInputButtons , QuickPickItem , Uri } from 'vscode' ;
4+ import { l10n , LogOutputChannel , ProgressLocation , QuickInputButtons , QuickPickItem , Uri , window } from 'vscode' ;
55import { PackageManagementOptions , PythonEnvironment , PythonEnvironmentApi , PythonProject } from '../../api' ;
66import { EXTENSION_ROOT_DIR } from '../../common/constants' ;
77import { PackageManagement , Pickers , VenvManagerStrings } from '../../common/localize' ;
@@ -160,11 +160,12 @@ async function getCommonPackages(): Promise<Installable[]> {
160160}
161161
162162async function selectWorkspaceOrCommon (
163- installable : Installable [ ] ,
163+ installableResult : ProjectInstallableResult ,
164164 common : Installable [ ] ,
165165 showSkipOption : boolean ,
166166 installed : string [ ] ,
167167) : Promise < PipPackages | undefined > {
168+ const installable = installableResult . installables ;
168169 if ( installable . length === 0 && common . length === 0 ) {
169170 return undefined ;
170171 }
@@ -206,7 +207,20 @@ async function selectWorkspaceOrCommon(
206207 if ( selected && ! Array . isArray ( selected ) ) {
207208 try {
208209 if ( selected . label === PackageManagement . workspaceDependencies ) {
209- return await selectFromInstallableToInstall ( installable , undefined , { showBackButton } ) ;
210+ const selectedInstallables = await selectFromInstallableToInstall ( installable , undefined , {
211+ showBackButton,
212+ } ) ;
213+
214+ const validationError = installableResult . validationError ;
215+ const shouldProceed = await shouldProceedAfterPyprojectValidation (
216+ validationError ,
217+ selectedInstallables ?. install ?? [ ] ,
218+ ) ;
219+ if ( ! shouldProceed ) {
220+ return undefined ;
221+ }
222+
223+ return selectedInstallables ;
210224 } else if ( selected . label === PackageManagement . searchCommonPackages ) {
211225 return await selectFromCommonPackagesToInstall ( common , installed , undefined , { showBackButton } ) ;
212226 } else if ( selected . label === PackageManagement . skipPackageInstallation ) {
@@ -218,7 +232,7 @@ async function selectWorkspaceOrCommon(
218232 // eslint-disable-next-line @typescript-eslint/no-explicit-any
219233 } catch ( ex : any ) {
220234 if ( ex === QuickInputButtons . Back ) {
221- return selectWorkspaceOrCommon ( installable , common , showSkipOption , installed ) ;
235+ return selectWorkspaceOrCommon ( installableResult , common , showSkipOption , installed ) ;
222236 }
223237 }
224238 }
@@ -239,17 +253,19 @@ export interface ProjectInstallableResult {
239253 /**
240254 * Validation error information if pyproject.toml validation failed
241255 */
242- validationError ?: {
243- /**
244- * Human-readable error message describing the validation issue
245- */
246- message : string ;
247-
248- /**
249- * URI to the pyproject.toml file that has the validation error
250- */
251- fileUri : Uri ;
252- } ;
256+ validationError ?: ValidationError ;
257+ }
258+
259+ export interface ValidationError {
260+ /**
261+ * Human-readable error message describing the validation issue
262+ */
263+ message : string ;
264+
265+ /**
266+ * URI to the pyproject.toml file that has the validation error
267+ */
268+ fileUri : Uri ;
253269}
254270
255271export async function getWorkspacePackagesToInstall (
@@ -259,15 +275,14 @@ export async function getWorkspacePackagesToInstall(
259275 environment ?: PythonEnvironment ,
260276 log ?: LogOutputChannel ,
261277) : Promise < PipPackages | undefined > {
262- const result = await getProjectInstallable ( api , project ) ;
263- const installable = result . installables ;
278+ const installableResult = await getProjectInstallable ( api , project ) ;
264279 let common = await getCommonPackages ( ) ;
265280 let installed : string [ ] | undefined ;
266281 if ( environment ) {
267282 installed = ( await refreshPipPackages ( environment , log , { showProgress : true } ) ) ?. map ( ( pkg ) => pkg . name ) ;
268283 common = mergePackages ( common , installed ?? [ ] ) ;
269284 }
270- return selectWorkspaceOrCommon ( installable , common , ! ! options . showSkipOption , installed ?? [ ] ) ;
285+ return selectWorkspaceOrCommon ( installableResult , common , ! ! options . showSkipOption , installed ?? [ ] ) ;
271286}
272287
273288export async function getProjectInstallable (
@@ -345,6 +360,45 @@ export async function getProjectInstallable(
345360 } ;
346361}
347362
363+ export async function shouldProceedAfterPyprojectValidation (
364+ validationError : ValidationError | undefined ,
365+ install : string [ ] ,
366+ ) : Promise < boolean > {
367+ // 1. If no validation error or no installables selected, proceed
368+ if ( ! validationError || install . length === 0 ) {
369+ return true ;
370+ }
371+
372+ const selectedTomlInstallables = install . some ( ( arg , index , arr ) => arg === '-e' && index + 1 < arr . length ) ;
373+ if ( ! selectedTomlInstallables ) {
374+ // 2. If no toml installables selected, proceed
375+ return true ;
376+ }
377+
378+ // 3. Otherwise, show error message and ask user what to do
379+ const openButton = { title : Pickers . pyProject . openFile } ;
380+ const continueButton = { title : Pickers . pyProject . continueAnyway } ;
381+ const cancelButton = { title : Pickers . pyProject . cancel , isCloseAffordance : true } ;
382+
383+ const selection = await window . showErrorMessage (
384+ validationError . message ,
385+ { modal : true } ,
386+ openButton ,
387+ continueButton ,
388+ cancelButton ,
389+ ) ;
390+
391+ if ( selection === continueButton ) {
392+ return true ;
393+ }
394+
395+ if ( selection === openButton ) {
396+ await window . showTextDocument ( validationError . fileUri ) ;
397+ }
398+
399+ return false ;
400+ }
401+
348402export function isPipInstallCommand ( command : string ) : boolean {
349403 // Regex to match pip install commands, capturing variations like:
350404 // pip install package
0 commit comments