bertui 1.0.3 → 1.1.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.
@@ -1,16 +1,36 @@
1
+ // bertui/src/config/defaultConfig.js
2
+ // Default configuration used when bertui.config.js is not present
3
+
1
4
  export const defaultConfig = {
5
+ // Site information (used for sitemap generation)
6
+ siteName: "BertUI App",
7
+ baseUrl: "http://localhost:3000", // Default to localhost
8
+
9
+ // HTML Meta Tags (SEO)
2
10
  meta: {
3
- title: "BertUI App",
4
- description: "Built with BertUI - Lightning fast React development",
5
- keywords: "react, bun, bertui",
11
+ title: "BertUI - Lightning Fast React",
12
+ description: "Build lightning-fast React applications with file-based routing powered by Bun",
13
+ keywords: "react, bun, bertui, fast, file-based routing",
6
14
  author: "Pease Ernest",
7
- ogImage: "/og-image.png",
8
- themeColor: "#f30606ff",
9
- lang: "en"
15
+ themeColor: "#667eea",
16
+ lang: "en",
17
+
18
+ // Open Graph for social sharing
19
+ ogTitle: "BertUI - Lightning Fast React Framework",
20
+ ogDescription: "Build lightning-fast React apps with zero config",
21
+ ogImage: "/og-image.png"
10
22
  },
23
+
24
+ // App Shell Configuration
11
25
  appShell: {
12
26
  loading: true,
13
27
  loadingText: "Loading...",
14
28
  backgroundColor: "#ffffff"
29
+ },
30
+
31
+ // robots.txt Configuration
32
+ robots: {
33
+ disallow: [], // No paths blocked by default
34
+ crawlDelay: null // No crawl delay by default
15
35
  }
16
36
  };
@@ -1,4 +1,4 @@
1
- // src/config/loadConfig.js
1
+ // src/config/loadConfig.js - COMPLETE CORRECTED VERSION
2
2
  import { join } from 'path';
3
3
  import { existsSync } from 'fs';
4
4
  import { defaultConfig } from './defaultConfig.js';
@@ -13,6 +13,13 @@ export async function loadConfig(root) {
13
13
  const userConfig = await import(configPath);
14
14
  logger.success('Loaded bertui.config.js');
15
15
 
16
+ // DEBUG: Show what we loaded
17
+ logger.info(`📋 Config loaded: ${JSON.stringify({
18
+ hasSiteName: !!(userConfig.default?.siteName || userConfig.siteName),
19
+ hasBaseUrl: !!(userConfig.default?.baseUrl || userConfig.baseUrl),
20
+ hasRobots: !!(userConfig.default?.robots || userConfig.robots)
21
+ })}`);
22
+
16
23
  // Merge user config with defaults
17
24
  return mergeConfig(defaultConfig, userConfig.default || userConfig);
18
25
  } catch (error) {
@@ -26,8 +33,17 @@ export async function loadConfig(root) {
26
33
  }
27
34
 
28
35
  function mergeConfig(defaults, user) {
29
- return {
30
- meta: { ...defaults.meta, ...(user.meta || {}) },
31
- appShell: { ...defaults.appShell, ...(user.appShell || {}) }
32
- };
36
+ // Start with user config (so user values override defaults)
37
+ const merged = { ...user };
38
+
39
+ // Deep merge for nested objects
40
+ merged.meta = { ...defaults.meta, ...(user.meta || {}) };
41
+ merged.appShell = { ...defaults.appShell, ...(user.appShell || {}) };
42
+ merged.robots = { ...defaults.robots, ...(user.robots || {}) };
43
+
44
+ // Ensure we have required top-level fields
45
+ if (!merged.siteName) merged.siteName = defaults.siteName;
46
+ if (!merged.baseUrl) merged.baseUrl = defaults.baseUrl;
47
+
48
+ return merged;
33
49
  }
@@ -1,12 +1,27 @@
1
+ // src/router/Router.js - SSR COMPATIBLE VERSION
1
2
  import { useState, useEffect, createContext, useContext } from 'react';
2
3
 
3
4
  const RouterContext = createContext(null);
4
5
 
6
+ // ✅ FIX: SSR-safe useRouter
5
7
  export function useRouter() {
6
8
  const context = useContext(RouterContext);
9
+
10
+ // During SSR (when window doesn't exist), return a mock router
11
+ if (typeof window === 'undefined') {
12
+ return {
13
+ pathname: '/',
14
+ params: {},
15
+ navigate: () => {},
16
+ currentRoute: null,
17
+ isSSR: true
18
+ };
19
+ }
20
+
7
21
  if (!context) {
8
22
  throw new Error('useRouter must be used within a Router component');
9
23
  }
24
+
10
25
  return context;
11
26
  }
12
27
 
@@ -62,15 +77,18 @@ export function Router({ routes }) {
62
77
  }
63
78
 
64
79
  function navigate(path) {
65
- window.history.pushState({}, '', path);
66
- matchAndSetRoute(path);
80
+ if (typeof window !== 'undefined') {
81
+ window.history.pushState({}, '', path);
82
+ matchAndSetRoute(path);
83
+ }
67
84
  }
68
85
 
69
86
  const routerValue = {
70
87
  currentRoute,
71
88
  params,
72
89
  navigate,
73
- pathname: window.location.pathname
90
+ pathname: typeof window !== 'undefined' ? window.location.pathname : '/',
91
+ isSSR: typeof window === 'undefined'
74
92
  };
75
93
 
76
94
  const Component = currentRoute?.component;
@@ -82,12 +100,27 @@ export function Router({ routes }) {
82
100
  );
83
101
  }
