@nativescript/windows-hermes 0.1.0-alpha.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (50) hide show
  1. package/README.md +27 -0
  2. package/framework/__PROJECT_NAME__/App.xaml +13 -0
  3. package/framework/__PROJECT_NAME__/App.xaml.cs +240 -0
  4. package/framework/__PROJECT_NAME__/Assets/SplashScreen.png +0 -0
  5. package/framework/__PROJECT_NAME__/Assets/Square150x150Logo.png +0 -0
  6. package/framework/__PROJECT_NAME__/Assets/Square44x44Logo.png +0 -0
  7. package/framework/__PROJECT_NAME__/Assets/StoreLogo.png +0 -0
  8. package/framework/__PROJECT_NAME__/Assets/Wide310x150Logo.png +0 -0
  9. package/framework/__PROJECT_NAME__/CrashDiagnostics.cs +281 -0
  10. package/framework/__PROJECT_NAME__/Directory.Build.props +15 -0
  11. package/framework/__PROJECT_NAME__/Directory.Build.targets +106 -0
  12. package/framework/__PROJECT_NAME__/Package.appxmanifest +35 -0
  13. package/framework/__PROJECT_NAME__/Program.cs +34 -0
  14. package/framework/__PROJECT_NAME__/Properties/Default.rd.xml +9 -0
  15. package/framework/__PROJECT_NAME__/RuntimeHost.cs +359 -0
  16. package/framework/__PROJECT_NAME__/__PROJECT_NAME__.csproj +263 -0
  17. package/framework/dotnet-bridge/BinaryProtocol.cs +161 -0
  18. package/framework/dotnet-bridge/Bridge.BinaryDispatch.cs +373 -0
  19. package/framework/dotnet-bridge/Bridge.Dispatch.cs +597 -0
  20. package/framework/dotnet-bridge/Bridge.JsDelegate.cs +244 -0
  21. package/framework/dotnet-bridge/Bridge.Proxy.cs +573 -0
  22. package/framework/dotnet-bridge/Bridge.TaskHelper.cs +207 -0
  23. package/framework/dotnet-bridge/Bridge.cs +724 -0
  24. package/framework/dotnet-bridge/DispatchTypes.cs +333 -0
  25. package/framework/dotnet-bridge/DotNetBridge.csproj +30 -0
  26. package/framework/dotnet-bridge/publish_build/DotNetBridge.deps.json +23 -0
  27. package/framework/dotnet-bridge/publish_build/DotNetBridge.dll +0 -0
  28. package/framework/dotnet-bridge/publish_build/DotNetBridge.pdb +0 -0
  29. package/framework/dotnet-bridge/publish_build/DotNetBridge.runtimeconfig.json +14 -0
  30. package/framework/libs/x64/hermes-icu.dll +0 -0
  31. package/framework/libs/x64/hermes.dll +0 -0
  32. package/framework/libs/x64/nativescript.dll +0 -0
  33. package/framework/tools/ManifestMerger/ManifestMerger.deps.json +24 -0
  34. package/framework/tools/ManifestMerger/ManifestMerger.dll +0 -0
  35. package/framework/tools/ManifestMerger/ManifestMerger.pdb +0 -0
  36. package/framework/tools/dotnet-tool-arm64.exe +0 -0
  37. package/framework/tools/dotnet-tool-x64.exe +0 -0
  38. package/framework/tools/dotnet-tool.exe +0 -0
  39. package/framework/tools/dotnet-typings-gen/dotnet-typings-gen.deps.json +23 -0
  40. package/framework/tools/dotnet-typings-gen/dotnet-typings-gen.dll +0 -0
  41. package/framework/tools/dotnet-typings-gen/dotnet-typings-gen.exe +0 -0
  42. package/framework/tools/dotnet-typings-gen/dotnet-typings-gen.pdb +0 -0
  43. package/framework/tools/dotnet-typings-gen/dotnet-typings-gen.runtimeconfig.json +13 -0
  44. package/framework/tools/sbg-arm64.exe +0 -0
  45. package/framework/tools/sbg-x64.exe +0 -0
  46. package/framework/tools/sbg.exe +0 -0
  47. package/framework/tools/typings-generator-arm64.exe +0 -0
  48. package/framework/tools/typings-generator-x64.exe +0 -0
  49. package/framework/tools/typings-generator.exe +0 -0
  50. package/package.json +43 -0
