@rubriclab/bunl 0.0.13 → 0.0.15

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 ADDED
@@ -0,0 +1,3 @@
1
+ PORT=1234
2
+ SCHEME=https
3
+ DOMAIN=example.so
package/README.md CHANGED
@@ -21,7 +21,7 @@ bun i
21
21
  To run the server:
22
22
 
23
23
  ```bash
24
- bun server
24
+ bun dev:server
25
25
  ```
26
26
 
27
27
  (Optional) to run a dummy process on localhost:3000:
package/build/client.js CHANGED
@@ -1,25 +1,487 @@
1
1
  #! /usr/bin/env bun
2
2
  // client.ts
3
- import { parseArgs } from "util";
4
- async function main({ url, domain, subdomain }) {
5
- const serverUrl = `ws://${domain || "localhost:1234"}?new${
6
- subdomain ? `&subdomain=${subdomain}` : ""
7
- }`;
3
+ import {parseArgs} from "util";
4
+
5
+ // node_modules/open/index.js
6
+ import process6 from "node:process";
7
+ import {Buffer} from "node:buffer";
8
+ import path from "node:path";
9
+ import {fileURLToPath} from "node:url";
10
+ import childProcess from "node:child_process";
11
+ import fs4, {constants as fsConstants} from "node:fs/promises";
12
+
13
+ // node_modules/is-wsl/index.js
14
+ import process2 from "node:process";
15
+ import os from "node:os";
16
+ import fs3 from "node:fs";
17
+
18
+ // node_modules/is-inside-container/index.js
19
+ import fs2 from "node:fs";
20
+
21
+ // node_modules/is-docker/index.js
22
+ import fs from "node:fs";
23
+ var hasDockerEnv = function() {
24
+ try {
25
+ fs.statSync("/.dockerenv");
26
+ return true;
27
+ } catch {
28
+ return false;
29
+ }
30
+ };
31
+ var hasDockerCGroup = function() {
32
+ try {
33
+ return fs.readFileSync("/proc/self/cgroup", "utf8").includes("docker");
34
+ } catch {
35
+ return false;
36
+ }
37
+ };
38
+ var isDockerCached;
39
+ function isDocker() {
40
+ if (isDockerCached === undefined) {
41
+ isDockerCached = hasDockerEnv() || hasDockerCGroup();
42
+ }
43
+ return isDockerCached;
44
+ }
45
+
46
+ // node_modules/is-inside-container/index.js
47
+ var cachedResult;
48
+ var hasContainerEnv = () => {
49
+ try {
50
+ fs2.statSync("/run/.containerenv");
51
+ return true;
52
+ } catch {
53
+ return false;
54
+ }
55
+ };
56
+ function isInsideContainer() {
57
+ if (cachedResult === undefined) {
58
+ cachedResult = hasContainerEnv() || isDocker();
59
+ }
60
+ return cachedResult;
61
+ }
62
+
63
+ // node_modules/is-wsl/index.js
64
+ var isWsl = () => {
65
+ if (process2.platform !== "linux") {
66
+ return false;
67
+ }
68
+ if (os.release().toLowerCase().includes("microsoft")) {
69
+ if (isInsideContainer()) {
70
+ return false;
71
+ }
72
+ return true;
73
+ }
74
+ try {
75
+ return fs3.readFileSync("/proc/version", "utf8").toLowerCase().includes("microsoft") ? !isInsideContainer() : false;
76
+ } catch {
77
+ return false;
78
+ }
79
+ };
80
+ var is_wsl_default = process2.env.__IS_WSL_TEST__ ? isWsl : isWsl();
81
+
82
+ // node_modules/define-lazy-prop/index.js
83
+ function defineLazyProperty(object, propertyName, valueGetter) {
84
+ const define = (value) => Object.defineProperty(object, propertyName, { value, enumerable: true, writable: true });
85
+ Object.defineProperty(object, propertyName, {
86
+ configurable: true,
87
+ enumerable: true,
88
+ get() {
89
+ const result = valueGetter();
90
+ define(result);
91
+ return result;
92
+ },
93
+ set(value) {
94
+ define(value);
95
+ }
96
+ });
97
+ return object;
98
+ }
99
+
100
+ // node_modules/default-browser/index.js
101
+ import {promisify as promisify4} from "node:util";
102
+ import process5 from "node:process";
103
+ import {execFile as execFile4} from "node:child_process";
104
+
105
+ // node_modules/default-browser-id/index.js
106
+ import {promisify} from "node:util";
107
+ import process3 from "node:process";
108
+ import {execFile} from "node:child_process";
109
+ var execFileAsync = promisify(execFile);
110
+ async function defaultBrowserId() {
111
+ if (process3.platform !== "darwin") {
112
+ throw new Error("macOS only");
113
+ }
114
+ const { stdout } = await execFileAsync("defaults", ["read", "com.apple.LaunchServices/com.apple.launchservices.secure", "LSHandlers"]);
115
+ const match = /LSHandlerRoleAll = "(?!-)(?<id>[^"]+?)";\s+?LSHandlerURLScheme = (?:http|https);/.exec(stdout);
116
+ return match?.groups.id ?? "com.apple.Safari";
117
+ }
118
+
119
+ // node_modules/run-applescript/index.js
120
+ import process4 from "node:process";
121
+ import {promisify as promisify2} from "node:util";
122
+ import {execFile as execFile2, execFileSync} from "node:child_process";
123
+ async function runAppleScript(script, { humanReadableOutput = true } = {}) {
124
+ if (process4.platform !== "darwin") {
125
+ throw new Error("macOS only");
126
+ }
127
+ const outputArguments = humanReadableOutput ? [] : ["-ss"];
128
+ const { stdout } = await execFileAsync2("osascript", ["-e", script, outputArguments]);
129
+ return stdout.trim();
130
+ }
131
+ var execFileAsync2 = promisify2(execFile2);
132
+
133
+ // node_modules/bundle-name/index.js
134
+ async function bundleName(bundleId) {
135
+ return runAppleScript(`tell application "Finder" to set app_path to application file id "${bundleId}" as string
136
+ tell application "System Events" to get value of property list item "CFBundleName" of property list file (app_path & ":Contents:Info.plist")`);
137
+ }
138
+
139
+ // node_modules/default-browser/windows.js
140
+ import {promisify as promisify3} from "node:util";
141
+ import {execFile as execFile3} from "node:child_process";
142
+ var execFileAsync3 = promisify3(execFile3);
143
+ var windowsBrowserProgIds = {
144
+ AppXq0fevzme2pys62n3e0fbqa7peapykr8v: { name: "Edge", id: "com.microsoft.edge.old" },
145
+ MSEdgeDHTML: { name: "Edge", id: "com.microsoft.edge" },
146
+ MSEdgeHTM: { name: "Edge", id: "com.microsoft.edge" },
147
+ "IE.HTTP": { name: "Internet Explorer", id: "com.microsoft.ie" },
148
+ FirefoxURL: { name: "Firefox", id: "org.mozilla.firefox" },
149
+ ChromeHTML: { name: "Chrome", id: "com.google.chrome" },
150
+ BraveHTML: { name: "Brave", id: "com.brave.Browser" },
151
+ BraveBHTML: { name: "Brave Beta", id: "com.brave.Browser.beta" },
152
+ BraveSSHTM: { name: "Brave Nightly", id: "com.brave.Browser.nightly" }
153
+ };
154
+
155
+ class UnknownBrowserError extends Error {
156
+ }
157
+ async function defaultBrowser(_execFileAsync = execFileAsync3) {
158
+ const { stdout } = await _execFileAsync("reg", [
159
+ "QUERY",
160
+ " HKEY_CURRENT_USER\Software\Microsoft\Windows\Shell\Associations\UrlAssociations\http\UserChoice",
161
+ "/v",
162
+ "ProgId"
163
+ ]);
164
+ const match = /ProgId\s*REG_SZ\s*(?<id>\S+)/.exec(stdout);
165
+ if (!match) {
166
+ throw new UnknownBrowserError(`Cannot find Windows browser in stdout: ${JSON.stringify(stdout)}`);
167
+ }
168
+ const { id } = match.groups;
169
+ const browser = windowsBrowserProgIds[id];
170
+ if (!browser) {
171
+ throw new UnknownBrowserError(`Unknown browser ID: ${id}`);
172
+ }
173
+ return browser;
174
+ }
175
+
176
+ // node_modules/default-browser/index.js
177
+ var execFileAsync4 = promisify4(execFile4);
178
+ var titleize = (string) => string.toLowerCase().replaceAll(/(?:^|\s|-)\S/g, (x) => x.toUpperCase());
179
+ async function defaultBrowser2() {
180
+ if (process5.platform === "darwin") {
181
+ const id = await defaultBrowserId();
182
+ const name = await bundleName(id);
183
+ return { name, id };
184
+ }
185
+ if (process5.platform === "linux") {
186
+ const { stdout } = await execFileAsync4("xdg-mime", ["query", "default", "x-scheme-handler/http"]);
187
+ const id = stdout.trim();
188
+ const name = titleize(id.replace(/.desktop$/, "").replace("-", " "));
189
+ return { name, id };
190
+ }
191
+ if (process5.platform === "win32") {
192
+ return defaultBrowser();
193
+ }
194
+ throw new Error("Only macOS, Linux, and Windows are supported");
195
+ }
196
+
197
+ // node_modules/open/index.js
198
+ var detectArchBinary = function(binary) {
199
+ if (typeof binary === "string" || Array.isArray(binary)) {
200
+ return binary;
201
+ }
202
+ const { [arch]: archBinary } = binary;
203
+ if (!archBinary) {
204
+ throw new Error(`${arch} is not supported`);
205
+ }
206
+ return archBinary;
207
+ };
208
+ var detectPlatformBinary = function({ [platform]: platformBinary }, { wsl }) {
209
+ if (wsl && is_wsl_default) {
210
+ return detectArchBinary(wsl);
211
+ }
212
+ if (!platformBinary) {
213
+ throw new Error(`${platform} is not supported`);
214
+ }
215
+ return detectArchBinary(platformBinary);
216
+ };
217
+ var __dirname2 = path.dirname(fileURLToPath(import.meta.url));
218
+ var localXdgOpenPath = path.join(__dirname2, "xdg-open");
219
+ var { platform, arch } = process6;
220
+ var getWslDrivesMountPoint = (() => {
221
+ const defaultMountPoint = "/mnt/";
222
+ let mountPoint;
223
+ return async function() {
224
+ if (mountPoint) {
225
+ return mountPoint;
226
+ }
227
+ const configFilePath = "/etc/wsl.conf";
228
+ let isConfigFileExists = false;
229
+ try {
230
+ await fs4.access(configFilePath, fsConstants.F_OK);
231
+ isConfigFileExists = true;
232
+ } catch {
233
+ }
234
+ if (!isConfigFileExists) {
235
+ return defaultMountPoint;
236
+ }
237
+ const configContent = await fs4.readFile(configFilePath, { encoding: "utf8" });
238
+ const configMountPoint = /(?<!#.*)root\s*=\s*(?<mountPoint>.*)/g.exec(configContent);
239
+ if (!configMountPoint) {
240
+ return defaultMountPoint;
241
+ }
242
+ mountPoint = configMountPoint.groups.mountPoint.trim();
243
+ mountPoint = mountPoint.endsWith("/") ? mountPoint : `${mountPoint}/`;
244
+ return mountPoint;
245
+ };
246
+ })();
247
+ var pTryEach = async (array, mapper) => {
248
+ let latestError;
249
+ for (const item of array) {
250
+ try {
251
+ return await mapper(item);
252
+ } catch (error) {
253
+ latestError = error;
254
+ }
255
+ }
256
+ throw latestError;
257
+ };
258
+ var baseOpen = async (options) => {
259
+ options = {
260
+ wait: false,
261
+ background: false,
262
+ newInstance: false,
263
+ allowNonzeroExitCode: false,
264
+ ...options
265
+ };
266
+ if (Array.isArray(options.app)) {
267
+ return pTryEach(options.app, (singleApp) => baseOpen({
268
+ ...options,
269
+ app: singleApp
270
+ }));
271
+ }
272
+ let { name: app, arguments: appArguments = [] } = options.app ?? {};
273
+ appArguments = [...appArguments];
274
+ if (Array.isArray(app)) {
275
+ return pTryEach(app, (appName) => baseOpen({
276
+ ...options,
277
+ app: {
278
+ name: appName,
279
+ arguments: appArguments
280
+ }
281
+ }));
282
+ }
283
+ if (app === "browser" || app === "browserPrivate") {
284
+ const ids = {
285
+ "com.google.chrome": "chrome",
286
+ "google-chrome.desktop": "chrome",
287
+ "org.mozilla.firefox": "firefox",
288
+ "firefox.desktop": "firefox",
289
+ "com.microsoft.msedge": "edge",
290
+ "com.microsoft.edge": "edge",
291
+ "microsoft-edge.desktop": "edge"
292
+ };
293
+ const flags = {
294
+ chrome: "--incognito",
295
+ firefox: "--private-window",
296
+ edge: "--inPrivate"
297
+ };
298
+ const browser = await defaultBrowser2();
299
+ if (browser.id in ids) {
300
+ const browserName = ids[browser.id];
301
+ if (app === "browserPrivate") {
302
+ appArguments.push(flags[browserName]);
303
+ }
304
+ return baseOpen({
305
+ ...options,
306
+ app: {
307
+ name: apps[browserName],
308
+ arguments: appArguments
309
+ }
310
+ });
311
+ }
312
+ throw new Error(`${browser.name} is not supported as a default browser`);
313
+ }
314
+ let command;
315
+ const cliArguments = [];
316
+ const childProcessOptions = {};
317
+ if (platform === "darwin") {
318
+ command = "open";
319
+ if (options.wait) {
320
+ cliArguments.push("--wait-apps");
321
+ }
322
+ if (options.background) {
323
+ cliArguments.push("--background");
324
+ }
325
+ if (options.newInstance) {
326
+ cliArguments.push("--new");
327
+ }
328
+ if (app) {
329
+ cliArguments.push("-a", app);
330
+ }
331
+ } else if (platform === "win32" || is_wsl_default && !isInsideContainer() && !app) {
332
+ const mountPoint = await getWslDrivesMountPoint();
333
+ 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`;
334
+ cliArguments.push("-NoProfile", "-NonInteractive", "-ExecutionPolicy", "Bypass", "-EncodedCommand");
335
+ if (!is_wsl_default) {
336
+ childProcessOptions.windowsVerbatimArguments = true;
337
+ }
338
+ const encodedArguments = ["Start"];
339
+ if (options.wait) {
340
+ encodedArguments.push("-Wait");
341
+ }
342
+ if (app) {
343
+ encodedArguments.push(`"\`"${app}\`""`);
344
+ if (options.target) {
345
+ appArguments.push(options.target);
346
+ }
347
+ } else if (options.target) {
348
+ encodedArguments.push(`"${options.target}"`);
349
+ }
350
+ if (appArguments.length > 0) {
351
+ appArguments = appArguments.map((argument) => `"\`"${argument}\`""`);
352
+ encodedArguments.push("-ArgumentList", appArguments.join(","));
353
+ }
354
+ options.target = Buffer.from(encodedArguments.join(" "), "utf16le").toString("base64");
355
+ } else {
356
+ if (app) {
357
+ command = app;
358
+ } else {
359
+ const isBundled = !__dirname2 || __dirname2 === "/";
360
+ let exeLocalXdgOpen = false;
361
+ try {
362
+ await fs4.access(localXdgOpenPath, fsConstants.X_OK);
363
+ exeLocalXdgOpen = true;
364
+ } catch {
365
+ }
366
+ const useSystemXdgOpen = process6.versions.electron ?? (platform === "android" || isBundled || !exeLocalXdgOpen);
367
+ command = useSystemXdgOpen ? "xdg-open" : localXdgOpenPath;
368
+ }
369
+ if (appArguments.length > 0) {
370
+ cliArguments.push(...appArguments);
371
+ }
372
+ if (!options.wait) {
373
+ childProcessOptions.stdio = "ignore";
374
+ childProcessOptions.detached = true;
375
+ }
376
+ }
377
+ if (platform === "darwin" && appArguments.length > 0) {
378
+ cliArguments.push("--args", ...appArguments);
379
+ }
380
+ if (options.target) {
381
+ cliArguments.push(options.target);
382
+ }
383
+ const subprocess = childProcess.spawn(command, cliArguments, childProcessOptions);
384
+ if (options.wait) {
385
+ return new Promise((resolve, reject) => {
386
+ subprocess.once("error", reject);
387
+ subprocess.once("close", (exitCode) => {
388
+ if (!options.allowNonzeroExitCode && exitCode > 0) {
389
+ reject(new Error(`Exited with code ${exitCode}`));
390
+ return;
391
+ }
392
+ resolve(subprocess);
393
+ });
394
+ });
395
+ }
396
+ subprocess.unref();
397
+ return subprocess;
398
+ };
399
+ var open = (target, options) => {
400
+ if (typeof target !== "string") {
401
+ throw new TypeError("Expected a `target`");
402
+ }
403
+ return baseOpen({
404
+ ...options,
405
+ target
406
+ });
407
+ };
408
+ var apps = {};
409
+ defineLazyProperty(apps, "chrome", () => detectPlatformBinary({
410
+ darwin: "google chrome",
411
+ win32: "chrome",
412
+ linux: ["google-chrome", "google-chrome-stable", "chromium"]
413
+ }, {
414
+ wsl: {
415
+ ia32: "/mnt/c/Program Files (x86)/Google/Chrome/Application/chrome.exe",
416
+ x64: ["/mnt/c/Program Files/Google/Chrome/Application/chrome.exe", "/mnt/c/Program Files (x86)/Google/Chrome/Application/chrome.exe"]
417
+ }
418
+ }));
419
+ defineLazyProperty(apps, "firefox", () => detectPlatformBinary({
420
+ darwin: "firefox",
421
+ win32: "C:\Program Files\Mozilla Firefox\firefox.exe",
422
+ linux: "firefox"
423
+ }, {
424
+ wsl: "/mnt/c/Program Files/Mozilla Firefox/firefox.exe"
425
+ }));
426
+ defineLazyProperty(apps, "edge", () => detectPlatformBinary({
427
+ darwin: "microsoft edge",
428
+ win32: "msedge",
429
+ linux: ["microsoft-edge", "microsoft-edge-dev"]
430
+ }, {
431
+ wsl: "/mnt/c/Program Files (x86)/Microsoft/Edge/Application/msedge.exe"
432
+ }));
433
+ defineLazyProperty(apps, "browser", () => "browser");
434
+ defineLazyProperty(apps, "browserPrivate", () => "browserPrivate");
435
+ var open_default = open;
436
+
437
+ // client.ts
438
+ async function main({
439
+ url,
440
+ domain,
441
+ subdomain,
442
+ open: open2
443
+ }) {
444
+ const params = new URLSearchParams({
445
+ new: "",
446
+ ...subdomain ? { subdomain } : {}
447
+ }).toString();
448
+ const serverUrl = `ws://${domain}?${params}`;
8
449
  const socket = new WebSocket(serverUrl);
9
- socket.addEventListener("message", (event) => {
450
+ socket.addEventListener("message", async (event) => {
10
451
  const data = JSON.parse(event.data);
11
- console.log("message:", data);
452
+ if (data.url) {
453
+ console.log(`
454
+
455
+ \u21AA Your URL: ${data.url}
456
+ `);
457
+ if (open2)
458
+ open_default(data.url);
459
+ }
12
460
  if (data.method) {
13
- fetch(`${url}${data.path}`, {
461
+ const res = await fetch(`${url}${data.pathname}`, {
14
462
  method: data.method,
15
463
  headers: data.headers,
16
- })
17
- .then((res) => res.text())
18
- .then((res) => socket.send(res));
464
+ body: data.body
465
+ });
466
+ const { status, statusText, headers } = res;
467
+ const body = await res.text();
468
+ const serializedRes = JSON.stringify({
469
+ pathname: data.pathname,
470
+ status,
471
+ statusText,
472
+ headers: Object.fromEntries(headers),
473
+ body
474
+ });
475
+ socket.send(serializedRes);
19
476
  }
20
477
  });
21
478
  socket.addEventListener("open", (event) => {
22
- if (!event.target.readyState) throw "Not ready";
479
+ if (!event.target.readyState)
480
+ throw "Not ready";
481
+ });
482
+ socket.addEventListener("close", () => {
483
+ console.log(`failed to connect to server`);
484
+ process.exit();
23
485
  });
24
486
  }
25
487
  var { values } = parseArgs({
@@ -28,22 +490,30 @@ var { values } = parseArgs({
28
490
  port: {
29
491
  type: "string",
30
492
  required: true,
31
- short: "p",
493
+ short: "p"
32
494
  },
33
495
  domain: {
34
496
  type: "string",
35
- short: "d",
497
+ default: "localhost:1234",
498
+ short: "d"
36
499
  },
37
500
  subdomain: {
38
501
  type: "string",
39
- short: "s",
502
+ short: "s"
40
503
  },
504
+ open: {
505
+ type: "boolean",
506
+ short: "o"
507
+ }
41
508
  },
42
- allowPositionals: true,
509
+ allowPositionals: true
43
510
  });
44
- if (!values.port) throw "pass -p 3000";
511
+ if (!values.port)
512
+ throw "pass -p 3000";
513
+ var { port, domain, subdomain, open: open2 } = values;
45
514
  main({
46
- url: `localhost:${values.port}`,
47
- domain: values.domain,
48
- subdomain: values.subdomain,
515
+ url: `localhost:${port}`,
516
+ domain,
517
+ subdomain,
518
+ open: open2
49
519
  });
package/client.ts CHANGED
@@ -21,14 +21,17 @@ async function main({
21
21
 
22
22
  socket.addEventListener("message", async (event) => {
23
23
  const data = JSON.parse(event.data as string);
24
- console.log("message:", data);
25
24
 
26
- if (open && data.url) browser(data.url);
25
+ if (data.url) {
26
+ console.log(`\n\n↪ Your URL: \x1b[32m${data.url}\x1b[0m\n`);
27
+ if (open) browser(data.url);
28
+ }
27
29
 
28
30
  if (data.method) {
29
31
  const res = await fetch(`${url}${data.pathname}`, {
30
32
  method: data.method,
31
33
  headers: data.headers,
34
+ body: data.body,
32
35
  });
33
36
 
34
37
  const { status, statusText, headers } = res;
package/package.json CHANGED
@@ -4,7 +4,7 @@
4
4
  },
5
5
  "name": "@rubriclab/bunl",
6
6
  "description": "Expose localhost to the world",
7
- "version": "0.0.13",
7
+ "version": "0.0.15",
8
8
  "license": "MIT",
9
9
  "repository": {
10
10
  "type": "git",
@@ -20,6 +20,7 @@
20
20
  "main": "build/client.js",
21
21
  "scripts": {
22
22
  "server": "bun server.ts",
23
+ "dev:server": "bun --hot server.ts",
23
24
  "client": "bun --hot client.ts",
24
25
  "demo": "bun --hot demo.ts",
25
26
  "build": "BUILD=build/client.js && bun build client.ts --outdir build --target node && echo -e \"#! /usr/bin/env bun\n$(cat $BUILD)\" > $BUILD"
package/server.ts CHANGED
@@ -34,8 +34,11 @@ serve<Client>({
34
34
  // The magic: forward the req to the client
35
35
  const client = clients.get(subdomain)!;
36
36
  const { method, url, headers: reqHeaders } = req;
37
+ const reqBody = await req.text();
37
38
  const { pathname } = new URL(url);
38
- client.send(JSON.stringify({ method, pathname, headers: reqHeaders }));
39
+ client.send(
40
+ JSON.stringify({ method, pathname, body: reqBody, headers: reqHeaders })
41
+ );
39
42
 
40
43
  // Wait for the client to cache its response above
41
44
  await sleep(1);