@@ -425,6 +425,119 @@ describe('CLI features subcommands', async function () {
425425 } ) ;
426426 } ) ;
427427
428+ describe ( 'features resolve-dependencies' , function ( ) {
429+
430+ it ( 'should resolve dependencies when workspace-folder defaults to current directory' , async function ( ) {
431+ // Create a test config with features that have dependencies
432+ const testConfigPath = path . resolve ( __dirname , 'configs/feature-dependencies/dependsOn/oci-ab' ) ;
433+ const originalCwd = process . cwd ( ) ;
434+
435+ try {
436+ // Change to test config directory to test default workspace folder behavior
437+ process . chdir ( testConfigPath ) ;
438+
439+ // Use absolute path to CLI to prevent npm ENOENT errors
440+ const absoluteTmpPath = path . resolve ( originalCwd , tmp ) ;
441+ const absoluteCliPath = `npx --prefix ${ absoluteTmpPath } devcontainer` ;
442+
443+ // First check if the config file exists
444+ const configExists = require ( 'fs' ) . existsSync ( '.devcontainer/devcontainer.json' ) ||
445+ require ( 'fs' ) . existsSync ( '.devcontainer.json' ) ;
446+ assert . isTrue ( configExists , 'Test config file should exist' ) ;
447+
448+ let result ;
449+ try {
450+ result = await shellExec ( `${ absoluteCliPath } features resolve-dependencies --log-level trace` ) ;
451+ } catch ( error : any ) {
452+ // If command fails, log details for debugging
453+ console . error ( 'Command failed:' , error ) ;
454+ if ( error . stderr ) {
455+ console . error ( 'STDERR:' , error . stderr ) ;
456+ }
457+ if ( error . stdout ) {
458+ console . error ( 'STDOUT:' , error . stdout ) ;
459+ }
460+ throw error ;
461+ }
462+
463+ // Verify the command succeeded
464+ assert . isDefined ( result ) ;
465+ assert . isString ( result . stdout ) ;
466+ assert . isNotEmpty ( result . stdout . trim ( ) , 'Command should produce output' ) ;
467+
468+ // Parse the JSON output to verify it contains expected structure
469+ let jsonOutput ;
470+ try {
471+ // Try parsing stdout directly first
472+ jsonOutput = JSON . parse ( result . stdout . trim ( ) ) ;
473+ } catch ( parseError ) {
474+ // If direct parsing fails, try extracting JSON from mixed output
475+ const lines = result . stdout . split ( '\n' ) ;
476+
477+ // Find the last occurrence of '{' that starts a complete JSON object
478+ let jsonStartIndex = - 1 ;
479+ let jsonEndIndex = - 1 ;
480+ let braceCount = 0 ;
481+
482+ // Work backwards from the end to find the complete JSON
483+ for ( let i = lines . length - 1 ; i >= 0 ; i -- ) {
484+ const line = lines [ i ] . trim ( ) ;
485+ if ( line === '}' && jsonEndIndex === - 1 ) {
486+ jsonEndIndex = i ;
487+ braceCount = 1 ;
488+ } else if ( jsonEndIndex !== - 1 ) {
489+ // Count braces to find matching opening
490+ for ( const char of line ) {
491+ if ( char === '}' ) {
492+ braceCount ++ ;
493+ } else if ( char === '{' ) {
494+ braceCount -- ;
495+ }
496+ }
497+ if ( braceCount === 0 && line === '{' ) {
498+ jsonStartIndex = i ;
499+ break ;
500+ }
501+ }
502+ }
503+
504+ if ( jsonStartIndex >= 0 && jsonEndIndex >= 0 ) {
505+ // Extract just the JSON lines
506+ const jsonLines = lines . slice ( jsonStartIndex , jsonEndIndex + 1 ) ;
507+ const jsonString = jsonLines . join ( '\n' ) ;
508+ try {
509+ jsonOutput = JSON . parse ( jsonString ) ;
510+ } catch ( innerError ) {
511+ console . error ( 'Failed to parse extracted JSON:' , jsonString . substring ( 0 , 500 ) + '...' ) ;
512+ throw new Error ( `Failed to parse extracted JSON: ${ innerError } ` ) ;
513+ }
514+ } else {
515+ console . error ( 'Could not find complete JSON in output' ) ;
516+ console . error ( 'Last 10 lines:' , lines . slice ( - 10 ) ) ;
517+ throw new Error ( `Failed to find complete JSON in output: ${ parseError } ` ) ;
518+ }
519+ }
520+
521+ assert . isDefined ( jsonOutput , 'Should have valid JSON output' ) ;
522+ assert . property ( jsonOutput , 'installOrder' ) ;
523+ assert . isArray ( jsonOutput . installOrder ) ;
524+
525+ // Verify the install order contains the expected features
526+ const installOrder = jsonOutput . installOrder ;
527+ assert . isAbove ( installOrder . length , 0 , 'Install order should contain at least one feature' ) ;
528+
529+ // Each item should have id and options
530+ installOrder . forEach ( ( item : any ) => {
531+ assert . property ( item , 'id' ) ;
532+ assert . property ( item , 'options' ) ;
533+ } ) ;
534+
535+ } finally {
536+ process . chdir ( originalCwd ) ;
537+ }
538+ } ) ;
539+ } ) ;
540+
428541 describe ( 'features package' , function ( ) {
429542
430543 it ( 'features package subcommand by collection' , async function ( ) {
0 commit comments