@@ -304,6 +304,72 @@ export class WindowsProjectService
304304 platformData . projectRoot ,
305305 projectData . projectName ,
306306 ) ;
307+
308+ // Attempt to run dotnet-tool to publish/copy DotNetBridge and app projects if available
309+ try {
310+ const marker = path . join ( appProjectDir , "dotnet-bridge" , "publish" , ".dotnet_tool_done" ) ;
311+ if ( fs . existsSync ( marker ) ) {
312+ this . $logger . info ( "DotNetBridge publish marker found; skipping dotnet-tool" ) ;
313+ }
314+ else {
315+ const arch = process . arch === "arm64" ? "arm64" : "x64" ;
316+ const exeCandidates = [
317+ process . env . DOTNET_TOOL_PATH ,
318+ path . join ( platformData . projectRoot , "tools" , `dotnet-tool-${ arch } .exe` ) ,
319+ path . join ( platformData . projectRoot , "tools" , "dotnet-tool.exe" ) ,
320+ ] . filter ( Boolean as any ) ;
321+ let exePath : string | null = null ;
322+ for ( const p of exeCandidates ) {
323+ if ( p && fs . existsSync ( p ) ) { exePath = p as string ; break ; }
324+ }
325+ if ( exePath ) {
326+ this . $logger . info ( `Running dotnet-tool: ${ exePath } ` ) ;
327+ try {
328+
329+ const result = await this . $childProcess . spawnFromEvent ( exePath , [ "--app-root" , platformData . projectRoot , "--dir" , "app" , "--force" ] , "close" , { cwd : platformData . projectRoot } , { throwError : false } ) ;
330+ if ( result && result . stdout ) { this . $logger . info ( result . stdout ) ; }
331+ }
332+ catch ( err ) {
333+ this . $logger . warn ( `dotnet-tool execution failed: ${ err } ` ) ;
334+ }
335+
336+ // Ensure sentinel exists: if publish/ contains DotNetBridge.dll, write marker so MSBuild waits succeed
337+ try {
338+ const markerPath = path . join ( appProjectDir , "dotnet-bridge" , "publish" , ".dotnet_tool_done" ) ;
339+ if ( ! fs . existsSync ( markerPath ) ) {
340+ const publishDir = path . join ( appProjectDir , "dotnet-bridge" , "publish" ) ;
341+ if ( fs . existsSync ( publishDir ) ) {
342+ const files = fs . readdirSync ( publishDir ) ;
343+ if ( files && files . length > 0 ) {
344+ const bridgeDll = path . join ( publishDir , "DotNetBridge.dll" ) ;
345+ if ( fs . existsSync ( bridgeDll ) ) {
346+ try {
347+ fs . writeFileSync ( markerPath , "done" , "utf8" ) ;
348+ this . $logger . info ( `Created dotnet-tool marker at ${ markerPath } ` ) ;
349+ }
350+ catch ( werr ) {
351+ this . $logger . warn ( `Failed creating dotnet-tool marker: ${ werr } ` ) ;
352+ }
353+ }
354+ else {
355+ this . $logger . info ( `[NativeScript] publish directory exists but DotNetBridge.dll missing; files=${ files . join ( ',' ) } ` ) ;
356+ }
357+ }
358+ }
359+ else {
360+ this . $logger . info ( `[NativeScript] publish directory not found at ${ publishDir } ` ) ;
361+ }
362+ }
363+ }
364+ catch ( e ) {
365+ this . $logger . warn ( `dotnet-tool sentinel check failed: ${ e } ` ) ;
366+ }
367+ }
368+ }
369+ }
370+ catch ( err ) {
371+ this . $logger . warn ( `dotnet-tool check failed: ${ err } ` ) ;
372+ }
307373 const pluginsDir = path . join ( appProjectDir , "plugins" ) ;
308374
309375 // Ensure plugins directory exists inside the platform app folder (where csproj expects it)
@@ -360,13 +426,14 @@ export class WindowsProjectService
360426 "<Project>" ,
361427 ] ;
362428 for ( const p of stagedPlugins ) {
363- const importPathProps = `plugins\\${ p . name } \\plugin.props` ;
364- const importPathTargets = `plugins\\${ p . name } \\plugin.targets` ;
429+ const pluginRelDir = p . name . split ( "/" ) . join ( "\\" ) ;
430+ const importPathProps = `$(MSBuildThisFileDirectory)${ pluginRelDir } \\plugin.props` ;
431+ const importPathTargets = `$(MSBuildThisFileDirectory)${ pluginRelDir } \\plugin.targets` ;
365432 propsLines . push (
366- ` <Import Project=\ "${ importPathProps } \ " Condition=\ "Exists('${ importPathProps . replace ( / ' / g , "''" ) } ')\ " />` ,
433+ ` <Import Project="${ importPathProps } " Condition="Exists('${ importPathProps } ')" />` ,
367434 ) ;
368435 targetsLines . push (
369- ` <Import Project=\ "${ importPathTargets } \ " Condition=\ "Exists('${ importPathTargets . replace ( / ' / g , "''" ) } ')\ " />` ,
436+ ` <Import Project="${ importPathTargets } " Condition="Exists('${ importPathTargets } ')" />` ,
370437 ) ;
371438 }
372439 propsLines . push ( "</Project>" ) ;
@@ -542,22 +609,23 @@ export class WindowsProjectService
542609 ) ;
543610 }
544611
612+ const collectStagedFiles = ( root : string ) : string [ ] => {
613+ const out : string [ ] = [ ] ;
614+ if ( ! this . $fs . exists ( root ) ) return out ;
615+ const walk = ( dir : string ) => {
616+ for ( const e of fs . readdirSync ( dir , { withFileTypes : true } ) ) {
617+ const full = path . join ( dir , e . name ) ;
618+ if ( e . isDirectory ( ) ) walk ( full ) ;
619+ else out . push ( path . relative ( root , full ) . split ( path . sep ) . join ( "\\" ) ) ;
620+ }
621+ } ;
622+ walk ( root ) ;
623+ return out ;
624+ } ;
625+
545626 // generate plugin.props if not provided
546627 if ( ! this . $fs . exists ( path . join ( pluginStageDir , "plugin.props" ) ) ) {
547- const collect = ( root : string ) => {
548- const out : string [ ] = [ ] ;
549- if ( ! this . $fs . exists ( root ) ) return out ;
550- const walk = ( dir : string ) => {
551- for ( const e of fs . readdirSync ( dir , { withFileTypes : true } ) ) {
552- const full = path . join ( dir , e . name ) ;
553- if ( e . isDirectory ( ) ) walk ( full ) ;
554- else out . push ( path . relative ( root , full ) . split ( path . sep ) . join ( "\\" ) ) ;
555- }
556- } ;
557- walk ( root ) ;
558- return out ;
559- } ;
560- const stagedFiles = collect ( pluginStageDir ) ;
628+ const stagedFiles = collectStagedFiles ( pluginStageDir ) ;
561629 const lines : string [ ] = [
562630 '<?xml version="1.0" encoding="utf-8"?>' ,
563631 "<Project>" ,
@@ -580,6 +648,38 @@ export class WindowsProjectService
580648 lines . join ( "\n" ) ,
581649 ) ;
582650 }
651+
652+ // generate plugin.targets if not provided — uses an explicit Copy task so
653+ // that DLLs reach bin/ even when EnableMsixTooling=true intercepts Content items.
654+ if ( ! this . $fs . exists ( path . join ( pluginStageDir , "plugin.targets" ) ) ) {
655+ const stagedFiles = collectStagedFiles ( pluginStageDir ) ;
656+ if ( stagedFiles . length > 0 ) {
657+ const safeName = pluginData . name . replace ( / [ ^ a - z A - Z 0 - 9 ] / g, "_" ) ;
658+ const pluginOutDir = `plugins\\${ pluginData . name . split ( "/" ) . join ( "\\" ) } ` ;
659+ const lines : string [ ] = [
660+ '<?xml version="1.0" encoding="utf-8"?>' ,
661+ "<Project>" ,
662+ ` <Target Name="CopyPlugin_${ safeName } " AfterTargets="Build">` ,
663+ ` <MakeDir Directories="$(OutDir)${ pluginOutDir } " />` ,
664+ ] ;
665+ for ( const f of stagedFiles ) {
666+ const rel = f . split ( path . sep ) . join ( "\\" ) ;
667+ lines . push (
668+ ` <Copy SourceFiles="$(MSBuildThisFileDirectory)${ rel } "` ,
669+ ) ;
670+ lines . push (
671+ ` DestinationFiles="$(OutDir)${ pluginOutDir } \\${ rel } "` ,
672+ ) ;
673+ lines . push ( ` SkipUnchangedFiles="true" />` ) ;
674+ }
675+ lines . push ( " </Target>" ) ;
676+ lines . push ( "</Project>" ) ;
677+ this . $fs . writeFile (
678+ path . join ( pluginStageDir , "plugin.targets" ) ,
679+ lines . join ( "\n" ) ,
680+ ) ;
681+ }
682+ }
583683 }
584684
585685 public async removePluginNativeCode (
0 commit comments