@float.js/core 2.0.7 → 2.2.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.
@@ -1,5 +1,11 @@
1
1
  var __defProp = Object.defineProperty;
2
2
  var __getOwnPropNames = Object.getOwnPropertyNames;
3
+ var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
4
+ get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
5
+ }) : x)(function(x) {
6
+ if (typeof require !== "undefined") return require.apply(this, arguments);
7
+ throw Error('Dynamic require of "' + x + '" is not supported');
8
+ });
3
9
  var __esm = (fn, res) => function __init() {
4
10
  return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
5
11
  };
@@ -729,7 +735,7 @@ function generateAPIResponse(state) {
729
735
  function createDevDashboard(options = {}) {
730
736
  const {
731
737
  enabled = process.env.NODE_ENV !== "production",
732
- path: path6 = "/__float",
738
+ path: path9 = "/__float",
733
739
  auth
734
740
  } = options;
735
741
  if (!enabled) {
@@ -737,7 +743,7 @@ function createDevDashboard(options = {}) {
737
743
  }
738
744
  return (req, res, next) => {
739
745
  const url = req.url || "";
740
- if (!url.startsWith(path6)) {
746
+ if (!url.startsWith(path9)) {
741
747
  return next();
742
748
  }
743
749
  if (auth) {
@@ -756,7 +762,7 @@ function createDevDashboard(options = {}) {
756
762
  return;
757
763
  }
758
764
  }
759
- const subPath = url.slice(path6.length);
765
+ const subPath = url.slice(path9.length);
760
766
  if (subPath === "" || subPath === "/") {
761
767
  res.setHeader("Content-Type", "text/html");
762
768
  res.end(generateDashboardHTML(dashboardState));
@@ -870,9 +876,9 @@ init_esm_shims();
870
876
  // src/server/dev-server.ts
871
877
  init_esm_shims();
872
878
  import http from "http";
873
- import fs2 from "fs";
874
- import path4 from "path";
875
- import pc from "picocolors";
879
+ import fs5 from "fs";
880
+ import path7 from "path";
881
+ import pc2 from "picocolors";
876
882
  import chokidar from "chokidar";
877
883
  import { WebSocketServer, WebSocket } from "ws";
878
884
  import mime from "mime-types";
@@ -934,6 +940,26 @@ function findLayouts(routePath, allLayouts) {
934
940
  }
935
941
  return layouts;
936
942
  }
943
+ function findLoading(routePath, allLoading) {
944
+ const segments = routePath.split("/").filter(Boolean);
945
+ for (let i = segments.length; i >= 0; i--) {
946
+ const currentPath = i === 0 ? "/" : "/" + segments.slice(0, i).join("/");
947
+ if (allLoading.has(currentPath)) {
948
+ return allLoading.get(currentPath);
949
+ }
950
+ }
951
+ return void 0;
952
+ }
953
+ function findError(routePath, allErrors) {
954
+ const segments = routePath.split("/").filter(Boolean);
955
+ for (let i = segments.length; i >= 0; i--) {
956
+ const currentPath = i === 0 ? "/" : "/" + segments.slice(0, i).join("/");
957
+ if (allErrors.has(currentPath)) {
958
+ return allErrors.get(currentPath);
959
+ }
960
+ }
961
+ return void 0;
962
+ }
937
963
  async function scanRoutes(rootDir, options = {}) {
938
964
  const opts = { ...DEFAULT_OPTIONS, ...options };
939
965
  const appDir = path2.join(rootDir, opts.appDir);
@@ -945,12 +971,20 @@ async function scanRoutes(rootDir, options = {}) {
945
971
  ignore: ["**/node_modules/**", "**/_*/**"]
946
972
  });
947
973
  const layoutMap = /* @__PURE__ */ new Map();
974
+ const loadingMap = /* @__PURE__ */ new Map();
975
+ const errorMap = /* @__PURE__ */ new Map();
948
976
  for (const file of files) {
949
977
  const type = getRouteType(file);
978
+ const { urlPath } = filePathToUrlPath(file, "");
950
979
  if (type === "layout") {
951
- const { urlPath } = filePathToUrlPath(file, "");
952
980
  const layoutPath = urlPath === "/" ? "/" : urlPath.replace(/\/layout$/, "") || "/";
953
981
  layoutMap.set(layoutPath, path2.join(appDir, file));
982
+ } else if (type === "loading") {
983
+ const loadingPath = urlPath === "/" ? "/" : urlPath.replace(/\/loading$/, "") || "/";
984
+ loadingMap.set(loadingPath, path2.join(appDir, file));
985
+ } else if (type === "error") {
986
+ const errorPath = urlPath === "/" ? "/" : urlPath.replace(/\/error$/, "") || "/";
987
+ errorMap.set(errorPath, path2.join(appDir, file));
954
988
  }
955
989
  }
956
990
  const routes = [];
@@ -968,7 +1002,9 @@ async function scanRoutes(rootDir, options = {}) {
968
1002
  params,
969
1003
  isCatchAll,
970
1004
  isOptionalCatchAll,
971
- layouts: type === "page" ? findLayouts(urlPath, layoutMap) : []
1005
+ layouts: type === "page" ? findLayouts(urlPath, layoutMap) : [],
1006
+ loading: type === "page" ? findLoading(urlPath, loadingMap) : void 0,
1007
+ error: type === "page" ? findError(urlPath, errorMap) : void 0
972
1008
  };
973
1009
  routes.push(route);
974
1010
  }
@@ -1026,23 +1062,222 @@ import { Writable } from "stream";
1026
1062
  // src/build/transform.ts
1027
1063
  init_esm_shims();
1028
1064
  import * as esbuild from "esbuild";
1065
+ import fs2 from "fs";
1066
+ import path4 from "path";
1067
+ import { pathToFileURL } from "url";
1068
+
1069
+ // src/build/persistent-cache.ts
1070
+ init_esm_shims();
1029
1071
  import fs from "fs";
1030
1072
  import path3 from "path";
1031
- import { pathToFileURL } from "url";
1073
+ import crypto from "crypto";
1074
+ var PersistentCache = class {
1075
+ cacheDir;
1076
+ manifestPath;
1077
+ manifest;
1078
+ constructor(rootDir = process.cwd()) {
1079
+ this.cacheDir = path3.join(rootDir, ".float", "cache");
1080
+ this.manifestPath = path3.join(this.cacheDir, "manifest.json");
1081
+ fs.mkdirSync(this.cacheDir, { recursive: true });
1082
+ this.manifest = this.loadManifest();
1083
+ }
1084
+ loadManifest() {
1085
+ if (fs.existsSync(this.manifestPath)) {
1086
+ try {
1087
+ const data = fs.readFileSync(this.manifestPath, "utf-8");
1088
+ return JSON.parse(data);
1089
+ } catch {
1090
+ }
1091
+ }
1092
+ return {
1093
+ version: "1.0",
1094
+ entries: {}
1095
+ };
1096
+ }
1097
+ saveManifest() {
1098
+ fs.writeFileSync(
1099
+ this.manifestPath,
1100
+ JSON.stringify(this.manifest, null, 2)
1101
+ );
1102
+ }
1103
+ /**
1104
+ * Generate hash for content
1105
+ */
1106
+ hash(content) {
1107
+ return crypto.createHash("sha256").update(content).digest("hex").slice(0, 16);
1108
+ }
1109
+ /**
1110
+ * Get cache key path
1111
+ */
1112
+ getKeyPath(key) {
1113
+ const safeKey = key.replace(/[^a-z0-9_-]/gi, "_");
1114
+ return path3.join(this.cacheDir, `${safeKey}.cache`);
1115
+ }
1116
+ /**
1117
+ * Check if cache entry is valid
1118
+ */
1119
+ has(key, contentHash) {
1120
+ const entry = this.manifest.entries[key];
1121
+ if (!entry) return false;
1122
+ const cachePath = this.getKeyPath(key);
1123
+ if (!fs.existsSync(cachePath)) {
1124
+ delete this.manifest.entries[key];
1125
+ this.saveManifest();
1126
+ return false;
1127
+ }
1128
+ if (contentHash && entry.hash !== contentHash) {
1129
+ return false;
1130
+ }
1131
+ return true;
1132
+ }
1133
+ /**
1134
+ * Get cached value
1135
+ */
1136
+ get(key) {
1137
+ if (!this.has(key)) return null;
1138
+ try {
1139
+ const cachePath = this.getKeyPath(key);
1140
+ const data = fs.readFileSync(cachePath, "utf-8");
1141
+ const entry = JSON.parse(data);
1142
+ return entry.value;
1143
+ } catch {
1144
+ return null;
1145
+ }
1146
+ }
1147
+ /**
1148
+ * Set cache value
1149
+ */
1150
+ set(key, value, content) {
1151
+ const entry = {
1152
+ key,
1153
+ value,
1154
+ timestamp: Date.now(),
1155
+ hash: content ? this.hash(content) : this.hash(JSON.stringify(value))
1156
+ };
1157
+ const cachePath = this.getKeyPath(key);
1158
+ const data = JSON.stringify(entry);
1159
+ fs.writeFileSync(cachePath, data);
1160
+ this.manifest.entries[key] = {
1161
+ hash: entry.hash,
1162
+ timestamp: entry.timestamp,
1163
+ size: Buffer.byteLength(data)
1164
+ };
1165
+ this.saveManifest();
1166
+ }
1167
+ /**
1168
+ * Delete cache entry
1169
+ */
1170
+ delete(key) {
1171
+ const cachePath = this.getKeyPath(key);
1172
+ if (fs.existsSync(cachePath)) {
1173
+ fs.unlinkSync(cachePath);
1174
+ }
1175
+ if (this.manifest.entries[key]) {
1176
+ delete this.manifest.entries[key];
1177
+ this.saveManifest();
1178
+ return true;
1179
+ }
1180
+ return false;
1181
+ }
1182
+ /**
1183
+ * Clear all cache
1184
+ */
1185
+ clear() {
1186
+ if (fs.existsSync(this.cacheDir)) {
1187
+ fs.rmSync(this.cacheDir, { recursive: true });
1188
+ fs.mkdirSync(this.cacheDir, { recursive: true });
1189
+ }
1190
+ this.manifest = {
1191
+ version: "1.0",
1192
+ entries: {}
1193
+ };
1194
+ this.saveManifest();
1195
+ }
1196
+ /**
1197
+ * Get cache statistics
1198
+ */
1199
+ stats() {
1200
+ const entries = Object.values(this.manifest.entries);
1201
+ const size = entries.reduce((acc, e) => acc + e.size, 0);
1202
+ const timestamps = entries.map((e) => e.timestamp);
1203
+ return {
1204
+ entries: entries.length,
1205
+ size,
1206
+ oldestEntry: timestamps.length > 0 ? Math.min(...timestamps) : null,
1207
+ newestEntry: timestamps.length > 0 ? Math.max(...timestamps) : null
1208
+ };
1209
+ }
1210
+ /**
1211
+ * Clean old entries (older than maxAge milliseconds)
1212
+ */
1213
+ prune(maxAge = 7 * 24 * 60 * 60 * 1e3) {
1214
+ const now = Date.now();
1215
+ let cleaned = 0;
1216
+ for (const [key, entry] of Object.entries(this.manifest.entries)) {
1217
+ if (now - entry.timestamp > maxAge) {
1218
+ this.delete(key);
1219
+ cleaned++;
1220
+ }
1221
+ }
1222
+ return cleaned;
1223
+ }
1224
+ };
1225
+ var globalCache = null;
1226
+ function getCache(rootDir) {
1227
+ if (!globalCache) {
1228
+ globalCache = new PersistentCache(rootDir);
1229
+ }
1230
+ return globalCache;
1231
+ }
1232
+
1233
+ // src/build/transform.ts
1032
1234
  var moduleCache = /* @__PURE__ */ new Map();
1033
- async function transformFile(filePath) {
1034
- const absolutePath = path3.isAbsolute(filePath) ? filePath : path3.resolve(filePath);
1035
- if (!fs.existsSync(absolutePath)) {
1235
+ async function transformFile(filePath, useCache = true) {
1236
+ const absolutePath = path4.isAbsolute(filePath) ? filePath : path4.resolve(filePath);
1237
+ if (!fs2.existsSync(absolutePath)) {
1036
1238
  throw new Error(`File not found: ${absolutePath}`);
1037
1239
  }
1038
- const stats = fs.statSync(absolutePath);
1240
+ const stats = fs2.statSync(absolutePath);
1039
1241
  const mtime = stats.mtimeMs;
1040
1242
  const cached = moduleCache.get(absolutePath);
1041
1243
  if (cached && cached.mtime === mtime) {
1042
1244
  return cached.module;
1043
1245
  }
1044
- const source = fs.readFileSync(absolutePath, "utf-8");
1045
- const ext = path3.extname(absolutePath);
1246
+ if (useCache) {
1247
+ const cache = getCache();
1248
+ const source2 = fs2.readFileSync(absolutePath, "utf-8");
1249
+ const sourceHash = __require("crypto").createHash("sha256").update(source2).digest("hex").slice(0, 16);
1250
+ const cacheKey = `transform_${absolutePath}_${sourceHash}`;
1251
+ if (cache.has(cacheKey)) {
1252
+ const cachedCode = cache.get(cacheKey);
1253
+ if (cachedCode) {
1254
+ const tempDir2 = path4.join(process.cwd(), ".float", ".cache");
1255
+ fs2.mkdirSync(tempDir2, { recursive: true });
1256
+ const tempFile2 = path4.join(tempDir2, `${path4.basename(absolutePath, path4.extname(absolutePath))}_${Date.now()}.mjs`);
1257
+ fs2.writeFileSync(tempFile2, cachedCode);
1258
+ try {
1259
+ const module = await import(pathToFileURL(tempFile2).href);
1260
+ moduleCache.set(absolutePath, { module, mtime });
1261
+ setImmediate(() => {
1262
+ try {
1263
+ fs2.unlinkSync(tempFile2);
1264
+ } catch {
1265
+ }
1266
+ });
1267
+ return module;
1268
+ } catch (error) {
1269
+ setImmediate(() => {
1270
+ try {
1271
+ fs2.unlinkSync(tempFile2);
1272
+ } catch {
1273
+ }
1274
+ });
1275
+ }
1276
+ }
1277
+ }
1278
+ }
1279
+ const source = fs2.readFileSync(absolutePath, "utf-8");
1280
+ const ext = path4.extname(absolutePath);
1046
1281
  const loader = getLoader(ext);
1047
1282
  const result = await esbuild.transform(source, {
1048
1283
  loader,
@@ -1052,25 +1287,31 @@ async function transformFile(filePath) {
1052
1287
  sourcemap: "inline",
1053
1288
  sourcefile: absolutePath
1054
1289
  });
1055
- const tempDir = path3.join(process.cwd(), ".float", ".cache");
1056
- fs.mkdirSync(tempDir, { recursive: true });
1057
- const tempFile = path3.join(tempDir, `${path3.basename(absolutePath, ext)}_${Date.now()}.mjs`);
1290
+ const tempDir = path4.join(process.cwd(), ".float", ".cache");
1291
+ fs2.mkdirSync(tempDir, { recursive: true });
1292
+ const tempFile = path4.join(tempDir, `${path4.basename(absolutePath, ext)}_${Date.now()}.mjs`);
1058
1293
  let code = result.code;
1059
- code = rewriteImports(code, path3.dirname(absolutePath));
1060
- fs.writeFileSync(tempFile, code);
1294
+ code = rewriteImports(code, path4.dirname(absolutePath));
1295
+ fs2.writeFileSync(tempFile, code);
1061
1296
  try {
1062
1297
  const module = await import(pathToFileURL(tempFile).href);
1063
1298
  moduleCache.set(absolutePath, { module, mtime });
1299
+ if (useCache) {
1300
+ const cache = getCache();
1301
+ const sourceHash = __require("crypto").createHash("sha256").update(source).digest("hex").slice(0, 16);
1302
+ const cacheKey = `transform_${absolutePath}_${sourceHash}`;
1303
+ cache.set(cacheKey, code, source);
1304
+ }
1064
1305
  setImmediate(() => {
1065
1306
  try {
1066
- fs.unlinkSync(tempFile);
1307
+ fs2.unlinkSync(tempFile);
1067
1308
  } catch {
1068
1309
  }
1069
1310
  });
1070
1311
  return module;
1071
1312
  } catch (error) {
1072
1313
  try {
1073
- fs.unlinkSync(tempFile);
1314
+ fs2.unlinkSync(tempFile);
1074
1315
  } catch {
1075
1316
  }
1076
1317
  throw error;
@@ -1099,18 +1340,18 @@ function getLoader(ext) {
1099
1340
  function rewriteImports(code, baseDir) {
1100
1341
  const importRegex = /from\s+['"](\.[^'"]+)['"]/g;
1101
1342
  return code.replace(importRegex, (match, importPath) => {
1102
- let resolvedPath = path3.resolve(baseDir, importPath);
1343
+ let resolvedPath = path4.resolve(baseDir, importPath);
1103
1344
  const extensions = [".tsx", ".ts", ".jsx", ".js", ".mjs", ""];
1104
1345
  let found = false;
1105
1346
  for (const ext of extensions) {
1106
1347
  const tryPath = resolvedPath + ext;
1107
- if (fs.existsSync(tryPath)) {
1348
+ if (fs2.existsSync(tryPath)) {
1108
1349
  resolvedPath = tryPath;
1109
1350
  found = true;
1110
1351
  break;
1111
1352
  }
1112
- const indexPath = path3.join(resolvedPath, `index${ext}`);
1113
- if (fs.existsSync(indexPath)) {
1353
+ const indexPath = path4.join(resolvedPath, `index${ext}`);
1354
+ if (fs2.existsSync(indexPath)) {
1114
1355
  resolvedPath = indexPath;
1115
1356
  found = true;
1116
1357
  break;
@@ -2494,11 +2735,214 @@ function generateExamplesPage() {
2494
2735
  ` + footer;
2495
2736
  }
2496
2737
 
2738
+ // src/build/css-processor.ts
2739
+ init_esm_shims();
2740
+ import fs4 from "fs";
2741
+ import path6 from "path";
2742
+
2743
+ // src/build/tailwind-setup.ts
2744
+ init_esm_shims();
2745
+ import fs3 from "fs";
2746
+ import path5 from "path";
2747
+ import pc from "picocolors";
2748
+ function checkTailwindSetup(rootDir) {
2749
+ const possibleConfigs = [
2750
+ "tailwind.config.js",
2751
+ "tailwind.config.ts",
2752
+ "tailwind.config.mjs",
2753
+ "tailwind.config.cjs"
2754
+ ];
2755
+ let configPath = null;
2756
+ for (const config of possibleConfigs) {
2757
+ const fullPath = path5.join(rootDir, config);
2758
+ if (fs3.existsSync(fullPath)) {
2759
+ configPath = fullPath;
2760
+ break;
2761
+ }
2762
+ }
2763
+ const globalsPath = path5.join(rootDir, "app", "globals.css");
2764
+ const hasGlobals = fs3.existsSync(globalsPath);
2765
+ return {
2766
+ hasTailwind: !!configPath,
2767
+ configPath,
2768
+ globalsPath: hasGlobals ? globalsPath : null,
2769
+ needsSetup: !configPath || !hasGlobals
2770
+ };
2771
+ }
2772
+ async function setupTailwind(rootDir, options = {}) {
2773
+ const { force = false, silent = false } = options;
2774
+ const config = checkTailwindSetup(rootDir);
2775
+ if (!force && !config.needsSetup) {
2776
+ return;
2777
+ }
2778
+ if (!silent) {
2779
+ console.log(pc.cyan("\n\u{1F3A8} Setting up Tailwind CSS..."));
2780
+ }
2781
+ if (!config.configPath || force) {
2782
+ const tailwindConfig = `/** @type {import('tailwindcss').Config} */
2783
+ export default {
2784
+ content: [
2785
+ './app/**/*.{js,ts,jsx,tsx}',
2786
+ './components/**/*.{js,ts,jsx,tsx}',
2787
+ ],
2788
+ theme: {
2789
+ extend: {},
2790
+ },
2791
+ plugins: [],
2792
+ }
2793
+ `;
2794
+ const configPath = path5.join(rootDir, "tailwind.config.js");
2795
+ fs3.writeFileSync(configPath, tailwindConfig);
2796
+ if (!silent) {
2797
+ console.log(pc.green(" \u2713 Created tailwind.config.js"));
2798
+ }
2799
+ }
2800
+ const postcssPath = path5.join(rootDir, "postcss.config.js");
2801
+ if (!fs3.existsSync(postcssPath) || force) {
2802
+ const postcssConfig = `export default {
2803
+ plugins: {
2804
+ tailwindcss: {},
2805
+ autoprefixer: {},
2806
+ },
2807
+ }
2808
+ `;
2809
+ fs3.writeFileSync(postcssPath, postcssConfig);
2810
+ if (!silent) {
2811
+ console.log(pc.green(" \u2713 Created postcss.config.js"));
2812
+ }
2813
+ }
2814
+ const appDir = path5.join(rootDir, "app");
2815
+ if (!fs3.existsSync(appDir)) {
2816
+ fs3.mkdirSync(appDir, { recursive: true });
2817
+ }
2818
+ const globalsPath = path5.join(appDir, "globals.css");
2819
+ if (!fs3.existsSync(globalsPath) || force) {
2820
+ const globalsCss = `@tailwind base;
2821
+ @tailwind components;
2822
+ @tailwind utilities;
2823
+ `;
2824
+ fs3.writeFileSync(globalsPath, globalsCss);
2825
+ if (!silent) {
2826
+ console.log(pc.green(" \u2713 Created app/globals.css"));
2827
+ }
2828
+ }
2829
+ const layoutPath = path5.join(appDir, "layout.tsx");
2830
+ if (!fs3.existsSync(layoutPath)) {
2831
+ const layoutContent = `import './globals.css'
2832
+
2833
+ export default function RootLayout({
2834
+ children,
2835
+ }: {
2836
+ children: React.ReactNode
2837
+ }) {
2838
+ return (
2839
+ <html lang="en">
2840
+ <body>{children}</body>
2841
+ </html>
2842
+ )
2843
+ }
2844
+ `;
2845
+ fs3.writeFileSync(layoutPath, layoutContent);
2846
+ if (!silent) {
2847
+ console.log(pc.green(" \u2713 Created app/layout.tsx"));
2848
+ }
2849
+ }
2850
+ if (!silent) {
2851
+ console.log(pc.green("\n\u2728 Tailwind CSS ready!\n"));
2852
+ }
2853
+ }
2854
+ function checkTailwindDeps(rootDir) {
2855
+ const packageJsonPath = path5.join(rootDir, "package.json");
2856
+ if (!fs3.existsSync(packageJsonPath)) {
2857
+ return {
2858
+ hasPackageJson: false,
2859
+ hasTailwind: false,
2860
+ hasPostCSS: false,
2861
+ hasAutoprefixer: false
2862
+ };
2863
+ }
2864
+ const packageJson = JSON.parse(fs3.readFileSync(packageJsonPath, "utf-8"));
2865
+ const allDeps = {
2866
+ ...packageJson.dependencies,
2867
+ ...packageJson.devDependencies
2868
+ };
2869
+ return {
2870
+ hasPackageJson: true,
2871
+ hasTailwind: !!allDeps["tailwindcss"],
2872
+ hasPostCSS: !!allDeps["postcss"],
2873
+ hasAutoprefixer: !!allDeps["autoprefixer"]
2874
+ };
2875
+ }
2876
+ function getTailwindInstallCommand(rootDir) {
2877
+ const deps = checkTailwindDeps(rootDir);
2878
+ if (!deps.hasPackageJson) {
2879
+ return null;
2880
+ }
2881
+ const missing = [];
2882
+ if (!deps.hasTailwind) missing.push("tailwindcss");
2883
+ if (!deps.hasPostCSS) missing.push("postcss");
2884
+ if (!deps.hasAutoprefixer) missing.push("autoprefixer");
2885
+ if (missing.length === 0) {
2886
+ return null;
2887
+ }
2888
+ const hasYarnLock = fs3.existsSync(path5.join(rootDir, "yarn.lock"));
2889
+ const hasPnpmLock = fs3.existsSync(path5.join(rootDir, "pnpm-lock.yaml"));
2890
+ const hasBunLock = fs3.existsSync(path5.join(rootDir, "bun.lockb"));
2891
+ let pm = "npm install -D";
2892
+ if (hasBunLock) pm = "bun add -d";
2893
+ else if (hasPnpmLock) pm = "pnpm add -D";
2894
+ else if (hasYarnLock) pm = "yarn add -D";
2895
+ return `${pm} ${missing.join(" ")}`;
2896
+ }
2897
+
2898
+ // src/build/css-processor.ts
2899
+ async function processCSS(filePath, rootDir = process.cwd()) {
2900
+ const content = fs4.readFileSync(filePath, "utf-8");
2901
+ const tailwindConfig = checkTailwindSetup(rootDir);
2902
+ if (!tailwindConfig.hasTailwind) {
2903
+ return { code: content };
2904
+ }
2905
+ try {
2906
+ const postcss = await import("postcss").then((m) => m.default);
2907
+ const tailwindcss = await import("tailwindcss").then((m) => m.default);
2908
+ const autoprefixer = await import("autoprefixer").then((m) => m.default);
2909
+ const configPath = tailwindConfig.configPath || path6.join(rootDir, "tailwind.config.js");
2910
+ let tailwindConfigModule = {};
2911
+ if (fs4.existsSync(configPath)) {
2912
+ const configUrl = new URL(`file://${configPath}`);
2913
+ tailwindConfigModule = await import(configUrl.href).then((m) => m.default || m);
2914
+ }
2915
+ const result = await postcss([
2916
+ tailwindcss(tailwindConfigModule),
2917
+ autoprefixer()
2918
+ ]).process(content, {
2919
+ from: filePath,
2920
+ to: filePath,
2921
+ map: { inline: false }
2922
+ });
2923
+ return {
2924
+ code: result.css,
2925
+ map: result.map?.toString()
2926
+ };
2927
+ } catch (error) {
2928
+ console.warn("CSS processing failed, serving raw CSS:", error);
2929
+ return { code: content };
2930
+ }
2931
+ }
2932
+ function needsCSSProcessing(filePath, rootDir) {
2933
+ const config = checkTailwindSetup(rootDir);
2934
+ if (!config.hasTailwind) {
2935
+ return false;
2936
+ }
2937
+ const content = fs4.readFileSync(filePath, "utf-8");
2938
+ return content.includes("@tailwind") || content.includes("@apply");
2939
+ }
2940
+
2497
2941
  // src/server/dev-server.ts
2498
2942
  async function createDevServer(options) {
2499
2943
  const { port, host, open } = options;
2500
2944
  const rootDir = process.cwd();
2501
- const publicDir = path4.join(rootDir, "public");
2945
+ const publicDir = path7.join(rootDir, "public");
2502
2946
  let routes = [];
2503
2947
  let server = null;
2504
2948
  let wss = null;
@@ -2506,9 +2950,20 @@ async function createDevServer(options) {
2506
2950
  async function refreshRoutes() {
2507
2951
  try {
2508
2952
  routes = await scanRoutes(rootDir);
2509
- console.log(pc.dim(` \u{1F4C1} Found ${routes.length} routes`));
2953
+ console.log(pc2.dim(` \u{1F4C1} Found ${routes.length} routes`));
2954
+ const tailwindConfig = checkTailwindSetup(rootDir);
2955
+ if (tailwindConfig.needsSetup) {
2956
+ console.log(pc2.yellow(" \u26A0\uFE0F Tailwind not configured"));
2957
+ const installCmd = getTailwindInstallCommand(rootDir);
2958
+ if (installCmd) {
2959
+ console.log(pc2.dim(` \u{1F4A1} Run: ${installCmd}`));
2960
+ console.log(pc2.dim(` \u{1F4A1} Then: npx float dev`));
2961
+ } else {
2962
+ await setupTailwind(rootDir);
2963
+ }
2964
+ }
2510
2965
  } catch (error) {
2511
- console.error(pc.red("Failed to scan routes:"), error);
2966
+ console.error(pc2.red("Failed to scan routes:"), error);
2512
2967
  }
2513
2968
  }
2514
2969
  function notifyClients(type, data) {
@@ -2569,16 +3024,46 @@ ${FLOAT_ERROR_OVERLAY}
2569
3024
  async function handleRequest(req, res) {
2570
3025
  const url = new URL(req.url || "/", `http://${host}:${port}`);
2571
3026
  const pathname = url.pathname;
2572
- console.log(pc.dim(` ${req.method} ${pathname}`));
3027
+ console.log(pc2.dim(` ${req.method} ${pathname}`));
2573
3028
  try {
2574
- const publicFilePath = path4.join(publicDir, pathname);
2575
- if (fs2.existsSync(publicFilePath) && fs2.statSync(publicFilePath).isFile()) {
2576
- const content = fs2.readFileSync(publicFilePath);
3029
+ const publicFilePath = path7.join(publicDir, pathname);
3030
+ if (fs5.existsSync(publicFilePath) && fs5.statSync(publicFilePath).isFile()) {
3031
+ const content = fs5.readFileSync(publicFilePath);
2577
3032
  const contentType = mime.lookup(publicFilePath) || "application/octet-stream";
2578
3033
  res.writeHead(200, { "Content-Type": contentType });
2579
3034
  res.end(content);
2580
3035
  return;
2581
3036
  }
3037
+ if (pathname.endsWith(".css")) {
3038
+ const cssPath = path7.join(rootDir, "app", pathname.replace(/^\//, ""));
3039
+ if (fs5.existsSync(cssPath)) {
3040
+ try {
3041
+ const needsProcessing = needsCSSProcessing(cssPath, rootDir);
3042
+ if (needsProcessing) {
3043
+ const result = await processCSS(cssPath, rootDir);
3044
+ res.writeHead(200, {
3045
+ "Content-Type": "text/css",
3046
+ "Cache-Control": "no-cache"
3047
+ });
3048
+ res.end(result.code);
3049
+ } else {
3050
+ const content = fs5.readFileSync(cssPath, "utf-8");
3051
+ res.writeHead(200, {
3052
+ "Content-Type": "text/css",
3053
+ "Cache-Control": "no-cache"
3054
+ });
3055
+ res.end(content);
3056
+ }
3057
+ return;
3058
+ } catch (error) {
3059
+ console.error(pc2.red("CSS processing error:"), error);
3060
+ const content = fs5.readFileSync(cssPath, "utf-8");
3061
+ res.writeHead(200, { "Content-Type": "text/css" });
3062
+ res.end(content);
3063
+ return;
3064
+ }
3065
+ }
3066
+ }
2582
3067
  if (pathname.startsWith("/_float/")) {
2583
3068
  res.writeHead(200, { "Content-Type": "application/javascript" });
2584
3069
  res.end("// Float.js internal asset");
@@ -2643,7 +3128,7 @@ ${FLOAT_ERROR_OVERLAY}
2643
3128
  });
2644
3129
  res.end(html);
2645
3130
  } catch (error) {
2646
- console.error(pc.red("Request error:"), error);
3131
+ console.error(pc2.red("Request error:"), error);
2647
3132
  res.writeHead(500, { "Content-Type": "text/html" });
2648
3133
  res.end(createErrorPage(error));
2649
3134
  }
@@ -2672,7 +3157,7 @@ ${FLOAT_ERROR_OVERLAY}
2672
3157
  const responseBody = await response.text();
2673
3158
  res.end(responseBody);
2674
3159
  } catch (error) {
2675
- console.error(pc.red("API route error:"), error);
3160
+ console.error(pc2.red("API route error:"), error);
2676
3161
  res.writeHead(500, { "Content-Type": "application/json" });
2677
3162
  res.end(JSON.stringify({ error: "Internal server error" }));
2678
3163
  }
@@ -2687,9 +3172,9 @@ ${FLOAT_ERROR_OVERLAY}
2687
3172
  });
2688
3173
  const watcher = chokidar.watch(
2689
3174
  [
2690
- path4.join(rootDir, "app/**/*.{ts,tsx,js,jsx}"),
2691
- path4.join(rootDir, "components/**/*.{ts,tsx,js,jsx}"),
2692
- path4.join(rootDir, "lib/**/*.{ts,tsx,js,jsx}")
3175
+ path7.join(rootDir, "app/**/*.{ts,tsx,js,jsx}"),
3176
+ path7.join(rootDir, "components/**/*.{ts,tsx,js,jsx}"),
3177
+ path7.join(rootDir, "lib/**/*.{ts,tsx,js,jsx}")
2693
3178
  ],
2694
3179
  {
2695
3180
  ignored: /node_modules/,
@@ -2697,8 +3182,8 @@ ${FLOAT_ERROR_OVERLAY}
2697
3182
  }
2698
3183
  );
2699
3184
  watcher.on("change", async (filePath) => {
2700
- console.log(pc.yellow(`
2701
- \u26A1 File changed: ${path4.relative(rootDir, filePath)}`));
3185
+ console.log(pc2.yellow(`
3186
+ \u26A1 File changed: ${path7.relative(rootDir, filePath)}`));
2702
3187
  if (filePath.includes("/app/")) {
2703
3188
  await refreshRoutes();
2704
3189
  }
@@ -2706,31 +3191,31 @@ ${FLOAT_ERROR_OVERLAY}
2706
3191
  });
2707
3192
  watcher.on("add", async (filePath) => {
2708
3193
  if (filePath.includes("/app/")) {
2709
- console.log(pc.green(`
2710
- \u2795 File added: ${path4.relative(rootDir, filePath)}`));
3194
+ console.log(pc2.green(`
3195
+ \u2795 File added: ${path7.relative(rootDir, filePath)}`));
2711
3196
  await refreshRoutes();
2712
3197
  notifyClients("reload");
2713
3198
  }
2714
3199
  });
2715
3200
  watcher.on("unlink", async (filePath) => {
2716
3201
  if (filePath.includes("/app/")) {
2717
- console.log(pc.red(`
2718
- \u2796 File removed: ${path4.relative(rootDir, filePath)}`));
3202
+ console.log(pc2.red(`
3203
+ \u2796 File removed: ${path7.relative(rootDir, filePath)}`));
2719
3204
  await refreshRoutes();
2720
3205
  notifyClients("reload");
2721
3206
  }
2722
3207
  });
2723
3208
  return new Promise((resolve, reject) => {
2724
3209
  server.listen(port, host, () => {
2725
- console.log(pc.green(` \u2705 Server running at ${pc.cyan(`http://${host}:${port}`)}`));
2726
- console.log(pc.dim(` \u26A1 HMR enabled on ws://${host}:${port + 1}
3210
+ console.log(pc2.green(` \u2705 Server running at ${pc2.cyan(`http://${host}:${port}`)}`));
3211
+ console.log(pc2.dim(` \u26A1 HMR enabled on ws://${host}:${port + 1}
2727
3212
  `));
2728
- console.log(pc.bold(" Routes:"));
3213
+ console.log(pc2.bold(" Routes:"));
2729
3214
  routes.forEach((route) => {
2730
3215
  if (route.type === "page") {
2731
- console.log(pc.dim(` ${pc.green("\u25CF")} ${route.path}`));
3216
+ console.log(pc2.dim(` ${pc2.green("\u25CF")} ${route.path}`));
2732
3217
  } else if (route.type === "api") {
2733
- console.log(pc.dim(` ${pc.blue("\u25C6")} ${route.path} (API)`));
3218
+ console.log(pc2.dim(` ${pc2.blue("\u25C6")} ${route.path} (API)`));
2734
3219
  }
2735
3220
  });
2736
3221
  console.log("");
@@ -2850,45 +3335,45 @@ function escapeHtml2(text) {
2850
3335
  // src/server/prod-server.ts
2851
3336
  init_esm_shims();
2852
3337
  import http2 from "http";
2853
- import fs3 from "fs";
2854
- import path5 from "path";
2855
- import pc2 from "picocolors";
3338
+ import fs6 from "fs";
3339
+ import path8 from "path";
3340
+ import pc3 from "picocolors";
2856
3341
  import mime2 from "mime-types";
2857
3342
  var cachedRoutes = [];
2858
3343
  var pageCache = /* @__PURE__ */ new Map();
2859
3344
  async function startProductionServer(options) {
2860
3345
  const { port, host } = options;
2861
3346
  const rootDir = process.cwd();
2862
- const distDir = path5.join(rootDir, ".float");
2863
- const publicDir = path5.join(rootDir, "public");
2864
- const manifestPath = path5.join(distDir, "routes-manifest.json");
2865
- if (fs3.existsSync(manifestPath)) {
2866
- const manifest = JSON.parse(fs3.readFileSync(manifestPath, "utf-8"));
3347
+ const distDir = path8.join(rootDir, ".float");
3348
+ const publicDir = path8.join(rootDir, "public");
3349
+ const manifestPath = path8.join(distDir, "routes-manifest.json");
3350
+ if (fs6.existsSync(manifestPath)) {
3351
+ const manifest = JSON.parse(fs6.readFileSync(manifestPath, "utf-8"));
2867
3352
  cachedRoutes = manifest.routes;
2868
- console.log(pc2.dim(` \u{1F4E6} Loaded ${cachedRoutes.length} routes from manifest`));
3353
+ console.log(pc3.dim(` \u{1F4E6} Loaded ${cachedRoutes.length} routes from manifest`));
2869
3354
  } else {
2870
- console.error(pc2.red(" \u274C No build manifest found. Run `float build` first."));
3355
+ console.error(pc3.red(" \u274C No build manifest found. Run `float build` first."));
2871
3356
  process.exit(1);
2872
3357
  }
2873
- const pagesDir = path5.join(distDir, "pages");
2874
- if (fs3.existsSync(pagesDir)) {
2875
- const prerenderedFiles = fs3.readdirSync(pagesDir, { recursive: true });
3358
+ const pagesDir = path8.join(distDir, "pages");
3359
+ if (fs6.existsSync(pagesDir)) {
3360
+ const prerenderedFiles = fs6.readdirSync(pagesDir, { recursive: true });
2876
3361
  for (const file of prerenderedFiles) {
2877
3362
  if (file.endsWith(".html")) {
2878
3363
  const routePath = "/" + file.replace(/\.html$/, "").replace(/index$/, "");
2879
- const content = fs3.readFileSync(path5.join(pagesDir, file), "utf-8");
3364
+ const content = fs6.readFileSync(path8.join(pagesDir, file), "utf-8");
2880
3365
  pageCache.set(routePath, content);
2881
3366
  }
2882
3367
  }
2883
- console.log(pc2.dim(` \u{1F4C4} Loaded ${pageCache.size} pre-rendered pages`));
3368
+ console.log(pc3.dim(` \u{1F4C4} Loaded ${pageCache.size} pre-rendered pages`));
2884
3369
  }
2885
3370
  const server = http2.createServer(async (req, res) => {
2886
3371
  const url = new URL(req.url || "/", `http://${host}:${port}`);
2887
3372
  const pathname = url.pathname;
2888
3373
  try {
2889
- const staticPath = path5.join(distDir, "static", pathname);
2890
- if (fs3.existsSync(staticPath) && fs3.statSync(staticPath).isFile()) {
2891
- const content = fs3.readFileSync(staticPath);
3374
+ const staticPath = path8.join(distDir, "static", pathname);
3375
+ if (fs6.existsSync(staticPath) && fs6.statSync(staticPath).isFile()) {
3376
+ const content = fs6.readFileSync(staticPath);
2892
3377
  const contentType = mime2.lookup(staticPath) || "application/octet-stream";
2893
3378
  res.writeHead(200, {
2894
3379
  "Content-Type": contentType,
@@ -2897,9 +3382,9 @@ async function startProductionServer(options) {
2897
3382
  res.end(content);
2898
3383
  return;
2899
3384
  }
2900
- const publicFilePath = path5.join(publicDir, pathname);
2901
- if (fs3.existsSync(publicFilePath) && fs3.statSync(publicFilePath).isFile()) {
2902
- const content = fs3.readFileSync(publicFilePath);
3385
+ const publicFilePath = path8.join(publicDir, pathname);
3386
+ if (fs6.existsSync(publicFilePath) && fs6.statSync(publicFilePath).isFile()) {
3387
+ const content = fs6.readFileSync(publicFilePath);
2903
3388
  const contentType = mime2.lookup(publicFilePath) || "application/octet-stream";
2904
3389
  res.writeHead(200, { "Content-Type": contentType });
2905
3390
  res.end(content);
@@ -2932,13 +3417,13 @@ async function startProductionServer(options) {
2932
3417
  });
2933
3418
  res.end(html);
2934
3419
  } catch (error) {
2935
- console.error(pc2.red("Request error:"), error);
3420
+ console.error(pc3.red("Request error:"), error);
2936
3421
  res.writeHead(500, { "Content-Type": "text/html" });
2937
3422
  res.end("<h1>500 - Internal Server Error</h1>");
2938
3423
  }
2939
3424
  });
2940
3425
  server.listen(port, host, () => {
2941
- console.log(pc2.green(` \u2705 Production server running at ${pc2.cyan(`http://${host}:${port}`)}
3426
+ console.log(pc3.green(` \u2705 Production server running at ${pc3.cyan(`http://${host}:${port}`)}
2942
3427
  `));
2943
3428
  });
2944
3429
  }