vite-plugin-io1 0.1.0 → 0.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -10,11 +10,15 @@ startup output.
10
10
 
11
11
  ## Why
12
12
 
13
- - **Real HTTPS, real cert** so secure-context APIs (service workers, camera, WebCrypto) just work.
14
- - **Working HMR through the tunnel** -- the shared URL is your live dev server, not a static copy.
15
- - **Email-gated sharing** via `allowedEmails` makes a preview shared-but-not-public.
16
- - **No `server.allowedHosts` fuss** -- the relay presents the upstream's own Host, so Vite's host
17
- check never trips.
13
+ A tunnel is only worth anything if it points at the dev server you are actually editing, and this
14
+ one does: the public URL is your running `vite dev` - HMR and all - so a teammate (or your phone)
15
+ hits the same live server you have open, and a save reloads their page in place. It arrives over a
16
+ genuine HTTPS cert, so the secure-context APIs that quietly refuse to run on plain HTTP - service
17
+ workers, the camera, WebCrypto - just work.
18
+
19
+ Set `allowedEmails` and the preview turns shared-but-not-public: a visitor verifies one of those
20
+ addresses before the host resolves for them. And because the relay presents the upstream's own Host,
21
+ Vite's host check never trips - there is no `server.allowedHosts` to wrangle.
18
22
 
19
23
  ## Install
20
24
 
@@ -71,14 +75,14 @@ explicitly for a shared demo host.
71
75
 
72
76
  The `bitmot` CLI carries a version (`bitmot version`), and the gateway advertises the latest one on
73
77
  every relay handshake; run by hand, the CLI nudges you when you are out of date. That nudge is
74
- deliberately silent when its output is piped or `CI` is set -- that is, whenever this plugin runs it.
78
+ deliberately silent when its output is piped or `CI` is set - that is, whenever this plugin runs it.
75
79
  So the plugin keeps the CLI current a different way: auto-download resolves the **currently deployed**
76
80
  bytes by content hash, so a plugin-managed CLI is always up to date and the nudge is moot. A CLI you
77
81
  installed yourself on your PATH is respected as-is and never replaced.
78
82
 
79
83
  ## Caveats
80
84
 
81
- - **macOS and Linux** -- published binaries cover macOS (arm64/x86_64) and Linux (x86_64/aarch64).
85
+ - **macOS and Linux** - published binaries cover macOS (arm64/x86_64) and Linux (x86_64/aarch64).
82
86
  Elsewhere (e.g. native Windows), install `bitmot` yourself (the plugin will use it) or set
83
87
  `bitmotPath`.
84
88
  - **Middleware mode** (e.g. Nuxt) exposes no `httpServer` for the plugin to hook; not yet supported.
@@ -1,8 +1,9 @@
1
1
  /**
2
2
  * Pure argument-building and output-parsing for the bitmot io1 relay. Kept free of I/O so the wire
3
- * contract with the CLI -- which flags it takes, and how it announces a live host -- is unit-tested
3
+ * contract with the CLI - which flags it takes, and how it announces a live host - is unit-tested
4
4
  * in isolation and lives in one place.
5
5
  */
