@swissjs/swite 0.3.5 → 0.4.1

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 (49) hide show
  1. package/CHANGELOG.md +23 -0
  2. package/DIRECTIVE.md +57 -2
  3. package/__tests__/import-rewriter-bug.test.ts +122 -135
  4. package/__tests__/security-r001-r002.test.ts +190 -0
  5. package/dist/build-engine/builder.js +9 -9
  6. package/dist/config/config.d.ts +0 -5
  7. package/dist/config/config.d.ts.map +1 -1
  8. package/dist/dev-engine/handlers/base-handler.d.ts +6 -0
  9. package/dist/dev-engine/handlers/base-handler.d.ts.map +1 -1
  10. package/dist/dev-engine/handlers/base-handler.js +91 -0
  11. package/dist/dev-engine/handlers/ui-handler.d.ts +0 -1
  12. package/dist/dev-engine/handlers/ui-handler.d.ts.map +1 -1
  13. package/dist/dev-engine/handlers/ui-handler.js +2 -64
  14. package/dist/dev-engine/handlers/uix-handler.d.ts +0 -1
  15. package/dist/dev-engine/handlers/uix-handler.d.ts.map +1 -1
  16. package/dist/dev-engine/handlers/uix-handler.js +2 -58
  17. package/dist/dev-engine/hmr/hmr.d.ts +10 -1
  18. package/dist/dev-engine/hmr/hmr.d.ts.map +1 -1
  19. package/dist/dev-engine/hmr/hmr.js +40 -2
  20. package/dist/dev-engine/middleware/static-files.d.ts.map +1 -1
  21. package/dist/dev-engine/middleware/static-files.js +145 -62
  22. package/dist/dev-engine/pythonDevManager.js +1 -1
  23. package/dist/dev-engine/router/file-router.d.ts.map +1 -1
  24. package/dist/dev-engine/router/file-router.js +2 -29
  25. package/dist/dev-engine/server.d.ts +7 -0
  26. package/dist/dev-engine/server.d.ts.map +1 -1
  27. package/dist/dev-engine/server.js +31 -3
  28. package/dist/kernel/package-finder.d.ts +0 -8
  29. package/dist/kernel/package-finder.d.ts.map +1 -1
  30. package/dist/kernel/package-finder.js +2 -2
  31. package/dist/kernel/package-registry.d.ts +6 -0
  32. package/dist/kernel/package-registry.d.ts.map +1 -1
  33. package/dist/kernel/package-registry.js +8 -0
  34. package/dist/kernel/workspace.d.ts.map +1 -1
  35. package/dist/kernel/workspace.js +12 -9
  36. package/package.json +26 -14
  37. package/src/build-engine/builder.ts +9 -9
  38. package/src/config/config.ts +0 -5
  39. package/src/dev-engine/handlers/base-handler.ts +109 -0
  40. package/src/dev-engine/handlers/ui-handler.ts +2 -82
  41. package/src/dev-engine/handlers/uix-handler.ts +2 -76
  42. package/src/dev-engine/hmr/hmr.ts +46 -1
  43. package/src/dev-engine/middleware/static-files.ts +813 -731
  44. package/src/dev-engine/pythonDevManager.ts +1 -1
  45. package/src/dev-engine/router/file-router.ts +2 -45
  46. package/src/dev-engine/server.ts +33 -3
  47. package/src/kernel/package-finder.ts +2 -2
  48. package/src/kernel/package-registry.ts +9 -0
  49. package/src/kernel/workspace.ts +8 -10
@@ -12,7 +12,9 @@ import { findWorkspaceRoot } from "../../kernel/workspace.js";
12
12
  * Setup static file serving for public directory, node_modules, and workspace packages
13
13
  */
14
14
  export async function setupStaticFiles(app, config) {
15
- console.log(chalk.magenta(`[static-files] setupStaticFiles called with root: ${config.root}`));
15
+ const _debug = process.env["SWITE_DEBUG"] === "1";
16
+ if (_debug)
17
+ console.log(chalk.magenta(`[static-files] ⚡ setupStaticFiles called with root: ${config.root}`));
16
18
  // Static file serving - ONLY serve public directory
17
19
  // Do NOT serve dist/ folder - it contains old build artifacts with bare imports
18
20
  const publicPath = path.join(config.root, config.publicDir);
@@ -111,7 +113,9 @@ export async function setupStaticFiles(app, config) {
111
113
  }
112
114
  next();
113
115
  });
114
- console.log(chalk.gray(` 📦 Serving workspace node_modules from ${workspaceNodeModules}`));
116
+ if (_debug) {
117
+ console.log(chalk.gray(` 📦 Serving workspace node_modules from ${workspaceNodeModules}`));
118
+ }
115
119
  }
