sparkbun 0.1.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 (131) hide show
  1. package/bin/sparkbun.cjs +18 -0
  2. package/dist-linux-arm64/bsdiff +0 -0
  3. package/dist-linux-arm64/bspatch +0 -0
  4. package/dist-linux-arm64/libElectrobunCore.so +0 -0
  5. package/dist-linux-arm64/libNativeWrapper.so +0 -0
  6. package/dist-linux-arm64/libasar.so +0 -0
  7. package/dist-linux-x64/bsdiff +0 -0
  8. package/dist-linux-x64/bspatch +0 -0
  9. package/dist-linux-x64/libElectrobunCore.so +0 -0
  10. package/dist-linux-x64/libNativeWrapper.so +0 -0
  11. package/dist-linux-x64/libasar.so +0 -0
  12. package/dist-macos-arm64/bsdiff +0 -0
  13. package/dist-macos-arm64/bspatch +0 -0
  14. package/dist-macos-arm64/libElectrobunCore.dylib +0 -0
  15. package/dist-macos-arm64/libNativeWrapper.dylib +0 -0
  16. package/dist-macos-arm64/libasar.dylib +0 -0
  17. package/dist-macos-arm64/libwebgpu_dawn.dylib +0 -0
  18. package/dist-macos-arm64/preload-full.js +885 -0
  19. package/dist-macos-arm64/preload-sandboxed.js +111 -0
  20. package/dist-macos-arm64/process_helper +0 -0
  21. package/dist-win-x64/ElectrobunCore.dll +0 -0
  22. package/dist-win-x64/WebView2Loader.dll +0 -0
  23. package/dist-win-x64/bsdiff.exe +0 -0
  24. package/dist-win-x64/bspatch.exe +0 -0
  25. package/dist-win-x64/libNativeWrapper.dll +0 -0
  26. package/dist-win-x64/zig-asar/arm64/libasar.dll +0 -0
  27. package/dist-win-x64/zig-asar/x64/libasar.dll +0 -0
  28. package/package.json +47 -0
  29. package/scripts/build-and-upload-artifacts.js +207 -0
  30. package/scripts/gen-webgpu-ffi.mjs +162 -0
  31. package/scripts/install-windows-deps.ps1 +80 -0
  32. package/scripts/package-release.js +237 -0
  33. package/scripts/push-version.js +84 -0
  34. package/scripts/update-bun-version.ts +122 -0
  35. package/scripts/update-cef-version.ts +145 -0
  36. package/src/browser/builtinrpcSchema.ts +19 -0
  37. package/src/browser/global.d.ts +36 -0
  38. package/src/browser/index.ts +234 -0
  39. package/src/browser/webviewtag.ts +88 -0
  40. package/src/browser/wgputag.ts +48 -0
  41. package/src/bun/SparkBunConfig.ts +497 -0
  42. package/src/bun/__tests__/ffi-contract.test.ts +105 -0
  43. package/src/bun/core/ApplicationMenu.ts +70 -0
  44. package/src/bun/core/BrowserView.ts +416 -0
  45. package/src/bun/core/BrowserWindow.ts +396 -0
  46. package/src/bun/core/BuildConfig.ts +71 -0
  47. package/src/bun/core/ContextMenu.ts +75 -0
  48. package/src/bun/core/GpuWindow.ts +289 -0
  49. package/src/bun/core/Paths.ts +5 -0
  50. package/src/bun/core/Socket.ts +22 -0
  51. package/src/bun/core/Tray.ts +197 -0
  52. package/src/bun/core/Updater.ts +1131 -0
  53. package/src/bun/core/Utils.ts +487 -0
  54. package/src/bun/core/WGPUView.ts +167 -0
  55. package/src/bun/core/menuRoles.ts +181 -0
  56. package/src/bun/events/ApplicationEvents.ts +22 -0
  57. package/src/bun/events/event.ts +27 -0
  58. package/src/bun/events/eventEmitter.ts +45 -0
  59. package/src/bun/events/trayEvents.ts +11 -0
  60. package/src/bun/events/webviewEvents.ts +39 -0
  61. package/src/bun/events/windowEvents.ts +23 -0
  62. package/src/bun/index.ts +120 -0
  63. package/src/bun/preload/.generated/compiled.ts +2 -0
  64. package/src/bun/preload/build.ts +65 -0
  65. package/src/bun/preload/dragRegions.ts +41 -0
  66. package/src/bun/preload/encryption.ts +86 -0
  67. package/src/bun/preload/events.ts +171 -0
  68. package/src/bun/preload/globals.d.ts +45 -0
  69. package/src/bun/preload/index-sandboxed.ts +28 -0
  70. package/src/bun/preload/index.ts +77 -0
  71. package/src/bun/preload/internalRpc.ts +80 -0
  72. package/src/bun/preload/overlaySync.ts +107 -0
  73. package/src/bun/preload/webviewTag.ts +451 -0
  74. package/src/bun/preload/wgpuTag.ts +246 -0
  75. package/src/bun/proc/linux.md +43 -0
  76. package/src/bun/proc/native.ts +3253 -0
  77. package/src/bun/webGPU.ts +346 -0
  78. package/src/bun/webgpuAdapter.ts +3011 -0
  79. package/src/cli/bun.lockb +0 -0
  80. package/src/cli/index.ts +4653 -0
  81. package/src/cli/package-lock.json +81 -0
  82. package/src/cli/package.json +11 -0
  83. package/src/cli/templates/embedded.ts +2 -0
  84. package/src/core/build.zig +16 -0
  85. package/src/core/main.zig +3378 -0
  86. package/src/extractor/build.zig +22 -0
  87. package/src/installer/installer-template.ts +216 -0
  88. package/src/launcher/main.ts +221 -0
  89. package/src/native/build/libNativeWrapper.so +0 -0
  90. package/src/native/linux/build/nativeWrapper.o +0 -0
  91. package/src/native/linux/cef_loader.cpp +110 -0
  92. package/src/native/linux/cef_loader.h +28 -0
  93. package/src/native/linux/cef_process_helper_linux.cpp +160 -0
  94. package/src/native/linux/nativeWrapper.cpp +11768 -0
  95. package/src/native/macos/cef_process_helper_mac.cc +160 -0
  96. package/src/native/macos/nativeWrapper.mm +9172 -0
  97. package/src/native/shared/accelerator_parser.h +72 -0
  98. package/src/native/shared/app_paths.h +110 -0
  99. package/src/native/shared/asar.h +35 -0
  100. package/src/native/shared/cache_migration.h +244 -0
  101. package/src/native/shared/callbacks.h +57 -0
  102. package/src/native/shared/cef_response_filter.h +189 -0
  103. package/src/native/shared/chromium_flags.h +181 -0
  104. package/src/native/shared/config.h +66 -0
  105. package/src/native/shared/download_event.h +197 -0
  106. package/src/native/shared/ffi_helpers.h +139 -0
  107. package/src/native/shared/glob_match.h +59 -0
  108. package/src/native/shared/json_menu_parser.h +223 -0
  109. package/src/native/shared/mime_types.h +101 -0
  110. package/src/native/shared/navigation_rules.h +98 -0
  111. package/src/native/shared/partition_context.h +137 -0
  112. package/src/native/shared/pending_resize_queue.h +45 -0
  113. package/src/native/shared/permissions.h +118 -0
  114. package/src/native/shared/permissions_cef.h +74 -0
  115. package/src/native/shared/preload_script.h +71 -0
  116. package/src/native/shared/shutdown_guard.h +134 -0
  117. package/src/native/shared/thread_safe_map.h +138 -0
  118. package/src/native/shared/webview_storage.h +91 -0
  119. package/src/native/win/cef_process_helper_win.cpp +143 -0
  120. package/src/native/win/dcomp_compositor.h +352 -0
  121. package/src/native/win/nativeWrapper.cpp +12434 -0
  122. package/src/npmbin/index.js +34 -0
  123. package/src/shared/bun-version.ts +3 -0
  124. package/src/shared/cef-version.ts +5 -0
  125. package/src/shared/naming.test.ts +327 -0
  126. package/src/shared/naming.ts +188 -0
  127. package/src/shared/platform.ts +48 -0
  128. package/src/shared/rpc.ts +541 -0
  129. package/src/shared/sparkbun-version.ts +2 -0
  130. package/src/types/three.d.ts +1 -0
  131. package/tsconfig.json +31 -0
