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.
- package/bin/sparkbun.cjs +18 -0
- package/dist-linux-arm64/bsdiff +0 -0
- package/dist-linux-arm64/bspatch +0 -0
- package/dist-linux-arm64/libElectrobunCore.so +0 -0
- package/dist-linux-arm64/libNativeWrapper.so +0 -0
- package/dist-linux-arm64/libasar.so +0 -0
- package/dist-linux-x64/bsdiff +0 -0
- package/dist-linux-x64/bspatch +0 -0
- package/dist-linux-x64/libElectrobunCore.so +0 -0
- package/dist-linux-x64/libNativeWrapper.so +0 -0
- package/dist-linux-x64/libasar.so +0 -0
- package/dist-macos-arm64/bsdiff +0 -0
- package/dist-macos-arm64/bspatch +0 -0
- package/dist-macos-arm64/libElectrobunCore.dylib +0 -0
- package/dist-macos-arm64/libNativeWrapper.dylib +0 -0
- package/dist-macos-arm64/libasar.dylib +0 -0
- package/dist-macos-arm64/libwebgpu_dawn.dylib +0 -0
- package/dist-macos-arm64/preload-full.js +885 -0
- package/dist-macos-arm64/preload-sandboxed.js +111 -0
- package/dist-macos-arm64/process_helper +0 -0
- package/dist-win-x64/ElectrobunCore.dll +0 -0
- package/dist-win-x64/WebView2Loader.dll +0 -0
- package/dist-win-x64/bsdiff.exe +0 -0
- package/dist-win-x64/bspatch.exe +0 -0
- package/dist-win-x64/libNativeWrapper.dll +0 -0
- package/dist-win-x64/zig-asar/arm64/libasar.dll +0 -0
- package/dist-win-x64/zig-asar/x64/libasar.dll +0 -0
- package/package.json +47 -0
- package/scripts/build-and-upload-artifacts.js +207 -0
- package/scripts/gen-webgpu-ffi.mjs +162 -0
- package/scripts/install-windows-deps.ps1 +80 -0
- package/scripts/package-release.js +237 -0
- package/scripts/push-version.js +84 -0
- package/scripts/update-bun-version.ts +122 -0
- package/scripts/update-cef-version.ts +145 -0
- package/src/browser/builtinrpcSchema.ts +19 -0
- package/src/browser/global.d.ts +36 -0
- package/src/browser/index.ts +234 -0
- package/src/browser/webviewtag.ts +88 -0
- package/src/browser/wgputag.ts +48 -0
- package/src/bun/SparkBunConfig.ts +497 -0
- package/src/bun/__tests__/ffi-contract.test.ts +105 -0
- package/src/bun/core/ApplicationMenu.ts +70 -0
- package/src/bun/core/BrowserView.ts +416 -0
- package/src/bun/core/BrowserWindow.ts +396 -0
- package/src/bun/core/BuildConfig.ts +71 -0
- package/src/bun/core/ContextMenu.ts +75 -0
- package/src/bun/core/GpuWindow.ts +289 -0
- package/src/bun/core/Paths.ts +5 -0
- package/src/bun/core/Socket.ts +22 -0
- package/src/bun/core/Tray.ts +197 -0
- package/src/bun/core/Updater.ts +1131 -0
- package/src/bun/core/Utils.ts +487 -0
- package/src/bun/core/WGPUView.ts +167 -0
- package/src/bun/core/menuRoles.ts +181 -0
- package/src/bun/events/ApplicationEvents.ts +22 -0
- package/src/bun/events/event.ts +27 -0
- package/src/bun/events/eventEmitter.ts +45 -0
- package/src/bun/events/trayEvents.ts +11 -0
- package/src/bun/events/webviewEvents.ts +39 -0
- package/src/bun/events/windowEvents.ts +23 -0
- package/src/bun/index.ts +120 -0
- package/src/bun/preload/.generated/compiled.ts +2 -0
- package/src/bun/preload/build.ts +65 -0
- package/src/bun/preload/dragRegions.ts +41 -0
- package/src/bun/preload/encryption.ts +86 -0
- package/src/bun/preload/events.ts +171 -0
- package/src/bun/preload/globals.d.ts +45 -0
- package/src/bun/preload/index-sandboxed.ts +28 -0
- package/src/bun/preload/index.ts +77 -0
- package/src/bun/preload/internalRpc.ts +80 -0
- package/src/bun/preload/overlaySync.ts +107 -0
- package/src/bun/preload/webviewTag.ts +451 -0
- package/src/bun/preload/wgpuTag.ts +246 -0
- package/src/bun/proc/linux.md +43 -0
- package/src/bun/proc/native.ts +3253 -0
- package/src/bun/webGPU.ts +346 -0
- package/src/bun/webgpuAdapter.ts +3011 -0
- package/src/cli/bun.lockb +0 -0
- package/src/cli/index.ts +4653 -0
- package/src/cli/package-lock.json +81 -0
- package/src/cli/package.json +11 -0
- package/src/cli/templates/embedded.ts +2 -0
- package/src/core/build.zig +16 -0
- package/src/core/main.zig +3378 -0
- package/src/extractor/build.zig +22 -0
- package/src/installer/installer-template.ts +216 -0
- package/src/launcher/main.ts +221 -0
- package/src/native/build/libNativeWrapper.so +0 -0
- package/src/native/linux/build/nativeWrapper.o +0 -0
- package/src/native/linux/cef_loader.cpp +110 -0
- package/src/native/linux/cef_loader.h +28 -0
- package/src/native/linux/cef_process_helper_linux.cpp +160 -0
- package/src/native/linux/nativeWrapper.cpp +11768 -0
- package/src/native/macos/cef_process_helper_mac.cc +160 -0
- package/src/native/macos/nativeWrapper.mm +9172 -0
- package/src/native/shared/accelerator_parser.h +72 -0
- package/src/native/shared/app_paths.h +110 -0
- package/src/native/shared/asar.h +35 -0
- package/src/native/shared/cache_migration.h +244 -0
- package/src/native/shared/callbacks.h +57 -0
- package/src/native/shared/cef_response_filter.h +189 -0
- package/src/native/shared/chromium_flags.h +181 -0
- package/src/native/shared/config.h +66 -0
- package/src/native/shared/download_event.h +197 -0
- package/src/native/shared/ffi_helpers.h +139 -0
- package/src/native/shared/glob_match.h +59 -0
- package/src/native/shared/json_menu_parser.h +223 -0
- package/src/native/shared/mime_types.h +101 -0
- package/src/native/shared/navigation_rules.h +98 -0
- package/src/native/shared/partition_context.h +137 -0
- package/src/native/shared/pending_resize_queue.h +45 -0
- package/src/native/shared/permissions.h +118 -0
- package/src/native/shared/permissions_cef.h +74 -0
- package/src/native/shared/preload_script.h +71 -0
- package/src/native/shared/shutdown_guard.h +134 -0
- package/src/native/shared/thread_safe_map.h +138 -0
- package/src/native/shared/webview_storage.h +91 -0
- package/src/native/win/cef_process_helper_win.cpp +143 -0
- package/src/native/win/dcomp_compositor.h +352 -0
- package/src/native/win/nativeWrapper.cpp +12434 -0
- package/src/npmbin/index.js +34 -0
- package/src/shared/bun-version.ts +3 -0
- package/src/shared/cef-version.ts +5 -0
- package/src/shared/naming.test.ts +327 -0
- package/src/shared/naming.ts +188 -0
- package/src/shared/platform.ts +48 -0
- package/src/shared/rpc.ts +541 -0
- package/src/shared/sparkbun-version.ts +2 -0
- package/src/types/three.d.ts +1 -0
- 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
|
|
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
|