bun-app-window 0.1.0 → 0.2.2

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 (91) hide show
  1. package/CHANGELOG.md +202 -0
  2. package/README.md +35 -3
  3. package/README.zh-CN.md +35 -3
  4. package/README.zh-TW.md +35 -3
  5. package/dist/libshell.dll +0 -0
  6. package/package.json +8 -10
  7. package/src/shell/native-asset.d.ts +18 -0
  8. package/src/shell/resolve.ts +1 -1
  9. package/native/build.ps1 +0 -67
  10. package/native/fetch.ps1 +0 -66
  11. package/native/shell.cc +0 -2134
  12. package/vendor/webview/compatibility/CMakeLists.txt +0 -3
  13. package/vendor/webview/compatibility/mingw/CMakeLists.txt +0 -14
  14. package/vendor/webview/compatibility/mingw/include/EventToken.h +0 -25
  15. package/vendor/webview/core/CMakeLists.txt +0 -94
  16. package/vendor/webview/core/include/webview/api.h +0 -251
  17. package/vendor/webview/core/include/webview/backends.hh +0 -40
  18. package/vendor/webview/core/include/webview/c_api_impl.hh +0 -258
  19. package/vendor/webview/core/include/webview/detail/backends/cocoa_webkit.hh +0 -626
  20. package/vendor/webview/core/include/webview/detail/backends/gtk_webkitgtk.hh +0 -355
  21. package/vendor/webview/core/include/webview/detail/backends/win32_edge.hh +0 -909
  22. package/vendor/webview/core/include/webview/detail/basic_result.hh +0 -119
  23. package/vendor/webview/core/include/webview/detail/engine_base.hh +0 -382
  24. package/vendor/webview/core/include/webview/detail/exceptions.hh +0 -42
  25. package/vendor/webview/core/include/webview/detail/json.hh +0 -337
  26. package/vendor/webview/core/include/webview/detail/native_library.hh +0 -170
  27. package/vendor/webview/core/include/webview/detail/optional.hh +0 -118
  28. package/vendor/webview/core/include/webview/detail/platform/darwin/cocoa/NSApplication.hh +0 -107
  29. package/vendor/webview/core/include/webview/detail/platform/darwin/cocoa/NSBundle.hh +0 -56
  30. package/vendor/webview/core/include/webview/detail/platform/darwin/cocoa/NSEvent.hh +0 -71
  31. package/vendor/webview/core/include/webview/detail/platform/darwin/cocoa/NSInvocation.hh +0 -66
  32. package/vendor/webview/core/include/webview/detail/platform/darwin/cocoa/NSMethodSignature.hh +0 -52
  33. package/vendor/webview/core/include/webview/detail/platform/darwin/cocoa/NSNotification.hh +0 -51
  34. package/vendor/webview/core/include/webview/detail/platform/darwin/cocoa/NSNumber.hh +0 -53
  35. package/vendor/webview/core/include/webview/detail/platform/darwin/cocoa/NSObject.hh +0 -51
  36. package/vendor/webview/core/include/webview/detail/platform/darwin/cocoa/NSOpenPanel.hh +0 -71
  37. package/vendor/webview/core/include/webview/detail/platform/darwin/cocoa/NSPoint.hh +0 -53
  38. package/vendor/webview/core/include/webview/detail/platform/darwin/cocoa/NSRect.hh +0 -53
  39. package/vendor/webview/core/include/webview/detail/platform/darwin/cocoa/NSSavePanel.hh +0 -52
  40. package/vendor/webview/core/include/webview/detail/platform/darwin/cocoa/NSSize.hh +0 -53
  41. package/vendor/webview/core/include/webview/detail/platform/darwin/cocoa/NSString.hh +0 -87
  42. package/vendor/webview/core/include/webview/detail/platform/darwin/cocoa/NSURL.hh +0 -63
  43. package/vendor/webview/core/include/webview/detail/platform/darwin/cocoa/NSURLRequest.hh +0 -52
  44. package/vendor/webview/core/include/webview/detail/platform/darwin/cocoa/NSValue.hh +0 -56
  45. package/vendor/webview/core/include/webview/detail/platform/darwin/cocoa/NSView.hh +0 -91
  46. package/vendor/webview/core/include/webview/detail/platform/darwin/cocoa/NSWindow.hh +0 -137
  47. package/vendor/webview/core/include/webview/detail/platform/darwin/cocoa/cocoa.hh +0 -59
  48. package/vendor/webview/core/include/webview/detail/platform/darwin/cocoa/types.hh +0 -49
  49. package/vendor/webview/core/include/webview/detail/platform/darwin/objc/Class.hh +0 -55
  50. package/vendor/webview/core/include/webview/detail/platform/darwin/objc/autoreleasepool.hh +0 -70
  51. package/vendor/webview/core/include/webview/detail/platform/darwin/objc/invoke.hh +0 -75
  52. package/vendor/webview/core/include/webview/detail/platform/darwin/objc/memory.hh +0 -57
  53. package/vendor/webview/core/include/webview/detail/platform/darwin/objc/objc.hh +0 -47
  54. package/vendor/webview/core/include/webview/detail/platform/darwin/webkit/WKOpenPanelParameters.hh +0 -57
  55. package/vendor/webview/core/include/webview/detail/platform/darwin/webkit/WKScriptMessage.hh +0 -51
  56. package/vendor/webview/core/include/webview/detail/platform/darwin/webkit/WKUserContentController.hh +0 -61
  57. package/vendor/webview/core/include/webview/detail/platform/darwin/webkit/WKUserScript.hh +0 -71
  58. package/vendor/webview/core/include/webview/detail/platform/darwin/webkit/WKWebView.hh +0 -108
  59. package/vendor/webview/core/include/webview/detail/platform/darwin/webkit/WKWebViewConfiguration.hh +0 -60
  60. package/vendor/webview/core/include/webview/detail/platform/darwin/webkit/webkit.hh +0 -46
  61. package/vendor/webview/core/include/webview/detail/platform/linux/gtk/compat.hh +0 -137
  62. package/vendor/webview/core/include/webview/detail/platform/linux/webkitgtk/compat.hh +0 -142
  63. package/vendor/webview/core/include/webview/detail/platform/linux/webkitgtk/dmabuf.hh +0 -167
  64. package/vendor/webview/core/include/webview/detail/platform/windows/com_init_wrapper.hh +0 -126
  65. package/vendor/webview/core/include/webview/detail/platform/windows/dpi.hh +0 -161
  66. package/vendor/webview/core/include/webview/detail/platform/windows/dwmapi.hh +0 -67
  67. package/vendor/webview/core/include/webview/detail/platform/windows/iid.hh +0 -78
  68. package/vendor/webview/core/include/webview/detail/platform/windows/ntdll.hh +0 -58
  69. package/vendor/webview/core/include/webview/detail/platform/windows/reg_key.hh +0 -135
  70. package/vendor/webview/core/include/webview/detail/platform/windows/shcore.hh +0 -59
  71. package/vendor/webview/core/include/webview/detail/platform/windows/theme.hh +0 -76
  72. package/vendor/webview/core/include/webview/detail/platform/windows/user32.hh +0 -90
  73. package/vendor/webview/core/include/webview/detail/platform/windows/version.hh +0 -151
  74. package/vendor/webview/core/include/webview/detail/platform/windows/webview2/loader.hh +0 -391
  75. package/vendor/webview/core/include/webview/detail/user_script.hh +0 -76
  76. package/vendor/webview/core/include/webview/detail/utility/string.hh +0 -99
  77. package/vendor/webview/core/include/webview/errors.h +0 -67
  78. package/vendor/webview/core/include/webview/errors.hh +0 -85
  79. package/vendor/webview/core/include/webview/json_deprecated.hh +0 -61
  80. package/vendor/webview/core/include/webview/macros.h +0 -123
  81. package/vendor/webview/core/include/webview/types.h +0 -81
  82. package/vendor/webview/core/include/webview/types.hh +0 -48
  83. package/vendor/webview/core/include/webview/version.h +0 -67
  84. package/vendor/webview/core/include/webview/webview.h +0 -32
  85. package/vendor/webview/core/include/webview.h +0 -15
  86. package/vendor/webview/core/src/webview.cc +0 -1
  87. package/vendor/webview/core/tests/CMakeLists.txt +0 -18
  88. package/vendor/webview/core/tests/src/functional_tests.cc +0 -316
  89. package/vendor/webview/core/tests/src/unit_tests.cc +0 -188
  90. package/vendor/webview2/build/native/include/WebView2.h +0 -23568
  91. package/vendor/webview2/build/native/include/WebView2EnvironmentOptions.h +0 -144