@@ -0,0 +1,111 @@
1
+ (function(){// src/bun/preload/events.ts
2
+ function emitWebviewEvent(eventName, detail) {
3
+ setTimeout(() => {
4
+ const bridge = window.__electrobunEventBridge || window.__electrobunInternalBridge;
5
+ bridge?.postMessage(JSON.stringify({
6
+ id: "webviewEvent",
7
+ type: "message",
8
+ payload: {
9
+ id: window.__electrobunWebviewId,
10
+ eventName,
11
+ detail
12
+ }
13
+ }));
14
+ });
15
+ }
16
+ function initLifecycleEvents() {
17
+ window.addEventListener("load", () => {
18
+ if (window === window.top) {
19
+ emitWebviewEvent("dom-ready", document.location.href);
20
+ }
21
+ });
22
+ window.addEventListener("popstate", () => {
23
+ emitWebviewEvent("did-navigate-in-page", window.location.href);
24
+ });
25
+ window.addEventListener("hashchange", () => {
26
+ emitWebviewEvent("did-navigate-in-page", window.location.href);
27
+ });
28
+ }
29
+ var cmdKeyHeld = false;
30
+ var cmdKeyTimestamp = 0;
31
+ var CMD_KEY_THRESHOLD_MS = 500;
32
+ function isCmdHeld() {
33
+ if (cmdKeyHeld)
34
+ return true;
35
+ return Date.now() - cmdKeyTimestamp < CMD_KEY_THRESHOLD_MS && cmdKeyTimestamp > 0;
36
+ }
37
+ function initCmdClickHandling() {
38
+ window.addEventListener("keydown", (event) => {
39
+ if (event.key === "Meta" || event.metaKey) {
40
+ cmdKeyHeld = true;
41
+ cmdKeyTimestamp = Date.now();
42
+ }
43
+ }, true);
44
+ window.addEventListener("keyup", (event) => {
45
+ if (event.key === "Meta") {
46
+ cmdKeyHeld = false;
47
+ cmdKeyTimestamp = Date.now();
48
+ }
49
+ }, true);
50
+ window.addEventListener("blur", () => {
51
+ cmdKeyHeld = false;
52
+ });
53
+ window.addEventListener("click", (event) => {
54
+ if (event.metaKey || event.ctrlKey) {
55
+ const anchor = event.target?.closest?.("a");
56
+ if (anchor && anchor.href) {
57
+ event.preventDefault();
58
+ event.stopPropagation();
59
+ event.stopImmediatePropagation();
60
+ emitWebviewEvent("new-window-open", JSON.stringify({
61
+ url: anchor.href,
62
+ isCmdClick: true,
63
+ isSPANavigation: false
64
+ }));
65
+ }
66
+ }
67
+ }, true);
68
+ }
69
+ function initSPANavigationInterception() {
70
+ const originalPushState = history.pushState;
71
+ const originalReplaceState = history.replaceState;
72
+ history.pushState = function(state, title, url) {
73
+ if (isCmdHeld() && url) {
74
+ const resolvedUrl = new URL(String(url), window.location.href).href;
75
+ emitWebviewEvent("new-window-open", JSON.stringify({
76
+ url: resolvedUrl,
77
+ isCmdClick: true,
78
+ isSPANavigation: true
79
+ }));
80
+ return;
81
+ }
82
+ return originalPushState.apply(this, [state, title, url]);
83
+ };
84
+ history.replaceState = function(state, title, url) {
85
+ if (isCmdHeld() && url) {
86
+ const resolvedUrl = new URL(String(url), window.location.href).href;
87
+ emitWebviewEvent("new-window-open", JSON.stringify({
88
+ url: resolvedUrl,
89
+ isCmdClick: true,
90
+ isSPANavigation: true
91
+ }));
92
+ return;
93
+ }
94
+ return originalReplaceState.apply(this, [state, title, url]);
95
+ };
96
+ }
97
+ function initOverscrollPrevention() {
98
+ document.addEventListener("DOMContentLoaded", () => {
99
+ const style = document.createElement("style");
100
+ style.type = "text/css";
101
+ style.appendChild(document.createTextNode("html, body { overscroll-behavior: none; }"));
102
+ document.head.appendChild(style);
103
+ });
104
+ }
105
+
106
+ // src/bun/preload/index-sandboxed.ts
107
+ initLifecycleEvents();
108
+ initCmdClickHandling();
109
+ initSPANavigationInterception();
110
+ initOverscrollPrevention();
111
+ })();
Binary file
Binary file
Binary file
Binary file
Binary file
package/package.json ADDED
@@ -0,0 +1,47 @@
1
+ {
2
+ "name": "sparkbun",
3
+ "version": "0.1.0",
4
+ "description": "Build fast, lightweight, cross-platform desktop apps with TypeScript and Bun.",
5
+ "license": "MIT",
6
+ "author": "SparkBun Contributors",
7
+ "keywords": [
8
+ "bun",
9
+ "desktop",
10
+ "app",
11
+ "cross-platform",
12
+ "typescript",
13
+ "electron-alternative",
14
+ "webview"
15
+ ],
16
+ "exports": {
17
+ ".": "./src/bun/index.ts",
18
+ "./bun": "./src/bun/index.ts",
19
+ "./view": "./src/browser/index.ts"
20
+ },
21
+ "type": "module",
22
+ "bin": {
23
+ "sparkbun": "./bin/sparkbun.cjs"
24
+ },
25
+ "homepage": "https://github.com/gruntlord5/SparkBun",
26
+ "repository": {
27
+ "type": "git",
28
+ "url": "git+https://github.com/gruntlord5/SparkBun.git"
29
+ },
30
+ "scripts": {
31
+ "start": "bun src/bun/index.ts",
32
+ "build:dev": "bun build.ts",
33
+ "build:release": "bun build.ts --release",
34
+ "dev": "bun install && bun build:dev && cd ../kitchen && bun install && bun dev",
35
+ "dev:clean": "cd ../kitchen && rm -rf node_modules && rm -rf vendors/cef && cd ../package && bun dev",
36
+ "typecheck": "bunx tsc --noEmit && cd ../kitchen && bunx tsc --noEmit",
37
+ "test:unit": "bun test src/shared src/bun",
38
+ "bump-cef": "bun scripts/update-cef-version.ts"
39
+ },
40
+ "devDependencies": {
41
+ "typescript": "^5.9.3"
42
+ },
43
+ "dependencies": {
44
+ "@types/bun": "^1.3.8",
45
+ "png-to-ico": "^2.1.8"
46
+ }
47
+ }
@@ -0,0 +1,207 @@
1
+ #!/usr/bin/env bun
2
+
3
+ import { execSync } from "child_process";
4
+ import fs from "fs";
5
+ import path from "path";
6
+ import { fileURLToPath } from "url";
7
+
8
+ const __filename = fileURLToPath(import.meta.url);
9
+ const __dirname = path.dirname(__filename);
10
+
11
+ // Check if gh CLI is installed
12
+ function checkGhInstalled() {
13
+ try {
14
+ execSync("gh --version", { stdio: "pipe" });
15
+ return true;
16
+ } catch (error) {
17
+ return false;
18
+ }
19
+ }
20
+
21
+ // Install gh CLI if needed
22
+ function installGhCli() {
23
+ console.log("\n⚠️ GitHub CLI (gh) is not installed.");
24
+ console.log("Please install it using one of these methods:\n");
25
+
26
+ const platform = process.platform;
27
+
28
+ if (platform === "darwin") {
29
+ console.log("macOS:");
30
+ console.log(" brew install gh");
31
+ } else if (platform === "linux") {
32
+ console.log("Ubuntu/Debian:");
33
+ console.log(
34
+ " curl -fsSL https://cli.github.com/packages/githubcli-archive-keyring.gpg | sudo dd of=/usr/share/keyrings/githubcli-archive-keyring.gpg",
35
+ );
36
+ console.log(
37
+ ' echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/githubcli-archive-keyring.gpg] https://cli.github.com/packages stable main" | sudo tee /etc/apt/sources.list.d/github-cli.list > /dev/null',
38
+ );
39
+ console.log(" sudo apt update && sudo apt install gh\n");
40
+
41
+ console.log("Fedora/CentOS/RHEL:");
42
+ console.log(" sudo dnf install gh\n");
43
+
44
+ console.log("Arch Linux:");
45
+ console.log(" sudo pacman -S github-cli");
46
+ } else if (platform === "win32") {
47
+ console.log("Windows:");
48
+ console.log(" winget install --id GitHub.cli");
49
+ console.log(" # or");
50
+ console.log(" choco install gh");
51
+ }
52
+
53
+ console.log("\nAfter installation, authenticate with:");
54
+ console.log(" gh auth login\n");
55
+
56
+ console.log("Or set GITHUB_TOKEN environment variable:");
57
+ console.log(' export GITHUB_TOKEN="your-personal-access-token"\n');
58
+
59
+ process.exit(1);
60
+ }
61
+
62
+ // Check for gh CLI
63
+ if (!checkGhInstalled()) {
64
+ installGhCli();
65
+ }
66
+
67
+ // Read package.json to get the version
68
+ const packageJsonPath = path.join(__dirname, "..", "package.json");
69
+ const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, "utf8"));
70
+ const version = packageJson.version;
71
+ const tagName = `v${version}`;
72
+
73
+ console.log(
74
+ `Building and uploading artifacts for version ${version} (tag: ${tagName})`,
75
+ );
76
+
77
+ // Get platform and architecture info
78
+ const platform = process.platform;
79
+ const arch = process.arch;
80
+
81
+ // Map Node.js platform/arch to our naming
82
+ const platformMap = {
83
+ darwin: "darwin",
84
+ linux: "linux",
85
+ win32: "win",
86
+ };
87
+
88
+ const archMap = {
89
+ x64: "x64",
90
+ arm64: "arm64",
91
+ };
92
+
93
+ const platformName = platformMap[platform] || platform;
94
+ // Always use x64 for Windows since we only build x64 Windows binaries
95
+ const archName = platform === "win32" ? "x64" : archMap[arch] || arch;
96
+
97
+ console.log(`Platform: ${platformName}, Architecture: ${archName}`);
98
+
99
+ // Step 1: Build the release artifacts
100
+ console.log("\n1. Building release artifacts...");
101
+ try {
102
+ execSync("bun scripts/package-release.js", { stdio: "inherit" });
103
+ } catch (error) {
104
+ console.error("Build failed:", error.message);
105
+ process.exit(1);
106
+ }
107
+
108
+ // Step 2: Check if the artifacts exist
109
+ const artifacts = [
110
+ `electrobun-cli-${platformName}-${archName}.tar.gz`,
111
+ `electrobun-core-${platformName}-${archName}.tar.gz`,
112
+ `electrobun-cef-${platformName}-${archName}.tar.gz`,
113
+ ];
114
+
115
+ const existingArtifacts = artifacts.filter((artifact) =>
116
+ fs.existsSync(path.join(__dirname, "..", artifact)),
117
+ );
118
+
119
+ if (existingArtifacts.length === 0) {
120
+ console.error("No artifacts found to upload");
121
+ process.exit(1);
122
+ }
123
+
124
+ console.log("\nFound artifacts:");
125
+ existingArtifacts.forEach((artifact) => console.log(` - ${artifact}`));
126
+
127
+ // Step 3: Check if release exists
128
+ console.log(`\n2. Checking if release ${tagName} exists...`);
129
+ try {
130
+ // First check if we're authenticated
131
+ execSync("gh auth status", { stdio: "pipe" });
132
+
133
+ // Try to view the release with more details for debugging
134
+ const releaseInfo = execSync(`gh release view ${tagName} --json tagName`, {
135
+ encoding: "utf8",
136
+ });
137
+ console.log(`Release ${tagName} found`);
138
+ } catch (error) {
139
+ // Try listing releases to see what's available
140
+ console.log("Failed to find release, listing available releases:");
141
+ try {
142
+ const releases = execSync("gh release list --limit 5", {
143
+ encoding: "utf8",
144
+ });
145
+ console.log(releases);
146
+ } catch (listError) {
147
+ console.error(
148
+ "Failed to list releases. Check your gh CLI authentication with: gh auth status",
149
+ );
150
+ }
151
+
152
+ console.error(
153
+ `\nRelease ${tagName} not found. Please create the release first with:`,
154
+ );
155
+ console.error(` git tag ${tagName} && git push origin ${tagName}`);
156
+ console.error(` gh release create ${tagName}`);
157
+ console.error("\nOr wait for CI to create it automatically");
158
+ process.exit(1);
159
+ }
160
+
161
+ // Step 4: Upload artifacts
162
+ console.log("\n3. Uploading artifacts to release...");
163
+ for (const artifact of existingArtifacts) {
164
+ const artifactPath = path.join(__dirname, "..", artifact);
165
+ console.log(`Uploading ${artifact}...`);
166
+
167
+ try {
168
+ // Upload the artifact (--clobber will overwrite if it exists)
169
+ execSync(`gh release upload ${tagName} "${artifactPath}" --clobber`, {
170
+ stdio: "inherit",
171
+ });
172
+ console.log(` ✓ Uploaded ${artifact}`);
173
+ } catch (error) {
174
+ console.error(` ✗ Failed to upload ${artifact}:`, error.message);
175
+ console.error("Command failed:", error.message);
176
+
177
+ // Try to get more details about the error
178
+ try {
179
+ const releaseDetails = execSync(`gh release view ${tagName}`, {
180
+ encoding: "utf8",
181
+ });
182
+ console.error("\nCurrent release details:");
183
+ console.error(releaseDetails);
184
+ } catch (e) {
185
+ // Ignore
186
+ }
187
+
188
+ process.exit(1);
189
+ }
190
+ }
191
+
192
+ // Step 5: Clean up local artifacts
193
+ console.log("\n4. Cleaning up local artifacts...");
194
+ for (const artifact of existingArtifacts) {
195
+ const artifactPath = path.join(__dirname, "..", artifact);
196
+ try {
197
+ fs.unlinkSync(artifactPath);
198
+ console.log(` ✓ Removed ${artifact}`);
199
+ } catch (error) {
200
+ console.error(` ✗ Failed to remove ${artifact}:`, error.message);
201
+ }
202
+ }
203
+
204
+ console.log("\n✅ Successfully built and uploaded artifacts!");
205
+ console.log(
206
+ `\nView the release at: https://github.com/blackboardsh/electrobun/releases/tag/${tagName}`,
207
+ );
@@ -0,0 +1,162 @@
1
+ import { readFileSync, writeFileSync, existsSync, readdirSync } from "fs";
2
+ import { resolve, join, dirname } from "path";
3
+ import { fileURLToPath } from "url";
4
+
5
+ const __dirname = dirname(fileURLToPath(import.meta.url));
6
+ const root = resolve(__dirname, "..");
7
+ function resolveHeaderPath() {
8
+ const base = resolve(root, "vendors", "wgpu");
9
+ if (!existsSync(base)) return null;
10
+
11
+ const entries = readdirSync(base, { withFileTypes: true });
12
+ for (const entry of entries) {
13
+ if (!entry.isDirectory()) continue;
14
+ const candidate = join(base, entry.name, "include", "dawn", "webgpu.h");
15
+ if (existsSync(candidate)) return candidate;
16
+ }
17
+
18
+ return null;
19
+ }
20
+
21
+ const headerPath = resolveHeaderPath();
22
+ if (!headerPath) {
23
+ throw new Error("Could not find vendors/wgpu/*/include/dawn/webgpu.h");
24
+ }
25
+
26
+ const header = readFileSync(headerPath, "utf8");
27
+
28
+ const callbackTypes = new Set();
29
+ const callbackRegex = /typedef\s+[^;]*\(\s*\*\s*(WGPU[A-Za-z0-9_]+)\s*\)\s*\(/g;
30
+ let cbMatch;
31
+ while ((cbMatch = callbackRegex.exec(header))) {
32
+ callbackTypes.add(cbMatch[1]);
33
+ }
34
+
35
+ const typedefMap = new Map();
36
+ const scalarTypedefRegex = /typedef\s+(uint32_t|uint64_t|int32_t|int64_t|size_t|intptr_t|uintptr_t|float|double|int|unsigned int)\s+(WGPU[A-Za-z0-9_]+)\s*;/g;
37
+ let tdMatch;
38
+ while ((tdMatch = scalarTypedefRegex.exec(header))) {
39
+ typedefMap.set(tdMatch[2], tdMatch[1]);
40
+ }
41
+
42
+ const enumRegex = /typedef\s+enum\s+WGPU[A-Za-z0-9_]+\s*\{[\s\S]*?\}\s*(WGPU[A-Za-z0-9_]+)(?:\s+WGPU_[A-Z_]+)*\s*;/g;
43
+ let enumMatch;
44
+ while ((enumMatch = enumRegex.exec(header))) {
45
+ if (!typedefMap.has(enumMatch[1])) {
46
+ typedefMap.set(enumMatch[1], "uint32_t");
47
+ }
48
+ }
49
+
50
+ const aliasTypedefRegex = /typedef\s+(WGPU[A-Za-z0-9_]+)\s+(WGPU[A-Za-z0-9_]+)\s*;/g;
51
+ let aliasMatch;
52
+ while ((aliasMatch = aliasTypedefRegex.exec(header))) {
53
+ const from = aliasMatch[1];
54
+ const to = aliasMatch[2];
55
+ if (typedefMap.has(from) && !typedefMap.has(to)) {
56
+ typedefMap.set(to, typedefMap.get(from));
57
+ }
58
+ }
59
+
60
+ const structValueMap = new Map([
61
+ ["WGPUFuture", "uint64_t"],
62
+ ["WGPUStringView", "ptr"],
63
+ ]);
64
+
65
+ const typeToFFI = (type, isReturn = false) => {
66
+ let t = type
67
+ .replace(/\bconst\b/g, "")
68
+ .replace(/\bstruct\b/g, "")
69
+ .replace(/\bWGPU_NULLABLE\b/g, "")
70
+ .replace(/\bWGPU_NONNULL\b/g, "")
71
+ .replace(/\s+/g, " ")
72
+ .trim();
73
+
74
+ if (t === "void") return "FFIType.void";
75
+
76
+ const isPtr = t.includes("*");
77
+ const base = t.replace(/\*/g, "").trim();
78
+
79
+ if (callbackTypes.has(base)) {
80
+ return isReturn ? "FFIType.ptr" : "FFIType.function";
81
+ }
82
+
83
+ if (structValueMap.has(base)) {
84
+ const mapped = structValueMap.get(base);
85
+ if (mapped === "ptr") return "FFIType.ptr";
86
+ if (mapped === "uint64_t") return "FFIType.u64";
87
+ }
88
+
89
+ if (isPtr) {
90
+ if (base === "char") return "FFIType.cstring";
91
+ return "FFIType.ptr";
92
+ }
93
+
94
+ const scalar = typedefMap.get(base) || base;
95
+ switch (scalar) {
96
+ case "uint32_t":
97
+ return "FFIType.u32";
98
+ case "uint64_t":
99
+ return "FFIType.u64";
100
+ case "int32_t":
101
+ return "FFIType.i32";
102
+ case "int64_t":
103
+ return "FFIType.i64";
104
+ case "size_t":
105
+ case "uintptr_t":
106
+ case "intptr_t":
107
+ return "FFIType.u64";
108
+ case "float":
109
+ return "FFIType.f32";
110
+ case "double":
111
+ return "FFIType.f64";
112
+ case "int":
113
+ return "FFIType.i32";
114
+ case "unsigned int":
115
+ return "FFIType.u32";
116
+ default:
117
+ if (base.startsWith("WGPU")) return "FFIType.ptr";
118
+ if (base === "char") return "FFIType.u8";
119
+ return "FFIType.ptr";
120
+ }
121
+ };
122
+
123
+ const funcRegex = /WGPU_EXPORT\s+([\s\S]*?)\s*\(([^;]*?)\)\s*(?:WGPU_FUNCTION_ATTRIBUTE\s*)?;/g;
124
+ const symbols = [];
125
+ let funcMatch;
126
+ while ((funcMatch = funcRegex.exec(header))) {
127
+ const beforeParen = funcMatch[1].trim().replace(/\s+/g, " ");
128
+ const argsRaw = funcMatch[2].trim();
129
+
130
+ const nameMatch = beforeParen.match(/^(.*)\s+([A-Za-z_][A-Za-z0-9_]*)$/);
131
+ if (!nameMatch) continue;
132
+
133
+ const returnType = nameMatch[1].trim();
134
+ const funcName = nameMatch[2].trim();
135
+
136
+ if (!funcName.startsWith("wgpu")) {
137
+ continue;
138
+ }
139
+
140
+ const args = [];
141
+ if (argsRaw && argsRaw !== "void") {
142
+ const parts = argsRaw.split(",");
143
+ for (const partRaw of parts) {
144
+ const part = partRaw.trim();
145
+ if (!part) continue;
146
+ const paramMatch = part.match(/^(.*?)([A-Za-z_][A-Za-z0-9_]*)$/);
147
+ const typePart = paramMatch ? paramMatch[1].trim() : part;
148
+ args.push(typeToFFI(typePart, false));
149
+ }
150
+ }
151
+
152
+ const returns = typeToFFI(returnType, true);
153
+ const argsList = args.length ? args.join(", ") : "";
154
+ symbols.push(`\t${funcName}: { args: [${argsList}], returns: ${returns} },`);
155
+ }
156
+
157
+ const output = `import { existsSync } from "fs";\nimport { join } from "path";\nimport { dlopen, suffix, FFIType } from "bun:ffi";\n\n// NOTE: WGPUStringView is passed by value in the C API. Bun FFI does not support\n// by-value structs, so WGPUStringView parameters are exposed as pointers for now.\n// If you need these calls, add a small C shim that accepts a pointer and\n// forwards by value. WGPUFuture is a single u64 and is mapped to FFIType.u64.\nconst WGPU_SYMBOLS = {\n${symbols.join("\n")}\n} as const;\n\nconst WGPU_LIB_NAMES: Record<string, string[]> = {\n\tdarwin: ["libwebgpu_dawn.dylib"],\n\twin32: ["webgpu_dawn.dll", "libwebgpu_dawn.dll"],\n\tlinux: ["libwebgpu_dawn.so"],\n};\n\nfunction findWgpuLibraryPath(): string | null {\n\tconst envPath = process.env.ELECTROBUN_WGPU_PATH;\n\tif (envPath && existsSync(envPath)) return envPath;\n\n\tconst names = WGPU_LIB_NAMES[process.platform] ?? ["libwebgpu_dawn." + suffix];\n\tfor (const name of names) {\n\t\tconst cwdCandidate = join(process.cwd(), name);\n\t\tif (existsSync(cwdCandidate)) return cwdCandidate;\n\t\tconst execDir = dirname(process.execPath);\n\t\tconst macCandidate = join(execDir, "..", "MacOS", name);\n\t\tif (existsSync(macCandidate)) return macCandidate;\n\t\tconst resCandidate = join(execDir, "..", "Resources", name);\n\t\tif (existsSync(resCandidate)) return resCandidate;\n\t\tconst execCandidate = join(execDir, name);\n\t\tif (existsSync(execCandidate)) return execCandidate;\n\t}\n\n\treturn null;\n}\n\nexport const native = (() => {\n\tconst libPath = findWgpuLibraryPath();\n\tif (!libPath) {\n\t\treturn {\n\t\t\tavailable: false,\n\t\t\tpath: null as string | null,\n\t\t\tsymbols: {} as Record<string, never>,\n\t\t\tclose: () => {},\n\t\t};\n\t}\n\n\ttry {\n\t\tconst lib = dlopen(libPath, WGPU_SYMBOLS);\n\t\treturn {\n\t\t\tavailable: true,\n\t\t\tpath: libPath,\n\t\t\tsymbols: lib.symbols,\n\t\t\tclose: lib.close,\n\t\t};\n\t} catch {\n\t\treturn {\n\t\t\tavailable: false,\n\t\t\tpath: libPath,\n\t\t\tsymbols: {} as Record<string, never>,\n\t\t\tclose: () => {},\n\t\t};\n\t}\n})();\n\nconst WGPU = {\n\tnative,\n};\n\nexport default WGPU;\n`;
158
+
159
+ const outPath = resolve(root, "src/bun/webGPU.ts");
160
+ writeFileSync(outPath, output, "utf8");
161
+
162
+ console.log(`Generated ${outPath}`);
@@ -0,0 +1,80 @@
1
+ Param(
2
+ [switch]$Quiet
3
+ )
4
+
5
+ function Ensure-RunningAsAdmin {
6
+ $current = [Security.Principal.WindowsIdentity]::GetCurrent()
7
+ $principal = New-Object Security.Principal.WindowsPrincipal($current)
8
+ if (-not $principal.IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)) {
9
+ Write-Host "Requesting elevation..."
10
+ $psi = New-Object System.Diagnostics.ProcessStartInfo
11
+ $psi.FileName = 'powershell'
12
+ $args = @('-ExecutionPolicy','Bypass','-NoProfile','-File', $MyInvocation.MyCommand.Path)
13
+ if ($Quiet) { $args += '-Quiet' }
14
+ $psi.Arguments = $args -join ' '
15
+ $psi.Verb = 'runas'
16
+ try {
17
+ [System.Diagnostics.Process]::Start($psi) | Out-Null
18
+ exit
19
+ } catch {
20
+ Write-Error "Elevation requested but was cancelled. Please run this script as Administrator."
21
+ exit 1
22
+ }
23
+ }
24
+ }
25
+
26
+ function Download-File($url, $outPath) {
27
+ Write-Host "Downloading $url to $outPath"
28
+ try {
29
+ Invoke-WebRequest -Uri $url -OutFile $outPath -UseBasicParsing -ErrorAction Stop
30
+ return $true
31
+ } catch {
32
+ Write-Error "Download failed: $_"
33
+ return $false
34
+ }
35
+ }
36
+
37
+ Ensure-RunningAsAdmin
38
+
39
+ $temp = [IO.Path]::GetTempPath()
40
+ $vsInstaller = Join-Path $temp 'vs_BuildTools.exe'
41
+ $vsUrl = 'https://aka.ms/vs/17/release/vs_BuildTools.exe'
42
+
43
+ if (-not (Test-Path $vsInstaller)) {
44
+ if (-not (Download-File $vsUrl $vsInstaller)) {
45
+ Write-Error "Failed to download Visual Studio Build Tools. Please download manually from https://visualstudio.microsoft.com/downloads/"
46
+ exit 1
47
+ }
48
+ }
49
+
50
+ if ($Quiet) {
51
+ Write-Host "Installing Visual Studio Build Tools (quiet). This may take some time..."
52
+ $args = @(
53
+ '--add', 'Microsoft.VisualStudio.Workload.VCTools',
54
+ '--add', 'Microsoft.VisualStudio.Component.VC.Tools.x86.x64',
55
+ '--add', 'Microsoft.VisualStudio.Component.Windows10SDK.19041',
56
+ '--includeRecommended',
57
+ '--quiet',
58
+ '--wait',
59
+ '--norestart'
60
+ )
61
+ & $vsInstaller $args
62
+ } else {
63
+ Write-Host "Launching Visual Studio Build Tools installer GUI. Please select 'Desktop development with C++' and install."
64
+ Start-Process -FilePath $vsInstaller -Wait
65
+ }
66
+
67
+ # Install CMake via winget if available
68
+ if (Get-Command winget -ErrorAction SilentlyContinue) {
69
+ Write-Host "Installing CMake via winget..."
70
+ try {
71
+ winget install --id Kitware.CMake -e --accept-package-agreements --accept-source-agreements
72
+ } catch {
73
+ Write-Warning "winget install failed or was interrupted. Please install CMake from https://cmake.org/download/"
74
+ }
75
+ } else {
76
+ Write-Host "winget not found. Please install CMake manually from https://cmake.org/download/ or install winget and re-run this script."
77
+ }
78
+
79
+ Write-Host "Installation steps finished. Please restart your shell (or log out/in) and re-run the build script if necessary."
80
+ Exit 0