skybridge 0.0.0-dev.f2c8fae → 0.0.0-dev.f2d6084

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (322) hide show
  1. package/README.md +123 -124
  2. package/dist/cli/build-helpers.d.ts +7 -0
  3. package/dist/cli/build-helpers.js +82 -0
  4. package/dist/cli/build-helpers.js.map +1 -0
  5. package/dist/cli/build-helpers.test.js +64 -0
  6. package/dist/cli/build-helpers.test.js.map +1 -0
  7. package/dist/cli/detect-port.d.ts +2 -2
  8. package/dist/cli/detect-port.js +9 -20
  9. package/dist/cli/detect-port.js.map +1 -1
  10. package/dist/cli/header.js +1 -1
  11. package/dist/cli/header.js.map +1 -1
  12. package/dist/cli/resolve-views-dir.d.ts +1 -0
  13. package/dist/cli/resolve-views-dir.js +17 -0
  14. package/dist/cli/resolve-views-dir.js.map +1 -0
  15. package/dist/cli/run-command.js.map +1 -1
  16. package/dist/cli/telemetry.js.map +1 -1
  17. package/dist/cli/tunnel-control-server.d.ts +9 -0
  18. package/dist/cli/tunnel-control-server.js +31 -0
  19. package/dist/cli/tunnel-control-server.js.map +1 -0
  20. package/dist/cli/tunnel-control-server.test.js +39 -0
  21. package/dist/cli/tunnel-control-server.test.js.map +1 -0
  22. package/dist/cli/tunnel-handler.d.ts +3 -0
  23. package/dist/cli/tunnel-handler.js +48 -0
  24. package/dist/cli/tunnel-handler.js.map +1 -0
  25. package/dist/cli/tunnel-handler.test.js +105 -0
  26. package/dist/cli/tunnel-handler.test.js.map +1 -0
  27. package/dist/cli/tunnel.d.ts +57 -0
  28. package/dist/cli/tunnel.js +154 -0
  29. package/dist/cli/tunnel.js.map +1 -0
  30. package/dist/cli/tunnel.test.d.ts +1 -0
  31. package/dist/cli/tunnel.test.js +190 -0
  32. package/dist/cli/tunnel.test.js.map +1 -0
  33. package/dist/cli/types.js.map +1 -1
  34. package/dist/cli/use-execute-steps.js.map +1 -1
  35. package/dist/cli/use-messages.d.ts +3 -0
  36. package/dist/cli/use-messages.js +11 -0
  37. package/dist/cli/use-messages.js.map +1 -0
  38. package/dist/cli/use-nodemon.d.ts +2 -2
  39. package/dist/cli/use-nodemon.js +18 -25
  40. package/dist/cli/use-nodemon.js.map +1 -1
  41. package/dist/cli/use-open-browser.d.ts +1 -0
  42. package/dist/cli/use-open-browser.js +44 -0
  43. package/dist/cli/use-open-browser.js.map +1 -0
  44. package/dist/cli/use-open-tunnel-browser.d.ts +6 -0
  45. package/dist/cli/use-open-tunnel-browser.js +19 -0
  46. package/dist/cli/use-open-tunnel-browser.js.map +1 -0
  47. package/dist/cli/use-tunnel.d.ts +13 -7
  48. package/dist/cli/use-tunnel.js +103 -73
  49. package/dist/cli/use-tunnel.js.map +1 -1
  50. package/dist/cli/use-typescript-check.d.ts +1 -0
  51. package/dist/cli/use-typescript-check.js +42 -7
  52. package/dist/cli/use-typescript-check.js.map +1 -1
  53. package/dist/commands/build.d.ts +0 -1
  54. package/dist/commands/build.js +52 -8
  55. package/dist/commands/build.js.map +1 -1
  56. package/dist/commands/create.d.ts +9 -0
  57. package/dist/commands/create.js +30 -0
  58. package/dist/commands/create.js.map +1 -0
  59. package/dist/commands/dev.d.ts +2 -0
  60. package/dist/commands/dev.js +60 -6
  61. package/dist/commands/dev.js.map +1 -1
  62. package/dist/commands/start.js +13 -10
  63. package/dist/commands/start.js.map +1 -1
  64. package/dist/commands/telemetry/disable.js.map +1 -1
  65. package/dist/commands/telemetry/enable.js.map +1 -1
  66. package/dist/commands/telemetry/status.js.map +1 -1
  67. package/dist/server/asset-base-url-transform-plugin.d.ts +1 -0
  68. package/dist/server/asset-base-url-transform-plugin.js +17 -2
  69. package/dist/server/asset-base-url-transform-plugin.js.map +1 -1
  70. package/dist/server/asset-base-url-transform-plugin.test.js +80 -1
  71. package/dist/server/asset-base-url-transform-plugin.test.js.map +1 -1
  72. package/dist/server/auth.d.ts +20 -0
  73. package/dist/server/auth.js +28 -0
  74. package/dist/server/auth.js.map +1 -0
  75. package/dist/server/build-manifest.test.d.ts +1 -0
  76. package/dist/server/build-manifest.test.js +27 -0
  77. package/dist/server/build-manifest.test.js.map +1 -0
  78. package/dist/server/content-helpers.d.ts +67 -0
  79. package/dist/server/content-helpers.js +79 -0
  80. package/dist/server/content-helpers.js.map +1 -0
  81. package/dist/server/content-helpers.test.d.ts +1 -0
  82. package/dist/server/content-helpers.test.js +70 -0
  83. package/dist/server/content-helpers.test.js.map +1 -0
  84. package/dist/server/express.d.ts +2 -6
  85. package/dist/server/express.js +34 -10
  86. package/dist/server/express.js.map +1 -1
  87. package/dist/server/express.test.js +279 -71
  88. package/dist/server/express.test.js.map +1 -1
  89. package/dist/server/file-ref.d.ts +28 -0
  90. package/dist/server/file-ref.js +27 -0
  91. package/dist/server/file-ref.js.map +1 -0
  92. package/dist/server/index.d.ts +6 -3
  93. package/dist/server/index.js +5 -2
  94. package/dist/server/index.js.map +1 -1
  95. package/dist/server/inferUtilityTypes.d.ts +6 -6
  96. package/dist/server/inferUtilityTypes.js.map +1 -1
  97. package/dist/server/metric.d.ts +14 -0
  98. package/dist/server/metric.js +62 -0
  99. package/dist/server/metric.js.map +1 -0
  100. package/dist/server/middleware.d.ts +16 -3
  101. package/dist/server/middleware.js.map +1 -1
  102. package/dist/server/middleware.test-d.js.map +1 -1
  103. package/dist/server/middleware.test.js +12 -9
  104. package/dist/server/middleware.test.js.map +1 -1
  105. package/dist/server/server.d.ts +304 -73
  106. package/dist/server/server.js +438 -130
  107. package/dist/server/server.js.map +1 -1
  108. package/dist/server/templateHelper.d.ts +5 -7
  109. package/dist/server/templateHelper.js +3 -22
  110. package/dist/server/templateHelper.js.map +1 -1
  111. package/dist/server/templates.generated.d.ts +4 -0
  112. package/dist/server/templates.generated.js +47 -0
  113. package/dist/server/templates.generated.js.map +1 -0
  114. package/dist/server/tunnel-proxy-router.d.ts +7 -0
  115. package/dist/server/tunnel-proxy-router.js +110 -0
  116. package/dist/server/tunnel-proxy-router.js.map +1 -0
  117. package/dist/server/tunnel-proxy-router.test.d.ts +1 -0
  118. package/dist/server/tunnel-proxy-router.test.js +229 -0
  119. package/dist/server/tunnel-proxy-router.test.js.map +1 -0
  120. package/dist/server/viewsDevServer.d.ts +14 -0
  121. package/dist/server/viewsDevServer.js +45 -0
  122. package/dist/server/viewsDevServer.js.map +1 -0
  123. package/dist/test/utils.d.ts +13 -21
  124. package/dist/test/utils.js +42 -37
  125. package/dist/test/utils.js.map +1 -1
  126. package/dist/test/view.test.d.ts +1 -0
  127. package/dist/test/view.test.js +568 -0
  128. package/dist/test/view.test.js.map +1 -0
  129. package/dist/version.d.ts +1 -0
  130. package/dist/version.js +3 -0
  131. package/dist/version.js.map +1 -0
  132. package/dist/web/bridges/apps-sdk/adaptor.d.ts +11 -4
  133. package/dist/web/bridges/apps-sdk/adaptor.js +59 -17
  134. package/dist/web/bridges/apps-sdk/adaptor.js.map +1 -1
  135. package/dist/web/bridges/apps-sdk/bridge.d.ts +1 -0
  136. package/dist/web/bridges/apps-sdk/bridge.js +1 -0
  137. package/dist/web/bridges/apps-sdk/bridge.js.map +1 -1
  138. package/dist/web/bridges/apps-sdk/index.js.map +1 -1
  139. package/dist/web/bridges/apps-sdk/types.d.ts +18 -6
  140. package/dist/web/bridges/apps-sdk/types.js.map +1 -1
  141. package/dist/web/bridges/apps-sdk/use-apps-sdk-context.d.ts +11 -0
  142. package/dist/web/bridges/apps-sdk/use-apps-sdk-context.js +11 -0
  143. package/dist/web/bridges/apps-sdk/use-apps-sdk-context.js.map +1 -1
  144. package/dist/web/bridges/get-adaptor.d.ts +7 -0
  145. package/dist/web/bridges/get-adaptor.js +7 -0
  146. package/dist/web/bridges/get-adaptor.js.map +1 -1
  147. package/dist/web/bridges/index.js.map +1 -1
  148. package/dist/web/bridges/mcp-app/adaptor.d.ts +21 -6
  149. package/dist/web/bridges/mcp-app/adaptor.js +127 -28
  150. package/dist/web/bridges/mcp-app/adaptor.js.map +1 -1
  151. package/dist/web/bridges/mcp-app/bridge.d.ts +4 -2
  152. package/dist/web/bridges/mcp-app/bridge.js +23 -1
  153. package/dist/web/bridges/mcp-app/bridge.js.map +1 -1
  154. package/dist/web/bridges/mcp-app/index.js.map +1 -1
  155. package/dist/web/bridges/mcp-app/types.js.map +1 -1
  156. package/dist/web/bridges/mcp-app/use-mcp-app-context.d.ts +12 -0
  157. package/dist/web/bridges/mcp-app/use-mcp-app-context.js +12 -0
  158. package/dist/web/bridges/mcp-app/use-mcp-app-context.js.map +1 -1
  159. package/dist/web/bridges/mcp-app/use-mcp-app-context.test.js.map +1 -1
  160. package/dist/web/bridges/mcp-app/view-tools.test.d.ts +1 -0
  161. package/dist/web/bridges/mcp-app/view-tools.test.js +144 -0
  162. package/dist/web/bridges/mcp-app/view-tools.test.js.map +1 -0
  163. package/dist/web/bridges/types.d.ts +112 -11
  164. package/dist/web/bridges/types.js.map +1 -1
  165. package/dist/web/bridges/use-host-context.d.ts +5 -0
  166. package/dist/web/bridges/use-host-context.js +5 -0
  167. package/dist/web/bridges/use-host-context.js.map +1 -1
  168. package/dist/web/components/modal-provider.js +2 -2
  169. package/dist/web/components/modal-provider.js.map +1 -1
  170. package/dist/web/create-store.d.ts +26 -0
  171. package/dist/web/create-store.js +43 -3
  172. package/dist/web/create-store.js.map +1 -1
  173. package/dist/web/create-store.test.js +14 -16
  174. package/dist/web/create-store.test.js.map +1 -1
  175. package/dist/web/data-llm.d.ts +34 -1
  176. package/dist/web/data-llm.js +31 -3
  177. package/dist/web/data-llm.js.map +1 -1
  178. package/dist/web/data-llm.test.js +22 -22
  179. package/dist/web/data-llm.test.js.map +1 -1
  180. package/dist/web/generate-helpers.d.ts +22 -18
  181. package/dist/web/generate-helpers.js +22 -18
  182. package/dist/web/generate-helpers.js.map +1 -1
  183. package/dist/web/generate-helpers.test-d.js +26 -26
  184. package/dist/web/generate-helpers.test-d.js.map +1 -1
  185. package/dist/web/generate-helpers.test.js.map +1 -1
  186. package/dist/web/helpers/state.d.ts +2 -2
  187. package/dist/web/helpers/state.js +11 -11
  188. package/dist/web/helpers/state.js.map +1 -1
  189. package/dist/web/helpers/state.test.js +9 -9
  190. package/dist/web/helpers/state.test.js.map +1 -1
  191. package/dist/web/hooks/index.d.ts +5 -1
  192. package/dist/web/hooks/index.js +5 -1
  193. package/dist/web/hooks/index.js.map +1 -1
  194. package/dist/web/hooks/test/utils.d.ts +6 -2
  195. package/dist/web/hooks/test/utils.js +13 -2
  196. package/dist/web/hooks/test/utils.js.map +1 -1
  197. package/dist/web/hooks/use-call-tool.d.ts +45 -0
  198. package/dist/web/hooks/use-call-tool.js +28 -0
  199. package/dist/web/hooks/use-call-tool.js.map +1 -1
  200. package/dist/web/hooks/use-call-tool.test-d.js.map +1 -1
  201. package/dist/web/hooks/use-call-tool.test.js +27 -6
  202. package/dist/web/hooks/use-call-tool.test.js.map +1 -1
  203. package/dist/web/hooks/use-display-mode.d.ts +20 -0
  204. package/dist/web/hooks/use-display-mode.js +20 -0
  205. package/dist/web/hooks/use-display-mode.js.map +1 -1
  206. package/dist/web/hooks/use-display-mode.test-d.js.map +1 -1
  207. package/dist/web/hooks/use-display-mode.test.js.map +1 -1
  208. package/dist/web/hooks/use-download.d.ts +5 -0
  209. package/dist/web/hooks/use-download.js +8 -0
  210. package/dist/web/hooks/use-download.js.map +1 -0
  211. package/dist/web/hooks/use-download.test.d.ts +1 -0
  212. package/dist/web/hooks/use-download.test.js +95 -0
  213. package/dist/web/hooks/use-download.test.js.map +1 -0
  214. package/dist/web/hooks/use-files.d.ts +34 -1
  215. package/dist/web/hooks/use-files.js +33 -0
  216. package/dist/web/hooks/use-files.js.map +1 -1
  217. package/dist/web/hooks/use-files.test.js +22 -2
  218. package/dist/web/hooks/use-files.test.js.map +1 -1
  219. package/dist/web/hooks/use-layout.d.ts +2 -0
  220. package/dist/web/hooks/use-layout.js +2 -0
  221. package/dist/web/hooks/use-layout.js.map +1 -1
  222. package/dist/web/hooks/use-layout.test.js.map +1 -1
  223. package/dist/web/hooks/use-open-external.d.ts +17 -0
  224. package/dist/web/hooks/use-open-external.js +16 -0
  225. package/dist/web/hooks/use-open-external.js.map +1 -1
  226. package/dist/web/hooks/use-open-external.test.js.map +1 -1
  227. package/dist/web/hooks/use-register-view-tool.d.ts +38 -0
  228. package/dist/web/hooks/use-register-view-tool.js +50 -0
  229. package/dist/web/hooks/use-register-view-tool.js.map +1 -0
  230. package/dist/web/hooks/use-request-close.d.ts +16 -0
  231. package/dist/web/hooks/use-request-close.js +21 -0
  232. package/dist/web/hooks/use-request-close.js.map +1 -0
  233. package/dist/web/hooks/use-request-close.test.d.ts +1 -0
  234. package/dist/web/hooks/use-request-close.test.js +52 -0
  235. package/dist/web/hooks/use-request-close.test.js.map +1 -0
  236. package/dist/web/hooks/use-request-modal.d.ts +16 -1
  237. package/dist/web/hooks/use-request-modal.js +19 -4
  238. package/dist/web/hooks/use-request-modal.js.map +1 -1
  239. package/dist/web/hooks/use-request-modal.test.js +1 -1
  240. package/dist/web/hooks/use-request-modal.test.js.map +1 -1
  241. package/dist/web/hooks/use-request-size.d.ts +20 -0
  242. package/dist/web/hooks/use-request-size.js +24 -0
  243. package/dist/web/hooks/use-request-size.js.map +1 -0
  244. package/dist/web/hooks/use-request-size.test.d.ts +1 -0
  245. package/dist/web/hooks/use-request-size.test.js +65 -0
  246. package/dist/web/hooks/use-request-size.test.js.map +1 -0
  247. package/dist/web/hooks/use-send-follow-up-message.d.ts +19 -1
  248. package/dist/web/hooks/use-send-follow-up-message.js +19 -2
  249. package/dist/web/hooks/use-send-follow-up-message.js.map +1 -1
  250. package/dist/web/hooks/use-set-open-in-app-url.d.ts +17 -0
  251. package/dist/web/hooks/use-set-open-in-app-url.js +17 -0
  252. package/dist/web/hooks/use-set-open-in-app-url.js.map +1 -1
  253. package/dist/web/hooks/use-set-open-in-app-url.test.js.map +1 -1
  254. package/dist/web/hooks/use-tool-info.d.ts +33 -0
  255. package/dist/web/hooks/use-tool-info.js +26 -0
  256. package/dist/web/hooks/use-tool-info.js.map +1 -1
  257. package/dist/web/hooks/use-tool-info.test-d.js.map +1 -1
  258. package/dist/web/hooks/use-tool-info.test.js.map +1 -1
  259. package/dist/web/hooks/use-user.d.ts +2 -0
  260. package/dist/web/hooks/use-user.js +2 -0
  261. package/dist/web/hooks/use-user.js.map +1 -1
  262. package/dist/web/hooks/use-user.test.js.map +1 -1
  263. package/dist/web/hooks/use-view-state.d.ts +25 -0
  264. package/dist/web/hooks/use-view-state.js +32 -0
  265. package/dist/web/hooks/use-view-state.js.map +1 -0
  266. package/dist/web/hooks/use-view-state.test.d.ts +1 -0
  267. package/dist/web/hooks/use-view-state.test.js +177 -0
  268. package/dist/web/hooks/use-view-state.test.js.map +1 -0
  269. package/dist/web/index.d.ts +1 -2
  270. package/dist/web/index.js +1 -2
  271. package/dist/web/index.js.map +1 -1
  272. package/dist/web/mount-view.d.ts +20 -0
  273. package/dist/web/{mount-widget.js → mount-view.js} +21 -2
  274. package/dist/web/mount-view.js.map +1 -0
  275. package/dist/web/plugin/data-llm.test.js.map +1 -1
  276. package/dist/web/plugin/plugin.d.ts +32 -1
  277. package/dist/web/plugin/plugin.js +160 -25
  278. package/dist/web/plugin/plugin.js.map +1 -1
  279. package/dist/web/plugin/scan-views.d.ts +16 -0
  280. package/dist/web/plugin/scan-views.js +88 -0
  281. package/dist/web/plugin/scan-views.js.map +1 -0
  282. package/dist/web/plugin/scan-views.test.d.ts +1 -0
  283. package/dist/web/plugin/scan-views.test.js +99 -0
  284. package/dist/web/plugin/scan-views.test.js.map +1 -0
  285. package/dist/web/plugin/transform-data-llm.js.map +1 -1
  286. package/dist/web/plugin/transform-data-llm.test.js.map +1 -1
  287. package/dist/web/plugin/validate-view.d.ts +1 -0
  288. package/dist/web/plugin/validate-view.js +9 -0
  289. package/dist/web/plugin/validate-view.js.map +1 -0
  290. package/dist/web/plugin/validate-view.test.d.ts +1 -0
  291. package/dist/web/plugin/validate-view.test.js +24 -0
  292. package/dist/web/plugin/validate-view.test.js.map +1 -0
  293. package/dist/web/proxy.js.map +1 -1
  294. package/dist/web/types.d.ts +4 -0
  295. package/dist/web/types.js.map +1 -1
  296. package/package.json +28 -12
  297. package/tsconfig.base.json +2 -0
  298. package/dist/server/monitoring.d.ts +0 -15
  299. package/dist/server/monitoring.js +0 -66
  300. package/dist/server/monitoring.js.map +0 -1
  301. package/dist/server/templates/development.hbs +0 -12
  302. package/dist/server/templates/production.hbs +0 -6
  303. package/dist/server/widgetsDevServer.d.ts +0 -13
  304. package/dist/server/widgetsDevServer.js +0 -52
  305. package/dist/server/widgetsDevServer.js.map +0 -1
  306. package/dist/test/widget.test.js +0 -263
  307. package/dist/test/widget.test.js.map +0 -1
  308. package/dist/web/hooks/use-widget-state.d.ts +0 -4
  309. package/dist/web/hooks/use-widget-state.js +0 -32
  310. package/dist/web/hooks/use-widget-state.js.map +0 -1
  311. package/dist/web/hooks/use-widget-state.test.js +0 -64
  312. package/dist/web/hooks/use-widget-state.test.js.map +0 -1
  313. package/dist/web/mount-widget.d.ts +0 -1
  314. package/dist/web/mount-widget.js.map +0 -1
  315. package/dist/web/plugin/validate-widget.d.ts +0 -5
  316. package/dist/web/plugin/validate-widget.js +0 -27
  317. package/dist/web/plugin/validate-widget.js.map +0 -1
  318. package/dist/web/plugin/validate-widget.test.js +0 -42
  319. package/dist/web/plugin/validate-widget.test.js.map +0 -1
  320. /package/dist/{test/widget.test.d.ts → cli/build-helpers.test.d.ts} +0 -0
  321. /package/dist/{web/hooks/use-widget-state.test.d.ts → cli/tunnel-control-server.test.d.ts} +0 -0
  322. /package/dist/{web/plugin/validate-widget.test.d.ts → cli/tunnel-handler.test.d.ts} +0 -0
