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