116
120
  catch {
117
121
  // Workspace node_modules doesn't exist, skip
@@ -121,45 +125,69 @@ export async function setupStaticFiles(app, config) {
121
125
  // This allows workspace packages to be served via HTTP
122
126
  // Reuse workspaceRoot from above if it exists, otherwise find it
123
127
  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}`));
128
+ if (_debug) {
129
+ console.log(chalk.blue(`[static-files] Workspace root: ${workspaceRoot}`));
130
+ }
131
+ if (_debug) {
132
+ console.log(chalk.blue(`[static-files] App root: ${config.root}`));
133
+ }
126
134
  // Try to serve lib/ directory - check both workspace root and app root parent
127
135
  let libPath = null;
128
- console.log(chalk.blue(`[static-files] Determining lib/ path... workspaceRoot: ${workspaceRoot}, config.root: ${config.root}`));
136
+ if (_debug) {
137
+ console.log(chalk.blue(`[static-files] Determining lib/ path... workspaceRoot: ${workspaceRoot}, config.root: ${config.root}`));
138
+ }
129
139
  // First, try workspace root
130
140
  if (workspaceRoot && workspaceRoot !== config.root) {
131
141
  libPath = path.join(workspaceRoot, "lib");
132
- console.log(chalk.blue(`[static-files] Trying workspace root lib/: ${libPath}`));
142
+ if (_debug) {
143
+ console.log(chalk.blue(`[static-files] Trying workspace root lib/: ${libPath}`));
144
+ }
133
145
  }
134
146
  else {
135
- console.log(chalk.yellow(`[static-files] Workspace root equals app root, trying parent directories...`));
147
+ if (_debug) {
148
+ console.log(chalk.yellow(`[static-files] Workspace root equals app root, trying parent directories...`));
149
+ }
136
150
  // If workspace root equals app root, try going up from app root
137
151
  const parentDir = path.dirname(config.root);
138
152
  const parentLibPath = path.join(parentDir, "lib");
139
- console.log(chalk.blue(`[static-files] Trying parent lib/: ${parentLibPath}`));
153
+ if (_debug) {
154
+ console.log(chalk.blue(`[static-files] Trying parent lib/: ${parentLibPath}`));
155
+ }
140
156
  try {
141
157
  await fs.access(parentLibPath);
142
158
  libPath = parentLibPath;
143
- console.log(chalk.blue(`[static-files] Using parent directory lib/: ${libPath}`));
159
+ if (_debug) {
160
+ console.log(chalk.blue(`[static-files] Using parent directory lib/: ${libPath}`));
161
+ }
144
162
  }
145
163
  catch (error) {
146
- console.log(chalk.yellow(`[static-files] Parent lib/ not found: ${error instanceof Error ? error.message : String(error)}`));
164
+ if (_debug) {
165
+ console.log(chalk.yellow(`[static-files] Parent lib/ not found: ${error instanceof Error ? error.message : String(error)}`));
166
+ }
147
167
  // Parent lib/ doesn't exist, try grandparent
148
168
  const grandparentDir = path.dirname(parentDir);
149
169
  const grandparentLibPath = path.join(grandparentDir, "lib");
150
- console.log(chalk.blue(`[static-files] Trying grandparent lib/: ${grandparentLibPath}`));
170
+ if (_debug) {
171
+ console.log(chalk.blue(`[static-files] Trying grandparent lib/: ${grandparentLibPath}`));
172
+ }
151
173
  try {
152
174
  await fs.access(grandparentLibPath);
153
175
  libPath = grandparentLibPath;
154
- console.log(chalk.blue(`[static-files] Using grandparent directory lib/: ${libPath}`));
176
+ if (_debug) {
177
+ console.log(chalk.blue(`[static-files] Using grandparent directory lib/: ${libPath}`));
178
+ }
155
179
  }
156
180
  catch (error2) {
157
- console.log(chalk.yellow(`[static-files] Grandparent lib/ not found: ${error2 instanceof Error ? error2.message : String(error2)}`));
181
+ if (_debug) {
182
+ console.log(chalk.yellow(`[static-files] Grandparent lib/ not found: ${error2 instanceof Error ? error2.message : String(error2)}`));
183
+ }
158
184
  }
159
185
  }
160
186
  }
161
187
  // Serve lib/ directory if found
162
- console.log(chalk.blue(`[static-files] Checking for lib/ directory... libPath: ${libPath}`));
188
+ if (_debug) {
189
+ console.log(chalk.blue(`[static-files] Checking for lib/ directory... libPath: ${libPath}`));
190
+ }
163
191
  // ALWAYS try to register /lib/ static serving
164
192
  // Calculate the lib path - prefer workspace root, fallback to parent of app root
165
193
  let finalLibPath;
@@ -174,32 +202,50 @@ export async function setupStaticFiles(app, config) {
174
202
  const parentDir = path.dirname(config.root);
175
203
  finalLibPath = path.join(parentDir, "lib");
176
204
  }
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}`));
205
+ if (_debug) {
206
+ console.log(chalk.blue(`[static-files] Final lib path to check: ${finalLibPath}`));
207
+ }
208
+ if (_debug) {
209
+ console.log(chalk.blue(`[static-files] workspaceRoot: ${workspaceRoot}, config.root: ${config.root}`));
210
+ }
179
211
  // Try to access the directory
180
212
  let libPathExists = false;
181
213
  try {
182
214
  await fs.access(finalLibPath);
183
215
  libPathExists = true;
184
- console.log(chalk.green(`[static-files] ✅ Found lib/ directory at: ${finalLibPath}`));
216
+ if (_debug) {
217
+ console.log(chalk.green(`[static-files] ✅ Found lib/ directory at: ${finalLibPath}`));
218
+ }
185
219
  // Verify the CSS file exists
186
220
  const testCssPath = path.join(finalLibPath, "skltn", "src", "css", "index.css");
187
221
  try {
188
222
  await fs.access(testCssPath);
189
- console.log(chalk.green(`[static-files] ✅ Test CSS file exists: ${testCssPath}`));
223
+ if (_debug) {
224
+ console.log(chalk.green(`[static-files] ✅ Test CSS file exists: ${testCssPath}`));
225
+ }
190
226
  }
191
227
  catch (error) {
192
- console.error(chalk.yellow(`[static-files] ⚠️ Test CSS file NOT found: ${testCssPath}`));
228
+ if (_debug) {
229
+ console.error(chalk.yellow(`[static-files] ⚠️ Test CSS file NOT found: ${testCssPath}`));
230
+ }
193
231
  }
194
232
  }
