@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,585 @@
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 { promises as fs, realpathSync, existsSync } from "node:fs";
8
+ import path from "node:path";
9
+ import chalk from "chalk";
10
+ import { findWorkspaceRoot } from "../utils/workspace.js";
11
+ /**
12
+ * Setup static file serving for public directory, node_modules, and workspace packages
13
+ */
14
+ export async function setupStaticFiles(app, config) {
15
+ console.log(chalk.magenta(`[static-files] ⚡ setupStaticFiles called with root: ${config.root}`));
16
+ // Static file serving - ONLY serve public directory
17
+ // Do NOT serve dist/ folder - it contains old build artifacts with bare imports
18
+ const publicPath = path.join(config.root, config.publicDir);
19
+ // Serve static files from public/ directory
20
+ // IMPORTANT: Skip source files (.ui, .uix, .ts, .js) - they should be handled by module transformation middleware
21
+ // Wrap express.static to prevent it from serving source files
22
+ const publicStaticMiddleware = express.static(publicPath, {
23
+ // Exclude dist folder and other build artifacts
24
+ dotfiles: "ignore",
25
+ index: false, // Don't serve index files from static middleware
26
+ setHeaders: (res, filePath) => {
27
+ // Add cache-busting headers for all static files in dev mode
28
+ res.setHeader("Cache-Control", "no-cache, no-store, must-revalidate");
29
+ res.setHeader("Pragma", "no-cache");
30
+ res.setHeader("Expires", "0");
31
+ },
32
+ });
33
+ app.use((req, res, next) => {
34
+ const url = req.url.split("?")[0];
35
+ // Skip source files - let module transformation middleware handle them
36
+ // DO NOT call express.static for source files - it will serve them with wrong MIME type
37
+ if (url.endsWith(".ui") ||
38
+ url.endsWith(".uix") ||
39
+ url.endsWith(".ts") ||
40
+ (url.endsWith(".js") && !url.includes("node_modules")) ||
41
+ url.endsWith(".mjs")) {
42
+ // Skip express.static for source files - pass to next middleware instead
43
+ return next();
44
+ }
45
+ // For non-source files, use express.static
46
+ publicStaticMiddleware(req, res, next);
47
+ });
48
+ // NOTE: We do NOT serve /src as static files here anymore
49
+ // The module transformation middleware in middleware-setup.ts handles ALL /src requests first
50
+ // This ensures .ui, .uix, .ts files are processed correctly before static middleware can interfere
51
+ // Only non-source files (CSS, images, etc.) will pass through to be served as static
52
+ // But they should be handled by the module transformation middleware's next() call
53
+ // Serve node_modules as static files from multiple locations
54
+ // 1. App root node_modules
55
+ // Wrap to skip source files
56
+ const nodeModulesStatic = express.static(path.join(config.root, "node_modules"));
57
+ app.use("/node_modules", (req, res, next) => {
58
+ const url = req.url.split("?")[0];
59
+ // Skip source files in node_modules - they should be handled by module transformation
60
+ if (url.endsWith(".ui") ||
61
+ url.endsWith(".uix") ||
62
+ url.endsWith(".ts") ||
63
+ (url.endsWith(".js") && !url.includes("/dist/") && !url.includes("/lib/")) ||
64
+ url.endsWith(".mjs")) {
65
+ return next();
66
+ }
67
+ nodeModulesStatic(req, res, next);
68
+ });
69
+ // 2. Workspace root node_modules (if different from app root)
70
+ const workspaceRootForNodeModules = config.workspaceRoot ?? (await findWorkspaceRoot(config.root));
71
+ if (workspaceRootForNodeModules &&
72
+ workspaceRootForNodeModules !== config.root) {
73
+ const workspaceNodeModules = path.join(workspaceRootForNodeModules, "node_modules");
74
+ try {
75
+ await fs.access(workspaceNodeModules);
76
+ // Serve workspace node_modules with a different path to avoid conflicts
77
+ // But also check if package exists in app node_modules first
78
+ app.use("/node_modules", (req, res, next) => {
79
+ const url = req.url.split("?")[0];
80
+ // Skip source files - let module transformation middleware handle them
81
+ if (url.endsWith(".ui") ||
82
+ url.endsWith(".uix") ||
83
+ url.endsWith(".ts") ||
84
+ (url.endsWith(".js") && !url.includes("/dist/") && !url.includes("/lib/")) ||
85
+ url.endsWith(".mjs")) {
86
+ return next();
87
+ }
88
+ // Strip leading slash: req.path inside app.use('/node_modules') is already
89
+ // relative to that prefix, e.g. '/@kibologic/shell/design-tokens/primitive.css'
90
+ const relPath = req.path.replace(/^\/+/, "");
91
+ if (!relPath)
92
+ return next();
93
+ // Resolve pnpm symlinks explicitly (same mechanism UIHandler uses).
94
+ // express.static / fs.access can silently fail on pnpm virtual-store symlinks
95
+ // in production containers, so we use realpathSync to get the real path first.
96
+ const isScoped = relPath.startsWith("@");
97
+ const parts = relPath.split("/");
98
+ const pkgName = isScoped ? `${parts[0]}/${parts[1]}` : parts[0];
99
+ const subPath = isScoped ? parts.slice(2).join("/") : parts.slice(1).join("/");
100
+ const pkgSymLink = path.join(workspaceNodeModules, pkgName);
101
+ try {
102
+ const pkgReal = realpathSync(pkgSymLink);
103
+ const realAbs = path.join(pkgReal, subPath);
104
+ if (existsSync(realAbs)) {
105
+ res.sendFile(realAbs);
106
+ return;
107
+ }
108
+ }
109
+ catch {
110
+ // symlink missing or broken — fall through to next()
111
+ }
112
+ next();
113
+ });
114
+ console.log(chalk.gray(` 📦 Serving workspace node_modules from ${workspaceNodeModules}`));
115
+ }
116
+ catch {
117
+ // Workspace node_modules doesn't exist, skip
118
+ }
119
+ }
120
+ // Serve workspace packages (lib/, libraries/, packages/, modules/, etc.)
121
+ // This allows workspace packages to be served via HTTP
122
+ // Reuse workspaceRoot from above if it exists, otherwise find it
123
+ const workspaceRoot = workspaceRootForNodeModules || (await findWorkspaceRoot(config.root));
124
+ console.log(chalk.blue(`[static-files] Workspace root: ${workspaceRoot}`));
125
+ console.log(chalk.blue(`[static-files] App root: ${config.root}`));
126
+ // Try to serve lib/ directory - check both workspace root and app root parent
127
+ let libPath = null;
128
+ console.log(chalk.blue(`[static-files] Determining lib/ path... workspaceRoot: ${workspaceRoot}, config.root: ${config.root}`));
129
+ // First, try workspace root
130
+ if (workspaceRoot && workspaceRoot !== config.root) {
131
+ libPath = path.join(workspaceRoot, "lib");
132
+ console.log(chalk.blue(`[static-files] Trying workspace root lib/: ${libPath}`));
133
+ }
134
+ else {
135
+ console.log(chalk.yellow(`[static-files] Workspace root equals app root, trying parent directories...`));
136
+ // If workspace root equals app root, try going up from app root
137
+ const parentDir = path.dirname(config.root);
138
+ const parentLibPath = path.join(parentDir, "lib");
139
+ console.log(chalk.blue(`[static-files] Trying parent lib/: ${parentLibPath}`));
140
+ try {
141
+ await fs.access(parentLibPath);
142
+ libPath = parentLibPath;
143
+ console.log(chalk.blue(`[static-files] Using parent directory lib/: ${libPath}`));
144
+ }
145
+ catch (error) {
146
+ console.log(chalk.yellow(`[static-files] Parent lib/ not found: ${error instanceof Error ? error.message : String(error)}`));
147
+ // Parent lib/ doesn't exist, try grandparent
148
+ const grandparentDir = path.dirname(parentDir);
149
+ const grandparentLibPath = path.join(grandparentDir, "lib");
150
+ console.log(chalk.blue(`[static-files] Trying grandparent lib/: ${grandparentLibPath}`));
151
+ try {
152
+ await fs.access(grandparentLibPath);
153
+ libPath = grandparentLibPath;
154
+ console.log(chalk.blue(`[static-files] Using grandparent directory lib/: ${libPath}`));
155
+ }
156
+ catch (error2) {
157
+ console.log(chalk.yellow(`[static-files] Grandparent lib/ not found: ${error2 instanceof Error ? error2.message : String(error2)}`));
158
+ }
159
+ }
160
+ }
161
+ // Serve lib/ directory if found
162
+ console.log(chalk.blue(`[static-files] Checking for lib/ directory... libPath: ${libPath}`));
163
+ // ALWAYS try to register /lib/ static serving
164
+ // Calculate the lib path - prefer workspace root, fallback to parent of app root
165
+ let finalLibPath;
166
+ if (libPath) {
167
+ finalLibPath = libPath;
168
+ }
169
+ else if (workspaceRoot && workspaceRoot !== config.root) {
170
+ finalLibPath = path.join(workspaceRoot, "lib");
171
+ }
172
+ else {
173
+ // Go up from app root to find lib/
174
+ const parentDir = path.dirname(config.root);
175
+ finalLibPath = path.join(parentDir, "lib");
176
+ }
177
+ console.log(chalk.blue(`[static-files] Final lib path to check: ${finalLibPath}`));
178
+ console.log(chalk.blue(`[static-files] workspaceRoot: ${workspaceRoot}, config.root: ${config.root}`));
179
+ // Try to access the directory
180
+ let libPathExists = false;
181
+ try {
182
+ await fs.access(finalLibPath);
183
+ libPathExists = true;
184
+ console.log(chalk.green(`[static-files] ✅ Found lib/ directory at: ${finalLibPath}`));
185
+ // Verify the CSS file exists
186
+ const testCssPath = path.join(finalLibPath, "skltn", "src", "css", "index.css");
187
+ try {
188
+ await fs.access(testCssPath);
189
+ console.log(chalk.green(`[static-files] ✅ Test CSS file exists: ${testCssPath}`));
190
+ }
191
+ catch (error) {
192
+ console.error(chalk.yellow(`[static-files] ⚠️ Test CSS file NOT found: ${testCssPath}`));
193
+ }
194
+ }
195
+ catch (error) {
196
+ console.error(chalk.red(`[static-files] ❌ lib/ directory not found at: ${finalLibPath}`));
197
+ console.error(chalk.red(`[static-files] Error: ${error instanceof Error ? error.message : String(error)}`));
198
+ console.error(chalk.red(`[static-files] ⚠️ /lib middleware will NOT be registered - CSS files will 404!`));
199
+ }
200
+ // Register static file middleware ONLY if directory exists
201
+ if (libPathExists) {
202
+ console.log(chalk.green(`[static-files] ✅ Registering /lib middleware with finalLibPath: ${finalLibPath}`));
203
+ // CRITICAL: First middleware to block source files BEFORE any express.static can serve them
204
+ // This MUST run before express.static to prevent wrong MIME types
205
+ app.use("/lib", (req, res, next) => {
206
+ const url = req.url.split("?")[0];
207
+ const path = req.path.split("?")[0];
208
+ // CRITICAL: Block source files immediately - check both url and path
209
+ // When express.static is mounted at /lib, req.path is stripped of /lib prefix
210
+ const isSourceFile = url.endsWith(".ui") || url.endsWith(".uix") || url.endsWith(".ts") ||
211
+ (url.endsWith(".js") && !url.includes("node_modules")) || url.endsWith(".mjs") ||
212
+ path.endsWith(".ui") || path.endsWith(".uix") || path.endsWith(".ts") ||
213
+ (path.endsWith(".js") && !path.includes("node_modules")) || path.endsWith(".mjs");
214
+ if (isSourceFile) {
215
+ console.log(chalk.red(`[static-files] ⚠️ FIRST BLOCK: Skipping source file: url=${url}, path=${path} - should be handled by module middleware`));
216
+ return next(); // Let module transformation middleware handle it
217
+ }
218
+ console.log(chalk.cyan(`[static-files] Request for /lib${path} (static file)`));
219
+ next();
220
+ });
221
+ // REMOVED: Logging middleware - it was just adding noise
222
+ // The blocking middleware above already handles source files
223
+ // CRITICAL: Add express.static for /lib/ but wrap it to skip source files
224
+ // Source files should be handled by module transformation middleware (registered before this)
225
+ const libStatic = express.static(finalLibPath, {
226
+ setHeaders: (res, filePath) => {
227
+ try {
228
+ res.setHeader("Cache-Control", "no-cache, no-store, must-revalidate");
229
+ res.setHeader("Pragma", "no-cache");
230
+ res.setHeader("Expires", "0");
231
+ }
232
+ catch (error) {
233
+ console.error(chalk.red(`[static-files] Error setting headers for ${filePath}:`), error);
234
+ }
235
+ },
236
+ });
237
+ // CRITICAL: Wrap express.static to prevent serving source files
238
+ // Check BOTH req.url (full path) and req.path (stripped path) to catch all cases
239
+ app.use("/lib", (req, res, next) => {
240
+ const url = req.url.split("?")[0];
241
+ const path = req.path.split("?")[0];
242
+ // CRITICAL: Skip source files - they should be handled by module transformation middleware
243
+ // Check both url and path because express.static strips the mount path
244
+ const isSourceFile = url.endsWith(".ui") || url.endsWith(".uix") || url.endsWith(".ts") ||
245
+ (url.endsWith(".js") && !url.includes("node_modules")) || url.endsWith(".mjs") ||
246
+ path.endsWith(".ui") || path.endsWith(".uix") || path.endsWith(".ts") ||
247
+ (path.endsWith(".js") && !path.includes("node_modules")) || path.endsWith(".mjs");
248
+ if (isSourceFile) {
249
+ console.log(chalk.red(`[static-files /lib express.static] ⚠️ BLOCKING source file: url=${url}, path=${path} - should be handled by module transformation middleware`));
250
+ // CRITICAL: Don't call libStatic - return next() to skip it
251
+ return next(); // Let module transformation middleware handle it
252
+ }
253
+ // For static files (CSS, images), use express.static
254
+ libStatic(req, res, next);
255
+ });
256
+ // This was serving source files with wrong MIME type (application/octet-stream)
257
+ // Source files should be handled by module transformation middleware (registered before this)
258
+ // Only static files (CSS, images) should be served, and they're handled by the custom handler above
259
+ // If a file isn't found, let it 404 rather than serving with wrong MIME type
260
+ console.log(chalk.gray(` 📦 Serving workspace lib/ from ${finalLibPath}`));
261
+ }
262
+ // Continue with other workspace directories if workspaceRoot is different from app root
263
+ if (workspaceRoot && workspaceRoot !== config.root) {
264
+ // Serve libraries/ directory (legacy support)
265
+ const librariesPath = path.join(workspaceRoot, "libraries");
266
+ try {
267
+ await fs.access(librariesPath);
268
+ app.use("/libraries", express.static(librariesPath));
269
+ console.log(chalk.gray(` 📦 Serving workspace libraries/ from ${librariesPath}`));
270
+ }
271
+ catch {
272
+ // libraries/ doesn't exist, skip
273
+ }
274
+ // NOTE: Do NOT serve /packages/ as static files
275
+ // Workspace packages contain source files (.ts, .ui, .uix) that need to be processed
276
+ // by handlers (TSHandler, UIHandler, etc.) to rewrite imports and compile them
277
+ // Static file serving would bypass this processing, causing bare imports to fail
278
+ // Only serve /packages/ if they're already compiled assets (handled by handlers)
279
+ // Serve modules/ directory (for CSS, assets, etc.)
280
+ // Source files (.ui, .uix, .ts) must NOT be served statically — they need
281
+ // to fall through to the compiler middleware (general handler). CG-07.
282
+ const modulesPath = path.join(workspaceRoot, "modules");
283
+ try {
284
+ await fs.access(modulesPath);
285
+ app.use("/modules", (req, res, next) => {
286
+ const url = req.url.split("?")[0];
287
+ const isSourceFile = url.endsWith(".ui") ||
288
+ url.endsWith(".uix") ||
289
+ url.endsWith(".ts") ||
290
+ (url.endsWith(".js") && !url.includes("node_modules")) ||
291
+ url.endsWith(".mjs");
292
+ if (isSourceFile) {
293
+ return next();
294
+ }
295
+ express.static(modulesPath)(req, res, next);
296
+ });
297
+ console.log(chalk.gray(` 📦 Serving workspace modules/ from ${modulesPath}`));
298
+ }
299
+ catch {
300
+ // modules/ doesn't exist, skip
301
+ }
302
+ }
303
+ // NOTE: SWISS packages are NOT served as static files
304
+ // They are processed by the middleware (TS/JS handlers) to rewrite imports
305
+ // This ensures all bare imports in SWISS packages are rewritten correctly
306
+ // The /swiss-packages/ URLs are handled by the middleware in middleware-setup.ts
307
+ }
308
+ /**
309
+ * Setup SPA fallback - serves index.html for all unmatched routes
310
+ */
311
+ export async function setupSPAFallback(app, config) {
312
+ console.log(chalk.magenta(`[SWITE] setupSPAFallback loaded - VERSION 3.0.0 (NO HARDCODED CSS)`));
313
+ // Use app.all() to catch ALL HTTP methods, but only for non-source files
314
+ app.all("*", async (req, res, next) => {
315
+ const url = req.url.split("?")[0];
316
+ const fullUrl = req.url;
317
+ const accept = String(req.headers?.accept || "");
318
+ // DEBUG: Verify handler is being called
319
+ process.stderr.write(`[SPA FALLBACK] Handler called for: ${req.method} ${fullUrl}\n`);
320
+ console.error(`[SWITE CSS DEBUG] ========== SPA FALLBACK HANDLER START ==========`);
321
+ console.error(`[SWITE CSS DEBUG] URL: ${url}, Full URL: ${fullUrl}`);
322
+ // --- CRITICAL SAFETY CHECK ---
323
+ // NEVER serve HTML for /src/* requests - these are source files that must be handled by middleware
324
+ // Even if middleware fails, we should return 404, not HTML
325
+ if (req.path?.startsWith("/src/") || url.startsWith("/src/")) {
326
+ console.error(chalk.red(`[SPA FALLBACK] ⚠️ BLOCKED: Attempt to serve HTML for source path: ${req.method} ${fullUrl}`));
327
+ console.error(chalk.red(`[SPA FALLBACK] This should have been handled by /src middleware! Returning 404.`));
328
+ res.status(404).setHeader("Content-Type", "text/plain");
329
+ res.send(`File not found: ${url}`);
330
+ return;
331
+ }
332
+ // --- CRITICAL SAFETY CHECK ---
333
+ // NEVER serve HTML for /swiss-packages/* requests - these are SWISS framework packages
334
+ // They should be handled by TS/JS handlers to rewrite imports
335
+ if (req.path?.startsWith("/swiss-packages/") || url.startsWith("/swiss-packages/")) {
336
+ console.error(chalk.red(`[SPA FALLBACK] ⚠️ BLOCKED: Attempt to serve HTML for SWISS package: ${req.method} ${fullUrl}`));
337
+ console.error(chalk.red(`[SPA FALLBACK] This should have been handled by module transformation middleware! Returning 404.`));
338
+ res.status(404).setHeader("Content-Type", "text/plain");
339
+ res.send(`File not found: ${url}`);
340
+ return;
341
+ }
342
+ // --- CRITICAL SAFETY CHECK ---
343
+ // NEVER serve HTML for /lib/* requests - these are workspace library files
344
+ // They should be handled by static file middleware
345
+ if (req.path?.startsWith("/lib/") || url.startsWith("/lib/")) {
346
+ console.error(chalk.red(`[SPA FALLBACK] ⚠️ BLOCKED: Attempt to serve HTML for /lib/ path: ${req.method} ${fullUrl}`));
347
+ console.error(chalk.red(`[SPA FALLBACK] This should have been handled by static file middleware! Returning 404.`));
348
+ res.status(404).setHeader("Content-Type", "text/plain");
349
+ res.send(`File not found: ${url}`);
350
+ return;
351
+ }
352
+ // Log every request that hits the fallback (for diagnostics)
353
+ console.log(chalk.gray(`[SPA FALLBACK] Serving HTML for: ${req.method} ${fullUrl}`));
354
+ process.stderr.write(`[SPA FALLBACK] About to read HTML file...\n`);
355
+ // Log if SPA fallback is being hit for .ui files (this should NOT happen after /src check)
356
+ if (url.endsWith(".ui")) {
357
+ console.error(chalk.red(`[SPA FALLBACK] ⚠️ WARNING: SPA fallback intercepted .ui file: ${fullUrl}`));
358
+ console.error(chalk.red(`[SPA FALLBACK] This should have been handled by module transformation middleware!`));
359
+ }
360
+ // DO NOT serve HTML for source files - they should be handled by handlers
361
+ // This prevents the SPA fallback from catching .ui, .uix, .ts, .js, .mjs files
362
+ // If we reach here, it means the middleware handlers didn't process it
363
+ if (url.endsWith(".ui") ||
364
+ url.endsWith(".uix") ||
365
+ url.endsWith(".ts") ||
366
+ url.endsWith(".js") ||
367
+ url.endsWith(".mjs") ||
368
+ url.endsWith(".css") ||
369
+ url.endsWith(".json")) {
370
+ // These should have been handled by middleware handlers
371
+ // If we reach here, the file wasn't found, return 404 with proper content type
372
+ console.error(chalk.red(`[SPA FALLBACK] Returning 404 for ${url} - should have been handled earlier`));
373
+ res.status(404).setHeader("Content-Type", "text/plain");
374
+ res.send(`File not found: ${url}`);
375
+ return;
376
+ }
377
+ // Only serve SPA HTML for real navigation/document requests.
378
+ // If a script/style/module fetch hits the fallback, returning HTML causes strict MIME failures.
379
+ if (!accept.includes("text/html")) {
380
+ res.status(404).setHeader("Content-Type", "text/plain");
381
+ res.send(`Not found: ${url}`);
382
+ return;
383
+ }
384
+ // Add cache-busting headers for HTML files during development
385
+ res.setHeader("Cache-Control", "no-cache, no-store, must-revalidate");
386
+ res.setHeader("Pragma", "no-cache");
387
+ res.setHeader("Expires", "0");
388
+ // Inject timestamp into script tag to force fresh load
389
+ const htmlPath = path.join(config.root, config.publicDir, "index.html");
390
+ let html = await fs.readFile(htmlPath, "utf-8");
391
+ const timestamp = Date.now();
392
+ const random = Math.random().toString(36).substring(7);
393
+ // More aggressive cache busting - replace ALL script src attributes
394
+ html = html.replace(/src="([^"]*index\.ui[^"]*)"/g, (match, src) => {
395
+ // Remove any existing cache-busting params
396
+ const cleanSrc = src.split("?")[0].split("&")[0];
397
+ return `src="${cleanSrc}?v=dev&t=${timestamp}&r=${random}"`;
398
+ });
399
+ // Also replace any script tags with type="module" that have src attributes
400
+ html = html.replace(/<script\s+type=["']module["'][^>]*src=["']([^"']*index\.ui[^"']*)["'][^>]*>/g, (match, src) => {
401
+ const cleanSrc = src.split("?")[0].split("&")[0];
402
+ return match.replace(src, `${cleanSrc}?v=dev&t=${timestamp}&r=${random}`);
403
+ });
404
+ // Add cache-busting meta tags to prevent browser caching
405
+ if (!html.includes('<meta http-equiv="Cache-Control"')) {
406
+ html = html.replace("<head>", `<head>\n <meta http-equiv="Cache-Control" content="no-cache, no-store, must-revalidate">\n <meta http-equiv="Pragma" content="no-cache">\n <meta http-equiv="Expires" content="0">`);
407
+ }
408
+ // Extract CSS imports from entry point and inject as <link> tags
409
+ // This dynamically discovers CSS files from the app's entry point
410
+ // CRITICAL: This MUST run before import map injection
411
+ // IMPORTANT: Only inject CSS files that actually exist in the app's directory
412
+ console.log(chalk.magenta(`[SWITE CSS] ========== CSS EXTRACTION START (VERSION 3.0.0) ==========`));
413
+ console.log(chalk.magenta(`[SWITE CSS] App root: ${config.root}`));
414
+ try {
415
+ const entryPointPath = path.join(config.root, "src", "index.ui");
416
+ console.log(chalk.blue(`[SWITE CSS] Checking entry point: ${entryPointPath}`));
417
+ const entryPointContent = await fs.readFile(entryPointPath, "utf-8");
418
+ // Extract CSS imports using regex
419
+ const cssImportPattern = /import\s+['"](.*?\.css)['"];?/g;
420
+ const cssImports = new Set();
421
+ let match;
422
+ // Check entry point
423
+ while ((match = cssImportPattern.exec(entryPointContent)) !== null) {
424
+ cssImports.add(match[1]);
425
+ }
426
+ // Also check imported files (like App.uix) for CSS imports
427
+ const importPattern = /import\s+.*?from\s+['"](.*?)['"];?/g;
428
+ const importedFiles = [];
429
+ let importMatch;
430
+ cssImportPattern.lastIndex = 0; // Reset regex
431
+ while ((importMatch = importPattern.exec(entryPointContent)) !== null) {
432
+ const importPath = importMatch[1];
433
+ // Skip node_modules and absolute imports
434
+ if (!importPath.startsWith("@") && !importPath.startsWith("/") && !importPath.startsWith(".")) {
435
+ continue;
436
+ }
437
+ // Resolve relative imports
438
+ if (importPath.startsWith(".")) {
439
+ importedFiles.push(importPath);
440
+ }
441
+ }
442
+ // Check imported files for CSS
443
+ for (const importedFile of importedFiles) {
444
+ try {
445
+ const importedFilePath = path.resolve(path.dirname(entryPointPath), importedFile);
446
+ // Try different extensions
447
+ const extensions = [".uix", ".ui", ".ts", ".js"];
448
+ let found = false;
449
+ for (const ext of extensions) {
450
+ const testPath = importedFilePath.endsWith(ext) ? importedFilePath : importedFilePath + ext;
451
+ try {
452
+ const importedContent = await fs.readFile(testPath, "utf-8");
453
+ found = true;
454
+ // Extract CSS imports from this file
455
+ cssImportPattern.lastIndex = 0; // Reset regex
456
+ let cssMatch2;
457
+ while ((cssMatch2 = cssImportPattern.exec(importedContent)) !== null) {
458
+ // Resolve relative CSS path
459
+ const cssPath = cssMatch2[1];
460
+ if (cssPath.startsWith(".")) {
461
+ const resolvedCssPath = path.resolve(path.dirname(testPath), cssPath);
462
+ const relativeCssPath = path.relative(path.join(config.root, "src"), resolvedCssPath);
463
+ const normalizedPath = relativeCssPath.replace(/\\/g, "/");
464
+ cssImports.add(normalizedPath);
465
+ }
466
+ else {
467
+ cssImports.add(cssPath);
468
+ }
469
+ }
470
+ break;
471
+ }
472
+ catch (err) {
473
+ // File doesn't exist with this extension, try next
474
+ }
475
+ }
476
+ }
477
+ catch (error) {
478
+ // Could not read imported file, skip
479
+ }
480
+ }
481
+ console.log(chalk.blue(`[SWITE CSS] Found ${cssImports.size} CSS import(s) in code`));
482
+ if (cssImports.size > 0) {
483
+ const cssArray = Array.from(cssImports);
484
+ console.log(chalk.blue(`[SWITE CSS] CSS imports found: ${cssArray.join(", ")}`));
485
+ // Verify CSS files exist before injecting them
486
+ const existingCssFiles = [];
487
+ for (const cssPath of cssArray) {
488
+ // Convert to file system path
489
+ const url = cssPath.startsWith("/") ? cssPath : `/src/${cssPath}`;
490
+ const filePath = url.startsWith("/src/")
491
+ ? path.join(config.root, url.substring(1)) // Remove leading /
492
+ : path.join(config.root, "src", cssPath);
493
+ console.log(chalk.blue(`[SWITE CSS] Checking if CSS file exists: ${filePath} (url: ${url})`));
494
+ try {
495
+ await fs.access(filePath);
496
+ console.log(chalk.green(`[SWITE CSS] ✅ CSS file exists: ${filePath}`));
497
+ existingCssFiles.push(url);
498
+ }
499
+ catch {
500
+ // CSS file doesn't exist, skip it
501
+ // This allows different apps/websites to have different CSS files
502
+ console.log(chalk.yellow(`[SWITE CSS] ⚠️ CSS file NOT found: ${filePath}, skipping`));
503
+ }
504
+ }
505
+ // Only inject CSS files that actually exist
506
+ console.log(chalk.blue(`[SWITE CSS] ${existingCssFiles.length} CSS file(s) exist out of ${cssArray.length} found`));
507
+ if (existingCssFiles.length === 0) {
508
+ console.log(chalk.yellow(`[SWITE CSS] ⚠️ No CSS files exist, skipping injection`));
509
+ }
510
+ else if (existingCssFiles.length > 0) {
511
+ const cssLinks = existingCssFiles
512
+ .map(url => ` <link rel="stylesheet" href="${url}">`)
513
+ .join("\n");
514
+ // Check if CSS links are already in HTML (to avoid duplicates)
515
+ const alreadyInjected = existingCssFiles.some(url => html.includes(`href="${url}"`) || html.includes(`href='${url}'`));
516
+ if (!alreadyInjected) {
517
+ // Inject CSS links before </head> - MUST happen before import map injection
518
+ const beforeReplace = html;
519
+ html = html.replace(/\s*<\/head>/i, `${cssLinks}\n </head>`);
520
+ if (html === beforeReplace) {
521
+ console.warn(chalk.yellow("[SWITE] Failed to inject CSS links - </head> not found"));
522
+ }
523
+ else {
524
+ console.log(chalk.green(`[SWITE] ✅ Injected ${existingCssFiles.length} CSS link(s): ${existingCssFiles.join(", ")}`));
525
+ }
526
+ }
527
+ else {
528
+ console.log(chalk.blue(`[SWITE CSS] CSS links already in HTML, skipping injection`));
529
+ }
530
+ }
531
+ }
532
+ }
533
+ catch (error) {
534
+ // If entry point doesn't exist or can't be read, continue without CSS injection
535
+ // Silently continue - CSS injection is optional
536
+ console.log(chalk.yellow(`[SWITE CSS] Could not extract CSS imports: ${error instanceof Error ? error.message : String(error)}`));
537
+ }
538
+ // Add/merge import map to help browser resolve bare module specifiers.
539
+ // If an importmap already exists in HTML, merge .swite/import-map.json entries
540
+ // into it — existing HTML entries take priority (never overwrite manual entries).
541
+ const cachedMapPath = path.join(config.root, ".swite", "import-map.json");
542
+ let switeImports = {};
543
+ try {
544
+ const raw = await fs.readFile(cachedMapPath, "utf-8");
545
+ const parsed = JSON.parse(raw);
546
+ if (parsed?.imports && typeof parsed.imports === "object") {
547
+ switeImports = parsed.imports;
548
+ }
549
+ }
550
+ catch {
551
+ // no cached map — nothing to merge
552
+ }
553
+ if (!html.includes('type="importmap"')) {
554
+ // No importmap at all — inject one from .swite/import-map.json
555
+ const importMap = `\n <script type="importmap">\n ${JSON.stringify({ imports: switeImports }, null, 2).replace(/\n/g, "\n ")}\n </script>`;
556
+ const beforeReplace = html;
557
+ html = html.replace(/\s*<\/head>/i, `${importMap}\n </head>`);
558
+ if (html === beforeReplace) {
559
+ console.warn("[SWITE] Failed to add import map - </head> not found or already replaced");
560
+ }
561
+ else {
562
+ console.log(`[SWITE] Added import map with ${Object.keys(switeImports).length} entries`);
563
+ }
564
+ }
565
+ else {
566
+ // Importmap already in HTML — merge swite entries without overwriting existing ones
567
+ console.log("[SWITE] Import map already exists in HTML — merging swite entries");
568
+ if (Object.keys(switeImports).length > 0) {
569
+ html = html.replace(/(<script\s+type=["']importmap["'][^>]*>)\s*([\s\S]*?)(\s*<\/script>)/i, (_match, open, body, close) => {
570
+ try {
571
+ const existing = JSON.parse(body.trim());
572
+ const existingImports = existing?.imports ?? {};
573
+ // Swite entries fill gaps; existing HTML entries win
574
+ const merged = { ...switeImports, ...existingImports };
575
+ return `${open}\n ${JSON.stringify({ imports: merged }, null, 2).replace(/\n/g, "\n ")}${close}`;
576
+ }
577
+ catch {
578
+ return _match; // parse failed — leave importmap untouched
579
+ }
580
+ });
581
+ }
582
+ }
583
+ res.send(html);
584
+ });
585
+ }
@@ -0,0 +1,6 @@
1
+ export declare class SwiteProxyError extends Error {
2
+ readonly status: number;
3
+ readonly responseBody: unknown;
4
+ constructor(status: number, message: string, responseBody?: unknown);
5
+ }
6
+ //# sourceMappingURL=SwiteProxyError.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"SwiteProxyError.d.ts","sourceRoot":"","sources":["../../src/proxy/SwiteProxyError.ts"],"names":[],"mappings":"AAAA,qBAAa,eAAgB,SAAQ,KAAK;IACxC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,YAAY,EAAE,OAAO,CAAC;gBAEnB,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,YAAY,CAAC,EAAE,OAAO;CAOpE"}
@@ -0,0 +1,9 @@
1
+ export class SwiteProxyError extends Error {
2
+ constructor(status, message, responseBody) {
3
+ super(message);
4
+ this.name = "SwiteProxyError";
5
+ this.status = status;
6
+ this.responseBody = responseBody ?? null;
7
+ Object.setPrototypeOf(this, SwiteProxyError.prototype);
8
+ }
9
+ }
@@ -0,0 +1,28 @@
1
+ import type { PythonServiceConfig } from "../config.js";
2
+ /**
3
+ * Called by swite start on startup.
4
+ * Disables localhost fallback — PYTHON_SERVICE_URL is the only valid base URL.
5
+ */
6
+ export declare function setProductionMode(): void;
7
+ /**
8
+ * Called by the swite dev process manager (S-03) on startup.
9
+ * Stores the resolved python service config for use by proxyToPython.
10
+ */
11
+ export declare function initPythonProxy(config: PythonServiceConfig): void;
12
+ export interface ProxyOptions {
13
+ method: "GET" | "POST" | "PUT" | "PATCH" | "DELETE";
14
+ path: string;
15
+ body?: unknown;
16
+ headers?: Record<string, string>;
17
+ }
18
+ /**
19
+ * Proxy a request from the Node server to the internal Python service.
20
+ *
21
+ * Resolves base URL from PYTHON_SERVICE_URL env var if set,
22
+ * otherwise falls back to http://localhost:{python.port} from config.
23
+ *
24
+ * Always injects X-Internal-Token header.
25
+ * Throws SwiteProxyError on non-2xx responses.
26
+ */
27
+ export declare function proxyToPython<T>(options: ProxyOptions): Promise<T>;
28
+ //# sourceMappingURL=proxyToPython.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"proxyToPython.d.ts","sourceRoot":"","sources":["../../src/proxy/proxyToPython.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,cAAc,CAAC;AAMxD;;;GAGG;AACH,wBAAgB,iBAAiB,IAAI,IAAI,CAExC;AAED;;;GAGG;AACH,wBAAgB,eAAe,CAAC,MAAM,EAAE,mBAAmB,GAAG,IAAI,CAEjE;AAED,MAAM,WAAW,YAAY;IAC3B,MAAM,EAAE,KAAK,GAAG,MAAM,GAAG,KAAK,GAAG,OAAO,GAAG,QAAQ,CAAC;IACpD,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CAClC;AAED;;;;;;;;GAQG;AACH,wBAAsB,aAAa,CAAC,CAAC,EAAE,OAAO,EAAE,YAAY,GAAG,OAAO,CAAC,CAAC,CAAC,CAiDxE"}