@nativescript/windows-v8 0.1.0-alpha.0 → 0.1.0-alpha.140

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 (30) hide show
  1. package/README.md +3 -55
  2. package/framework/__PROJECT_NAME__/App.xaml +13 -13
  3. package/framework/__PROJECT_NAME__/Package.appxmanifest +35 -35
  4. package/framework/__PROJECT_NAME__/Program.cs +34 -34
  5. package/framework/__PROJECT_NAME__/Properties/Default.rd.xml +9 -9
  6. package/framework/__PROJECT_NAME__/RuntimeHost.cs +67 -11
  7. package/framework/__PROJECT_NAME__/__PROJECT_NAME__.csproj +6 -1
  8. package/framework/dotnet-bridge/BinaryProtocol.cs +161 -161
  9. package/framework/dotnet-bridge/Bridge.JsDelegate.cs +244 -244
  10. package/framework/dotnet-bridge/Bridge.TaskHelper.cs +207 -207
  11. package/framework/dotnet-bridge/DispatchTypes.cs +333 -333
  12. package/framework/dotnet-bridge/DotNetBridge.csproj +30 -30
  13. package/framework/libs/x64/nativescript.dll +0 -0
  14. package/framework/tools/ManifestMerger/ManifestMerger.dll +0 -0
  15. package/framework/tools/ManifestMerger/ManifestMerger.pdb +0 -0
  16. package/framework/tools/dotnet-tool-arm64.exe +0 -0
  17. package/framework/tools/dotnet-tool-x64.exe +0 -0
  18. package/framework/tools/dotnet-tool.exe +0 -0
  19. package/framework/tools/dotnet-typings-gen/dotnet-typings-gen.dll +0 -0
  20. package/framework/tools/dotnet-typings-gen/dotnet-typings-gen.exe +0 -0
  21. package/framework/tools/dotnet-typings-gen/dotnet-typings-gen.pdb +0 -0
  22. package/framework/tools/nsbundle_pack-x64.exe +0 -0
  23. package/framework/tools/nsbundle_pack.exe +0 -0
  24. package/framework/tools/sbg-arm64.exe +0 -0
  25. package/framework/tools/sbg-x64.exe +0 -0
  26. package/framework/tools/sbg.exe +0 -0
  27. package/framework/tools/typings-generator-arm64.exe +0 -0
  28. package/framework/tools/typings-generator-x64.exe +0 -0
  29. package/framework/tools/typings-generator.exe +0 -0
  30. package/package.json +43 -43