195
233
  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!`));
234
+ if (_debug) {
235
+ console.error(chalk.red(`[static-files] lib/ directory not found at: ${finalLibPath}`));
236
+ }
237
+ if (_debug) {
238
+ console.error(chalk.red(`[static-files] Error: ${error instanceof Error ? error.message : String(error)}`));
239
+ }
240
+ if (_debug) {
241
+ console.error(chalk.red(`[static-files] ⚠️ /lib middleware will NOT be registered - CSS files will 404!`));
242
+ }
199
243
  }
200
244
  // Register static file middleware ONLY if directory exists
201
245
  if (libPathExists) {
202
- console.log(chalk.green(`[static-files] ✅ Registering /lib middleware with finalLibPath: ${finalLibPath}`));
246
+ if (_debug) {
247
+ console.log(chalk.green(`[static-files] ✅ Registering /lib middleware with finalLibPath: ${finalLibPath}`));
248
+ }
203
249
  // CRITICAL: First middleware to block source files BEFORE any express.static can serve them
204
250
  // This MUST run before express.static to prevent wrong MIME types
205
251
  app.use("/lib", (req, res, next) => {
@@ -212,10 +258,14 @@ export async function setupStaticFiles(app, config) {
212
258
  path.endsWith(".ui") || path.endsWith(".uix") || path.endsWith(".ts") ||
213
259
  (path.endsWith(".js") && !path.includes("node_modules")) || path.endsWith(".mjs");
214
260
  if (isSourceFile) {
215
- console.log(chalk.red(`[static-files] ⚠️ FIRST BLOCK: Skipping source file: url=${url}, path=${path} - should be handled by module middleware`));
261
+ if (_debug) {
262
+ console.log(chalk.red(`[static-files] ⚠️ FIRST BLOCK: Skipping source file: url=${url}, path=${path} - should be handled by module middleware`));
263
+ }
216
264
  return next(); // Let module transformation middleware handle it
217
265
  }
218
- console.log(chalk.cyan(`[static-files] Request for /lib${path} (static file)`));
266
+ if (_debug) {
267
+ console.log(chalk.cyan(`[static-files] Request for /lib${path} (static file)`));
268
+ }
219
269
  next();
220
270
  });
221
271
  // REMOVED: Logging middleware - it was just adding noise
@@ -230,7 +280,9 @@ export async function setupStaticFiles(app, config) {
230
280
  res.setHeader("Expires", "0");
231
281
  }
232
282
  catch (error) {
233
- console.error(chalk.red(`[static-files] Error setting headers for ${filePath}:`), error);
283
+ if (_debug) {
284
+ console.error(chalk.red(`[static-files] Error setting headers for ${filePath}:`), error);
285
+ }
234
286
  }
235
287
  },
236
288
  });
@@ -246,7 +298,9 @@ export async function setupStaticFiles(app, config) {
246
298
  path.endsWith(".ui") || path.endsWith(".uix") || path.endsWith(".ts") ||
247
299
  (path.endsWith(".js") && !path.includes("node_modules")) || path.endsWith(".mjs");
248
300
  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`));
301
+ if (_debug) {
302
+ console.log(chalk.red(`[static-files /lib express.static] ⚠️ BLOCKING source file: url=${url}, path=${path} - should be handled by module transformation middleware`));
303
+ }
250
304
  // CRITICAL: Don't call libStatic - return next() to skip it
251
305
  return next(); // Let module transformation middleware handle it
252
306
  }
@@ -257,7 +311,9 @@ export async function setupStaticFiles(app, config) {
257
311
  // Source files should be handled by module transformation middleware (registered before this)
258
312
  // Only static files (CSS, images) should be served, and they're handled by the custom handler above
259
313
  // 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}`));
314
+ if (_debug) {
315
+ console.log(chalk.gray(` 📦 Serving workspace lib/ from ${finalLibPath}`));
316
+ }
261
317
  }
262
318
  // Continue with other workspace directories if workspaceRoot is different from app root
263
319
  if (workspaceRoot && workspaceRoot !== config.root) {
@@ -266,7 +322,9 @@ export async function setupStaticFiles(app, config) {
266
322
  try {
267
323
  await fs.access(librariesPath);
268
324
  app.use("/libraries", express.static(librariesPath));
269
- console.log(chalk.gray(` 📦 Serving workspace libraries/ from ${librariesPath}`));
325
+ if (_debug) {
326
+ console.log(chalk.gray(` 📦 Serving workspace libraries/ from ${librariesPath}`));
327
+ }
270
328
  }
271
329
  catch {
272
330
  // libraries/ doesn't exist, skip
@@ -294,7 +352,9 @@ export async function setupStaticFiles(app, config) {
294
352
  }
295
353
  express.static(modulesPath)(req, res, next);
296
354
  });
297
- console.log(chalk.gray(` 📦 Serving workspace modules/ from ${modulesPath}`));
355
+ if (_debug) {
356
+ console.log(chalk.gray(` 📦 Serving workspace modules/ from ${modulesPath}`));
357
+ }
298
358
  }
