@@ -2,6 +2,8 @@ import assert = require('assert');
22import { stripColors } from 'colors' ;
33import path = require( 'path' ) ;
44import fs = require( 'fs' ) ;
5+ // eslint-disable-next-line @typescript-eslint/no-var-requires
6+ const AdmZip = require ( 'adm-zip' ) ;
57import { execAsyncWithLogging } from './test-utils/debug-exec' ;
68
79// Basic test framework functions to avoid TypeScript errors
@@ -381,6 +383,192 @@ describe('Extension Commands - Local Tests', function() {
381383 } )
382384 . catch ( done ) ;
383385 } ) ;
386+
387+ it ( 'should handle manifest-globs with glob patterns and merge scopes' , function ( done ) {
388+ const complexExtensionPath = path . join ( samplesPath , 'complex-extension' ) ;
389+ if ( ! fs . existsSync ( complexExtensionPath ) ) {
390+ console . log ( 'Skipping manifest-globs glob pattern test - sample not found' ) ;
391+ done ( ) ;
392+ return ;
393+ }
394+
395+ const outputPath = path . join ( complexExtensionPath , 'manifest-globs-scopes-test.vsix' ) ;
396+ const manifestsRoot = path . join ( complexExtensionPath , 'dist' , 'Manifests' ) ;
397+ const manifestsSubDir = path . join ( manifestsRoot , 'a' ) ;
398+ const mainManifestPath = path . join ( complexExtensionPath , 'azure-devops-extension.json' ) ;
399+ const secondaryManifestPath = path . join ( manifestsSubDir , 'manifest-a.json' ) ;
400+
401+ const manifestsRootParent = path . dirname ( manifestsRoot ) ;
402+ if ( ! fs . existsSync ( manifestsRootParent ) ) {
403+ fs . mkdirSync ( manifestsRootParent ) ;
404+ }
405+ if ( ! fs . existsSync ( manifestsRoot ) ) {
406+ fs . mkdirSync ( manifestsRoot ) ;
407+ }
408+ if ( ! fs . existsSync ( manifestsSubDir ) ) {
409+ fs . mkdirSync ( manifestsSubDir ) ;
410+ }
411+
412+ const primaryManifest = {
413+ "manifestVersion" : 1 ,
414+ "id" : "glob-test-extension" ,
415+ "publisher" : "glob-test-publisher" ,
416+ "version" : "1.0.0" ,
417+ "name" : "Glob Test Extension" ,
418+ "categories" : "Azure Boards" ,
419+ "scopes" : [
420+ "vso.analytics"
421+ ] ,
422+ "targets" : [
423+ { "id" : "Microsoft.VisualStudio.Services" }
424+ ] ,
425+ "contributions" : [
426+ {
427+ "id" : "glob-test-hub" ,
428+ "type" : "ms.vss-web.hub" ,
429+ "targets" : [ "ms.vss-web.project-hub-group" ] ,
430+ "properties" : {
431+ "name" : "Glob Test Hub"
432+ }
433+ }
434+ ]
435+ } ;
436+ fs . writeFileSync ( mainManifestPath , JSON . stringify ( primaryManifest , null , 2 ) ) ;
437+
438+ const secondaryManifest = {
439+ "scopes" : [
440+ "vso.work"
441+ ]
442+ } ;
443+ fs . writeFileSync ( secondaryManifestPath , JSON . stringify ( secondaryManifest , null , 2 ) ) ;
444+
445+ const manifestGlobsArg = 'azure-devops-extension.json dist/Manifests/**/manifest-*.json' ;
446+ execAsyncWithLogging (
447+ `node "${ tfxPath } " extension create --root "${ complexExtensionPath } " --output-path "${ outputPath } " --manifest-globs ${ manifestGlobsArg } ` ,
448+ 'extension create --manifest-globs glob patterns'
449+ )
450+ . then ( ( { stdout } ) => {
451+ const cleanOutput = stripColors ( stdout ) ;
452+ assert ( cleanOutput . includes ( 'Completed operation: create extension' ) , 'Should handle manifest-globs with glob patterns' ) ;
453+ assert ( fs . existsSync ( outputPath ) , 'Should create .vsix file' ) ;
454+
455+ // Read extension.vsomanifest (JSON) from VSIX and validate scopes
456+ const zip = new AdmZip ( outputPath ) ;
457+ const vsomanifestEntry =
458+ zip . getEntry ( 'extension.vsomanifest' ) ||
459+ zip . getEntry ( 'extension/extension.vsomanifest' ) ;
460+ assert ( vsomanifestEntry , 'VSIX must contain extension.vsomanifest' ) ;
461+
462+ const vsomanifestJson = JSON . parse ( vsomanifestEntry . getData ( ) . toString ( 'utf8' ) ) ;
463+ const scopes : string [ ] = vsomanifestJson . scopes || [ ] ;
464+
465+ assert ( scopes . indexOf ( 'vso.analytics' ) !== - 1 , 'Resulting manifest should contain vso.analytics scope' ) ;
466+ assert ( scopes . indexOf ( 'vso.work' ) !== - 1 , 'Resulting manifest should contain vso.work scope' ) ;
467+
468+ done ( ) ;
469+ } )
470+ . catch ( done ) ;
471+ } ) ;
472+
473+ it ( 'should resolve manifest-globs to both root and globbed manifests (gatherManifests)' , function ( done ) {
474+ const complexExtensionPath = path . join ( samplesPath , 'complex-extension' ) ;
475+ if ( ! fs . existsSync ( complexExtensionPath ) ) {
476+ console . log ( 'Skipping gatherManifests test - sample not found' ) ;
477+ done ( ) ;
478+ return ;
479+ }
480+
481+ const manifestsRoot = path . join ( complexExtensionPath , 'dist' , 'Manifests' ) ;
482+ const manifestsSubDir = path . join ( manifestsRoot , 'a' ) ;
483+ const mainManifestPath = path . join ( complexExtensionPath , 'azure-devops-extension.json' ) ;
484+ const secondaryManifestPath = path . join ( manifestsSubDir , 'manifest-a.json' ) ;
485+
486+ const manifestsRootParent = path . dirname ( manifestsRoot ) ;
487+ if ( ! fs . existsSync ( manifestsRootParent ) ) {
488+ fs . mkdirSync ( manifestsRootParent ) ;
489+ }
490+ if ( ! fs . existsSync ( manifestsRoot ) ) {
491+ fs . mkdirSync ( manifestsRoot ) ;
492+ }
493+ if ( ! fs . existsSync ( manifestsSubDir ) ) {
494+ fs . mkdirSync ( manifestsSubDir ) ;
495+ }
496+
497+ const primaryManifest = {
498+ "manifestVersion" : 1 ,
499+ "id" : "glob-test-extension-gather" ,
500+ "publisher" : "glob-test-publisher" ,
501+ "version" : "1.0.0" ,
502+ "name" : "Glob Test Extension Gather" ,
503+ "categories" : "Azure Boards" ,
504+ "scopes" : [
505+ "vso.analytics"
506+ ] ,
507+ "targets" : [
508+ { "id" : "Microsoft.VisualStudio.Services" }
509+ ] ,
510+ "contributions" : [
511+ {
512+ "id" : "glob-test-hub-gather" ,
513+ "type" : "ms.vss-web.hub" ,
514+ "targets" : [ "ms.vss-web.project-hub-group" ] ,
515+ "properties" : {
516+ "name" : "Glob Test Hub Gather"
517+ }
518+ }
519+ ]
520+ } ;
521+ fs . writeFileSync ( mainManifestPath , JSON . stringify ( primaryManifest , null , 2 ) ) ;
522+
523+ const secondaryManifest = {
524+ "scopes" : [
525+ "vso.work"
526+ ]
527+ } ;
528+ fs . writeFileSync ( secondaryManifestPath , JSON . stringify ( secondaryManifest , null , 2 ) ) ;
529+
530+ const mergerModulePath = path . resolve ( __dirname , '../../_build/exec/extension/_lib/merger' ) ;
531+ let MergerCtor : any ;
532+ try {
533+ // eslint-disable-next-line @typescript-eslint/no-var-requires
534+ MergerCtor = require ( mergerModulePath ) . Merger || require ( mergerModulePath ) . default ;
535+ } catch ( e ) {
536+ done ( e ) ;
537+ return ;
538+ }
539+
540+ const settings : any = {
541+ root : complexExtensionPath ,
542+ manifests : [ ] ,
543+ manifestGlobs : [
544+ 'azure-devops-extension.json' ,
545+ 'dist/Manifests/**/manifest-*.json'
546+ ] ,
547+ overrides : { } ,
548+ noPrompt : true
549+ } ;
550+
551+ const merger = new MergerCtor ( settings ) ;
552+ const gatherFn = ( merger as any ) [ 'gatherManifests' ] ;
553+ if ( typeof gatherFn !== 'function' ) {
554+ done ( new Error ( 'Merger.gatherManifests is not accessible' ) ) ;
555+ return ;
556+ }
557+
558+ Promise . resolve ( gatherFn . call ( merger ) )
559+ . then ( ( manifestPaths : string [ ] ) => {
560+ assert ( Array . isArray ( manifestPaths ) && manifestPaths . length >= 2 , 'gatherManifests should return at least two manifests' ) ;
561+
562+ const normalizedPaths = manifestPaths . map ( p => path . normalize ( p ) ) ;
563+ const expectedMain = path . normalize ( mainManifestPath ) ;
564+ const expectedSecondary = path . normalize ( secondaryManifestPath ) ;
565+
566+ assert ( normalizedPaths . indexOf ( expectedMain ) !== - 1 , 'gatherManifests should include root manifest' ) ;
567+ assert ( normalizedPaths . indexOf ( expectedSecondary ) !== - 1 , 'gatherManifests should include globbed manifest' ) ;
568+ done ( ) ;
569+ } )
570+ . catch ( done ) ;
571+ } ) ;
384572 } ) ;
385573
386574 describe ( 'Extension Creation - Complex Scenarios' , function ( ) {
0 commit comments