@lolyjs/core 0.2.0-alpha.20 → 0.2.0-alpha.22

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/README.md CHANGED
@@ -392,6 +392,64 @@ export default function Dashboard() {
392
392
  }
393
393
  ```
394
394
 
395
+ ### 📄 Static Files & Assets
396
+
397
+ Loly serves static files from the `public/` directory at the root of your application. This is perfect for SEO files like `sitemap.xml`, `robots.txt`, favicons, and other static assets.
398
+
399
+ **How it works:**
400
+ - Files in `public/` are served at the root URL (e.g., `public/sitemap.xml` → `/sitemap.xml`)
401
+ - Static files have **priority over dynamic routes** - if a file exists in `public/`, it will be served instead of matching a route
402
+ - Perfect for SEO: Google automatically finds `sitemap.xml` and `robots.txt` at the root
403
+ - Works in both development and production environments
404
+ - Subdirectories are supported: `public/assets/logo.png` → `/assets/logo.png`
405
+
406
+ **Directory Structure:**
407
+ ```
408
+ public/
409
+ ├── sitemap.xml # Available at /sitemap.xml
410
+ ├── robots.txt # Available at /robots.txt
411
+ ├── favicon.ico # Available at /favicon.ico
412
+ └── assets/
413
+ ├── logo.png # Available at /assets/logo.png
414
+ └── images/ # Available at /assets/images/*
415
+ └── hero.jpg
416
+ ```
417
+
418
+ **SEO Example:**
419
+
420
+ Create `public/sitemap.xml`:
421
+ ```xml
422
+ <?xml version="1.0" encoding="UTF-8"?>
423
+ <urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
424
+ <url>
425
+ <loc>https://example.com/</loc>
426
+ <lastmod>2024-01-01</lastmod>
427
+ <changefreq>daily</changefreq>
428
+ <priority>1.0</priority>
429
+ </url>
430
+ </urlset>
431
+ ```
432
+
433
+ Create `public/robots.txt`:
434
+ ```
435
+ User-agent: *
436
+ Allow: /
437
+
438
+ Sitemap: https://example.com/sitemap.xml
439
+ ```
440
+
441
+ Both files will be automatically available at `/sitemap.xml` and `/robots.txt` respectively, and search engines will find them at the standard locations.
442
+
443
+ **Configuration:**
444
+ The static directory can be customized in `loly.config.ts`:
445
+ ```tsx
446
+ export default {
447
+ directories: {
448
+ static: "public", // Default: "public"
449
+ },
450
+ } satisfies FrameworkConfig;
451
+ ```
452
+
395
453
  ### 🔌 API Routes
396
454
 
397
455
  Create RESTful APIs with flexible middleware support:
@@ -667,7 +725,7 @@ your-app/
667
725
  │ └── events.ts # WebSocket namespace /chat
668
726
  ├── components/ # React components
669
727
  ├── lib/ # Utilities
670
- ├── public/ # Static files
728
+ ├── public/ # Static files (served at root: /sitemap.xml, /robots.txt, etc.)
671
729
  ├── loly.config.ts # Framework configuration
672
730
  ├── init.server.ts # Server initialization (DB, services, etc.)
673
731
  └── package.json
package/dist/cli.cjs CHANGED
@@ -13743,6 +13743,105 @@ function validateRealtimeConfig(config) {
13743
13743
  // modules/build/bundler/server.ts
13744
13744
  init_globals();
13745
13745
  var SERVER_FILES = [INIT_FILE_NAME, CONFIG_FILE_NAME];
13746
+ function createPathAliasPlugin(projectRoot, outDir) {
13747
+ const aliases = loadAliasesFromTsconfig(projectRoot);
13748
+ const tsconfigPath = import_path25.default.join(projectRoot, "tsconfig.json");
13749
+ let baseUrl = ".";
13750
+ if (import_fs16.default.existsSync(tsconfigPath)) {
13751
+ try {
13752
+ const tsconfig = JSON.parse(import_fs16.default.readFileSync(tsconfigPath, "utf-8"));
13753
+ baseUrl = tsconfig.compilerOptions?.baseUrl ?? ".";
13754
+ } catch {
13755
+ }
13756
+ }
13757
+ function resolveAliasToRelative(importPath, sourceFile) {
13758
+ if (importPath.startsWith(".") || importPath.startsWith("/") || import_path25.default.isAbsolute(importPath) || importPath.includes("node_modules")) {
13759
+ return null;
13760
+ }
13761
+ for (const [aliasKey, aliasPath] of Object.entries(aliases)) {
13762
+ if (importPath.startsWith(aliasKey + "/") || importPath === aliasKey) {
13763
+ const restPath = importPath.startsWith(aliasKey + "/") ? importPath.slice(aliasKey.length + 1) : "";
13764
+ const resolvedPath = restPath ? import_path25.default.join(aliasPath, restPath) : aliasPath;
13765
+ let actualPath = null;
13766
+ const extensions = [".ts", ".tsx", ".js", ".jsx", ".json"];
13767
+ if (import_fs16.default.existsSync(resolvedPath) && import_fs16.default.statSync(resolvedPath).isDirectory()) {
13768
+ for (const ext of extensions) {
13769
+ const indexPath = import_path25.default.join(resolvedPath, `index${ext}`);
13770
+ if (import_fs16.default.existsSync(indexPath)) {
13771
+ actualPath = indexPath;
13772
+ break;
13773
+ }
13774
+ }
13775
+ } else {
13776
+ for (const ext of extensions) {
13777
+ const filePath = resolvedPath + ext;
13778
+ if (import_fs16.default.existsSync(filePath)) {
13779
+ actualPath = filePath;
13780
+ break;
13781
+ }
13782
+ }
13783
+ if (!actualPath && import_fs16.default.existsSync(resolvedPath)) {
13784
+ actualPath = resolvedPath;
13785
+ }
13786
+ }
13787
+ if (actualPath) {
13788
+ const relativePath = import_path25.default.relative(outDir, actualPath);
13789
+ const normalizedPath = relativePath.replace(/\\/g, "/");
13790
+ const finalPath = normalizedPath.startsWith(".") ? normalizedPath : `./${normalizedPath}`;
13791
+ const ext = import_path25.default.extname(finalPath);
13792
+ const pathWithoutExt = ext === ".json" ? finalPath : finalPath.slice(0, -ext.length);
13793
+ return pathWithoutExt;
13794
+ }
13795
+ }
13796
+ }
13797
+ return null;
13798
+ }
13799
+ return {
13800
+ name: "path-alias-resolver",
13801
+ setup(build) {
13802
+ build.onLoad({ filter: /\.(ts|tsx|js|jsx)$/ }, (args) => {
13803
+ const fileName = import_path25.default.basename(args.path);
13804
+ const isServerFile = SERVER_FILES.some((f) => fileName === `${f}.ts` || fileName === `${f}.tsx` || fileName === `${f}.js` || fileName === `${f}.jsx`);
13805
+ const isInProjectRoot = import_path25.default.dirname(args.path) === projectRoot;
13806
+ if (!isServerFile || !isInProjectRoot) {
13807
+ return null;
13808
+ }
13809
+ const contents = import_fs16.default.readFileSync(args.path, "utf-8");
13810
+ let transformed = contents;
13811
+ const aliasPatterns = Object.keys(aliases).sort((a, b) => b.length - a.length);
13812
+ for (const aliasKey of aliasPatterns) {
13813
+ const escapedAlias = aliasKey.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
13814
+ const aliasInQuotesPattern = new RegExp(
13815
+ `(['"\`])${escapedAlias}(/[^'"\`\\s]*)?(['"\`])`,
13816
+ "g"
13817
+ );
13818
+ transformed = transformed.replace(aliasInQuotesPattern, (match, quote1, rest, quote2) => {
13819
+ const fullPath = aliasKey + (rest || "");
13820
+ const resolved = resolveAliasToRelative(fullPath, args.path);
13821
+ if (resolved) {
13822
+ return `${quote1}${resolved}${quote2}`;
13823
+ }
13824
+ return match;
13825
+ });
13826
+ }
13827
+ return {
13828
+ contents: transformed,
13829
+ loader: import_path25.default.extname(args.path).slice(1)
13830
+ };
13831
+ });
13832
+ build.onResolve({ filter: /.*/ }, (args) => {
13833
+ const resolved = resolveAliasToRelative(args.path, args.importer || "");
13834
+ if (resolved) {
13835
+ return {
13836
+ path: resolved,
13837
+ namespace: "file"
13838
+ };
13839
+ }
13840
+ return null;
13841
+ });
13842
+ }
13843
+ };
13844
+ }
13746
13845
  function collectAppSources(appDir) {
13747
13846
  const entries = [];
13748
13847
  function walk(dir) {
@@ -13786,6 +13885,7 @@ async function buildServerApp(projectRoot, appDir) {
13786
13885
  tsconfig: import_path25.default.join(projectRoot, "tsconfig.json"),
13787
13886
  packages: "external"
13788
13887
  });
13888
+ const pathAliasPlugin = createPathAliasPlugin(projectRoot, outDir);
13789
13889
  for (const fileName of SERVER_FILES) {
13790
13890
  const initTS = import_path25.default.join(projectRoot, `${fileName}.ts`);
13791
13891
  const initJS = import_path25.default.join(outDir, `${fileName}.js`);
@@ -13800,7 +13900,8 @@ async function buildServerApp(projectRoot, appDir) {
13800
13900
  sourcemap: true,
13801
13901
  bundle: false,
13802
13902
  logLevel: "info",
13803
- tsconfig: import_path25.default.join(projectRoot, "tsconfig.json")
13903
+ tsconfig: import_path25.default.join(projectRoot, "tsconfig.json"),
13904
+ plugins: [pathAliasPlugin]
13804
13905
  });
13805
13906
  }
