@@ -53,6 +53,7 @@ internal sealed class RuntimeHost : IDisposable
5353 private static extern void runtime_free_js_error ( IntPtr ptr ) ;
5454
5555 [ DllImport ( NativeScriptLibrary , EntryPoint = nameof ( runtime_has_devtools ) ) ]
56+ [ return : MarshalAs ( UnmanagedType . U1 ) ]
5657 private static extern bool runtime_has_devtools ( ) ;
5758
5859 [ DllImport ( NativeScriptLibrary , EntryPoint = nameof ( runtime_pump_timers ) ) ]
@@ -98,6 +99,21 @@ public void PumpTimers()
9899 [ DllImport ( NativeScriptLibrary , EntryPoint = nameof ( runtime_free_string ) ) ]
99100 private static extern void runtime_free_string ( IntPtr ptr ) ;
100101
102+ [ DllImport ( NativeScriptLibrary , EntryPoint = nameof ( runtime_devtools_wait_for_debugger ) ) ]
103+ [ return : MarshalAs ( UnmanagedType . U1 ) ]
104+ private static extern bool runtime_devtools_wait_for_debugger ( long runtime , uint timeoutMs ) ;
105+
106+ // iOS uses 41000 and Android 42000.
107+ private const ushort DevtoolsPort = 43000 ;
108+ private const uint DebuggerWaitTimeoutMs = 30000 ;
109+
110+ // LocalFolder sentinel files shared with the CLI's WindowsApplicationManager.
111+ private const string InspectorMarker = "ns-inspector" ;
112+ private const string DebugBreakMarker = "ns-debugbreak" ;
113+ private const string DebuggerStartedMarker = "ns-debugger-started" ;
114+
115+ private bool _waitForDebugger ;
116+
101117 public string DevtoolsFrontendUrl { get ; private set ; }
102118
103119 private bool _devtoolsAvailable ;
@@ -118,7 +134,7 @@ private void StartDevtoolsSafely()
118134 IntPtr urlPtr = IntPtr . Zero ;
119135 try
120136 {
121- urlPtr = runtime_devtools_start ( _runtime , 42000 ) ;
137+ urlPtr = runtime_devtools_start ( _runtime , DevtoolsPort ) ;
122138 if ( urlPtr == IntPtr . Zero ) return ;
123139 var wsUrl = Marshal . PtrToStringUTF8 ( urlPtr ) ;
124140 DevtoolsFrontendUrl = wsUrl != null
@@ -127,6 +143,8 @@ private void StartDevtoolsSafely()
127143 if ( DevtoolsFrontendUrl != null )
128144 {
129145 _devtoolsAvailable = true ;
146+ // The server may fall back to the next free port.
147+ WriteMarker ( DebuggerStartedMarker , new Uri ( wsUrl ) . Port . ToString ( ) ) ;
130148 System . Diagnostics . Debug . WriteLine ( $ "[NativeScript DevTools] { DevtoolsFrontendUrl } ") ;
131149 }
132150 }
@@ -178,31 +196,48 @@ public void Initialize()
178196 }
179197 }
180198 catch { }
181- if ( ConsumeDebugBreakMarker ( ) )
199+ DeleteMarker ( DebuggerStartedMarker ) ;
200+ var debugBreak = ConsumeMarker ( DebugBreakMarker ) ;
201+ if ( ConsumeMarker ( InspectorMarker ) || debugBreak )
202+ {
182203 StartDevtoolsSafely ( ) ;
204+ _waitForDebugger = debugBreak && _devtoolsAvailable ;
205+ }
183206#endif
184207 _initialized = true ;
185208 }
186209
187210#if DEBUG
188- /// <summary>
189- /// Returns true and deletes the marker if the CLI wrote ns-debugbreak to LocalFolder,
190- /// matching the Android sentinel-file pattern used by the NativeScript CLI.
191- /// </summary>
192- private static bool ConsumeDebugBreakMarker ( )
211+ private static string MarkerPath ( string name ) =>
212+ Path . Combine ( Windows . Storage . ApplicationData . Current . LocalFolder . Path , name ) ;
213+
214+ private static bool ConsumeMarker ( string name )
193215 {
194216 try
195217 {
196- var markerPath = System . IO . Path . Combine (
197- Windows . Storage . ApplicationData . Current . LocalFolder . Path ,
198- "ns-debugbreak" ) ;
199- if ( ! System . IO . File . Exists ( markerPath ) )
218+ var markerPath = MarkerPath ( name ) ;
219+ if ( ! File . Exists ( markerPath ) )
200220 return false ;
201- System . IO . File . Delete ( markerPath ) ;
221+ File . Delete ( markerPath ) ;
202222 return true ;
203223 }
204224 catch { return false ; }
205225 }
226+
227+ private static void WriteMarker ( string name , string contents )
228+ {
229+ try { File . WriteAllText ( MarkerPath ( name ) , contents ) ; }
230+ catch ( Exception ex )
231+ {
232+ System . Diagnostics . Debug . WriteLine ( $ "[NativeScript DevTools] Could not write { name } : { ex . Message } ") ;
233+ }
234+ }
235+
236+ private static void DeleteMarker ( string name )
237+ {
238+ try { File . Delete ( MarkerPath ( name ) ) ; }
239+ catch { }
240+ }
206241#endif
207242
208243 /// Reads a file that may live inside a sealed app.nsbundle instead of on disk: tries the
@@ -231,6 +266,15 @@ public void RunMainScript()
231266 if ( ! _initialized )
232267 throw new InvalidOperationException ( "Runtime must be initialized before running scripts." ) ;
233268
269+ #if DEBUG
270+ if ( _waitForDebugger )
271+ {
272+ // --debug-brk: hold the first script until DevTools attaches.
273+ _waitForDebugger = false ;
274+ if ( ! runtime_devtools_wait_for_debugger ( _runtime , DebuggerWaitTimeoutMs ) )
275+ System . Diagnostics . Debug . WriteLine ( "[NativeScript DevTools] No debugger attached, continuing." ) ;
276+ }
277+ #endif
234278 var entryPath = ResolveEntryScriptPath ( ) ;
235279 if ( entryPath == null )
236280 {
0 commit comments