84
102
 
103
+ // ✅ FIX: SSR-safe Link component
85
104
  export function Link({ to, children, ...props }) {
86
- const { navigate } = useRouter();
105
+ // Try to get router, but don't fail if it doesn't exist
106
+ let router;
107
+ try {
108
+ router = useRouter();
109
+ } catch (e) {
110
+ // During SSR, router might not be available
111
+ router = null;
112
+ }
87
113
 
88
114
  function handleClick(e) {
115
+ // During SSR, just use normal link behavior
116
+ if (typeof window === 'undefined') return;
117
+
118
+ // If no router or navigate function, use normal link
119
+ if (!router || !router.navigate) return;
120
+
121
+ // Only prevent default if we have client-side routing
89
122
  e.preventDefault();
90
- navigate(to);
123
+ router.navigate(to);
91
124
  }
92
125
 
93
126
  return (
@@ -0,0 +1,156 @@
1
+ // src/router/SSRRouter.jsx
2
+ // SSR-Compatible Router for BertUI - Works during build AND runtime
3
+ import React, { useState, useEffect, createContext, useContext } from 'react';
4
+
5
+ const RouterContext = createContext(null);
6
+
7
+ // ✅ SSR-safe useRouter hook
8
+ export function useRouter() {
9
+ const context = useContext(RouterContext);
10
+
11
+ // During SSR, provide a mock router
12
+ if (!context) {
13
+ return {
14
+ pathname: '/',
15
+ params: {},
16
+ navigate: () => {},
17
+ isSSR: true
18
+ };
19
+ }
20
+
21
+ return context;
22
+ }
23
+
24
+ // ✅ SSR-safe Router component
25
+ export function Router({ routes, initialPath = '/' }) {
26
+ const [currentRoute, setCurrentRoute] = useState(null);
27
+ const [params, setParams] = useState({});
28
+ const [isClient, setIsClient] = useState(false);
29
+
30
+ // Detect if we're in the browser
31
+ useEffect(() => {
32
+ setIsClient(true);
33
+ matchAndSetRoute(window.location.pathname);
34
+
35
+ const handlePopState = () => {
36
+ matchAndSetRoute(window.location.pathname);
37
+ };
38
+
39
+ window.addEventListener('popstate', handlePopState);
40
+ return () => window.removeEventListener('popstate', handlePopState);
41
+ }, [routes]);
42
+
43
+ // Match route on server-side (SSR)
44
+ if (!isClient && !currentRoute) {
45
+ const matched = matchRoute(initialPath, routes);
46
+ if (matched) {
47
+ return React.createElement(
48
+ RouterContext.Provider,
49
+ { value: { pathname: initialPath, params: matched.params, navigate: () => {}, isSSR: true } },
50
+ React.createElement(matched.route.component, { params: matched.params })
51
+ );
52
+ }
53
+ }
54
+
55
+ function matchRoute(pathname, routesList) {
56
+ // Try static routes first
57
+ for (const route of routesList) {
58
+ if (route.type === 'static' && route.path === pathname) {
59
+ return { route, params: {} };
60
+ }
61
+ }
62
+
63
+ // Try dynamic routes
64
+ for (const route of routesList) {
65
+ if (route.type === 'dynamic') {
66
+ const pattern = route.path.replace(/\[([^\]]+)\]/g, '([^/]+)');
67
+ const regex = new RegExp('^' + pattern + '$');
68
+ const match = pathname.match(regex);
69
+
70
+ if (match) {
71
+ const paramNames = [...route.path.matchAll(/\[([^\]]+)\]/g)].map(m => m[1]);
72
+ const extractedParams = {};
73
+ paramNames.forEach((name, i) => {
74
+ extractedParams[name] = match[i + 1];
75
+ });
76
+
77
+ return { route, params: extractedParams };
78
+ }
79
+ }
80
+ }
81
+
82
+ return null;
83
+ }
84
+
85
+ function matchAndSetRoute(pathname) {
86
+ const matched = matchRoute(pathname, routes);
87
+
88
+ if (matched) {
89
+ setCurrentRoute(matched.route);
90
+ setParams(matched.params);
91
+ } else {
92
+ setCurrentRoute(null);
93
+ setParams({});
94
+ }
95
+ }
96
+
97
+ function navigate(path) {
98
+ if (typeof window !== 'undefined') {
99
+ window.history.pushState({}, '', path);
100
+ matchAndSetRoute(path);
101
+ }
102
+ }
103
+
104
+ const routerValue = {
105
+ currentRoute,
106
+ params,
107
+ navigate,
108
+ pathname: typeof window !== 'undefined' ? window.location.pathname : initialPath,
109
+ isSSR: !isClient
110
+ };
111
+
112
+ const Component = currentRoute?.component;
113
+
114
+ return React.createElement(
115
+ RouterContext.Provider,
116
+ { value: routerValue },
117
+ Component ? React.createElement(Component, { params }) : React.createElement(NotFound, null)
118
+ );
119
+ }
120
+
121
+ // ✅ SSR-safe Link component
122
+ export function Link({ to, children, ...props }) {
123
+ const { navigate, isSSR } = useRouter();
124
+
125
+ function handleClick(e) {
126
+ // Don't prevent default during SSR
127
+ if (isSSR) return;
128
+
129
+ e.preventDefault();
130
+ navigate(to);
131
+ }
132
+
133
+ return React.createElement('a', { href: to, onClick: handleClick, ...props }, children);
134
+ }
135
+
136
+ function NotFound() {
137
+ return React.createElement(
138
+ 'div',
139
+ {
140
+ style: {
141
+ display: 'flex',
142
+ flexDirection: 'column',
143
+ alignItems: 'center',
144
+ justifyContent: 'center',
145
+ minHeight: '100vh',
146
+ fontFamily: 'system-ui'
147
+ }
148
+ },
149
+ React.createElement('h1', { style: { fontSize: '6rem', margin: 0 } }, '404'),
150
+ React.createElement('p', { style: { fontSize: '1.5rem', color: '#666' } }, 'Page not found'),
151
+ React.createElement('a', {
152
+ href: '/',
153
+ style: { color: '#10b981', textDecoration: 'none', fontSize: '1.2rem' }
154
+ }, 'Go home')
155
+ );
156
+ }
@@ -0,0 +1,61 @@
1
+ // bertui/src/utils/meta-extractor.js
2
+ export function extractMetaFromSource(code) {
3
+ try {
4
+ if (!code.includes('export const meta')) {
5
+ return null;
6
+ }
7
+
8
+ const metaStart = code.indexOf('export const meta = {');
9
+ if (metaStart === -1) return null;
10
+
11
+ let braceCount = 0;
12
+ let inString = false;
13
+ let stringChar = '';
14
+ let metaEnd = -1;
15
+
16
+ for (let i = metaStart + 'export const meta = {'.length; i < code.length; i++) {
17
+ const char = code[i];
18
+ const prevChar = i > 0 ? code[i - 1] : '';
19
+
20
+ if (!inString && (char === '"' || char === "'" || char === '`')) {
21
+ inString = true;
22
+ stringChar = char;
23
+ } else if (inString && char === stringChar && prevChar !== '\\') {
24
+ inString = false;
25
+ }
26
+
27
+ if (!inString) {
28
+ if (char === '{') braceCount++;
29
+ if (char === '}') {
30
+ if (braceCount === 0) {
31
+ metaEnd = i;
32
+ break;
33
+ }
34
+ braceCount--;
35
+ }
36
+ }
37
+ }
38
+
39
+ if (metaEnd === -1) return null;
40
+
41
+ const metaString = code.substring(metaStart + 'export const meta = {'.length - 1, metaEnd + 1);
42
+ const meta = {};
43
+ const pairs = metaString.match(/(\w+)\s*:\s*(['"`][^'"`]*['"`])/g) || [];
44
+
45
+ pairs.forEach(pair => {
46
+ const colonIndex = pair.indexOf(':');
47
+ if (colonIndex === -1) return;
48
+
49
+ const key = pair.substring(0, colonIndex).trim();
50
+ const value = pair.substring(colonIndex + 1).trim().slice(1, -1);
51
+
52
+ if (key && value) {
53
+ meta[key] = value;
54
+ }
55
+ });
56
+
57
+ return Object.keys(meta).length > 0 ? meta : null;
58
+ } catch (error) {
59
+ return null;
60
+ }
61
+ }
@@ -0,0 +1,80 @@
1
+ // bertui/types/config.d.ts
2
+ declare module 'bertui/config' {
3
+ /**
4
+ * BertUI Configuration
5
+ */
6
+ export interface BertuiConfig {
7
+ /** Site name for SEO */
8
+ siteName?: string;
9
+
10
+ /** Base URL for sitemap generation (e.g., "https://example.com") */
11
+ baseUrl?: string;
12
+
13
+ /** HTML meta tags configuration */
14
+ meta?: {
15
+ /** Page title */
16
+ title?: string;
17
+ /** Meta description */
18
+ description?: string;
19
+ /** Meta keywords */
20
+ keywords?: string;
21
+ /** Author name */
22
+ author?: string;
23
+ /** Open Graph image URL */
24
+ ogImage?: string;
25
+ /** Open Graph title */
26
+ ogTitle?: string;
27
+ /** Open Graph description */
28
+ ogDescription?: string;
29
+ /** Theme color */
30
+ themeColor?: string;
31
+ /** Language code (e.g., "en") */
32
+ lang?: string;
33
+ };
34
+
35
+ /** App shell configuration */
36
+ appShell?: {
37
+ /** Show loading indicator */
38
+ loading?: boolean;
39
+ /** Loading text */
40
+ loadingText?: string;
41
+ /** Background color */
42
+ backgroundColor?: string;
43
+ };
44
+
45
+ /** robots.txt configuration */
46
+ robots?: {
47
+ /** Paths to disallow in robots.txt */
48
+ disallow?: string[];
49
+ /** Crawl delay in seconds */
50
+ crawlDelay?: number;
51
+ };
52
+ }
53
+
54
+ /**
55
+ * Page meta configuration (exported from page files)
56
+ */
57
+ export interface PageMeta {
58
+ title?: string;
59
+ description?: string;
60
+ keywords?: string;
61
+ author?: string;
62
+ ogTitle?: string;
63
+ ogDescription?: string;
64
+ ogImage?: string;
65
+ themeColor?: string;
66
+ lang?: string;
67
+ publishedDate?: string;
68
+ updatedDate?: string;
69
+ }
70
+
71
+ /**
72
+ * Default BertUI configuration
73
+ */
74
+ export const defaultConfig: BertuiConfig;
75
+
76
+ /**
77
+ * Load BertUI configuration from bertui.config.js
78
+ */
79
+ export function loadConfig(root: string): Promise<BertuiConfig>;
80
+ }
@@ -0,0 +1,116 @@
1
+ // bertui/types/index.d.ts
2
+
3
+ declare namespace React {
4
+ type ReactNode = any;
5
+ }
6
+
7
+ declare global {
8
+ namespace JSX {
9
+ type Element = any;
10
+ interface IntrinsicElements {
11
+ [elemName: string]: any;
12
+ }
13
+ }
14
+ }
15
+ declare module 'bertui' {
16
+ import { BertuiConfig } from 'bertui/config';
17
+
18
+ /**
19
+ * Logger utility
20
+ */
21
+ export interface Logger {
22
+ info(message: string): void;
23
+ success(message: string): void;
24
+ warn(message: string): void;
25
+ error(message: string): void;
26
+ debug(message: string): void;
27
+ bigLog(message: string, options?: { color?: string }): void;
28
+ table(data: any[]): void;
29
+ }
30
+
31
+ /**
32
+ * Build options
33
+ */
34
+ export interface BuildOptions {
35
+ /** Project root directory */
36
+ root?: string;
37
+ }
38
+
39
+ /**
40
+ * Dev server options
41
+ */
42
+ export interface DevOptions {
43
+ /** Server port */
44
+ port?: number;
45
+ /** Project root directory */
46
+ root?: string;
47
+ }
48
+
49
+ /**
50
+ * Compile options
51
+ */
52
+ export interface CompileOptions {
53
+ /** Project root directory */
54
+ root?: string;
55
+ }
56
+
57
+ /**
58
+ * Logger instance
59
+ */
60
+ export const logger: Logger;
61
+
62
+ /**
63
+ * Default configuration
64
+ */
65
+ export const defaultConfig: BertuiConfig;
66
+
67
+ /**
68
+ * Load configuration
69
+ */
70
+ export function loadConfig(root: string): Promise<BertuiConfig>;
71
+
72
+ /**
73
+ * Start development server
74
+ */
75
+ export function startDev(options?: DevOptions): Promise<void>;
76
+
77
+ /**
78
+ * Build for production
79
+ */
80
+ export function buildProduction(options?: BuildOptions): Promise<void>;
81
+
82
+ /**
83
+ * Compile project
84
+ */
85
+ export function compileProject(root: string): Promise<{
86
+ outDir: string;
87
+ stats: { files: number; skipped: number };
88
+ routes: any[];
89
+ }>;
90
+
91
+ /**
92
+ * CLI program
93
+ */
94
+ export function program(): void;
95
+
96
+ /**
97
+ * BertUI version
98
+ */
99
+ export const version: string;
100
+ }
101
+
102
+ // Global declarations for Server Islands
103
+ declare global {
104
+ /**
105
+ * Mark a page component as a Server Island (SSG)
106
+ * Add this export to any page to enable static generation:
107
+ *
108
+ * @example
109
+ * ```tsx
110
+ * export const render = "server";
111
+ * ```
112
+ */
113
+ export const render: "server" | "client";
114
+ }
115
+
116
+ export {};
@@ -0,0 +1,13 @@
1
+ // bertui/types/react.d.ts
2
+ import React from 'react';
3
+
4
+ declare global {
5
+ namespace JSX {
6
+ interface IntrinsicElements {
7
+ [elemName: string]: React.DetailedHTMLProps<
8
+ React.HTMLAttributes<HTMLElement>,
9
+ HTMLElement
10
+ >;
11
+ }
12
+ }
13
+ }
@@ -0,0 +1,79 @@
1
+ // bertui/types/router.d.ts
2
+ declare module 'bertui/router' {
3
+ import { ReactNode, ComponentType } from 'react';
4
+
5
+ /**
6
+ * Route parameter object
7
+ */
8
+ export interface RouteParams {
9
+ [key: string]: string;
10
+ }
11
+
12
+ /**
13
+ * Router context value
14
+ */
15
+ export interface RouterContext {
16
+ /** Current active route */
17
+ currentRoute: Route | null;
18
+ /** Dynamic route parameters */
19
+ params: RouteParams;
20
+ /** Navigate to a new path */
21
+ navigate: (path: string) => void;
22
+ /** Current pathname */
23
+ pathname: string;
24
+ /** Whether running in SSR mode */
25
+ isSSR?: boolean;
26
+ }
27
+
28
+ /**
29
+ * Route configuration object
30
+ */
31
+ export interface Route {
32
+ /** Route path (e.g., "/", "/blog", "/user/:id") */
33
+ path: string;
34
+ /** React component to render */
35
+ component: ComponentType<{ params?: RouteParams }>;
36
+ /** Route type: "static" or "dynamic" */
37
+ type: 'static' | 'dynamic';
38
+ }
39
+
40
+ /**
41
+ * Router component props
42
+ */
43
+ export interface RouterProps {
44
+ /** Array of route configurations */
45
+ routes: Route[];
46
+ /** Initial path for SSR */
47
+ initialPath?: string;
48
+ }
49
+
50
+ /**
51
+ * Link component props
52
+ */
53
+ export interface LinkProps extends React.AnchorHTMLAttributes<HTMLAnchorElement> {
54
+ /** Destination path */
55
+ to: string;
56
+ /** Link content */
57
+ children: ReactNode;
58
+ }
59
+
60
+ /**
61
+ * Router component for client-side routing
62
+ */
63
+ export const Router: ComponentType<RouterProps>;
64
+
65
+ /**
66
+ * Link component for navigation
67
+ */
68
+ export const Link: ComponentType<LinkProps>;
69
+
70
+ /**
71
+ * Hook to access router context
72
+ */
73
+ export function useRouter(): RouterContext;
74
+
75
+ /**
76
+ * Exported routes configuration
77
+ */
78
+ export const routes: Route[];
79
+ }