package/CHANGELOG.md ADDED
@@ -0,0 +1,202 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project are documented in this file.
4
+
5
+ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
6
+
7
+ ## [Unreleased]
8
+
9
+ ## [0.2.2] - 2026-10-21
10
+
11
+ ### Fixed
12
+
13
+ - **The PNG encoder produced IDAT chunks that were not zlib.** `Bun.deflateSync` is byte-identical to `zlib.deflateRawSync`, so the output was **raw deflate**, while the specification requires zlib framing (`0x78` header plus an adler32 trailer). What made this expensive to find is that **three decoders gave two different answers**: `System.Drawing` opened the files, and our own decoder plus `Bun.inflateSync` decompressed them — because they share the same raw-deflate assumption as the encoder. Only the strict decoder rejected them: `Bun.Image` (libspng) returned `ERR_IMAGE_DECODE_FAILED`, and so did the upstream validator behind `read_file`. Worse, `Bun.Image.metadata()` still succeeded, because it reads only the header and never touches the IDAT payload — so the failure looked like "correct size, correct format, just won't open". `native/png.ts` now imports `deflateSync` from `node:zlib` (Bun's built-in node compat module, not an external node runtime), and `tests/png.test.ts` asserts both that the first IDAT byte is `0x78` and that the payload decompresses under **strict** zlib inflate — never `Bun.inflateSync`, which would make the assertion vacuous. Recorded as `docs/pitfalls.md` 3.11.
14
+ - **`Bun.Image.resize(w)` limits the width, not the longest edge.** The documented semantics are `resize(800)` → "width 800, keep aspect ratio". `native/model-image.ts` was calling `resize(maxEdge, undefined, { withoutEnlargement: true })`, which is correct for landscape images and silently wrong for portrait ones: `300×600` with `maxEdge: 100` produced `100×200`, four times the intended pixel count, with the token estimate and byte size inflated to match. The fix passes both dimensions with `fit: "inside"`. Caught by asserting on a **portrait** image; a landscape-only test would have stayed green. Recorded as `docs/pitfalls.md` 3.15.
15
+ - Six references in `AGENTS.md` and `docs/pitfalls.md` pointed at files that no longer exist: `tools/capture-window.ps1` (three occurrences), `probe-memory.ps1`, `src/safe-port.ts` (the module is actually `src/shell/port.ts`), and `src/gui/page-script.test.ts` (the whole `src/gui/` directory was removed when the UI became a static `src/ui/index.html`). A migration leaves legitimate historical references behind, so the fix is not to delete them but to make the decision explicit — see the guard under *Added*.
16
+
17
+ ### Added
18
+
19
+ - `native/resample.ts` — area-average (box) downscaling over BGRA, and the reason `--max-edge` no longer goes through `Bun.Image` at all. Measured on a real 3098×1850 window scaled to 1300×776, median of five runs:
20
+
21
+ | route | ms | bytes |
22
+ | --- | --- | --- |
23
+ | **our `downscaleBgra` + our encoder** | **45.9** | **102,036** |
24
+ | `Bun.Image` resize + png | 95.9 | 222,621 |
25
+
26
+ **2.1× faster and 2.2× smaller — both directions, not a trade-off.** It is faster because `Bun.Image` takes a PNG as input, so it must encode and then decode again before it can resample; we hold the raw `CreateDIBSection` pointer and never pay that. It is smaller because its default filter is `lanczos3` — a **sharpening** filter that rings around text edges, and the resulting high-frequency detail is exactly what DEFLATE compresses worst. Area averaging is inherently smooth, so the correct algorithm saves both costs at once. A prefix-sum variant was tried and came out **slower** (51.3 vs 39.1 ms for the resample alone): the extra full-width pass and the larger intermediate buffer cost more in cache locality than the inner-loop subtraction saves. Recorded as `docs/pitfalls.md` 3.16.
27
+ - `native/resample.ts` weights the average by alpha (`Σ(b·a)/Σa`) whenever any pixel is transparent, because a plain average mixes the RGB of fully transparent pixels — usually `0` — into the result and grows a black fringe around rounded corners and shadows. `scripts/make-icon.ts` produces exactly such rounded corners. Fully opaque input takes a fast path where the arithmetic is exact. `tests/resample.test.ts` pins this with a 2×2 → 1×1 construction (one opaque black pixel, three opaque whites): the right answer is white at alpha 191, the wrong one is grey 191.
28
+ - `cropBgra` in `native/gdi.ts` — cropping is a per-row `memcpy` over the raw pointer, so it costs no FFI and no resample. It is deliberately applied **before** encoding: once the pixels are handed to `Bun.Image` they are an opaque object and can no longer be cut. `--crop x,y,w,h` exposes it, and the rectangle is clamped to the image rather than producing a malformed one.
29
+ - `native/model-image.ts` — narrowed to the three things the in-repo code cannot do: **enlarging** (box averaging cannot interpolate), **palette** quantisation, and **jpeg / webp** encoding. Everything else on the PNG path now stays inside `native/`.
30
+ - A **two-way** guard on the `AGENTS.md` directory tree. The existing assertion ("everything declared must exist on disk") cannot catch the opposite failure, and `native/model-image.ts` sat outside the tree for a whole round while the gate was green — an unlisted file is invisible to an agent reading the tree as its map. Writing the reverse assertion also exposed a blind spot in the forward one: the tree is aligned by longest name, so `probe-child-windows.ts` is followed by a **single** space, and the parser's `\s{2,}` separator meant that line had never been parsed — and therefore never checked — since the guard was written.
31
+ - A guard for script paths referenced in the docs, backed by an explicit *deleted* allow-list (same orientation as `verify-pack.ts`'s `ALLOW`: adding an entry must be a deliberate decision). A second assertion fails if a listed-deleted file reappears, so the list cannot rot unnoticed.
32
+
33
+ ### Changed
34
+
35
+ - `tools/capture-window.ts` defaults are now set by measurement rather than intuition: `--format png`, `--level 6`, `--max-edge 0`, plus a new `--level <1-9>` and `--crop <x,y,w,h>`. Level 6 is the knee — 31% smaller than level 1 for about 3 ms, and 4× faster than level 9 for 13% more bytes. End-to-end on a real window (including Bun startup and `PrintWindow`):
36
+
37
+ | command | bytes | end-to-end ms |
38
+ | --- | --- | --- |
39
+ | `--level 1` | 704,353 | 148 |
40
+ | default (level 6) | 439,347 | 170 |
41
+ | `--level 9` | 411,366 | 330 |
42
+ | `--crop 700,300,800,600` | **50,836** | 170 |
43
+
44
+ Level 9 buys 6% for double the time; cropping buys **88%** and also drops the token estimate from 1024 to 291. Recorded as `docs/pitfalls.md` 3.14.
45
+ - The choice between the in-repo encoder and `Bun.Image` is now two conditions rather than one: **downscale *and* png** together keep the whole path inside `native/`; anything else (enlarging, palette, jpeg, webp) still goes to `Bun.Image`. Previously a crop was enough to trigger the `Bun.Image` detour, which re-encoded through it for no benefit — 50.0 ms / 84 KiB against 6.0 ms / 62 KiB for the same crop. **Cropping is a pixel operation and must not change the encoding route.**
46
+
47
+ ### Note
48
+
49
+ - The published tarball is unchanged again: `native/` and `tools/` are repository-only. What a repository user will notice is that `bun run demo` screenshots are now roughly half the size at the same readable resolution.
50
+ - A screenshot is now cheap enough to be the primary feedback channel for agent work: a 3098×1850 window at full size is 212 KB, and the same view scaled to the vision model's 1300 normalisation ceiling is 102 KB — with text still legible, as the byte count alone cannot prove. That is the prerequisite for driving a GUI from an agent loop rather than for looking at a window once.
51
+
52
+ ## [0.2.1] - 2026-10-21
53
+
54
+ ### Changed
55
+
56
+ - **The PowerShell tool layer is gone: 14 scripts down to 2.** Everything that could move did. The remaining `scripts/test-sign.ps1` and `scripts/verify-sign-crypto.ps1` need CNG key generation and `SignedCms`, where a hand-written `bun:ffi` structure that is off by one field is a process-level segfault rather than an exception — a cost that buys nothing for two scripts run once per release. The decision and its evidence are in ADR 0014.
57
+ - `native/build-pack.ps1` → `native/build-pack.ts`, run as `bun run build:exe`. Three external tools disappeared with it: `llvm-objdump -p` (Subsystem), `rg -a -o` (embedded asset names) and `Get-AuthenticodeSignature` (signature presence) are now pure byte reads in `native/pe.ts`. Removing them is not about dependency count — the parsing is a pure function, so all three became `bun test` assertions (`tests/pe.test.ts`) instead of output a human has to eyeball.
58
+ - `scripts/make-icon.ps1` → `scripts/make-icon.ts`, and it no longer needs `System.Drawing`. The rounded rectangle is an SDF, the gradient is a projection, and anti-aliasing is 4×4 supersampling — `renderIcon(size)` is a pure function returning pixels, so `tests/make-icon.test.ts` asserts the corner alpha is 0, the centre alpha is 255, and that 16px omits the stroke and the three dots (below that size they smear into grey). The output ICO is 15,605 bytes against the previous 17,429; `System.Drawing.Icon` reads it back at 32×32 and the sampled centre pixel is the brand colour.
59
+ - The other five moved too: `scripts/trust-cert.ts`, `scripts/extract-icon.ts`, `scripts/probe-memory.ts`, `scripts/test-av.ts`, `tools/capture-window.ts`, `tools/probe-child-windows.ts`, `tools/probe-gui-boundary.ts`. `scripts/test-av-runtime.ps1` was **deleted rather than migrated** — its premise was a Session 0 agent observing a Session 1 GUI process through `schtasks /IT`, which stopped being true when OTT moved to Session 1 (`ADR 0005`). Its two remaining abilities moved into `probe-memory.ts` (module enumeration, WebView2 child processes) and `test-av.ts` (Defender scan plus event log).
60
+
61
+ ### Added
62
+
63
+ - `native/png.ts` — a PNG writer, and the single reason three of those scripts could move. `System.Drawing.Bitmap.Save(ImageFormat.Png)` was the only genuine blocker; what it actually did was four chunks and a zlib call, and `Bun.deflateSync` already supplies the zlib. `tests/png.test.ts` round-trips every image it encodes back through a minimal decoder and compares pixels byte for byte, because asserting the signature `89 50 4E 47` would stay green even if the channel order were wrong.
64
+ - `native/win32.ts` and `native/gdi.ts` — the Win32 surface the diagnostic scripts need, grouped by the DLL each symbol actually lives in rather than by meaning. `EnumWindows` and `EnumChildWindows` are replaced by `GetTopWindow` + `GetWindow` iteration, so no C→JS callback is involved at all.
65
+ - `native/pe.ts` — read-only PE parsing: Subsystem, certificate table, and embedded asset names.
66
+ - 324 new test cases across `tests/png.test.ts`, `tests/make-icon.test.ts`, `tests/win32.test.ts`, `tests/pe.test.ts`, `tests/build-pack.test.ts`, `tests/tooling.test.ts`. Total is now 456 across 25 files.
67
+
68
+ ### Fixed
69
+
70
+ - **`u64_fast` returns `number` or `bigint` — and which one is not stable.** The same declaration returned `number 1694568820` from one `CreateCompatibleDC` call and `bigint 18446744073676065759n` from the next. Code that checked `typeof h === "number"` to detect a null handle therefore treated a valid DC as `0`, and `extract-icon` silently produced a fully transparent PNG. Every handle return now goes through `toHandle()`; the pitfall is recorded as `docs/pitfalls.md` 3.7.
71
+ - **`DrawIconEx` has nine parameters, and the first version declared eight.** The missing `hbrFlickerFreeDraw` shifted `diFlags` to zero, so the call returned `true` and drew nothing. `bun:ffi` moves arguments as declared and does not check arity — a wrong parameter count is as silent as a wrong type, not only as fatal as a wrong pointer. Recorded as `docs/pitfalls.md` 3.9.
72
+ - **Two symbols were declared in the wrong DLL.** `DestroyIcon` and `DrawIconEx` both live in `user32.dll`, not `shell32.dll` or `gdi32.dll`, and `dlopen` refuses the whole descriptor when any symbol is missing — so one wrong DLL disables every symbol in that block. Recorded as `docs/pitfalls.md` 3.10.
73
+ - `native/build-pack.ts` computed the repository root with `resolve(dirname(import.meta.dir), "..")`, one level too far, so `bun run build:exe` resolved the entry as `_Repositories/src/host.ts`. The gate was fully green at the time: every `main()` test passed `root` as an argument, and no test asked what the entry point computes for itself. The fix exports `REPO_ROOT` and pins it with three assertions, including the reverse one — the parent directory must **not** contain a `package.json`.
74
+ - The pitfalls guard compared section numbers as floats: `Number("3.10")` is `3.1`, which sorts before `3.9`. Nothing noticed until this release introduced the first two-digit minor number. Now encoded as `major * 1000 + minor`.
75
+ - `scripts/test-av.ts` treated a missing `wevtutil` as a thrown `ENOENT` from `Bun.spawn` rather than as a reportable state; the spawn wrapper now returns a negative exit code instead of propagating.
76
+
77
+ ### Note
78
+
79
+ - Nothing in the published tarball changes: `files` still ships `src/shell`, `src/ui/titlebar.ts`, `dist/libshell.dll` and the docs. This release is entirely about the tooling that builds and verifies it. `bun run build:exe` is the one behaviour a repository user will notice — it no longer needs pwsh.
80
+
81
+ ## [0.2.0] - 2026-10-20
82
+
83
+ ### Changed
84
+
85
+ - The gate entry point moved from `scripts/gate.ps1` to `scripts/gate.ts`, run as `bun run scripts/gate.ts` (`--clean`, `--skip-pack`, `--keep-clone`). PowerShell-style `-Clean` / `-SkipPack` are accepted too. The reason is structural rather than aesthetic: `$LASTEXITCODE` is a global that only reflects the last native command, and every PowerShell function leaks its child-process output into the return-value pipeline — the exact mechanism behind the "reported GATE PASSED while the type check had failed" bug recorded in `docs/pitfalls.md` 5.3. `Bun.spawn`'s `exited` is bound per call, and `gate.ts` is itself covered by `bun check` (which caught a dead import on the first run). See ADR 0012.
86
+ - Four more PowerShell scripts moved to TypeScript: `install-hooks`, `native/build`, `run`, and `bump-version`. All now accept both `--kebab-case` and `-PascalCase` flags, are covered by `bun check`, and are behind the entry-point guard. Only the 14 scripts that genuinely need Win32 APIs (`Add-Type` P/Invoke, `System.Drawing`, the certificate store, `Get-CimInstance`) remain as `.ps1`. Total: 18 files / 1361 lines down to 14 files, with the four ordinary ones gone.
87
+ - `bun run build:native` now runs `bun run native/build.ts`. The clang++ argument list became a pure function (`clangArgs`) asserted by `tests/build.test.ts`: a missing `-l` or a misplaced `-I` used to surface only as a C++ link error pointing at the compiler rather than at the script.
88
+ - `scripts/install-hooks.ts` writes the hook as raw UTF-8 bytes with no EOL translation — the failure mode the `.ps1` version had (`Set-Content` rewrites to CRLF, which makes `sh` unable to execute the hook) is structurally impossible here, and `tests/install-hooks.test.ts` asserts the template contains no CRLF.
89
+ - `run.ts` drops the hardcoded `C:\Users\root\scoop\shims\bun.exe` and uses `process.execPath` — the running interpreter is by definition the bun to use.
90
+ - `scripts/bump-version.ts` replaces only the version literal and asserts the file's original EOL survived; the `.ps1` version used `-replace` (regex engine) plus `Set-Content` (CRLF rewrite), so a one-token edit could rewrite every byte of the file.
91
+ - Verified equivalent output: `build.ts` produces a byte-identical-size `libshell.dll` (940,032 bytes, 33 exported symbols) and `.build/` and `dist/` copies hash equal.
92
+ - A shebang was verified rather than assumed: `#!/usr/bin/env bun` does nothing in PowerShell (Windows does not parse shebangs — `./gate.ts` is opened as a document), but it **does** work under the `sh` that Git ships, which is the environment `pre-push` runs in. The hook therefore calls `bun run scripts/gate.ts` explicitly, which is correct on every platform.
93
+ - The published tarball now carries only the shipping surface: 19 files (1.1 MB unpacked) instead of 101 (2.40 MB). The native C++ layer (`native/`) and the vendored upstream headers (`vendor/`) are no longer packed — they are only needed to rebuild `libshell.dll`, which requires cloning the repository. `bun run build:native` and `build:exe` are therefore repository-only entry points and do nothing useful inside `node_modules`.
94
+
95
+ ### Fixed
96
+
97
+ - `bun-app-window/native` had no type declaration, so the documented `with { type: "file" }` import failed `bun check` with `TS2307` in any consumer. The subpath now uses a conditional export (`types` → `src/shell/native-asset.d.ts`, `default` → the DLL), leaving the runtime resolution unchanged: `import.meta.resolve()` still yields the real DLL path.
98
+ - Type-checking a project that depends on this package requires `@types/bun`; the requirement is now stated in the requirements table of all three READMEs, together with the exact error you get without it.
99
+ - `native/fetch.ps1` never worked on a fresh clone. Its first step gated on whether `vendor/webview/.git` existed, but the vendored copy is committed as plain files with no `.git` — so the condition was always true, `git clone` was attempted into a non-empty directory, `git` returned 128, and the script threw before ever reaching the SDK extraction step. The criterion is now the same three files `native/build.ps1` validates (`webview.h`, `EventToken.h`, `WebView2.h`), so the two scripts can no longer disagree about what "ready" means. **The script is now `native/fetch.ts`** (see below).
100
+ - Building the native layer on a fresh clone is now fully offline by default: `vendor/webview` ships in the repository and `vendor/webview2` is extracted from the also-committed `Microsoft.Web.WebView2.1.0.1150.38.nupkg`. Network access became opt-in via `-Webview` (re-clone upstream, overwriting 145 tracked files) and `-Sdk` (re-download and re-extract); `-Force` keeps the old "do everything" meaning. `-Webview` also drops the staged `.git` so the result stays identical in shape to the committed copy, instead of nesting a second repository inside this one.
101
+ - `fetch.ps1` now reports the three inputs with sizes plus the upstream `WEBVIEW_VERSION_*` values, and exits 2 when a required tool (`git.exe` / `aria2c.exe` / `7z.exe`) is missing rather than failing halfway through.
102
+ - `native/fetch.ps1` was migrated to `native/fetch.ts`, run as `bun run native/fetch.ts` (`--root`, `--webview`, `--sdk`, `--force`; PowerShell-style `-Webview` etc. are accepted too). Behaviour is unchanged, but the decision logic — four directory states for `vendor/webview`, four for the SDK — is now two pure functions (`decideWebview`, `decideSdk`) covered by 14 cases in `tests/fetch.test.ts`. The state that had never been verified before, "directory exists but inputs are incomplete", is now pinned: it must refuse with exit 1 and leave the tree untouched. A static assertion also reads `native/build.ps1` and requires every entry of `INPUTS` to appear in its pre-flight checks, so the two scripts cannot drift apart again. That assertion failed on its first run for a real reason — `build.ps1` builds those paths by joining `$wv` with a relative tail, so the full string never appears contiguously.
103
+ - `scripts/verify-pack.ts` leaked its temp workspace on every run. Two independent causes: `process.exit()` was called inside the `try` block, which terminates the process immediately and skips `finally`; and the success path's cleanup failure was swallowed by `.catch(() => {})`. All paths now funnel through `finally`, the exit code is set via `process.exitCode` instead of an immediate exit, and a failed removal prints a warning rather than disappearing. Verified on both paths — a normal run and an injected failure (a disallowed file added to `files`) each leave `%TEMP%` with zero `baw-pack-*` directories.
104
+ - The gate's third stage failed inside the `pre-push` hook with `tar (child): Cannot connect to C: resolve failed`, while passing in pwsh. `Bun.which("tar")` resolves to different binaries in the two environments: Git ships **GNU tar 1.35**, which reads `C:/...` as a `host:path` remote; pwsh resolves to `%SystemRoot%\System32\tar.exe` (bsdtar), which understands drive letters. Fixed with two independent layers — prefer System32's bsdtar on win32, and pass a bare filename with `cwd` instead of an absolute path, so a relative name never contains a colon.
105
+ - Entry-point detection could silently disable a script. `import.meta.main` becomes `false` under `bun run <path>` when the caller's cwd differs from the real directory only in letter case (`_repositories` vs `_Repositories`), which is exactly what Git's `sh` produces. The guard then skipped the whole entry block and the script exited `0` having done nothing — a gate that runs nothing and reports success. Detection is now `import.meta.main || argv[1]` matching `import.meta.path` after normalisation (`scripts/lib/entrypoint.ts`).
106
+ - The first version of that helper was itself the bug: it read `import.meta.main` / `import.meta.path` inside the imported module, where `main` is always `false` and `path` points at the helper. It returned `false` unconditionally and turned off all three entry points — silently. It is now a pure function taking both values from the caller, pinned by `tests/entrypoint.test.ts` (11 cases, including a regression test that the guard can never be constantly false).
107
+
108
+ ### Added
109
+
110
+ - `tests/gate.test.ts` and `tests/verify-pack.test.ts` close the gap that mattered most: the gate had no tests of its own. `gate.ts` is where every other guarantee enters, so a quiet regression there — a stage that always reports success, a short-circuit that stops short-circuiting — disables everything downstream while still printing `GATE PASSED`. The decision logic is now split into exported pure functions (`normalizeArg`, `parseGateArgs`, `checkedFileCount`, `parseTestCounts`, `parsePackSummary`, `parsePs1Summary`, `planStages`, `formatStage`, `summarize`) with execution injected through `GateDeps`, so the short-circuit itself is asserted by handing `runGate` a fake runner rather than by spawning four child processes. `checkPayload` — the deny-by-default allow-list that decides what may be published — is covered the same way. 49 new cases; both entry points are behind an entry-point guard, so importing them in tests has no side effects.
111
+ - `tests/entrypoint.test.ts` (11 cases) pins the entry-point guard, including a regression test that it can never be constantly false — the first implementation was, and it silently disabled all three scripts.
112
+ - The gate gained a second stage: syntax-checks every remaining `.ps1` with `[System.Management.Automation.Language.Parser]::ParseFile`. The 18 PowerShell scripts that stay have neither type checking nor tests, so this is the cheapest available guard — it catches unbalanced braces and misspelled keywords, not semantic errors. It is explicitly a migration-period fence: when those scripts are migrated or formally kept, the stage should be deleted rather than left as permanent weight. Missing `pwsh` counts as a skip, not a failure.
113
+ - `scripts/verify-pack.ts` and `scripts/consumer-probe.ts`: consumer acceptance as an executable gate. It packs the tarball, asserts every file against a deny-by-default allow-list, installs it into a scratch project under `%TEMP%`, type-checks that project, and runs 40 runtime assertions against the installed package — all eight subpaths resolving, the DLL actually being `dlopen`-ed out of `node_modules`, `libInfo.source === "package"`, IPC request/reply and event push, and a real `serveOnSafePort` bind. Offline, about 0.4 s.
114
+ - The gate gains a third stage that runs the above, on by default. `--skip-pack` exists for bootstrapping changes to the gate itself.
115
+ - `docs/pitfalls.md` 5.6 records that PowerShell's `Set-Content` rewrites line endings to CRLF regardless of the original, so a script that edits and then "restores" a file leaves a byte-level diff with an empty content diff. Encountered while smoke-testing the new `.ps1` syntax stage.
116
+ - `bun-app-window/native` is documented in all three READMEs (`import.meta.resolve` gives the DLL path; use `bun-app-window/embedded` for `bun build --compile` instead).
117
+ - ADR 0010 records the publish-surface decision, the conditional-export fix, and why `sideEffects: false` must not be added (it would let a bundler shake away the DLL embedding import in `embedded.ts`).
118
+ - ADR 0011 records the `fetch` criteria change: why the criterion must equal the files `build.ps1` actually validates, and why the network-touching steps became opt-in switches. The operational version lives in `docs/pitfalls.md` 5.5.
119
+ - ADR 0012 records the move of the gate to TypeScript: the three PowerShell mechanisms it removes (`$LASTEXITCODE` as a global, function output leaking into the return-value pipeline, no static checker), the measured behaviour of shebangs on Windows, and the migration boundary — scripts that are pure process orchestration migrate, those that need Win32 APIs (`Add-Type` P/Invoke, `System.Drawing`, the certificate store, CIM) stay where they are. The migration is now complete on the orchestration side: only those 14 Win32 scripts remain as `.ps1`.
120
+
121
+ ## [0.1.0] - 2026-10-20
122
+
123
+ First public release.
124
+
125
+ ### Added
126
+
127
+ #### Window shell
128
+
129
+ - `createShell()` builds a shell from `ShellOptions` without starting it; `openShell()` starts it and throws on failure. `Shell.start()` returns `{ rc, error }` instead of throwing, and the native layer creates the UI thread itself, so the caller's main thread is never blocked.
130
+ - Window flags, composable with bitwise OR: `FLAG_FRAMELESS` (1), `FLAG_TOPMOST` (2), `FLAG_CENTER` (4), `FLAG_RESIZABLE` (8), `FLAG_DEFER_SHOW` (16), `FLAG_ACCEPT_DROP` (32), `FLAG_START_MAXIMIZED` (64). `FLAG_CENTER` and `FLAG_START_MAXIMIZED` work in both decorated and frameless modes.
131
+ - Window controls: `setTitle`, `minimize`, `toggleMaximize`, `close`, `drag`, `beginResize(edge)` with `RESIZE_EDGE` matching the Win32 `HT*` constants, `setMinSize` (logical pixels, converted per current DPI), `show`, `setAppId`.
132
+ - `state()` reports `maximized` / `minimized` / `focused` / `dark`; `isAlive()` distinguishes "destroyed" from "never started"; `lastError()` copies the shared error slot instead of returning its pointer, so concurrent writers cannot corrupt the read.
133
+ - `stop()` is idempotent. Termination is dispatched to the UI thread, because `PostQuitMessage` only affects the calling thread's queue.
134
+
135
+ #### Desktop behaviour
136
+
137
+ - First frame without a white flash, via three cooperating mechanisms: subclassing installed before sizing, the initial size call kept (it sets the internal `m_is_size_set` flag), and the WebView2 default background replaced before navigation. Omitting any one of them reintroduces the flash.
138
+ - Frameless windows keep DPI-correct hit testing, edge resize, and snap layout support.
139
+ - Window state push with change detection plus a 16 ms trailing throttle, so drag-resizing does not flood the renderer over the cross-process bridge. Both window procedures keep their `WM_TIMER` branch.
140
+ - Zoom from `Ctrl` `+` / `-` / `0` and `Ctrl`+wheel, clamped to 0.25–3.0, with `setZoom` / `getZoom` for programmatic control. Scale factors cross the native boundary as milli-units because `wsprintfA` cannot format floats.
141
+ - File drop, reload, DevTools, and fullscreen toggles, all routed through one shared input pre-handler instead of being duplicated per window procedure.
142
+
143
+ #### Application chrome
144
+
145
+ - `setBrowserUi(mask)` and `ShellOptions.browserUi`, with `BROWSER_UI_MENU` (1), `BROWSER_UI_KEYS` (2), `BROWSER_UI_STATUSBAR` (4), `BROWSER_UI_SWIPE` (8), `BROWSER_UI_AUTOFILL` (16), and `BROWSER_UI_ALL`. The swipe switch lives on `ICoreWebView2Settings6`, not `Settings5`.
146
+ - `showContextMenu(x, y, items)` renders a native Win32 menu from a line-based `id|label` protocol, where a lone `-` is a separator. The selection comes back as `{ t: "menu", id }`.
147
+ - `pickFolder()` calls `SHBrowseForFolder` directly — no child process, no temporary script, no execution-policy dependency — and binds the dialog to the host window so it cannot fall behind it. It is synchronous and blocks the calling thread.
148
+
149
+ #### Typed IPC (`bun-app-window/ipc`)
150
+
151
+ - `createIpcServer({ service })` derives a route table from a plain object; `connect()` and `webSocketChannel()` hand the client a generic proxy typed against the service.
152
+ - Request/reply over WebSocket with per-call ids and concurrency-safe matching. Unknown methods and methods inherited from the prototype chain are rejected as errors returned to the caller, never thrown into the host.
153
+ - `undefined` returns normalize to `null`, because JSON drops the field. Error stacks are withheld unless `exposeStack` is enabled.
154
+ - Events broadcast to every attached peer; writes to dead connections are skipped without disturbing live ones. A throwing handler does not prevent the other handlers from running.
155
+ - Timeouts reject the pending call and late replies are discarded. Closing the channel rejects everything in flight, and calling after close fails immediately without writing.
156
+
157
+ #### Windows 11 title bar (`bun-app-window/titlebar`)
158
+
159
+ - `mountTitleBar()` with `TITLEBAR_GLYPHS` in both `unicode` and `fluent` sets, accessible labels that track the maximized state, drag and double-click-to-maximize, and control-area exclusion so pressing a button does not drag the window.
160
+ - `titleBarCss()` injects the stylesheet once and shares it across instances; themes switch through attribute selectors. `bridgeFromWindowService()` forwards the four window methods.
161
+ - Zero external dependencies and no access to shell internals — the component only needs the DOM and the bridge.
162
+
163
+ #### Placement persistence (`bun-app-window/state`)
164
+
165
+ - `savePlacement` / `loadPlacement` / `capturePlacement` / `restorePlacement` / `attachPlacementStore`, plus `parseBounds` / `parsePlacement` for the raw formats.
166
+ - Writes are atomic (temporary file plus rename, no `.tmp` residue). Reads validate the shape, so a hand-edited, truncated, or missing file degrades to `null` instead of throwing.
167
+ - `attachPlacementStore` polls at 700 ms and does not touch the disk while the window is at rest.
168
+ - Maximized windows persist their restore rectangle; coordinates are converted across DPI changes and corrected when the monitor they were saved on is gone.
169
+
170
+ #### Diagnostics (`bun-app-window/debug`)
171
+
172
+ - `debugOf(shell)` exposes `hittest`, `metrics`, `dpi`, `selfBase`, and `selfPath`. Deliberately kept off the main facade so the diagnostic shape can evolve without becoming part of the public contract.
173
+
174
+ #### Packaging and distribution
175
+
176
+ - `resolveNativeLib()` follows a four-level priority: explicit `nativeLibPath`, embedded bytes released to `%LOCALAPPDATA%\<appName>\runtime\` and named by content hash, a `libshell.dll` next to the executable, then the development layout (`.build/` before `dist/`). The reported `source` distinguishes `explicit` / `embedded` / `package` / `dev`.
177
+ - `bun-app-window/embedded` bundles the DLL into `bun build --compile` output. `embeddedDll()` returns `null` outside a compiled binary rather than throwing.
178
+ - Released copies are written atomically, reused when an existing file already matches by size (`reused: true`), and stale hashed copies are pruned without failing on files still mapped by another process.
179
+ - `serveOnSafePort()` avoids the browser-restricted port list that causes `ERR_UNSAFE_PORT`, binding inside 40000–60000; `isRestrictedPort()` exposes the check.
180
+ - The `bun-app-window/native` subpath resolves to the shipped `dist/libshell.dll`.
181
+
182
+ #### Quality gates
183
+
184
+ - `bun check` type-checks 21 files and `bun test` runs 103 cases across 10 files, covering exports, first paint, capabilities, IPC, the native surface, ports, library resolution, placement, the title bar, and agreement between TypeScript constants and their Win32 counterparts.
185
+ - TypeScript strict mode with `exactOptionalPropertyTypes`, `noUncheckedIndexedAccess`, `verbatimModuleSyntax`, `erasableSyntaxOnly`, `noImplicitOverride`, and `noUnusedLocals` / `noUnusedParameters`.
186
+ - The 33 exported symbols of `libshell.dll` are pinned by a hand-written list in the native surface test, so a symbol cannot silently drop out of coverage.
187
+
188
+ ### Known limitations
189
+
190
+ - Windows x64 only. The `os` and `cpu` fields in `package.json` reject every other platform at install time. Cross-platform support is a reservation in the design, not a capability: `shell.cc` is Win32 and WebView2 throughout.
191
+ - TypeScript sources are published, not compiled. Consumers need Bun 1.4.3 or newer. Installing under Node succeeds but nothing runs.
192
+ - `bun:ffi` is marked experimental in Bun, and the entire native boundary depends on it.
193
+ - `pickFolder()` blocks the calling thread. The modal loop inside `SHBrowseForFolder` keeps timers on that thread from firing, and the call cannot be cancelled or timed out. Run it in a worker if you need concurrency.
194
+ - One window per process. The native layer keeps a process-wide singleton, so a second `start()` fails until the previous window is observed dead through `isAlive()`.
195
+ - Not yet verified in managed environments that enforce AppLocker, WDAC, or third-party EDR behaviour blocking.
196
+ - No commercially trusted code signing: only a self-signed certificate has been exercised end to end.
197
+
198
+ [Unreleased]: https://github.com/TiancongLx/bun-app-window/compare/v0.2.2...HEAD
199
+ [0.2.2]: https://github.com/TiancongLx/bun-app-window/compare/v0.2.1...v0.2.2
200
+ [0.2.1]: https://github.com/TiancongLx/bun-app-window/compare/v0.2.0...v0.2.1
201
+ [0.2.0]: https://github.com/TiancongLx/bun-app-window/compare/v0.1.0...v0.2.0
202
+ [0.1.0]: https://github.com/TiancongLx/bun-app-window/releases/tag/v0.1.0
package/README.md CHANGED
@@ -29,6 +29,7 @@ Before adopting this package, please review the following architectural constrai
29
29
  | --- | --- |
30
30
  | Windows x64 only | Enforced via `os` and `cpu` fields in `package.json` (the platform-neutral package name is reserved for future expansion). A prebuilt `libshell.dll` is shipped in the package; no install-time compilation is required |
31
31
  | Bun runtime required | Ships raw TypeScript source files and requires Bun >= 1.4.3. While Node.js can install the package, it cannot execute it |
32
+ | Type-checking needs `@types/bun` | Because the package ships `.ts` sources, your own `bun check` compiles them too. Add `bun add -d @types/bun`; without it the checker reports `TS2868: Cannot find name 'Bun'` from inside `node_modules` |
32
33
  | Built on `bun:ffi` | `bun:ffi` is marked as experimental in Bun. While Bun recommends Node-API for production stability, this library chooses `bun:ffi` to achieve a zero-dependency footprint |
33
34
 
34
35
  Runtime prerequisites and non-goals:
@@ -43,6 +44,10 @@ Runtime prerequisites and non-goals:
43
44
  bun add bun-app-window
44
45
  ```
45
46
 
47
+ ### What the tarball contains
48
+
49
+ The published package holds the prebuilt `dist/libshell.dll` plus the TypeScript sources — 19 files, ~0.36 MB packed. The native C++ layer (`native/`) and the vendored upstream headers (`vendor/`) are **not** shipped: they are only needed to rebuild `libshell.dll`, and rebuilding requires cloning the repository. `bun run build:native` and `build:exe` are therefore repository-only scripts that do nothing useful inside `node_modules`.
50
+
46
51
  ## Quick Start
47
52
 
48
53
  ```ts
@@ -233,6 +238,18 @@ d.dpi(); // Returns DPI awareness level and scaling factor
233
238
  d.hittest(x, y);
234
239
  ```
235
240
 
241
+ ### `bun-app-window/native` — The Prebuilt DLL
242
+
243
+ Resolves to `dist/libshell.dll` itself, so `import.meta.resolve()` hands you a real path you can copy next to your executable:
244
+
245
+ ```ts
246
+ import nativeLibPath from "bun-app-window/native" with { type: "file" };
247
+
248
+ await Bun.write(join(dirname(process.execPath), "libshell.dll"), Bun.file(nativeLibPath));
249
+ ```
250
+
251
+ The `with { type: "file" }` attribute is required — without it the bundler tries to parse the DLL as a module. This subpath is for hand-rolled packaging; if you use `bun build --compile`, `bun-app-window/embedded` does the same job without touching the filesystem.
252
+
236
253
  ## Common Recipes & Best Practices
237
254
 
238
255
  ### 1. Disabling Browser-Specific UI
@@ -373,15 +390,30 @@ At runtime, `libshell.dll` is located in the following order:
373
390
 
374
391
  ## Development & Verification
375
392
 
376
- Built with Bun 1.4.3 and TypeScript 7.0.2 with full strict type-checking enabled. Quality gates use standard CLI commands:
393
+ Built with Bun 1.4.3 and TypeScript 7.0.2 with full strict type-checking enabled.
394
+
395
+ **This project has no remote CI.** GitHub Actions cannot allocate runners for this account (billing lock), and the project is a single-platform, single-developer package with no external service dependencies — so the gates run locally instead. The recommended entry point is:
396
+
397
+ ```powershell
398
+ bun run scripts/gate.ts # bun check + bun test + consumer acceptance
399
+ bun run scripts/gate.ts --clean # same gates, but inside a fresh clone of HEAD
400
+ ```
401
+
402
+ `-Clean` reproduces the conditions a hosted runner would provide: it clones `HEAD` into `%TEMP%`, installs from the lockfile, and runs the gates there, so local artefacts (`.build/`, `node_modules/`, `vendor/webview2/`) cannot mask a missing file. It takes about two seconds. Uncommitted changes are reported and excluded — run without `-Clean` to verify the working tree as-is.
403
+
404
+ The gate has three stages: `bun check`, `bun test`, and consumer acceptance (`scripts/verify-pack.ts`). The third packs the tarball, installs it into a scratch project under `%TEMP%`, type-checks that project, and runs `scripts/consumer-probe.ts` against the installed package — 40 assertions covering every subpath, the DLL actually being `dlopen`-ed out of `node_modules`, and `libInfo.source === "package"`. It runs offline in about 0.4 s and denies by default: a file packed without being listed in the allow-list fails the gate. Pass `-SkipPack` only when the gate itself is what you are changing.
405
+
406
+ The underlying commands remain available directly:
377
407
 
378
408
  ```bash
379
409
  bun check # Run TypeScript type checker
380
- bun test # Run automated test suite (tests/, 103 test cases)
410
+ bun test # Run automated test suite (tests/, 182 test cases)
381
411
  bun run demo # Launch interactive self-test demo (requires desktop session)
382
412
  ```
383
413
 
384
- Whenever `src/shell/` or `package.json` is modified, run the external consumer smoke test documented under the package acceptance section in `AGENTS.md`.
414
+ Note that `bun run demo` and the compiled-executable acceptance (shape B in `AGENTS.md`) both need an interactive desktop, so they were never CI-capable — they are local-only by construction.
415
+
416
+ Whenever `src/shell/` or `package.json` is modified, the consumer acceptance stage already covers the installed-package path (shape A in `AGENTS.md`); it runs as part of the gate, so there is nothing to remember. Only shape B — building the single-file `.exe` — still needs a manual run inside a desktop session.
385
417
 
386
418
  For window enumeration (`EnumWindows`) or automated screenshot scripts, filter by the corresponding Win32 window class name:
387
419
 
package/README.zh-CN.md CHANGED
@@ -29,6 +29,7 @@ await openShell({ url: `http://127.0.0.1:${server.port}`, flags: FLAG_FRAMELESS
29
29
  | --- | --- |
30
30
  | 仅支持 Windows x64 | `package.json` 中的 `os` 与 `cpu` 字段已做硬性限制,拒绝在其他平台安装(包名保持平台中立仅为预留命名空间)。预编译动态库 `libshell.dll` 随包分发,无需在安装期编译 |
31
31
  | 仅支持 Bun 运行时 | 包内直接发布 TypeScript 源码,要求 Bun ≥ 1.4.3。Node.js 环境虽可完成包安装,但无法直接运行 |
32
+ | 类型检查需自备 `@types/bun` | 包内是 `.ts` 源文件,消费方的 `bun check` 会一并编译它们。需执行 `bun add -d @types/bun`,否则类型检查会在 `node_modules` 内报 `TS2868: Cannot find name 'Bun'` |
32
33
  | 基于 `bun:ffi` 构建 | `bun:ffi` 在 Bun 中仍标记为实验性特性(Experimental)。官方建议生产环境优先采用 Node-API,本项目选择 `bun:ffi` 是为了换取零第三方依赖的轻量架构 |
33
34
 
34
35
  运行环境与能力边界:
@@ -43,6 +44,10 @@ await openShell({ url: `http://127.0.0.1:${server.port}`, flags: FLAG_FRAMELESS
43
44
  bun add bun-app-window
44
45
  ```
45
46
 
47
+ ### 包内包含什么
48
+
49
+ 发布的 tarball 只有预编译的 `dist/libshell.dll` 与 TypeScript 源码,共 19 个文件(约 0.36 MB)。原生 C++ 层(`native/`)与上游 vendored 头文件(`vendor/`)**不随包分发**:它们只在重编 `libshell.dll` 时需要,而重编必须 clone 仓库。因此 `bun run build:native` 与 `build:exe` 是本仓库专用脚本,装进 `node_modules` 后执行没有意义。
50
+
46
51
  ## 快速开始
47
52
 
48
53
  ```ts
@@ -233,6 +238,18 @@ d.dpi(); // 获取当前 DPI 感知级别与缩放因子
233
238
  d.hittest(x, y);
234
239
  ```
235
240
 
241
+ ### `bun-app-window/native` — 预编译动态库本体
242
+
243
+ 该子路径解析到 `dist/libshell.dll` 本身,`import.meta.resolve()` 拿到的是可直接拷到 exe 旁边的真实路径:
244
+
245
+ ```ts
246
+ import nativeLibPath from "bun-app-window/native" with { type: "file" };
247
+
248
+ await Bun.write(join(dirname(process.execPath), "libshell.dll"), Bun.file(nativeLibPath));
249
+ ```
250
+
251
+ `with { type: "file" }` 不能省,否则打包器会把 DLL 当模块解析。此入口用于自行封装的场景;走 `bun build --compile` 请直接用 `bun-app-window/embedded`,它不必碰文件系统。
252
+
236
253
  ## 常见场景与最佳实践
237
254
 
238
255
  ### 1. 禁用浏览器默认行为
@@ -371,15 +388,30 @@ bun build --compile --windows-hide-console src/index.ts --outfile dist/app.exe
371
388
 
372
389
  ## 开发与验证
373
390
 
374
- 本项目工具链基于 Bun 1.4.3 与 TypeScript 7.0.2 构建,`tsconfig.json` 开启全量严格模式检查。质量门禁直接采用原生 CLI 命令:
391
+ 本项目工具链基于 Bun 1.4.3 与 TypeScript 7.0.2 构建,`tsconfig.json` 开启全量严格模式检查。
392
+
393
+ **本项目不使用远端 CI。** 该账户的 GitHub Actions 因 billing lock 无法分配 runner;而项目本身是单平台、单人开发、零外部服务依赖的依赖包,远端 CI 的边际价值低于其维护成本,因此质量门禁改为本地执行。推荐入口:
394
+
395
+ ```powershell
396
+ bun run scripts/gate.ts # 对当前工作区跑三段门禁
397
+ bun run scripts/gate.ts --clean # 同上,但在 HEAD 的干净副本里跑
398
+ ```
399
+
400
+ `-Clean` 复现了托管 runner 的条件:把 `HEAD` clone 到 `%TEMP%`,按 lockfile 安装依赖,再在其中跑门禁。这样本地产物(`.build/`、`node_modules/`、`vendor/webview2/`)就无法掩盖缺失的文件。耗时约两秒。未提交改动会被提示并排除在干净副本之外——如需验证工作区现状,请去掉 `-Clean`。
401
+
402
+ 门禁分三段:`bun check`、`bun test`,以及消费方验收(`scripts/verify-pack.ts`)。第三段会把 tarball 打进 `%TEMP%`,装进一个临时工程,对那个工程做类型检查,再运行 `scripts/consumer-probe.ts`——40 条断言覆盖全部子路径、DLL 是否真从 `node_modules` 里被 `dlopen`、以及 `libInfo.source` 是否为 `package`。离线可跑,约 0.4 秒,且默认拒绝:任何没在允许清单里登记的文件被打进包,门禁就红。只有在改动门禁自身时才用 `-SkipPack`。
403
+
404
+ 底层命令仍可直接调用:
375
405
 
376
406
  ```bash
377
407
  bun check # 执行 TypeScript 类型检查
378
- bun test # 运行自动化测试套件(位于 tests/,共 103 个用例)
408
+ bun test # 运行自动化测试套件(位于 tests/,共 182 个用例)
379
409
  bun run demo # 启动交互式自检示例程序(需在桌面会话下运行)
380
410
  ```
381
411
 
382
- 注意:每当修改 `src/shell/` 或 `package.json` 后,除运行上述测试外,还需执行一次「作为外部工程依赖安装」的集成冒烟测试,具体流程详见 `AGENTS.md` 中的「依赖包验收」章节。
412
+ 需要注意:`bun run demo` 与「编译为单文件可执行程序」的验收(`AGENTS.md` 中的形态 B)都需要交互式桌面,本就不具备 CI 可行性,属于构造上的本地专属项。
413
+
414
+ 注意:每当修改 `src/shell/` 或 `package.json` 后,「作为外部工程依赖安装」这条链路(`AGENTS.md` 中的形态 A)已由消费方验收阶段自动覆盖——它随门禁一起跑,不需要额外记得。只剩形态 B(编译单文件 `.exe`)仍需在桌面会话下手动执行一次。
383
415
 
384
416
  自动化测试或截图脚本若需通过 Win32 `EnumWindows` 过滤窗口句柄,请根据窗口模式匹配对应的窗口类名(Window Class):
385
417
 
package/README.zh-TW.md CHANGED
@@ -29,6 +29,7 @@ await openShell({ url: `http://127.0.0.1:${server.port}`, flags: FLAG_FRAMELESS
29
29
  | --- | --- |
30
30
  | 僅支援 Windows x64 | `package.json` 中的 `os` 與 `cpu` 欄位已做強制限制,拒絕在其他平台安裝(套件名稱保持平台中立僅為預留命名空間)。預編譯動態連結程式庫 `libshell.dll` 隨套件發佈,無需在安裝期編譯 |
31
31
  | 僅支援 Bun 執行環境 | 套件內直接發佈 TypeScript 原始碼,要求 Bun ≥ 1.4.3。Node.js 環境雖可完成套件安裝,但無法直接執行 |
32
+ | 型別檢查需自備 `@types/bun` | 套件內是 `.ts` 原始碼,消費方的 `bun check` 會一併編譯它們。需執行 `bun add -d @types/bun`,否則型別檢查會在 `node_modules` 內回報 `TS2868: Cannot find name 'Bun'` |
32
33
  | 基於 `bun:ffi` 建構 | `bun:ffi` 在 Bun 中仍標記為實驗性功能(Experimental)。官方建議正式環境優先採用 Node-API,本專案選擇 `bun:ffi` 是為了換取零第三方相依性的輕量架構 |
33
34
 
34
35
  執行環境與能力邊界:
@@ -43,6 +44,10 @@ await openShell({ url: `http://127.0.0.1:${server.port}`, flags: FLAG_FRAMELESS
43
44
  bun add bun-app-window
44
45
  ```
45
46
 
47
+ ### 套件內容包含什麼
48
+
49
+ 發佈的 tarball 只有預先編譯的 `dist/libshell.dll` 與 TypeScript 原始碼,共 19 個檔案(約 0.36 MB)。原生 C++ 層(`native/`)與上游 vendored 標頭檔(`vendor/`)**不隨套件散布**:它們只在重新編譯 `libshell.dll` 時需要,而重新編譯必須 clone 儲存庫。因此 `bun run build:native` 與 `build:exe` 是本儲存庫專用指令碼,安裝進 `node_modules` 後執行沒有意義。
50
+
46
51
  ## 快速開始
47
52
 
48
53
  ```ts
@@ -233,6 +238,18 @@ d.dpi(); // 取得目前 DPI 感知層級與縮放係數
233
238
  d.hittest(x, y);
234
239
  ```
235
240
 
241
+ ### `bun-app-window/native` — 預編譯動態連結程式庫本體
242
+
243
+ 該子路徑解析到 `dist/libshell.dll` 本身,`import.meta.resolve()` 拿到的是可直接複製到 exe 旁邊的真實路徑:
244
+
245
+ ```ts
246
+ import nativeLibPath from "bun-app-window/native" with { type: "file" };
247
+
248
+ await Bun.write(join(dirname(process.execPath), "libshell.dll"), Bun.file(nativeLibPath));
249
+ ```
250
+
251
+ `with { type: "file" }` 不能省略,否則封裝器會把 DLL 當模組解析。此入口用於自行封裝的場景;走 `bun build --compile` 請直接用 `bun-app-window/embedded`,它不必碰檔案系統。
252
+
236
253
  ## 常見場景與最佳實踐
237
254
 
238
255
  ### 1. 停用瀏覽器預設行為
@@ -371,15 +388,30 @@ bun build --compile --windows-hide-console src/index.ts --outfile dist/app.exe
371
388
 
372
389
  ## 開發與驗證
373
390
 
374
- 本專案工具鏈基於 Bun 1.4.3 與 TypeScript 7.0.2 建構,`tsconfig.json` 開啟全量嚴格模式檢查。品質把關直接採用原生 CLI 指令:
391
+ 本專案工具鏈基於 Bun 1.4.3 與 TypeScript 7.0.2 建構,`tsconfig.json` 開啟全量嚴格模式檢查。
392
+
393
+ **本專案不使用遠端 CI。** 該帳戶的 GitHub Actions 因 billing lock 無法分配 runner;而專案本身是單平台、單人開發、零外部服務相依的相依套件,遠端 CI 的邊際價值低於其維護成本,因此品質把關改為本機執行。建議入口:
394
+
395
+ ```powershell
396
+ bun run scripts/gate.ts # 對目前工作區執行三段把關
397
+ bun run scripts/gate.ts --clean # 同上,但在 HEAD 的乾淨副本內執行
398
+ ```
399
+
400
+ `-Clean` 重現了託管 runner 的條件:將 `HEAD` clone 到 `%TEMP%`,依 lockfile 安裝相依套件,再於其中執行把關。如此一來,本機產物(`.build/`、`node_modules/`、`vendor/webview2/`)便無法掩蓋缺少的檔案。耗時約兩秒。未提交的變更會被提示並排除於乾淨副本之外——如需驗證工作區現況,請移除 `-Clean`。
401
+
402
+ 品質把關分三段:`bun check`、`bun test`,以及消費方驗收(`scripts/verify-pack.ts`)。第三段會把 tarball 打包到 `%TEMP%`,安裝進一個臨時專案,對該專案執行型別檢查,再執行 `scripts/consumer-probe.ts`——40 條斷言涵蓋全部子路徑、DLL 是否真的從 `node_modules` 被 `dlopen`、以及 `libInfo.source` 是否為 `package`。可離線執行,約 0.4 秒,且預設拒絕:任何未在允許清單登記的檔案被打進套件,把關就會失敗。只有在改動把關腳本本身時才用 `-SkipPack`。
403
+
404
+ 底層指令仍可直接呼叫:
375
405
 
376
406
  ```bash
377
407
  bun check # 執行 TypeScript 型別檢查
378
- bun test # 執行自動化測試套件(位於 tests/,共 103 個測試案例)
408
+ bun test # 執行自動化測試套件(位於 tests/,共 182 個測試案例)
379
409
  bun run demo # 啟動互動式自我檢測範例程式(需於桌面工作階段下執行)
380
410
  ```
381
411
 
382
- 注意:每當修改 `src/shell/` 或 `package.json` 後,除執行上述測試外,還需執行一次「作為外部專案相依套件安裝」的整合冒煙測試(Smoke Test),具體流程詳見 `AGENTS.md` 中的「依賴套件驗收」章節。
412
+ 需注意:`bun run demo` 與「編譯為單一可執行檔」的驗收(`AGENTS.md` 中的形態 B)皆需要互動式桌面,本就不具備 CI 可行性,屬於構造上的本機專屬項目。
413
+
414
+ 注意:每當修改 `src/shell/` 或 `package.json` 後,「作為外部專案相依套件安裝」這條鏈路(`AGENTS.md` 中的形態 A)已由消費方驗收階段自動涵蓋——它隨品質把關一起執行,不需要額外記得。只剩形態 B(編譯單一 `.exe`)仍需於桌面工作階段手動執行一次。
383
415
 
384
416
  自動化測試或截圖腳本若需透過 Win32 `EnumWindows` 過濾視窗控制代碼(HWND),請依據視窗模式比對對應的視窗類別名稱(Window Class):
385
417
 
package/dist/libshell.dll CHANGED
Binary file
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "$schema": "https://json.schemastore.org/package.json",
3
3
  "name": "bun-app-window",
4
- "version": "0.1.0",
4
+ "version": "0.2.2",
5
5
  "description": "Native WebView2 window shell for Bun — frameless or system-decorated windows, DPI-aware geometry, typed IPC, system-accurate title bar",
6
6
  "type": "module",
7
7
  "license": "MIT",
@@ -34,27 +34,25 @@
34
34
  "./state": "./src/shell/state.ts",
35
35
  "./ipc": "./src/shell/ipc.ts",
36
36
  "./titlebar": "./src/ui/titlebar.ts",
37
- "./native": "./dist/libshell.dll",
37
+ "./native": {
38
+ "types": "./src/shell/native-asset.d.ts",
39
+ "default": "./dist/libshell.dll"
40
+ },
38
41
  "./package.json": "./package.json"
39
42
  },
40
43
  "files": [
41
44
  "src/shell",
42
45
  "src/ui/titlebar.ts",
43
46
  "dist/libshell.dll",
44
- "native/shell.cc",
45
- "native/build.ps1",
46
- "native/fetch.ps1",
47
- "vendor/webview/core",
48
- "vendor/webview/compatibility",
49
- "vendor/webview2/build/native/include",
50
47
  "README.md",
51
48
  "README.zh-CN.md",
52
49
  "README.zh-TW.md",
50
+ "CHANGELOG.md",
53
51
  "LICENSE"
54
52
  ],
55
53
  "scripts": {
56
- "build:native": "pwsh -NoProfile -ExecutionPolicy Bypass -File native/build.ps1",
57
- "build:exe": "pwsh -NoProfile -ExecutionPolicy Bypass -File native/build-pack.ps1",
54
+ "build:native": "bun run native/build.ts",
55
+ "build:exe": "bun run native/build-pack.ts",
58
56
  "demo": "bun run ./src/host.ts",
59
57
  "pack": "bun pm pack"
60
58
  },
@@ -0,0 +1,18 @@
1
+ /**
2
+ * native-asset.d.ts — `bun-app-window/native` 子路径的类型声明
3
+ *
4
+ * 该子路径的运行时目标是真实的 `dist/libshell.dll`,不是 JavaScript 模块,
5
+ * TypeScript 无从推导它的形状,消费方会得到 `TS2307: Cannot find module`。
6
+ * `package.json` 的 `exports["./native"]` 用 `types` 条件把类型指到本文件,
7
+ * 运行时的 `default` 条件仍指向 DLL 本体——两件事互不干扰。
8
+ *
9
+ * 为什么不做成 `.ts` 包装模块:那会让 `import.meta.resolve("bun-app-window/native")`
10
+ * 返回一个 `.ts` 路径。把 DLL 拷到 exe 旁边是真实用法,解析结果必须仍是 DLL 本身。
11
+ *
12
+ * 消费方用法(`with { type: "file" }` 不能省,否则打包器会试着把 DLL 当模块解析):
13
+ *
14
+ * import nativeLibPath from "bun-app-window/native" with { type: "file" };
15
+ */
16
+
17
+ declare const nativeLibPath: string;
18
+ export default nativeLibPath;
@@ -205,7 +205,7 @@ export async function resolveNativeLib(opts: ResolveOptions = {}): Promise<Nativ
205
205
 
206
206
  throw new Error(
207
207
  "libshell.dll not found. Build the native layer first: " +
208
- "`pwsh -File native/build.ps1`. " +
208
+ "`bun run native/build.ts`. " +
209
209
  `Searched under ${PKG_ROOT}`,
210
210
  );
211
211
  }
package/native/build.ps1 DELETED
@@ -1,67 +0,0 @@
1
- #Requires -Version 7.0
2
- param(
3
- [string]$Root = (Join-Path $PSScriptRoot '..'),
4
- [switch]$Clean
5
- )
6
-
7
- Set-StrictMode -Version Latest
8
- $ErrorActionPreference = 'Stop'
9
-
10
- $wv = Join-Path $Root 'vendor\webview'
11
- $sdk = Join-Path $Root 'vendor\webview2'
12
- $out = Join-Path $Root '.build'
13
- New-Item -ItemType Directory -Force -Path $out | Out-Null
14
-
15
- if ($Clean -and (Test-Path $out)) {
16
- Get-ChildItem -File $out | Remove-Item -Force
17
- }
18
-
19
- foreach ($p in @(
20
- (Join-Path $wv 'core\include\webview\webview.h'),
21
- (Join-Path $wv 'compatibility\mingw\include\EventToken.h'),
22
- (Join-Path $sdk 'build\native\include\WebView2.h')
23
- )) {
24
- if (-not (Test-Path $p)) { throw "missing prerequisite: $p" }
25
- }
26
-
27
- $includes = @(
28
- (Join-Path $wv 'core\include'),
29
- (Join-Path $wv 'compatibility\mingw\include'),
30
- (Join-Path $sdk 'build\native\include')
31
- ) | ForEach-Object { "-I$_" }
32
-
33
- $libs = @('advapi32', 'ole32', 'oleaut32', 'uuid', 'shell32', 'shlwapi', 'user32', 'version', 'dwmapi') |
34
- ForEach-Object { "-l$_" }
35
-
36
- $args = @(
37
- '-std=c++17', '-O2', '-shared',
38
- '-static-libgcc', '-static-libstdc++',
39
- '-DWEBVIEW_STATIC', '-DUNICODE', '-D_UNICODE',
40
- '-Wall', '-Wextra',
41
- '-Wno-unknown-pragmas', '-Wno-unused-parameter'
42
- ) + $includes + @(
43
- (Join-Path $PSScriptRoot 'shell.cc'),
44
- '-o', (Join-Path $out 'libshell.dll')
45
- ) + $libs
46
-
47
- Write-Host ('clang++ ' + ($args -join ' '))
48
- Write-Host ''
49
- & clang++.exe @args
50
- if ($LASTEXITCODE -ne 0) { throw "clang++ failed: $LASTEXITCODE" }
51
-
52
- $dll = Join-Path $out 'libshell.dll'
53
-
54
- # 同时产出一份到 dist/:包分发从这里取(package.json 的 files 与 embedded 入口都指向 dist)
55
- $distDir = Join-Path $Root 'dist'
56
- New-Item -ItemType Directory -Force -Path $distDir | Out-Null
57
- Copy-Item $dll (Join-Path $distDir 'libshell.dll') -Force
58
-
59
- Write-Host ''
60
- Write-Host ('built: {0} ({1:N0} bytes)' -f $dll, (Get-Item $dll).Length)
61
- Write-Host ('staged: {0}' -f (Join-Path $distDir 'libshell.dll'))
62
- Write-Host ''
63
- Write-Host '=== exported symbols ==='
64
- & llvm-nm.exe --defined-only --extern-only --demangle $dll 2>&1 | Select-String 'shell_'
65
- Write-Host ''
66
- Write-Host '=== runtime deps ==='
67
- & llvm-objdump.exe -p $dll 2>&1 | Select-String 'DLL Name'