299
359
  catch {
300
360
  // modules/ doesn't exist, skip
@@ -309,22 +369,20 @@ export async function setupStaticFiles(app, config) {
309
369
  * Setup SPA fallback - serves index.html for all unmatched routes
310
370
  */
311
371
  export async function setupSPAFallback(app, config) {
312
- console.log(chalk.magenta(`[SWITE] setupSPAFallback loaded - VERSION 3.0.0 (NO HARDCODED CSS)`));
372
+ const _debug = process.env["SWITE_DEBUG"] === "1";
373
+ if (_debug) {
374
+ console.log(chalk.magenta(`[SWITE] setupSPAFallback loaded - VERSION 0.3.5 (NO HARDCODED CSS)`));
375
+ }
313
376
  // Use app.all() to catch ALL HTTP methods, but only for non-source files
314
377
  app.all("*", async (req, res, next) => {
315
378
  const url = req.url.split("?")[0];
316
379
  const fullUrl = req.url;
317
380
  const accept = String(req.headers?.accept || "");
318
381
  // 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
382
  // --- CRITICAL SAFETY CHECK ---
323
383
  // NEVER serve HTML for /src/* requests - these are source files that must be handled by middleware
324
384
  // Even if middleware fails, we should return 404, not HTML
325
385
  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
386
  res.status(404).setHeader("Content-Type", "text/plain");
329
387
  res.send(`File not found: ${url}`);
330
388
  return;
@@ -333,8 +391,6 @@ export async function setupSPAFallback(app, config) {
333
391
  // NEVER serve HTML for /swiss-packages/* requests - these are SWISS framework packages
334
392
  // They should be handled by TS/JS handlers to rewrite imports
335
393
  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
394
  res.status(404).setHeader("Content-Type", "text/plain");
339
395
  res.send(`File not found: ${url}`);
340
396
  return;
@@ -343,19 +399,13 @@ export async function setupSPAFallback(app, config) {
343
399
  // NEVER serve HTML for /lib/* requests - these are workspace library files
344
400
  // They should be handled by static file middleware
345
401
  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
402
  res.status(404).setHeader("Content-Type", "text/plain");
349
403
  res.send(`File not found: ${url}`);
350
404
  return;
351
405
  }
352
406
  // 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
407
  // Log if SPA fallback is being hit for .ui files (this should NOT happen after /src check)
356
408
  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
409
  }
360
410
  // DO NOT serve HTML for source files - they should be handled by handlers
361
411
  // This prevents the SPA fallback from catching .ui, .uix, .ts, .js, .mjs files
@@ -369,7 +419,6 @@ export async function setupSPAFallback(app, config) {
369
419
  url.endsWith(".json")) {
370
420
  // These should have been handled by middleware handlers
371
421
  // 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
422
  res.status(404).setHeader("Content-Type", "text/plain");
374
423
  res.send(`File not found: ${url}`);
375
424
  return;
@@ -409,12 +458,18 @@ export async function setupSPAFallback(app, config) {
409
458
  // This dynamically discovers CSS files from the app's entry point
410
459
  // CRITICAL: This MUST run before import map injection
411
460
  // 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}`));
461
+ if (_debug) {
462
+ console.log(chalk.magenta(`[SWITE CSS] ========== CSS EXTRACTION START (VERSION 0.3.5) ==========`));
463
+ }
464
+ if (_debug) {
465
+ console.log(chalk.magenta(`[SWITE CSS] App root: ${config.root}`));
466
+ }
414
467
  try {
415
468
  const entryFile = config.entry ?? "src/index.ui";
416
469
  const entryPointPath = path.join(config.root, entryFile);
417
- console.log(chalk.blue(`[SWITE CSS] Checking entry point: ${entryPointPath}`));
470
+ if (_debug) {
471
+ console.log(chalk.blue(`[SWITE CSS] Checking entry point: ${entryPointPath}`));
472
+ }
418
473
  const entryPointContent = await fs.readFile(entryPointPath, "utf-8");
419
474
  // Extract CSS imports using regex
420
475
  const cssImportPattern = /import\s+['"](.*?\.css)['"];?/g;
@@ -479,10 +534,14 @@ export async function setupSPAFallback(app, config) {
479
534
  // Could not read imported file, skip
480
535
  }
481
536
  }
482
- console.log(chalk.blue(`[SWITE CSS] Found ${cssImports.size} CSS import(s) in code`));
537
+ if (_debug) {
538
+ console.log(chalk.blue(`[SWITE CSS] Found ${cssImports.size} CSS import(s) in code`));
539
+ }
483
540
  if (cssImports.size > 0) {
484
541
  const cssArray = Array.from(cssImports);
485
- console.log(chalk.blue(`[SWITE CSS] CSS imports found: ${cssArray.join(", ")}`));
542
+ if (_debug) {
543
+ console.log(chalk.blue(`[SWITE CSS] CSS imports found: ${cssArray.join(", ")}`));
544
+ }
486
545
  // Verify CSS files exist before injecting them
487
546
  const existingCssFiles = [];
488
547
  for (const cssPath of cssArray) {
@@ -491,22 +550,32 @@ export async function setupSPAFallback(app, config) {
491
550
  const filePath = url.startsWith("/src/")
492
551
  ? path.join(config.root, url.substring(1)) // Remove leading /
493
552
  : path.join(config.root, "src", cssPath);
494
- console.log(chalk.blue(`[SWITE CSS] Checking if CSS file exists: ${filePath} (url: ${url})`));
553
+ if (_debug) {
554
+ console.log(chalk.blue(`[SWITE CSS] Checking if CSS file exists: ${filePath} (url: ${url})`));
555
+ }
495
556
  try {
496
557
  await fs.access(filePath);
497
- console.log(chalk.green(`[SWITE CSS] ✅ CSS file exists: ${filePath}`));
558
+ if (_debug) {
559
+ console.log(chalk.green(`[SWITE CSS] ✅ CSS file exists: ${filePath}`));
560
+ }
498
561
  existingCssFiles.push(url);
499
562
  }
500
563
  catch {
501
564
  // CSS file doesn't exist, skip it
502
565
  // This allows different apps/websites to have different CSS files
503
- console.log(chalk.yellow(`[SWITE CSS] ⚠️ CSS file NOT found: ${filePath}, skipping`));
566
+ if (_debug) {
567
+ console.log(chalk.yellow(`[SWITE CSS] ⚠️ CSS file NOT found: ${filePath}, skipping`));
568
+ }
504
569
  }
505
570
  }
506
571
  // Only inject CSS files that actually exist
507
- console.log(chalk.blue(`[SWITE CSS] ${existingCssFiles.length} CSS file(s) exist out of ${cssArray.length} found`));
572
+ if (_debug) {
573
+ console.log(chalk.blue(`[SWITE CSS] ${existingCssFiles.length} CSS file(s) exist out of ${cssArray.length} found`));
574
+ }
508
575
  if (existingCssFiles.length === 0) {
509
- console.log(chalk.yellow(`[SWITE CSS] ⚠️ No CSS files exist, skipping injection`));
576
+ if (_debug) {
577
+ console.log(chalk.yellow(`[SWITE CSS] ⚠️ No CSS files exist, skipping injection`));
578
+ }
510
579
  }
511
580
  else if (existingCssFiles.length > 0) {
512
581
  const cssLinks = existingCssFiles
@@ -519,14 +588,20 @@ export async function setupSPAFallback(app, config) {
519
588
  const beforeReplace = html;
520
589
  html = html.replace(/\s*<\/head>/i, `${cssLinks}\n </head>`);
521
590
  if (html === beforeReplace) {
522
- console.warn(chalk.yellow("[SWITE] Failed to inject CSS links - </head> not found"));
591
+ if (_debug) {
592
+ console.warn(chalk.yellow("[SWITE] Failed to inject CSS links - </head> not found"));
593
+ }
523
594
  }
524
595
  else {
525
- console.log(chalk.green(`[SWITE] ✅ Injected ${existingCssFiles.length} CSS link(s): ${existingCssFiles.join(", ")}`));
596
+ if (_debug) {
597
+ console.log(chalk.green(`[SWITE] ✅ Injected ${existingCssFiles.length} CSS link(s): ${existingCssFiles.join(", ")}`));
598
+ }
526
599
  }
527
600
  }
528
601
  else {
529
- console.log(chalk.blue(`[SWITE CSS] CSS links already in HTML, skipping injection`));
602
+ if (_debug) {
603
+ console.log(chalk.blue(`[SWITE CSS] CSS links already in HTML, skipping injection`));
604
+ }
530
605
  }
531
606
  }
532
607
  }
@@ -534,7 +609,9 @@ export async function setupSPAFallback(app, config) {
534
609
  catch (error) {
535
610
  // If entry point doesn't exist or can't be read, continue without CSS injection
536
611
  // Silently continue - CSS injection is optional
537
- console.log(chalk.yellow(`[SWITE CSS] Could not extract CSS imports: ${error instanceof Error ? error.message : String(error)}`));
612
+ if (_debug) {
613
+ console.log(chalk.yellow(`[SWITE CSS] Could not extract CSS imports: ${error instanceof Error ? error.message : String(error)}`));
614
+ }
538
615
  }
539
616
  // Add/merge import map to help browser resolve bare module specifiers.
540
617
  // If an importmap already exists in HTML, merge .swite/import-map.json entries
@@ -557,15 +634,21 @@ export async function setupSPAFallback(app, config) {
557
634
  const beforeReplace = html;
558
635
  html = html.replace(/\s*<\/head>/i, `${importMap}\n </head>`);
559
636
  if (html === beforeReplace) {
560
- console.warn("[SWITE] Failed to add import map - </head> not found or already replaced");
637
+ if (_debug) {
638
+ console.warn("[SWITE] Failed to add import map - </head> not found or already replaced");
639
+ }
561
640
  }
562
641
  else {
563
- console.log(`[SWITE] Added import map with ${Object.keys(switeImports).length} entries`);
642
+ if (_debug) {
643
+ console.log(`[SWITE] Added import map with ${Object.keys(switeImports).length} entries`);
644
+ }
564
645
  }
565
646
  }
566
647
  else {
567
648
  // Importmap already in HTML — merge swite entries without overwriting existing ones
568
- console.log("[SWITE] Import map already exists in HTML — merging swite entries");
649
+ if (_debug) {
650
+ console.log("[SWITE] Import map already exists in HTML — merging swite entries");
651
+ }
569
652
  if (Object.keys(switeImports).length > 0) {
570
653
  html = html.replace(/(<script\s+type=["']importmap["'][^>]*>)\s*([\s\S]*?)(\s*<\/script>)/i, (_match, open, body, close) => {
571
654
  try {
@@ -3,7 +3,7 @@ import { resolve } from "node:path";
3
3
  import chalk from "chalk";
4
4
  import { initPythonProxy } from "../adapters/proxy/proxyToPython.js";
5
5
  const POLL_INTERVAL_MS = 500;
6
- const HEALTH_TIMEOUT_MS = 15000;
6
+ const HEALTH_TIMEOUT_MS = 30000;
7
7
  const BACKOFF_THRESHOLD = 5;
8
8
  let _child = null;
9
9
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"file-router.d.ts","sourceRoot":"","sources":["../../../src/dev-engine/router/file-router.ts"],"names":[],"mappings":"AASA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,eAAe,CAAC;AACrD,OAAO,EAAE,YAAY,EAAE,MAAM,kCAAkC,CAAC;AAChE,OAAO,EAAE,iBAAiB,EAAE,MAAM,iCAAiC,CAAC;AACpE,OAAO,EAAE,SAAS,EAAE,MAAM,eAAe,CAAC;AAG1C,MAAM,WAAW,gBAAgB;IAC/B,IAAI,EAAE,MAAM,CAAC;IACb,GAAG,EAAE,SAAS,CAAC;CAChB;AAED,MAAM,WAAW,gBAAgB;IAC/B,YAAY,EAAE,YAAY,GAAG,IAAI,CAAC;IAClC,YAAY,EAAE,OAAO,CAAC,UAAU,CAAC,OAAO,iBAAiB,CAAC,CAAC,GAAG,IAAI,CAAC;IACnE,MAAM,EAAE,eAAe,EAAE,CAAC;CAC3B;AAED;;;GAGG;AACH,wBAAsB,eAAe,CACnC,MAAM,EAAE,gBAAgB,GACvB,OAAO,CAAC,gBAAgB,CAAC,CAmI3B"}
1
+ {"version":3,"file":"file-router.d.ts","sourceRoot":"","sources":["../../../src/dev-engine/router/file-router.ts"],"names":[],"mappings":"AASA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,eAAe,CAAC;AACrD,OAAO,EAAE,YAAY,EAAE,MAAM,kCAAkC,CAAC;AAChE,OAAO,EAAE,iBAAiB,EAAE,MAAM,iCAAiC,CAAC;AACpE,OAAO,EAAE,SAAS,EAAE,MAAM,eAAe,CAAC;AAE1C,MAAM,WAAW,gBAAgB;IAC/B,IAAI,EAAE,MAAM,CAAC;IACb,GAAG,EAAE,SAAS,CAAC;CAChB;AAED,MAAM,WAAW,gBAAgB;IAC/B,YAAY,EAAE,YAAY,GAAG,IAAI,CAAC;IAClC,YAAY,EAAE,OAAO,CAAC,UAAU,CAAC,OAAO,iBAAiB,CAAC,CAAC,GAAG,IAAI,CAAC;IACnE,MAAM,EAAE,eAAe,EAAE,CAAC;CAC3B;AAED;;;GAGG;AACH,wBAAsB,eAAe,CACnC,MAAM,EAAE,gBAAgB,GACvB,OAAO,CAAC,gBAAgB,CAAC,CAyF3B"}
@@ -9,7 +9,6 @@ import chalk from "chalk";
9
9
  import { RouteScanner } from "@swissjs/plugin-file-router/core";
10
10
  import { createFileWatcher } from "@swissjs/plugin-file-router/dev";
11
11
  import { HMREngine } from "../hmr/hmr.js";
12
- import { findWorkspaceRoot } from "../../kernel/workspace.js";
13
12
  /**
14
13
  * Setup file-based routing
15
14
  * Scans for route files in pages/ directories and watches for changes
@@ -21,7 +20,6 @@ export async function setupFileRouter(config) {
21
20
  routes: [],
22
21
  };
23
22
  try {
24
- const workspaceRoot = await findWorkspaceRoot(config.root);
25
23
  const appRoot = config.root;
26
24
  // Initialize route scanner
27
25
  result.routeScanner = new RouteScanner({
@@ -30,42 +28,17 @@ export async function setupFileRouter(config) {
30
28
  layouts: true,
31
29
  lazyLoading: true,
32
30
  });
33
- // Scan routes from multiple locations:
34
- // 1. App's pages directory (apps/alpine/src/pages)
35
- // 2. SKLTN's pages directory (framework/skltn/src/pages) - for reusable auth pages
36
31
  const routesToScan = [];
37
- // App pages
32
+ // App pages directory
38
33
  const appPagesDir = path.join(appRoot, "src", "pages");
39
34
  try {
40
35
  await fs.access(appPagesDir);
41
36
  routesToScan.push(appPagesDir);
42
- console.log(chalk.gray(` 📄 Scanning app routes from ${appPagesDir}`));
37
+ console.log(chalk.gray(` Scanning app routes from ${appPagesDir}`));
43
38
  }
44
39
  catch {
45
40
  // pages directory doesn't exist, skip
46
41
  }
47
- // SKLTN pages (if workspace root exists)
48
- if (workspaceRoot && workspaceRoot !== appRoot) {
49
- // Try framework/skltn first (new location), then fallback to lib/skltn (legacy)
50
- const skltnPagesDir = path.join(workspaceRoot, "framework", "skltn", "src", "pages");
51
- const legacySkltnPagesDir = path.join(workspaceRoot, "lib", "skltn", "src", "pages");
52
- try {
53
- await fs.access(skltnPagesDir);
54
- routesToScan.push(skltnPagesDir);
55
- console.log(chalk.gray(` 📄 Scanning SKLTN routes from ${skltnPagesDir}`));
56
- }
57
- catch {
58
- // Try legacy location
59
- try {
60
- await fs.access(legacySkltnPagesDir);
61
- routesToScan.push(legacySkltnPagesDir);
62
- console.log(chalk.gray(` 📄 Scanning SKLTN routes from ${legacySkltnPagesDir} (legacy)`));
63
- }
64
- catch {
65
- // pages directory doesn't exist, skip
66
- }
67
- }
68
- }
69
42
  // Scan all route directories
70
43
  for (const pagesDir of routesToScan) {
71
44
  try {
@@ -16,6 +16,13 @@ export declare class SwiteServer {
16
16
  private routeWatcher;
17
17
  private routes;
18
18
  constructor(config?: Partial<SwiteConfig>);
19
+ /**
20
+ * Build the list of origins that are allowed to open an HMR WebSocket.
21
+ * Always includes both the configured host and its loopback alias so the
22
+ * browser can connect regardless of whether the dev typed "localhost" or
23
+ * "127.0.0.1" in the address bar.
24
+ */
25
+ private buildHmrAllowedOrigins;
19
26
  private findWorkspaceRoot;
20
27
  start(): Promise<void>;
21
28
  }
@@ -1 +1 @@
1
- {"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../../src/dev-engine/server.ts"],"names":[],"mappings":"AAoBA,MAAM,WAAW,WAAW;IAC1B,IAAI,EAAE,MAAM,CAAC;IAKb,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;IAClB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,OAAO,CAAC;IACd,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,qBAAa,WAAW;IACtB,OAAO,CAAC,GAAG,CAAa;IACxB,OAAO,CAAC,QAAQ,CAAiB;IACjC,OAAO,CAAC,GAAG,CAAY;IACvB,OAAO,CAAC,MAAM,CAAc;IAC5B,OAAO,CAAC,YAAY,CAA6B;IACjD,OAAO,CAAC,YAAY,CACb;IACP,OAAO,CAAC,MAAM,CAAyB;gBAE3B,MAAM,GAAE,OAAO,CAAC,WAAW,CAAM;YAe/B,iBAAiB;IAuBzB,KAAK;CA0EZ"}
1
+ {"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../../src/dev-engine/server.ts"],"names":[],"mappings":"AAoBA,MAAM,WAAW,WAAW;IAC1B,IAAI,EAAE,MAAM,CAAC;IAKb,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;IAClB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,OAAO,CAAC;IACd,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,qBAAa,WAAW;IACtB,OAAO,CAAC,GAAG,CAAa;IACxB,OAAO,CAAC,QAAQ,CAAiB;IACjC,OAAO,CAAC,GAAG,CAAY;IACvB,OAAO,CAAC,MAAM,CAAc;IAC5B,OAAO,CAAC,YAAY,CAA6B;IACjD,OAAO,CAAC,YAAY,CACb;IACP,OAAO,CAAC,MAAM,CAAyB;gBAE3B,MAAM,GAAE,OAAO,CAAC,WAAW,CAAM;IAmB7C;;;;;OAKG;IACH,OAAO,CAAC,sBAAsB;YAehB,iBAAiB;IAuBzB,KAAK;CA+EZ"}
@@ -30,7 +30,30 @@ export class SwiteServer {
30
30
  ...config,
31
31
  };
32
32
  this.resolver = new ModuleResolver(this.config.root);
33
- this.hmr = new HMREngine(this.config.root, this.config.hmrPort);
33
+ // Security (R-002): build the HMR allowed-origin list from the dev server
34
+ // host+port so the WebSocket server can reject cross-origin connections.
35
+ // When host is "localhost" we also add the numeric loopback form and vice
36
+ // versa — browsers send whichever name the user typed in the address bar.
37
+ const devOrigins = this.buildHmrAllowedOrigins();
38
+ this.hmr = new HMREngine(this.config.root, this.config.hmrPort, devOrigins);
39
+ }
40
+ /**
41
+ * Build the list of origins that are allowed to open an HMR WebSocket.
42
+ * Always includes both the configured host and its loopback alias so the
43
+ * browser can connect regardless of whether the dev typed "localhost" or
44
+ * "127.0.0.1" in the address bar.
45
+ */
46
+ buildHmrAllowedOrigins() {
47
+ const { host, port } = this.config;
48
+ const origins = [];
49
+ const add = (h) => origins.push(`http://${h}:${port}`);
50
+ add(host);
51
+ // When the dev host is either loopback alias, also allow the other form.
52
+ if (host === "localhost")
53
+ add("127.0.0.1");
54
+ else if (host === "127.0.0.1")
55
+ add("localhost");
56
+ return origins;
34
57
  }
35
58
  // CG-03: find workspace root by walking up from startDir
36
59
  async findWorkspaceRoot(startDir) {
@@ -110,8 +133,13 @@ export class SwiteServer {
110
133
  await this.hmr.start(userConfig?.excludeFromHmr);
111
134
  console.timeEnd("HMR Start");
112
135
  // Start HTTP server
113
- // Use 0.0.0.0 to bind to all interfaces (IPv4 and IPv6)
114
- const bindHost = this.config.host === "localhost" ? "0.0.0.0" : this.config.host;
136
+ // Security (R-001): honour the requested host literally.
137
+ // The default host is "localhost" which Node binds to the loopback
138
+ // interface only (127.0.0.1 / ::1). Binding all interfaces (0.0.0.0)
139
+ // must be an explicit opt-in: the developer must set host to "0.0.0.0"
140
+ // in their swite.config.ts or pass --host 0.0.0.0 on the CLI.
141
+ // We never silently rewrite a requested loopback address to 0.0.0.0.
142
+ const bindHost = this.config.host;
115
143
  console.time("HTTP Listen");
116
144
  await new Promise((resolve) => {
117
145
  this.app.listen(this.config.port, bindHost, () => {
@@ -6,10 +6,6 @@ export interface PackageLocation {
6
6
  path: string;
7
7
  type: 'swiss-lib' | 'workspace' | 'node_modules';
8
8
  }
9
- /**
10
- * Find any sibling monorepo by searching for its package.json
11
- */
12
- export declare function findSiblingRepository(startPath: string, repoName: string): Promise<string | null>;
13
9
  /**
14
10
  * Find the co-located framework monorepo. Tries each name in `fallbackNames`
15
11
  * in order and returns the first match. The default list is ['swiss-lib'] for
@@ -22,8 +18,4 @@ export declare function findSwissLibMonorepo(startPath: string, fallbackNames?:
22
18
  * In development, we prioritize local sibling source code.
23
19
  */
24
20
  export declare function findPackage(packageName: string, startPath: string, workspaceRoot?: string | null): Promise<PackageLocation | null>;
25
- /**
26
- * Find all possible workspace roots by searching up the tree
27
- */
28
- export declare function findWorkspaceRoots(startPath: string): Promise<string[]>;
29
21
  //# sourceMappingURL=package-finder.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"package-finder.d.ts","sourceRoot":"","sources":["../../src/kernel/package-finder.ts"],"names":[],"mappings":"AASA;;;GAGG;AAEH,MAAM,WAAW,eAAe;IAC9B,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,WAAW,GAAG,WAAW,GAAG,cAAc,CAAC;CAClD;AAED;;GAEG;AACH,wBAAsB,qBAAqB,CAAC,SAAS,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAyBvG;AAED;;;;;GAKG;AACH,wBAAsB,oBAAoB,CACxC,SAAS,EAAE,MAAM,EACjB,aAAa,GAAE,MAAM,EAAkB,GACtC,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAMxB;AAED;;;GAGG;AACH,wBAAsB,WAAW,CAC/B,WAAW,EAAE,MAAM,EACnB,SAAS,EAAE,MAAM,EACjB,aAAa,CAAC,EAAE,MAAM,GAAG,IAAI,GAC5B,OAAO,CAAC,eAAe,GAAG,IAAI,CAAC,CAuEjC;AAED;;GAEG;AACH,wBAAsB,kBAAkB,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,CA2B7E"}
1
+ {"version":3,"file":"package-finder.d.ts","sourceRoot":"","sources":["../../src/kernel/package-finder.ts"],"names":[],"mappings":"AASA;;;GAGG;AAEH,MAAM,WAAW,eAAe;IAC9B,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,WAAW,GAAG,WAAW,GAAG,cAAc,CAAC;CAClD;AAgCD;;;;;GAKG;AACH,wBAAsB,oBAAoB,CACxC,SAAS,EAAE,MAAM,EACjB,aAAa,GAAE,MAAM,EAAkB,GACtC,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAMxB;AAED;;;GAGG;AACH,wBAAsB,WAAW,CAC/B,WAAW,EAAE,MAAM,EACnB,SAAS,EAAE,MAAM,EACjB,aAAa,CAAC,EAAE,MAAM,GAAG,IAAI,GAC5B,OAAO,CAAC,eAAe,GAAG,IAAI,CAAC,CAuEjC"}
@@ -8,7 +8,7 @@ import path from "node:path";
8
8
  /**
9
9
  * Find any sibling monorepo by searching for its package.json
10
10
  */
11
- export async function findSiblingRepository(startPath, repoName) {
11
+ async function findSiblingRepository(startPath, repoName) {
12
12
  let current = startPath;
13
13
  for (let i = 0; i < 20; i++) {
14
14
  const siblingPath = path.join(current, repoName);
@@ -121,7 +121,7 @@ export async function findPackage(packageName, startPath, workspaceRoot) {
121
121
  /**
122
122
  * Find all possible workspace roots by searching up the tree
123
123
  */
124
- export async function findWorkspaceRoots(startPath) {
124
+ async function findWorkspaceRoots(startPath) {
125
125
  const roots = [];
126
126
  let current = startPath;
127
127
  for (let i = 0; i < 20; i++) {
@@ -33,4 +33,10 @@ export declare class PackageRegistry {
33
33
  getPackageCount(): number;
34
34
  }
35
35
  export declare function getPackageRegistry(): PackageRegistry;
36
+ /**
37
+ * Reset the package registry singleton. For use in tests only.
38
+ * Call before creating a ModuleResolver to prevent the previous scan state
39
+ * from leaking between tests.
40
+ */
41
+ export declare function resetPackageRegistry(): void;
36
42
  //# sourceMappingURL=package-registry.d.ts.map