@swissjs/swite 0.3.0

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 (163) hide show
  1. package/.changeset/config.json +11 -0
  2. package/.github/workflows/ci.yml +59 -0
  3. package/.github/workflows/publish.yml +50 -0
  4. package/.github/workflows/release.yml +53 -0
  5. package/BUILD_ANALYSIS.md +89 -0
  6. package/BUILD_STRATEGY.md +75 -0
  7. package/CHANGELOG.md +53 -0
  8. package/DIRECTIVE.md +488 -0
  9. package/__tests__/css-extraction.test.ts +261 -0
  10. package/__tests__/css-injection-integration.test.ts +247 -0
  11. package/__tests__/css-middleware.test.ts +191 -0
  12. package/__tests__/import-rewriter-bug.test.ts +135 -0
  13. package/dist/builder.d.ts +36 -0
  14. package/dist/builder.d.ts.map +1 -0
  15. package/dist/builder.js +772 -0
  16. package/dist/cache/compilation-cache.d.ts +33 -0
  17. package/dist/cache/compilation-cache.d.ts.map +1 -0
  18. package/dist/cache/compilation-cache.js +130 -0
  19. package/dist/cli.d.ts +3 -0
  20. package/dist/cli.d.ts.map +1 -0
  21. package/dist/cli.js +85 -0
  22. package/dist/config-loader.d.ts +8 -0
  23. package/dist/config-loader.d.ts.map +1 -0
  24. package/dist/config-loader.js +40 -0
  25. package/dist/config.d.ts +29 -0
  26. package/dist/config.d.ts.map +1 -0
  27. package/dist/config.js +7 -0
  28. package/dist/dev/pythonDevManager.d.ts +12 -0
  29. package/dist/dev/pythonDevManager.d.ts.map +1 -0
  30. package/dist/dev/pythonDevManager.js +85 -0
  31. package/dist/env.d.ts +19 -0
  32. package/dist/env.d.ts.map +1 -0
  33. package/dist/env.js +112 -0
  34. package/dist/handlers/base-handler.d.ts +21 -0
  35. package/dist/handlers/base-handler.d.ts.map +1 -0
  36. package/dist/handlers/base-handler.js +38 -0
  37. package/dist/handlers/js-handler.d.ts +10 -0
  38. package/dist/handlers/js-handler.d.ts.map +1 -0
  39. package/dist/handlers/js-handler.js +87 -0
  40. package/dist/handlers/mjs-handler.d.ts +8 -0
  41. package/dist/handlers/mjs-handler.d.ts.map +1 -0
  42. package/dist/handlers/mjs-handler.js +44 -0
  43. package/dist/handlers/node-module-handler.d.ts +16 -0
  44. package/dist/handlers/node-module-handler.d.ts.map +1 -0
  45. package/dist/handlers/node-module-handler.js +267 -0
  46. package/dist/handlers/ts-handler.d.ts +11 -0
  47. package/dist/handlers/ts-handler.d.ts.map +1 -0
  48. package/dist/handlers/ts-handler.js +120 -0
  49. package/dist/handlers/ui-handler.d.ts +12 -0
  50. package/dist/handlers/ui-handler.d.ts.map +1 -0
  51. package/dist/handlers/ui-handler.js +182 -0
  52. package/dist/handlers/uix-handler.d.ts +12 -0
  53. package/dist/handlers/uix-handler.d.ts.map +1 -0
  54. package/dist/handlers/uix-handler.js +135 -0
  55. package/dist/hmr.d.ts +20 -0
  56. package/dist/hmr.d.ts.map +1 -0
  57. package/dist/hmr.js +265 -0
  58. package/dist/import-rewriter.d.ts +3 -0
  59. package/dist/import-rewriter.d.ts.map +1 -0
  60. package/dist/import-rewriter.js +351 -0
  61. package/dist/index.d.ts +14 -0
  62. package/dist/index.d.ts.map +1 -0
  63. package/dist/index.js +13 -0
  64. package/dist/middleware/hmr-routes.d.ts +12 -0
  65. package/dist/middleware/hmr-routes.d.ts.map +1 -0
  66. package/dist/middleware/hmr-routes.js +97 -0
  67. package/dist/middleware/middleware-setup.d.ts +23 -0
  68. package/dist/middleware/middleware-setup.d.ts.map +1 -0
  69. package/dist/middleware/middleware-setup.js +596 -0
  70. package/dist/middleware/static-files.d.ts +15 -0
  71. package/dist/middleware/static-files.d.ts.map +1 -0
  72. package/dist/middleware/static-files.js +585 -0
  73. package/dist/proxy/SwiteProxyError.d.ts +6 -0
  74. package/dist/proxy/SwiteProxyError.d.ts.map +1 -0
  75. package/dist/proxy/SwiteProxyError.js +9 -0
  76. package/dist/proxy/proxyToPython.d.ts +28 -0
  77. package/dist/proxy/proxyToPython.d.ts.map +1 -0
  78. package/dist/proxy/proxyToPython.js +66 -0
  79. package/dist/resolver/bare-import-resolver.d.ts +9 -0
  80. package/dist/resolver/bare-import-resolver.d.ts.map +1 -0
  81. package/dist/resolver/bare-import-resolver.js +363 -0
  82. package/dist/resolver/symlink-registry.d.ts +13 -0
  83. package/dist/resolver/symlink-registry.d.ts.map +1 -0
  84. package/dist/resolver/symlink-registry.js +98 -0
  85. package/dist/resolver/url-resolver.d.ts +11 -0
  86. package/dist/resolver/url-resolver.d.ts.map +1 -0
  87. package/dist/resolver/url-resolver.js +268 -0
  88. package/dist/resolver/workspace-package-resolver.d.ts +10 -0
  89. package/dist/resolver/workspace-package-resolver.d.ts.map +1 -0
  90. package/dist/resolver/workspace-package-resolver.js +185 -0
  91. package/dist/resolver.d.ts +17 -0
  92. package/dist/resolver.d.ts.map +1 -0
  93. package/dist/resolver.js +191 -0
  94. package/dist/router/file-router.d.ts +19 -0
  95. package/dist/router/file-router.d.ts.map +1 -0
  96. package/dist/router/file-router.js +114 -0
  97. package/dist/server.d.ts +22 -0
  98. package/dist/server.d.ts.map +1 -0
  99. package/dist/server.js +122 -0
  100. package/dist/utils/cdn-fallback.d.ts +14 -0
  101. package/dist/utils/cdn-fallback.d.ts.map +1 -0
  102. package/dist/utils/cdn-fallback.js +36 -0
  103. package/dist/utils/file-path-resolver.d.ts +9 -0
  104. package/dist/utils/file-path-resolver.d.ts.map +1 -0
  105. package/dist/utils/file-path-resolver.js +187 -0
  106. package/dist/utils/generate-import-map-cli.d.ts +3 -0
  107. package/dist/utils/generate-import-map-cli.d.ts.map +1 -0
  108. package/dist/utils/generate-import-map-cli.js +32 -0
  109. package/dist/utils/generate-import-map.d.ts +21 -0
  110. package/dist/utils/generate-import-map.d.ts.map +1 -0
  111. package/dist/utils/generate-import-map.js +119 -0
  112. package/dist/utils/package-finder.d.ts +24 -0
  113. package/dist/utils/package-finder.d.ts.map +1 -0
  114. package/dist/utils/package-finder.js +161 -0
  115. package/dist/utils/package-registry.d.ts +36 -0
  116. package/dist/utils/package-registry.d.ts.map +1 -0
  117. package/dist/utils/package-registry.js +159 -0
  118. package/dist/utils/workspace.d.ts +6 -0
  119. package/dist/utils/workspace.d.ts.map +1 -0
  120. package/dist/utils/workspace.js +65 -0
  121. package/docs/IMPORT_REWRITING.md +164 -0
  122. package/docs/IMPORT_REWRITING_TROUBLESHOOTING.md +139 -0
  123. package/docs/PATH_RESOLUTION_GUIDE.md +221 -0
  124. package/package.json +49 -0
  125. package/src/adapters/proxy/SwiteProxyError.ts +12 -0
  126. package/src/adapters/proxy/proxyToPython.ts +88 -0
  127. package/src/build-engine/builder.ts +960 -0
  128. package/src/cli.ts +109 -0
  129. package/src/config/config-loader.ts +46 -0
  130. package/src/config/config.ts +34 -0
  131. package/src/config/env.ts +98 -0
  132. package/src/dev-engine/handlers/base-handler.ts +68 -0
  133. package/src/dev-engine/handlers/js-handler.ts +134 -0
  134. package/src/dev-engine/handlers/mjs-handler.ts +65 -0
  135. package/src/dev-engine/handlers/node-module-handler.ts +339 -0
  136. package/src/dev-engine/handlers/ts-handler.ts +143 -0
  137. package/src/dev-engine/handlers/ui-handler.ts +105 -0
  138. package/src/dev-engine/handlers/uix-handler.ts +90 -0
  139. package/src/dev-engine/hmr/hmr-client-template.ts +122 -0
  140. package/src/dev-engine/hmr/hmr.ts +173 -0
  141. package/src/dev-engine/middleware/hmr-routes.ts +120 -0
  142. package/src/dev-engine/middleware/middleware-setup.ts +351 -0
  143. package/src/dev-engine/middleware/static-files.ts +728 -0
  144. package/src/dev-engine/pythonDevManager.ts +116 -0
  145. package/src/dev-engine/router/file-router.ts +164 -0
  146. package/src/dev-engine/server.ts +152 -0
  147. package/src/index.ts +26 -0
  148. package/src/internal/cache/compilation-cache.ts +182 -0
  149. package/src/internal/generate-import-map-cli.ts +40 -0
  150. package/src/internal/generate-import-map.ts +154 -0
  151. package/src/kernel/package-finder.ts +164 -0
  152. package/src/kernel/package-registry.ts +198 -0
  153. package/src/kernel/workspace.ts +62 -0
  154. package/src/resolution/bare-import-resolver.ts +400 -0
  155. package/src/resolution/cdn/cdn-fallback.ts +37 -0
  156. package/src/resolution/path/file-path-resolver.ts +190 -0
  157. package/src/resolution/path/path-fixup.ts +19 -0
  158. package/src/resolution/resolver.ts +198 -0
  159. package/src/resolution/rewriting/import-rewriter.ts +237 -0
  160. package/src/resolution/symlink-registry.ts +114 -0
  161. package/src/resolution/url-resolver.ts +231 -0
  162. package/src/resolution/workspace-package-resolver.ts +94 -0
  163. package/tsconfig.json +37 -0