@@ -0,0 +1,190 @@
1
+ import { EventEmitter } from "node:events";
2
+ import { Readable } from "node:stream";
3
+ import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
4
+ import { parseStdoutLine, TunnelManager } from "./tunnel.js";
5
+ describe("parseStdoutLine", () => {
6
+ it("returns a connected event when the forwarding line is seen", () => {
7
+ const result = parseStdoutLine("Forwarding: https://abc.tunnel.example -> http://localhost:3000");
8
+ expect(result).toEqual({
9
+ kind: "connected",
10
+ url: "https://abc.tunnel.example",
11
+ });
12
+ });
13
+ it("strips a trailing slash from the forwarded URL", () => {
14
+ const result = parseStdoutLine("Forwarding: https://abc.tunnel.example/ -> http://localhost:3000");
15
+ expect(result).toEqual({
16
+ kind: "connected",
17
+ url: "https://abc.tunnel.example",
18
+ });
19
+ });
20
+ it("returns a starting event for any other non-empty line", () => {
21
+ expect(parseStdoutLine("Opening tunnel...")).toEqual({
22
+ kind: "starting",
23
+ message: "Opening tunnel...",
24
+ });
25
+ });
26
+ it("returns null for empty lines", () => {
27
+ expect(parseStdoutLine("")).toBeNull();
28
+ expect(parseStdoutLine(" ")).toBeNull();
29
+ });
30
+ });
31
+ function makeFakeChild() {
32
+ const child = new EventEmitter();
33
+ child.stdout = new Readable({ read() { } });
34
+ child.stderr = new Readable({ read() { } });
35
+ child.kill = vi.fn(() => true);
36
+ return child;
37
+ }
38
+ describe("TunnelManager", () => {
39
+ beforeEach(() => {
40
+ vi.useFakeTimers();
41
+ });
42
+ afterEach(() => {
43
+ vi.useRealTimers();
44
+ });
45
+ it("starts idle", () => {
46
+ const manager = new TunnelManager({
47
+ getPort: () => 3000,
48
+ spawn: () => makeFakeChild(),
49
+ });
50
+ expect(manager.getState()).toEqual({ status: "idle" });
51
+ });
52
+ it("transitions idle -> starting -> connected as stdout reports progress", () => {
53
+ const child = makeFakeChild();
54
+ const manager = new TunnelManager({
55
+ getPort: () => 3000,
56
+ spawn: () => child,
57
+ });
58
+ const states = [];
59
+ manager.on("state", (s) => states.push(s.status));
60
+ manager.start();
61
+ child.stdout.emit("data", Buffer.from("Opening tunnel...\n"));
62
+ child.stdout.emit("data", Buffer.from("Forwarding: https://abc.tunnel.example -> http://localhost:3000\n"));
63
+ expect(states).toEqual(["starting", "starting", "connected"]);
64
+ expect(manager.getState()).toEqual({
65
+ status: "connected",
66
+ url: "https://abc.tunnel.example",
67
+ });
68
+ });
69
+ it("emits activity events for stdout lines after connect", () => {
70
+ const child = makeFakeChild();
71
+ const manager = new TunnelManager({
72
+ getPort: () => 3000,
73
+ spawn: () => child,
74
+ });
75
+ const activity = [];
76
+ manager.on("activity", (a) => activity.push({ text: a.text, level: a.level }));
77
+ manager.start();
78
+ child.stdout.emit("data", Buffer.from("Forwarding: https://abc.tunnel.example -> http://localhost:3000\n"));
79
+ child.stdout.emit("data", Buffer.from("GET /widgets/foo\n"));
80
+ child.stderr.emit("data", Buffer.from("connection wobble\n"));
81
+ expect(activity).toEqual([
82
+ { text: "GET /widgets/foo", level: "log" },
83
+ { text: "connection wobble", level: "error" },
84
+ ]);
85
+ });
86
+ it("calling start twice does not spawn twice", () => {
87
+ const spawn = vi.fn(() => makeFakeChild());
88
+ const manager = new TunnelManager({ getPort: () => 3000, spawn });
89
+ manager.start();
90
+ manager.start();
91
+ expect(spawn).toHaveBeenCalledTimes(1);
92
+ });
93
+ it("transitions to error when the connection times out", () => {
94
+ const child = makeFakeChild();
95
+ const manager = new TunnelManager({
96
+ getPort: () => 3000,
97
+ spawn: () => child,
98
+ });
99
+ manager.start();
100
+ vi.advanceTimersByTime(60_000);
101
+ expect(manager.getState().status).toBe("error");
102
+ expect(child.kill).toHaveBeenCalled();
103
+ });
104
+ it("preserves the timeout error message when the killed child later emits close", () => {
105
+ const child = makeFakeChild();
106
+ const manager = new TunnelManager({
107
+ getPort: () => 3000,
108
+ spawn: () => child,
109
+ });
110
+ manager.start();
111
+ vi.advanceTimersByTime(60_000);
112
+ // The killed child emits a non-zero close after the timeout fired.
113
+ child.emit("close", 1);
114
+ expect(manager.getState()).toEqual({
115
+ status: "error",
116
+ message: "Tunnel connection timed out after one minute",
117
+ });
118
+ });
119
+ it("stop() kills the subprocess and goes idle", () => {
120
+ const child = makeFakeChild();
121
+ const manager = new TunnelManager({
122
+ getPort: () => 3000,
123
+ spawn: () => child,
124
+ });
125
+ manager.start();
126
+ manager.stop();
127
+ expect(child.kill).toHaveBeenCalled();
128
+ expect(manager.getState()).toEqual({ status: "idle" });
129
+ });
130
+ it("subscribers get the current state on subscribe", () => {
131
+ const child = makeFakeChild();
132
+ const manager = new TunnelManager({
133
+ getPort: () => 3000,
134
+ spawn: () => child,
135
+ });
136
+ manager.start();
137
+ child.stdout.emit("data", Buffer.from("Forwarding: https://abc.tunnel.example -> http://localhost:3000\n"));
138
+ let received;
139
+ const unsubscribe = manager.subscribe((s) => {
140
+ received = s;
141
+ });
142
+ expect(received).toEqual({
143
+ status: "connected",
144
+ url: "https://abc.tunnel.example",
145
+ });
146
+ unsubscribe();
147
+ });
148
+ it("ignores deferred close from a child that was replaced via stop()+start()", () => {
149
+ const childA = makeFakeChild();
150
+ const childB = makeFakeChild();
151
+ let spawnCount = 0;
152
+ const manager = new TunnelManager({
153
+ getPort: () => 3000,
154
+ spawn: () => (spawnCount++ === 0 ? childA : childB),
155
+ });
156
+ manager.start();
157
+ manager.stop();
158
+ manager.start();
159
+ childB.stdout.emit("data", Buffer.from("Forwarding: https://abc.tunnel.example -> http://localhost:3000\n"));
160
+ expect(manager.getState()).toEqual({
161
+ status: "connected",
162
+ url: "https://abc.tunnel.example",
163
+ });
164
+ // Stale close from childA arrives only now — must not clobber state.
165
+ childA.emit("close", null);
166
+ expect(manager.getState()).toEqual({
167
+ status: "connected",
168
+ url: "https://abc.tunnel.example",
169
+ });
170
+ });
171
+ it("ignores deferred error from a child that was replaced via stop()+start()", () => {
172
+ const childA = makeFakeChild();
173
+ const childB = makeFakeChild();
174
+ let spawnCount = 0;
175
+ const manager = new TunnelManager({
176
+ getPort: () => 3000,
177
+ spawn: () => (spawnCount++ === 0 ? childA : childB),
178
+ });
179
+ manager.start();
180
+ manager.stop();
181
+ manager.start();
182
+ childB.stdout.emit("data", Buffer.from("Forwarding: https://abc.tunnel.example -> http://localhost:3000\n"));
183
+ childA.emit("error", new Error("late spawn failure"));
184
+ expect(manager.getState()).toEqual({
185
+ status: "connected",
186
+ url: "https://abc.tunnel.example",
187
+ });
188
+ });
189
+ });
190
+ //# sourceMappingURL=tunnel.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tunnel.test.js","sourceRoot":"","sources":["../../src/cli/tunnel.test.ts"],"names":[],"mappings":"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","sourcesContent":["import { EventEmitter } from \"node:events\";\nimport { Readable } from \"node:stream\";\nimport { afterEach, beforeEach, describe, expect, it, vi } from \"vitest\";\nimport { parseStdoutLine, TunnelManager, type TunnelState } from \"./tunnel.js\";\n\ndescribe(\"parseStdoutLine\", () => {\n it(\"returns a connected event when the forwarding line is seen\", () => {\n const result = parseStdoutLine(\n \"Forwarding: https://abc.tunnel.example -> http://localhost:3000\",\n );\n expect(result).toEqual({\n kind: \"connected\",\n url: \"https://abc.tunnel.example\",\n });\n });\n\n it(\"strips a trailing slash from the forwarded URL\", () => {\n const result = parseStdoutLine(\n \"Forwarding: https://abc.tunnel.example/ -> http://localhost:3000\",\n );\n expect(result).toEqual({\n kind: \"connected\",\n url: \"https://abc.tunnel.example\",\n });\n });\n\n it(\"returns a starting event for any other non-empty line\", () => {\n expect(parseStdoutLine(\"Opening tunnel...\")).toEqual({\n kind: \"starting\",\n message: \"Opening tunnel...\",\n });\n });\n\n it(\"returns null for empty lines\", () => {\n expect(parseStdoutLine(\"\")).toBeNull();\n expect(parseStdoutLine(\" \")).toBeNull();\n });\n});\n\ntype FakeChild = EventEmitter & {\n stdout: Readable;\n stderr: Readable;\n kill: ReturnType<typeof vi.fn<() => boolean>>;\n};\n\nfunction makeFakeChild(): FakeChild {\n const child = new EventEmitter() as FakeChild;\n child.stdout = new Readable({ read() {} });\n child.stderr = new Readable({ read() {} });\n child.kill = vi.fn<() => boolean>(() => true);\n return child;\n}\n\ndescribe(\"TunnelManager\", () => {\n beforeEach(() => {\n vi.useFakeTimers();\n });\n afterEach(() => {\n vi.useRealTimers();\n });\n\n it(\"starts idle\", () => {\n const manager = new TunnelManager({\n getPort: () => 3000,\n spawn: () => makeFakeChild(),\n });\n expect(manager.getState()).toEqual({ status: \"idle\" });\n });\n\n it(\"transitions idle -> starting -> connected as stdout reports progress\", () => {\n const child = makeFakeChild();\n const manager = new TunnelManager({\n getPort: () => 3000,\n spawn: () => child,\n });\n const states: string[] = [];\n manager.on(\"state\", (s: TunnelState) => states.push(s.status));\n\n manager.start();\n child.stdout.emit(\"data\", Buffer.from(\"Opening tunnel...\\n\"));\n child.stdout.emit(\n \"data\",\n Buffer.from(\n \"Forwarding: https://abc.tunnel.example -> http://localhost:3000\\n\",\n ),\n );\n\n expect(states).toEqual([\"starting\", \"starting\", \"connected\"]);\n expect(manager.getState()).toEqual({\n status: \"connected\",\n url: \"https://abc.tunnel.example\",\n });\n });\n\n it(\"emits activity events for stdout lines after connect\", () => {\n const child = makeFakeChild();\n const manager = new TunnelManager({\n getPort: () => 3000,\n spawn: () => child,\n });\n const activity: Array<{ text: string; level: string }> = [];\n manager.on(\"activity\", (a: { text: string; level: string }) =>\n activity.push({ text: a.text, level: a.level }),\n );\n\n manager.start();\n child.stdout.emit(\n \"data\",\n Buffer.from(\n \"Forwarding: https://abc.tunnel.example -> http://localhost:3000\\n\",\n ),\n );\n child.stdout.emit(\"data\", Buffer.from(\"GET /widgets/foo\\n\"));\n child.stderr.emit(\"data\", Buffer.from(\"connection wobble\\n\"));\n\n expect(activity).toEqual([\n { text: \"GET /widgets/foo\", level: \"log\" },\n { text: \"connection wobble\", level: \"error\" },\n ]);\n });\n\n it(\"calling start twice does not spawn twice\", () => {\n const spawn = vi.fn(() => makeFakeChild());\n const manager = new TunnelManager({ getPort: () => 3000, spawn });\n manager.start();\n manager.start();\n expect(spawn).toHaveBeenCalledTimes(1);\n });\n\n it(\"transitions to error when the connection times out\", () => {\n const child = makeFakeChild();\n const manager = new TunnelManager({\n getPort: () => 3000,\n spawn: () => child,\n });\n manager.start();\n\n vi.advanceTimersByTime(60_000);\n\n expect(manager.getState().status).toBe(\"error\");\n expect(child.kill).toHaveBeenCalled();\n });\n\n it(\"preserves the timeout error message when the killed child later emits close\", () => {\n const child = makeFakeChild();\n const manager = new TunnelManager({\n getPort: () => 3000,\n spawn: () => child,\n });\n manager.start();\n\n vi.advanceTimersByTime(60_000);\n // The killed child emits a non-zero close after the timeout fired.\n child.emit(\"close\", 1);\n\n expect(manager.getState()).toEqual({\n status: \"error\",\n message: \"Tunnel connection timed out after one minute\",\n });\n });\n\n it(\"stop() kills the subprocess and goes idle\", () => {\n const child = makeFakeChild();\n const manager = new TunnelManager({\n getPort: () => 3000,\n spawn: () => child,\n });\n manager.start();\n manager.stop();\n expect(child.kill).toHaveBeenCalled();\n expect(manager.getState()).toEqual({ status: \"idle\" });\n });\n\n it(\"subscribers get the current state on subscribe\", () => {\n const child = makeFakeChild();\n const manager = new TunnelManager({\n getPort: () => 3000,\n spawn: () => child,\n });\n manager.start();\n child.stdout.emit(\n \"data\",\n Buffer.from(\n \"Forwarding: https://abc.tunnel.example -> http://localhost:3000\\n\",\n ),\n );\n\n let received: TunnelState | undefined;\n const unsubscribe = manager.subscribe((s) => {\n received = s;\n });\n expect(received).toEqual({\n status: \"connected\",\n url: \"https://abc.tunnel.example\",\n });\n unsubscribe();\n });\n\n it(\"ignores deferred close from a child that was replaced via stop()+start()\", () => {\n const childA = makeFakeChild();\n const childB = makeFakeChild();\n let spawnCount = 0;\n const manager = new TunnelManager({\n getPort: () => 3000,\n spawn: () => (spawnCount++ === 0 ? childA : childB),\n });\n\n manager.start();\n manager.stop();\n manager.start();\n childB.stdout.emit(\n \"data\",\n Buffer.from(\n \"Forwarding: https://abc.tunnel.example -> http://localhost:3000\\n\",\n ),\n );\n expect(manager.getState()).toEqual({\n status: \"connected\",\n url: \"https://abc.tunnel.example\",\n });\n\n // Stale close from childA arrives only now — must not clobber state.\n childA.emit(\"close\", null);\n expect(manager.getState()).toEqual({\n status: \"connected\",\n url: \"https://abc.tunnel.example\",\n });\n });\n\n it(\"ignores deferred error from a child that was replaced via stop()+start()\", () => {\n const childA = makeFakeChild();\n const childB = makeFakeChild();\n let spawnCount = 0;\n const manager = new TunnelManager({\n getPort: () => 3000,\n spawn: () => (spawnCount++ === 0 ? childA : childB),\n });\n\n manager.start();\n manager.stop();\n manager.start();\n childB.stdout.emit(\n \"data\",\n Buffer.from(\n \"Forwarding: https://abc.tunnel.example -> http://localhost:3000\\n\",\n ),\n );\n\n childA.emit(\"error\", new Error(\"late spawn failure\"));\n expect(manager.getState()).toEqual({\n status: \"connected\",\n url: \"https://abc.tunnel.example\",\n });\n });\n});\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/cli/types.ts"],"names":[],"mappings":""}
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/cli/types.ts"],"names":[],"mappings":"","sourcesContent":["export type Message = {\n id: string;\n text: string;\n type: \"log\" | \"restart\" | \"error\";\n};\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"use-execute-steps.js","sourceRoot":"","sources":["../../src/cli/use-execute-steps.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,QAAQ,EAAE,MAAM,OAAO,CAAC;AAC9C,OAAO,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAQ9C,MAAM,CAAC,MAAM,eAAe,GAAG,CAAC,KAAoB,EAAE,EAAE;IACtD,MAAM,CAAC,WAAW,EAAE,cAAc,CAAC,GAAG,QAAQ,CAAS,CAAC,CAAC,CAAC;IAC1D,MAAM,CAAC,MAAM,EAAE,SAAS,CAAC,GAAG,QAAQ,CAClC,SAAS,CACV,CAAC;IACF,MAAM,CAAC,KAAK,EAAE,QAAQ,CAAC,GAAG,QAAQ,CAAgB,IAAI,CAAC,CAAC;IAExD,MAAM,OAAO,GAAG,WAAW,CAAC,KAAK,IAAI,EAAE;QACrC,IAAI,CAAC;YACH,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,KAAK,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;gBACtC,MAAM,IAAI,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC;gBACtB,IAAI,IAAI,EAAE,CAAC;oBACT,cAAc,CAAC,CAAC,CAAC,CAAC;oBAClB,IAAI,IAAI,CAAC,GAAG,EAAE,CAAC;wBACb,MAAM,IAAI,CAAC,GAAG,EAAE,CAAC;oBACnB,CAAC;oBACD,IAAI,IAAI,CAAC,OAAO,EAAE,CAAC;wBACjB,MAAM,UAAU,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;oBACjC,CAAC;gBACH,CAAC;YACH,CAAC;YACD,SAAS,CAAC,SAAS,CAAC,CAAC;YACrB,YAAY,CAAC,GAAG,EAAE;gBAChB,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;YAClB,CAAC,CAAC,CAAC;QACL,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,SAAS,CAAC,OAAO,CAAC,CAAC;YACnB,QAAQ,CAAC,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC;YAC3D,YAAY,CAAC,GAAG,EAAE;gBAChB,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;YAClB,CAAC,CAAC,CAAC;QACL,CAAC;IACH,CAAC,EAAE,CAAC,KAAK,CAAC,CAAC,CAAC;IAEZ,OAAO,EAAE,WAAW,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,EAAE,CAAC;AACjD,CAAC,CAAC"}
1
+ {"version":3,"file":"use-execute-steps.js","sourceRoot":"","sources":["../../src/cli/use-execute-steps.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,QAAQ,EAAE,MAAM,OAAO,CAAC;AAC9C,OAAO,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAQ9C,MAAM,CAAC,MAAM,eAAe,GAAG,CAAC,KAAoB,EAAE,EAAE;IACtD,MAAM,CAAC,WAAW,EAAE,cAAc,CAAC,GAAG,QAAQ,CAAS,CAAC,CAAC,CAAC;IAC1D,MAAM,CAAC,MAAM,EAAE,SAAS,CAAC,GAAG,QAAQ,CAClC,SAAS,CACV,CAAC;IACF,MAAM,CAAC,KAAK,EAAE,QAAQ,CAAC,GAAG,QAAQ,CAAgB,IAAI,CAAC,CAAC;IAExD,MAAM,OAAO,GAAG,WAAW,CAAC,KAAK,IAAI,EAAE;QACrC,IAAI,CAAC;YACH,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,KAAK,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;gBACtC,MAAM,IAAI,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC;gBACtB,IAAI,IAAI,EAAE,CAAC;oBACT,cAAc,CAAC,CAAC,CAAC,CAAC;oBAClB,IAAI,IAAI,CAAC,GAAG,EAAE,CAAC;wBACb,MAAM,IAAI,CAAC,GAAG,EAAE,CAAC;oBACnB,CAAC;oBACD,IAAI,IAAI,CAAC,OAAO,EAAE,CAAC;wBACjB,MAAM,UAAU,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;oBACjC,CAAC;gBACH,CAAC;YACH,CAAC;YACD,SAAS,CAAC,SAAS,CAAC,CAAC;YACrB,YAAY,CAAC,GAAG,EAAE;gBAChB,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;YAClB,CAAC,CAAC,CAAC;QACL,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,SAAS,CAAC,OAAO,CAAC,CAAC;YACnB,QAAQ,CAAC,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC;YAC3D,YAAY,CAAC,GAAG,EAAE;gBAChB,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;YAClB,CAAC,CAAC,CAAC;QACL,CAAC;IACH,CAAC,EAAE,CAAC,KAAK,CAAC,CAAC,CAAC;IAEZ,OAAO,EAAE,WAAW,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,EAAE,CAAC;AACjD,CAAC,CAAC","sourcesContent":["import { useCallback, useState } from \"react\";\nimport { runCommand } from \"./run-command.js\";\n\nexport interface CommandStep {\n label: string;\n command?: string;\n run?: () => void | Promise<void>;\n}\n\nexport const useExecuteSteps = (steps: CommandStep[]) => {\n const [currentStep, setCurrentStep] = useState<number>(0);\n const [status, setStatus] = useState<\"running\" | \"success\" | \"error\">(\n \"running\",\n );\n const [error, setError] = useState<string | null>(null);\n\n const execute = useCallback(async () => {\n try {\n for (let i = 0; i < steps.length; i++) {\n const step = steps[i];\n if (step) {\n setCurrentStep(i);\n if (step.run) {\n await step.run();\n }\n if (step.command) {\n await runCommand(step.command);\n }\n }\n }\n setStatus(\"success\");\n setImmediate(() => {\n process.exit(0);\n });\n } catch (err) {\n setStatus(\"error\");\n setError(err instanceof Error ? err.message : String(err));\n setImmediate(() => {\n process.exit(1);\n });\n }\n }, [steps]);\n\n return { currentStep, status, error, execute };\n};\n"]}
@@ -0,0 +1,3 @@
1
+ import type { Message } from "./types.js";
2
+ export type PushMessage = (text: string, type: Message["type"]) => void;
3
+ export declare function useMessages(): [Array<Message>, PushMessage];
@@ -0,0 +1,11 @@
1
+ import { randomUUID } from "node:crypto";
2
+ import { useCallback, useState } from "react";
3
+ const MAX_MESSAGES = 10;
4
+ export function useMessages() {
5
+ const [messages, setMessages] = useState([]);
6
+ const push = useCallback((text, type) => {
7
+ setMessages((prev) => [...prev, { id: randomUUID(), text, type }].slice(-MAX_MESSAGES));
8
+ }, []);
9
+ return [messages, push];
10
+ }
11
+ //# sourceMappingURL=use-messages.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"use-messages.js","sourceRoot":"","sources":["../../src/cli/use-messages.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AACzC,OAAO,EAAE,WAAW,EAAE,QAAQ,EAAE,MAAM,OAAO,CAAC;AAG9C,MAAM,YAAY,GAAG,EAAE,CAAC;AAIxB,MAAM,UAAU,WAAW;IACzB,MAAM,CAAC,QAAQ,EAAE,WAAW,CAAC,GAAG,QAAQ,CAAiB,EAAE,CAAC,CAAC;IAE7D,MAAM,IAAI,GAAG,WAAW,CAAc,CAAC,IAAI,EAAE,IAAI,EAAE,EAAE;QACnD,WAAW,CAAC,CAAC,IAAI,EAAE,EAAE,CACnB,CAAC,GAAG,IAAI,EAAE,EAAE,EAAE,EAAE,UAAU,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,YAAY,CAAC,CACjE,CAAC;IACJ,CAAC,EAAE,EAAE,CAAC,CAAC;IAEP,OAAO,CAAC,QAAQ,EAAE,IAAI,CAAC,CAAC;AAC1B,CAAC","sourcesContent":["import { randomUUID } from \"node:crypto\";\nimport { useCallback, useState } from \"react\";\nimport type { Message } from \"./types.js\";\n\nconst MAX_MESSAGES = 10;\n\nexport type PushMessage = (text: string, type: Message[\"type\"]) => void;\n\nexport function useMessages(): [Array<Message>, PushMessage] {\n const [messages, setMessages] = useState<Array<Message>>([]);\n\n const push = useCallback<PushMessage>((text, type) => {\n setMessages((prev) =>\n [...prev, { id: randomUUID(), text, type }].slice(-MAX_MESSAGES),\n );\n }, []);\n\n return [messages, push];\n}\n"]}
@@ -1,2 +1,2 @@
1
- import type { Message } from "./types.js";
2
- export declare function useNodemon(env: NodeJS.ProcessEnv): Array<Message>;
1
+ import type { PushMessage } from "./use-messages.js";
2
+ export declare function useNodemon(env: NodeJS.ProcessEnv, pushMessage: PushMessage): void;
@@ -1,11 +1,10 @@
1
- import { randomUUID } from "node:crypto";
2
1
  import { existsSync } from "node:fs";
3
2
  import { resolve } from "node:path";
4
3
  import nodemonOriginal from "nodemon";
5
- import { useEffect, useState } from "react";
4
+ import { useEffect } from "react";
6
5
  const nodemon = nodemonOriginal;
7
- export function useNodemon(env) {
8
- const [messages, setMessages] = useState([]);
6
+ const SOURCEMAP_WARNING = /^Sourcemap for ".*" points to missing source files$/;
7
+ export function useNodemon(env, pushMessage) {
9
8
  useEffect(() => {
10
9
  const configFile = resolve(process.cwd(), "nodemon.json");
11
10
  const config = existsSync(configFile)
@@ -13,31 +12,29 @@ export function useNodemon(env) {
13
12
  configFile,
14
13
  }
15
14
  : {
16
- watch: ["server/src"],
15
+ watch: ["src"],
17
16
  ext: "ts,json",
18
- exec: "tsx server/src/index.ts",
17
+ exec: "tsx src/server.ts",
19
18
  };
20
19
  nodemon({ ...config, env, stdout: false });
21
20
  const handleStdoutData = (chunk) => {
22
21
  const message = chunk.toString().trim();
23
22
  if (message) {
24
- setMessages((prev) => [
25
- ...prev,
26
- { id: randomUUID(), text: message, type: "log" },
27
- ].slice(-10));
23
+ pushMessage(message, "log");
28
24
  }
29
25
  };
30
26
  const handleStderrData = (chunk) => {
31
27
  const message = chunk.toString().trim();
32
- if (message) {
33
- setMessages((prev) => [
34
- ...prev,
35
- {
36
- id: randomUUID(),
37
- text: message,
38
- type: "error",
39
- },
40
- ].slice(-10));
28
+ if (!message) {
29
+ return;
30
+ }
31
+ // Node's source-map warnings for third-party deps (superjson, @mcp/sdk, …) — not actionable.
32
+ const filtered = message
33
+ .split("\n")
34
+ .filter((line) => !SOURCEMAP_WARNING.test(line))
35
+ .join("\n");
36
+ if (filtered) {
37
+ pushMessage(filtered, "error");
41
38
  }
42
39
  };
43
40
  const setupStdoutListener = () => {
@@ -58,10 +55,7 @@ export function useNodemon(env) {
58
55
  });
59
56
  nodemon.on("restart", (files) => {
60
57
  const restartMessage = `Server restarted due to file changes: ${files.join(", ")}`;
61
- setMessages((prev) => [
62
- ...prev,
63
- { id: randomUUID(), text: restartMessage, type: "restart" },
64
- ]);
58
+ pushMessage(restartMessage, "restart");
65
59
  setupStdoutListener();
66
60
  setupStderrListener();
67
61
  });
@@ -74,7 +68,6 @@ export function useNodemon(env) {
74
68
  }
75
69
  nodemon.emit("quit");
76
70
  };
77
- }, [env]);
78
- return messages;
71
+ }, [env, pushMessage]);
79
72
  }
80
73
  //# sourceMappingURL=use-nodemon.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"use-nodemon.js","sourceRoot":"","sources":["../../src/cli/use-nodemon.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"use-nodemon.js","sourceRoot":"","sources":["../../src/cli/use-nodemon.ts"],"names":[],"mappings":"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","sourcesContent":["import { existsSync } from \"node:fs\";\nimport { resolve } from \"node:path\";\nimport nodemonOriginal from \"nodemon\";\nimport { useEffect } from \"react\";\nimport type { ExtendedNodemon } from \"./nodemon.d.ts\";\nimport type { PushMessage } from \"./use-messages.js\";\n\nconst nodemon = nodemonOriginal as ExtendedNodemon;\n\nconst SOURCEMAP_WARNING = /^Sourcemap for \".*\" points to missing source files$/;\n\nexport function useNodemon(\n env: NodeJS.ProcessEnv,\n pushMessage: PushMessage,\n): void {\n useEffect(() => {\n const configFile = resolve(process.cwd(), \"nodemon.json\");\n\n const config = existsSync(configFile)\n ? {\n configFile,\n }\n : {\n watch: [\"src\"],\n ext: \"ts,json\",\n exec: \"tsx src/server.ts\",\n };\n\n nodemon({ ...config, env, stdout: false });\n\n const handleStdoutData = (chunk: Buffer) => {\n const message = chunk.toString().trim();\n if (message) {\n pushMessage(message, \"log\");\n }\n };\n\n const handleStderrData = (chunk: Buffer) => {\n const message = chunk.toString().trim();\n if (!message) {\n return;\n }\n // Node's source-map warnings for third-party deps (superjson, @mcp/sdk, …) — not actionable.\n const filtered = message\n .split(\"\\n\")\n .filter((line) => !SOURCEMAP_WARNING.test(line))\n .join(\"\\n\");\n if (filtered) {\n pushMessage(filtered, \"error\");\n }\n };\n\n const setupStdoutListener = () => {\n if (nodemon.stdout) {\n nodemon.stdout.off(\"data\", handleStdoutData);\n nodemon.stdout.on(\"data\", handleStdoutData);\n }\n };\n\n const setupStderrListener = () => {\n if (nodemon.stderr) {\n nodemon.stderr.off(\"data\", handleStderrData);\n nodemon.stderr.on(\"data\", handleStderrData);\n }\n };\n\n nodemon.on(\"readable\", () => {\n setupStdoutListener();\n setupStderrListener();\n });\n\n nodemon.on(\"restart\", (files: string[]) => {\n const restartMessage = `Server restarted due to file changes: ${files.join(\", \")}`;\n pushMessage(restartMessage, \"restart\");\n setupStdoutListener();\n setupStderrListener();\n });\n\n return () => {\n if (nodemon.stdout) {\n nodemon.stdout.off(\"data\", handleStdoutData);\n }\n if (nodemon.stderr) {\n nodemon.stderr.off(\"data\", handleStderrData);\n }\n nodemon.emit(\"quit\");\n };\n }, [env, pushMessage]);\n}\n"]}
@@ -0,0 +1 @@
1
+ export declare function useOpenBrowser(port: number, enabled: boolean): void;
@@ -0,0 +1,44 @@
1
+ import net from "node:net";
2
+ import open from "open";
3
+ import { useEffect, useRef } from "react";
4
+ const POLL_INTERVAL_MS = 200;
5
+ const POLL_TIMEOUT_MS = 5_000;
6
+ const isPortListening = (port) => new Promise((resolve) => {
7
+ const socket = net.createConnection({ port, host: "127.0.0.1" });
8
+ socket.once("connect", () => {
9
+ socket.end();
10
+ resolve(true);
11
+ });
12
+ socket.once("error", () => {
13
+ socket.destroy();
14
+ resolve(false);
15
+ });
16
+ });
17
+ export function useOpenBrowser(port, enabled) {
18
+ const opened = useRef(false);
19
+ useEffect(() => {
20
+ if (!enabled || opened.current) {
21
+ return;
22
+ }
23
+ let cancelled = false;
24
+ const deadline = Date.now() + POLL_TIMEOUT_MS;
25
+ const tick = async () => {
26
+ while (!cancelled && Date.now() < deadline) {
27
+ if (await isPortListening(port)) {
28
+ if (cancelled || opened.current) {
29
+ return;
30
+ }
31
+ opened.current = true;
32
+ await open(`http://localhost:${port}/`).catch(() => { });
33
+ return;
34
+ }
35
+ await new Promise((r) => setTimeout(r, POLL_INTERVAL_MS));
36
+ }
37
+ };
38
+ void tick();
39
+ return () => {
40
+ cancelled = true;
41
+ };
42
+ }, [port, enabled]);
43
+ }
44
+ //# sourceMappingURL=use-open-browser.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"use-open-browser.js","sourceRoot":"","sources":["../../src/cli/use-open-browser.ts"],"names":[],"mappings":"AAAA,OAAO,GAAG,MAAM,UAAU,CAAC;AAC3B,OAAO,IAAI,MAAM,MAAM,CAAC;AACxB,OAAO,EAAE,SAAS,EAAE,MAAM,EAAE,MAAM,OAAO,CAAC;AAE1C,MAAM,gBAAgB,GAAG,GAAG,CAAC;AAC7B,MAAM,eAAe,GAAG,KAAK,CAAC;AAE9B,MAAM,eAAe,GAAG,CAAC,IAAY,EAAoB,EAAE,CACzD,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,EAAE;IACtB,MAAM,MAAM,GAAG,GAAG,CAAC,gBAAgB,CAAC,EAAE,IAAI,EAAE,IAAI,EAAE,WAAW,EAAE,CAAC,CAAC;IACjE,MAAM,CAAC,IAAI,CAAC,SAAS,EAAE,GAAG,EAAE;QAC1B,MAAM,CAAC,GAAG,EAAE,CAAC;QACb,OAAO,CAAC,IAAI,CAAC,CAAC;IAChB,CAAC,CAAC,CAAC;IACH,MAAM,CAAC,IAAI,CAAC,OAAO,EAAE,GAAG,EAAE;QACxB,MAAM,CAAC,OAAO,EAAE,CAAC;QACjB,OAAO,CAAC,KAAK,CAAC,CAAC;IACjB,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC;AAEL,MAAM,UAAU,cAAc,CAAC,IAAY,EAAE,OAAgB;IAC3D,MAAM,MAAM,GAAG,MAAM,CAAC,KAAK,CAAC,CAAC;IAE7B,SAAS,CAAC,GAAG,EAAE;QACb,IAAI,CAAC,OAAO,IAAI,MAAM,CAAC,OAAO,EAAE,CAAC;YAC/B,OAAO;QACT,CAAC;QAED,IAAI,SAAS,GAAG,KAAK,CAAC;QACtB,MAAM,QAAQ,GAAG,IAAI,CAAC,GAAG,EAAE,GAAG,eAAe,CAAC;QAE9C,MAAM,IAAI,GAAG,KAAK,IAAI,EAAE;YACtB,OAAO,CAAC,SAAS,IAAI,IAAI,CAAC,GAAG,EAAE,GAAG,QAAQ,EAAE,CAAC;gBAC3C,IAAI,MAAM,eAAe,CAAC,IAAI,CAAC,EAAE,CAAC;oBAChC,IAAI,SAAS,IAAI,MAAM,CAAC,OAAO,EAAE,CAAC;wBAChC,OAAO;oBACT,CAAC;oBACD,MAAM,CAAC,OAAO,GAAG,IAAI,CAAC;oBACtB,MAAM,IAAI,CAAC,oBAAoB,IAAI,GAAG,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE,GAAE,CAAC,CAAC,CAAC;oBACxD,OAAO;gBACT,CAAC;gBACD,MAAM,IAAI,OAAO,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,UAAU,CAAC,CAAC,EAAE,gBAAgB,CAAC,CAAC,CAAC;YAC5D,CAAC;QACH,CAAC,CAAC;QAEF,KAAK,IAAI,EAAE,CAAC;QAEZ,OAAO,GAAG,EAAE;YACV,SAAS,GAAG,IAAI,CAAC;QACnB,CAAC,CAAC;IACJ,CAAC,EAAE,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC,CAAC;AACtB,CAAC","sourcesContent":["import net from \"node:net\";\nimport open from \"open\";\nimport { useEffect, useRef } from \"react\";\n\nconst POLL_INTERVAL_MS = 200;\nconst POLL_TIMEOUT_MS = 5_000;\n\nconst isPortListening = (port: number): Promise<boolean> =>\n new Promise((resolve) => {\n const socket = net.createConnection({ port, host: \"127.0.0.1\" });\n socket.once(\"connect\", () => {\n socket.end();\n resolve(true);\n });\n socket.once(\"error\", () => {\n socket.destroy();\n resolve(false);\n });\n });\n\nexport function useOpenBrowser(port: number, enabled: boolean): void {\n const opened = useRef(false);\n\n useEffect(() => {\n if (!enabled || opened.current) {\n return;\n }\n\n let cancelled = false;\n const deadline = Date.now() + POLL_TIMEOUT_MS;\n\n const tick = async () => {\n while (!cancelled && Date.now() < deadline) {\n if (await isPortListening(port)) {\n if (cancelled || opened.current) {\n return;\n }\n opened.current = true;\n await open(`http://localhost:${port}/`).catch(() => {});\n return;\n }\n await new Promise((r) => setTimeout(r, POLL_INTERVAL_MS));\n }\n };\n\n void tick();\n\n return () => {\n cancelled = true;\n };\n }, [port, enabled]);\n}\n"]}
@@ -0,0 +1,6 @@
1
+ import type { TunnelState } from "./use-tunnel.js";
2
+ /**
3
+ * Opens the tunnel URL in the browser once the tunnel reaches "connected"
4
+ * state. Fires at most once per mount.
5
+ */
6
+ export declare function useOpenTunnelBrowser(tunnelState: TunnelState, enabled: boolean): void;
@@ -0,0 +1,19 @@
1
+ import open from "open";
2
+ import { useEffect, useRef } from "react";
3
+ /**
4
+ * Opens the tunnel URL in the browser once the tunnel reaches "connected"
5
+ * state. Fires at most once per mount.
6
+ */
7
+ export function useOpenTunnelBrowser(tunnelState, enabled) {
8
+ const opened = useRef(false);
9
+ useEffect(() => {
10
+ if (!enabled || opened.current) {
11
+ return;
12
+ }
13
+ if (tunnelState.status === "connected") {
14
+ opened.current = true;
15
+ void open(tunnelState.url).catch(() => { });
16
+ }
17
+ }, [tunnelState, enabled]);
18
+ }
19
+ //# sourceMappingURL=use-open-tunnel-browser.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"use-open-tunnel-browser.js","sourceRoot":"","sources":["../../src/cli/use-open-tunnel-browser.ts"],"names":[],"mappings":"AAAA,OAAO,IAAI,MAAM,MAAM,CAAC;AACxB,OAAO,EAAE,SAAS,EAAE,MAAM,EAAE,MAAM,OAAO,CAAC;AAG1C;;;GAGG;AACH,MAAM,UAAU,oBAAoB,CAClC,WAAwB,EACxB,OAAgB;IAEhB,MAAM,MAAM,GAAG,MAAM,CAAC,KAAK,CAAC,CAAC;IAE7B,SAAS,CAAC,GAAG,EAAE;QACb,IAAI,CAAC,OAAO,IAAI,MAAM,CAAC,OAAO,EAAE,CAAC;YAC/B,OAAO;QACT,CAAC;QACD,IAAI,WAAW,CAAC,MAAM,KAAK,WAAW,EAAE,CAAC;YACvC,MAAM,CAAC,OAAO,GAAG,IAAI,CAAC;YACtB,KAAK,IAAI,CAAC,WAAW,CAAC,GAAG,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE,GAAE,CAAC,CAAC,CAAC;QAC7C,CAAC;IACH,CAAC,EAAE,CAAC,WAAW,EAAE,OAAO,CAAC,CAAC,CAAC;AAC7B,CAAC","sourcesContent":["import open from \"open\";\nimport { useEffect, useRef } from \"react\";\nimport type { TunnelState } from \"./use-tunnel.js\";\n\n/**\n * Opens the tunnel URL in the browser once the tunnel reaches \"connected\"\n * state. Fires at most once per mount.\n */\nexport function useOpenTunnelBrowser(\n tunnelState: TunnelState,\n enabled: boolean,\n): void {\n const opened = useRef(false);\n\n useEffect(() => {\n if (!enabled || opened.current) {\n return;\n }\n if (tunnelState.status === \"connected\") {\n opened.current = true;\n void open(tunnelState.url).catch(() => {});\n }\n }, [tunnelState, enabled]);\n}\n"]}
@@ -1,8 +1,14 @@
1
- import type { Message } from "./types.js";
2
- type TunnelState = {
3
- label: string;
4
- labelColor: "yellow" | "white" | "red";
5
- logs: Message[];
1
+ import type { PushMessage } from "./use-messages.js";
2
+ export type TunnelState = {
3
+ status: "idle";
4
+ } | {
5
+ status: "starting";
6
+ message: string;
7
+ } | {
8
+ status: "connected";
9
+ url: string;
10
+ } | {
11
+ status: "error";
12
+ message: string;
6
13
  };
7
- export declare function useTunnel(port: number | null): TunnelState;
8
- export {};
14
+ export declare function useTunnel(port: number | null, pushMessage: PushMessage, verbose: boolean, autoStart: boolean): TunnelState;