package/README.md CHANGED
@@ -1,55 +1,3 @@
1
- # @nativescript/windows-v8
2
-
3
- NativeScript Windows runtime on **V8** — the same engine our classic runtime is built on. No Node.
4
-
5
- ## As an app runtime (drop-in for `@nativescript/windows`)
6
- Publishes as `@nativescript/windows-v8`, the **same** WinUI 3 framework as `@nativescript/windows`
7
- with a napi-backed V8 runtime DLL — swap the dependency and an app runs unchanged. Build the
8
- framework with the engine flag:
9
- ```sh
10
- pwsh -File ../../template/build.ps1 -Engine v8 # or: npm run build
11
- ```
12
- The engine → runtime-DLL adapter (the C ABI the WinUI 3 host P/Invokes) follows the reference
13
- implementation in `../windows-quickjs/src/abi.rs`. See `../README.md` for the full contract.
14
-
15
- ## Standalone host
16
- Reuses napi-android's `v8-api.cpp` (napi over V8's C++ API), **ported to V8 14.7**, compiled against
17
- the `v8` crate's bundled headers and linked against its prebuilt rusty_v8 (V8 14.7). The Android
18
- bring-up (`jsr.cpp`) is replaced by `csrc/win_jsr.cpp`. Full WinRT demo passes (`exit 0`):
19
- static-method resolution, typed WinRT calls, a 20-call stress loop, and a `JsonObject` round-trip.
20
-
21
- ```sh
22
- cargo build --release --manifest-path packages/windows-v8/Cargo.toml
23
- target/release/nativescript-windows.exe
24
- ```
25
-
26
- ## Why this was tractable (no ABI nightmare)
27
- The `v8` crate's default config has **pointer compression and the sandbox OFF** (they're opt-in
28
- cargo features we don't enable), so V8 uses plain/uncompressed pointers — there are **no ABI-matching
29
- defines** and none of the corruption risk that config would bring. The port was therefore a bounded
30
- API migration, not an ABI hunt.
31
-
32
- ## What the port involved (V8 13 → 14.7)
33
- - Build recipe: C++20 + `/Zc:__cplusplus` (MSVC reports the real `__cplusplus`), locate the crate's
34
- `v8/include`, stub `<android/log.h>`, `NAPI_EXTERN` → `__declspec(dllexport)`, define `__V8_13__`
35
- (take the shim's modern `SetAccessorProperty` etc. paths). No pointer-compression/sandbox defines.
36
- - API migrations: `String::Write/WriteUtf8/WriteOneByte` → `*V2` (+ `WriteFlags`); mandatory pointer
37
- **type tags** on internal fields / `External` (safe defaults `kEmbedderDataTypeTagDefault` /
38
- `kExternalPointerTypeTagDefault`); `Context::GetIsolate()` removed → `Isolate::GetCurrent()`;
39
- interceptor setters return `v8::Intercepted` (`ReturnValue<void>`).
40
- - Platform init done from **Rust** (`v8::new_default_platform` → `V8::initialize`) because
41
- `NewDefaultPlatform`'s `std::unique_ptr<Platform>` return can't be linked from the MSVC-STL shim
42
- (rusty_v8 uses V8's bundled libc++). See below.
43
-
44
- ## The libc++ boundary — solved, including zero-copy
45
- rusty_v8 is built with V8's bundled libc++, so V8 API methods whose signatures involve
46
- `std::shared_ptr`/`unique_ptr<BackingStore>` can't be linked directly from this MSVC-STL shim.
47
- Handled without any copy:
48
- - Reads use `ArrayBuffer::Data()` (the direct `void*` accessor — no `BackingStore`).
49
- - `napi_create_external_arraybuffer` is **true zero-copy**: it goes through rusty_v8's own
50
- `extern "C"` bindings (`v8__ArrayBuffer__NewBackingStore__with_data`, the unique→shared converter,
51
- `v8__ArrayBuffer__New__with_backing_store`) which pass the backing store as raw pointers / an
52
- opaque 16-byte `shared_ptr`, then bridges the returned pointer back to a `Local` (a `Local` is one
53
- pointer). The buffer aliases the source memory; the napi finalizer runs from the BackingStore
54
- deleter on GC. **Verified:** `interop.arrayBufferFromBuffer(CryptographicBuffer.GenerateRandom(8))`
55
- → `byteLength=8`, `instanceof ArrayBuffer`, no copy. So the IBuffer→ArrayBuffer perf path is intact.
1
+ # @nativescript/windows-v8
2
+
3
+ NativeScript Windows runtime on **V8**
@@ -1,13 +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>
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>
@@ -1,35 +1,35 @@
1
- <?xml version="1.0" encoding="utf-8"?>
2
- <Package
3
- xmlns="http://schemas.microsoft.com/appx/manifest/foundation/windows10"
4
- xmlns:uap="http://schemas.microsoft.com/appx/manifest/uap/windows10"
5
- xmlns:rescap="http://schemas.microsoft.com/appx/manifest/foundation/windows10/restrictedcapabilities"
6
- IgnorableNamespaces="uap rescap">
7
- <Identity Name="__APP_IDENTIFIER__" Publisher="CN=__PROJECT_NAME__" Version="1.0.0.0" />
8
- <Properties>
9
- <DisplayName>__PROJECT_NAME__</DisplayName>
10
- <PublisherDisplayName>__PROJECT_NAME__</PublisherDisplayName>
11
- <Logo>Assets\StoreLogo.png</Logo>
12
- </Properties>
13
- <Dependencies>
14
- <TargetDeviceFamily Name="Windows.Desktop" MinVersion="10.0.17763.0" MaxVersionTested="10.0.26100.0" />
15
- <!-- WinUI3 / Windows App SDK 1.6 framework package (framework-dependent). Required so the
16
- registered app can activate Microsoft.UI.Xaml.* (else REGDB_E_CLASSNOTREG at startup). -->
17
- <PackageDependency Name="Microsoft.WindowsAppRuntime.1.6" MinVersion="6000.0.0.0" Publisher="CN=Microsoft Corporation, O=Microsoft Corporation, L=Redmond, S=Washington, C=US" />
18
- </Dependencies>
19
- <Resources>
20
- <Resource Language="en-US" />
21
- </Resources>
22
- <Applications>
23
- <Application Id="App" Executable="$targetnametoken$.exe" EntryPoint="Windows.FullTrustApplication">
24
- <uap:VisualElements DisplayName="__PROJECT_NAME__" Square150x150Logo="Assets\Square150x150Logo.png" Square44x44Logo="Assets\Square44x44Logo.png" Description="A NativeScript Windows application" BackgroundColor="#65ADF1">
25
- <uap:DefaultTile Wide310x150Logo="Assets\Wide310x150Logo.png" />
26
- <uap:SplashScreen Image="Assets\SplashScreen.png" />
27
- </uap:VisualElements>
28
- </Application>
29
- </Applications>
30
- <Capabilities>
31
- <rescap:Capability Name="runFullTrust" />
32
- <Capability Name="internetClient" />
33
- <Capability Name="privateNetworkClientServer" />
34
- </Capabilities>
35
- </Package>
1
+ <?xml version="1.0" encoding="utf-8"?>
2
+ <Package
3
+ xmlns="http://schemas.microsoft.com/appx/manifest/foundation/windows10"
4
+ xmlns:uap="http://schemas.microsoft.com/appx/manifest/uap/windows10"
5
+ xmlns:rescap="http://schemas.microsoft.com/appx/manifest/foundation/windows10/restrictedcapabilities"
6
+ IgnorableNamespaces="uap rescap">
7
+ <Identity Name="__APP_IDENTIFIER__" Publisher="CN=__PROJECT_NAME__" Version="1.0.0.0" />
8
+ <Properties>
9
+ <DisplayName>__PROJECT_NAME__</DisplayName>
10
+ <PublisherDisplayName>__PROJECT_NAME__</PublisherDisplayName>
11
+ <Logo>Assets\StoreLogo.png</Logo>
12
+ </Properties>
13
+ <Dependencies>
14
+ <TargetDeviceFamily Name="Windows.Desktop" MinVersion="10.0.17763.0" MaxVersionTested="10.0.26100.0" />
15
+ <!-- WinUI3 / Windows App SDK 1.6 framework package (framework-dependent). Required so the
16
+ registered app can activate Microsoft.UI.Xaml.* (else REGDB_E_CLASSNOTREG at startup). -->
17
+ <PackageDependency Name="Microsoft.WindowsAppRuntime.1.6" MinVersion="6000.0.0.0" Publisher="CN=Microsoft Corporation, O=Microsoft Corporation, L=Redmond, S=Washington, C=US" />
18
+ </Dependencies>
19
+ <Resources>
20
+ <Resource Language="en-US" />
21
+ </Resources>
22
+ <Applications>
23
+ <Application Id="App" Executable="$targetnametoken$.exe" EntryPoint="Windows.FullTrustApplication">
24
+ <uap:VisualElements DisplayName="__PROJECT_NAME__" Square150x150Logo="Assets\Square150x150Logo.png" Square44x44Logo="Assets\Square44x44Logo.png" Description="A NativeScript Windows application" BackgroundColor="#65ADF1">
25
+ <uap:DefaultTile Wide310x150Logo="Assets\Wide310x150Logo.png" />
26
+ <uap:SplashScreen Image="Assets\SplashScreen.png" />
27
+ </uap:VisualElements>
28
+ </Application>
29
+ </Applications>
30
+ <Capabilities>
31
+ <rescap:Capability Name="runFullTrust" />
32
+ <Capability Name="internetClient" />
33
+ <Capability Name="privateNetworkClientServer" />
34
+ </Capabilities>
35
+ </Package>
@@ -1,34 +1,34 @@
1
- using System;
2
- using System.Threading;
3
- using Microsoft.UI.Dispatching;
4
- using Microsoft.UI.Xaml;
5
- using WinRT;
6
-
7
- // Windows-only WinUI desktop app. The custom entry point (DISABLE_XAML_GENERATED_MAIN)
8
- // has no platform attribute, so CA1416 treats every Windows API call site as "reachable
9
- // on all platforms." Declaring the assembly's supported platform once tells the analyzer
10
- // the whole assembly only runs on Windows >= 17763, silencing those false positives.
11
- [assembly: System.Runtime.Versioning.SupportedOSPlatform("windows10.0.17763.0")]
12
-
13
- namespace __PROJECT_NAME__
14
- {
15
- public static class Program
16
- {
17
- [System.STAThread]
18
- public static void Main(string[] args)
19
- {
20
- ComWrappersSupport.InitializeComWrappers();
21
-
22
- Application.Start((_callbackParams) =>
23
- {
24
- var dispatcherQueue = DispatcherQueue.GetForCurrentThread();
25
- if (dispatcherQueue != null)
26
- {
27
- SynchronizationContext.SetSynchronizationContext(new DispatcherQueueSynchronizationContext(dispatcherQueue));
28
- }
29
-
30
- new App();
31
- });
32
- }
33
- }
34
- }
1
+ using System;
2
+ using System.Threading;
3
+ using Microsoft.UI.Dispatching;
4
+ using Microsoft.UI.Xaml;
5
+ using WinRT;
6
+
7
+ // Windows-only WinUI desktop app. The custom entry point (DISABLE_XAML_GENERATED_MAIN)
8
+ // has no platform attribute, so CA1416 treats every Windows API call site as "reachable
9
+ // on all platforms." Declaring the assembly's supported platform once tells the analyzer
10
+ // the whole assembly only runs on Windows >= 17763, silencing those false positives.
11
+ [assembly: System.Runtime.Versioning.SupportedOSPlatform("windows10.0.17763.0")]
12
+
13
+ namespace __PROJECT_NAME__
14
+ {
15
+ public static class Program
16
+ {
17
+ [System.STAThread]
18
+ public static void Main(string[] args)
19
+ {
20
+ ComWrappersSupport.InitializeComWrappers();
21
+
22
+ Application.Start((_callbackParams) =>
23
+ {
24
+ var dispatcherQueue = DispatcherQueue.GetForCurrentThread();
25
+ if (dispatcherQueue != null)
26
+ {
27
+ SynchronizationContext.SetSynchronizationContext(new DispatcherQueueSynchronizationContext(dispatcherQueue));
28
+ }
29
+
30
+ new App();
31
+ });
32
+ }
33
+ }
34
+ }
@@ -1,9 +1,9 @@
1
- <!--
2
- Runtime Directives for .NET Native.
3
- See https://go.microsoft.com/fwlink/?LinkID=391919
4
- -->
5
- <Directives xmlns="http://schemas.microsoft.com/netfx/2013/01/metadata">
6
- <Application>
7
- <Assembly Name="*Application*" Dynamic="Required All" />
8
- </Application>
9
- </Directives>
1
+ <!--
2
+ Runtime Directives for .NET Native.
3
+ See https://go.microsoft.com/fwlink/?LinkID=391919
4
+ -->
5
+ <Directives xmlns="http://schemas.microsoft.com/netfx/2013/01/metadata">
6
+ <Application>
7
+ <Assembly Name="*Application*" Dynamic="Required All" />
8
+ </Application>
9
+ </Directives>
@@ -32,6 +32,20 @@ namespace __PROJECT_NAME__
32
32
  [DllImport(NativeScriptLibrary, EntryPoint = nameof(runtime_set_local_folder))]
33
33
  private static extern void runtime_set_local_folder([MarshalAs(UnmanagedType.LPUTF8Str)] string localFolder);
34
34
 
35
+ // Escape hatch for apps packed with `nsbundle_pack --key-hex <hex>` (custom-key
36
+ // app.nsbundle containers). Must be called before runtime_init if used at all: apps
37
+ // packed with the default pepper (no --key-hex) never need to call this. Not wired to a
38
+ // default call site here; an app author supplies their own key material and calls this
39
+ // from Initialize() before runtime_init(...).
40
+ [DllImport(NativeScriptLibrary, EntryPoint = nameof(runtime_set_bundle_key))]
41
+ private static extern int runtime_set_bundle_key([MarshalAs(UnmanagedType.LPUTF8Str)] string keyHex);
42
+
43
+ [DllImport(NativeScriptLibrary, EntryPoint = nameof(runtime_read_protected_file))]
44
+ private static extern IntPtr runtime_read_protected_file([MarshalAs(UnmanagedType.LPUTF8Str)] string virtualPath);
45
+
46
+ [DllImport(NativeScriptLibrary, EntryPoint = nameof(runtime_free_protected_string))]
47
+ private static extern void runtime_free_protected_string(IntPtr ptr);
48
+
35
49
  [DllImport(NativeScriptLibrary, EntryPoint = nameof(runtime_get_last_js_error))]
36
50
  private static extern IntPtr runtime_get_last_js_error();
37
51
 
@@ -55,6 +69,18 @@ namespace __PROJECT_NAME__
55
69
  }
56
70
 
57
71
  #if DEBUG
72
+ // Engine runtimes (@nativescript/windows-<engine>) only: a debug host always allows
73
+ // dev-server modules (Vite HMR). The classic runtime derives this from its devtools build
74
+ // and doesn't export it.
75
+ [DllImport(NativeScriptLibrary, EntryPoint = nameof(runtime_set_debug_build))]
76
+ private static extern void runtime_set_debug_build(int debug);
77
+
78
+ private static void TrySetDebugBuild()
79
+ {
80
+ try { runtime_set_debug_build(1); }
81
+ catch (EntryPointNotFoundException) { }
82
+ }
83
+
58
84
  [DllImport(NativeScriptLibrary, EntryPoint = nameof(ns_set_log_to_console))]
59
85
  private static extern int ns_set_log_to_console(int enabled);
60
86
 
@@ -140,6 +166,7 @@ namespace __PROJECT_NAME__
140
166
  runtime_set_local_folder(Windows.Storage.ApplicationData.Current.LocalFolder.Path);
141
167
  _runtime = runtime_init(AppContext.BaseDirectory);
142
168
  #if DEBUG
169
+ TrySetDebugBuild();
143
170
  var trySet = TrySetLogToConsole(true);
144
171
  System.Diagnostics.Debug.WriteLine($"[NativeScript] TrySetLogToConsole returned={trySet}");
145
172
 
@@ -191,6 +218,27 @@ namespace __PROJECT_NAME__
191
218
  }
