@databricks/appkit 0.0.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.
Files changed (166) hide show
  1. package/CLAUDE.md +3 -0
  2. package/DCO +25 -0
  3. package/LICENSE +203 -0
  4. package/NOTICE.md +73 -0
  5. package/README.md +35 -0
  6. package/bin/setup-claude.js +190 -0
  7. package/dist/_virtual/rolldown_runtime.js +39 -0
  8. package/dist/analytics/analytics.d.ts +31 -0
  9. package/dist/analytics/analytics.d.ts.map +1 -0
  10. package/dist/analytics/analytics.js +149 -0
  11. package/dist/analytics/analytics.js.map +1 -0
  12. package/dist/analytics/defaults.js +17 -0
  13. package/dist/analytics/defaults.js.map +1 -0
  14. package/dist/analytics/index.js +3 -0
  15. package/dist/analytics/query.js +50 -0
  16. package/dist/analytics/query.js.map +1 -0
  17. package/dist/analytics/types.d.ts +9 -0
  18. package/dist/analytics/types.d.ts.map +1 -0
  19. package/dist/app/index.d.ts +23 -0
  20. package/dist/app/index.d.ts.map +1 -0
  21. package/dist/app/index.js +49 -0
  22. package/dist/app/index.js.map +1 -0
  23. package/dist/appkit/package.js +7 -0
  24. package/dist/appkit/package.js.map +1 -0
  25. package/dist/cache/defaults.js +14 -0
  26. package/dist/cache/defaults.js.map +1 -0
  27. package/dist/cache/index.d.ts +119 -0
  28. package/dist/cache/index.d.ts.map +1 -0
  29. package/dist/cache/index.js +307 -0
  30. package/dist/cache/index.js.map +1 -0
  31. package/dist/cache/storage/defaults.js +16 -0
  32. package/dist/cache/storage/defaults.js.map +1 -0
  33. package/dist/cache/storage/index.js +4 -0
  34. package/dist/cache/storage/memory.js +87 -0
  35. package/dist/cache/storage/memory.js.map +1 -0
  36. package/dist/cache/storage/persistent.js +211 -0
  37. package/dist/cache/storage/persistent.js.map +1 -0
  38. package/dist/connectors/index.js +6 -0
  39. package/dist/connectors/lakebase/client.js +348 -0
  40. package/dist/connectors/lakebase/client.js.map +1 -0
  41. package/dist/connectors/lakebase/defaults.js +13 -0
  42. package/dist/connectors/lakebase/defaults.js.map +1 -0
  43. package/dist/connectors/lakebase/index.js +3 -0
  44. package/dist/connectors/sql-warehouse/client.js +284 -0
  45. package/dist/connectors/sql-warehouse/client.js.map +1 -0
  46. package/dist/connectors/sql-warehouse/defaults.js +12 -0
  47. package/dist/connectors/sql-warehouse/defaults.js.map +1 -0
  48. package/dist/connectors/sql-warehouse/index.js +3 -0
  49. package/dist/core/appkit.d.ts +14 -0
  50. package/dist/core/appkit.d.ts.map +1 -0
  51. package/dist/core/appkit.js +66 -0
  52. package/dist/core/appkit.js.map +1 -0
  53. package/dist/core/index.js +3 -0
  54. package/dist/index.d.ts +15 -0
  55. package/dist/index.js +21 -0
  56. package/dist/index.js.map +1 -0
  57. package/dist/plugin/dev-reader.d.ts +20 -0
  58. package/dist/plugin/dev-reader.d.ts.map +1 -0
  59. package/dist/plugin/dev-reader.js +63 -0
  60. package/dist/plugin/dev-reader.js.map +1 -0
  61. package/dist/plugin/index.js +4 -0
  62. package/dist/plugin/interceptors/cache.js +15 -0
  63. package/dist/plugin/interceptors/cache.js.map +1 -0
  64. package/dist/plugin/interceptors/retry.js +32 -0
  65. package/dist/plugin/interceptors/retry.js.map +1 -0
  66. package/dist/plugin/interceptors/telemetry.js +33 -0
  67. package/dist/plugin/interceptors/telemetry.js.map +1 -0
  68. package/dist/plugin/interceptors/timeout.js +35 -0
  69. package/dist/plugin/interceptors/timeout.js.map +1 -0
  70. package/dist/plugin/plugin.d.ts +43 -0
  71. package/dist/plugin/plugin.d.ts.map +1 -0
  72. package/dist/plugin/plugin.js +119 -0
  73. package/dist/plugin/plugin.js.map +1 -0
  74. package/dist/plugin/to-plugin.d.ts +7 -0
  75. package/dist/plugin/to-plugin.d.ts.map +1 -0
  76. package/dist/plugin/to-plugin.js +12 -0
  77. package/dist/plugin/to-plugin.js.map +1 -0
  78. package/dist/server/base-server.js +24 -0
  79. package/dist/server/base-server.js.map +1 -0
  80. package/dist/server/index.d.ts +100 -0
  81. package/dist/server/index.d.ts.map +1 -0
  82. package/dist/server/index.js +224 -0
  83. package/dist/server/index.js.map +1 -0
  84. package/dist/server/remote-tunnel/denied.html +68 -0
  85. package/dist/server/remote-tunnel/gate.js +51 -0
  86. package/dist/server/remote-tunnel/gate.js.map +1 -0
  87. package/dist/server/remote-tunnel/index.html +165 -0
  88. package/dist/server/remote-tunnel/remote-tunnel-controller.js +100 -0
  89. package/dist/server/remote-tunnel/remote-tunnel-controller.js.map +1 -0
  90. package/dist/server/remote-tunnel/remote-tunnel-manager.js +320 -0
  91. package/dist/server/remote-tunnel/remote-tunnel-manager.js.map +1 -0
  92. package/dist/server/remote-tunnel/wait.html +158 -0
  93. package/dist/server/static-server.js +47 -0
  94. package/dist/server/static-server.js.map +1 -0
  95. package/dist/server/types.d.ts +14 -0
  96. package/dist/server/types.d.ts.map +1 -0
  97. package/dist/server/utils.js +70 -0
  98. package/dist/server/utils.js.map +1 -0
  99. package/dist/server/vite-dev-server.js +103 -0
  100. package/dist/server/vite-dev-server.js.map +1 -0
  101. package/dist/shared/src/cache.d.ts +62 -0
  102. package/dist/shared/src/cache.d.ts.map +1 -0
  103. package/dist/shared/src/execute.d.ts +46 -0
  104. package/dist/shared/src/execute.d.ts.map +1 -0
  105. package/dist/shared/src/plugin.d.ts +50 -0
  106. package/dist/shared/src/plugin.d.ts.map +1 -0
  107. package/dist/shared/src/sql/helpers.d.ts +160 -0
  108. package/dist/shared/src/sql/helpers.d.ts.map +1 -0
  109. package/dist/shared/src/sql/helpers.js +103 -0
  110. package/dist/shared/src/sql/helpers.js.map +1 -0
  111. package/dist/shared/src/sql/types.d.ts +34 -0
  112. package/dist/shared/src/sql/types.d.ts.map +1 -0
  113. package/dist/shared/src/tunnel.d.ts +30 -0
  114. package/dist/shared/src/tunnel.d.ts.map +1 -0
  115. package/dist/stream/arrow-stream-processor.js +154 -0
  116. package/dist/stream/arrow-stream-processor.js.map +1 -0
  117. package/dist/stream/buffers.js +88 -0
  118. package/dist/stream/buffers.js.map +1 -0
  119. package/dist/stream/defaults.js +14 -0
  120. package/dist/stream/defaults.js.map +1 -0
  121. package/dist/stream/index.js +6 -0
  122. package/dist/stream/sse-writer.js +61 -0
  123. package/dist/stream/sse-writer.js.map +1 -0
  124. package/dist/stream/stream-manager.d.ts +27 -0
  125. package/dist/stream/stream-manager.d.ts.map +1 -0
  126. package/dist/stream/stream-manager.js +191 -0
  127. package/dist/stream/stream-manager.js.map +1 -0
  128. package/dist/stream/stream-registry.js +54 -0
  129. package/dist/stream/stream-registry.js.map +1 -0
  130. package/dist/stream/types.js +14 -0
  131. package/dist/stream/types.js.map +1 -0
  132. package/dist/stream/validator.js +25 -0
  133. package/dist/stream/validator.js.map +1 -0
  134. package/dist/telemetry/config.js +20 -0
  135. package/dist/telemetry/config.js.map +1 -0
  136. package/dist/telemetry/index.d.ts +4 -0
  137. package/dist/telemetry/index.js +8 -0
  138. package/dist/telemetry/instrumentations.js +38 -0
  139. package/dist/telemetry/instrumentations.js.map +1 -0
  140. package/dist/telemetry/noop.js +54 -0
  141. package/dist/telemetry/noop.js.map +1 -0
  142. package/dist/telemetry/telemetry-manager.js +113 -0
  143. package/dist/telemetry/telemetry-manager.js.map +1 -0
  144. package/dist/telemetry/telemetry-provider.js +82 -0
  145. package/dist/telemetry/telemetry-provider.js.map +1 -0
  146. package/dist/telemetry/types.d.ts +74 -0
  147. package/dist/telemetry/types.d.ts.map +1 -0
  148. package/dist/type-generator/vite-plugin.d.ts +22 -0
  149. package/dist/type-generator/vite-plugin.d.ts.map +1 -0
  150. package/dist/type-generator/vite-plugin.js +49 -0
  151. package/dist/type-generator/vite-plugin.js.map +1 -0
  152. package/dist/utils/databricks-client-middleware.d.ts +17 -0
  153. package/dist/utils/databricks-client-middleware.d.ts.map +1 -0
  154. package/dist/utils/databricks-client-middleware.js +117 -0
  155. package/dist/utils/databricks-client-middleware.js.map +1 -0
  156. package/dist/utils/env-validator.js +14 -0
  157. package/dist/utils/env-validator.js.map +1 -0
  158. package/dist/utils/index.js +26 -0
  159. package/dist/utils/index.js.map +1 -0
  160. package/dist/utils/merge.js +25 -0
  161. package/dist/utils/merge.js.map +1 -0
  162. package/dist/utils/vite-config-merge.js +22 -0
  163. package/dist/utils/vite-config-merge.js.map +1 -0
  164. package/llms.txt +193 -0
  165. package/package.json +70 -0
  166. package/scripts/postinstall.js +6 -0