13806
13907
  }
@@ -14037,6 +14138,9 @@ function getAppDir(projectRoot, config) {
14037
14138
  function getBuildDir(projectRoot, config) {
14038
14139
  return import_path26.default.join(projectRoot, config.directories.build);
14039
14140
  }
14141
+ function getStaticDir(projectRoot, config) {
14142
+ return import_path26.default.resolve(projectRoot, config.directories.static);
14143
+ }
14040
14144
 
14041
14145
  // modules/build/index.ts
14042
14146
  async function buildApp(options = {}) {
@@ -14087,12 +14191,13 @@ async function buildApp(options = {}) {
14087
14191
  }
14088
14192
 
14089
14193
  // src/server.ts
14090
- var import_fs20 = __toESM(require("fs"));
14194
+ var import_fs21 = __toESM(require("fs"));
14091
14195
  var import_path31 = __toESM(require("path"));
14092
14196
 
14093
14197
  // modules/server/setup.ts
14094
14198
  var import_express = __toESM(require("express"));
14095
14199
  var import_path29 = __toESM(require("path"));
14200
+ var import_fs20 = __toESM(require("fs"));
14096
14201
 
14097
14202
  // ../../node_modules/.pnpm/chokidar@4.0.3/node_modules/chokidar/esm/index.js
14098
14203
  var import_fs19 = require("fs");
@@ -15934,8 +16039,22 @@ function clearAppRequireCache(appDir) {
15934
16039
 
15935
16040
  // modules/server/setup.ts
15936
16041
  init_globals();
16042
+ function setupStaticFiles(app, projectRoot, config) {
16043
+ if (!config) return;
16044
+ const staticDir = getStaticDir(projectRoot, config);
16045
+ if (import_fs20.default.existsSync(staticDir)) {
16046
+ app.use(
16047
+ import_express.default.static(staticDir, {
16048
+ // In production, add caching headers for better performance
16049
+ maxAge: process.env.NODE_ENV === "production" ? "1y" : 0,
16050
+ immutable: process.env.NODE_ENV === "production"
16051
+ })
16052
+ );
16053
+ }
16054
+ }
15937
16055
  function setupServer(app, options) {
15938
16056
  const { projectRoot, appDir, isDev, config } = options;
16057
+ setupStaticFiles(app, projectRoot, config);
15939
16058
  const routeLoader = isDev ? new FilesystemRouteLoader(appDir, projectRoot) : new ManifestRouteLoader(projectRoot);
15940
16059
  if (isDev) {
15941
16060
  let getRoutes2 = function() {
@@ -17498,7 +17617,7 @@ var setupApplication = async ({
17498
17617
  // src/server.ts
17499
17618
  var import_dotenv2 = __toESM(require("dotenv"));
17500
17619
  var envPath = import_path31.default.join(process.cwd(), ".env");
17501
- if (import_fs20.default.existsSync(envPath)) {
17620
+ if (import_fs21.default.existsSync(envPath)) {
17502
17621
  import_dotenv2.default.config({ path: envPath });
17503
17622
  } else {
17504
17623
  import_dotenv2.default.config();
@@ -17520,7 +17639,7 @@ async function startServer(options = {}) {
17520
17639
  const port = options.port ?? (process.env.PORT ? parseInt(process.env.PORT, 10) : void 0) ?? config.server.port;
17521
17640
  const host = process.env.HOST ?? (!isDev ? "0.0.0.0" : void 0) ?? config.server.host;
17522
17641
  const appDir = options.appDir ?? (isDev ? getAppDir(projectRoot, config) : import_path31.default.join(getBuildDir(projectRoot, config), "server"));
17523
- if (!isDev && !import_fs20.default.existsSync(appDir)) {
17642
+ if (!isDev && !import_fs21.default.existsSync(appDir)) {
17524
17643
  logger4.error("Compiled directory not found", void 0, {
17525
17644
  buildDir: config.directories.build,
17526
17645
  appDir,