package/README.md ADDED
@@ -0,0 +1,27 @@
1
+ # @nativescript/windows-hermes
2
+
3
+ NativeScript Windows runtime on **Microsoft's prebuilt Hermes** (`hermes.dll`, which exports both
4
+ `napi_*` and the JSR C API), with no Node/Bun/Deno host. The engine-neutral
5
+ `runtime::napi_engine` WinRT interop runs unchanged over Hermes's `napi_env`.
6
+
7
+ ## As an app runtime (drop-in for `@nativescript/windows`)
8
+ Publishes as `@nativescript/windows-hermes`, the **same** WinUI 3 framework as
9
+ `@nativescript/windows` with the Hermes runtime DLL — swap the dependency and an app runs
10
+ unchanged. Build the framework with the engine flag:
11
+ ```sh
12
+ pwsh -File ../../template/build.ps1 -Engine hermes # or: npm run build
13
+ ```
14
+ The engine → runtime-DLL adapter (the C ABI the WinUI 3 host P/Invokes) follows the reference
15
+ implementation in `../windows-quickjs/src/abi.rs`. See `../README.md` for the full contract.
16
+
17
+ ## Standalone host (dev/bench)
18
+ ```sh
19
+ cargo build --release --manifest-path packages/windows-hermes/Cargo.toml
20
+ target/release/nativescript-windows.exe # runs the WinRT demo, exit 0
21
+ ```
22
+ build.rs links `vendor/x64/hermes.lib`, forward-exports Hermes's `napi_*` from the exe (so napi-sys
23
+ resolves them), and copies `hermes.dll`/`hermes-icu.dll` next to the binary.
24
+
25
+ ## Notes
26
+ - Prebuilt engine binaries are committed under `vendor/` — see `vendor/README.md` for provenance
27
+ (Microsoft.JavaScript.Hermes NuGet) and the refresh recipe.
@@ -0,0 +1,13 @@
1
+ <Application
2
+ x:Class="__PROJECT_NAME__.App"
3
+ xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
4
+ xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
5
+ xmlns:local="using:__PROJECT_NAME__">
6
+ <Application.Resources>
7
+ <ResourceDictionary>
8
+ <ResourceDictionary.MergedDictionaries>
9
+ <XamlControlsResources xmlns="using:Microsoft.UI.Xaml.Controls" />
10
+ </ResourceDictionary.MergedDictionaries>
11
+ </ResourceDictionary>
12
+ </Application.Resources>
13
+ </Application>
@@ -0,0 +1,240 @@
1
+ using System;
2
+ using System.Text.Json;
3
+ using Windows.ApplicationModel;
4
+ using Microsoft.UI.Dispatching;
5
+ using Microsoft.UI.Xaml;
6
+ using Microsoft.UI.Xaml.Controls;
7
+ using Microsoft.UI.Xaml.Media;
8
+ using Windows.Storage;
9
+
10
+ namespace __PROJECT_NAME__
11
+ {
12
+ public interface INativeScriptApp
13
+ {
14
+ Microsoft.UI.Xaml.Window MainWindow { get; }
15
+ Microsoft.UI.Xaml.Window Window { get; }
16
+ }
17
+
18
+ sealed partial class App : Application, INativeScriptApp
19
+ {
20
+ private const string LastLaunchArgsKey = "LastLaunchArgs";
21
+ private readonly RuntimeHost _runtimeHost = new RuntimeHost();
22
+
23
+ internal static Window CurrentWindow { get; private set; }
24
+ public Window MainWindow => CurrentWindow;
25
+ public Window Window => CurrentWindow;
26
+
27
+ enum AppEventKind
28
+ {
29
+ Activated = 1,
30
+ Deactivated = 2,
31
+ Shown = 3,
32
+ Hidden = 4,
33
+ UncaughtError = 5,
34
+ Exit = 6
35
+ }
36
+
37
+ public App()
38
+ {
39
+ this.InitializeComponent();
40
+ CurrentWindow ??= new Window();
41
+
42
+ CrashDiagnostics.InstallGlobalHandlers();
43
+ CurrentWindow.Closed += OnWindowClosed;
44
+ CurrentWindow.Activated += OnWindowActivated;
45
+ CurrentWindow.VisibilityChanged += OnWindowVisibilityChanged;
46
+ this.UnhandledException += OnUnhandledException;
47
+ }
48
+
49
+ protected override async void OnLaunched(LaunchActivatedEventArgs e)
50
+ {
51
+ _runtimeHost.Initialize();
52
+
53
+ // Capture before any await — continuations may resume on a thread pool thread.
54
+ var dispatcherQueue = DispatcherQueue.GetForCurrentThread();
55
+
56
+ // Show crash report from the previous run if one exists.
57
+ try
58
+ {
59
+ var panicLogPath = System.IO.Path.Combine(
60
+ ApplicationData.Current.LocalFolder.Path, "nativescript-panic.log");
61
+ if (System.IO.File.Exists(panicLogPath))
62
+ {
63
+ var content = System.IO.File.ReadAllText(panicLogPath);
64
+ System.IO.File.Delete(panicLogPath);
65
+ if (!string.IsNullOrWhiteSpace(content))
66
+ await CrashDiagnostics.ShowCrashDialogAsync("Crash from previous run", content);
67
+ }
68
+ }
69
+ catch { }
70
+
71
+ string jsError = null;
72
+ try
73
+ {
74
+ _runtimeHost.RunMainScript();
75
+ jsError = _runtimeHost.GetLastJsError();
76
+ if (!string.IsNullOrEmpty(jsError))
77
+ {
78
+ CrashDiagnostics.WriteMessage("JS Error", jsError);
79
+ var report = CrashDiagnostics.BuildErrorReport(null, jsError);
80
+ CrashDiagnostics.WriteToTraceLog(report);
81
+ await CrashDiagnostics.ShowCrashDialogAsync("JavaScript Error", report);
82
+ }
83
+ }
84
+ catch (Exception scriptEx)
85
+ {
86
+ jsError = _runtimeHost.GetLastJsError();
87
+ System.Diagnostics.Debug.WriteLine($"[NativeScript] Script exception: {scriptEx.Message}");
88
+ CrashDiagnostics.WriteExceptionReport("RunMainScript", scriptEx, null);
89
+ var report = CrashDiagnostics.BuildErrorReport(scriptEx, jsError);
90
+ CrashDiagnostics.WriteToTraceLog(report);
91
+ await CrashDiagnostics.ShowCrashDialogAsync("Script Execution Error", report);
92
+ }
93
+
94
+ void ShowMainWindow()
95
+ {
96
+ StartPump();
97
+
98
+ try
99
+ {
100
+ if (CurrentWindow?.Content != null)
101
+ {
102
+ CurrentWindow.Activate();
103
+ }
104
+ }
105
+ catch { }
106
+ }
107
+
108
+ // After any await, the continuation may run on a thread pool thread.
109
+ // Schedule all UI-thread-required operations through the dispatcher queue.
110
+ if (dispatcherQueue?.HasThreadAccess == true)
111
+ {
112
+ ShowMainWindow();
113
+ }
114
+ else if (!(dispatcherQueue?.TryEnqueue(ShowMainWindow) ?? false))
115
+ {
116
+ ShowMainWindow();
117
+ }
118
+ }
119
+
120
+ private void OnWindowClosed(object sender, WindowEventArgs e)
121
+ {
122
+ StopPump();
123
+ _runtimeHost.NotifyAppEvent((int)AppEventKind.Exit, null);
124
+ ApplicationData.Current.LocalSettings.Values[LastLaunchArgsKey] = string.Empty;
125
+ _runtimeHost.Dispose();
126
+ }
127
+
128
+ private void OnWindowActivated(object sender, WindowActivatedEventArgs e)
129
+ {
130
+ var kind = e.WindowActivationState == WindowActivationState.Deactivated ? AppEventKind.Deactivated : AppEventKind.Activated;
131
+ _runtimeHost.NotifyAppEvent((int)kind, null);
132
+ }
133
+
134
+ private void OnWindowVisibilityChanged(object sender, WindowVisibilityChangedEventArgs e)
135
+ {
136
+ var kind = e.Visible ? AppEventKind.Shown : AppEventKind.Hidden;
137
+ _runtimeHost.NotifyAppEvent((int)kind, null);
138
+ }
139
+
140
+ private void OnUnhandledException(object sender, Microsoft.UI.Xaml.UnhandledExceptionEventArgs e)
141
+ {
142
+ e.Handled = true;
143
+ var jsError = _runtimeHost.GetLastJsError();
144
+
145
+ var message = e.Message ?? jsError ?? "Unhandled exception";
146
+ _runtimeHost.NotifyAppEvent((int)AppEventKind.UncaughtError, message);
147
+
148
+ CrashDiagnostics.WriteExceptionReport(
149
+ "Xaml.UnhandledException",
150
+ e.Exception,
151
+ "JsError=" + (jsError ?? "<none>"));
152
+
153
+ var report = CrashDiagnostics.BuildErrorReport(e.Exception, jsError);
154
+ CrashDiagnostics.WriteToTraceLog(report);
155
+ var _ = CrashDiagnostics.ShowCrashDialogAsync(
156
+ e.Message ?? "Unhandled exception", report);
157
+ }
158
+
159
+ // The V8 runtime pump (JS timers + microtask/promise continuations) must NOT run *inside*
160
+ // CompositionTarget.Rendering: that callback executes within XAML's render walk, and the JS it
161
+ // pumps frequently mutates the live XAML tree (e.g. setting Image.Source when an async image
162
+ // load completes, or layout-invalidating writes from JS-driven animations). Mutating the tree
163
+ // during the render walk re-enters the XAML core illegally and trips its re-entrancy guard,
164
+ // which RoFailFastWithErrorContext's with E_UNEXPECTED (0x8000FFFF): a stowed exception
165
+ // (0xC000027B) that faults in Microsoft.UI.Xaml.dll and bypasses every managed/native handler
166
+ // (so it leaves no crash/panic log).
167
+ //
168
+ // Pump cadence comes from two sources, both routed through the guarded SchedulePump():
169
+ // 1. CompositionTarget.Rendering — fires at the display's true refresh rate (120/144/240Hz,
170
+ // NOT capped at 60) while the compositor is producing frames, so JS-driven animations stay
171
+ // smooth on high-refresh displays.
172
+ // 2. A low-frequency DispatcherQueueTimer heartbeat — keeps timers/promise continuations
173
+ // running even when Rendering stops firing (e.g. the window is minimised or fully occluded,
174
+ // so the compositor idles and stops raising Rendering).
175
+ // Either way the actual pump runs as an ordinary dispatcher work item OUTSIDE the render walk,
176
+ // where mutating the tree is legal. _pumpQueued keeps at most one pump in flight, so the two
177
+ // sources never double-pump and work items can't pile up. All fields are touched only on the UI
178
+ // thread, so no synchronization is needed.
179
+ private DispatcherQueue _dispatcherQueue;
180
+ private DispatcherQueueTimer _pumpHeartbeat;
181
+ private bool _pumpQueued;
182
+
183
+ private void StartPump()
184
+ {
185
+ _dispatcherQueue = DispatcherQueue.GetForCurrentThread();
186
+
187
+ CompositionTarget.Rendering -= OnRenderFrame;
188
+ CompositionTarget.Rendering += OnRenderFrame;
189
+
190
+ if (_pumpHeartbeat == null && _dispatcherQueue != null)
191
+ {
192
+ _pumpHeartbeat = _dispatcherQueue.CreateTimer();
193
+ _pumpHeartbeat.Interval = TimeSpan.FromMilliseconds(100);
194
+ _pumpHeartbeat.IsRepeating = true;
195
+ _pumpHeartbeat.Tick += OnPumpHeartbeat;
196
+ _pumpHeartbeat.Start();
197
+ }
198
+ }
199
+
200
+ private void StopPump()
201
+ {
202
+ CompositionTarget.Rendering -= OnRenderFrame;
203
+ if (_pumpHeartbeat != null)
204
+ {
205
+ _pumpHeartbeat.Stop();
206
+ _pumpHeartbeat.Tick -= OnPumpHeartbeat;
207
+ _pumpHeartbeat = null;
208
+ }
209
+ }
210
+
211
+ // Inside XAML's render walk — only schedule; never pump or touch the tree here.
212
+ private void OnRenderFrame(object sender, object e) => SchedulePump();
213
+
214
+ private void OnPumpHeartbeat(DispatcherQueueTimer sender, object args) => SchedulePump();
215
+
216
+ private void SchedulePump()
217
+ {
218
+ if (_pumpQueued)
219
+ {
220
+ return;
221
+ }
222
+
223
+ var dispatcherQueue = _dispatcherQueue;
224
+ if (dispatcherQueue == null)
225
+ {
226
+ return;
227
+ }
228
+
229
+ _pumpQueued = true;
230
+ dispatcherQueue.TryEnqueue(() =>
231
+ {
232
+ _pumpQueued = false;
233
+ _runtimeHost.PumpTimers();
234
+ #if DEBUG
235
+ _runtimeHost.PumpDevtools();
236
+ #endif
237
+ });
238
+ }
239
+ }
240
+ }
@@ -0,0 +1,281 @@
1
+ using System;
2
+ using System.IO;
3
+ using System.Text;
4
+ using System.Text.RegularExpressions;
5
+ using System.Threading.Tasks;
6
+ using WinRT.Interop;
7
+ using Windows.ApplicationModel.Core;
8
+ using Windows.ApplicationModel.DataTransfer;
9
+ using Windows.Storage;
10
+ using Windows.System;
11
+ using Windows.UI.Popups;
12
+
13
+ namespace __PROJECT_NAME__
14
+ {
15
+ internal static class CrashDiagnostics
16
+ {
17
+ private static bool _installed;
18
+
19
+ public static void InstallGlobalHandlers()
20
+ {
21
+ if (_installed) return;
22
+ _installed = true;
23
+
24
+ AppDomain.CurrentDomain.UnhandledException += (_, args) =>
25
+ {
26
+ var ex = args.ExceptionObject as Exception;
27
+ WriteExceptionReport("AppDomain.UnhandledException", ex, "IsTerminating=" + args.IsTerminating);
28
+ };
29
+
30
+ TaskScheduler.UnobservedTaskException += (_, args) =>
31
+ {
32
+ // Always mark the exception as observed to prevent the finalizer
33
+ // from rethrowing it on the finalizer thread.
34
+ try { args.SetObserved(); } catch { }
35
+
36
+ // Log the exception but never allow logging failures to propagate
37
+ // (this handler runs on the finalizer thread in some cases).
38
+ try
39
+ {
40
+ WriteExceptionReport("TaskScheduler.UnobservedTaskException", args.Exception, null);
41
+ }
42
+ catch (Exception logEx)
43
+ {
44
+ try { WriteToTraceLog("[CrashDiagnostics] Failed to write unobserved exception: " + logEx); } catch { }
45
+ }
46
+ };
47
+ }
48
+
49
+ public static void WriteExceptionReport(string source, Exception ex, string details)
50
+ {
51
+ var sb = new StringBuilder();
52
+ sb.AppendLine("============================================================");
53
+ sb.AppendLine("Timestamp: " + DateTimeOffset.UtcNow.ToString("o"));
54
+ sb.AppendLine("Source: " + source);
55
+ if (!string.IsNullOrWhiteSpace(details)) sb.AppendLine("Details: " + details);
56
+ if (ex != null) { sb.AppendLine("Exception:"); sb.AppendLine(ex.ToString()); }
57
+ else sb.AppendLine("Exception: <null>");
58
+ sb.AppendLine("Managed stack snapshot:");
59
+ sb.AppendLine(Environment.StackTrace);
60
+ sb.AppendLine();
61
+ AppendToLog(sb.ToString());
62
+ }
63
+
64
+ public static void WriteMessage(string source, string message)
65
+ {
66
+ var sb = new StringBuilder();
67
+ sb.AppendLine("============================================================");
68
+ sb.AppendLine("Timestamp: " + DateTimeOffset.UtcNow.ToString("o"));
69
+ sb.AppendLine("Source: " + source);
70
+ sb.AppendLine("Message: " + message);
71
+ sb.AppendLine();
72
+ AppendToLog(sb.ToString());
73
+ }
74
+
75
+ public static string BuildErrorReport(Exception ex, string jsError = null, string extraDetails = null)
76
+ {
77
+ var sb = new StringBuilder();
78
+
79
+ if (!string.IsNullOrWhiteSpace(jsError))
80
+ {
81
+ sb.AppendLine("── JavaScript Error ──────────────────────────────────────");
82
+ sb.AppendLine(jsError.Trim());
83
+ sb.AppendLine();
84
+ }
85
+
86
+ if (ex != null)
87
+ {
88
+ sb.AppendLine("── Native Exception ──────────────────────────────────────");
89
+ sb.AppendLine(ex.GetType().Name + ": " + ex.Message);
90
+ if (ex.StackTrace != null) sb.AppendLine(ex.StackTrace);
91
+ var inner = ex.InnerException;
92
+ while (inner != null)
93
+ {
94
+ sb.AppendLine("Caused by: " + inner.GetType().Name + ": " + inner.Message);
95
+ if (inner.StackTrace != null) sb.AppendLine(inner.StackTrace);
96
+ inner = inner.InnerException;
97
+ }
98
+ sb.AppendLine();
99
+ }
100
+
101
+ // Include panic log from previous or current run
102
+ try
103
+ {
104
+ var panicLog = Path.Combine(ApplicationData.Current.LocalFolder.Path, "nativescript-panic.log");
105
+ if (File.Exists(panicLog))
106
+ {
107
+ var content = File.ReadAllText(panicLog, Encoding.UTF8).Trim();
108
+ if (!string.IsNullOrEmpty(content))
109
+ {
110
+ sb.AppendLine("── Rust Panic Log ────────────────────────────────────────");
111
+ sb.AppendLine(content);
112
+ sb.AppendLine();
113
+ }
114
+ }
115
+ }
116
+ catch { }
117
+
118
+ if (!string.IsNullOrWhiteSpace(extraDetails))
119
+ {
120
+ sb.AppendLine("── Additional Info ───────────────────────────────────────");
121
+ sb.AppendLine(extraDetails);
122
+ sb.AppendLine();
123
+ }
124
+
125
+ sb.AppendLine("──────────────────────────────────────────────────────────");
126
+ sb.AppendLine("Timestamp: " + DateTimeOffset.UtcNow.ToString("o"));
127
+
128
+ return sb.ToString();
129
+ }
130
+
131
+ public static string CrashLogPath()
132
+ {
133
+ try { return Path.Combine(ApplicationData.Current.LocalFolder.Path, "nativescript-crash.log"); }
134
+ catch { return null; }
135
+ }
136
+
137
+ /// Parses the first "path:line:col" source location from a JS stack trace.
138
+ /// Returns null if none found. Handles Windows drive-letter paths (C:\...).
139
+ public static (string file, int line, int col)? TryExtractSourceLocation(string errorReport)
140
+ {
141
+ if (string.IsNullOrEmpty(errorReport)) return null;
142
+
143
+ // Match the contents of the first (path:line:col) group in the stack trace.
144
+ var m = Regex.Match(errorReport, @"\(([^)]+)\)");
145
+ if (!m.Success) return null;
146
+
147
+ var inner = m.Groups[1].Value; // e.g. "C:\app\bundle.js:42:13"
148
+
149
+ // Parse col from the right (last :digits).
150
+ var lastColon = inner.LastIndexOf(':');
151
+ if (lastColon < 0 || !int.TryParse(inner.Substring(lastColon + 1), out int col)) return null;
152
+ inner = inner.Substring(0, lastColon);
153
+
154
+ // Parse line from the right (second-to-last :digits).
155
+ lastColon = inner.LastIndexOf(':');
156
+ if (lastColon < 0 || !int.TryParse(inner.Substring(lastColon + 1), out int line)) return null;
157
+ var file = inner.Substring(0, lastColon);
158
+
159
+ return string.IsNullOrEmpty(file) ? null : (file, line, col);
160
+ }
161
+
162
+ public static async Task ShowCrashDialogAsync(string heading, string errorReport)
163
+ {
164
+ try
165
+ {
166
+ // Write full details to log so developer can access them even if the
167
+ // dialog summary is truncated.
168
+ var logPath = CrashLogPath();
169
+ if (logPath != null)
170
+ File.WriteAllText(logPath, errorReport, Encoding.UTF8);
171
+
172
+ // Truncate for the dialog (MessageDialog has practical limits).
173
+ const int MaxLen = 800;
174
+ var summary = errorReport.Length > MaxLen
175
+ ? errorReport.Substring(0, MaxLen) + "\n\n[truncated — see log for full details]"
176
+ : errorReport;
177
+
178
+ if (logPath != null)
179
+ summary += "\n\nFull log: " + logPath;
180
+
181
+ var dialog = new MessageDialog(summary, "NativeScript Runtime Error");
182
+ try
183
+ {
184
+ var window = App.CurrentWindow;
185
+ if (window != null)
186
+ {
187
+ InitializeWithWindow.Initialize(dialog, WindowNative.GetWindowHandle(window));
188
+ }
189
+ }
190
+ catch { }
191
+
192
+ // MessageDialog supports at most 3 commands. When a source location is
193
+ // available, swap "Copy Details" for "Open in VS Code" so the user can
194
+ // choose between jumping to the error or restarting. Full details are
195
+ // always written to the log file shown in the dialog text.
196
+ var srcLocation = TryExtractSourceLocation(errorReport);
197
+ if (srcLocation.HasValue)
198
+ {
199
+ var (srcFile, srcLine, srcCol) = srcLocation.Value;
200
+ dialog.Commands.Add(new UICommand("Open Source File", async _ =>
201
+ {
202
+ try
203
+ {
204
+ var normalizedPath = srcFile.Replace('\\', '/');
205
+ var uriStr = $"file:///{normalizedPath}";
206
+ if (Uri.TryCreate(uriStr, UriKind.Absolute, out var fileUri))
207
+ await Launcher.LaunchUriAsync(fileUri,
208
+ new LauncherOptions { DisplayApplicationPicker = true });
209
+ }
210
+ catch { }
211
+ }));
212
+ }
213
+ else
214
+ {
215
+ dialog.Commands.Add(new UICommand("Copy Details", _ =>
216
+ {
217
+ try
218
+ {
219
+ var dp = new DataPackage();
220
+ dp.SetText(errorReport);
221
+ Clipboard.SetContent(dp);
222
+ }
223
+ catch { }
224
+ }));
225
+ }
226
+
227
+ dialog.Commands.Add(new UICommand("Restart App", async _ =>
228
+ {
229
+ try { await CoreApplication.RequestRestartAsync("crash-restart"); }
230
+ catch { }
231
+ }));
232
+
233
+ dialog.Commands.Add(new UICommand("Dismiss"));
234
+ dialog.DefaultCommandIndex = 0;
235
+ dialog.CancelCommandIndex = 2;
236
+
237
+ await dialog.ShowAsync();
238
+ }
239
+ catch (Exception ex)
240
+ {
241
+ System.Diagnostics.Debug.WriteLine("[NativeScript] Failed to show crash dialog: " + ex.Message);
242
+ }
243
+ }
244
+
245
+ /// <summary>
246
+ /// Appends <paramref name="message"/> to the runtime trace file in the Win32 temp path.
247
+ /// Prefer `console.log`; fall back to `ns_trace.log` for CLI/legacy compatibility.
248
+ /// In a packaged Windows app, GetTempPath() resolves inside the app container temp area,
249
+ /// matching Rust's std::env::temp_dir() so the CLI still sees the same log stream.
250
+ /// </summary>
251
+ public static void WriteToTraceLog(string message)
252
+ {
253
+ try
254
+ {
255
+ // System.IO.Path.GetTempPath() calls Win32 GetTempPathW() which for a packaged
256
+ // desktop app resolves into the container temp folder, matching Rust.
257
+ var temp = System.IO.Path.GetTempPath();
258
+ var consolePath = Path.Combine(temp, "console.log");
259
+ File.AppendAllText(consolePath, message, Encoding.UTF8);
260
+
261
+ // Maintain legacy compatibility: also append to ns_trace.log if it already exists
262
+ var legacyPath = Path.Combine(temp, "ns_trace.log");
263
+ if (File.Exists(legacyPath))
264
+ {
265
+ File.AppendAllText(legacyPath, message, Encoding.UTF8);
266
+ }
267
+ }
268
+ catch { }
269
+ }
270
+
271
+ private static void AppendToLog(string content)
272
+ {
273
+ try
274
+ {
275
+ var logPath = Path.Combine(ApplicationData.Current.LocalFolder.Path, "nativescript-crash.log");
276
+ File.AppendAllText(logPath, content, Encoding.UTF8);
277
+ }
278
+ catch { }
279
+ }
280
+ }
281
+ }
@@ -0,0 +1,15 @@
1
+ <Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
2
+ <!-- Default property values for manifest overrides.
3
+ Override these in Directory.Build.props or via /p: properties. -->
4
+ <PropertyGroup>
5
+ <AppDisplayName Condition=" '$(AppDisplayName)' == '' ">$(MSBuildProjectName)</AppDisplayName>
6
+ <AppDescription Condition=" '$(AppDescription)' == '' ">A NativeScript Windows application</AppDescription>
7
+ <Square150x150Logo Condition=" '$(Square150x150Logo)' == '' ">Assets\Square150x150Logo.png</Square150x150Logo>
8
+ <Square44x44Logo Condition=" '$(Square44x44Logo)' == '' ">Assets\Square44x44Logo.png</Square44x44Logo>
9
+ <Wide310x150Logo Condition=" '$(Wide310x150Logo)' == '' ">Assets\Wide310x150Logo.png</Wide310x150Logo>
10
+ <StoreLogo Condition=" '$(StoreLogo)' == '' ">Assets\StoreLogo.png</StoreLogo>
11
+ <LockScreenLogo Condition=" '$(LockScreenLogo)' == '' ">Assets\LockScreenLogo.png</LockScreenLogo>
12
+ <SplashBackgroundColor Condition=" '$(SplashBackgroundColor)' == '' ">#65ADF1</SplashBackgroundColor>
13
+ <SplashImage Condition=" '$(SplashImage)' == '' ">Assets\SplashScreen.png</SplashImage>
14
+ </PropertyGroup>
15
+ </Project>