@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.
- package/CHANGELOG.md +23 -0
- package/DIRECTIVE.md +57 -2
- package/__tests__/import-rewriter-bug.test.ts +122 -135
- package/__tests__/security-r001-r002.test.ts +190 -0
- package/dist/build-engine/builder.js +9 -9
- package/dist/config/config.d.ts +0 -5
- package/dist/config/config.d.ts.map +1 -1
- package/dist/dev-engine/handlers/base-handler.d.ts +6 -0
- package/dist/dev-engine/handlers/base-handler.d.ts.map +1 -1
- package/dist/dev-engine/handlers/base-handler.js +91 -0
- package/dist/dev-engine/handlers/ui-handler.d.ts +0 -1
- package/dist/dev-engine/handlers/ui-handler.d.ts.map +1 -1
- package/dist/dev-engine/handlers/ui-handler.js +2 -64
- package/dist/dev-engine/handlers/uix-handler.d.ts +0 -1
- package/dist/dev-engine/handlers/uix-handler.d.ts.map +1 -1
- package/dist/dev-engine/handlers/uix-handler.js +2 -58
- package/dist/dev-engine/hmr/hmr.d.ts +10 -1
- package/dist/dev-engine/hmr/hmr.d.ts.map +1 -1
- package/dist/dev-engine/hmr/hmr.js +40 -2
- package/dist/dev-engine/middleware/static-files.d.ts.map +1 -1
- package/dist/dev-engine/middleware/static-files.js +145 -62
- package/dist/dev-engine/pythonDevManager.js +1 -1
- package/dist/dev-engine/router/file-router.d.ts.map +1 -1
- package/dist/dev-engine/router/file-router.js +2 -29
- package/dist/dev-engine/server.d.ts +7 -0
- package/dist/dev-engine/server.d.ts.map +1 -1
- package/dist/dev-engine/server.js +31 -3
- package/dist/kernel/package-finder.d.ts +0 -8
- package/dist/kernel/package-finder.d.ts.map +1 -1
- package/dist/kernel/package-finder.js +2 -2
- package/dist/kernel/package-registry.d.ts +6 -0
- package/dist/kernel/package-registry.d.ts.map +1 -1
- package/dist/kernel/package-registry.js +8 -0
- package/dist/kernel/workspace.d.ts.map +1 -1
- package/dist/kernel/workspace.js +12 -9
- package/package.json +26 -14
- package/src/build-engine/builder.ts +9 -9
- package/src/config/config.ts +0 -5
- package/src/dev-engine/handlers/base-handler.ts +109 -0
- package/src/dev-engine/handlers/ui-handler.ts +2 -82
- package/src/dev-engine/handlers/uix-handler.ts +2 -76
- package/src/dev-engine/hmr/hmr.ts +46 -1
- package/src/dev-engine/middleware/static-files.ts +813 -731
- package/src/dev-engine/pythonDevManager.ts +1 -1
- package/src/dev-engine/router/file-router.ts +2 -45
- package/src/dev-engine/server.ts +33 -3
- package/src/kernel/package-finder.ts +2 -2
- package/src/kernel/package-registry.ts +9 -0
- 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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
125
|
-
|
|
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
|
-
|
|
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
|
-
|
|
142
|
+
if (_debug) {
|
|
143
|
+
console.log(chalk.blue(`[static-files] Trying workspace root lib/: ${libPath}`));
|
|
144
|
+
}
|
|
133
145
|
}
|
|
134
146
|
else {
|
|
135
|
-
|
|
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
|
-
|
|
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
|
-
|
|
159
|
+
if (_debug) {
|
|
160
|
+
console.log(chalk.blue(`[static-files] Using parent directory lib/: ${libPath}`));
|
|
161
|
+
}
|
|
144
162
|
}
|
|
145
163
|
catch (error) {
|
|
146
|
-
|
|
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
|
-
|
|
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
|
-
|
|
176
|
+
if (_debug) {
|
|
177
|
+
console.log(chalk.blue(`[static-files] Using grandparent directory lib/: ${libPath}`));
|
|
178
|
+
}
|
|
155
179
|
}
|
|
156
180
|
catch (error2) {
|
|
157
|
-
|
|
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
|
-
|
|
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
|
-
|
|
178
|
-
|
|
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
|
-
|
|
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
|
-
|
|
223
|
+
if (_debug) {
|
|
224
|
+
console.log(chalk.green(`[static-files] ✅ Test CSS file exists: ${testCssPath}`));
|
|
225
|
+
}
|
|
190
226
|
}
|
|
191
227
|
catch (error) {
|
|
192
|
-
|
|
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
|
-
|
|
197
|
-
|
|
198
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
413
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
591
|
+
if (_debug) {
|
|
592
|
+
console.warn(chalk.yellow("[SWITE] Failed to inject CSS links - </head> not found"));
|
|
593
|
+
}
|
|
523
594
|
}
|
|
524
595
|
else {
|
|
525
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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 =
|
|
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;
|
|
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(`
|
|
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;
|
|
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
|
-
|
|
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
|
-
//
|
|
114
|
-
|
|
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;
|
|
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
|
-
|
|
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
|
-
|
|
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
|