@@ -0,0 +1,224 @@
1
+ import { instrumentations } from "../telemetry/instrumentations.js";
2
+ import "../telemetry/index.js";
3
+ import { databricksClientMiddleware } from "../utils/databricks-client-middleware.js";
4
+ import { init_utils } from "../utils/index.js";
5
+ import { Plugin } from "../plugin/plugin.js";
6
+ import { toPlugin } from "../plugin/to-plugin.js";
7
+ import "../plugin/index.js";
8
+ import { RemoteTunnelController } from "./remote-tunnel/remote-tunnel-controller.js";
9
+ import { getRoutes } from "./utils.js";
10
+ import { StaticServer } from "./static-server.js";
11
+ import { ViteDevServer } from "./vite-dev-server.js";
12
+ import path from "node:path";
13
+ import fs from "node:fs";
14
+ import dotenv from "dotenv";
15
+ import express from "express";
16
+
17
+ //#region src/server/index.ts
18
+ init_utils();
19
+ dotenv.config({ path: path.resolve(process.cwd(), "./.env") });
20
+ /**
21
+ * Server plugin for the AppKit.
22
+ *
23
+ * This plugin is responsible for starting the server and serving the static files.
24
+ * It also handles the remote tunneling for development purposes.
25
+ *
26
+ * @example
27
+ * ```ts
28
+ * createApp({
29
+ * plugins: [server(), telemetryExamples(), analytics({})],
30
+ * });
31
+ * ```
32
+ *
33
+ */
34
+ var ServerPlugin = class ServerPlugin extends Plugin {
35
+ static {
36
+ this.DEFAULT_CONFIG = {
37
+ autoStart: true,
38
+ host: process.env.FLASK_RUN_HOST || "0.0.0.0",
39
+ port: Number(process.env.DATABRICKS_APP_PORT) || 8e3
40
+ };
41
+ }
42
+ static {
43
+ this.phase = "deferred";
44
+ }
45
+ constructor(config) {
46
+ super(config);
47
+ this.name = "server";
48
+ this.envVars = [];
49
+ this.serverExtensions = [];
50
+ this.config = config;
51
+ this.serverApplication = express();
52
+ this.server = null;
53
+ this.serverExtensions = [];
54
+ this.telemetry.registerInstrumentations([instrumentations.http, instrumentations.express]);
55
+ }
56
+ /** Setup the server plugin. */
57
+ async setup() {
58
+ if (this.shouldAutoStart()) await this.start();
59
+ }
60
+ /** Get the server configuration. */
61
+ getConfig() {
62
+ const { plugins: _plugins, ...config } = this.config;
63
+ return config;
64
+ }
65
+ /** Check if the server should auto start. */
66
+ shouldAutoStart() {
67
+ return this.config.autoStart;
68
+ }
69
+ /**
70
+ * Start the server.
71
+ *
72
+ * This method starts the server and sets up the frontend.
73
+ * It also sets up the remote tunneling if enabled.
74
+ *
75
+ * @returns The express application.
76
+ */
77
+ async start() {
78
+ this.serverApplication.use(express.json());
79
+ const endpoints = await this.extendRoutes();
80
+ for (const extension of this.serverExtensions) extension(this.serverApplication);
81
+ this.remoteTunnelController = new RemoteTunnelController(this.devFileReader);
82
+ this.serverApplication.use(this.remoteTunnelController.middleware);
83
+ await this.setupFrontend(endpoints);
84
+ const server$1 = this.serverApplication.listen(this.config.port ?? ServerPlugin.DEFAULT_CONFIG.port, this.config.host ?? ServerPlugin.DEFAULT_CONFIG.host, () => this.logStartupInfo());
85
+ this.server = server$1;
86
+ this.remoteTunnelController.setServer(server$1);
87
+ process.on("SIGTERM", () => this._gracefulShutdown());
88
+ process.on("SIGINT", () => this._gracefulShutdown());
89
+ if (process.env.NODE_ENV === "development") {
90
+ const allRoutes = getRoutes(this.serverApplication._router.stack);
91
+ console.dir(allRoutes, { depth: null });
92
+ }
93
+ return this.serverApplication;
94
+ }
95
+ /**
96
+ * Get the low level node.js http server instance.
97
+ *
98
+ * Only use this method if you need to access the server instance for advanced usage like a custom websocket server, etc.
99
+ *
100
+ * @throws {Error} If the server is not started or autoStart is true.
101
+ * @returns {HTTPServer} The server instance.
102
+ */
103
+ getServer() {
104
+ if (this.shouldAutoStart()) throw new Error("Cannot get server when autoStart is true.");
105
+ if (!this.server) throw new Error("Server not started. Please start the server first by calling the start() method.");
106
+ return this.server;
107
+ }
108
+ /**
109
+ * Extend the server with custom routes or middleware.
110
+ *
111
+ * @param fn - A function that receives the express application.
112
+ * @returns The server plugin instance for chaining.
113
+ * @throws {Error} If autoStart is true.
114
+ */
115
+ extend(fn) {
116
+ if (this.shouldAutoStart()) throw new Error("Cannot extend server when autoStart is true.");
117
+ this.serverExtensions.push(fn);
118
+ return this;
119
+ }
120
+ /**
121
+ * Setup the routes with the plugins.
122
+ *
123
+ * This method goes through all the plugins and injects the routes into the server application.
124
+ * Returns a map of plugin names to their registered named endpoints.
125
+ */
126
+ async extendRoutes() {
127
+ const endpoints = {};
128
+ if (!this.config.plugins) return endpoints;
129
+ this.serverApplication.get("/health", (_, res) => {
130
+ res.status(200).json({ status: "ok" });
131
+ });
132
+ this.registerEndpoint("health", "/health");
133
+ for (const plugin of Object.values(this.config.plugins)) {
134
+ if (EXCLUDED_PLUGINS.includes(plugin.name)) continue;
135
+ if (plugin?.injectRoutes && typeof plugin.injectRoutes === "function") {
136
+ const router = express.Router();
137
+ if (plugin.requiresDatabricksClient) router.use(await databricksClientMiddleware());
138
+ plugin.injectRoutes(router);
139
+ const basePath = `/api/${plugin.name}`;
140
+ this.serverApplication.use(basePath, router);
141
+ endpoints[plugin.name] = plugin.getEndpoints();
142
+ }
143
+ }
144
+ return endpoints;
145
+ }
146
+ /**
147
+ * Setup frontend serving based on environment:
148
+ * - If staticPath is explicitly provided: use static server
149
+ * - Dev mode (no staticPath): Vite for HMR
150
+ * - Production (no staticPath): Static files auto-detected
151
+ */
152
+ async setupFrontend(endpoints) {
153
+ const isDev = process.env.NODE_ENV === "development";
154
+ if (this.config.staticPath !== void 0) {
155
+ new StaticServer(this.serverApplication, this.config.staticPath, endpoints).setup();
156
+ return;
157
+ }
158
+ if (isDev) {
159
+ this.viteDevServer = new ViteDevServer(this.serverApplication, endpoints);
160
+ await this.viteDevServer.setup();
161
+ return;
162
+ }
163
+ const staticPath = ServerPlugin.findStaticPath();
164
+ if (staticPath) new StaticServer(this.serverApplication, staticPath, endpoints).setup();
165
+ }
166
+ static findStaticPath() {
167
+ const staticPaths = [
168
+ "dist",
169
+ "client/dist",
170
+ "build",
171
+ "public",
172
+ "out"
173
+ ];
174
+ const cwd = process.cwd();
175
+ for (const p of staticPaths) {
176
+ const fullPath = path.resolve(cwd, p);
177
+ if (fs.existsSync(path.resolve(fullPath, "index.html"))) {
178
+ console.log(`Static files: serving from ${fullPath}`);
179
+ return fullPath;
180
+ }
181
+ }
182
+ }
183
+ logStartupInfo() {
184
+ const isDev = process.env.NODE_ENV === "development";
185
+ const hasExplicitStaticPath = this.config.staticPath !== void 0;
186
+ const port = this.config.port ?? ServerPlugin.DEFAULT_CONFIG.port;
187
+ const host = this.config.host ?? ServerPlugin.DEFAULT_CONFIG.host;
188
+ console.log(`Server running on http://${host}:${port}`);
189
+ if (hasExplicitStaticPath) console.log(`Mode: static (${this.config.staticPath})`);
190
+ else if (isDev) console.log("Mode: development (Vite HMR)");
191
+ else console.log("Mode: production (static)");
192
+ const remoteServerController = this.remoteTunnelController;
193
+ if (!remoteServerController) console.log("Remote tunnel: disabled (controller not initialized)");
194
+ else console.log(`Remote tunnel: ${remoteServerController.isAllowedByEnv() ? "allowed" : "blocked"}; ${remoteServerController.isActive() ? "active" : "inactive"}`);
195
+ }
196
+ async _gracefulShutdown() {
197
+ console.log("Starting graceful shutdown...");
198
+ if (this.viteDevServer) await this.viteDevServer.close();
199
+ if (this.remoteTunnelController) this.remoteTunnelController.cleanup();
200
+ if (this.config.plugins) {
201
+ for (const plugin of Object.values(this.config.plugins)) if (plugin.abortActiveOperations) try {
202
+ plugin.abortActiveOperations();
203
+ } catch (err) {
204
+ console.error(`Error aborting operations for plugin ${plugin.name}:`, err);
205
+ }
206
+ }
207
+ if (this.server) {
208
+ this.server.close(() => {
209
+ console.log("Server closed gracefully");
210
+ process.exit(0);
211
+ });
212
+ setTimeout(() => {
213
+ console.log("Force shutdown after timeout");
214
+ process.exit(1);
215
+ }, 15e3);
216
+ } else process.exit(0);
217
+ }
218
+ };
219
+ const EXCLUDED_PLUGINS = [ServerPlugin.name];
220
+ const server = toPlugin(ServerPlugin, "server");
221
+
222
+ //#endregion
223
+ export { server };
224
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","names":["server","endpoints: PluginEndpoints"],"sources":["../../src/server/index.ts"],"sourcesContent":["import fs from \"node:fs\";\nimport type { Server as HTTPServer } from \"node:http\";\nimport path from \"node:path\";\nimport dotenv from \"dotenv\";\nimport express from \"express\";\nimport type { PluginPhase } from \"shared\";\nimport { Plugin, toPlugin } from \"../plugin\";\nimport { instrumentations } from \"../telemetry\";\nimport { databricksClientMiddleware } from \"../utils\";\nimport { RemoteTunnelController } from \"./remote-tunnel/remote-tunnel-controller\";\nimport { StaticServer } from \"./static-server\";\nimport type { ServerConfig } from \"./types\";\nimport { type PluginEndpoints, getRoutes } from \"./utils\";\nimport { ViteDevServer } from \"./vite-dev-server\";\n\ndotenv.config({ path: path.resolve(process.cwd(), \"./.env\") });\n\n/**\n * Server plugin for the AppKit.\n *\n * This plugin is responsible for starting the server and serving the static files.\n * It also handles the remote tunneling for development purposes.\n *\n * @example\n * ```ts\n * createApp({\n * plugins: [server(), telemetryExamples(), analytics({})],\n * });\n * ```\n *\n */\nexport class ServerPlugin extends Plugin {\n public static DEFAULT_CONFIG = {\n autoStart: true,\n host: process.env.FLASK_RUN_HOST || \"0.0.0.0\",\n port: Number(process.env.DATABRICKS_APP_PORT) || 8000,\n };\n\n public name = \"server\" as const;\n public envVars: string[] = [];\n private serverApplication: express.Application;\n private server: HTTPServer | null;\n private viteDevServer?: ViteDevServer;\n private remoteTunnelController?: RemoteTunnelController;\n protected declare config: ServerConfig;\n private serverExtensions: ((app: express.Application) => void)[] = [];\n static phase: PluginPhase = \"deferred\";\n\n constructor(config: ServerConfig) {\n super(config);\n this.config = config;\n this.serverApplication = express();\n this.server = null;\n this.serverExtensions = [];\n this.telemetry.registerInstrumentations([\n instrumentations.http,\n instrumentations.express,\n ]);\n }\n\n /** Setup the server plugin. */\n async setup() {\n if (this.shouldAutoStart()) {\n await this.start();\n }\n }\n\n /** Get the server configuration. */\n getConfig() {\n const { plugins: _plugins, ...config } = this.config;\n\n return config;\n }\n\n /** Check if the server should auto start. */\n shouldAutoStart() {\n return this.config.autoStart;\n }\n\n /**\n * Start the server.\n *\n * This method starts the server and sets up the frontend.\n * It also sets up the remote tunneling if enabled.\n *\n * @returns The express application.\n */\n async start(): Promise<express.Application> {\n this.serverApplication.use(express.json());\n\n const endpoints = await this.extendRoutes();\n\n for (const extension of this.serverExtensions) {\n extension(this.serverApplication);\n }\n\n // register remote tunnel controller (before static/vite)\n this.remoteTunnelController = new RemoteTunnelController(\n this.devFileReader,\n );\n this.serverApplication.use(this.remoteTunnelController.middleware);\n\n await this.setupFrontend(endpoints);\n\n const server = this.serverApplication.listen(\n this.config.port ?? ServerPlugin.DEFAULT_CONFIG.port,\n this.config.host ?? ServerPlugin.DEFAULT_CONFIG.host,\n () => this.logStartupInfo(),\n );\n\n this.server = server;\n\n // attach server to remote tunnel controller\n this.remoteTunnelController.setServer(server);\n\n process.on(\"SIGTERM\", () => this._gracefulShutdown());\n process.on(\"SIGINT\", () => this._gracefulShutdown());\n\n if (process.env.NODE_ENV === \"development\") {\n const allRoutes = getRoutes(this.serverApplication._router.stack);\n console.dir(allRoutes, { depth: null });\n }\n return this.serverApplication;\n }\n\n /**\n * Get the low level node.js http server instance.\n *\n * Only use this method if you need to access the server instance for advanced usage like a custom websocket server, etc.\n *\n * @throws {Error} If the server is not started or autoStart is true.\n * @returns {HTTPServer} The server instance.\n */\n getServer(): HTTPServer {\n if (this.shouldAutoStart()) {\n throw new Error(\"Cannot get server when autoStart is true.\");\n }\n\n if (!this.server) {\n throw new Error(\n \"Server not started. Please start the server first by calling the start() method.\",\n );\n }\n\n return this.server;\n }\n\n /**\n * Extend the server with custom routes or middleware.\n *\n * @param fn - A function that receives the express application.\n * @returns The server plugin instance for chaining.\n * @throws {Error} If autoStart is true.\n */\n extend(fn: (app: express.Application) => void) {\n if (this.shouldAutoStart()) {\n throw new Error(\"Cannot extend server when autoStart is true.\");\n }\n\n this.serverExtensions.push(fn);\n return this;\n }\n\n /**\n * Setup the routes with the plugins.\n *\n * This method goes through all the plugins and injects the routes into the server application.\n * Returns a map of plugin names to their registered named endpoints.\n */\n private async extendRoutes(): Promise<PluginEndpoints> {\n const endpoints: PluginEndpoints = {};\n\n if (!this.config.plugins) return endpoints;\n\n this.serverApplication.get(\"/health\", (_, res) => {\n res.status(200).json({ status: \"ok\" });\n });\n this.registerEndpoint(\"health\", \"/health\");\n\n for (const plugin of Object.values(this.config.plugins)) {\n if (EXCLUDED_PLUGINS.includes(plugin.name)) continue;\n\n if (plugin?.injectRoutes && typeof plugin.injectRoutes === \"function\") {\n const router = express.Router();\n\n // add databricks client middleware to the router if the plugin needs the request context\n if (plugin.requiresDatabricksClient)\n router.use(await databricksClientMiddleware());\n\n plugin.injectRoutes(router);\n\n const basePath = `/api/${plugin.name}`;\n this.serverApplication.use(basePath, router);\n\n // Collect named endpoints from the plugin\n endpoints[plugin.name] = plugin.getEndpoints();\n }\n }\n\n return endpoints;\n }\n\n /**\n * Setup frontend serving based on environment:\n * - If staticPath is explicitly provided: use static server\n * - Dev mode (no staticPath): Vite for HMR\n * - Production (no staticPath): Static files auto-detected\n */\n private async setupFrontend(endpoints: PluginEndpoints) {\n const isDev = process.env.NODE_ENV === \"development\";\n const hasExplicitStaticPath = this.config.staticPath !== undefined;\n\n // explict static path provided\n if (hasExplicitStaticPath) {\n const staticServer = new StaticServer(\n this.serverApplication,\n this.config.staticPath as string,\n endpoints,\n );\n staticServer.setup();\n return;\n }\n\n // auto-detection based on environment\n if (isDev) {\n this.viteDevServer = new ViteDevServer(this.serverApplication, endpoints);\n await this.viteDevServer.setup();\n return;\n }\n\n // auto-detection based on static path\n const staticPath = ServerPlugin.findStaticPath();\n if (staticPath) {\n const staticServer = new StaticServer(\n this.serverApplication,\n staticPath,\n endpoints,\n );\n\n staticServer.setup();\n }\n }\n\n private static findStaticPath() {\n const staticPaths = [\"dist\", \"client/dist\", \"build\", \"public\", \"out\"];\n const cwd = process.cwd();\n for (const p of staticPaths) {\n const fullPath = path.resolve(cwd, p);\n if (fs.existsSync(path.resolve(fullPath, \"index.html\"))) {\n console.log(`Static files: serving from ${fullPath}`);\n return fullPath;\n }\n }\n return undefined;\n }\n\n private logStartupInfo() {\n const isDev = process.env.NODE_ENV === \"development\";\n const hasExplicitStaticPath = this.config.staticPath !== undefined;\n const port = this.config.port ?? ServerPlugin.DEFAULT_CONFIG.port;\n const host = this.config.host ?? ServerPlugin.DEFAULT_CONFIG.host;\n\n console.log(`Server running on http://${host}:${port}`);\n\n if (hasExplicitStaticPath) {\n console.log(`Mode: static (${this.config.staticPath})`);\n } else if (isDev) {\n console.log(\"Mode: development (Vite HMR)\");\n } else {\n console.log(\"Mode: production (static)\");\n }\n\n const remoteServerController = this.remoteTunnelController;\n if (!remoteServerController) {\n console.log(\"Remote tunnel: disabled (controller not initialized)\");\n } else {\n console.log(\n `Remote tunnel: ${\n remoteServerController.isAllowedByEnv() ? \"allowed\" : \"blocked\"\n }; ${remoteServerController.isActive() ? \"active\" : \"inactive\"}`,\n );\n }\n }\n\n private async _gracefulShutdown() {\n console.log(\"Starting graceful shutdown...\");\n\n if (this.viteDevServer) {\n await this.viteDevServer.close();\n }\n\n if (this.remoteTunnelController) {\n this.remoteTunnelController.cleanup();\n }\n\n // 1. abort active operations from plugins\n if (this.config.plugins) {\n for (const plugin of Object.values(this.config.plugins)) {\n if (plugin.abortActiveOperations) {\n try {\n plugin.abortActiveOperations();\n } catch (err) {\n console.error(\n `Error aborting operations for plugin ${plugin.name}:`,\n err,\n );\n }\n }\n }\n }\n\n // 2. close the server\n if (this.server) {\n this.server.close(() => {\n console.log(\"Server closed gracefully\");\n process.exit(0);\n });\n\n // 3. timeout to force shutdown after 15 seconds\n setTimeout(() => {\n console.log(\"Force shutdown after timeout\");\n process.exit(1);\n }, 15000);\n } else {\n process.exit(0);\n }\n }\n}\n\nconst EXCLUDED_PLUGINS = [ServerPlugin.name];\n\nexport const server = toPlugin<typeof ServerPlugin, ServerConfig, \"server\">(\n ServerPlugin,\n \"server\",\n);\n"],"mappings":"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"}
@@ -0,0 +1,68 @@
1
+ <html>
2
+ <head>
3
+ <meta charset="UTF-8" />
4
+ <title>Access Denied</title>
5
+ <style>
6
+ body {
7
+ background: #1b1b1d;
8
+ height: 100vh;
9
+ margin: 0;
10
+ display: flex;
11
+ align-items: center;
12
+ justify-content: center;
13
+ font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Helvetica", "Arial", sans-serif;
14
+ }
15
+ .container {
16
+ text-align: center;
17
+ padding: 50px 70px;
18
+ max-width: 500px;
19
+ }
20
+ .icon {
21
+ font-size: 64px;
22
+ margin-bottom: 24px;
23
+ }
24
+ h1 {
25
+ font-size: 1.5rem;
26
+ color: #ffffff;
27
+ margin: 0 0 16px 0;
28
+ font-weight: 500;
29
+ }
30
+ p {
31
+ font-size: 0.875rem;
32
+ color: rgba(255, 255, 255, 0.6);
33
+ line-height: 1.6;
34
+ margin: 0 0 24px 0;
35
+ }
36
+ .retry-btn {
37
+ background: #ff3621;
38
+ color: white;
39
+ border: none;
40
+ padding: 12px 24px;
41
+ border-radius: 6px;
42
+ font-size: 0.875rem;
43
+ font-weight: 500;
44
+ cursor: pointer;
45
+ text-decoration: none;
46
+ display: inline-block;
47
+ }
48
+ .retry-btn:hover {
49
+ background: #e62e1a;
50
+ }
51
+ .tunnel-id {
52
+ font-size: 0.75rem;
53
+ color: rgba(255, 255, 255, 0.4);
54
+ margin-top: 24px;
55
+ font-family: monospace;
56
+ }
57
+ </style>
58
+ </head>
59
+ <body>
60
+ <div class="container">
61
+ <div class="icon">🚫</div>
62
+ <h1>Access Denied</h1>
63
+ <p>The tunnel owner has declined your request to view their local environment.</p>
64
+ <a href="?dev={{tunnelId}}&retry=true" class="retry-btn">Request Access Again</a>
65
+ <div class="tunnel-id">Tunnel ID: {{tunnelId}}</div>
66
+ </div>
67
+ </body>
68
+ </html>
@@ -0,0 +1,51 @@
1
+ //#region src/server/remote-tunnel/gate.ts
2
+ /** Assets prefixes that are served through the remote tunnel */
3
+ const REMOTE_TUNNEL_ASSET_PREFIXES = [
4
+ "/@vite/",
5
+ "/@fs/",
6
+ "/node_modules/.vite/deps/",
7
+ "/node_modules/vite/",
8
+ "/src/",
9
+ "/@react-refresh"
10
+ ];
11
+ /**
12
+ * Check if the server is running in local development mode
13
+ * - NODE_ENV is set to "development"
14
+ * @param env - the environment variables
15
+ * @returns true if the server is running in local development mode
16
+ */
17
+ function isLocalDev(env = process.env) {
18
+ return env.NODE_ENV === "development";
19
+ }
20
+ /**
21
+ * Check if the environment allows the remote tunnel
22
+ * - not in local development mode
23
+ * - DISABLE_REMOTE_SERVING is not set to "true"
24
+ * - DATABRICKS_CLIENT_SECRET is set
25
+ * @param env - the environment variables
26
+ * @returns true if the environment allows the remote tunnel
27
+ */
28
+ function isRemoteTunnelAllowedByEnv(env = process.env) {
29
+ return !isLocalDev(env) && env.DISABLE_REMOTE_SERVING !== "true" && Boolean(env.DATABRICKS_CLIENT_SECRET);
30
+ }
31
+ /**
32
+ * Check if the request has a dev query parameter
33
+ * @param req - the request
34
+ * @returns true if the request has a dev query parameter
35
+ */
36
+ function hasDevQuery(req) {
37
+ return "dev" in req.query;
38
+ }
39
+ /**
40
+ * Check if the request is an asset request
41
+ * @param req - the request
42
+ * @returns true if the request is an asset request
43
+ */
44
+ function isRemoteTunnelAssetRequest(req) {
45
+ const path = req.originalUrl;
46
+ return REMOTE_TUNNEL_ASSET_PREFIXES.some((prefix) => path.startsWith(prefix));
47
+ }
48
+
49
+ //#endregion
50
+ export { REMOTE_TUNNEL_ASSET_PREFIXES, hasDevQuery, isLocalDev, isRemoteTunnelAllowedByEnv, isRemoteTunnelAssetRequest };
51
+ //# sourceMappingURL=gate.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"gate.js","names":[],"sources":["../../../src/server/remote-tunnel/gate.ts"],"sourcesContent":["import type express from \"express\";\n\n/** Assets prefixes that are served through the remote tunnel */\nexport const REMOTE_TUNNEL_ASSET_PREFIXES = [\n \"/@vite/\",\n \"/@fs/\",\n \"/node_modules/.vite/deps/\",\n \"/node_modules/vite/\",\n \"/src/\",\n \"/@react-refresh\",\n];\n\n/**\n * Check if the server is running in local development mode\n * - NODE_ENV is set to \"development\"\n * @param env - the environment variables\n * @returns true if the server is running in local development mode\n */\nexport function isLocalDev(env: NodeJS.ProcessEnv = process.env): boolean {\n return env.NODE_ENV === \"development\";\n}\n\n/**\n * Check if the environment allows the remote tunnel\n * - not in local development mode\n * - DISABLE_REMOTE_SERVING is not set to \"true\"\n * - DATABRICKS_CLIENT_SECRET is set\n * @param env - the environment variables\n * @returns true if the environment allows the remote tunnel\n */\nexport function isRemoteTunnelAllowedByEnv(\n env: NodeJS.ProcessEnv = process.env,\n): boolean {\n return (\n !isLocalDev(env) &&\n env.DISABLE_REMOTE_SERVING !== \"true\" &&\n Boolean(env.DATABRICKS_CLIENT_SECRET)\n );\n}\n\n/**\n * Check if the request has a dev query parameter\n * @param req - the request\n * @returns true if the request has a dev query parameter\n */\nexport function hasDevQuery(req: express.Request): boolean {\n const queryParams = req.query;\n\n return \"dev\" in queryParams;\n}\n\n/**\n * Check if the request is an asset request\n * @param req - the request\n * @returns true if the request is an asset request\n */\nexport function isRemoteTunnelAssetRequest(req: express.Request): boolean {\n const path = req.originalUrl;\n return REMOTE_TUNNEL_ASSET_PREFIXES.some((prefix) => path.startsWith(prefix));\n}\n"],"mappings":";;AAGA,MAAa,+BAA+B;CAC1C;CACA;CACA;CACA;CACA;CACA;CACD;;;;;;;AAQD,SAAgB,WAAW,MAAyB,QAAQ,KAAc;AACxE,QAAO,IAAI,aAAa;;;;;;;;;;AAW1B,SAAgB,2BACd,MAAyB,QAAQ,KACxB;AACT,QACE,CAAC,WAAW,IAAI,IAChB,IAAI,2BAA2B,UAC/B,QAAQ,IAAI,yBAAyB;;;;;;;AASzC,SAAgB,YAAY,KAA+B;AAGzD,QAAO,SAFa,IAAI;;;;;;;AAU1B,SAAgB,2BAA2B,KAA+B;CACxE,MAAM,OAAO,IAAI;AACjB,QAAO,6BAA6B,MAAM,WAAW,KAAK,WAAW,OAAO,CAAC"}
@@ -0,0 +1,165 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8" />
5
+ <title>Local Environment Preview</title>
6
+ </head>
7
+ <body>
8
+ <div id="root">
9
+ <style>
10
+ body {
11
+ background: #1b1b1d;
12
+ height: 100vh;
13
+ margin: 0;
14
+ display: flex;
15
+ align-items: center;
16
+ justify-content: center;
17
+ font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto",
18
+ "Helvetica", "Arial", sans-serif;
19
+ }
20
+ .loader-container {
21
+ display: flex;
22
+ flex-direction: column;
23
+ align-items: center;
24
+ padding: 50px 70px;
25
+ }
26
+ .logo {
27
+ width: 64px;
28
+ height: 64px;
29
+ margin-bottom: 32px;
30
+ position: relative;
31
+ }
32
+ .logo svg {
33
+ width: 100%;
34
+ height: 100%;
35
+ }
36
+ .spinner {
37
+ width: 32px;
38
+ height: 32px;
39
+ border: 3px solid rgba(255, 255, 255, 0.1);
40
+ border-top: 3px solid #ff3621;
41
+ border-radius: 50%;
42
+ animation: spin 0.8s linear infinite;
43
+ margin-bottom: 32px;
44
+ }
45
+ @keyframes spin {
46
+ 0% {
47
+ transform: rotate(0deg);
48
+ }
49
+ 100% {
50
+ transform: rotate(360deg);
51
+ }
52
+ }
53
+ .loading-text {
54
+ font-size: 1.125rem;
55
+ color: #ffffff;
56
+ letter-spacing: 0.02em;
57
+ font-weight: 400;
58
+ text-align: center;
59
+ margin-top: 0;
60
+ opacity: 0.9;
61
+ }
62
+ .databricks-brand {
63
+ font-size: 0.875rem;
64
+ color: rgba(255, 255, 255, 0.5);
65
+ margin-top: 16px;
66
+ letter-spacing: 0.05em;
67
+ text-transform: uppercase;
68
+ font-weight: 500;
69
+ }
70
+ .dots {
71
+ display: inline-block;
72
+ animation: dots 1.5s steps(4, end) infinite;
73
+ }
74
+ @keyframes dots {
75
+ 0%,
76
+ 20% {
77
+ content: ".";
78
+ }
79
+ 40% {
80
+ content: "..";
81
+ }
82
+ 60%,
83
+ 100% {
84
+ content: "...";
85
+ }
86
+ }
87
+ </style>
88
+ <div class="loader-container">
89
+ <div class="logo">
90
+ <svg
91
+ version="1.1"
92
+ id="Layer_1"
93
+ xmlns:x="ns_extend;"
94
+ xmlns:i="ns_ai;"
95
+ xmlns:graph="ns_graphs;"
96
+ xmlns="http://www.w3.org/2000/svg"
97
+ xmlns:xlink="http://www.w3.org/1999/xlink"
98
+ x="0px"
99
+ y="0px"
100
+ viewBox="0 0 40.1 42"
101
+ style="enable-background: new 0 0 40.1 42"
102
+ xml:space="preserve"
103
+ >
104
+ <style type="text/css">
105
+ .st0 {
106
+ fill: #ff3621;
107
+ }
108
+ </style>
109
+ <metadata>
110
+ <sfw xmlns="ns_sfw;">
111
+ <slices></slices>
112
+ <sliceSourceBounds
113
+ bottomLeftOrigin="true"
114
+ height="42"
115
+ width="40.1"
116
+ x="-69.1"
117
+ y="-10.5"
118
+ ></sliceSourceBounds>
119
+ </sfw>
120
+ </metadata>
121
+ <g>
122
+ <path
123
+ class="st0"
124
+ d="M40.1,31.1v-7.4l-0.8-0.5L20.1,33.7l-18.2-10l0-4.3l18.2,9.9l20.1-10.9v-7.3l-0.8-0.5L20.1,21.2L2.6,11.6
125
+ L20.1,2l14.1,7.7l1.1-0.6V8.3L20.1,0L0,10.9V12L20.1,23l18.2-10v4.4l-18.2,10L0.8,16.8L0,17.3v7.4l20.1,10.9l18.2-9.9v4.3l-18.2,10
126
+ L0.8,29.5L0,30v1.1L20.1,42L40.1,31.1z"
127
+ ></path>
128
+ </g>
129
+ </svg>
130
+ </div>
131
+
132
+ <div class="spinner"></div>
133
+ <div class="loading-text">
134
+ Loading local environment<span class="dots">...</span>
135
+ </div>
136
+ <div class="databricks-brand">Databricks</div>
137
+ </div>
138
+ </div>
139
+ <script type="module">
140
+ import RefreshRuntime from "/@react-refresh";
141
+ if (!window.__vite_plugin_react_preamble_installed__) {
142
+ RefreshRuntime.injectIntoGlobalHook(window);
143
+ window.$RefreshReg$ = () => {};
144
+ window.$RefreshSig$ = () => (type) => type;
145
+ window.__vite_plugin_react_preamble_installed__ = true;
146
+ }
147
+ </script>
148
+
149
+ <script id="vite-client" type="module" src="/@vite/client"></script>
150
+ <script type="module" src="/src/main.tsx"></script>
151
+ <script>
152
+ const viteClient = document.getElementById("vite-client");
153
+ if (viteClient) {
154
+ viteClient.onload = () => {
155
+ console.log("Vite client loaded");
156
+ };
157
+ viteClient.onerror = () => {
158
+ setTimeout(() => {
159
+ window.location.reload();
160
+ }, 1000);
161
+ };
162
+ }
163
+ </script>
164
+ </body>
165
+ </html>
@@ -0,0 +1,100 @@
1
+ import { hasDevQuery, isLocalDev, isRemoteTunnelAllowedByEnv, isRemoteTunnelAssetRequest } from "./gate.js";
2
+
3
+ //#region src/server/remote-tunnel/remote-tunnel-controller.ts
4
+ /**
5
+ * Controller for the remote tunnel
6
+ *
7
+ * - Reads files from the dev file reader
8
+ * - Manages the remote tunnel manager
9
+ * - Sets up the web socket
10
+ * - Cleans up the remote tunnel
11
+ */
12
+ var RemoteTunnelController = class {
13
+ constructor(devFileReader) {
14
+ this.middleware = async (req, res, next) => {
15
+ if (isLocalDev()) return next();
16
+ if (!this.isAllowedByEnv()) return next();
17
+ const wantsDev = hasDevQuery(req);
18
+ const wantsRemoteAssets = isRemoteTunnelAssetRequest(req);
19
+ if (!wantsDev && !wantsRemoteAssets) return next();
20
+ const manager = await this.getOrInitManager();
21
+ if (!manager) return next();
22
+ if (wantsDev) return manager.devModeMiddleware()(req, res, next);
23
+ try {
24
+ await manager.assetMiddleware()(req, res);
25
+ } catch (error) {
26
+ next(error);
27
+ }
28
+ };
29
+ this.devFileReader = devFileReader;
30
+ this.manager = null;
31
+ this.initPromise = null;
32
+ this.wsReady = false;
33
+ }
34
+ /**
35
+ * Set the server instance
36
+ * @param server
37
+ */
38
+ setServer(server) {
39
+ this.server = server;
40
+ this.maybeSetupWebSocket();
41
+ }
42
+ /** Check if the remote tunnel is active */
43
+ isActive() {
44
+ return this.manager != null;
45
+ }
46
+ /** Check if the remote tunnel is allowed by the environment */
47
+ isAllowedByEnv() {
48
+ return isRemoteTunnelAllowedByEnv();
49
+ }
50
+ /** Cleanup the remote tunnel */
51
+ cleanup() {
52
+ try {
53
+ this.manager?.cleanup();
54
+ } finally {
55
+ this.manager = null;
56
+ this.initPromise = null;
57
+ this.wsReady = false;
58
+ }
59
+ }
60
+ /**
61
+ * Get or initialize the remote tunnel manager
62
+ * - If the manager is already initialized, return it
63
+ * - If the manager is not initialized, initialize it
64
+ * - If the manager is not allowed by the environment, return null
65
+ * @returns the remote tunnel manager
66
+ */
67
+ async getOrInitManager() {
68
+ if (this.manager) return this.manager;
69
+ if (this.initPromise) return await this.initPromise;
70
+ this.initPromise = (async () => {
71
+ if (isLocalDev() || !isRemoteTunnelAllowedByEnv()) return null;
72
+ const remoteTunnelManager = new (await (import("./remote-tunnel-manager.js"))).RemoteTunnelManager(this.devFileReader);
73
+ this.manager = remoteTunnelManager;
74
+ this.maybeSetupWebSocket();
75
+ console.log("RemoteTunnel: initialized (on-demand)");
76
+ return remoteTunnelManager;
77
+ })();
78
+ return this.initPromise;
79
+ }
80
+ /**
81
+ * Setup the web socket
82
+ * - If the server is not set, return
83
+ * - If the manager is not set, return
84
+ * - If the web socket is already setup, return
85
+ * - Setup the web socket
86
+ */
87
+ maybeSetupWebSocket() {
88
+ if (!this.server) return;
89
+ if (!this.manager) return;
90
+ if (this.wsReady) return;
91
+ this.manager.setServer(this.server);
92
+ this.manager.setupWebSocket();
93
+ this.wsReady = true;
94
+ console.log("RemoteTunnel: web socket setup complete");
95
+ }
96
+ };
97
+
98
+ //#endregion
99
+ export { RemoteTunnelController };
100
+ //# sourceMappingURL=remote-tunnel-controller.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"remote-tunnel-controller.js","names":[],"sources":["../../../src/server/remote-tunnel/remote-tunnel-controller.ts"],"sourcesContent":["import type { Server as HTTPServer } from \"node:http\";\nimport type express from \"express\";\nimport type { DevFileReader } from \"../../plugin/dev-reader\";\nimport {\n hasDevQuery,\n isLocalDev,\n isRemoteTunnelAllowedByEnv,\n isRemoteTunnelAssetRequest,\n} from \"./gate\";\nimport type { RemoteTunnelManager } from \"./remote-tunnel-manager\";\n\n/**\n * Controller for the remote tunnel\n *\n * - Reads files from the dev file reader\n * - Manages the remote tunnel manager\n * - Sets up the web socket\n * - Cleans up the remote tunnel\n */\nexport class RemoteTunnelController {\n private devFileReader: DevFileReader;\n private server?: HTTPServer;\n private manager: RemoteTunnelManager | null;\n private initPromise: Promise<RemoteTunnelManager | null> | null;\n private wsReady: boolean;\n\n constructor(devFileReader: DevFileReader) {\n this.devFileReader = devFileReader;\n this.manager = null;\n this.initPromise = null;\n this.wsReady = false;\n }\n\n /**\n * Set the server instance\n * @param server\n */\n setServer(server: HTTPServer) {\n this.server = server;\n this.maybeSetupWebSocket();\n }\n\n /** Check if the remote tunnel is active */\n isActive(): boolean {\n return this.manager != null;\n }\n\n /** Check if the remote tunnel is allowed by the environment */\n isAllowedByEnv(): boolean {\n return isRemoteTunnelAllowedByEnv();\n }\n\n /**\n * Middleware for the remote tunnel\n * - Hard blocks in local dev\n * - Blocks when not allowed by env\n * - Handles dev query and asset requests\n * @param req - the request\n * @param res - the response\n * @param next - the next function\n * @returns the next function\n */\n middleware: express.RequestHandler = async (req, res, next) => {\n // hard blocker in local dev\n if (isLocalDev()) return next();\n\n // if not allowed by env, block\n if (!this.isAllowedByEnv()) return next();\n\n const wantsDev = hasDevQuery(req);\n const wantsRemoteAssets = isRemoteTunnelAssetRequest(req);\n\n // if not wants dev and not wants remote assets, skip\n if (!wantsDev && !wantsRemoteAssets) return next();\n\n const manager = await this.getOrInitManager();\n // if no manager, skip\n if (!manager) return next();\n\n // dev query present, let manager handle it\n if (wantsDev) {\n return manager.devModeMiddleware()(req, res, next);\n }\n\n // otherwise, handle vite asset fetch paths through the tunnel\n try {\n await manager.assetMiddleware()(req, res);\n } catch (error) {\n next(error);\n }\n };\n\n /** Cleanup the remote tunnel */\n cleanup() {\n try {\n this.manager?.cleanup();\n } finally {\n this.manager = null;\n this.initPromise = null;\n this.wsReady = false;\n }\n }\n\n /**\n * Get or initialize the remote tunnel manager\n * - If the manager is already initialized, return it\n * - If the manager is not initialized, initialize it\n * - If the manager is not allowed by the environment, return null\n * @returns the remote tunnel manager\n */\n private async getOrInitManager(): Promise<RemoteTunnelManager | null> {\n if (this.manager) return this.manager;\n if (this.initPromise) return await this.initPromise;\n\n this.initPromise = (async () => {\n // double check gate\n if (isLocalDev() || !isRemoteTunnelAllowedByEnv()) return null;\n const mod = await import(\"./remote-tunnel-manager\");\n const remoteTunnelManager = new mod.RemoteTunnelManager(\n this.devFileReader,\n );\n this.manager = remoteTunnelManager;\n\n // attach server + ws setup\n this.maybeSetupWebSocket();\n\n console.log(\"RemoteTunnel: initialized (on-demand)\");\n return remoteTunnelManager;\n })();\n\n return this.initPromise;\n }\n\n /**\n * Setup the web socket\n * - If the server is not set, return\n * - If the manager is not set, return\n * - If the web socket is already setup, return\n * - Setup the web socket\n */\n private maybeSetupWebSocket() {\n if (!this.server) return;\n if (!this.manager) return;\n if (this.wsReady) return;\n\n this.manager.setServer(this.server);\n this.manager.setupWebSocket();\n this.wsReady = true;\n\n console.log(\"RemoteTunnel: web socket setup complete\");\n }\n}\n"],"mappings":";;;;;;;;;;;AAmBA,IAAa,yBAAb,MAAoC;CAOlC,YAAY,eAA8B;oBAoCL,OAAO,KAAK,KAAK,SAAS;AAE7D,OAAI,YAAY,CAAE,QAAO,MAAM;AAG/B,OAAI,CAAC,KAAK,gBAAgB,CAAE,QAAO,MAAM;GAEzC,MAAM,WAAW,YAAY,IAAI;GACjC,MAAM,oBAAoB,2BAA2B,IAAI;AAGzD,OAAI,CAAC,YAAY,CAAC,kBAAmB,QAAO,MAAM;GAElD,MAAM,UAAU,MAAM,KAAK,kBAAkB;AAE7C,OAAI,CAAC,QAAS,QAAO,MAAM;AAG3B,OAAI,SACF,QAAO,QAAQ,mBAAmB,CAAC,KAAK,KAAK,KAAK;AAIpD,OAAI;AACF,UAAM,QAAQ,iBAAiB,CAAC,KAAK,IAAI;YAClC,OAAO;AACd,SAAK,MAAM;;;AA7Db,OAAK,gBAAgB;AACrB,OAAK,UAAU;AACf,OAAK,cAAc;AACnB,OAAK,UAAU;;;;;;CAOjB,UAAU,QAAoB;AAC5B,OAAK,SAAS;AACd,OAAK,qBAAqB;;;CAI5B,WAAoB;AAClB,SAAO,KAAK,WAAW;;;CAIzB,iBAA0B;AACxB,SAAO,4BAA4B;;;CA4CrC,UAAU;AACR,MAAI;AACF,QAAK,SAAS,SAAS;YACf;AACR,QAAK,UAAU;AACf,QAAK,cAAc;AACnB,QAAK,UAAU;;;;;;;;;;CAWnB,MAAc,mBAAwD;AACpE,MAAI,KAAK,QAAS,QAAO,KAAK;AAC9B,MAAI,KAAK,YAAa,QAAO,MAAM,KAAK;AAExC,OAAK,eAAe,YAAY;AAE9B,OAAI,YAAY,IAAI,CAAC,4BAA4B,CAAE,QAAO;GAE1D,MAAM,sBAAsB,KADhB,OAAM,OAAO,gCACW,oBAClC,KAAK,cACN;AACD,QAAK,UAAU;AAGf,QAAK,qBAAqB;AAE1B,WAAQ,IAAI,wCAAwC;AACpD,UAAO;MACL;AAEJ,SAAO,KAAK;;;;;;;;;CAUd,AAAQ,sBAAsB;AAC5B,MAAI,CAAC,KAAK,OAAQ;AAClB,MAAI,CAAC,KAAK,QAAS;AACnB,MAAI,KAAK,QAAS;AAElB,OAAK,QAAQ,UAAU,KAAK,OAAO;AACnC,OAAK,QAAQ,gBAAgB;AAC7B,OAAK,UAAU;AAEf,UAAQ,IAAI,0CAA0C"}