@@ -0,0 +1,596 @@
1
+ /*
2
+ * Copyright (c) 2024 Themba Mzumara
3
+ * SWITE - SWISS Development Server
4
+ * Licensed under the MIT License.
5
+ */
6
+ import express from "express";
7
+ import { RouteScanner } from "@kibologic/plugin-file-router/core";
8
+ import { createFileWatcher } from "@kibologic/plugin-file-router/dev";
9
+ import chalk from "chalk";
10
+ import path from "path";
11
+ import fs from "fs/promises";
12
+ import { ModuleResolver } from "../resolver.js";
13
+ import { UIHandler } from "../handlers/ui-handler.js";
14
+ import { UIXHandler } from "../handlers/uix-handler.js";
15
+ import { TSHandler } from "../handlers/ts-handler.js";
16
+ import { JSHandler } from "../handlers/js-handler.js";
17
+ import { MJSHandler } from "../handlers/mjs-handler.js";
18
+ import { NodeModuleHandler } from "../handlers/node-module-handler.js";
19
+ import { setupStaticFiles, setupSPAFallback } from "./static-files.js";
20
+ import { setupHMRRoutes } from "./hmr-routes.js";
21
+ import { setupFileRouter, } from "../router/file-router.js";
22
+ import { HMREngine } from "../hmr.js";
23
+ import { findWorkspaceRoot } from "../utils/workspace.js";
24
+ import { loadImportMap } from "../utils/generate-import-map.js";
25
+ /**
26
+ * Setup all middleware for the SWITE server
27
+ */
28
+ export async function setupMiddleware(app, config) {
29
+ // Initialize file router
30
+ const fileRouterResult = await setupFileRouter({
31
+ root: config.root,
32
+ hmr: config.hmr,
33
+ });
34
+ // HMR client injection and routes endpoint
35
+ setupHMRRoutes(app, {
36
+ hmr: config.hmr,
37
+ routes: fileRouterResult.routes,
38
+ });
39
+ // EARLY REQUEST LOGGER - catch ALL requests before any middleware
40
+ app.use((req, res, next) => {
41
+ const originalUrl = req.originalUrl || req.url;
42
+ const urlWithoutQuery = originalUrl.split("?")[0];
43
+ // Log EXACT /src/index.ui match (the main app entry point)
44
+ if (urlWithoutQuery === "/src/index.ui") {
45
+ console.log(chalk.red(`[EARLY LOGGER] ⚡⚡⚡ EXACT /src/index.ui REQUEST: ${req.method} ${originalUrl}`));
46
+ console.log(chalk.red(`[EARLY LOGGER] urlWithoutQuery: ${urlWithoutQuery}`));
47
+ console.log(chalk.red(`[EARLY LOGGER] User-Agent: ${req.get("user-agent")?.substring(0, 80)}`));
48
+ console.log(chalk.red(`[EARLY LOGGER] Accept: ${req.get("accept")}`));
49
+ console.log(chalk.red(`[EARLY LOGGER] req.url: ${req.url}, req.originalUrl: ${req.originalUrl}`));
50
+ console.log(chalk.red(`[EARLY LOGGER] Headers sent? ${res.headersSent}`));
51
+ }
52
+ // Also log any other /src/ request with .ui (but not /src/index.ui)
53
+ if (urlWithoutQuery.startsWith("/src/") &&
54
+ urlWithoutQuery.endsWith(".ui") &&
55
+ urlWithoutQuery !== "/src/index.ui") {
56
+ console.log(chalk.red(`[EARLY LOGGER] ⚡ Request received: ${req.method} ${originalUrl}`));
57
+ console.log(chalk.red(`[EARLY LOGGER] User-Agent: ${req.get("user-agent")?.substring(0, 80)}`));
58
+ console.log(chalk.red(`[EARLY LOGGER] Accept: ${req.get("accept")}`));
59
+ }
60
+ next();
61
+ });
62
+ // Get workspace root for handlers
63
+ const workspaceRoot = await findWorkspaceRoot(config.root);
64
+ console.log(chalk.blue(`[SWITE] App root: ${config.root}`));
65
+ console.log(chalk.blue(`[SWITE] Detected workspace root: ${workspaceRoot}`));
66
+ // Load pre-resolved import map if available
67
+ const { join } = await import("node:path");
68
+ const importMapPath = join(config.root, ".swite", "import-map.json");
69
+ const importMap = await loadImportMap(importMapPath);
70
+ if (importMap) {
71
+ config.resolver.setImportMap(importMap);
72
+ }
73
+ else {
74
+ console.log(chalk.yellow(`[SWITE] No import map found at ${importMapPath}, using runtime resolution`));
75
+ }
76
+ // Create handlers
77
+ const handlerContext = {
78
+ resolver: config.resolver,
79
+ root: config.root,
80
+ workspaceRoot,
81
+ };
82
+ const uiHandler = new UIHandler(handlerContext);
83
+ const uixHandler = new UIXHandler(handlerContext);
84
+ const tsHandler = new TSHandler(handlerContext);
85
+ const jsHandler = new JSHandler(handlerContext);
86
+ const mjsHandler = new MJSHandler(handlerContext);
87
+ const nodeModuleHandler = new NodeModuleHandler(handlerContext);
88
+ // Response interceptor middleware - log what's actually being sent
89
+ // This runs for ALL requests to help debug MIME type issues
90
+ app.use((req, res, next) => {
91
+ const originalUrl = req.originalUrl || req.url;
92
+ const urlWithoutQuery = originalUrl.split("?")[0];
93
+ // Log ALL /src requests to see what's happening
94
+ if (urlWithoutQuery.includes("/src/")) {
95
+ console.log(chalk.magenta(`[REQUEST INTERCEPTOR] Incoming request: ${req.method} ${originalUrl}`));
96
+ console.log(chalk.magenta(`[REQUEST INTERCEPTOR] URL without query: ${urlWithoutQuery}`));
97
+ }
98
+ // Only intercept /src/*.ui requests (check URL without query params)
99
+ if (urlWithoutQuery.includes("/src/") && urlWithoutQuery.endsWith(".ui")) {
100
+ console.log(chalk.yellow(`[RESPONSE INTERCEPTOR] Setting up interceptor for: ${originalUrl}`));
101
+ const originalSend = res.send;
102
+ const originalEnd = res.end;
103
+ const originalSetHeader = res.setHeader;
104
+ // Track headers being set
105
+ const headers = {};
106
+ res.setHeader = function (name, value) {
107
+ headers[name.toLowerCase()] = value;
108
+ const result = originalSetHeader.call(this, name, value);
109
+ if (name.toLowerCase() === "content-type") {
110
+ console.log(chalk.yellow(`[RESPONSE INTERCEPTOR] Content-Type set to: ${value} for ${originalUrl}`));
111
+ }
112
+ return result;
113
+ };
114
+ // Intercept send() to log final headers
115
+ res.send = function (body) {
116
+ console.log(chalk.cyan(`[RESPONSE INTERCEPTOR] res.send() called for: ${req.method} ${originalUrl}`));
117
+ console.log(chalk.cyan(`[RESPONSE INTERCEPTOR] Final Content-Type: ${headers["content-type"] || res.getHeader("content-type") || "NOT SET"}`));
118
+ console.log(chalk.cyan(`[RESPONSE INTERCEPTOR] All tracked headers: ${JSON.stringify(headers, null, 2)}`));
119
+ console.log(chalk.cyan(`[RESPONSE INTERCEPTOR] Body type: ${typeof body}, length: ${typeof body === "string" ? body.length : "N/A"}`));
120
+ if (typeof body === "string") {
121
+ const preview = body.substring(0, 300);
122
+ const isHTML = preview.trim().startsWith("<!") ||
123
+ preview.trim().startsWith("<html");
124
+ console.log(chalk.cyan(`[RESPONSE INTERCEPTOR] Body starts with: ${preview.substring(0, 50)}...`));
125
+ console.log(chalk.cyan(`[RESPONSE INTERCEPTOR] Looks like HTML: ${isHTML}`));
126
+ }
127
+ return originalSend.call(this, body);
128
+ };
129
+ // Intercept end() to log final headers
130
+ res.end = function (chunk, encoding) {
131
+ console.log(chalk.cyan(`[RESPONSE INTERCEPTOR] res.end() called for: ${req.method} ${originalUrl}`));
132
+ console.log(chalk.cyan(`[RESPONSE INTERCEPTOR] Final Content-Type: ${headers["content-type"] || res.getHeader("content-type") || "NOT SET"}`));
133
+ console.log(chalk.cyan(`[RESPONSE INTERCEPTOR] All tracked headers: ${JSON.stringify(headers, null, 2)}`));
134
+ console.log(chalk.cyan(`[RESPONSE INTERCEPTOR] Chunk type: ${typeof chunk}, length: ${typeof chunk === "string" ? chunk.length : "N/A"}`));
135
+ if (typeof chunk === "string") {
136
+ const preview = chunk.substring(0, 300);
137
+ const isHTML = preview.trim().startsWith("<!") ||
138
+ preview.trim().startsWith("<html");
139
+ console.log(chalk.cyan(`[RESPONSE INTERCEPTOR] Chunk starts with: ${preview.substring(0, 50)}...`));
140
+ console.log(chalk.cyan(`[RESPONSE INTERCEPTOR] Looks like HTML: ${isHTML}`));
141
+ }
142
+ return originalEnd.call(this, chunk, encoding);
143
+ };
144
+ }
145
+ next();
146
+ });
147
+ // CRITICAL: Handle /packages/ workspace source files FIRST (dist→src fallback in resolveFilePath)
148
+ app.use("/packages", async (req, res, next) => {
149
+ const rawUrl = req.url?.split("?")[0] || "";
150
+ const fullUrl = "/packages" + (rawUrl.startsWith("/") ? rawUrl : "/" + rawUrl);
151
+ if (fullUrl.endsWith(".ts") && !fullUrl.endsWith(".d.ts")) {
152
+ try {
153
+ await tsHandler.handle(fullUrl, res);
154
+ if (res.headersSent)
155
+ return;
156
+ }
157
+ catch (error) {
158
+ console.error(chalk.red(`[MIDDLEWARE /packages] Error .ts ${fullUrl}:`), error);
159
+ if (!res.headersSent)
160
+ res.status(500).setHeader("Content-Type", "text/plain").send(String(error));
161
+ return;
162
+ }
163
+ return;
164
+ }
165
+ if (fullUrl.endsWith(".js") || fullUrl.endsWith(".mjs")) {
166
+ try {
167
+ await (fullUrl.endsWith(".js") ? jsHandler.handle(fullUrl, res) : mjsHandler.handle(fullUrl, res));
168
+ if (res.headersSent)
169
+ return;
170
+ }
171
+ catch (error) {
172
+ console.error(chalk.red(`[MIDDLEWARE /packages] Error .js/.mjs ${fullUrl}:`), error);
173
+ if (!res.headersSent)
174
+ res.status(500).setHeader("Content-Type", "text/plain").send(String(error));
175
+ return;
176
+ }
177
+ return;
178
+ }
179
+ next();
180
+ });
181
+ // Module transformation middleware
182
+ // MUST be registered before static files and SPA fallback
183
+ // CRITICAL: Register path-specific middleware for /src FIRST to ensure it runs before static middleware
184
+ // Use app.use() for middleware (not app.all() which is for route handlers)
185
+ // The path "/src" will match /src and all subpaths like /src/index.ui
186
+ // IMPORTANT: When Express matches "/src", req.url is the path AFTER /src (e.g., "/index.ui")
187
+ // So we need to reconstruct the full path for the handler
188
+ app.use("/src", async (req, res, next) => {
189
+ // Log ALL requests to /src to debug
190
+ const originalUrl = req.originalUrl || req.url;
191
+ console.log(chalk.blue(`[DEBUG /src] ${req.method} ${originalUrl}`));
192
+ console.log(chalk.blue(`[DEBUG /src] req.url: ${req.url}, req.originalUrl: ${req.originalUrl || "N/A"}, req.path: ${req.path || "N/A"}`));
193
+ console.log(chalk.blue(`[DEBUG /src] Headers: ${JSON.stringify({ "user-agent": req.get("user-agent")?.substring(0, 50), accept: req.get("accept")?.substring(0, 50) })}`));
194
+ // req.url is relative to the mount point, so "/src/index.ui" becomes "/index.ui"
195
+ // We need to reconstruct the full path: "/src" + req.url
196
+ const relativeUrl = req.url.split("?")[0];
197
+ const fullPath = "/src" + relativeUrl;
198
+ const fullUrl = "/src" + req.url; // Include query params
199
+ // Handle .ui files in /src directory FIRST (before static middleware can interfere)
200
+ if (relativeUrl.endsWith(".ui")) {
201
+ console.log(chalk.magenta(`[MIDDLEWARE /src] ✅ Intercepted .ui request: ${req.method} ${fullUrl}`));
202
+ console.log(chalk.magenta(`[MIDDLEWARE /src] req.url: ${req.url}, fullPath: ${fullPath}`));
203
+ console.log(chalk.magenta(`[MIDDLEWARE /src] Headers sent? ${res.headersSent}, User-Agent: ${req.get("user-agent")?.substring(0, 50)}`));
204
+ try {
205
+ if (res.headersSent) {
206
+ console.warn(chalk.yellow(`[MIDDLEWARE /src] Response already sent for ${fullPath}`));
207
+ return;
208
+ }
209
+ // Handle HEAD requests - just send headers, no body
210
+ if (req.method === "HEAD") {
211
+ console.log(chalk.blue(`[MIDDLEWARE /src] Handling HEAD request for ${fullPath}`));
212
+ res.setHeader("Content-Type", "application/javascript; charset=utf-8");
213
+ res.setHeader("Cache-Control", "no-cache, no-store, must-revalidate");
214
+ res.setHeader("Pragma", "no-cache");
215
+ res.setHeader("Expires", "0");
216
+ res.status(200).end();
217
+ return;
218
+ }
219
+ // Handler expects full path like "/src/index.ui"
220
+ await uiHandler.handle(fullPath, res);
221
+ if (!res.headersSent) {
222
+ console.error(chalk.red(`[MIDDLEWARE /src] Handler did not send response for ${fullPath}`));
223
+ res
224
+ .status(500)
225
+ .send("Internal server error: handler did not send response");
226
+ }
227
+ else {
228
+ const contentType = res.getHeader("Content-Type");
229
+ console.log(chalk.green(`[MIDDLEWARE /src] ✅ Successfully sent .ui file: ${fullPath}`));
230
+ console.log(chalk.green(`[MIDDLEWARE /src] Content-Type header: ${contentType}`));
231
+ console.log(chalk.green(`[MIDDLEWARE /src] Response headers sent: ${res.headersSent}`));
232
+ }
233
+ return; // Don't call next() - response already sent
234
+ }
235
+ catch (error) {
236
+ console.error(chalk.red(`[MIDDLEWARE /src] Error handling .ui file ${fullPath}:`), error);
237
+ // CRITICAL: Do NOT call next() or throw - send error response directly
238
+ // This prevents the SPA fallback from catching it and serving HTML
239
+ if (!res.headersSent) {
240
+ // Check if it's a file not found error (ENOENT)
241
+ const isFileNotFound = error instanceof Error &&
242
+ (("code" in error && error.code === "ENOENT") ||
243
+ ("errno" in error && error.errno === -4058));
244
+ const status = isFileNotFound ? 404 : 500;
245
+ res.status(status).setHeader("Content-Type", "text/plain");
246
+ res.send(isFileNotFound
247
+ ? `File not found: ${fullPath}`
248
+ : `Error loading module: ${error instanceof Error ? error.message : String(error)}`);
249
+ }
250
+ return; // Don't call next() - error response already sent
251
+ }
252
+ }
253
+ // Handle .uix files
254
+ if (relativeUrl.endsWith(".uix")) {
255
+ try {
256
+ await uixHandler.handle(fullPath, res);
257
+ if (!res.headersSent) {
258
+ res.status(500).setHeader("Content-Type", "text/plain");
259
+ res.send("Error loading module");
260
+ }
261
+ return;
262
+ }
263
+ catch (error) {
264
+ console.error(chalk.red(`[MIDDLEWARE /src] Error handling .uix file ${fullPath}:`), error);
265
+ if (!res.headersSent) {
266
+ // Check if it's a file not found error (ENOENT)
267
+ const isFileNotFound = error instanceof Error &&
268
+ (("code" in error && error.code === "ENOENT") ||
269
+ ("errno" in error && error.errno === -4058));
270
+ const status = isFileNotFound ? 404 : 500;
271
+ res.status(status).setHeader("Content-Type", "text/plain");
272
+ res.send(isFileNotFound
273
+ ? `File not found: ${fullPath}`
274
+ : `Error loading module: ${error instanceof Error ? error.message : String(error)}`);
275
+ }
276
+ return;
277
+ }
278
+ }
279
+ // Handle .ts files
280
+ if (relativeUrl.endsWith(".ts") && !relativeUrl.endsWith(".d.ts")) {
281
+ try {
282
+ await tsHandler.handle(fullPath, res);
283
+ if (!res.headersSent) {
284
+ res.status(500).setHeader("Content-Type", "text/plain");
285
+ res.send("Error loading module");
286
+ }
287
+ return;
288
+ }
289
+ catch (error) {
290
+ console.error(chalk.red(`[MIDDLEWARE /src] Error handling .ts file ${fullPath}:`), error);
291
+ if (!res.headersSent) {
292
+ // Check if it's a file not found error (ENOENT)
293
+ const isFileNotFound = error instanceof Error &&
294
+ (("code" in error && error.code === "ENOENT") ||
295
+ ("errno" in error && error.errno === -4058));
296
+ const status = isFileNotFound ? 404 : 500;
297
+ res.status(status).setHeader("Content-Type", "text/plain");
298
+ res.send(isFileNotFound
299
+ ? `File not found: ${fullPath}`
300
+ : `Error loading module: ${error instanceof Error ? error.message : String(error)}`);
301
+ }
302
+ return;
303
+ }
304
+ }
305
+ // Handle .js and .mjs files (they might be source files that need import rewriting)
306
+ if (relativeUrl.endsWith(".js") || relativeUrl.endsWith(".mjs")) {
307
+ // Check if it's a source file that needs processing
308
+ // For now, try the JS handler first - if it fails, it will call next()
309
+ try {
310
+ if (relativeUrl.endsWith(".js")) {
311
+ await jsHandler.handle(fullPath, res);
312
+ if (res.headersSent)
313
+ return;
314
+ }
315
+ else {
316
+ await mjsHandler.handle(fullPath, res);
317
+ if (res.headersSent)
318
+ return;
319
+ }
320
+ }
321
+ catch (error) {
322
+ // If handler fails, fall through to static file serving
323
+ console.log(chalk.gray(`[MIDDLEWARE /src] JS handler failed for ${fullPath}, trying static file serving`));
324
+ }
325
+ // If handler didn't send response, continue to static file serving
326
+ if (!res.headersSent) {
327
+ next();
328
+ return;
329
+ }
330
+ return;
331
+ }
332
+ // For other files (CSS, images, etc.), serve as static files
333
+ // We need to serve them from the src directory
334
+ const srcPath = path.join(config.root, "src");
335
+ const filePath = path.join(srcPath, relativeUrl);
336
+ try {
337
+ // Check if file exists and is a file (not directory)
338
+ const stats = await fs.stat(filePath);
339
+ if (!stats.isFile()) {
340
+ console.log(chalk.gray(`[MIDDLEWARE /src] Path is not a file: ${filePath}, passing to next middleware`));
341
+ next();
342
+ return;
343
+ }
344
+ console.log(chalk.gray(`[MIDDLEWARE /src] Serving static file: ${fullPath} from ${filePath}`));
345
+ // Read and serve the file directly
346
+ const content = await fs.readFile(filePath);
347
+ const ext = path.extname(relativeUrl).toLowerCase();
348
+ // CRITICAL: Skip source files - they should have been handled by handlers above
349
+ // If we get here with a .js, .ts, .ui, .uix, .mjs file, something went wrong
350
+ if (ext === ".js" || ext === ".ts" || ext === ".ui" || ext === ".uix" || ext === ".mjs") {
351
+ console.error(chalk.red(`[MIDDLEWARE /src] ⚠️ Attempting to serve source file as static: ${fullPath}`));
352
+ // Don't serve it - return 404 or pass to next middleware
353
+ res.status(404).setHeader("Content-Type", "text/plain");
354
+ res.send(`Source file not found: ${fullPath}`);
355
+ return;
356
+ }
357
+ // Set appropriate Content-Type
358
+ const contentTypeMap = {
359
+ ".css": "text/css",
360
+ ".json": "application/json",
361
+ ".png": "image/png",
362
+ ".jpg": "image/jpeg",
363
+ ".jpeg": "image/jpeg",
364
+ ".gif": "image/gif",
365
+ ".svg": "image/svg+xml",
366
+ ".webp": "image/webp",
367
+ };
368
+ const contentType = contentTypeMap[ext] || "application/octet-stream";
369
+ res.setHeader("Content-Type", contentType);
370
+ res.setHeader("Cache-Control", "no-cache, no-store, must-revalidate");
371
+ res.setHeader("Pragma", "no-cache");
372
+ res.setHeader("Expires", "0");
373
+ res.send(content);
374
+ console.log(chalk.green(`[MIDDLEWARE /src] ✅ Served static file: ${fullPath} (${contentType})`));
375
+ }
376
+ catch (error) {
377
+ // File doesn't exist - return 404 directly, don't call next()
378
+ // This prevents SPA fallback from serving HTML
379
+ console.log(chalk.gray(`[MIDDLEWARE /src] File not found: ${filePath}, returning 404`));
380
+ res.status(404).setHeader("Content-Type", "text/plain");
381
+ res.send(`File not found: ${fullPath}`);
382
+ return;
383
+ }
384
+ });
385
+ // CRITICAL: Register general middleware for /lib/ source files BEFORE static file middleware
386
+ // This ensures /lib/*.ui and /lib/*.uix files are handled by module transformation, not static serving
387
+ app.use(async (req, res, next) => {
388
+ const url = req.url.split("?")[0];
389
+ // Handle /lib/ source files FIRST before static middleware can intercept them
390
+ if (url.startsWith("/lib/") && (url.endsWith(".ui") || url.endsWith(".uix") || url.endsWith(".ts") || (url.endsWith(".js") && !url.includes("node_modules")))) {
391
+ console.log(chalk.magenta(`[PRE-STATIC] Handling /lib/ source file: ${url} before static middleware`));
392
+ // Set Content-Type explicitly
393
+ res.setHeader("Content-Type", "application/javascript; charset=utf-8");
394
+ // Handle .ui files
395
+ if (url.endsWith(".ui")) {
396
+ try {
397
+ await uiHandler.handle(url, res);
398
+ return; // Response sent
399
+ }
400
+ catch (error) {
401
+ console.error(chalk.red(`[PRE-STATIC] Error handling .ui file ${url}:`), error);
402
+ if (!res.headersSent) {
403
+ res.status(500).send("Internal server error");
404
+ }
405
+ return;
406
+ }
407
+ }
408
+ // Handle .uix files
409
+ if (url.endsWith(".uix")) {
410
+ try {
411
+ await uixHandler.handle(url, res);
412
+ return; // Response sent
413
+ }
414
+ catch (error) {
415
+ console.error(chalk.red(`[PRE-STATIC] Error handling .uix file ${url}:`), error);
416
+ if (!res.headersSent) {
417
+ res.status(500).send("Internal server error");
418
+ }
419
+ return;
420
+ }
421
+ }
422
+ // For .ts/.js files, let general middleware handle them
423
+ return next();
424
+ }
425
+ next();
426
+ });
427
+ // CRITICAL: Add explicit MIME type handler for source files BEFORE static file middleware
428
+ // This ensures .ui/.uix files always get correct Content-Type even if they slip through
429
+ app.use((req, res, next) => {
430
+ const url = req.url.split("?")[0];
431
+ // If this is a source file request, set Content-Type explicitly
432
+ if (url.endsWith(".ui") || url.endsWith(".uix")) {
433
+ // Set Content-Type BEFORE any other middleware can override it
434
+ res.setHeader("Content-Type", "application/javascript; charset=utf-8");
435
+ console.log(chalk.cyan(`[MIME FIX] Set Content-Type for ${url} to application/javascript`));
436
+ }
437
+ next();
438
+ });
439
+ // Handle .skltn/modules.css requests - return 204 No Content (bundled CSS not available in dev)
440
+ // This prevents 404 errors in console - the client already has fallback logic
441
+ app.use("/.skltn/modules.css", (req, res) => {
442
+ res.status(204).end(); // No Content - bundled CSS not available in dev mode
443
+ });
444
+ // Static file serving - MUST be AFTER /src middleware but BEFORE general middleware
445
+ // This serves static files from public/, node_modules/, and lib/
446
+ // Register it after /src middleware so /src/ requests are handled first
447
+ await setupStaticFiles(app, {
448
+ root: config.root,
449
+ publicDir: config.publicDir,
450
+ workspaceRoot: config.workspaceRoot ?? null,
451
+ });
452
+ // General module transformation middleware for all other paths
453
+ // IMPORTANT: This should NOT catch /src requests - they should be handled by the /src middleware above
454
+ app.use(async (req, res, next) => {
455
+ const url = req.url.split("?")[0];
456
+ const fullUrl = req.url;
457
+ // Skip /src requests - they should be handled by the /src middleware above
458
+ if (url.startsWith("/src")) {
459
+ console.log(chalk.yellow(`[GENERAL MIDDLEWARE] Skipping /src request: ${fullUrl} - should be handled by /src middleware`));
460
+ return next(); // Let /src middleware handle it (but it's already registered, so this shouldn't happen)
461
+ }
462
+ // CRITICAL: Handle /lib/ source files FIRST before static middleware can intercept them
463
+ // Source files (.ui, .uix, .ts, .js) in /lib/ MUST be handled by module transformation middleware
464
+ // Static files (CSS, images) in /lib/ can be handled by static middleware
465
+ if (url.startsWith("/lib/")) {
466
+ const isSourceFile = url.endsWith(".ui") ||
467
+ url.endsWith(".uix") ||
468
+ url.endsWith(".ts") ||
469
+ (url.endsWith(".js") && !url.includes("node_modules"));
470
+ if (isSourceFile) {
471
+ // CRITICAL: Process source files in /lib/ - they need module transformation
472
+ console.log(chalk.magenta(`[GENERAL MIDDLEWARE] Processing /lib/ source file: ${fullUrl}`));
473
+ // Continue to handle .ui/.uix/.ts/.js files below
474
+ // Don't return next() - we want to handle them here
475
+ }
476
+ else {
477
+ // Static file - let static middleware handle it
478
+ console.log(chalk.cyan(`[GENERAL MIDDLEWARE] Skipping /lib/ static file: ${fullUrl} - should be handled by static file middleware`));
479
+ return next(); // Let static file middleware handle it
480
+ }
481
+ }
482
+ // Log all requests to help debug routing
483
+ if (url.includes("css") || url.includes("lib")) {
484
+ console.log(chalk.gray(`[GENERAL MIDDLEWARE] Processing request: ${fullUrl} (url: ${url})`));
485
+ }
486
+ // Log ALL requests to .ui files to debug
487
+ if (url.endsWith(".ui")) {
488
+ console.log(chalk.magenta(`[GENERAL MIDDLEWARE] 🔍 Intercepted .ui request: ${fullUrl} (path: ${url})`));
489
+ console.log(chalk.magenta(`[GENERAL MIDDLEWARE] Headers sent? ${res.headersSent}, Method: ${req.method}, User-Agent: ${req.get("user-agent")?.substring(0, 50)}`));
490
+ console.log(chalk.magenta(`[GENERAL MIDDLEWARE] Request headers: Accept=${req.get("accept")?.substring(0, 100)}`));
491
+ }
492
+ try {
493
+ // Handle .ui files (for paths other than /src)
494
+ // CRITICAL: Set MIME type explicitly BEFORE handler to ensure it's correct
495
+ if (url.endsWith(".ui")) {
496
+ console.log(chalk.cyan(`[GENERAL MIDDLEWARE] 🔧 Processing .ui file: ${url}`));
497
+ // Set Content-Type explicitly BEFORE any handler runs
498
+ res.setHeader("Content-Type", "application/javascript; charset=utf-8");
499
+ // Check if response already sent (shouldn't happen, but safety check)
500
+ if (res.headersSent) {
501
+ console.warn(chalk.yellow(`[GENERAL MIDDLEWARE] ⚠️ Response already sent for ${url}, skipping handler`));
502
+ return;
503
+ }
504
+ try {
505
+ console.log(chalk.cyan(`[GENERAL MIDDLEWARE] 🔧 Calling uiHandler.handle(${url})`));
506
+ await uiHandler.handle(url, res);
507
+ // Verify response was sent
508
+ if (!res.headersSent) {
509
+ console.error(chalk.red(`[GENERAL MIDDLEWARE] ❌ Handler did not send response for ${url}`));
510
+ res
511
+ .status(500)
512
+ .send("Internal server error: handler did not send response");
513
+ }
514
+ else {
515
+ const contentType = res.getHeader("Content-Type");
516
+ console.log(chalk.green(`[GENERAL MIDDLEWARE] ✅ Successfully sent .ui file: ${url} with Content-Type: ${contentType}`));
517
+ // CRITICAL: Verify Content-Type is correct
518
+ if (contentType !== "application/javascript; charset=utf-8") {
519
+ console.error(chalk.red(`[GENERAL MIDDLEWARE] ⚠️ WRONG Content-Type for ${url}: Expected 'application/javascript; charset=utf-8', got '${contentType}'`));
520
+ // Force correct Content-Type
521
+ res.setHeader("Content-Type", "application/javascript; charset=utf-8");
522
+ }
523
+ }
524
+ return; // Don't call next() - response already sent
525
+ }
526
+ catch (error) {
527
+ console.error(chalk.red(`[GENERAL MIDDLEWARE] ❌ Error handling .ui file ${url}:`), error);
528
+ // Re-throw to be caught by outer catch block
529
+ throw error;
530
+ }
531
+ }
532
+ // Handle .uix files
533
+ if (url.endsWith(".uix")) {
534
+ await uixHandler.handle(url, res);
535
+ return;
536
+ }
537
+ // Handle .ts files
538
+ if (url.endsWith(".ts") && !url.endsWith(".d.ts")) {
539
+ await tsHandler.handle(url, res);
540
+ return;
541
+ }
542
+ // Handle .js/.mjs files from node_modules (rewrite imports)
543
+ if ((url.endsWith(".js") || url.endsWith(".mjs")) &&
544
+ url.includes("node_modules")) {
545
+ await nodeModuleHandler.handle(url, res);
546
+ return;
547
+ }
548
+ // Handle .js files (rewrite imports) - including /swiss-packages files
549
+ if (url.endsWith(".js")) {
550
+ // Log when processing /swiss-packages files to debug
551
+ if (url.startsWith("/swiss-packages/")) {
552
+ console.log(chalk.cyan(`[middleware] Processing SWISS package: ${url}`));
553
+ }
554
+ await jsHandler.handle(url, res);
555
+ return;
556
+ }
557
+ // Handle .mjs files (ES modules, rewrite imports)
558
+ if (url.endsWith(".mjs")) {
559
+ await mjsHandler.handle(url, res);
560
+ return;
561
+ }
562
+ // Let CSS and other static files pass through to static file serving
563
+ if (url.endsWith(".css") ||
564
+ url.endsWith(".png") ||
565
+ url.endsWith(".jpg") ||
566
+ url.endsWith(".jpeg") ||
567
+ url.endsWith(".gif") ||
568
+ url.endsWith(".svg") ||
569
+ url.endsWith(".webp") ||
570
+ url.endsWith(".woff") ||
571
+ url.endsWith(".woff2") ||
572
+ url.endsWith(".ttf") ||
573
+ url.endsWith(".eot")) {
574
+ console.log(chalk.cyan(`[GENERAL MIDDLEWARE] Passing static file to static middleware: ${fullUrl}`));
575
+ return next();
576
+ }
577
+ next();
578
+ }
579
+ catch (error) {
580
+ console.error(chalk.red(`Error processing ${url}:`), error);
581
+ res
582
+ .status(500)
583
+ .send(`Error: ${error instanceof Error ? error.message : String(error)}`);
584
+ }
585
+ });
586
+ // SPA fallback
587
+ await setupSPAFallback(app, {
588
+ root: config.root,
589
+ publicDir: config.publicDir,
590
+ });
591
+ return {
592
+ routes: fileRouterResult.routes,
593
+ routeScanner: fileRouterResult.routeScanner,
594
+ routeWatcher: fileRouterResult.routeWatcher,
595
+ };
596
+ }
@@ -0,0 +1,15 @@
1
+ import type { Express } from "express";
2
+ export interface StaticFilesConfig {
3
+ root: string;
4
+ publicDir: string;
5
+ workspaceRoot?: string | null;
6
+ }
7
+ /**
8
+ * Setup static file serving for public directory, node_modules, and workspace packages
9
+ */
10
+ export declare function setupStaticFiles(app: Express, config: StaticFilesConfig): Promise<void>;
11
+ /**
12
+ * Setup SPA fallback - serves index.html for all unmatched routes
13
+ */
14
+ export declare function setupSPAFallback(app: Express, config: StaticFilesConfig): Promise<void>;
15
+ //# sourceMappingURL=static-files.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"static-files.d.ts","sourceRoot":"","sources":["../../src/middleware/static-files.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,SAAS,CAAC;AAMvC,MAAM,WAAW,iBAAiB;IAChC,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,EAAE,MAAM,CAAC;IAClB,aAAa,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CAC/B;AAED;;GAEG;AACH,wBAAsB,gBAAgB,CACpC,GAAG,EAAE,OAAO,EACZ,MAAM,EAAE,iBAAiB,GACxB,OAAO,CAAC,IAAI,CAAC,CAuYf;AAED;;GAEG;AACH,wBAAsB,gBAAgB,CACpC,GAAG,EAAE,OAAO,EACZ,MAAM,EAAE,iBAAiB,GACxB,OAAO,CAAC,IAAI,CAAC,CA+Sf"}