@rubriclab/bunl 0.1.25 → 0.2.1
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/.env.example +2 -1
- package/.github/workflows/publish.yml +16 -0
- package/.vscode/settings.json +16 -0
- package/CHANGELOG.md +3 -0
- package/Dockerfile +6 -8
- package/LICENSE +1 -1
- package/README.md +15 -67
- package/biome.json +3 -0
- package/build/client.js +130 -468
- package/bun.lock +169 -0
- package/client.ts +132 -85
- package/demo.tsx +127 -0
- package/docker-compose.yml +36 -0
- package/e2e.test.ts +187 -0
- package/package.json +52 -44
- package/server.ts +160 -85
- package/tsconfig.json +3 -3
- package/types.ts +23 -9
- package/utils.ts +149 -6
- package/bun.lockb +0 -0
- package/demo.ts +0 -18
- package/fly.toml +0 -19
- package/index.ts +0 -67
package/build/client.js
CHANGED
|
@@ -1,508 +1,170 @@
|
|
|
1
1
|
#! /usr/bin/env bun
|
|
2
2
|
// @bun
|
|
3
3
|
// client.ts
|
|
4
|
-
import {parseArgs} from "util";
|
|
4
|
+
import { parseArgs } from "util";
|
|
5
5
|
|
|
6
|
-
//
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
// node_modules/is-wsl/index.js
|
|
15
|
-
import process2 from "process";
|
|
16
|
-
import os from "os";
|
|
17
|
-
import fs3 from "fs";
|
|
18
|
-
|
|
19
|
-
// node_modules/is-inside-container/index.js
|
|
20
|
-
import fs2 from "fs";
|
|
21
|
-
|
|
22
|
-
// node_modules/is-docker/index.js
|
|
23
|
-
import fs from "fs";
|
|
24
|
-
function hasDockerEnv() {
|
|
25
|
-
try {
|
|
26
|
-
fs.statSync("/.dockerenv");
|
|
27
|
-
return true;
|
|
28
|
-
} catch {
|
|
29
|
-
return false;
|
|
30
|
-
}
|
|
31
|
-
}
|
|
32
|
-
function hasDockerCGroup() {
|
|
33
|
-
try {
|
|
34
|
-
return fs.readFileSync("/proc/self/cgroup", "utf8").includes("docker");
|
|
35
|
-
} catch {
|
|
36
|
-
return false;
|
|
37
|
-
}
|
|
38
|
-
}
|
|
39
|
-
var isDockerCached;
|
|
40
|
-
function isDocker() {
|
|
41
|
-
if (isDockerCached === undefined) {
|
|
42
|
-
isDockerCached = hasDockerEnv() || hasDockerCGroup();
|
|
43
|
-
}
|
|
44
|
-
return isDockerCached;
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
// node_modules/is-inside-container/index.js
|
|
48
|
-
var cachedResult;
|
|
49
|
-
var hasContainerEnv = () => {
|
|
50
|
-
try {
|
|
51
|
-
fs2.statSync("/run/.containerenv");
|
|
52
|
-
return true;
|
|
53
|
-
} catch {
|
|
54
|
-
return false;
|
|
55
|
-
}
|
|
56
|
-
};
|
|
57
|
-
function isInsideContainer() {
|
|
58
|
-
if (cachedResult === undefined) {
|
|
59
|
-
cachedResult = hasContainerEnv() || isDocker();
|
|
60
|
-
}
|
|
61
|
-
return cachedResult;
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
// node_modules/is-wsl/index.js
|
|
65
|
-
var isWsl = () => {
|
|
66
|
-
if (process2.platform !== "linux") {
|
|
67
|
-
return false;
|
|
68
|
-
}
|
|
69
|
-
if (os.release().toLowerCase().includes("microsoft")) {
|
|
70
|
-
if (isInsideContainer()) {
|
|
71
|
-
return false;
|
|
72
|
-
}
|
|
73
|
-
return true;
|
|
74
|
-
}
|
|
75
|
-
try {
|
|
76
|
-
return fs3.readFileSync("/proc/version", "utf8").toLowerCase().includes("microsoft") ? !isInsideContainer() : false;
|
|
77
|
-
} catch {
|
|
78
|
-
return false;
|
|
79
|
-
}
|
|
80
|
-
};
|
|
81
|
-
var is_wsl_default = process2.env.__IS_WSL_TEST__ ? isWsl : isWsl();
|
|
82
|
-
|
|
83
|
-
// node_modules/define-lazy-prop/index.js
|
|
84
|
-
function defineLazyProperty(object, propertyName, valueGetter) {
|
|
85
|
-
const define = (value) => Object.defineProperty(object, propertyName, { value, enumerable: true, writable: true });
|
|
86
|
-
Object.defineProperty(object, propertyName, {
|
|
87
|
-
configurable: true,
|
|
88
|
-
enumerable: true,
|
|
89
|
-
get() {
|
|
90
|
-
const result = valueGetter();
|
|
91
|
-
define(result);
|
|
92
|
-
return result;
|
|
93
|
-
},
|
|
94
|
-
set(value) {
|
|
95
|
-
define(value);
|
|
96
|
-
}
|
|
97
|
-
});
|
|
98
|
-
return object;
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
// node_modules/default-browser/index.js
|
|
102
|
-
import {promisify as promisify4} from "util";
|
|
103
|
-
import process5 from "process";
|
|
104
|
-
import {execFile as execFile4} from "child_process";
|
|
105
|
-
|
|
106
|
-
// node_modules/default-browser-id/index.js
|
|
107
|
-
import {promisify} from "util";
|
|
108
|
-
import process3 from "process";
|
|
109
|
-
import {execFile} from "child_process";
|
|
110
|
-
var execFileAsync = promisify(execFile);
|
|
111
|
-
async function defaultBrowserId() {
|
|
112
|
-
if (process3.platform !== "darwin") {
|
|
113
|
-
throw new Error("macOS only");
|
|
114
|
-
}
|
|
115
|
-
const { stdout } = await execFileAsync("defaults", ["read", "com.apple.LaunchServices/com.apple.launchservices.secure", "LSHandlers"]);
|
|
116
|
-
const match = /LSHandlerRoleAll = "(?!-)(?<id>[^"]+?)";\s+?LSHandlerURLScheme = (?:http|https);/.exec(stdout);
|
|
117
|
-
return match?.groups.id ?? "com.apple.Safari";
|
|
6
|
+
// utils.ts
|
|
7
|
+
function openBrowser(url) {
|
|
8
|
+
const cmds = {
|
|
9
|
+
darwin: ["open", url],
|
|
10
|
+
win32: ["cmd", "/c", "start", url]
|
|
11
|
+
};
|
|
12
|
+
const args = cmds[process.platform] ?? ["xdg-open", url];
|
|
13
|
+
Bun.spawn(args, { stdio: ["ignore", "ignore", "ignore"] });
|
|
118
14
|
}
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
import process4 from "process";
|
|
122
|
-
import {promisify as promisify2} from "util";
|
|
123
|
-
import {execFile as execFile2, execFileSync} from "child_process";
|
|
124
|
-
async function runAppleScript(script, { humanReadableOutput = true } = {}) {
|
|
125
|
-
if (process4.platform !== "darwin") {
|
|
126
|
-
throw new Error("macOS only");
|
|
127
|
-
}
|
|
128
|
-
const outputArguments = humanReadableOutput ? [] : ["-ss"];
|
|
129
|
-
const { stdout } = await execFileAsync2("osascript", ["-e", script, outputArguments]);
|
|
130
|
-
return stdout.trim();
|
|
15
|
+
function toBase64(buf) {
|
|
16
|
+
return Buffer.from(buf).toString("base64");
|
|
131
17
|
}
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
async function bundleName(bundleId) {
|
|
136
|
-
return runAppleScript(`tell application "Finder" to set app_path to application file id "${bundleId}" as string
|
|
137
|
-
tell application "System Events" to get value of property list item "CFBundleName" of property list file (app_path & ":Contents:Info.plist")`);
|
|
18
|
+
function fromBase64(str) {
|
|
19
|
+
const buf = Buffer.from(str, "base64");
|
|
20
|
+
return new Uint8Array(buf.buffer, buf.byteOffset, buf.byteLength);
|
|
138
21
|
}
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
22
|
+
function page(title, body) {
|
|
23
|
+
return `<!DOCTYPE html>
|
|
24
|
+
<html lang="en">
|
|
25
|
+
<head>
|
|
26
|
+
<meta charset="utf-8">
|
|
27
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
28
|
+
<title>${title} \u2014 bunl</title>
|
|
29
|
+
<style>
|
|
30
|
+
* { margin: 0; padding: 0; box-sizing: border-box; }
|
|
31
|
+
body {
|
|
32
|
+
font-family: system-ui, -apple-system, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
|
|
33
|
+
background: #fff;
|
|
34
|
+
color: #000;
|
|
35
|
+
min-height: 100vh;
|
|
36
|
+
display: flex;
|
|
37
|
+
align-items: center;
|
|
38
|
+
justify-content: center;
|
|
39
|
+
padding: 2rem;
|
|
157
40
|
}
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
if (!browser) {
|
|
172
|
-
throw new UnknownBrowserError(`Unknown browser ID: ${id}`);
|
|
173
|
-
}
|
|
174
|
-
return browser;
|
|
41
|
+
main { max-width: 480px; width: 100%; }
|
|
42
|
+
h1 { font-size: 1.5rem; font-weight: 600; letter-spacing: -0.02em; margin-bottom: 1rem; }
|
|
43
|
+
p { color: #666; line-height: 1.6; margin-bottom: 0.75rem; }
|
|
44
|
+
code { background: #f5f5f5; padding: 0.15em 0.4em; border-radius: 3px; font-size: 0.9em; }
|
|
45
|
+
a { color: #000; }
|
|
46
|
+
</style>
|
|
47
|
+
</head>
|
|
48
|
+
<body>
|
|
49
|
+
<main>
|
|
50
|
+
${body}
|
|
51
|
+
</main>
|
|
52
|
+
</body>
|
|
53
|
+
</html>`;
|
|
175
54
|
}
|
|
176
55
|
|
|
177
|
-
// node_modules/default-browser/index.js
|
|
178
|
-
var execFileAsync4 = promisify4(execFile4);
|
|
179
|
-
var titleize = (string) => string.toLowerCase().replaceAll(/(?:^|\s|-)\S/g, (x) => x.toUpperCase());
|
|
180
|
-
async function defaultBrowser2() {
|
|
181
|
-
if (process5.platform === "darwin") {
|
|
182
|
-
const id = await defaultBrowserId();
|
|
183
|
-
const name = await bundleName(id);
|
|
184
|
-
return { name, id };
|
|
185
|
-
}
|
|
186
|
-
if (process5.platform === "linux") {
|
|
187
|
-
const { stdout } = await execFileAsync4("xdg-mime", ["query", "default", "x-scheme-handler/http"]);
|
|
188
|
-
const id = stdout.trim();
|
|
189
|
-
const name = titleize(id.replace(/.desktop$/, "").replace("-", " "));
|
|
190
|
-
return { name, id };
|
|
191
|
-
}
|
|
192
|
-
if (process5.platform === "win32") {
|
|
193
|
-
return defaultBrowser();
|
|
194
|
-
}
|
|
195
|
-
throw new Error("Only macOS, Linux, and Windows are supported");
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
// node_modules/open/index.js
|
|
199
|
-
function detectArchBinary(binary) {
|
|
200
|
-
if (typeof binary === "string" || Array.isArray(binary)) {
|
|
201
|
-
return binary;
|
|
202
|
-
}
|
|
203
|
-
const { [arch]: archBinary } = binary;
|
|
204
|
-
if (!archBinary) {
|
|
205
|
-
throw new Error(`${arch} is not supported`);
|
|
206
|
-
}
|
|
207
|
-
return archBinary;
|
|
208
|
-
}
|
|
209
|
-
function detectPlatformBinary({ [platform]: platformBinary }, { wsl }) {
|
|
210
|
-
if (wsl && is_wsl_default) {
|
|
211
|
-
return detectArchBinary(wsl);
|
|
212
|
-
}
|
|
213
|
-
if (!platformBinary) {
|
|
214
|
-
throw new Error(`${platform} is not supported`);
|
|
215
|
-
}
|
|
216
|
-
return detectArchBinary(platformBinary);
|
|
217
|
-
}
|
|
218
|
-
var __dirname2 = path.dirname(fileURLToPath(import.meta.url));
|
|
219
|
-
var localXdgOpenPath = path.join(__dirname2, "xdg-open");
|
|
220
|
-
var { platform, arch } = process6;
|
|
221
|
-
var getWslDrivesMountPoint = (() => {
|
|
222
|
-
const defaultMountPoint = "/mnt/";
|
|
223
|
-
let mountPoint;
|
|
224
|
-
return async function() {
|
|
225
|
-
if (mountPoint) {
|
|
226
|
-
return mountPoint;
|
|
227
|
-
}
|
|
228
|
-
const configFilePath = "/etc/wsl.conf";
|
|
229
|
-
let isConfigFileExists = false;
|
|
230
|
-
try {
|
|
231
|
-
await fs4.access(configFilePath, fsConstants.F_OK);
|
|
232
|
-
isConfigFileExists = true;
|
|
233
|
-
} catch {
|
|
234
|
-
}
|
|
235
|
-
if (!isConfigFileExists) {
|
|
236
|
-
return defaultMountPoint;
|
|
237
|
-
}
|
|
238
|
-
const configContent = await fs4.readFile(configFilePath, { encoding: "utf8" });
|
|
239
|
-
const configMountPoint = /(?<!#.*)root\s*=\s*(?<mountPoint>.*)/g.exec(configContent);
|
|
240
|
-
if (!configMountPoint) {
|
|
241
|
-
return defaultMountPoint;
|
|
242
|
-
}
|
|
243
|
-
mountPoint = configMountPoint.groups.mountPoint.trim();
|
|
244
|
-
mountPoint = mountPoint.endsWith("/") ? mountPoint : `${mountPoint}/`;
|
|
245
|
-
return mountPoint;
|
|
246
|
-
};
|
|
247
|
-
})();
|
|
248
|
-
var pTryEach = async (array, mapper) => {
|
|
249
|
-
let latestError;
|
|
250
|
-
for (const item of array) {
|
|
251
|
-
try {
|
|
252
|
-
return await mapper(item);
|
|
253
|
-
} catch (error) {
|
|
254
|
-
latestError = error;
|
|
255
|
-
}
|
|
256
|
-
}
|
|
257
|
-
throw latestError;
|
|
258
|
-
};
|
|
259
|
-
var baseOpen = async (options) => {
|
|
260
|
-
options = {
|
|
261
|
-
wait: false,
|
|
262
|
-
background: false,
|
|
263
|
-
newInstance: false,
|
|
264
|
-
allowNonzeroExitCode: false,
|
|
265
|
-
...options
|
|
266
|
-
};
|
|
267
|
-
if (Array.isArray(options.app)) {
|
|
268
|
-
return pTryEach(options.app, (singleApp) => baseOpen({
|
|
269
|
-
...options,
|
|
270
|
-
app: singleApp
|
|
271
|
-
}));
|
|
272
|
-
}
|
|
273
|
-
let { name: app, arguments: appArguments = [] } = options.app ?? {};
|
|
274
|
-
appArguments = [...appArguments];
|
|
275
|
-
if (Array.isArray(app)) {
|
|
276
|
-
return pTryEach(app, (appName) => baseOpen({
|
|
277
|
-
...options,
|
|
278
|
-
app: {
|
|
279
|
-
name: appName,
|
|
280
|
-
arguments: appArguments
|
|
281
|
-
}
|
|
282
|
-
}));
|
|
283
|
-
}
|
|
284
|
-
if (app === "browser" || app === "browserPrivate") {
|
|
285
|
-
const ids = {
|
|
286
|
-
"com.google.chrome": "chrome",
|
|
287
|
-
"google-chrome.desktop": "chrome",
|
|
288
|
-
"org.mozilla.firefox": "firefox",
|
|
289
|
-
"firefox.desktop": "firefox",
|
|
290
|
-
"com.microsoft.msedge": "edge",
|
|
291
|
-
"com.microsoft.edge": "edge",
|
|
292
|
-
"microsoft-edge.desktop": "edge"
|
|
293
|
-
};
|
|
294
|
-
const flags = {
|
|
295
|
-
chrome: "--incognito",
|
|
296
|
-
firefox: "--private-window",
|
|
297
|
-
edge: "--inPrivate"
|
|
298
|
-
};
|
|
299
|
-
const browser = await defaultBrowser2();
|
|
300
|
-
if (browser.id in ids) {
|
|
301
|
-
const browserName = ids[browser.id];
|
|
302
|
-
if (app === "browserPrivate") {
|
|
303
|
-
appArguments.push(flags[browserName]);
|
|
304
|
-
}
|
|
305
|
-
return baseOpen({
|
|
306
|
-
...options,
|
|
307
|
-
app: {
|
|
308
|
-
name: apps[browserName],
|
|
309
|
-
arguments: appArguments
|
|
310
|
-
}
|
|
311
|
-
});
|
|
312
|
-
}
|
|
313
|
-
throw new Error(`${browser.name} is not supported as a default browser`);
|
|
314
|
-
}
|
|
315
|
-
let command;
|
|
316
|
-
const cliArguments = [];
|
|
317
|
-
const childProcessOptions = {};
|
|
318
|
-
if (platform === "darwin") {
|
|
319
|
-
command = "open";
|
|
320
|
-
if (options.wait) {
|
|
321
|
-
cliArguments.push("--wait-apps");
|
|
322
|
-
}
|
|
323
|
-
if (options.background) {
|
|
324
|
-
cliArguments.push("--background");
|
|
325
|
-
}
|
|
326
|
-
if (options.newInstance) {
|
|
327
|
-
cliArguments.push("--new");
|
|
328
|
-
}
|
|
329
|
-
if (app) {
|
|
330
|
-
cliArguments.push("-a", app);
|
|
331
|
-
}
|
|
332
|
-
} else if (platform === "win32" || is_wsl_default && !isInsideContainer() && !app) {
|
|
333
|
-
const mountPoint = await getWslDrivesMountPoint();
|
|
334
|
-
command = is_wsl_default ? `${mountPoint}c/Windows/System32/WindowsPowerShell/v1.0/powershell.exe` : `${process6.env.SYSTEMROOT || process6.env.windir || "C:\Windows"}\System32\WindowsPowerShell\v1.0\powershell`;
|
|
335
|
-
cliArguments.push("-NoProfile", "-NonInteractive", "-ExecutionPolicy", "Bypass", "-EncodedCommand");
|
|
336
|
-
if (!is_wsl_default) {
|
|
337
|
-
childProcessOptions.windowsVerbatimArguments = true;
|
|
338
|
-
}
|
|
339
|
-
const encodedArguments = ["Start"];
|
|
340
|
-
if (options.wait) {
|
|
341
|
-
encodedArguments.push("-Wait");
|
|
342
|
-
}
|
|
343
|
-
if (app) {
|
|
344
|
-
encodedArguments.push(`"\`"${app}\`""`);
|
|
345
|
-
if (options.target) {
|
|
346
|
-
appArguments.push(options.target);
|
|
347
|
-
}
|
|
348
|
-
} else if (options.target) {
|
|
349
|
-
encodedArguments.push(`"${options.target}"`);
|
|
350
|
-
}
|
|
351
|
-
if (appArguments.length > 0) {
|
|
352
|
-
appArguments = appArguments.map((argument) => `"\`"${argument}\`""`);
|
|
353
|
-
encodedArguments.push("-ArgumentList", appArguments.join(","));
|
|
354
|
-
}
|
|
355
|
-
options.target = Buffer.from(encodedArguments.join(" "), "utf16le").toString("base64");
|
|
356
|
-
} else {
|
|
357
|
-
if (app) {
|
|
358
|
-
command = app;
|
|
359
|
-
} else {
|
|
360
|
-
const isBundled = !__dirname2 || __dirname2 === "/";
|
|
361
|
-
let exeLocalXdgOpen = false;
|
|
362
|
-
try {
|
|
363
|
-
await fs4.access(localXdgOpenPath, fsConstants.X_OK);
|
|
364
|
-
exeLocalXdgOpen = true;
|
|
365
|
-
} catch {
|
|
366
|
-
}
|
|
367
|
-
const useSystemXdgOpen = process6.versions.electron ?? (platform === "android" || isBundled || !exeLocalXdgOpen);
|
|
368
|
-
command = useSystemXdgOpen ? "xdg-open" : localXdgOpenPath;
|
|
369
|
-
}
|
|
370
|
-
if (appArguments.length > 0) {
|
|
371
|
-
cliArguments.push(...appArguments);
|
|
372
|
-
}
|
|
373
|
-
if (!options.wait) {
|
|
374
|
-
childProcessOptions.stdio = "ignore";
|
|
375
|
-
childProcessOptions.detached = true;
|
|
376
|
-
}
|
|
377
|
-
}
|
|
378
|
-
if (platform === "darwin" && appArguments.length > 0) {
|
|
379
|
-
cliArguments.push("--args", ...appArguments);
|
|
380
|
-
}
|
|
381
|
-
if (options.target) {
|
|
382
|
-
cliArguments.push(options.target);
|
|
383
|
-
}
|
|
384
|
-
const subprocess = childProcess.spawn(command, cliArguments, childProcessOptions);
|
|
385
|
-
if (options.wait) {
|
|
386
|
-
return new Promise((resolve, reject) => {
|
|
387
|
-
subprocess.once("error", reject);
|
|
388
|
-
subprocess.once("close", (exitCode) => {
|
|
389
|
-
if (!options.allowNonzeroExitCode && exitCode > 0) {
|
|
390
|
-
reject(new Error(`Exited with code ${exitCode}`));
|
|
391
|
-
return;
|
|
392
|
-
}
|
|
393
|
-
resolve(subprocess);
|
|
394
|
-
});
|
|
395
|
-
});
|
|
396
|
-
}
|
|
397
|
-
subprocess.unref();
|
|
398
|
-
return subprocess;
|
|
399
|
-
};
|
|
400
|
-
var open = (target, options) => {
|
|
401
|
-
if (typeof target !== "string") {
|
|
402
|
-
throw new TypeError("Expected a `target`");
|
|
403
|
-
}
|
|
404
|
-
return baseOpen({
|
|
405
|
-
...options,
|
|
406
|
-
target
|
|
407
|
-
});
|
|
408
|
-
};
|
|
409
|
-
var apps = {};
|
|
410
|
-
defineLazyProperty(apps, "chrome", () => detectPlatformBinary({
|
|
411
|
-
darwin: "google chrome",
|
|
412
|
-
win32: "chrome",
|
|
413
|
-
linux: ["google-chrome", "google-chrome-stable", "chromium"]
|
|
414
|
-
}, {
|
|
415
|
-
wsl: {
|
|
416
|
-
ia32: "/mnt/c/Program Files (x86)/Google/Chrome/Application/chrome.exe",
|
|
417
|
-
x64: ["/mnt/c/Program Files/Google/Chrome/Application/chrome.exe", "/mnt/c/Program Files (x86)/Google/Chrome/Application/chrome.exe"]
|
|
418
|
-
}
|
|
419
|
-
}));
|
|
420
|
-
defineLazyProperty(apps, "firefox", () => detectPlatformBinary({
|
|
421
|
-
darwin: "firefox",
|
|
422
|
-
win32: "C:\Program Files\Mozilla Firefox\firefox.exe",
|
|
423
|
-
linux: "firefox"
|
|
424
|
-
}, {
|
|
425
|
-
wsl: "/mnt/c/Program Files/Mozilla Firefox/firefox.exe"
|
|
426
|
-
}));
|
|
427
|
-
defineLazyProperty(apps, "edge", () => detectPlatformBinary({
|
|
428
|
-
darwin: "microsoft edge",
|
|
429
|
-
win32: "msedge",
|
|
430
|
-
linux: ["microsoft-edge", "microsoft-edge-dev"]
|
|
431
|
-
}, {
|
|
432
|
-
wsl: "/mnt/c/Program Files (x86)/Microsoft/Edge/Application/msedge.exe"
|
|
433
|
-
}));
|
|
434
|
-
defineLazyProperty(apps, "browser", () => "browser");
|
|
435
|
-
defineLazyProperty(apps, "browserPrivate", () => "browserPrivate");
|
|
436
|
-
var open_default = open;
|
|
437
|
-
|
|
438
56
|
// client.ts
|
|
57
|
+
function badGatewayHtml(port) {
|
|
58
|
+
return page("Bad Gateway", `<h1>Bad Gateway</h1>
|
|
59
|
+
<p>Could not reach <code>localhost:${port}</code>.</p>
|
|
60
|
+
<p>Make sure your local server is running.</p>`);
|
|
61
|
+
}
|
|
439
62
|
async function main({
|
|
440
63
|
port,
|
|
441
64
|
domain,
|
|
442
65
|
subdomain,
|
|
443
|
-
open
|
|
66
|
+
open
|
|
444
67
|
}) {
|
|
445
68
|
const params = new URLSearchParams({
|
|
446
69
|
new: "",
|
|
447
70
|
...subdomain ? { subdomain } : {}
|
|
448
71
|
}).toString();
|
|
449
|
-
const
|
|
72
|
+
const isLocal = /^(localhost|127\.|0\.0\.0\.0|\[::1\])/.test(domain || "");
|
|
73
|
+
const wsScheme = isLocal ? "ws" : "wss";
|
|
74
|
+
const serverUrl = `${wsScheme}://${domain}?${params}`;
|
|
450
75
|
const socket = new WebSocket(serverUrl);
|
|
451
|
-
const
|
|
76
|
+
const localHost = Bun.env.HOST || "localhost";
|
|
77
|
+
const localOrigin = `http://${localHost}:${port}`;
|
|
452
78
|
socket.addEventListener("message", async (event) => {
|
|
453
79
|
const data = JSON.parse(event.data);
|
|
454
|
-
if (data.
|
|
80
|
+
if (data.type === "init") {
|
|
81
|
+
const init = data;
|
|
455
82
|
console.log(`
|
|
456
|
-
\u21AA Your URL:
|
|
83
|
+
\u21AA Your URL: \x1B[32m${init.url}\x1B[0m
|
|
457
84
|
`);
|
|
458
|
-
if (
|
|
459
|
-
|
|
85
|
+
if (open)
|
|
86
|
+
openBrowser(init.url);
|
|
87
|
+
return;
|
|
460
88
|
}
|
|
461
|
-
|
|
462
|
-
|
|
89
|
+
if (data.type === "request") {
|
|
90
|
+
const req = data;
|
|
463
91
|
const now = performance.now();
|
|
464
|
-
|
|
465
|
-
method
|
|
466
|
-
headers
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
status
|
|
477
|
-
|
|
478
|
-
headers
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
92
|
+
try {
|
|
93
|
+
const reqBody = req.body && req.method !== "GET" && req.method !== "HEAD" ? fromBase64(req.body) : null;
|
|
94
|
+
const fwdHeaders = { ...req.headers };
|
|
95
|
+
delete fwdHeaders.host;
|
|
96
|
+
delete fwdHeaders.connection;
|
|
97
|
+
delete fwdHeaders["transfer-encoding"];
|
|
98
|
+
const res = await fetch(`${localOrigin}${req.pathname}`, {
|
|
99
|
+
body: reqBody,
|
|
100
|
+
headers: fwdHeaders,
|
|
101
|
+
method: req.method
|
|
102
|
+
});
|
|
103
|
+
const elapsed = (performance.now() - now).toFixed(1);
|
|
104
|
+
console.log(`\x1B[32m${req.method}\x1B[0m ${req.pathname} \u2192 ${res.status} (${elapsed}ms)`);
|
|
105
|
+
const resBody = await res.arrayBuffer();
|
|
106
|
+
const headers = {};
|
|
107
|
+
res.headers.forEach((v, k) => {
|
|
108
|
+
headers[k] = v;
|
|
109
|
+
});
|
|
110
|
+
const response = {
|
|
111
|
+
body: toBase64(resBody),
|
|
112
|
+
headers,
|
|
113
|
+
id: req.id,
|
|
114
|
+
status: res.status,
|
|
115
|
+
statusText: res.statusText,
|
|
116
|
+
type: "response"
|
|
117
|
+
};
|
|
118
|
+
socket.send(JSON.stringify(response));
|
|
119
|
+
} catch (err) {
|
|
120
|
+
console.error(`\x1B[31mERR\x1B[0m ${req.method} ${req.pathname}: ${err}`);
|
|
121
|
+
const html = badGatewayHtml(port || "3000");
|
|
122
|
+
const response = {
|
|
123
|
+
body: toBase64(new TextEncoder().encode(html).buffer),
|
|
124
|
+
headers: { "content-type": "text/html; charset=utf-8" },
|
|
125
|
+
id: req.id,
|
|
126
|
+
status: 502,
|
|
127
|
+
statusText: "Bad Gateway",
|
|
128
|
+
type: "response"
|
|
129
|
+
};
|
|
130
|
+
socket.send(JSON.stringify(response));
|
|
131
|
+
}
|
|
482
132
|
}
|
|
483
133
|
});
|
|
484
|
-
socket.addEventListener("open", (
|
|
485
|
-
|
|
486
|
-
throw "not ready";
|
|
134
|
+
socket.addEventListener("open", () => {
|
|
135
|
+
console.log(`Connected to ${serverUrl}`);
|
|
487
136
|
});
|
|
488
137
|
socket.addEventListener("close", () => {
|
|
489
|
-
console.warn("
|
|
490
|
-
process.exit();
|
|
138
|
+
console.warn("Server closed connection");
|
|
139
|
+
process.exit(1);
|
|
140
|
+
});
|
|
141
|
+
socket.addEventListener("error", (err) => {
|
|
142
|
+
console.error("WebSocket error:", err);
|
|
143
|
+
process.exit(1);
|
|
491
144
|
});
|
|
492
145
|
}
|
|
493
146
|
var { values } = parseArgs({
|
|
147
|
+
allowPositionals: true,
|
|
494
148
|
args: process.argv,
|
|
495
149
|
options: {
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
version: {
|
|
501
|
-
}
|
|
502
|
-
allowPositionals: true
|
|
150
|
+
domain: { default: "bunl.sh", short: "d", type: "string" },
|
|
151
|
+
open: { short: "o", type: "boolean" },
|
|
152
|
+
port: { default: "3000", short: "p", type: "string" },
|
|
153
|
+
subdomain: { short: "s", type: "string" },
|
|
154
|
+
version: { short: "v", type: "boolean" }
|
|
155
|
+
}
|
|
503
156
|
});
|
|
504
157
|
if (values.version) {
|
|
505
|
-
|
|
158
|
+
const pkg = await Bun.file(new URL("./package.json", import.meta.url)).json();
|
|
159
|
+
console.log(pkg.version);
|
|
506
160
|
process.exit();
|
|
507
161
|
}
|
|
508
|
-
main(
|
|
162
|
+
main({
|
|
163
|
+
domain: values.domain || "bunl.sh",
|
|
164
|
+
open: values.open || false,
|
|
165
|
+
port: values.port || "3000",
|
|
166
|
+
subdomain: values.subdomain || ""
|
|
167
|
+
}).catch((err) => {
|
|
168
|
+
console.error(err);
|
|
169
|
+
process.exit(1);
|
|
170
|
+
});
|