192
219
  #endif
193
220
 
221
+ /// Reads a file that may live inside a sealed app.nsbundle instead of on disk: tries the
222
+ /// protected-VFS native call first (cheap no-op when no bundle is loaded), falls back to
223
+ /// the real filesystem. `path` is whatever candidate the caller already built for
224
+ /// `File.Exists`/`File.ReadAllText`: the native side strips it down to the bundle's
225
+ /// virtual (app/App-relative) path itself, so no extra bookkeeping is needed here.
226
+ private static string TryReadVirtual(string path)
227
+ {
228
+ IntPtr ptr = IntPtr.Zero;
229
+ try
230
+ {
231
+ ptr = runtime_read_protected_file(path);
232
+ return ptr == IntPtr.Zero ? null : Marshal.PtrToStringUTF8(ptr);
233
+ }
234
+ catch { return null; }
235
+ finally { if (ptr != IntPtr.Zero) runtime_free_protected_string(ptr); }
236
+ }
237
+
238
+ private static bool VExists(string path) => TryReadVirtual(path) != null || File.Exists(path);
239
+
240
+ private static string VReadAllText(string path) => TryReadVirtual(path) ?? File.ReadAllText(path);
241
+
194
242
  public void RunMainScript()
195
243
  {
196
244
  if (!_initialized)
@@ -205,10 +253,14 @@ namespace __PROJECT_NAME__
205
253
 
206
254
  var dir = Path.GetDirectoryName(Path.GetFullPath(entryPath));
207
255
  var chunks = new List<string>();
208
- foreach (var chunkName in new[] { "runtime.js", "vendor.js" })
256
+ // An ES-module entry (Vite's bundle.mjs) imports its own chunks; webpack's classic
257
+ // runtime.js/vendor.js prelude must not run in front of it (a stale pair can be left
258
+ // in app/ after switching bundlers).
259
+ var isModuleEntry = entryPath.EndsWith(".mjs", StringComparison.OrdinalIgnoreCase);
260
+ foreach (var chunkName in isModuleEntry ? Array.Empty<string>() : new[] { "runtime.js", "vendor.js" })
209
261
  {
210
262
  var chunkPath = Path.Combine(dir, chunkName);
211
- if (File.Exists(chunkPath) &&
263
+ if (VExists(chunkPath) &&
212
264
  !string.Equals(chunkPath, Path.GetFullPath(entryPath), StringComparison.OrdinalIgnoreCase))
213
265
  {
214
266
  chunks.Add(chunkPath);
@@ -218,10 +270,14 @@ namespace __PROJECT_NAME__
218
270
 
219
271
  foreach (var scriptPath in chunks)
220
272
  {
221
- var script = File.ReadAllText(Path.GetFullPath(scriptPath));
273
+ var script = VReadAllText(Path.GetFullPath(scriptPath));
222
274
  try
223
275
  {
224
- runtime_runscript(_runtime, script, Path.GetFileName(scriptPath));
276
+ // Modules resolve their imports (chunks, ~/ paths) relative to their own
277
+ // location, so they need the full path; classic scripts keep the short name
278
+ // their stack traces and code-cache keys have always used.
279
+ var name = isModuleEntry ? Path.GetFullPath(scriptPath) : Path.GetFileName(scriptPath);
280
+ runtime_runscript(_runtime, script, name);
225
281
  }
226
282
  catch (Exception ex)
227
283
  {
@@ -260,7 +316,7 @@ namespace __PROJECT_NAME__
260
316
  foreach (var dir in appDirCandidates)
261
317
  {
262
318
  var candidate = Path.Combine(dir, "package.json");
263
- if (File.Exists(candidate))
319
+ if (VExists(candidate))
264
320
  {
265
321
  packageJsonPath = candidate;
266
322
  resolvedBaseDir = dir;
@@ -269,7 +325,7 @@ namespace __PROJECT_NAME__
269
325
  }
270
326
 
271
327
  // Also accept package.json at the project root (parent of bin/).
272
- if (packageJsonPath == null && File.Exists(Path.Combine(parentDir, "package.json")))
328
+ if (packageJsonPath == null && VExists(Path.Combine(parentDir, "package.json")))
273
329
  {
274
330
  packageJsonPath = Path.Combine(parentDir, "package.json");
275
331
  resolvedBaseDir = parentDir;
@@ -278,7 +334,7 @@ namespace __PROJECT_NAME__
278
334
  string Fallback() =>
279
335
  appDirCandidates
280
336
  .SelectMany(d => new[] { Path.Combine(d, "bundle.js"), Path.Combine(d, "bundle.mjs") })
281
- .FirstOrDefault(File.Exists);
337
+ .FirstOrDefault(VExists);
282
338
 
283
339
  if (packageJsonPath == null)
284
340
  return Fallback();
@@ -304,7 +360,7 @@ namespace __PROJECT_NAME__
304
360
 
305
361
  private static RuntimePackageConfig ParsePackageConfig(string packageJsonPath)
306
362
  {
307
- using var doc = JsonDocument.Parse(File.ReadAllText(packageJsonPath));
363
+ using var doc = JsonDocument.Parse(VReadAllText(packageJsonPath));
308
364
  var config = new RuntimePackageConfig();
309
365
  if (doc.RootElement.TryGetProperty("main", out var main) && main.ValueKind == JsonValueKind.String)
310
366
  config.Main = main.GetString();
@@ -321,11 +377,11 @@ namespace __PROJECT_NAME__
321
377
  foreach (var candidate in new[] { normalized, normalized + ".js", normalized + ".mjs" })
322
378
  {
323
379
  var direct = Path.IsPathRooted(candidate) ? candidate : Path.Combine(baseDir, candidate);
324
- if (File.Exists(direct)) return direct;
380
+ if (VExists(direct)) return direct;
325
381
  var appLower = Path.Combine(baseDir, "app", candidate);
326
- if (File.Exists(appLower)) return appLower;
382
+ if (VExists(appLower)) return appLower;
327
383
  var appUpper = Path.Combine(baseDir, "App", candidate);
328
- if (File.Exists(appUpper)) return appUpper;
384
+ if (VExists(appUpper)) return appUpper;
329
385
  }
330
386
  return null;
331
387
  }
@@ -87,7 +87,12 @@
87
87
  <Content Include="Properties\Default.rd.xml" />
88
88
 
89
89
  <!-- App JS/resource files — copied to the output directory on every build -->
90
- <Content Include="app\**\*">
90
+ <!-- Sealed source-protected bundle when present, takes
91
+ priority over the plaintext app\ tree; produced by the nsbundle_pack packer tool. -->
92
+ <Content Include="app.nsbundle" Condition="Exists('app.nsbundle')">
93
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
94
+ </Content>
95
+ <Content Include="app\**\*" Condition="!Exists('app.nsbundle')">
91
96
  <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
92
97
  </Content>
93
98