6
+ import type { AddressInfo } from 'node:net';
6
7
  export interface RelayInvocation {
7
8
  /** Hosts to claim, one `--host` each (the CLI appends across repeats). At least one. */
8
9
  readonly hosts: readonly string[];
@@ -30,9 +31,19 @@ export declare function buildRelayArgs(invocation: RelayInvocation): string[];
30
31
  * The public URL from a relay's per-host announcement line, or null for any other line. The relay
31
32
  * prints one `<arrow> <url> live` line per claimed host once its link is up (uncolored when its
32
33
  * stderr is piped, as it is under this plugin). Tolerant of the arrow glyph and surrounding spaces so
33
- * both the native and JVM CLIs -- and any cached older build -- parse the same; the trailing `live`
34
+ * both the native and JVM CLIs - and any cached older build - parse the same; the trailing `live`
34
35
  * badge is what distinguishes it from the pending-requests and update-nudge lines, which also carry
35
36
  * URLs but must not be mistaken for a live host.
36
37
  */
37
38
  export declare function parseLiveUrl(line: string): string | null;
39
+ /**
40
+ * The `--to` proxy URL for a dev server bound at [address], a Node AddressInfo read once the socket is
41
+ * listening (so its port is the one the OS actually settled on, whatever Vite asked for). A wildcard
42
+ * bind - '0.0.0.0', '::', or the empty string - is not itself a connectable target, so we dial the
43
+ * loopback of the same family; a concrete address is kept as-is, and an IPv6 authority is bracketed.
44
+ * Using the bound address rather than a hardcoded 127.0.0.1 is what lets an IPv6 bind (Vite falling
45
+ * back to ::1 when 127.0.0.1:PORT is taken) be reached, instead of silently missing it or hitting
46
+ * whatever unrelated server already holds that IPv4 port.
47
+ */
48
+ export declare function relayTargetUrl(address: AddressInfo): string;
38
49
  //# sourceMappingURL=announce.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"announce.d.ts","sourceRoot":"","sources":["../src/announce.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,MAAM,WAAW,eAAe;IAC9B,wFAAwF;IACxF,QAAQ,CAAC,KAAK,EAAE,SAAS,MAAM,EAAE,CAAA;IACjC,iGAAiG;IACjG,QAAQ,CAAC,aAAa,CAAC,EAAE,SAAS,MAAM,EAAE,CAAA;IAC1C,qEAAqE;IACrE,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAA;IACzB,mFAAmF;IACnF,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAA;IACzB,uFAAuF;IACvF,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAA;IAC7B,kGAAkG;IAClG,QAAQ,CAAC,SAAS,CAAC,EAAE,SAAS,MAAM,EAAE,CAAA;IACtC,sEAAsE;IACtE,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAA;CACpB;AAED;;;;;GAKG;AACH,wBAAgB,cAAc,CAAC,UAAU,EAAE,eAAe,GAAG,MAAM,EAAE,CAWpE;AAED;;;;;;;GAOG;AACH,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAGxD"}
1
+ {"version":3,"file":"announce.d.ts","sourceRoot":"","sources":["../src/announce.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,UAAU,CAAA;AAE3C,MAAM,WAAW,eAAe;IAC9B,wFAAwF;IACxF,QAAQ,CAAC,KAAK,EAAE,SAAS,MAAM,EAAE,CAAA;IACjC,iGAAiG;IACjG,QAAQ,CAAC,aAAa,CAAC,EAAE,SAAS,MAAM,EAAE,CAAA;IAC1C,qEAAqE;IACrE,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAA;IACzB,mFAAmF;IACnF,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAA;IACzB,uFAAuF;IACvF,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAA;IAC7B,kGAAkG;IAClG,QAAQ,CAAC,SAAS,CAAC,EAAE,SAAS,MAAM,EAAE,CAAA;IACtC,sEAAsE;IACtE,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAA;CACpB;AAED;;;;;GAKG;AACH,wBAAgB,cAAc,CAAC,UAAU,EAAE,eAAe,GAAG,MAAM,EAAE,CAWpE;AAED;;;;;;;GAOG;AACH,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAGxD;AAED;;;;;;;;GAQG;AACH,wBAAgB,cAAc,CAAC,OAAO,EAAE,WAAW,GAAG,MAAM,CAK3D"}
package/dist/announce.js CHANGED
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * Pure argument-building and output-parsing for the bitmot io1 relay. Kept free of I/O so the wire
3
- * contract with the CLI -- which flags it takes, and how it announces a live host -- is unit-tested
3
+ * contract with the CLI - which flags it takes, and how it announces a live host - is unit-tested
4
4
  * in isolation and lives in one place.
5
5
  */
6
6
  /**
@@ -31,7 +31,7 @@ export function buildRelayArgs(invocation) {
31
31
  * The public URL from a relay's per-host announcement line, or null for any other line. The relay
32
32
  * prints one `<arrow> <url> live` line per claimed host once its link is up (uncolored when its
33
33
  * stderr is piped, as it is under this plugin). Tolerant of the arrow glyph and surrounding spaces so
34
- * both the native and JVM CLIs -- and any cached older build -- parse the same; the trailing `live`
34
+ * both the native and JVM CLIs - and any cached older build - parse the same; the trailing `live`
35
35
  * badge is what distinguishes it from the pending-requests and update-nudge lines, which also carry
36
36
  * URLs but must not be mistaken for a live host.
37
37
  */
@@ -39,4 +39,19 @@ export function parseLiveUrl(line) {
39
39
  const match = /(https?:\/\/\S+)\s+live\s*$/u.exec(line);
40
40
  return match ? match[1] : null;
41
41
  }
42
+ /**
43
+ * The `--to` proxy URL for a dev server bound at [address], a Node AddressInfo read once the socket is
44
+ * listening (so its port is the one the OS actually settled on, whatever Vite asked for). A wildcard
45
+ * bind - '0.0.0.0', '::', or the empty string - is not itself a connectable target, so we dial the
46
+ * loopback of the same family; a concrete address is kept as-is, and an IPv6 authority is bracketed.
47
+ * Using the bound address rather than a hardcoded 127.0.0.1 is what lets an IPv6 bind (Vite falling
48
+ * back to ::1 when 127.0.0.1:PORT is taken) be reached, instead of silently missing it or hitting
49
+ * whatever unrelated server already holds that IPv4 port.
50
+ */
51
+ export function relayTargetUrl(address) {
52
+ const wildcard = '' === address.address || '0.0.0.0' === address.address || '::' === address.address;
53
+ const host = wildcard ? ('IPv6' === address.family ? '::1' : '127.0.0.1') : address.address;
54
+ const authority = host.includes(':') ? `[${host}]` : host;
55
+ return `http://${authority}:${address.port}`;
56
+ }
42
57
  //# sourceMappingURL=announce.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"announce.js","sourceRoot":"","sources":["../src/announce.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAmBH;;;;;GAKG;AACH,MAAM,UAAU,cAAc,CAAC,UAA2B;IACxD,MAAM,IAAI,GAAG,CAAC,KAAK,EAAE,OAAO,CAAC,CAAA;IAC7B,KAAK,MAAM,IAAI,IAAI,UAAU,CAAC,KAAK;QAAE,IAAI,CAAC,IAAI,CAAC,QAAQ,EAAE,IAAI,CAAC,CAAA;IAC9D,MAAM,MAAM,GAAG,UAAU,CAAC,aAAa,IAAI,EAAE,CAAA;IAC7C,IAAI,MAAM,CAAC,MAAM,GAAG,CAAC;QAAE,IAAI,CAAC,IAAI,CAAC,kBAAkB,EAAE,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAA;IACtE,IAAI,UAAU,CAAC,OAAO;QAAE,IAAI,CAAC,IAAI,CAAC,WAAW,EAAE,UAAU,CAAC,OAAO,CAAC,CAAA;IAClE,IAAI,UAAU,CAAC,OAAO;QAAE,IAAI,CAAC,IAAI,CAAC,WAAW,EAAE,UAAU,CAAC,OAAO,CAAC,CAAA;IAClE,IAAI,UAAU,CAAC,WAAW;QAAE,IAAI,CAAC,IAAI,CAAC,gBAAgB,EAAE,UAAU,CAAC,WAAW,CAAC,CAAA;IAC/E,IAAI,UAAU,CAAC,SAAS;QAAE,IAAI,CAAC,IAAI,CAAC,GAAG,UAAU,CAAC,SAAS,CAAC,CAAA;IAC5D,IAAI,CAAC,IAAI,CAAC,MAAM,EAAE,UAAU,CAAC,EAAE,CAAC,CAAA;IAChC,OAAO,IAAI,CAAA;AACb,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,UAAU,YAAY,CAAC,IAAY;IACvC,MAAM,KAAK,GAAG,8BAA8B,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;IACvD,OAAO,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAE,CAAC,CAAC,CAAC,IAAI,CAAA;AACjC,CAAC"}
1
+ {"version":3,"file":"announce.js","sourceRoot":"","sources":["../src/announce.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAqBH;;;;;GAKG;AACH,MAAM,UAAU,cAAc,CAAC,UAA2B;IACxD,MAAM,IAAI,GAAG,CAAC,KAAK,EAAE,OAAO,CAAC,CAAA;IAC7B,KAAK,MAAM,IAAI,IAAI,UAAU,CAAC,KAAK;QAAE,IAAI,CAAC,IAAI,CAAC,QAAQ,EAAE,IAAI,CAAC,CAAA;IAC9D,MAAM,MAAM,GAAG,UAAU,CAAC,aAAa,IAAI,EAAE,CAAA;IAC7C,IAAI,MAAM,CAAC,MAAM,GAAG,CAAC;QAAE,IAAI,CAAC,IAAI,CAAC,kBAAkB,EAAE,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAA;IACtE,IAAI,UAAU,CAAC,OAAO;QAAE,IAAI,CAAC,IAAI,CAAC,WAAW,EAAE,UAAU,CAAC,OAAO,CAAC,CAAA;IAClE,IAAI,UAAU,CAAC,OAAO;QAAE,IAAI,CAAC,IAAI,CAAC,WAAW,EAAE,UAAU,CAAC,OAAO,CAAC,CAAA;IAClE,IAAI,UAAU,CAAC,WAAW;QAAE,IAAI,CAAC,IAAI,CAAC,gBAAgB,EAAE,UAAU,CAAC,WAAW,CAAC,CAAA;IAC/E,IAAI,UAAU,CAAC,SAAS;QAAE,IAAI,CAAC,IAAI,CAAC,GAAG,UAAU,CAAC,SAAS,CAAC,CAAA;IAC5D,IAAI,CAAC,IAAI,CAAC,MAAM,EAAE,UAAU,CAAC,EAAE,CAAC,CAAA;IAChC,OAAO,IAAI,CAAA;AACb,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,UAAU,YAAY,CAAC,IAAY;IACvC,MAAM,KAAK,GAAG,8BAA8B,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;IACvD,OAAO,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAE,CAAC,CAAC,CAAC,IAAI,CAAA;AACjC,CAAC;AAED;;;;;;;;GAQG;AACH,MAAM,UAAU,cAAc,CAAC,OAAoB;IACjD,MAAM,QAAQ,GAAG,EAAE,KAAK,OAAO,CAAC,OAAO,IAAI,SAAS,KAAK,OAAO,CAAC,OAAO,IAAI,IAAI,KAAK,OAAO,CAAC,OAAO,CAAA;IACpG,MAAM,IAAI,GAAG,QAAQ,CAAC,CAAC,CAAC,CAAC,MAAM,KAAK,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,OAAO,CAAA;IAC3F,MAAM,SAAS,GAAG,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,IAAI,GAAG,CAAC,CAAC,CAAC,IAAI,CAAA;IACzD,OAAO,UAAU,SAAS,IAAI,OAAO,CAAC,IAAI,EAAE,CAAA;AAC9C,CAAC"}
package/dist/binary.d.ts CHANGED
@@ -22,7 +22,7 @@ export interface ResolvedBinary {
22
22
  * The published artifact name for a platform/arch, or null when unsupported. Mirrors the server's
23
23
  * download whitelist (io/io1/gateway/download/DownloadRoutesImpl.kt) and the installer's `uname`
24
24
  * mapping: macOS and Linux, on x86_64 or 64-bit ARM. Node's arch is translated to the server's
25
- * spelling -- 'x64' -> 'x86_64' on both, but 64-bit ARM is 'arm64' on macOS and 'aarch64' on Linux.
25
+ * spelling - 'x64' -> 'x86_64' on both, but 64-bit ARM is 'arm64' on macOS and 'aarch64' on Linux.
26
26
  */
27
27
  export declare function artifactFor(platform: NodeJS.Platform, arch: string): string | null;
28
28
  /** `bitmot version` -> "0.2.1", or null if the binary is missing or its output is unexpected. */
@@ -33,7 +33,7 @@ export declare function readVersion(binary: string): string | null;
33
33
  * followed response's checksum header names the current version: if a file for that hash is already
34
34
  * cached the body is dropped undownloaded, otherwise it is streamed, verified against the advertised
35
35
  * checksum, and atomically installed. This is what makes a plugin-managed CLI track production
36
- * automatically -- so the CLI's own "you're out of date" nudge (suppressed anyway when its stderr is
36
+ * automatically - so the CLI's own "you're out of date" nudge (suppressed anyway when its stderr is
37
37
  * piped, as it is here) does not matter for the auto-managed case.
38
38
  */
39
39
  export declare function ensureDownloaded(baseUrl: string, artifact: string, cacheDir: string, log?: (message: string) => void): Promise<{
package/dist/binary.js CHANGED
@@ -11,7 +11,7 @@ const DEFAULT_BASE_URL = 'https://bitmot.com';
11
11
  * The published artifact name for a platform/arch, or null when unsupported. Mirrors the server's
12
12
  * download whitelist (io/io1/gateway/download/DownloadRoutesImpl.kt) and the installer's `uname`
13
13
  * mapping: macOS and Linux, on x86_64 or 64-bit ARM. Node's arch is translated to the server's
14
- * spelling -- 'x64' -> 'x86_64' on both, but 64-bit ARM is 'arm64' on macOS and 'aarch64' on Linux.
14
+ * spelling - 'x64' -> 'x86_64' on both, but 64-bit ARM is 'arm64' on macOS and 'aarch64' on Linux.
15
15
  */
16
16
  export function artifactFor(platform, arch) {
17
17
  if (platform === 'darwin') {
@@ -63,7 +63,7 @@ function onPath() {
63
63
  * followed response's checksum header names the current version: if a file for that hash is already
64
64
  * cached the body is dropped undownloaded, otherwise it is streamed, verified against the advertised
65
65
  * checksum, and atomically installed. This is what makes a plugin-managed CLI track production
66
- * automatically -- so the CLI's own "you're out of date" nudge (suppressed anyway when its stderr is
66
+ * automatically - so the CLI's own "you're out of date" nudge (suppressed anyway when its stderr is
67
67
  * piped, as it is here) does not matter for the auto-managed case.
68
68
  */
69
69
  export async function ensureDownloaded(baseUrl, artifact, cacheDir, log) {
package/dist/index.d.ts CHANGED
@@ -27,11 +27,12 @@ export interface Io1PluginOptions {
27
27
  }
28
28
  /**
29
29
  * Vite plugin: while the dev (or preview) server runs, publish it to the internet over a
30
- * `bitmot io1 relay` child process, proxying to the server's real bound port. The relay's lifetime is
31
- * tied to the server -- spawned once it is listening, killed when it closes -- so a shared URL never
32
- * outlives the session. The CLI is located on PATH or auto-downloaded (content-addressed, so it tracks
33
- * the deployed build). Reconnects are the relay's own concern; a non-zero exit is a definitive refusal
34
- * (bad auth, host held by another account) and is surfaced, not retried.
30
+ * `bitmot io1 relay` child process, proxying to the address the server actually bound. The relay's
31
+ * lifetime is tied to the server - armed by intercepting listen() and spawned the moment the socket
32
+ * binds, killed when it closes - so a shared URL never outlives the session. The CLI is located on
33
+ * PATH or auto-downloaded (content-addressed, so it tracks the deployed build). Reconnects are the
34
+ * relay's own concern; a non-zero exit is a definitive refusal (bad auth, host held by another
35
+ * account) and is surfaced, not retried.
35
36
  */
36
37
  export declare function io1(options?: Io1PluginOptions): Plugin;
37
38
  export default io1;
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,MAAM,EAAgC,MAAM,MAAM,CAAA;AAIhE,YAAY,EAAE,eAAe,EAAE,MAAM,eAAe,CAAA;AACpD,YAAY,EAAE,cAAc,EAAE,cAAc,EAAE,MAAM,aAAa,CAAA;AAEjE,MAAM,WAAW,gBAAgB;IAC/B;;;;OAIG;IACH,IAAI,CAAC,EAAE,MAAM,GAAG,MAAM,EAAE,CAAA;IACxB,uFAAuF;IACvF,aAAa,CAAC,EAAE,MAAM,EAAE,CAAA;IACxB,sEAAsE;IACtE,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,gFAAgF;IAChF,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,6EAA6E;IAC7E,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,0DAA0D;IAC1D,WAAW,CAAC,EAAE,OAAO,CAAA;IACrB,4EAA4E;IAC5E,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,kGAAkG;IAClG,SAAS,CAAC,EAAE,MAAM,EAAE,CAAA;IACpB,oFAAoF;IACpF,OAAO,CAAC,EAAE,OAAO,CAAA;CAClB;AAUD;;;;;;;GAOG;AACH,wBAAgB,GAAG,CAAC,OAAO,GAAE,gBAAqB,GAAG,MAAM,CA0F1D;AAyBD,eAAe,GAAG,CAAA"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,MAAM,EAAgC,MAAM,MAAM,CAAA;AAIhE,YAAY,EAAE,eAAe,EAAE,MAAM,eAAe,CAAA;AACpD,YAAY,EAAE,cAAc,EAAE,cAAc,EAAE,MAAM,aAAa,CAAA;AAEjE,MAAM,WAAW,gBAAgB;IAC/B;;;;OAIG;IACH,IAAI,CAAC,EAAE,MAAM,GAAG,MAAM,EAAE,CAAA;IACxB,uFAAuF;IACvF,aAAa,CAAC,EAAE,MAAM,EAAE,CAAA;IACxB,sEAAsE;IACtE,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,gFAAgF;IAChF,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,6EAA6E;IAC7E,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,0DAA0D;IAC1D,WAAW,CAAC,EAAE,OAAO,CAAA;IACrB,4EAA4E;IAC5E,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,kGAAkG;IAClG,SAAS,CAAC,EAAE,MAAM,EAAE,CAAA;IACpB,oFAAoF;IACpF,OAAO,CAAC,EAAE,OAAO,CAAA;CAClB;AAUD;;;;;;;;GAQG;AACH,wBAAgB,GAAG,CAAC,OAAO,GAAE,gBAAqB,GAAG,MAAM,CAyG1D;AAyBD,eAAe,GAAG,CAAA"}
package/dist/index.js CHANGED
@@ -1,5 +1,5 @@
1
1
  import { spawn } from 'node:child_process';
2
- import { buildRelayArgs, parseLiveUrl } from './announce.js';
2
+ import { buildRelayArgs, parseLiveUrl, relayTargetUrl } from './announce.js';
3
3
  import { resolveBitmot } from './binary.js';
4
4
  function normalizeHosts(host) {
5
5
  const raw = host ?? process.env.IO1_DEV_HOST;
@@ -9,11 +9,12 @@ function normalizeHosts(host) {
9
9
  }
10
10
  /**
11
11
  * Vite plugin: while the dev (or preview) server runs, publish it to the internet over a
12
- * `bitmot io1 relay` child process, proxying to the server's real bound port. The relay's lifetime is
13
- * tied to the server -- spawned once it is listening, killed when it closes -- so a shared URL never
14
- * outlives the session. The CLI is located on PATH or auto-downloaded (content-addressed, so it tracks
15
- * the deployed build). Reconnects are the relay's own concern; a non-zero exit is a definitive refusal
16
- * (bad auth, host held by another account) and is surfaced, not retried.
12
+ * `bitmot io1 relay` child process, proxying to the address the server actually bound. The relay's
13
+ * lifetime is tied to the server - armed by intercepting listen() and spawned the moment the socket
14
+ * binds, killed when it closes - so a shared URL never outlives the session. The CLI is located on
15
+ * PATH or auto-downloaded (content-addressed, so it tracks the deployed build). Reconnects are the
16
+ * relay's own concern; a non-zero exit is a definitive refusal (bad auth, host held by another
17
+ * account) and is surfaced, not retried.
17
18
  */
18
19
  export function io1(options = {}) {
19
20
  const hosts = normalizeHosts(options.host);
@@ -39,10 +40,24 @@ export function io1(options = {}) {
39
40
  child = undefined;
40
41
  process.removeListener('exit', onExit); // avoid piling up a listener per dev-server restart
41
42
  };
42
- httpServer.once('listening', () => {
43
- void start(server).catch((error) => {
44
- logger.error(`[io1] ${error.message}`);
45
- });
43
+ // Arm the relay by intercepting listen() rather than only awaiting the 'listening' event: Vite
44
+ // calls listen once it has resolved the final port - the configured one, or (under
45
+ // strictPort:false) the next free port after a collision - so wrapping the very call that binds
46
+ // the socket is what guarantees we read the port the server actually took. The one-time
47
+ // 'listening' hook fires the instant the bind completes; `armed` keeps a second listen() from
48
+ // spawning a second relay. The casts are the price of replacing an overloaded native method.
49
+ const originalListen = httpServer.listen.bind(httpServer);
50
+ let armed = false;
51
+ httpServer.listen = ((...args) => {
52
+ if (!armed) {
53
+ armed = true;
54
+ httpServer.once('listening', () => {
55
+ void start(server).catch((error) => {
56
+ logger.error(`[io1] ${error.message}`);
57
+ });
58
+ });
59
+ }
60
+ return originalListen(...args);
46
61
  });
47
62
  httpServer.once('close', onExit);
48
63
  process.once('exit', onExit);
@@ -51,7 +66,7 @@ export function io1(options = {}) {
51
66
  const logger = server.config.logger;
52
67
  const address = server.httpServer?.address();
53
68
  if (!address || typeof address === 'string') {
54
- logger.error('[io1] could not determine the dev server port; relay not started');
69
+ logger.error('[io1] could not determine the dev server address; relay not started');
55
70
  return;
56
71
  }
57
72
  const binary = await resolveBitmot({
@@ -68,7 +83,7 @@ export function io1(options = {}) {
68
83
  account: options.account,
69
84
  gateway: options.gateway,
70
85
  extraArgs: options.extraArgs,
71
- to: `http://127.0.0.1:${address.port}`,
86
+ to: relayTargetUrl(address),
72
87
  });
73
88
  if (stopping)
74
89
  return; // the server closed while we were resolving the binary
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAqB,MAAM,oBAAoB,CAAA;AAE7D,OAAO,EAAE,cAAc,EAAE,YAAY,EAAE,MAAM,eAAe,CAAA;AAC5D,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAA;AA8B3C,SAAS,cAAc,CAAC,IAA8B;IACpD,MAAM,GAAG,GAAG,IAAI,IAAI,OAAO,CAAC,GAAG,CAAC,YAAY,CAAA;IAC5C,IAAI,CAAC,GAAG;QAAE,OAAO,EAAE,CAAA;IACnB,OAAO,CAAC,KAAK,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,MAAM,GAAG,CAAC,CAAC,CAAA;AAC/E,CAAC;AAID;;;;;;;GAOG;AACH,MAAM,UAAU,GAAG,CAAC,UAA4B,EAAE;IAChD,MAAM,KAAK,GAAG,cAAc,CAAC,OAAO,CAAC,IAAI,CAAC,CAAA;IAC1C,MAAM,OAAO,GAAG,OAAO,CAAC,OAAO,IAAI,CAAC,CAAC,OAAO,CAAC,GAAG,CAAC,EAAE,IAAI,KAAK,CAAC,MAAM,GAAG,CAAC,CAAC,CAAA;IAExE,IAAI,KAA+B,CAAA;IACnC,IAAI,QAAQ,GAAG,KAAK,CAAA;IAEpB,MAAM,SAAS,GAAG,CAAC,MAAmB,EAAQ,EAAE;QAC9C,IAAI,CAAC,OAAO;YAAE,OAAM;QACpB,MAAM,MAAM,GAAG,MAAM,CAAC,MAAM,CAAC,MAAM,CAAA;QACnC,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YACvB,MAAM,CAAC,IAAI,CAAC,2EAA2E,CAAC,CAAA;YACxF,OAAM;QACR,CAAC;QACD,MAAM,UAAU,GAAG,MAAM,CAAC,UAAU,CAAA;QACpC,IAAI,CAAC,UAAU,EAAE,CAAC;YAChB,MAAM,CAAC,IAAI,CAAC,sEAAsE,CAAC,CAAA;YACnF,OAAM;QACR,CAAC;QAED,MAAM,MAAM,GAAG,GAAS,EAAE;YACxB,QAAQ,GAAG,IAAI,CAAA;YACf,KAAK,EAAE,IAAI,CAAC,SAAS,CAAC,CAAA;YACtB,KAAK,GAAG,SAAS,CAAA;YACjB,OAAO,CAAC,cAAc,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA,CAAC,oDAAoD;QAC7F,CAAC,CAAA;QAED,UAAU,CAAC,IAAI,CAAC,WAAW,EAAE,GAAG,EAAE;YAChC,KAAK,KAAK,CAAC,MAAM,CAAC,CAAC,KAAK,CAAC,CAAC,KAAc,EAAE,EAAE;gBAC1C,MAAM,CAAC,KAAK,CAAC,SAAU,KAAe,CAAC,OAAO,EAAE,CAAC,CAAA;YACnD,CAAC,CAAC,CAAA;QACJ,CAAC,CAAC,CAAA;QACF,UAAU,CAAC,IAAI,CAAC,OAAO,EAAE,MAAM,CAAC,CAAA;QAChC,OAAO,CAAC,IAAI,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;IAC9B,CAAC,CAAA;IAED,MAAM,KAAK,GAAG,KAAK,EAAE,MAAmB,EAAiB,EAAE;QACzD,MAAM,MAAM,GAAG,MAAM,CAAC,MAAM,CAAC,MAAM,CAAA;QACnC,MAAM,OAAO,GAAG,MAAM,CAAC,UAAU,EAAE,OAAO,EAAE,CAAA;QAC5C,IAAI,CAAC,OAAO,IAAI,OAAO,OAAO,KAAK,QAAQ,EAAE,CAAC;YAC5C,MAAM,CAAC,KAAK,CAAC,kEAAkE,CAAC,CAAA;YAChF,OAAM;QACR,CAAC;QAED,MAAM,MAAM,GAAG,MAAM,aAAa,CAAC;YACjC,UAAU,EAAE,OAAO,CAAC,UAAU;YAC9B,WAAW,EAAE,OAAO,CAAC,WAAW;YAChC,OAAO,EAAE,OAAO,CAAC,OAAO;YACxB,GAAG,EAAE,CAAC,OAAO,EAAE,EAAE,CAAC,MAAM,CAAC,IAAI,CAAC,SAAS,OAAO,EAAE,CAAC;SAClD,CAAC,CAAA;QACF,IAAI,MAAM,CAAC,OAAO;YAAE,MAAM,CAAC,IAAI,CAAC,sBAAsB,MAAM,CAAC,OAAO,KAAK,MAAM,CAAC,MAAM,GAAG,CAAC,CAAA;QAE1F,MAAM,IAAI,GAAG,cAAc,CAAC;YAC1B,KAAK;YACL,aAAa,EAAE,OAAO,CAAC,aAAa;YACpC,OAAO,EAAE,OAAO,CAAC,OAAO;YACxB,OAAO,EAAE,OAAO,CAAC,OAAO;YACxB,SAAS,EAAE,OAAO,CAAC,SAAS;YAC5B,EAAE,EAAE,oBAAoB,OAAO,CAAC,IAAI,EAAE;SACvC,CAAC,CAAA;QAEF,IAAI,QAAQ;YAAE,OAAM,CAAC,uDAAuD;QAC5E,MAAM,KAAK,GAAG,KAAK,CAAC,MAAM,CAAC,IAAI,EAAE,IAAI,EAAE,EAAE,KAAK,EAAE,CAAC,QAAQ,EAAE,QAAQ,EAAE,MAAM,CAAC,EAAE,CAAC,CAAA;QAC/E,KAAK,GAAG,KAAK,CAAA;QAEb,KAAK,CAAC,EAAE,CAAC,OAAO,EAAE,CAAC,KAAK,EAAE,EAAE;YAC1B,MAAM,CAAC,KAAK,CAAC,gCAAgC,KAAK,CAAC,OAAO,EAAE,CAAC,CAAA;QAC/D,CAAC,CAAC,CAAA;QACF,KAAK,CAAC,EAAE,CAAC,MAAM,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE;YAChC,IAAI,KAAK,KAAK,KAAK;gBAAE,KAAK,GAAG,SAAS,CAAA;YACtC,IAAI,QAAQ,IAAI,MAAM;gBAAE,OAAM;YAC9B,IAAI,IAAI,IAAI,IAAI,KAAK,CAAC,EAAE,CAAC;gBACvB,MAAM,CAAC,KAAK,CACV,4BAA4B,IAAI,wDAAwD,CACzF,CAAA;YACH,CAAC;QACH,CAAC,CAAC,CAAA;QAEF,kBAAkB,CAAC,KAAK,EAAE,MAAM,CAAC,CAAA;IACnC,CAAC,CAAA;IAED,OAAO;QACL,IAAI,EAAE,iBAAiB;QACvB,eAAe,CAAC,MAAM;YACpB,SAAS,CAAC,MAAM,CAAC,CAAA;QACnB,CAAC;QACD,sBAAsB,CAAC,MAAM;YAC3B,SAAS,CAAC,MAAM,CAAC,CAAA;QACnB,CAAC;KACF,CAAA;AACH,CAAC;AAED;;;;;GAKG;AACH,SAAS,kBAAkB,CAAC,KAAmB,EAAE,MAAmB;IAClE,MAAM,MAAM,GAAG,MAAM,CAAC,MAAM,CAAC,MAAM,CAAA;IACnC,IAAI,MAAM,GAAG,EAAE,CAAA;IACf,KAAK,CAAC,MAAM,EAAE,WAAW,CAAC,MAAM,CAAC,CAAA;IACjC,KAAK,CAAC,MAAM,EAAE,EAAE,CAAC,MAAM,EAAE,CAAC,KAAa,EAAE,EAAE;QACzC,MAAM,IAAI,KAAK,CAAA;QACf,KAAK,IAAI,OAAO,GAAG,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,OAAO,IAAI,CAAC,EAAE,OAAO,GAAG,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,CAAC;YACtF,MAAM,IAAI,GAAG,MAAM,CAAC,KAAK,CAAC,CAAC,EAAE,OAAO,CAAC,CAAC,OAAO,CAAC,KAAK,EAAE,EAAE,CAAC,CAAA;YACxD,MAAM,GAAG,MAAM,CAAC,KAAK,CAAC,OAAO,GAAG,CAAC,CAAC,CAAA;YAClC,IAAI,IAAI,CAAC,IAAI,EAAE,CAAC,MAAM,KAAK,CAAC;gBAAE,SAAQ;YACtC,MAAM,GAAG,GAAG,YAAY,CAAC,IAAI,CAAC,CAAA;YAC9B,IAAI,GAAG;gBAAE,MAAM,CAAC,IAAI,CAAC,iBAAiB,GAAG,EAAE,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,SAAS,EAAE,KAAK,EAAE,CAAC,CAAA;;gBAC3E,MAAM,CAAC,IAAI,CAAC,SAAS,IAAI,EAAE,CAAC,CAAA;QACnC,CAAC;IACH,CAAC,CAAC,CAAA;AACJ,CAAC;AAED,eAAe,GAAG,CAAA"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vite-plugin-io1",
3
- "version": "0.1.0",
3
+ "version": "0.1.2",
4
4
  "description": "Publish your Vite dev server to the public internet over a bitmot io1 relay, with the CLI auto-managed.",
5
5
  "keywords": [
6
6
  "vite",
package/src/announce.ts CHANGED
@@ -1,9 +1,11 @@
1
1
  /**
2
2
  * Pure argument-building and output-parsing for the bitmot io1 relay. Kept free of I/O so the wire
3
- * contract with the CLI -- which flags it takes, and how it announces a live host -- is unit-tested
3
+ * contract with the CLI - which flags it takes, and how it announces a live host - is unit-tested
4
4
  * in isolation and lives in one place.
5
5
  */
6
6
 
7
+ import type { AddressInfo } from 'node:net'
8
+
7
9
  export interface RelayInvocation {
8
10
  /** Hosts to claim, one `--host` each (the CLI appends across repeats). At least one. */
9
11
  readonly hosts: readonly string[]
@@ -44,7 +46,7 @@ export function buildRelayArgs(invocation: RelayInvocation): string[] {
44
46
  * The public URL from a relay's per-host announcement line, or null for any other line. The relay
45
47
  * prints one `<arrow> <url> live` line per claimed host once its link is up (uncolored when its
46
48
  * stderr is piped, as it is under this plugin). Tolerant of the arrow glyph and surrounding spaces so
47
- * both the native and JVM CLIs -- and any cached older build -- parse the same; the trailing `live`
49
+ * both the native and JVM CLIs - and any cached older build - parse the same; the trailing `live`
48
50
  * badge is what distinguishes it from the pending-requests and update-nudge lines, which also carry
49
51
  * URLs but must not be mistaken for a live host.
50
52
  */
@@ -52,3 +54,19 @@ export function parseLiveUrl(line: string): string | null {
52
54
  const match = /(https?:\/\/\S+)\s+live\s*$/u.exec(line)
53
55
  return match ? match[1]! : null
54
56
  }
57
+
58
+ /**
59
+ * The `--to` proxy URL for a dev server bound at [address], a Node AddressInfo read once the socket is
60
+ * listening (so its port is the one the OS actually settled on, whatever Vite asked for). A wildcard
61
+ * bind - '0.0.0.0', '::', or the empty string - is not itself a connectable target, so we dial the
62
+ * loopback of the same family; a concrete address is kept as-is, and an IPv6 authority is bracketed.
63
+ * Using the bound address rather than a hardcoded 127.0.0.1 is what lets an IPv6 bind (Vite falling
64
+ * back to ::1 when 127.0.0.1:PORT is taken) be reached, instead of silently missing it or hitting
65
+ * whatever unrelated server already holds that IPv4 port.
66
+ */
67
+ export function relayTargetUrl(address: AddressInfo): string {
68
+ const wildcard = '' === address.address || '0.0.0.0' === address.address || '::' === address.address
69
+ const host = wildcard ? ('IPv6' === address.family ? '::1' : '127.0.0.1') : address.address
70
+ const authority = host.includes(':') ? `[${host}]` : host
71
+ return `http://${authority}:${address.port}`
72
+ }
package/src/binary.ts CHANGED
@@ -36,7 +36,7 @@ const DEFAULT_BASE_URL = 'https://bitmot.com'
36
36
  * The published artifact name for a platform/arch, or null when unsupported. Mirrors the server's
37
37
  * download whitelist (io/io1/gateway/download/DownloadRoutesImpl.kt) and the installer's `uname`
38
38
  * mapping: macOS and Linux, on x86_64 or 64-bit ARM. Node's arch is translated to the server's
39
- * spelling -- 'x64' -> 'x86_64' on both, but 64-bit ARM is 'arm64' on macOS and 'aarch64' on Linux.
39
+ * spelling - 'x64' -> 'x86_64' on both, but 64-bit ARM is 'arm64' on macOS and 'aarch64' on Linux.
40
40
  */
41
41
  export function artifactFor(platform: NodeJS.Platform, arch: string): string | null {
42
42
  if (platform === 'darwin') {
@@ -87,7 +87,7 @@ function onPath(): boolean {
87
87
  * followed response's checksum header names the current version: if a file for that hash is already
88
88
  * cached the body is dropped undownloaded, otherwise it is streamed, verified against the advertised
89
89
  * checksum, and atomically installed. This is what makes a plugin-managed CLI track production
90
- * automatically -- so the CLI's own "you're out of date" nudge (suppressed anyway when its stderr is
90
+ * automatically - so the CLI's own "you're out of date" nudge (suppressed anyway when its stderr is
91
91
  * piped, as it is here) does not matter for the auto-managed case.
92
92
  */
93
93
  export async function ensureDownloaded(
package/src/index.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  import { spawn, type ChildProcess } from 'node:child_process'
2
2
  import type { Plugin, PreviewServer, ViteDevServer } from 'vite'
3
- import { buildRelayArgs, parseLiveUrl } from './announce.js'
3
+ import { buildRelayArgs, parseLiveUrl, relayTargetUrl } from './announce.js'
4
4
  import { resolveBitmot } from './binary.js'
5
5
 
6
6
  export type { RelayInvocation } from './announce.js'
@@ -41,11 +41,12 @@ type ServeServer = ViteDevServer | PreviewServer
41
41
 
42
42
  /**
43
43
  * Vite plugin: while the dev (or preview) server runs, publish it to the internet over a
44
- * `bitmot io1 relay` child process, proxying to the server's real bound port. The relay's lifetime is
45
- * tied to the server -- spawned once it is listening, killed when it closes -- so a shared URL never
46
- * outlives the session. The CLI is located on PATH or auto-downloaded (content-addressed, so it tracks
47
- * the deployed build). Reconnects are the relay's own concern; a non-zero exit is a definitive refusal
48
- * (bad auth, host held by another account) and is surfaced, not retried.
44
+ * `bitmot io1 relay` child process, proxying to the address the server actually bound. The relay's
45
+ * lifetime is tied to the server - armed by intercepting listen() and spawned the moment the socket
46
+ * binds, killed when it closes - so a shared URL never outlives the session. The CLI is located on
47
+ * PATH or auto-downloaded (content-addressed, so it tracks the deployed build). Reconnects are the
48
+ * relay's own concern; a non-zero exit is a definitive refusal (bad auth, host held by another
49
+ * account) and is surfaced, not retried.
49
50
  */
50
51
  export function io1(options: Io1PluginOptions = {}): Plugin {
51
52
  const hosts = normalizeHosts(options.host)
@@ -74,11 +75,26 @@ export function io1(options: Io1PluginOptions = {}): Plugin {
74
75
  process.removeListener('exit', onExit) // avoid piling up a listener per dev-server restart
75
76
  }
76
77
 
77
- httpServer.once('listening', () => {
78
- void start(server).catch((error: unknown) => {
79
- logger.error(`[io1] ${(error as Error).message}`)
80
- })
81
- })
78
+ // Arm the relay by intercepting listen() rather than only awaiting the 'listening' event: Vite
79
+ // calls listen once it has resolved the final port - the configured one, or (under
80
+ // strictPort:false) the next free port after a collision - so wrapping the very call that binds
81
+ // the socket is what guarantees we read the port the server actually took. The one-time
82
+ // 'listening' hook fires the instant the bind completes; `armed` keeps a second listen() from
83
+ // spawning a second relay. The casts are the price of replacing an overloaded native method.
84
+ const originalListen = httpServer.listen.bind(httpServer) as unknown as (...args: unknown[]) => unknown
85
+ let armed = false
86
+ httpServer.listen = ((...args: unknown[]): unknown => {
87
+ if (!armed) {
88
+ armed = true
89
+ httpServer.once('listening', () => {
90
+ void start(server).catch((error: unknown) => {
91
+ logger.error(`[io1] ${(error as Error).message}`)
92
+ })
93
+ })
94
+ }
95
+ return originalListen(...args)
96
+ }) as unknown as typeof httpServer.listen
97
+
82
98
  httpServer.once('close', onExit)
83
99
  process.once('exit', onExit)
84
100
  }
@@ -87,7 +103,7 @@ export function io1(options: Io1PluginOptions = {}): Plugin {
87
103
  const logger = server.config.logger
88
104
  const address = server.httpServer?.address()
89
105
  if (!address || typeof address === 'string') {
90
- logger.error('[io1] could not determine the dev server port; relay not started')
106
+ logger.error('[io1] could not determine the dev server address; relay not started')
91
107
  return
92
108
  }
93
109
 
@@ -105,7 +121,7 @@ export function io1(options: Io1PluginOptions = {}): Plugin {
105
121
  account: options.account,
106
122
  gateway: options.gateway,
107
123
  extraArgs: options.extraArgs,
108
- to: `http://127.0.0.1:${address.port}`,
124
+ to: relayTargetUrl(address),
109
125
  })
110
126
 
111
127
  if (stopping) return // the server closed while we were resolving the binary